diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/RegisterRequest.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/RegisterRequest.kt index 650eab3..19581dc 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/RegisterRequest.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/RegisterRequest.kt @@ -8,7 +8,11 @@ data class RegisterRequest ( val password: String?, val username: String?, val phone: String?, - @SerializedName("birth_date") val birthDate: String?, - @SerializedName("userimg") val image: String?, + @SerializedName("birth_date") + val birthDate: String?, + + @SerializedName("userimg") + val image: String?, + val otp: String? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/SearchRequest.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/SearchRequest.kt new file mode 100644 index 0000000..87c39cf --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/SearchRequest.kt @@ -0,0 +1,8 @@ +package com.alya.ecommerce_serang.data.api.dto + +import com.google.gson.annotations.SerializedName + +data class SearchRequest( + @SerializedName("search_query") + val searchQuery: String +) \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/CreateSearchResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/CreateSearchResponse.kt new file mode 100644 index 0000000..b6490bd --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/CreateSearchResponse.kt @@ -0,0 +1,24 @@ +package com.alya.ecommerce_serang.data.api.response.product + +import com.google.gson.annotations.SerializedName + +data class CreateSearchResponse( + + @field:SerializedName("search") + val search: Search +) + +data class Search( + + @field:SerializedName("user_id") + val userId: Int, + + @field:SerializedName("created_at") + val createdAt: String, + + @field:SerializedName("id") + val id: Int, + + @field:SerializedName("search_query") + val searchQuery: String +) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/SearchHistoryResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/SearchHistoryResponse.kt new file mode 100644 index 0000000..43dabc0 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/SearchHistoryResponse.kt @@ -0,0 +1,15 @@ +package com.alya.ecommerce_serang.data.api.response.product + +import com.google.gson.annotations.SerializedName + +data class SearchHistoryResponse( + + @field:SerializedName("data") + val data: List +) + +data class DataItem( + + @field:SerializedName("search_query") + val searchQuery: String +) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt index 1077a9c..d222885 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt @@ -10,11 +10,9 @@ import com.alya.ecommerce_serang.data.api.dto.OrderRequest import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy import com.alya.ecommerce_serang.data.api.dto.OtpRequest import com.alya.ecommerce_serang.data.api.dto.RegisterRequest +import com.alya.ecommerce_serang.data.api.dto.SearchRequest import com.alya.ecommerce_serang.data.api.dto.UpdateCart -import com.alya.ecommerce_serang.data.api.response.product.CreateProductResponse import com.alya.ecommerce_serang.data.api.response.ViewStoreProductsResponse -import okhttp3.MultipartBody -import okhttp3.RequestBody import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse @@ -32,21 +30,22 @@ import com.alya.ecommerce_serang.data.api.response.order.OrderDetailResponse import com.alya.ecommerce_serang.data.api.response.order.OrderListResponse import com.alya.ecommerce_serang.data.api.response.product.AllProductResponse import com.alya.ecommerce_serang.data.api.response.product.CategoryResponse +import com.alya.ecommerce_serang.data.api.response.product.CreateProductResponse +import com.alya.ecommerce_serang.data.api.response.product.CreateSearchResponse import com.alya.ecommerce_serang.data.api.response.product.DetailStoreProductResponse import com.alya.ecommerce_serang.data.api.response.product.ProductResponse import com.alya.ecommerce_serang.data.api.response.product.ReviewProductResponse +import com.alya.ecommerce_serang.data.api.response.product.SearchHistoryResponse import com.alya.ecommerce_serang.data.api.response.product.StoreResponse import com.alya.ecommerce_serang.data.api.response.profile.AddressResponse import com.alya.ecommerce_serang.data.api.response.profile.CreateAddressResponse import com.alya.ecommerce_serang.data.api.response.profile.ProfileResponse +import okhttp3.MultipartBody +import okhttp3.RequestBody import retrofit2.Call import retrofit2.Response import retrofit2.http.Body -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.HeaderMap import retrofit2.http.Multipart import retrofit2.http.POST import retrofit2.http.PUT @@ -202,4 +201,12 @@ interface ApiService { @Part("description") description: RequestBody, @Part complaintimg: MultipartBody.Part ): Response + + @POST("search") + suspend fun saveSearchQuery( + @Body searchRequest: SearchRequest + ): Response + + @GET("search") + suspend fun getSearchHistory(): Response } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt index 1a8781c..c51008e 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt @@ -4,19 +4,19 @@ import android.util.Log import com.alya.ecommerce_serang.data.api.dto.CartItem 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.product.CreateProductResponse +import com.alya.ecommerce_serang.data.api.dto.SearchRequest import com.alya.ecommerce_serang.data.api.response.cart.AddCartResponse +import com.alya.ecommerce_serang.data.api.response.product.CreateProductResponse import com.alya.ecommerce_serang.data.api.response.product.ProductResponse import com.alya.ecommerce_serang.data.api.response.product.ReviewsItem +import com.alya.ecommerce_serang.data.api.response.product.Search import com.alya.ecommerce_serang.data.api.response.product.StoreProduct import com.alya.ecommerce_serang.data.api.retrofit.ApiService -import com.alya.ecommerce_serang.utils.SessionManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody -import java.io.File class ProductRepository(private val apiService: ApiService) { suspend fun getAllProducts(): Result> = @@ -192,6 +192,70 @@ class ProductRepository(private val apiService: ApiService) { } } + suspend fun searchProducts(query: String): Result> = + withContext(Dispatchers.IO) { + try { + // First save the search query + saveSearchQuery(query) + + // Then fetch all products + val response = apiService.getAllProduct() + + if (response.isSuccessful) { + val allProducts = response.body()?.products ?: emptyList() + + // Filter products based on the search query + val filteredProducts = allProducts.filter { product -> + product.name.contains(query, ignoreCase = true) || + (product.description?.contains(query, ignoreCase = true) ?: false) + } + + Log.d(TAG, "Found ${filteredProducts.size} products matching '$query'") + Result.Success(filteredProducts) + } else { + Result.Error(Exception("Failed to fetch products for search. Code: ${response.code()}")) + } + } catch (e: Exception) { + Log.e(TAG, "Error searching products", e) + Result.Error(e) + } + } + + suspend fun saveSearchQuery(query: String): Result = + withContext(Dispatchers.IO) { + try { + val response = apiService.saveSearchQuery(SearchRequest(query)) + + if (response.isSuccessful) { + Result.Success(response.body()?.search) + } else { + Log.e(TAG, "Failed to save search query. Code: ${response.code()}") + Result.Error(Exception("Failed to save search query")) + } + } catch (e: Exception) { + Log.e(TAG, "Error saving search query", e) + Result.Error(e) + } + } + + suspend fun getSearchHistory(): Result> = + withContext(Dispatchers.IO) { + try { + val response = apiService.getSearchHistory() + + if (response.isSuccessful) { + val searches = response.body()?.data?.map { it.searchQuery } ?: emptyList() + Result.Success(searches) + } else { + Log.e(TAG, "Failed to fetch search history. Code: ${response.code()}") + Result.Error(Exception("Failed to fetch search history")) + } + } catch (e: Exception) { + Log.e(TAG, "Error fetching search history", e) + Result.Error(e) + } + } + companion object { private const val TAG = "ProductRepository" 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 c0dd046..b9b6fc4 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,12 +6,14 @@ 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 import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.findNavController import androidx.recyclerview.widget.LinearLayoutManager import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.dto.CategoryItem @@ -63,6 +65,8 @@ class HomeFragment : Fragment() { initUi() setupRecyclerView() observeData() + setupSearchView() + } private fun setupRecyclerView() { @@ -95,6 +99,40 @@ class HomeFragment : Fragment() { } } + private fun setupSearchView() { + binding.searchContainer.search.apply { + // When user clicks the search box, navigate to search fragment + 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 + binding.searchContainer.btnCart.setOnClickListener { + // Navigate to cart + } + + binding.searchContainer.btnNotification.setOnClickListener { + // Navigate to notifications + } + } + private fun observeData() { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -109,7 +147,7 @@ class HomeFragment : Fragment() { binding.loading.root.isVisible = false binding.error.root.isVisible = false binding.home.isVisible = true - productAdapter?.updateLimitedProducts(state.products) // Ensure productAdapter is initialized + productAdapter?.updateLimitedProducts(state.products) } is HomeUiState.Error -> { binding.loading.root.isVisible = false @@ -125,18 +163,16 @@ class HomeFragment : Fragment() { } } - viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.categories.collect { categories -> Log.d("Categories", "Updated Categories: $categories") - categories.forEach { Log.d("Category Image", "Category: ${it.name}, Image: ${it.image}") } categoryAdapter?.updateLimitedCategory(categories) } } } } - private fun initUi() { // For LightStatusBar setLightStatusBar() @@ -161,7 +197,6 @@ class HomeFragment : Fragment() { ) } - private fun handleProductClick(product: ProductsItem) { val intent = Intent(requireContext(), DetailProductActivity::class.java) intent.putExtra("PRODUCT_ID", product.id) // Pass product ID @@ -169,7 +204,7 @@ class HomeFragment : Fragment() { } private fun handleCategoryProduct(category: CategoryItem) { - + // Your implementation } override fun onDestroyView() { @@ -179,7 +214,7 @@ class HomeFragment : Fragment() { _binding = null } - private fun showLoading(isLoading: Boolean) { - binding.progressBar.isVisible = isLoading - } +// private fun showLoading(isLoading: Boolean) { +// binding.progressBar.isVisible = isLoading +// } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchHistoryAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchHistoryAdapter.kt new file mode 100644 index 0000000..a00b3d7 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchHistoryAdapter.kt @@ -0,0 +1,56 @@ +package com.alya.ecommerce_serang.ui.home + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.alya.ecommerce_serang.databinding.ItemRecentSearchBinding + +class SearchHistoryAdapter( + private val onItemClick: (String) -> Unit +) : ListAdapter(DIFF_CALLBACK) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = ItemRecentSearchBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val query = getItem(position) + holder.bind(query) + } + + inner class ViewHolder(private val binding: ItemRecentSearchBinding) : + RecyclerView.ViewHolder(binding.root) { + + init { + binding.root.setOnClickListener { + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + onItemClick(getItem(position)) + } + } + } + + fun bind(query: String) { + binding.recentSearchText.text = query + } + } + + companion object { + private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: String, newItem: String): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame(oldItem: String, newItem: String): Boolean { + return oldItem == newItem + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..94003c6 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchHomeFragment.kt @@ -0,0 +1,162 @@ +package com.alya.ecommerce_serang.ui.home + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import androidx.recyclerview.widget.GridLayoutManager +import com.alya.ecommerce_serang.data.api.dto.ProductsItem +import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig +import com.alya.ecommerce_serang.data.repository.ProductRepository +import com.alya.ecommerce_serang.databinding.FragmentSearchHomeBinding +import com.alya.ecommerce_serang.ui.product.DetailProductActivity +import com.alya.ecommerce_serang.utils.BaseViewModelFactory +import com.alya.ecommerce_serang.utils.SessionManager + +class SearchHomeFragment : Fragment() { + private var _binding: FragmentSearchHomeBinding? = null + private val binding get() = _binding!! + private var searchResultsAdapter: SearchResultsAdapter? = null + private lateinit var sessionManager: SessionManager + private val args: SearchHomeFragmentArgs by navArgs() + + private val viewModel: SearchHomeViewModel by viewModels { + BaseViewModelFactory { + val apiService = ApiConfig.getApiService(sessionManager) + val productRepository = ProductRepository(apiService) + SearchHomeViewModel(productRepository) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + sessionManager = SessionManager(requireContext()) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentSearchHomeBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupUI() + setupSearchResultsRecyclerView() + observeData() + + // Perform search with the query passed from HomeFragment + args.query?.let { query -> + // Wait until layout is done, then set query text + binding.searchView.post { + binding.searchView.setQuery(query, false) // sets "food" as text, doesn't submit + } + + viewModel.searchProducts(query) + } + } + + private fun setupUI() { + // Setup back button + binding.backButton.setOnClickListener { + findNavController().navigateUp() + } + + // Setup search view + binding.searchView.apply { + setOnQueryTextListener(object : androidx.appcompat.widget.SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean { + query?.let { + if (it.isNotEmpty()) { + viewModel.searchProducts(it) + hideKeyboard() + } + } + return true + } + + override fun onQueryTextChange(newText: String?): Boolean { + newText?.let { + if (it.isEmpty()) { + // Clear the search results if user clears the input + searchResultsAdapter?.submitList(emptyList()) + binding.noResultsText.isVisible = false + return true + } + + // Optional: do real-time search + if (it.length >= 2) { + viewModel.searchProducts(it) + } + } + return true + } + }) + + // Request focus and show keyboard + if (args.query.isNullOrEmpty()) { + requestFocus() + postDelayed({ + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.showSoftInput(findFocus(), InputMethodManager.SHOW_IMPLICIT) + }, 200) + } + } + } + + private fun setupSearchResultsRecyclerView() { + searchResultsAdapter = SearchResultsAdapter { product -> + navigateToProductDetail(product) + } + + binding.searchResultsRecyclerView.apply { + adapter = searchResultsAdapter + layoutManager = GridLayoutManager(requireContext(), 2) + } + } + + private fun observeData() { + viewModel.searchResults.observe(viewLifecycleOwner) { products -> + + searchResultsAdapter?.submitList(products) + binding.noResultsText.isVisible = products.isEmpty() && !viewModel.isSearching.value!! + binding.searchResultsRecyclerView.isVisible = products.isNotEmpty() + } + + viewModel.isSearching.observe(viewLifecycleOwner) { isSearching -> + binding.progressBar.isVisible = isSearching + } + } + + private fun navigateToProductDetail(product: ProductsItem) { + val intent = Intent(requireContext(), DetailProductActivity::class.java) + intent.putExtra("PRODUCT_ID", product.id) + startActivity(intent) + } + + private fun hideKeyboard() { + val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + binding.searchView.let { + imm.hideSoftInputFromWindow(it.windowToken, 0) + } + } + + + override fun onDestroyView() { + super.onDestroyView() + searchResultsAdapter = null + _binding = null + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..fec5c4e --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchHomeViewModel.kt @@ -0,0 +1,76 @@ +package com.alya.ecommerce_serang.ui.home + +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.ProductsItem +import com.alya.ecommerce_serang.data.repository.ProductRepository +import com.alya.ecommerce_serang.data.repository.Result +import kotlinx.coroutines.launch + +class SearchHomeViewModel (private val productRepository: ProductRepository) : ViewModel() { + + private val _searchResults = MutableLiveData>(emptyList()) + val searchResults: LiveData> = _searchResults + + private val _searchHistory = MutableLiveData>(emptyList()) + val searchHistory: LiveData> = _searchHistory + + private val _isSearching = MutableLiveData(false) + val isSearching: LiveData = _isSearching + + private val _isSearchActive = MutableLiveData(false) + val isSearchActive: LiveData = _isSearchActive + + fun searchProducts(query: String) { + Log.d("HomeViewModel", "searchProducts called with query: '$query'") + + if (query.isBlank()) { + Log.d("HomeViewModel", "Query is blank, clearing results") + _searchResults.value = emptyList() + _isSearchActive.value = false + return + } + + _isSearching.value = true + _isSearchActive.value = true + + viewModelScope.launch { + Log.d("HomeViewModel", "Starting search coroutine") + + when (val result = productRepository.searchProducts(query)) { + is Result.Success -> { + Log.d("HomeViewModel", "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") + } + is Result.Error -> { + Log.e("HomeViewModel", "Search failed", result.exception) + _searchResults.postValue(emptyList()) + } + else -> {} + } + _isSearching.postValue(false) + } + } + + fun clearSearch() { + _isSearchActive.value = false + _searchResults.value = emptyList() + _isSearching.value = false + } + + fun loadSearchHistory() { + 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) + else -> {} + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..7a95fdc --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/SearchResultAdapter.kt @@ -0,0 +1,78 @@ +package com.alya.ecommerce_serang.ui.home + +import android.util.Log +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.alya.ecommerce_serang.R +import com.alya.ecommerce_serang.data.api.dto.ProductsItem +import com.alya.ecommerce_serang.databinding.ItemProductGridBinding +import com.bumptech.glide.Glide + +class SearchResultsAdapter( + private val onItemClick: (ProductsItem) -> Unit +) : ListAdapter(DIFF_CALLBACK) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = ItemProductGridBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val product = getItem(position) + holder.bind(product) + } + + inner class ViewHolder(private val binding: ItemProductGridBinding) : + RecyclerView.ViewHolder(binding.root) { + + init { + binding.root.setOnClickListener { + val position = adapterPosition + if (position != RecyclerView.NO_POSITION) { + onItemClick(getItem(position)) + } + } + } + + fun bind(product: ProductsItem) { + binding.productName.text = product.name + binding.productPrice.text = (product.price) + + // Load image with Glide + Glide.with(binding.root.context) + .load(product.image) + .placeholder(R.drawable.placeholder_image) +// .error(R.drawable.error_image) + .into(binding.productImage) + + // Set store name if available + product.storeId?.toString().let { + binding.storeName.text = it + } + } + } + + override fun submitList(list: List?) { + Log.d("SearchResultsAdapter", "Submitting list with ${list?.size ?: 0} items") + super.submitList(list) + } + + companion object { + private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: ProductsItem, newItem: ProductsItem): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: ProductsItem, newItem: ProductsItem): Boolean { + return oldItem == newItem + } + } + } +} \ 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 0e321ba..0f0437b 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 @@ -52,6 +52,8 @@ class HomeViewModel ( loadProducts() loadCategories() } + + } sealed class HomeUiState { diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 4deae79..1ce0c9b 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -6,34 +6,28 @@ xmlns:app="http://schemas.android.com/apk/res-auto" tools:context=".ui.home.HomeFragment"> + + + + android:layout_height="0dp" + app:layout_constraintTop_toBottomOf="@id/searchContainer" + app:layout_constraintBottom_toBottomOf="parent"> + - - - + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_search_home.xml b/app/src/main/res/layout/fragment_search_home.xml new file mode 100644 index 0000000..69eb47b --- /dev/null +++ b/app/src/main/res/layout/fragment_search_home.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + \ 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 new file mode 100644 index 0000000..cebc279 --- /dev/null +++ b/app/src/main/res/layout/item_product_grid.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_recent_search.xml b/app/src/main/res/layout/item_recent_search.xml new file mode 100644 index 0000000..c098004 --- /dev/null +++ b/app/src/main/res/layout/item_recent_search.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 99d7a48..ffe5064 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -9,7 +9,11 @@ android:id="@+id/homeFragment" android:name="com.alya.ecommerce_serang.ui.home.HomeFragment" android:label="fragment_home" - tools:layout="@layout/fragment_home" /> + tools:layout="@layout/fragment_home"> + + + + +