From fe331e7bb3d69cee87183343935cb7b47ecd48cc Mon Sep 17 00:00:00 2001 From: shaulascr Date: Tue, 17 Jun 2025 00:18:15 +0700 Subject: [PATCH] update display product --- app/src/main/AndroidManifest.xml | 3 + .../ui/home/HomeCategoryAdapter.kt | 6 +- .../ecommerce_serang/ui/home/HomeFragment.kt | 92 ++++++++---- .../ui/home/HorizontalProductAdapter.kt | 25 ++-- .../ui/home/SearchHomeFragment.kt | 20 ++- .../ui/home/SearchHomeViewModel.kt | 42 +++++- .../ui/home/SearchResultAdapter.kt | 21 ++- .../ui/product/DetailProductActivity.kt | 29 ++-- .../ui/product/OtherProductAdapter.kt | 94 ++++++++++++ .../ui/product/ProductUserViewModel.kt | 45 ++++++ .../category/ProductsCategoryAdapter.kt | 17 +-- .../listproduct/ListProductActivity.kt | 133 +++++++++++++++++ .../product/listproduct/ListProductAdapter.kt | 85 +++++++++++ .../storeDetail/StoreDetailActivity.kt | 24 ++- .../storeDetail/StoreDetailViewModel.kt | 25 ++++ .../utils/viewmodel/HomeViewModel.kt | 26 ++++ .../res/layout/activity_detail_product.xml | 2 +- .../main/res/layout/activity_list_product.xml | 39 +++++ app/src/main/res/layout/fragment_home.xml | 29 ++-- .../main/res/layout/fragment_search_home.xml | 1 + .../main/res/layout/item_category_home.xml | 63 +++++--- app/src/main/res/layout/item_product_grid.xml | 24 ++- .../res/layout/item_product_horizontal.xml | 141 +++++++++++------- .../res/layout/item_section_horizontal.xml | 5 +- app/src/main/res/values/styles.xml | 13 ++ 25 files changed, 816 insertions(+), 188 deletions(-) create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/product/OtherProductAdapter.kt create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListProductActivity.kt create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListProductAdapter.kt create mode 100644 app/src/main/res/layout/activity_list_product.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ec2e571..428d94c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,6 +29,9 @@ android:theme="@style/Theme.Ecommerce_serang" android:usesCleartextTraffic="true" tools:targetApi="31"> + diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeCategoryAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeCategoryAdapter.kt index 0e9a0bd..5bdc7dc 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeCategoryAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeCategoryAdapter.kt @@ -16,10 +16,6 @@ class HomeCategoryAdapter( private val onClick:(category:CategoryItem) -> Unit ): RecyclerView.Adapter() { - /* - 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: CategoryItem) = with(binding) { Log.d("CategoriesAdapter", "Binding category: ${category.name}, Image: ${category.image}") @@ -59,7 +55,7 @@ class HomeCategoryAdapter( } fun updateLimitedCategory(newCategories: List){ - val limitedCategories = newCategories.take(10) + val limitedCategories = newCategories.take(9) updateData(limitedCategories) } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeFragment.kt index db00d1b..2c6cc9d 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeFragment.kt @@ -14,10 +14,11 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.findNavController +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager -import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.dto.CategoryItem import com.alya.ecommerce_serang.data.api.dto.ProductsItem +import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.databinding.FragmentHomeBinding @@ -25,8 +26,8 @@ import com.alya.ecommerce_serang.ui.cart.CartActivity import com.alya.ecommerce_serang.ui.notif.NotificationActivity import com.alya.ecommerce_serang.ui.product.DetailProductActivity import com.alya.ecommerce_serang.ui.product.category.CategoryProductsActivity +import com.alya.ecommerce_serang.ui.product.listproduct.ListProductActivity import com.alya.ecommerce_serang.utils.BaseViewModelFactory -import com.alya.ecommerce_serang.utils.HorizontalMarginItemDecoration import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.setLightStatusBar import com.alya.ecommerce_serang.utils.viewmodel.HomeUiState @@ -75,11 +76,6 @@ class HomeFragment : Fragment() { } private fun setupRecyclerView() { - productAdapter = HorizontalProductAdapter( - products = emptyList(), - onClick = { product -> handleProductClick(product) } - ) - categoryAdapter = HomeCategoryAdapter( categories = emptyList(), onClick = { category -> handleCategoryProduct(category)} @@ -87,8 +83,9 @@ class HomeFragment : Fragment() { binding.newProducts.apply { adapter = productAdapter - layoutManager = LinearLayoutManager( + layoutManager = GridLayoutManager( context, + 2, LinearLayoutManager.HORIZONTAL, false ) @@ -96,12 +93,18 @@ class HomeFragment : Fragment() { binding.categories.apply { adapter = categoryAdapter - layoutManager = LinearLayoutManager( + layoutManager = GridLayoutManager( context, + 3, LinearLayoutManager.HORIZONTAL, false ) } + + binding.productshowAll.setOnClickListener { + val intent = Intent(requireContext(), ListProductActivity::class.java) + startActivity(intent) + } } private fun setupSearchView() { @@ -155,7 +158,10 @@ class HomeFragment : Fragment() { binding.loading.root.isVisible = false binding.error.root.isVisible = false binding.home.isVisible = true - productAdapter?.updateLimitedProducts(state.products) + val products = state.products + viewModel.loadStoresForProducts(products) // << add this here + + productAdapter?.updateLimitedProducts(products) } is HomeUiState.Error -> { binding.loading.root.isVisible = false @@ -168,7 +174,9 @@ class HomeFragment : Fragment() { } } } + } + } viewLifecycleOwner.lifecycleScope.launch { @@ -179,30 +187,56 @@ class HomeFragment : Fragment() { } } } + + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.storeMap.collect { storeMap -> + val products = (viewModel.uiState.value as? HomeUiState.Success)?.products.orEmpty() + if (products.isNotEmpty()) { + updateProducts(products, storeMap) + } + } + } + } + } + + private fun updateProducts(products: List, storeMap: Map) { + if (products.isEmpty()) { + Log.d("HomeFragment", "Product list is empty, hiding RecyclerView") + binding.newProducts.visibility = View.VISIBLE + } else { + Log.d("HomeFragment", "Displaying product list in RecyclerView") + binding.newProducts.visibility = View.VISIBLE // <-- Fix here + productAdapter = HorizontalProductAdapter(products, onClick = { product -> + handleProductClick(product) + }, storeMap = storeMap) + binding.newProducts.adapter = productAdapter + productAdapter?.updateProducts(products) + } } 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 - ) - ) +// 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) { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/home/HorizontalProductAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/home/HorizontalProductAdapter.kt index 7c82da7..55792f9 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/home/HorizontalProductAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/HorizontalProductAdapter.kt @@ -8,15 +8,17 @@ import androidx.recyclerview.widget.RecyclerView import com.alya.ecommerce_serang.BuildConfig.BASE_URL import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.dto.ProductsItem -import com.alya.ecommerce_serang.databinding.ItemProductHorizontalBinding +import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem +import com.alya.ecommerce_serang.databinding.ItemProductGridBinding import com.bumptech.glide.Glide class HorizontalProductAdapter( private var products: List, - private val onClick: (ProductsItem) -> Unit + private val onClick: (ProductsItem) -> Unit, + private val storeMap: Map ) : RecyclerView.Adapter() { - inner class ProductViewHolder(private val binding: ItemProductHorizontalBinding) : + inner class ProductViewHolder(private val binding: ItemProductGridBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(product: ProductsItem) = with(binding) { @@ -29,22 +31,25 @@ class HorizontalProductAdapter( Log.d("ProductAdapter", "Loading image: $fullImageUrl") - itemName.text = product.name - itemPrice.text = product.price + tvProductName.text = product.name + tvProductPrice.text = product.price rating.text = product.rating // Load image using Glide Glide.with(itemView) .load(fullImageUrl) .placeholder(R.drawable.placeholder_image) - .into(imageProduct) + .into(ivProductImage) + + val storeName = product.storeId?.let { storeMap[it]?.storeName } ?: "Unknown Store" + binding.tvStoreName.text = storeName root.setOnClickListener { onClick(product) } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder { - val binding = ItemProductHorizontalBinding.inflate( + val binding = ItemProductGridBinding.inflate( LayoutInflater.from(parent.context), parent, false ) return ProductViewHolder(binding) @@ -65,11 +70,11 @@ class HorizontalProductAdapter( } fun updateLimitedProducts(newProducts: List) { - val diffCallback = ProductDiffCallback(products, newProducts) - val limitedProducts = newProducts.take(10) //limit 10 produk + val limitedProducts = newProducts.take(10) + val diffCallback = ProductDiffCallback(products, limitedProducts) val diffResult = DiffUtil.calculateDiff(diffCallback) + products = limitedProducts diffResult.dispatchUpdatesTo(this) - updateProducts(limitedProducts) } class ProductDiffCallback( diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchHomeFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchHomeFragment.kt index 94003c6..b715d2b 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchHomeFragment.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchHomeFragment.kt @@ -117,9 +117,6 @@ class SearchHomeFragment : Fragment() { } private fun setupSearchResultsRecyclerView() { - searchResultsAdapter = SearchResultsAdapter { product -> - navigateToProductDetail(product) - } binding.searchResultsRecyclerView.apply { adapter = searchResultsAdapter @@ -130,9 +127,26 @@ class SearchHomeFragment : Fragment() { private fun observeData() { viewModel.searchResults.observe(viewLifecycleOwner) { products -> + if (!products.isNullOrEmpty()){ + viewModel.storeDetail(products) + } + searchResultsAdapter?.submitList(products) binding.noResultsText.isVisible = products.isEmpty() && !viewModel.isSearching.value!! binding.searchResultsRecyclerView.isVisible = products.isNotEmpty() + + } + + viewModel.storeDetail.observe(viewLifecycleOwner) {storeMap -> + val products = viewModel.searchResults.value.orEmpty() + if (products.isNotEmpty()){ + searchResultsAdapter = SearchResultsAdapter( + onItemClick = {product -> navigateToProductDetail(product) }, + storeMap = storeMap + ) + binding.searchResultsRecyclerView.adapter = searchResultsAdapter + searchResultsAdapter?.submitList(products) + } } viewModel.isSearching.observe(viewLifecycleOwner) { isSearching -> diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchHomeViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchHomeViewModel.kt index fec5c4e..2a62af6 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchHomeViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchHomeViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.alya.ecommerce_serang.data.api.dto.ProductsItem +import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.data.repository.Result import kotlinx.coroutines.launch @@ -15,6 +16,9 @@ class SearchHomeViewModel (private val productRepository: ProductRepository) : V private val _searchResults = MutableLiveData>(emptyList()) val searchResults: LiveData> = _searchResults + private val _storeDetail = MutableLiveData>() + val storeDetail : LiveData> get() = _storeDetail + private val _searchHistory = MutableLiveData>(emptyList()) val searchHistory: LiveData> = _searchHistory @@ -25,10 +29,10 @@ class SearchHomeViewModel (private val productRepository: ProductRepository) : V val isSearchActive: LiveData = _isSearchActive fun searchProducts(query: String) { - Log.d("HomeViewModel", "searchProducts called with query: '$query'") + Log.d("SearchHomeViewModel", "searchProducts called with query: '$query'") if (query.isBlank()) { - Log.d("HomeViewModel", "Query is blank, clearing results") + Log.d("SearchHomeViewModel", "Query is blank, clearing results") _searchResults.value = emptyList() _isSearchActive.value = false return @@ -38,18 +42,18 @@ class SearchHomeViewModel (private val productRepository: ProductRepository) : V _isSearchActive.value = true viewModelScope.launch { - Log.d("HomeViewModel", "Starting search coroutine") + Log.d("SearchHomeViewModeldel", "Starting search coroutine") when (val result = productRepository.searchProducts(query)) { is Result.Success -> { - Log.d("HomeViewModel", "Search successful, found ${result.data.size} products") + Log.d("SearchHomeViewModel", "Search successful, found ${result.data.size} products") _searchResults.postValue(result.data) // Double check the state after assignment - Log.d("HomeViewModel", "Updated searchResults value has ${result.data.size} items") + Log.d("SearchHomeViewModel", "Updated searchResults value has ${result.data.size} items") } is Result.Error -> { - Log.e("HomeViewModel", "Search failed", result.exception) + Log.e("SearchHomeViewModel", "Search failed", result.exception) _searchResults.postValue(emptyList()) } else -> {} @@ -58,6 +62,30 @@ class SearchHomeViewModel (private val productRepository: ProductRepository) : V } } + fun storeDetail(products: List){ + viewModelScope.launch { + val map = mutableMapOf() + + val storeIds = products.mapNotNull { it.storeId }.toSet() + for (storeId in storeIds){ + try { + when (val result = productRepository.fetchStoreDetail(storeId)){ + is Result.Success -> map[storeId] = result.data + is Result.Error -> Log.e("SearchHomeViewModel", "Error Loading Store") + else -> {} + } + } catch (e: Exception){ + Log.e("SearchHomeViewModel", "Exception error for storeId: $storeId", e) + } + } + _storeDetail.value = map + } + } + + fun loadStoreDetail(storeId: Int) { + + } + fun clearSearch() { _isSearchActive.value = false _searchResults.value = emptyList() @@ -68,7 +96,7 @@ class SearchHomeViewModel (private val productRepository: ProductRepository) : V viewModelScope.launch { when (val result = productRepository.getSearchHistory()) { is Result.Success -> _searchHistory.value = result.data - is Result.Error -> Log.e("HomeViewModel", "Failed to load search history", result.exception) + is Result.Error -> Log.e("SearchHomeViewModel", "Failed to load search history", result.exception) else -> {} } } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchResultAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchResultAdapter.kt index 555976f..b46a143 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchResultAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchResultAdapter.kt @@ -6,13 +6,16 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import com.alya.ecommerce_serang.BuildConfig.BASE_URL import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.dto.ProductsItem +import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem import com.alya.ecommerce_serang.databinding.ItemProductGridBinding import com.bumptech.glide.Glide class SearchResultsAdapter( - private val onItemClick: (ProductsItem) -> Unit + private val onItemClick: (ProductsItem) -> Unit, + private val storeMap: Map ) : ListAdapter(DIFF_CALLBACK) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -43,19 +46,23 @@ class SearchResultsAdapter( fun bind(product: ProductsItem) { binding.tvProductName.text = product.name - binding.tvProductPrice.text = (product.price) + binding.tvProductPrice.text = product.price + val fullImageUrl = if (product.image.startsWith("/")) { + BASE_URL + product.image.removePrefix("/") // Append base URL if the path starts with "/" + } else { + product.image // Use as is if it's already a full URL + } + Log.d("ProductAdapter", "Loading image: $fullImageUrl") // Load image with Glide Glide.with(binding.root.context) - .load(product.image) + .load(fullImageUrl) .placeholder(R.drawable.placeholder_image) // .error(R.drawable.error_image) .into(binding.ivProductImage) - // Set store name if available - product.storeId?.toString().let { - binding.tvStoreName.text = it - } + val storeName = product.storeId?.let { storeMap[it]?.storeName } ?: "Unknown Store" + binding.tvStoreName.text = storeName } } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/DetailProductActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/DetailProductActivity.kt index 3cbe27d..81fee0e 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/product/DetailProductActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/DetailProductActivity.kt @@ -31,7 +31,6 @@ import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding import com.alya.ecommerce_serang.ui.cart.CartActivity import com.alya.ecommerce_serang.ui.chat.ChatActivity -import com.alya.ecommerce_serang.ui.home.HorizontalProductAdapter import com.alya.ecommerce_serang.ui.order.CheckoutActivity import com.alya.ecommerce_serang.ui.product.storeDetail.StoreDetailActivity import com.alya.ecommerce_serang.utils.BaseViewModelFactory @@ -45,7 +44,7 @@ class DetailProductActivity : AppCompatActivity() { private lateinit var binding: ActivityDetailProductBinding private lateinit var apiService: ApiService private lateinit var sessionManager: SessionManager - private var productAdapter: HorizontalProductAdapter? = null + private var productAdapter: OtherProductAdapter? = null private var reviewsAdapter: ReviewsAdapter? = null private var currentQuantity = 1 private var isWholesaleAvailable: Boolean = false @@ -125,7 +124,8 @@ class DetailProductActivity : AppCompatActivity() { } viewModel.otherProducts.observe(this) { products -> - updateOtherProducts(products) + viewModel.loadStoresForProducts(products) +// updateOtherProducts(products) } viewModel.reviewProduct.observe(this) { reviews -> @@ -155,6 +155,13 @@ class DetailProductActivity : AppCompatActivity() { } } } + + viewModel.storeMap.observe(this){ storeMap -> + val products = viewModel.otherProducts.value.orEmpty() + if (products.isNotEmpty()) { + updateOtherProducts(products, storeMap) + } + } } private fun updateStoreInfo(store: StoreItem?) { @@ -178,13 +185,21 @@ class DetailProductActivity : AppCompatActivity() { } } - private fun updateOtherProducts(products: List) { + + private fun updateOtherProducts(products: List, storeMap: Map) { if (products.isEmpty()) { - binding.recyclerViewOtherProducts.visibility = View.GONE + Log.d("DetailProductActivity", "Product list is empty, hiding RecyclerView") + binding.recyclerViewOtherProducts.visibility = View.VISIBLE binding.tvViewAllProducts.visibility = View.GONE } else { + Log.d("DetailProductActivity", "Displaying product list in RecyclerView") binding.recyclerViewOtherProducts.visibility = View.VISIBLE binding.tvViewAllProducts.visibility = View.VISIBLE + + productAdapter = OtherProductAdapter(products, onClick = { product -> + handleProductClick(product) + }, storeMap = storeMap) + binding.recyclerViewOtherProducts.adapter = productAdapter productAdapter?.updateProducts(products) } } @@ -275,10 +290,6 @@ class DetailProductActivity : AppCompatActivity() { } private fun setupRecyclerViewOtherProducts(){ - productAdapter = HorizontalProductAdapter( - products = emptyList(), - onClick = { productsItem -> handleProductClick(productsItem) } - ) binding.recyclerViewOtherProducts.apply { adapter = productAdapter diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/OtherProductAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/OtherProductAdapter.kt new file mode 100644 index 0000000..76a9de9 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/OtherProductAdapter.kt @@ -0,0 +1,94 @@ +package com.alya.ecommerce_serang.ui.product + +import android.util.Log +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.alya.ecommerce_serang.BuildConfig.BASE_URL +import com.alya.ecommerce_serang.R +import com.alya.ecommerce_serang.data.api.dto.ProductsItem +import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem +import com.alya.ecommerce_serang.databinding.ItemProductHorizontalBinding +import com.bumptech.glide.Glide + +class OtherProductAdapter ( + private var products: List, + private val onClick: (ProductsItem) -> Unit, + private val storeMap: Map + ) : RecyclerView.Adapter() { + + inner class ProductViewHolder(private val binding: ItemProductHorizontalBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(product: ProductsItem) = with(binding) { + + val fullImageUrl = if (product.image.startsWith("/")) { + BASE_URL + product.image.removePrefix("/") // Append base URL if the path starts with "/" + } else { + product.image // Use as is if it's already a full URL + } + + Log.d("ProductAdapter", "Loading image: $fullImageUrl") + + tvProductName.text = product.name + tvProductPrice.text = product.price + rating.text = product.rating + + // Load image using Glide + Glide.with(itemView) + .load(fullImageUrl) + .placeholder(R.drawable.placeholder_image) + .into(ivProductImage) + + val storeName = product.storeId?.let { storeMap[it]?.storeName } ?: "Unknown Store" + binding.tvStoreName.text = storeName + + 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) { + val diffCallback = ProductDiffCallback(products, newProducts) + val diffResult = DiffUtil.calculateDiff(diffCallback) + products = newProducts + diffResult.dispatchUpdatesTo(this) + notifyDataSetChanged() + } + + fun updateLimitedProducts(newProducts: List) { + val limitedProducts = newProducts.take(10) + val diffCallback = ProductDiffCallback(products, limitedProducts) + val diffResult = DiffUtil.calculateDiff(diffCallback) + products = limitedProducts + diffResult.dispatchUpdatesTo(this) + } + + class ProductDiffCallback( + private val oldList: List, + private val newList: List + ) : DiffUtil.Callback() { + + override fun getOldListSize() = oldList.size + override fun getNewListSize() = newList.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition].id == newList[newItemPosition].id // Compare unique IDs + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] // Compare entire object + } + } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/ProductUserViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/ProductUserViewModel.kt index 2338cdb..38b3bc6 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/product/ProductUserViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/ProductUserViewModel.kt @@ -20,9 +20,15 @@ class ProductUserViewModel(private val repository: ProductRepository) : ViewMode private val _productDetail = MutableLiveData() val productDetail: LiveData get() = _productDetail + private val _productList = MutableLiveData>>() + val productList: LiveData>> get() = _productList + private val _storeDetail = MutableLiveData>() val storeDetail : LiveData> get() = _storeDetail + private val _storeMap = MutableLiveData>() + val storeMap: LiveData> get() = _storeMap + private val _reviewProduct = MutableLiveData>() val reviewProduct: LiveData> get() = _reviewProduct @@ -59,6 +65,23 @@ class ProductUserViewModel(private val repository: ProductRepository) : ViewMode } } + fun loadProductsList() { + _isLoading.value = true + viewModelScope.launch { + try { + val result = repository.getAllProducts() + _productList.value = result + + } catch (e: Exception) { + Log.e(TAG, "Error loading product list: ${e.message}") + _error.value = "Failed to load product list ${e.message}" + } finally { + _isLoading.value = false + } + } + } + + fun loadStoreDetail(storeId: Int) { viewModelScope.launch { try { @@ -73,6 +96,28 @@ class ProductUserViewModel(private val repository: ProductRepository) : ViewMode } } + fun loadStoresForProducts(products: List) { + viewModelScope.launch { + val map = mutableMapOf() + val storeIds = products.mapNotNull { it.storeId }.toSet() + + for (storeId in storeIds) { + try { + val result = repository.fetchStoreDetail(storeId) + if (result is Result.Success) { + map[storeId] = result.data + } else if (result is Result.Error) { + Log.e(TAG, "Failed to load storeId $storeId", result.exception) + } + } catch (e: Exception) { + Log.e(TAG, "Exception fetching storeId $storeId", e) + } + } + + _storeMap.postValue(map) + } + } + fun loadReviews(productId: Int) { viewModelScope.launch { try { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/category/ProductsCategoryAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/category/ProductsCategoryAdapter.kt index 7e82b2b..05a2ffb 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/product/category/ProductsCategoryAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/category/ProductsCategoryAdapter.kt @@ -2,7 +2,6 @@ package com.alya.ecommerce_serang.ui.product.category import android.view.LayoutInflater import android.view.ViewGroup -import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import com.alya.ecommerce_serang.BuildConfig import com.alya.ecommerce_serang.R @@ -59,14 +58,14 @@ class ProductsCategoryAdapter( onClick(product) } - // Optional: Show stock status - if (product.stock > 0) { - tvStockStatus.text = "Stock: ${product.stock}" - tvStockStatus.setTextColor(ContextCompat.getColor(itemView.context, R.color.green)) - } else { - tvStockStatus.text = "Out of Stock" - tvStockStatus.setTextColor(ContextCompat.getColor(itemView.context, R.color.red)) - } +// // Optional: Show stock status +// if (product.stock > 0) { +// tvStockStatus.text = "Stock: ${product.stock}" +// tvStockStatus.setTextColor(ContextCompat.getColor(itemView.context, R.color.green)) +// } else { +// tvStockStatus.text = "Out of Stock" +// tvStockStatus.setTextColor(ContextCompat.getColor(itemView.context, R.color.red)) +// } } } } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListProductActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListProductActivity.kt new file mode 100644 index 0000000..6a00f2e --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListProductActivity.kt @@ -0,0 +1,133 @@ +package com.alya.ecommerce_serang.ui.product.listproduct + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.View +import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import com.alya.ecommerce_serang.data.api.dto.ProductsItem +import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem +import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig +import com.alya.ecommerce_serang.data.repository.ProductRepository +import com.alya.ecommerce_serang.data.repository.Result +import com.alya.ecommerce_serang.databinding.ActivityListProductBinding +import com.alya.ecommerce_serang.ui.product.DetailProductActivity +import com.alya.ecommerce_serang.ui.product.ProductUserViewModel +import com.alya.ecommerce_serang.utils.BaseViewModelFactory +import com.alya.ecommerce_serang.utils.SessionManager + +class ListProductActivity : AppCompatActivity() { + companion object { + private val TAG = "ListProductActivity" + } + private lateinit var binding: ActivityListProductBinding + private lateinit var sessionManager: SessionManager + private var productAdapter: ListProductAdapter? = null + + private val viewModel: ProductUserViewModel by viewModels { + BaseViewModelFactory { + val apiService = ApiConfig.getApiService(sessionManager) + val repository = ProductRepository(apiService) + ProductUserViewModel(repository) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityListProductBinding.inflate(layoutInflater) + setContentView(binding.root) + + sessionManager = SessionManager(this) + + WindowCompat.setDecorFitsSystemWindows(window, false) + enableEdgeToEdge() + + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets -> + val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + view.setPadding( + systemBars.left, + systemBars.top, + systemBars.right, + systemBars.bottom + ) + windowInsets + } + + + setupObserver() + setupRecyclerView() + viewModel.loadProductsList() + + } + + private fun setupRecyclerView() { + + binding.rvProducts.apply { + adapter = productAdapter + layoutManager = GridLayoutManager( + context, + 2, + LinearLayoutManager.VERTICAL, + false + ) + } + + Log.d(TAG, "RecyclerView setup complete with GridLayoutManager") + } + + private fun setupObserver(){ + + viewModel.productList.observe(this) { result -> + when (result) { + is Result.Success -> { + val products = result.data + viewModel.loadStoresForProducts(products) + Log.d(TAG, "Product list loaded successfully: ${products.size} items") + } + is Result.Error -> { + Log.e(TAG, "Failed to load products: ${result.exception.message}") + } + is Result.Loading -> { + // Show loading indicator if needed + } + } + } + + viewModel.storeMap.observe(this){ storeMap -> + val products = (viewModel.productList.value as? Result.Success)?.data.orEmpty() + if (products.isNotEmpty()) { + updateProducts(products, storeMap) + } + } + + } + + private fun updateProducts(products: List, storeMap: Map) { + if (products.isEmpty()) { + Log.d(TAG, "Product list is empty, hiding RecyclerView") + binding.rvProducts.visibility = View.VISIBLE + } else { + Log.d(TAG, "Displaying product list in RecyclerView") + binding.rvProducts.visibility = View.VISIBLE // <-- Fix here + productAdapter = ListProductAdapter(products, onClick = { product -> + handleProductClick(product) + }, storeMap = storeMap) + binding.rvProducts.adapter = productAdapter + productAdapter?.updateProducts(products) + } + } + + private fun handleProductClick(product: ProductsItem) { + val intent = Intent(this, DetailProductActivity::class.java) + intent.putExtra("PRODUCT_ID", product.id) // Pass product ID + startActivity(intent) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListProductAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListProductAdapter.kt new file mode 100644 index 0000000..f0cb5f4 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListProductAdapter.kt @@ -0,0 +1,85 @@ +package com.alya.ecommerce_serang.ui.product.listproduct + +import android.util.Log +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.alya.ecommerce_serang.BuildConfig.BASE_URL +import com.alya.ecommerce_serang.R +import com.alya.ecommerce_serang.data.api.dto.ProductsItem +import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem +import com.alya.ecommerce_serang.databinding.ItemProductGridBinding +import com.bumptech.glide.Glide + +class ListProductAdapter( + private var products: List, + private val onClick: (ProductsItem) -> Unit, + private val storeMap: Map +): RecyclerView.Adapter() { + + inner class ProductViewHolder(private val binding: ItemProductGridBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(product: ProductsItem) = with(binding) { + + tvProductName.text = product.name + tvProductPrice.text = product.price + rating.text = product.rating + + val fullImageUrl = if (product.image.startsWith("/")) { + BASE_URL + product.image.removePrefix("/") // Append base URL if the path starts with "/" + } else { + product.image // Use as is if it's already a full URL + } + Log.d("ProductAdapter", "Loading image: $fullImageUrl") + + + // Load image using Glide + Glide.with(itemView) + .load(fullImageUrl) + .placeholder(R.drawable.placeholder_image) + .into(ivProductImage) + + val storeName = product.storeId?.let { storeMap[it]?.storeName } ?: "Unknown Store" + binding.tvStoreName.text = storeName + root.setOnClickListener { onClick(product) } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder { + val binding = ItemProductGridBinding.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) { + val diffCallback = ProductDiffCallback(products, newProducts) + val diffResult = DiffUtil.calculateDiff(diffCallback) + products = newProducts + diffResult.dispatchUpdatesTo(this) + notifyDataSetChanged() + } + + class ProductDiffCallback( + private val oldList: List, + private val newList: List + ) : DiffUtil.Callback() { + + override fun getOldListSize() = oldList.size + override fun getNewListSize() = newList.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition].id == newList[newItemPosition].id // Compare unique IDs + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] // Compare entire object + } +} \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/storeDetail/StoreDetailActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/storeDetail/StoreDetailActivity.kt index fd00b60..50ebbe2 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/product/storeDetail/StoreDetailActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/storeDetail/StoreDetailActivity.kt @@ -101,7 +101,15 @@ class StoreDetailActivity : AppCompatActivity() { } } viewModel.otherProducts.observe(this) { products -> - updateOtherProducts(products) + viewModel.loadStoresForProducts(products) +// updateOtherProducts(products) + } + + viewModel.storeMap.observe(this){ storeMap -> + val products = viewModel.otherProducts.value.orEmpty() + if (products.isNotEmpty()) { + updateProducts(products, storeMap) + } } } @@ -140,20 +148,24 @@ class StoreDetailActivity : AppCompatActivity() { } } - private fun updateOtherProducts(products: List) { + private fun updateProducts(products: List, storeMap: Map) { if (products.isEmpty()) { binding.rvProducts.visibility = View.GONE + Log.d("StoreDetailActivity", "Product list is empty, hiding RecyclerView") } else { + Log.d("StoreDetailActivity", "Displaying product list in RecyclerView") + binding.rvProducts.visibility = View.VISIBLE + productAdapter = HorizontalProductAdapter(products, onClick = { product -> + handleProductClick(product) + }, storeMap = storeMap) + binding.rvProducts.adapter = productAdapter productAdapter?.updateProducts(products) } } + private fun setupRecyclerViewOtherProducts(){ - productAdapter = HorizontalProductAdapter( - products = emptyList(), - onClick = { productsItem -> handleProductClick(productsItem) } - ) binding.rvProducts.apply { adapter = productAdapter diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/storeDetail/StoreDetailViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/storeDetail/StoreDetailViewModel.kt index 329a7df..201df5f 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/product/storeDetail/StoreDetailViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/storeDetail/StoreDetailViewModel.kt @@ -21,6 +21,9 @@ class StoreDetailViewModel (private val repository: ProductRepository private val _otherProducts = MutableLiveData>() val otherProducts: LiveData> get() = _otherProducts + private val _storeMap = MutableLiveData>() + val storeMap: LiveData> get() = _storeMap + private val _isLoading = MutableLiveData() val isLoading: LiveData get() = _isLoading @@ -84,4 +87,26 @@ class StoreDetailViewModel (private val repository: ProductRepository } } } + + fun loadStoresForProducts(products: List) { + viewModelScope.launch { + val map = mutableMapOf() + val storeIds = products.mapNotNull { it.storeId }.toSet() + + for (storeId in storeIds) { + try { + val result = repository.fetchStoreDetail(storeId) + if (result is Result.Success) { + map[storeId] = result.data + } else if (result is Result.Error) { + Log.e("ProductViewModel", "Failed to load storeId $storeId", result.exception) + } + } catch (e: Exception) { + Log.e("ProductViewModel", "Exception fetching storeId $storeId", e) + } + } + + _storeMap.postValue(map) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/HomeViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/HomeViewModel.kt index 0f0437b..a7667cf 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/HomeViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.alya.ecommerce_serang.data.api.dto.CategoryItem import com.alya.ecommerce_serang.data.api.dto.ProductsItem +import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.data.repository.Result import kotlinx.coroutines.flow.MutableStateFlow @@ -21,6 +22,9 @@ class HomeViewModel ( private val _categories = MutableStateFlow>(emptyList()) val categories: StateFlow> = _categories.asStateFlow() + private val _storeMap = MutableStateFlow>(emptyMap()) + val storeMap: StateFlow> = _storeMap.asStateFlow() + init { loadProducts() loadCategories() @@ -47,6 +51,28 @@ class HomeViewModel ( } } + fun loadStoresForProducts(products: List) { + viewModelScope.launch { + val map = mutableMapOf() + val storeIds = products.mapNotNull { it.storeId }.toSet() + + for (storeId in storeIds) { + try { + val result = productRepository.fetchStoreDetail(storeId) + if (result is Result.Success) { + map[storeId] = result.data + } else if (result is Result.Error) { + Log.e("HomeViewModel", "Failed to load storeId $storeId", result.exception) + } + } catch (e: Exception) { + Log.e("HomeViewModel", "Exception fetching storeId $storeId", e) + } + } + + _storeMap.value = map + } + } + fun retry() { loadProducts() diff --git a/app/src/main/res/layout/activity_detail_product.xml b/app/src/main/res/layout/activity_detail_product.xml index 1bb20ff..187e853 100644 --- a/app/src/main/res/layout/activity_detail_product.xml +++ b/app/src/main/res/layout/activity_detail_product.xml @@ -487,7 +487,7 @@ android:orientation="horizontal" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" tools:itemCount="3" - tools:listitem="@layout/item_related_product" /> + tools:listitem="@layout/item_product_horizontal" /> diff --git a/app/src/main/res/layout/activity_list_product.xml b/app/src/main/res/layout/activity_list_product.xml new file mode 100644 index 0000000..ebf3300 --- /dev/null +++ b/app/src/main/res/layout/activity_list_product.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 858d751..3372ca4 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -28,15 +28,15 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - + + + + + + + + + + app:layout_constraintTop_toTopOf="parent" /> @@ -110,7 +113,7 @@ android:layout_marginTop="16dp" android:layout_marginBottom="16dp" android:orientation="vertical" - app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layout_constraintTop_toBottomOf="@id/new_products_text" tools:itemCount="5" tools:listitem="@layout/item_section_horizontal" /> diff --git a/app/src/main/res/layout/fragment_search_home.xml b/app/src/main/res/layout/fragment_search_home.xml index 69eb47b..c88d048 100644 --- a/app/src/main/res/layout/fragment_search_home.xml +++ b/app/src/main/res/layout/fragment_search_home.xml @@ -4,6 +4,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" + style="@style/Theme.Ecommerce_serang" tools:context=".ui.home.SearchHomeFragment"> - - - + android:layout_height="match_parent"> + + + + + + + - \ No newline at end of file diff --git a/app/src/main/res/layout/item_product_grid.xml b/app/src/main/res/layout/item_product_grid.xml index 3d9c567..60d0863 100644 --- a/app/src/main/res/layout/item_product_grid.xml +++ b/app/src/main/res/layout/item_product_grid.xml @@ -57,14 +57,24 @@ + android:textAlignment="center" + android:gravity="center" + app:drawableStartCompat="@drawable/baseline_star_24" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/tv_product_price" /> + tools:text="Toko Jaya" /> diff --git a/app/src/main/res/layout/item_product_horizontal.xml b/app/src/main/res/layout/item_product_horizontal.xml index 6f38ce8..2d09da3 100644 --- a/app/src/main/res/layout/item_product_horizontal.xml +++ b/app/src/main/res/layout/item_product_horizontal.xml @@ -1,69 +1,94 @@ - + android:layout_margin="8dp" + app:cardCornerRadius="12dp" + app:cardElevation="4dp" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - + + - + android:background="@color/light_gray" + android:contentDescription="Product Image" + tools:src="@drawable/placeholder_image" /> - + + - + + - + + - \ No newline at end of file + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_section_horizontal.xml b/app/src/main/res/layout/item_section_horizontal.xml index 5b8d4ed..9e1b55a 100644 --- a/app/src/main/res/layout/item_section_horizontal.xml +++ b/app/src/main/res/layout/item_section_horizontal.xml @@ -38,9 +38,10 @@ android:clipToPadding="false" android:clipChildren="false" app:layout_constraintTop_toBottomOf="@id/title" + app:layout_constraintBottom_toBottomOf="parent" android:layout_marginTop="16dp" android:orientation="horizontal" - tools:listitem="@layout/item_product_horizontal" - app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> + tools:listitem="@layout/item_product_grid" + app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"/> \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index b72680e..0fc9a63 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -60,4 +60,17 @@ 8dp @color/white + + + + + \ No newline at end of file