From fe331e7bb3d69cee87183343935cb7b47ecd48cc Mon Sep 17 00:00:00 2001 From: shaulascr Date: Tue, 17 Jun 2025 00:18:15 +0700 Subject: [PATCH 1/5] 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 From 95d5439050f93ef9dff907a9a32cee072a106774 Mon Sep 17 00:00:00 2001 From: shaulascr Date: Tue, 17 Jun 2025 16:20:30 +0700 Subject: [PATCH 2/5] update display product list and paid status --- .../ui/home/HomeCategoryAdapter.kt | 1 - .../ecommerce_serang/ui/home/HomeFragment.kt | 13 +- .../ui/order/history/HistoryViewModel.kt | 7 +- .../ui/order/history/OrderHistoryAdapter.kt | 125 ++++++++---------- .../ui/order/history/OrderHistoryFragment.kt | 4 +- .../ui/order/history/OrderListFragment.kt | 64 ++++++++- .../ui/order/history/OrderViewPageAdapter.kt | 4 +- .../detailorder/DetailOrderStatusActivity.kt | 36 ++--- .../storeDetail/StoreDetailActivity.kt | 8 +- .../main/res/layout/activity_store_detail.xml | 9 +- app/src/main/res/layout/fragment_home.xml | 53 ++++---- .../main/res/layout/item_category_home.xml | 5 +- .../res/layout/item_product_horizontal.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 14 files changed, 181 insertions(+), 152 deletions(-) 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 5bdc7dc..9a8a788 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 @@ -12,7 +12,6 @@ import com.bumptech.glide.Glide class HomeCategoryAdapter( private var categories:List, - //A lambda function that will be invoked when a category item is clicked. private val onClick:(category:CategoryItem) -> Unit ): RecyclerView.Adapter() { 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 2c6cc9d..0965a96 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 @@ -15,7 +15,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.findNavController import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView 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 @@ -83,20 +83,15 @@ class HomeFragment : Fragment() { binding.newProducts.apply { adapter = productAdapter - layoutManager = GridLayoutManager( - context, - 2, - LinearLayoutManager.HORIZONTAL, - false - ) + layoutManager = GridLayoutManager(requireContext(), 2) } binding.categories.apply { adapter = categoryAdapter layoutManager = GridLayoutManager( context, - 3, - LinearLayoutManager.HORIZONTAL, + 3, // 3 columns + RecyclerView.VERTICAL, // vertical layout false ) } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/HistoryViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/HistoryViewModel.kt index c2df487..1a6f8e5 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/HistoryViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/HistoryViewModel.kt @@ -91,7 +91,7 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() { private suspend fun getAllOrdersCombined() { try { - val allStatuses = listOf("pending", "unpaid", "processed", "shipped", "completed", "canceled") + val allStatuses = listOf("unpaid", "paid", "processed", "shipped", "completed", "canceled") val allOrders = mutableListOf() // Use coroutineScope to allow launching async blocks @@ -211,10 +211,7 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() { fun refreshOrders(status: String = "all") { Log.d(TAG, "Refreshing orders with status: $status") - // Clear current orders before fetching new ones - _orders.value = ViewState.Loading - - // Re-fetch the orders with the current status + // Don't set Loading here if you want to show current data while refreshing getOrderList(status) } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt index a67a2d7..f395ec2 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt @@ -4,7 +4,6 @@ import android.app.Activity import android.app.Dialog import android.content.ContextWrapper import android.content.Intent -import android.graphics.Color import android.net.Uri import android.provider.MediaStore import android.util.Log @@ -15,10 +14,10 @@ import android.view.Window import android.widget.ArrayAdapter import android.widget.AutoCompleteTextView import android.widget.ImageView -import android.widget.ProgressBar import android.widget.TextView import android.widget.Toast import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.Observer import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -40,9 +39,16 @@ import java.util.TimeZone class OrderHistoryAdapter( private val onOrderClickListener: (OrdersItem) -> Unit, - private val viewModel: HistoryViewModel // Add this parameter + private val viewModel: HistoryViewModel, + private val callbacks: OrderActionCallbacks ) : RecyclerView.Adapter() { + interface OrderActionCallbacks { + fun onOrderCancelled(orderId: String, success: Boolean, message: String) + fun onOrderCompleted(orderId: Int, success: Boolean, message: String) + fun onShowLoading(show: Boolean) + } + private val orders = mutableListOf() private var fragmentStatus: String = "all" @@ -140,28 +146,6 @@ class OrderHistoryAdapter( deadlineLabel.visibility = View.GONE when (status) { - "pending" -> { - statusOrder.apply { - visibility = View.VISIBLE - text = itemView.context.getString(R.string.pending_orders) - } - deadlineLabel.apply { - visibility = View.VISIBLE - text = itemView.context.getString(R.string.dl_pending) - } - btnLeft.apply { - visibility = View.VISIBLE - text = itemView.context.getString(R.string.canceled_order_btn) - setOnClickListener { - showCancelOrderBottomSheet(order.orderId) - viewModel.refreshOrders() - } - } - deadlineDate.apply { - visibility = View.VISIBLE - text = formatDate(order.createdAt) - } - } "unpaid" -> { statusOrder.apply { visibility = View.VISIBLE @@ -176,7 +160,6 @@ class OrderHistoryAdapter( text = itemView.context.getString(R.string.canceled_order_btn) setOnClickListener { showCancelOrderBottomSheet(order.orderId) - viewModel.refreshOrders() } } @@ -198,6 +181,28 @@ class OrderHistoryAdapter( text = formatDatePay(order.updatedAt) } } + "paid" -> { + statusOrder.apply { + visibility = View.VISIBLE + text = itemView.context.getString(R.string.paid_orders) + } + deadlineLabel.apply { + visibility = View.VISIBLE + text = itemView.context.getString(R.string.dl_paid) + } + btnLeft.apply { + visibility = View.VISIBLE + text = itemView.context.getString(R.string.canceled_order_btn) + setOnClickListener { + showCancelOrderDialog(order.orderId.toString()) + viewModel.refreshOrders() + } + } +// deadlineDate.apply { +// visibility = View.VISIBLE +// text = formatDatePay(order.updatedAt) +// } + } "processed" -> { // Untuk status processed, tampilkan "Hubungi Penjual" statusOrder.apply { @@ -239,11 +244,14 @@ class OrderHistoryAdapter( visibility = View.VISIBLE text = itemView.context.getString(R.string.claim_order) setOnClickListener { - // Handle click event + callbacks.onShowLoading(true) + + // Call ViewModel viewModel.confirmOrderCompleted(order.orderId, "completed") viewModel.refreshOrders() } + } deadlineDate.apply { visibility = View.VISIBLE @@ -454,52 +462,32 @@ class OrderHistoryAdapter( } } - // Show loading indicator - val loadingView = View(context).apply { - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - setBackgroundColor(Color.parseColor("#80000000")) + callbacks.onShowLoading(true) - val progressBar = ProgressBar(context).apply { - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - } - -// addView(progressBar) -// (progressBar.layoutParams as? ViewGroup.MarginLayoutParams)?.apply { -// gravity = Gravity.CENTER -// } - } - - dialog.addContentView(loadingView, loadingView.layoutParams) - - // Call the ViewModel to cancel the order with image + // Call ViewModel method but don't observe here viewModel.cancelOrderWithImage(orderId, reason, imageFile) - // Observe for success/failure - viewModel.isSuccess.observe(itemView.findViewTreeLifecycleOwner()!!) { isSuccess -> - // Remove loading indicator - (loadingView.parent as? ViewGroup)?.removeView(loadingView) + // Create a one-time observer that will be removed automatically + val observer = object : Observer { + override fun onChanged(isSuccess: Boolean) { + callbacks.onShowLoading(false) - if (isSuccess) { - Toast.makeText(context, context.getString(R.string.order_canceled_successfully), Toast.LENGTH_SHORT).show() - dialog.dismiss() - - // Find the order in the list and remove it or update its status - val position = orders.indexOfFirst { it.orderId.toString() == orderId } - if (position != -1) { - orders.removeAt(position) - notifyItemRemoved(position) - notifyItemRangeChanged(position, orders.size) + if (isSuccess) { + val message = viewModel.message.value ?: context.getString(R.string.order_canceled_successfully) + callbacks.onOrderCancelled(orderId, true, message) + dialog.dismiss() + } else { + val message = viewModel.message.value ?: context.getString(R.string.failed_to_cancel_order) + callbacks.onOrderCancelled(orderId, false, message) } - } else { - Toast.makeText(context, viewModel.message.value ?: context.getString(R.string.failed_to_cancel_order), Toast.LENGTH_SHORT).show() + + // Remove this observer after first use + viewModel.isSuccess.removeObserver(this) } } + + // Add observer only once + viewModel.isSuccess.observe(itemView.findViewTreeLifecycleOwner()!!, observer) } dialog.show() } @@ -534,10 +522,7 @@ class OrderHistoryAdapter( val bottomSheet = CancelOrderBottomSheet( orderId = orderId, onOrderCancelled = { - // Handle the successful cancellation - // Refresh the data - viewModel.refreshOrders() // Assuming there's a method to refresh orders - + callbacks.onOrderCancelled(orderId.toString(), true, "Order cancelled successfully") // Show a success message Toast.makeText(context, "Order cancelled successfully", Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryFragment.kt index 267eb28..353a952 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryFragment.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryFragment.kt @@ -44,8 +44,8 @@ class OrderHistoryFragment : Fragment() { TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> tab.text = when (position) { 0 -> getString(R.string.all_orders) - 1 -> getString(R.string.pending_orders) - 2 -> getString(R.string.unpaid_orders) + 1 -> getString(R.string.unpaid_orders) + 2 -> getString(R.string.paid_orders) 3 -> getString(R.string.processed_orders) 4 -> getString(R.string.shipped_orders) 5 -> getString(R.string.completed_orders) diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderListFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderListFragment.kt index 5e43898..28931bd 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderListFragment.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderListFragment.kt @@ -21,7 +21,7 @@ import com.alya.ecommerce_serang.ui.order.history.detailorder.DetailOrderStatusA import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager -class OrderListFragment : Fragment() { +class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks { private var _binding: FragmentOrderListBinding? = null private val binding get() = _binding!! @@ -72,6 +72,7 @@ class OrderListFragment : Fragment() { setupRecyclerView() observeOrderList() + observeViewModel() observeOrderCompletionStatus() loadOrders() } @@ -81,7 +82,8 @@ class OrderListFragment : Fragment() { onOrderClickListener = { order -> navigateToOrderDetail(order) }, - viewModel = viewModel + viewModel = viewModel, + callbacks = this // Pass this fragment as callback ) orderAdapter.setFragmentStatus(status) @@ -120,6 +122,40 @@ class OrderListFragment : Fragment() { } } + private fun observeViewModel() { + // Observe order completion + viewModel.orderCompletionStatus.observe(viewLifecycleOwner) { result -> + when (result) { + is Result.Success -> { + Toast.makeText(requireContext(), "Order completed successfully!", Toast.LENGTH_SHORT).show() + loadOrders() // Refresh here + } + is Result.Error -> { + Toast.makeText(requireContext(), "Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show() + } + is Result.Loading -> { + // Show loading if needed + } + } + } + + // Observe cancel order status + viewModel.cancelOrderStatus.observe(viewLifecycleOwner) { result -> + when (result) { + is Result.Success -> { + Toast.makeText(requireContext(), "Order cancelled successfully!", Toast.LENGTH_SHORT).show() + loadOrders() // Refresh here + } + is Result.Error -> { + Toast.makeText(requireContext(), "Failed to cancel: ${result.exception.message}", Toast.LENGTH_SHORT).show() + } + is Result.Loading -> { + // Show loading if needed + } + } + } + } + private fun loadOrders() { // Simple - just call getOrderList for any status including "all" viewModel.getOrderList(status) @@ -142,6 +178,30 @@ class OrderListFragment : Fragment() { detailOrderLauncher.launch(intent) } + + + override fun onOrderCancelled(orderId: String, success: Boolean, message: String) { + if (success) { + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() + loadOrders() // Refresh the list + } else { + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() + } + } + + override fun onOrderCompleted(orderId: Int, success: Boolean, message: String) { + if (success) { + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() + loadOrders() // Refresh the list + } else { + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() + } + } + + override fun onShowLoading(show: Boolean) { + binding.progressBar.visibility = if (show) View.VISIBLE else View.GONE + } + override fun onDestroyView() { super.onDestroyView() _binding = null diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderViewPageAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderViewPageAdapter.kt index 615f7a1..146100d 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderViewPageAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderViewPageAdapter.kt @@ -11,8 +11,8 @@ class OrderViewPagerAdapter( // Define all possible order statuses private val orderStatuses = listOf( "all", // All orders - "pending", // Menunggu Tagihan - "unpaid", // Belum Dibayar + "unpaid", // Menunggu Tagihan + "paid", // Belum Dibayar "processed", // Diproses "shipped", // Dikirim "completed", // Selesai diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderStatusActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderStatusActivity.kt index 4e5d6b4..d68dd35 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderStatusActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderStatusActivity.kt @@ -234,8 +234,8 @@ class DetailOrderStatusActivity : AppCompatActivity() { // Set status header val statusText = when(status) { - "pending" -> "Belum Bayar" "unpaid" -> "Belum Bayar" + "paid" -> "Sudah Dibayar" "processed" -> "Diproses" "shipped" -> "Dikirim" "delivered" -> "Diterima" @@ -248,22 +248,6 @@ class DetailOrderStatusActivity : AppCompatActivity() { Log.d(TAG, "adjustButtonsBasedOnStatus: Status header set to '$statusText'") when (status) { - "pending"->{ - binding.tvStatusHeader.text = "Menunggu Tagihan" - binding.tvStatusNote.visibility = View.VISIBLE - binding.tvStatusNote.text = "Pesanan ini harus dibayar sebelum ${formatDatePay(orders.updatedAt)}" - - // Set buttons - binding.btnSecondary.apply { - visibility = View.VISIBLE - text = "Batalkan Pesanan" - setOnClickListener { - Log.d(TAG, "Cancel Order button clicked") - showCancelOrderBottomSheet(orders.orderId) - viewModel.getOrderDetails(orders.orderId) - } - } - } "unpaid" -> { Log.d(TAG, "adjustButtonsBasedOnStatus: Setting up UI for pending/unpaid order") @@ -295,7 +279,25 @@ class DetailOrderStatusActivity : AppCompatActivity() { } } } + "paid" -> { + Log.d(TAG, "adjustButtonsBasedOnStatus: Setting up UI for pending/unpaid order") + // Show status note + binding.tvStatusHeader.text = "Sudah Dibayar" + binding.tvStatusNote.visibility = View.VISIBLE + binding.tvStatusNote.text = "Menunggu pesanan dikonfirmasi penjual ${formatDatePay(orders.updatedAt)}" + + // Set buttons + binding.btnSecondary.apply { + visibility = View.VISIBLE + text = "Batalkan Pesanan" + setOnClickListener { + Log.d(TAG, "Cancel Order button clicked") + showCancelOrderDialog(orders.orderId.toString()) + viewModel.getOrderDetails(orders.orderId) + } + } + } "processed" -> { Log.d(TAG, "adjustButtonsBasedOnStatus: Setting up UI for processed order") 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 50ebbe2..73a64b0 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 @@ -11,7 +11,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat -import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.GridLayoutManager import com.alya.ecommerce_serang.BuildConfig.BASE_URL import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.dto.ProductsItem @@ -169,11 +169,7 @@ class StoreDetailActivity : AppCompatActivity() { binding.rvProducts.apply { adapter = productAdapter - layoutManager = LinearLayoutManager( - context, - LinearLayoutManager.HORIZONTAL, - false - ) + layoutManager = GridLayoutManager(context, 2) } } diff --git a/app/src/main/res/layout/activity_store_detail.xml b/app/src/main/res/layout/activity_store_detail.xml index 1b2fdce..0e71ad2 100644 --- a/app/src/main/res/layout/activity_store_detail.xml +++ b/app/src/main/res/layout/activity_store_detail.xml @@ -158,14 +158,13 @@ + app:spanCount="2" + tools:listitem="@layout/item_product_grid" /> \ 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 3372ca4..af75ae6 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -15,34 +15,25 @@ android:layout_marginTop="16dp" app:layout_constraintTop_toTopOf="parent" /> - - - - - - - - - - - - - + android:layout_height="wrap_content" + android:paddingBottom="16dp"> + - + + + tools:listitem="@layout/item_product_grid" /> + - + Batas Tagihan Batas Pembayaran Batas Pengiriman - Semua Pesanan + Batas Konfirmasi Penjual Semua Pesanan Semua Pesanan Tanggal Pesanan Sampai From 420208ba1965fd2ab7a242a2e140a1efbc76a8ee Mon Sep 17 00:00:00 2001 From: shaulascr Date: Wed, 18 Jun 2025 00:31:52 +0700 Subject: [PATCH 3/5] update display category list --- app/src/main/AndroidManifest.xml | 3 + .../ecommerce_serang/ui/home/HomeFragment.kt | 6 + .../ui/order/CheckoutActivity.kt | 8 +- .../ui/product/DetailProductActivity.kt | 12 +- .../listproduct/ListCategoryActivity.kt | 112 ++++++++++++++++++ .../listproduct/ListCategoryAdapter.kt | 55 +++++++++ .../listproduct/ListProductActivity.kt | 8 +- .../utils/viewmodel/HomeViewModel.kt | 15 +++ app/src/main/res/layout/activity_checkout.xml | 29 ----- .../res/layout/activity_list_category.xml | 28 +++++ .../main/res/layout/activity_list_product.xml | 10 +- app/src/main/res/layout/dialog_count_buy.xml | 17 +-- app/src/main/res/layout/fragment_home.xml | 2 +- 13 files changed, 254 insertions(+), 51 deletions(-) create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListCategoryActivity.kt create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListCategoryAdapter.kt create mode 100644 app/src/main/res/layout/activity_list_category.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 428d94c..16efbe1 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/HomeFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/home/HomeFragment.kt index 0965a96..8f270a5 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 @@ -26,6 +26,7 @@ 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.ListCategoryActivity import com.alya.ecommerce_serang.ui.product.listproduct.ListProductActivity import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager @@ -100,6 +101,11 @@ class HomeFragment : Fragment() { val intent = Intent(requireContext(), ListProductActivity::class.java) startActivity(intent) } + + binding.categoryShowAll.setOnClickListener { + val intent = Intent(requireContext(), ListCategoryActivity::class.java) + startActivity(intent) + } } private fun setupSearchView() { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt index f7859e4..9f4e7e4 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt @@ -326,10 +326,10 @@ class CheckoutActivity : AppCompatActivity() { } } - // Voucher section (if implemented) - binding.layoutVoucher?.setOnClickListener { - Toast.makeText(this, "Voucher feature not implemented", Toast.LENGTH_SHORT).show() - } +// // Voucher section (if implemented) +// binding.layoutVoucher?.setOnClickListener { +// Toast.makeText(this, "Voucher feature not implemented", Toast.LENGTH_SHORT).show() +// } } private val addressSelectionLauncher = registerForActivityResult( 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 81fee0e..9630784 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 @@ -360,6 +360,8 @@ class DetailProductActivity : AppCompatActivity() { val btnClose = view.findViewById(R.id.btnCloseDialog) val switchWholesale = view.findViewById(R.id.switch_price) + val titleWholesale = view.findViewById(R.id.tv_active_wholesale) +// val descWholesale = view.findViewById(R.id.tv_desc_wholesale) if (!isBuyNow) { btnBuyNow.setText(R.string.add_to_cart) @@ -368,10 +370,18 @@ class DetailProductActivity : AppCompatActivity() { switchWholesale.isEnabled = isWholesaleAvailable switchWholesale.isChecked = isWholesaleSelected - // Set initial quantity based on current selection currentQuantity = if (isWholesaleSelected) minOrder else 1 tvQuantity.text = currentQuantity.toString() + if (isWholesaleAvailable){ + switchWholesale.visibility = View.VISIBLE + Toast.makeText(this, "Minimal pembelian grosir $currentQuantity produk", Toast.LENGTH_SHORT).show() + } else { + switchWholesale.visibility = View.GONE + } + // Set initial quantity based on current selection + + switchWholesale.setOnCheckedChangeListener { _, isChecked -> isWholesaleSelected = isChecked diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListCategoryActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListCategoryActivity.kt new file mode 100644 index 0000000..73f20fd --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListCategoryActivity.kt @@ -0,0 +1,112 @@ +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.RecyclerView +import com.alya.ecommerce_serang.data.api.dto.CategoryItem +import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig +import com.alya.ecommerce_serang.data.repository.ProductRepository +import com.alya.ecommerce_serang.databinding.ActivityListCategoryBinding +import com.alya.ecommerce_serang.ui.product.category.CategoryProductsActivity +import com.alya.ecommerce_serang.utils.BaseViewModelFactory +import com.alya.ecommerce_serang.utils.SessionManager +import com.alya.ecommerce_serang.utils.viewmodel.HomeViewModel + +class ListCategoryActivity : AppCompatActivity() { + companion object { + private val TAG = "ListCategoryActivity" + } + private lateinit var binding: ActivityListCategoryBinding + private lateinit var sessionManager: SessionManager + private var categoryAdapter: ListCategoryAdapter? = null + + private val viewModel: HomeViewModel by viewModels { + BaseViewModelFactory { + val apiService = ApiConfig.getApiService(sessionManager) + val repository = ProductRepository(apiService) + HomeViewModel(repository) + } + } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityListCategoryBinding.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 + } + + setupToolbar() + setupObserver() + viewModel.loadCategory() + setupRecyclerView() + + } + + private fun setupToolbar(){ + binding.header.headerLeftIcon.setOnClickListener{ + finish() + } + binding.header.headerTitle.text = "Kategori Produk" + } + + private fun setupRecyclerView(){ + categoryAdapter = ListCategoryAdapter( + categories = emptyList(), + onClick = { category -> handleClickOnCategory(category) } + ) + + binding.rvListCategories.apply { + adapter = categoryAdapter + layoutManager = GridLayoutManager( + context, + 3, // 3 columns + RecyclerView.VERTICAL, + false + ) + } + } + + private fun setupObserver(){ + viewModel.category.observe(this) { category -> + Log.d("ListCategoryActivity", "Received categories: ${category.size}") + updateCategories(category) + } + } + + private fun updateCategories(category: List){ + if (category.isEmpty()) { + binding.rvListCategories.visibility = View.GONE + } else { + binding.rvListCategories.visibility = View.VISIBLE + categoryAdapter?.updateData(category) + } + } + + private fun handleClickOnCategory(category: CategoryItem){ + val intent = Intent(this, CategoryProductsActivity::class.java) + intent.putExtra(CategoryProductsActivity.EXTRA_CATEGORY, category) + startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListCategoryAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListCategoryAdapter.kt new file mode 100644 index 0000000..d5fa5af --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/listproduct/ListCategoryAdapter.kt @@ -0,0 +1,55 @@ +package com.alya.ecommerce_serang.ui.product.listproduct + +import android.util.Log +import android.view.LayoutInflater +import android.view.ViewGroup +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.CategoryItem +import com.alya.ecommerce_serang.databinding.ItemCategoryHomeBinding +import com.bumptech.glide.Glide + +class ListCategoryAdapter( + private var categories:List, + private val onClick:(category: CategoryItem) -> Unit +): RecyclerView.Adapter() { + + 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}") + + val fullImageUrl = if (category.image.startsWith("/")) { + BASE_URL + category.image.removePrefix("/") // Append base URL if the path starts with "/" + } else { + category.image // Use as is if it's already a full URL + } + + Log.d("CategoriesAdapter", "Loading image: $fullImageUrl") + + Glide.with(itemView.context) + .load(fullImageUrl) // Ensure full URL + .placeholder(R.drawable.placeholder_image) + .into(imageCategory) + + name.text = category.name + + 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: ListCategoryAdapter.ViewHolder, position: Int) { + holder.bind(categories[position]) + } + + fun updateData(newCategories: List) { + categories = newCategories.toList() + notifyDataSetChanged() + } +} \ No newline at end of file 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 index 6a00f2e..2b74409 100644 --- 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 @@ -69,7 +69,7 @@ class ListProductActivity : AppCompatActivity() { private fun setupRecyclerView() { - binding.rvProducts.apply { + binding.rvProductsList.apply { adapter = productAdapter layoutManager = GridLayoutManager( context, @@ -112,14 +112,14 @@ class ListProductActivity : AppCompatActivity() { private fun updateProducts(products: List, storeMap: Map) { if (products.isEmpty()) { Log.d(TAG, "Product list is empty, hiding RecyclerView") - binding.rvProducts.visibility = View.VISIBLE + binding.rvProductsList.visibility = View.VISIBLE } else { Log.d(TAG, "Displaying product list in RecyclerView") - binding.rvProducts.visibility = View.VISIBLE // <-- Fix here + binding.rvProductsList.visibility = View.VISIBLE // <-- Fix here productAdapter = ListProductAdapter(products, onClick = { product -> handleProductClick(product) }, storeMap = storeMap) - binding.rvProducts.adapter = productAdapter + binding.rvProductsList.adapter = productAdapter productAdapter?.updateProducts(products) } } 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 a7667cf..b37ad1b 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 @@ -1,6 +1,8 @@ package com.alya.ecommerce_serang.utils.viewmodel import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.alya.ecommerce_serang.data.api.dto.CategoryItem @@ -25,6 +27,9 @@ class HomeViewModel ( private val _storeMap = MutableStateFlow>(emptyMap()) val storeMap: StateFlow> = _storeMap.asStateFlow() + private val _category = MutableLiveData>() + val category: LiveData> get() = _category + init { loadProducts() loadCategories() @@ -73,6 +78,16 @@ class HomeViewModel ( } } + fun loadCategory() { + viewModelScope.launch { + when (val result = productRepository.getAllCategories()) { + is Result.Success -> _category.value = result.data + is Result.Error -> Log.e("HomeViewModel", "Failed to fetch categories", result.exception) + is Result.Loading -> {} + } + } + } + fun retry() { loadProducts() diff --git a/app/src/main/res/layout/activity_checkout.xml b/app/src/main/res/layout/activity_checkout.xml index 8c793d4..2c5a7ba 100644 --- a/app/src/main/res/layout/activity_checkout.xml +++ b/app/src/main/res/layout/activity_checkout.xml @@ -142,35 +142,6 @@ android:layout_height="8dp" android:background="#F5F5F5" /> - - - - - - - - - - - - - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_list_product.xml b/app/src/main/res/layout/activity_list_product.xml index ebf3300..aab4e4c 100644 --- a/app/src/main/res/layout/activity_list_product.xml +++ b/app/src/main/res/layout/activity_list_product.xml @@ -8,12 +8,14 @@ tools:context=".ui.product.listproduct.ListProductActivity"> + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toTopOf="@id/rvProductsList"/> @@ -23,11 +25,11 @@ - + + + + + + + + + Date: Wed, 18 Jun 2025 14:00:20 +0700 Subject: [PATCH 4/5] fix search --- .../ecommerce_serang/ui/home/HomeFragment.kt | 21 +++++-------------- .../ui/home/SearchHomeFragment.kt | 8 +++---- .../listproduct/ListProductActivity.kt | 13 +++++++++++- .../main/res/layout/activity_list_product.xml | 1 - .../main/res/layout/fragment_search_home.xml | 4 +++- 5 files changed, 24 insertions(+), 23 deletions(-) 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 8f270a5..dba398a 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 @@ -6,7 +6,6 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.inputmethod.EditorInfo import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -110,26 +109,16 @@ class HomeFragment : Fragment() { private fun setupSearchView() { binding.searchContainer.search.apply { - // When user clicks the search box, navigate to search fragment + // Make it non-editable so it acts like a button + isFocusable = false + isFocusableInTouchMode = false + isClickable = true + setOnClickListener { findNavController().navigate( HomeFragmentDirections.actionHomeFragmentToSearchHomeFragment(null) ) } - -// Handle search action if user presses search on keyboard - setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_SEARCH) { - val query = text.toString().trim() - if (query.isNotEmpty()) { - findNavController().navigate( - HomeFragmentDirections.actionHomeFragmentToSearchHomeFragment(query) - ) - } - return@setOnEditorActionListener true - } - false - } } // Setup cart and notification buttons 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 b715d2b..aca01ec 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 @@ -76,6 +76,7 @@ class SearchHomeFragment : Fragment() { // Setup search view binding.searchView.apply { + clearFocus() setOnQueryTextListener(object : androidx.appcompat.widget.SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { query?.let { @@ -105,13 +106,12 @@ class SearchHomeFragment : Fragment() { } }) - // Request focus and show keyboard if (args.query.isNullOrEmpty()) { requestFocus() - postDelayed({ + post { val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.showSoftInput(findFocus(), InputMethodManager.SHOW_IMPLICIT) - }, 200) + imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) + } } } } 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 index 2b74409..f6a380b 100644 --- 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 @@ -18,6 +18,7 @@ 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.cart.CartActivity import com.alya.ecommerce_serang.ui.product.DetailProductActivity import com.alya.ecommerce_serang.ui.product.ProductUserViewModel import com.alya.ecommerce_serang.utils.BaseViewModelFactory @@ -60,13 +61,23 @@ class ListProductActivity : AppCompatActivity() { windowInsets } - + setupToolbar() setupObserver() setupRecyclerView() viewModel.loadProductsList() } + private fun setupToolbar(){ + binding.searchContainerList.btnBack.setOnClickListener{ + finish() + } + binding.searchContainerList.btnCart.setOnClickListener{ + val intent = Intent(this, CartActivity::class.java) + startActivity(intent) + } + } + private fun setupRecyclerView() { binding.rvProductsList.apply { diff --git a/app/src/main/res/layout/activity_list_product.xml b/app/src/main/res/layout/activity_list_product.xml index aab4e4c..cb0fbff 100644 --- a/app/src/main/res/layout/activity_list_product.xml +++ b/app/src/main/res/layout/activity_list_product.xml @@ -4,7 +4,6 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - style="@style/Theme.Ecommerce_serang" tools:context=".ui.product.listproduct.ListProductActivity"> + android:queryHint="Search products..." + app:iconifiedByDefault="false" + app:queryHint="Search products..." /> From 6ced0d31dce5829f41197b8f1d6b1d1dff1c240d Mon Sep 17 00:00:00 2001 From: shaulascr Date: Wed, 18 Jun 2025 16:43:03 +0700 Subject: [PATCH 5/5] fix price and chat date --- .../customer/order/OrderDetailResponse.kt | 2 +- .../ecommerce_serang/ui/chat/ChatActivity.kt | 11 ++- .../ecommerce_serang/ui/chat/ChatAdapter.kt | 84 +++++++++++++---- .../ecommerce_serang/ui/chat/ChatViewModel.kt | 93 ++++++++++++++++++- .../ui/home/HorizontalProductAdapter.kt | 21 ++++- .../ui/home/SearchResultAdapter.kt | 9 +- .../ui/order/PaymentMethodAdapter.kt | 6 ++ .../ui/order/ShippingAdapter.kt | 9 +- .../detail/AddEvidencePaymentActivity.kt | 38 +++++--- .../ui/order/detail/PaymentActivity.kt | 9 +- .../ui/order/detail/SpinnerCardAdapter.kt | 33 +++++++ .../detailorder/DetailOrderItemsAdapter.kt | 13 ++- .../detailorder/DetailOrderStatusActivity.kt | 45 +++++++-- .../ui/product/OtherProductAdapter.kt | 9 +- .../product/listproduct/ListProductAdapter.kt | 9 +- .../profile/mystore/chat/ChatStoreActivity.kt | 4 +- app/src/main/res/drawable/bg_date_header.xml | 6 ++ app/src/main/res/layout/item_date_header.xml | 21 +++++ .../res/layout/item_dialog_add_evidence.xml | 34 +++++++ .../res/layout/item_dialog_spinner_card.xml | 21 +++++ .../res/layout/item_order_detail_product.xml | 3 +- 21 files changed, 419 insertions(+), 61 deletions(-) create mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/SpinnerCardAdapter.kt create mode 100644 app/src/main/res/drawable/bg_date_header.xml create mode 100644 app/src/main/res/layout/item_date_header.xml create mode 100644 app/src/main/res/layout/item_dialog_add_evidence.xml create mode 100644 app/src/main/res/layout/item_dialog_spinner_card.xml diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/order/OrderDetailResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/order/OrderDetailResponse.kt index 6530886..3adb6d4 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/order/OrderDetailResponse.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/order/OrderDetailResponse.kt @@ -80,7 +80,7 @@ data class Orders( val cancelReason: String? = null, @field:SerializedName("total_amount") - val totalAmount: String? = null, + val totalAmount: Int? = null, @field:SerializedName("user_id") val userId: Int, diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt index 2151030..39fc4a1 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt @@ -394,7 +394,10 @@ class ChatActivity : AppCompatActivity() { // Update messages val previousCount = chatAdapter.itemCount - chatAdapter.submitList(state.messages) { + + val displayItems = viewModel.getDisplayItems() + + chatAdapter.submitList(displayItems) { Log.d(TAG, "Messages submitted to adapter") // Only auto-scroll for new messages or initial load if (previousCount == 0 || state.messages.size > previousCount) { @@ -426,17 +429,15 @@ class ChatActivity : AppCompatActivity() { .error(R.drawable.placeholder_image) .into(binding.imgProduct) } + updateProductCardUI(state.hasProductAttachment) - binding.productContainer.visibility = View.VISIBLE + binding.productContainer.visibility = View.GONE } else { binding.productContainer.visibility = View.GONE } updateInputHint(state) - // Update product card visual feedback - updateProductCardUI(state.hasProductAttachment) - // Update attachment hint if (state.hasAttachment) { binding.editTextMessage.hint = getString(R.string.image_attached) diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatAdapter.kt index 05cfbfc..8a136f0 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatAdapter.kt @@ -8,6 +8,7 @@ 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.databinding.ItemDateHeaderBinding import com.alya.ecommerce_serang.databinding.ItemMessageProductReceivedBinding import com.alya.ecommerce_serang.databinding.ItemMessageProductSentBinding import com.alya.ecommerce_serang.databinding.ItemMessageReceivedBinding @@ -17,22 +18,29 @@ import com.bumptech.glide.Glide class ChatAdapter( private val onProductClick: ((ProductInfo) -> Unit)? = null -) : ListAdapter(ChatMessageDiffCallback()) { +) : ListAdapter(ChatMessageDiffCallback()) { companion object { private const val VIEW_TYPE_MESSAGE_SENT = 1 private const val VIEW_TYPE_MESSAGE_RECEIVED = 2 private const val VIEW_TYPE_PRODUCT_SENT = 3 private const val VIEW_TYPE_PRODUCT_RECEIVED = 4 + private const val VIEW_TYPE_DATE_HEADER = 5 } override fun getItemViewType(position: Int): Int { - val message = getItem(position) - return when { - message.messageType == MessageType.PRODUCT && message.isSentByMe -> VIEW_TYPE_PRODUCT_SENT - message.messageType == MessageType.PRODUCT && !message.isSentByMe -> VIEW_TYPE_PRODUCT_RECEIVED - message.isSentByMe -> VIEW_TYPE_MESSAGE_SENT - else -> VIEW_TYPE_MESSAGE_RECEIVED + val item = getItem(position) + return when (item) { + is ChatDisplayItem.DateHeaderItem -> VIEW_TYPE_DATE_HEADER + is ChatDisplayItem.MessageItem -> { + val message = item.chatUiMessage + when { + message.messageType == MessageType.PRODUCT && message.isSentByMe -> VIEW_TYPE_PRODUCT_SENT + message.messageType == MessageType.PRODUCT && !message.isSentByMe -> VIEW_TYPE_PRODUCT_RECEIVED + message.isSentByMe -> VIEW_TYPE_MESSAGE_SENT + else -> VIEW_TYPE_MESSAGE_RECEIVED + } + } } } @@ -40,6 +48,10 @@ class ChatAdapter( val inflater = LayoutInflater.from(parent.context) return when (viewType) { + VIEW_TYPE_DATE_HEADER -> { + val binding = ItemDateHeaderBinding.inflate(inflater, parent, false) + DateHeaderViewHolder(binding) + } VIEW_TYPE_MESSAGE_SENT -> { val binding = ItemMessageSentBinding.inflate(inflater, parent, false) SentMessageViewHolder(binding) @@ -61,13 +73,34 @@ class ChatAdapter( } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - val message = getItem(position) + val item = getItem(position) when (holder) { - is SentMessageViewHolder -> holder.bind(message) - is ReceivedMessageViewHolder -> holder.bind(message) - is SentProductViewHolder -> holder.bind(message) - is ReceivedProductViewHolder -> holder.bind(message) + is DateHeaderViewHolder -> { + if (item is ChatDisplayItem.DateHeaderItem) { + holder.bind(item) + } + } + is SentMessageViewHolder -> { + if (item is ChatDisplayItem.MessageItem) { + holder.bind(item.chatUiMessage) + } + } + is ReceivedMessageViewHolder -> { + if (item is ChatDisplayItem.MessageItem) { + holder.bind(item.chatUiMessage) + } + } + is SentProductViewHolder -> { + if (item is ChatDisplayItem.MessageItem) { + holder.bind(item.chatUiMessage) + } + } + is ReceivedProductViewHolder -> { + if (item is ChatDisplayItem.MessageItem) { + holder.bind(item.chatUiMessage) + } + } } } @@ -233,17 +266,36 @@ class ChatAdapter( } } } + + inner class DateHeaderViewHolder(private val binding: ItemDateHeaderBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(item: ChatDisplayItem.DateHeaderItem) { + binding.tvDate.text = item.formattedDate + } + } } /** * DiffUtil callback for optimizing RecyclerView updates */ -class ChatMessageDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: ChatUiMessage, newItem: ChatUiMessage): Boolean { - return oldItem.id == newItem.id +class ChatMessageDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: ChatDisplayItem, newItem: ChatDisplayItem): Boolean { + return when { + oldItem is ChatDisplayItem.MessageItem && newItem is ChatDisplayItem.MessageItem -> + oldItem.chatUiMessage.id == newItem.chatUiMessage.id + oldItem is ChatDisplayItem.DateHeaderItem && newItem is ChatDisplayItem.DateHeaderItem -> + oldItem.date == newItem.date + else -> false + } } - override fun areContentsTheSame(oldItem: ChatUiMessage, newItem: ChatUiMessage): Boolean { + override fun areContentsTheSame(oldItem: ChatDisplayItem, newItem: ChatDisplayItem): Boolean { return oldItem == newItem } +} + +sealed class ChatDisplayItem { + data class MessageItem(val chatUiMessage: ChatUiMessage) : ChatDisplayItem() + data class DateHeaderItem(val date: String, val formattedDate: String) : ChatDisplayItem() } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt index 677bf34..3b550f5 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt @@ -16,6 +16,9 @@ import com.alya.ecommerce_serang.utils.SessionManager import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import java.io.File +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date import java.util.Locale import java.util.TimeZone import javax.inject.Inject @@ -737,7 +740,8 @@ class ChatViewModel @Inject constructor( attachment = chatLine.attachment ?: "", status = chatLine.status, time = formattedTime, - isSentByMe = chatLine.senderId == currentUserId + isSentByMe = chatLine.senderId == currentUserId, + createdAt = chatLine.createdAt ) } @@ -781,7 +785,8 @@ class ChatViewModel @Inject constructor( time = formattedTime, isSentByMe = chatItem.senderId == currentUserId, messageType = messageType, - productInfo = productInfo + productInfo = productInfo, + createdAt = chatItem.createdAt ) // Fetch product info for non-current-user products @@ -923,6 +928,85 @@ class ChatViewModel @Inject constructor( Log.d(TAG, "ChatViewModel cleared - Disconnecting socket") socketService.disconnect() } + + fun getDisplayItems(): List { + return transformMessagesToDisplayItems(state.value?.messages ?: emptyList()) + } + + private fun transformMessagesToDisplayItems(messages: List): List { + if (messages.isEmpty()) return emptyList() + + val displayItems = mutableListOf() + var lastDate: String? = null + + for (message in messages) { + // Extract date from message timestamp + val messageDate = extractDateFromTimestamp(message.createdAt) // You need to implement this + + // Add date header if this is a new day + if (messageDate != lastDate) { + val formattedDate = formatDateHeader(messageDate) // You need to implement this + displayItems.add(ChatDisplayItem.DateHeaderItem(messageDate, formattedDate)) + lastDate = messageDate + } + + // Add the message + displayItems.add(ChatDisplayItem.MessageItem(message)) + } + + return displayItems + } + + private fun extractDateFromTimestamp(timestamp: String): String { + return try { + // Parse ISO 8601 format: "2025-05-27T08:36:53.946Z" + val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()) + inputFormat.timeZone = TimeZone.getTimeZone("UTC") + + val date = inputFormat.parse(timestamp) + val outputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + outputFormat.format(date ?: Date()) + } catch (e: Exception) { + Log.e(TAG, "Error parsing timestamp: $timestamp", e) + return timestamp.take(10) + } + } + + private fun formatDateHeader(dateString: String): String { + return try { + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + val messageDate = dateFormat.parse(dateString) ?: return dateString + + val today = Calendar.getInstance() + val yesterday = Calendar.getInstance().apply { add(Calendar.DAY_OF_YEAR, -1) } + val messageCalendar = Calendar.getInstance().apply { time = messageDate } + + when { + isSameDay(messageCalendar, today) -> "Today" + isSameDay(messageCalendar, yesterday) -> "Yesterday" + isThisYear(messageCalendar, today) -> { + // Show "Mon, Dec 15" format for this year + SimpleDateFormat("EEE, MMM dd", Locale.getDefault()).format(messageDate) + } + else -> { + // Show "Dec 15, 2024" format for other years + SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(messageDate) + } + } + } catch (e: Exception) { + Log.e(TAG, "Error formatting date: $dateString", e) + dateString // Fallback to raw date + } + } + + private fun isSameDay(cal1: Calendar, cal2: Calendar): Boolean { + return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && + cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) + } + + private fun isThisYear(messageCalendar: Calendar, today: Calendar): Boolean { + return messageCalendar.get(Calendar.YEAR) == today.get(Calendar.YEAR) + } } enum class MessageType { @@ -949,9 +1033,12 @@ data class ChatUiMessage( val time: String, val isSentByMe: Boolean, val messageType: MessageType = MessageType.TEXT, - val productInfo: ProductInfo? = null + val productInfo: ProductInfo? = null, + val createdAt: String ) + + // representing UI state to screen data class ChatUiState( val messages: List = emptyList(), 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 55792f9..b89d7fd 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 @@ -2,6 +2,7 @@ package com.alya.ecommerce_serang.ui.home import android.util.Log import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView @@ -11,6 +12,8 @@ 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 +import java.text.NumberFormat +import java.util.Locale class HorizontalProductAdapter( private var products: List, @@ -32,8 +35,17 @@ class HorizontalProductAdapter( Log.d("ProductAdapter", "Loading image: $fullImageUrl") tvProductName.text = product.name - tvProductPrice.text = product.price - rating.text = product.rating + tvProductPrice.text = formatCurrency(product.price.toDouble()) + val ratingStr = product.rating + val ratingValue = ratingStr?.toFloatOrNull() + + if (ratingValue != null && ratingValue > 0f) { + binding.rating.text = String.format("%.1f", ratingValue) + binding.rating.visibility = View.VISIBLE + } else { + binding.rating.text = "Belum ada rating" + binding.rating.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null) + } // Load image using Glide Glide.with(itemView) @@ -77,6 +89,11 @@ class HorizontalProductAdapter( diffResult.dispatchUpdatesTo(this) } + private fun formatCurrency(amount: Double): String { + val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID")) + return formatter.format(amount).replace(",00", "") + } + class ProductDiffCallback( private val oldList: List, private val newList: List 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 b46a143..89d8e22 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 @@ -12,6 +12,8 @@ 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 +import java.text.NumberFormat +import java.util.Locale class SearchResultsAdapter( private val onItemClick: (ProductsItem) -> Unit, @@ -46,7 +48,7 @@ class SearchResultsAdapter( fun bind(product: ProductsItem) { binding.tvProductName.text = product.name - binding.tvProductPrice.text = product.price + binding.tvProductPrice.text = formatCurrency(product.price.toDouble()) val fullImageUrl = if (product.image.startsWith("/")) { BASE_URL + product.image.removePrefix("/") // Append base URL if the path starts with "/" @@ -71,6 +73,11 @@ class SearchResultsAdapter( super.submitList(list) } + private fun formatCurrency(amount: Double): String { + val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID")) + return formatter.format(amount).replace(",00", "") + } + companion object { private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: ProductsItem, newItem: ProductsItem): Boolean { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt index 86b7254..a520da2 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt @@ -48,6 +48,12 @@ class PaymentMethodAdapter( // Load payment icon if available if (!payment.qrisImage.isNullOrEmpty()) { +// val fullImageUrl = if (payment.qrisImage.startsWith("/")) { +// BASE_URL + payment.qrisImage.removePrefix("/") // Append base URL if the path starts with "/" +// } else { +// payment.qrisImage// Use as is if it's already a full URL +// } + Glide.with(ivPaymentMethod.context) .load(payment.qrisImage) .apply( diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingAdapter.kt index bea432b..d311a86 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingAdapter.kt @@ -6,6 +6,8 @@ import androidx.recyclerview.widget.RecyclerView import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostsItem import com.alya.ecommerce_serang.data.api.response.customer.order.ServicesItem import com.alya.ecommerce_serang.databinding.ItemShippingOrderBinding +import java.text.NumberFormat +import java.util.Locale class ShippingAdapter( private val onItemSelected: (CourierCostsItem, ServicesItem) -> Unit @@ -30,7 +32,7 @@ class ShippingAdapter( // Combine courier name and service courierNameCost.text = "${courierCostsItem.courier} - ${service.service}" estDate.text = "Estimasi ${service.etd} hari" - costPrice.text = "Rp${service.cost}" + costPrice.text = formatCurrency(service.cost.toDouble()) // Single click handler for both item and radio button val onClickAction = { @@ -90,6 +92,11 @@ class ShippingAdapter( } } + private fun formatCurrency(amount: Double): String { + val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID")) + return formatter.format(amount).replace(",00", "") + } + override fun getItemCount(): Int { return courierCostsList.sumOf { it.services.size } } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/AddEvidencePaymentActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/AddEvidencePaymentActivity.kt index fc37df7..1329a95 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/AddEvidencePaymentActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/AddEvidencePaymentActivity.kt @@ -1,8 +1,8 @@ package com.alya.ecommerce_serang.ui.order.detail import android.Manifest -import android.R import android.app.Activity +import android.app.AlertDialog import android.app.DatePickerDialog import android.content.Intent import android.content.pm.PackageManager @@ -12,6 +12,7 @@ import android.os.Bundle import android.provider.MediaStore import android.util.Log import android.view.View +import android.view.ViewGroup import android.webkit.MimeTypeMap import android.widget.AdapterView import android.widget.ArrayAdapter @@ -25,6 +26,7 @@ import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat +import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.dto.AddEvidenceMultipartRequest import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.repository.OrderRepository @@ -37,6 +39,7 @@ import okhttp3.MultipartBody import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody import java.io.File +import java.text.NumberFormat import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -59,11 +62,9 @@ class AddEvidencePaymentActivity : AppCompatActivity() { } private val paymentMethods = arrayOf( - "Pilih metode pembayaran", "Transfer Bank", "E-Wallet", - "Virtual Account", - "Cash on Delivery" + "QRIS", ) // private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> @@ -128,11 +129,8 @@ class AddEvidencePaymentActivity : AppCompatActivity() { } private fun setupUI() { - // Set product details\ - - // Setup payment methods spinner - val adapter = ArrayAdapter(this, R.layout.simple_spinner_item, paymentMethods) - adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item) + val paymentMethods = listOf("Transfer Bank", "COD", "QRIS") + val adapter = SpinnerCardAdapter(this, paymentMethods) binding.spinnerPaymentMethod.adapter = adapter } @@ -219,15 +217,23 @@ class AddEvidencePaymentActivity : AppCompatActivity() { private fun showImagePickerOptions() { val options = arrayOf( "Pilih dari Galeri", - "Batal" + "Kembali" ) - androidx.appcompat.app.AlertDialog.Builder(this) - .setTitle("Pilih Bukti Pembayaran") - .setItems(options) { dialog, which -> + val adapter = object : ArrayAdapter(this, R.layout.item_dialog_add_evidence, R.id.tvOption, options) { + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = super.getView(position, convertView, parent) + val divider = view.findViewById(R.id.divider) + divider.visibility = if (position == count - 1) View.GONE else View.VISIBLE + return view + } + } + + AlertDialog.Builder(this) + .setAdapter(adapter) { dialog, which -> when (which) { - 0 -> openGallery() // Gallery - 1 -> dialog.dismiss() // Cancel + 0 -> openGallery() + 1 -> dialog.dismiss() } } .show() @@ -440,6 +446,8 @@ class AddEvidencePaymentActivity : AppCompatActivity() { } + + companion object { private const val PERMISSION_REQUEST_CODE = 100 private const val TAG = "AddEvidenceActivity" diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/PaymentActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/PaymentActivity.kt index d2c525a..c470b52 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/PaymentActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/PaymentActivity.kt @@ -17,6 +17,7 @@ import com.alya.ecommerce_serang.data.repository.OrderRepository import com.alya.ecommerce_serang.databinding.ActivityPaymentBinding import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager +import java.text.NumberFormat import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -100,6 +101,7 @@ class PaymentActivity : AppCompatActivity() { // Log.d(TAG, "Button clicked - showing toast") // Toast.makeText(this@PaymentActivity, "Button works! OrderID: $orderId", Toast.LENGTH_LONG).show() // } + binding.btnUploadPaymentProof.apply { isEnabled = true isClickable = true @@ -134,7 +136,7 @@ class PaymentActivity : AppCompatActivity() { Log.d(TAG, "Order details received: $order") // Set total amount - binding.tvTotalAmount.text = order.totalAmount ?: "Rp0" + binding.tvTotalAmount.text = formatCurrency(order.totalAmount?.toDouble() ?: 0.00) Log.d(TAG, "Total Amount: ${order.totalAmount}") @@ -202,6 +204,11 @@ class PaymentActivity : AppCompatActivity() { } } + private fun formatCurrency(amount: Double): String { + val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID")) + return formatter.format(amount).replace(",00", "") + } + private fun showInstructions(type: String) { // Implementasi tampilkan instruksi val instructions = when (type) { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/SpinnerCardAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/SpinnerCardAdapter.kt new file mode 100644 index 0000000..edd1988 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/detail/SpinnerCardAdapter.kt @@ -0,0 +1,33 @@ +package com.alya.ecommerce_serang.ui.order.detail + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.TextView +import com.alya.ecommerce_serang.R + +class SpinnerCardAdapter( + context: Context, + private val items: List +) : ArrayAdapter(context, 0, items) { + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + // View shown when Spinner is collapsed + return createCardView(position, convertView, parent) + } + + override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { + // View shown for dropdown items + return createCardView(position, convertView, parent) + } + + private fun createCardView(position: Int, convertView: View?, parent: ViewGroup): View { + val inflater = LayoutInflater.from(context) + val view = convertView ?: inflater.inflate(R.layout.item_dialog_spinner_card, parent, false) + val textView = view.findViewById(R.id.tvOption) + textView.text = items[position] + return view + } +} diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderItemsAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderItemsAdapter.kt index 58f5c43..924d4ef 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderItemsAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/detailorder/DetailOrderItemsAdapter.kt @@ -6,6 +6,7 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView 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.response.customer.order.OrderListItemsItem import com.bumptech.glide.Glide @@ -39,11 +40,18 @@ class DetailOrderItemsAdapter : RecyclerView.Adapter, @@ -32,7 +34,7 @@ class OtherProductAdapter ( Log.d("ProductAdapter", "Loading image: $fullImageUrl") tvProductName.text = product.name - tvProductPrice.text = product.price + tvProductPrice.text = formatCurrency(product.price.toDouble()) rating.text = product.rating // Load image using Glide @@ -77,6 +79,11 @@ class OtherProductAdapter ( diffResult.dispatchUpdatesTo(this) } + private fun formatCurrency(amount: Double): String { + val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID")) + return formatter.format(amount).replace(",00", "") + } + class ProductDiffCallback( private val oldList: List, private val newList: List 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 index f0cb5f4..25314e5 100644 --- 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 @@ -11,6 +11,8 @@ 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 +import java.text.NumberFormat +import java.util.Locale class ListProductAdapter( private var products: List, @@ -24,7 +26,7 @@ class ListProductAdapter( fun bind(product: ProductsItem) = with(binding) { tvProductName.text = product.name - tvProductPrice.text = product.price + tvProductPrice.text = formatCurrency(product.price.toDouble()) rating.text = product.rating val fullImageUrl = if (product.image.startsWith("/")) { @@ -68,6 +70,11 @@ class ListProductAdapter( notifyDataSetChanged() } + private fun formatCurrency(amount: Double): String { + val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID")) + return formatter.format(amount).replace(",00", "") + } + class ProductDiffCallback( private val oldList: List, private val newList: List diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/chat/ChatStoreActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/chat/ChatStoreActivity.kt index 8dfabfc..169f1de 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/chat/ChatStoreActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/chat/ChatStoreActivity.kt @@ -378,7 +378,9 @@ class ChatStoreActivity : AppCompatActivity() { // Update messages val previousCount = chatAdapter.itemCount - chatAdapter.submitList(state.messages) { + val displayItems = viewModel.getDisplayItems() + + chatAdapter.submitList(displayItems) { Log.d(TAG, "Messages submitted to adapter") // Only auto-scroll for new messages or initial load if (previousCount == 0 || state.messages.size > previousCount) { diff --git a/app/src/main/res/drawable/bg_date_header.xml b/app/src/main/res/drawable/bg_date_header.xml new file mode 100644 index 0000000..7539706 --- /dev/null +++ b/app/src/main/res/drawable/bg_date_header.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_date_header.xml b/app/src/main/res/layout/item_date_header.xml new file mode 100644 index 0000000..70aa5f8 --- /dev/null +++ b/app/src/main/res/layout/item_date_header.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_dialog_add_evidence.xml b/app/src/main/res/layout/item_dialog_add_evidence.xml new file mode 100644 index 0000000..9756df8 --- /dev/null +++ b/app/src/main/res/layout/item_dialog_add_evidence.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_dialog_spinner_card.xml b/app/src/main/res/layout/item_dialog_spinner_card.xml new file mode 100644 index 0000000..96694bd --- /dev/null +++ b/app/src/main/res/layout/item_dialog_spinner_card.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/app/src/main/res/layout/item_order_detail_product.xml b/app/src/main/res/layout/item_order_detail_product.xml index f34e2a1..71f7ed1 100644 --- a/app/src/main/res/layout/item_order_detail_product.xml +++ b/app/src/main/res/layout/item_order_detail_product.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:padding="8dp"> + android:paddingHorizontal="8dp">