From 6239745d82f1eeb1e1482ae4b65d6ba36e3b1c91 Mon Sep 17 00:00:00 2001 From: shaulascr Date: Tue, 25 Mar 2025 17:17:28 +0700 Subject: [PATCH] call detail product and profile --- .../data/api/dto/UserProfile.kt | 2 +- .../data/api/response/AllStoreResponse.kt | 33 ++++++++ .../response/DetailStoreProductResponse.kt | 33 ++++++++ .../data/api/response/ProductResponse.kt | 2 +- .../data/api/retrofit/ApiService.kt | 5 ++ .../data/repository/ProductRepository.kt | 2 + .../data/repository/UserRepository.kt | 18 +++++ .../ecommerce_serang/ui/home/HomeFragment.kt | 4 +- .../ui/product/DetailProductActivity.kt | 48 ++++++++--- .../ui/profile/DetailProfileActivity.kt | 80 +++++++++++++++++-- .../ui/profile/ProfileFragment.kt | 70 +++++++++++++++- .../ui/profile/ProfileViewModel.kt | 25 +++++- .../res/layout/activity_detail_profile.xml | 5 ++ app/src/main/res/layout/fragment_profile.xml | 4 + 14 files changed, 303 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/com/alya/ecommerce_serang/data/api/response/AllStoreResponse.kt create mode 100644 app/src/main/java/com/alya/ecommerce_serang/data/api/response/DetailStoreProductResponse.kt diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/UserProfile.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/UserProfile.kt index 333a2ac..8a0a173 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/UserProfile.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/UserProfile.kt @@ -5,7 +5,7 @@ import com.google.gson.annotations.SerializedName data class UserProfile( @field:SerializedName("image") - val image: Any?, + val image: String? = null, @field:SerializedName("role") val role: String, diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/AllStoreResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/AllStoreResponse.kt new file mode 100644 index 0000000..4d2f860 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/AllStoreResponse.kt @@ -0,0 +1,33 @@ +package com.alya.ecommerce_serang.data.api.response + +import com.google.gson.annotations.SerializedName + +data class AllStoreResponse( + + @field:SerializedName("store") + val store: AllStore, + + @field:SerializedName("message") + val message: String +) + +data class AllStore( + + @field:SerializedName("store_name") + val storeName: String, + + @field:SerializedName("description") + val description: String, + + @field:SerializedName("store_type") + val storeType: String, + + @field:SerializedName("store_location") + val storeLocation: String, + + @field:SerializedName("store_image") + val storeImage: Any, + + @field:SerializedName("status") + val status: String +) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/DetailStoreProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/DetailStoreProductResponse.kt new file mode 100644 index 0000000..4f83751 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/DetailStoreProductResponse.kt @@ -0,0 +1,33 @@ +package com.alya.ecommerce_serang.data.api.response + +import com.google.gson.annotations.SerializedName + +data class DetailStoreProductResponse( + + @field:SerializedName("store") + val store: StoreProduct, + + @field:SerializedName("message") + val message: String +) + +data class StoreProduct( + + @field:SerializedName("store_name") + val storeName: String, + + @field:SerializedName("description") + val description: String, + + @field:SerializedName("store_type") + val storeType: String, + + @field:SerializedName("store_location") + val storeLocation: String, + + @field:SerializedName("store_image") + val storeImage: Any, + + @field:SerializedName("status") + val status: String +) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ProductResponse.kt index 041a2a4..448ad92 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ProductResponse.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ProductResponse.kt @@ -17,7 +17,7 @@ data class Product( val storeId: Int, @field:SerializedName("image") - val image: String, + val image: String? = null, @field:SerializedName("rating") val rating: 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 5a2954a..c9b951a 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 @@ -8,6 +8,7 @@ import com.alya.ecommerce_serang.data.api.response.CategoryResponse import com.alya.ecommerce_serang.data.api.response.LoginResponse import com.alya.ecommerce_serang.data.api.response.OtpResponse import com.alya.ecommerce_serang.data.api.response.ProductResponse +import com.alya.ecommerce_serang.data.api.response.ProfileResponse import com.alya.ecommerce_serang.data.api.response.RegisterResponse import com.alya.ecommerce_serang.data.api.response.ReviewProductResponse import com.alya.ecommerce_serang.data.api.response.StoreResponse @@ -51,6 +52,10 @@ interface ApiService { @Path("id") productId: Int ): Response + @GET("profile") + suspend fun getUserProfile(): Response + + @GET("mystore") fun getStore (): Call } \ 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 dcf5402..2c82c17 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 @@ -37,6 +37,7 @@ class ProductRepository(private val apiService: ApiService) { if (response.isSuccessful) { response.body() } else { + Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}") null } } catch (e: Exception) { @@ -70,6 +71,7 @@ class ProductRepository(private val apiService: ApiService) { if (response.isSuccessful) { response.body()?.reviews // Ambil daftar review dari response } else { + Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}") null } } catch (e: Exception) { diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt index 335cea9..0977624 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt @@ -3,6 +3,7 @@ package com.alya.ecommerce_serang.data.repository import com.alya.ecommerce_serang.data.api.dto.LoginRequest 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.UserProfile import com.alya.ecommerce_serang.data.api.response.LoginResponse import com.alya.ecommerce_serang.data.api.response.OtpResponse import com.alya.ecommerce_serang.data.api.retrofit.ApiService @@ -42,6 +43,23 @@ class UserRepository(private val apiService: ApiService) { Result.Error(e) } } + + suspend fun fetchUserProfile(): Result { + return try { + val response = apiService.getUserProfile() + if (response.isSuccessful) { + response.body()?.user?.let { + Result.Success(it) // ✅ Returning only UserProfile + } ?: Result.Error(Exception("User data not found")) + } else { + Result.Error(Exception("Error fetching profile: ${response.code()}")) + } + } catch (e: Exception) { + Result.Error(e) + } + } + + } sealed class Result { 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 279641c..c72dd76 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 @@ -107,7 +107,7 @@ class HomeFragment : Fragment() { binding.loading.root.isVisible = false binding.error.root.isVisible = false binding.home.isVisible = true - productAdapter?.updateLimitedProducts(state.products) + productAdapter?.updateLimitedProducts(state.products) // Ensure productAdapter is initialized } is HomeUiState.Error -> { binding.loading.root.isVisible = false @@ -123,7 +123,7 @@ 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") 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 cc5c7ea..b55ed61 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 @@ -4,13 +4,16 @@ import android.os.Bundle import android.util.Log import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import com.alya.ecommerce_serang.BuildConfig.BASE_URL +import com.alya.ecommerce_serang.R +import com.alya.ecommerce_serang.data.api.response.Product import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding -import com.alya.ecommerce_serang.ui.home.HomeViewModel import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager +import com.bumptech.glide.Glide class DetailProductActivity : AppCompatActivity() { private lateinit var binding: ActivityDetailProductBinding @@ -21,7 +24,7 @@ class DetailProductActivity : AppCompatActivity() { BaseViewModelFactory { val apiService = ApiConfig.getApiService(sessionManager) val productRepository = ProductRepository(apiService) - HomeViewModel(productRepository) + ProductViewModel(productRepository) } } override fun onCreate(savedInstanceState: Bundle?) { @@ -51,21 +54,42 @@ class DetailProductActivity : AppCompatActivity() { if (product != null) { Log.d("ProductDetail", "Name: ${product.productName}, Price: ${product.price}") // Update UI here, e.g., show in a TextView or ImageView - binding.tvProductName.text = product.productName - binding.tvPrice.text = product.price - binding.tvSold.text = product.totalSold.toString() - binding.tvRating.text = product.rating - binding.tvWeight.text = product.weight.toString() - binding.tvStock.text = product.stock.toString() - binding.tvCategory.text = product.productCategory - binding.tvDescription.text = product.description - binding.tvSellerName.text = product.storeId.toString() - + viewModel.loadProductDetail(productId) } else { Log.e("ProductDetail", "Failed to fetch product details") } } + observeProductDetail() + } + private fun observeProductDetail() { + viewModel.productDetail.observe(this) { product -> + product?.let { updateUI(it) } + } + } + private fun updateUI(product: Product){ + binding.tvProductName.text = product.productName + binding.tvPrice.text = product.price + binding.tvSold.text = product.totalSold.toString() + binding.tvRating.text = product.rating + binding.tvWeight.text = product.weight.toString() + binding.tvStock.text = product.stock.toString() + binding.tvCategory.text = product.productCategory + binding.tvDescription.text = product.description + binding.tvSellerName.text = product.storeId.toString() + + val fullImageUrl = when (val img = product.image) { + is String -> { + if (img.startsWith("/")) BASE_URL + img.substring(1) else img + } + else -> R.drawable.placeholder_image // Default image for null + } + Log.d("ProductAdapter", "Loading image: $fullImageUrl") + + Glide.with(this) + .load(fullImageUrl) + .placeholder(R.drawable.placeholder_image) + .into(binding.ivProductImage) } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/DetailProfileActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/DetailProfileActivity.kt index 4b806dd..af33617 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/DetailProfileActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/DetailProfileActivity.kt @@ -1,21 +1,85 @@ package com.alya.ecommerce_serang.ui.profile import android.os.Bundle +import android.widget.Toast import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import com.alya.ecommerce_serang.R +import com.alya.ecommerce_serang.data.api.dto.UserProfile +import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig +import com.alya.ecommerce_serang.data.api.retrofit.ApiService +import com.alya.ecommerce_serang.data.repository.UserRepository +import com.alya.ecommerce_serang.databinding.ActivityDetailProfileBinding +import com.alya.ecommerce_serang.utils.BaseViewModelFactory +import com.alya.ecommerce_serang.utils.SessionManager +import com.bumptech.glide.Glide +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.TimeZone class DetailProfileActivity : AppCompatActivity() { + private lateinit var binding: ActivityDetailProfileBinding + private lateinit var apiService: ApiService + private lateinit var sessionManager: SessionManager + + private val viewModel: ProfileViewModel by viewModels { + BaseViewModelFactory { + val apiService = ApiConfig.getApiService(sessionManager) + val userRepository = UserRepository(apiService) + ProfileViewModel(userRepository) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + binding = ActivityDetailProfileBinding.inflate(layoutInflater) + setContentView(binding.root) + + sessionManager = SessionManager(this) + apiService = ApiConfig.getApiService(sessionManager) + enableEdgeToEdge() - setContentView(R.layout.activity_detail_profile) - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> - val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) - insets +// ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> +// val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) +// v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) +// insets +// } + + viewModel.loadUserProfile() + + viewModel.userProfile.observe(this){ user -> + user?.let { updateProfile(it) } + } + + viewModel.errorMessage.observe(this) { error -> + Toast.makeText(this, error, Toast.LENGTH_SHORT).show() + } + } + + private fun updateProfile(user: UserProfile){ + + binding.tvNameUser.setText(user.name.toString()) + binding.tvUsername.setText(user.username) + binding.tvEmailUser.setText(user.email) + binding.tvDateBirth.setText(formatDate(user.birthDate)) + binding.tvNumberPhoneUser.setText(user.phone) + + if (user.image != null && user.image is String) { + Glide.with(this) + .load(user.image) + .into(binding.profileImage) + } + } + + private fun formatDate(dateString: String): String { + return try { + val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()) //from json + inputFormat.timeZone = TimeZone.getTimeZone("UTC") //get timezone + val outputFormat = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()) // new format + val date = inputFormat.parse(dateString) // Parse from json format + outputFormat.format(date!!) // convert to new format + } catch (e: Exception) { + dateString // Return original if error occurs } } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/ProfileFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/ProfileFragment.kt index f4ee0e1..71329ba 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/ProfileFragment.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/ProfileFragment.kt @@ -1,22 +1,43 @@ package com.alya.ecommerce_serang.ui.profile +import android.content.Intent import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.alya.ecommerce_serang.BuildConfig.BASE_URL +import com.alya.ecommerce_serang.R +import com.alya.ecommerce_serang.data.api.dto.UserProfile +import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig +import com.alya.ecommerce_serang.data.repository.UserRepository import com.alya.ecommerce_serang.databinding.FragmentProfileBinding +import com.alya.ecommerce_serang.ui.profile.mystore.TokoSayaActivity +import com.alya.ecommerce_serang.utils.BaseViewModelFactory +import com.alya.ecommerce_serang.utils.SessionManager +import com.bumptech.glide.Glide class ProfileFragment : Fragment() { private var _binding: FragmentProfileBinding? = null private val binding get() = _binding!! - private lateinit var viewModel: ProfileViewModel + private lateinit var sessionManager: SessionManager + + private val viewModel: ProfileViewModel by viewModels { + BaseViewModelFactory { + val apiService = ApiConfig.getApiService(sessionManager) + val userRepository = UserRepository(apiService) + ProfileViewModel(userRepository) + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + sessionManager = SessionManager(requireContext()) - // TODO: Use the ViewModel } override fun onCreateView( @@ -26,4 +47,49 @@ class ProfileFragment : Fragment() { _binding = FragmentProfileBinding.inflate(inflater, container, false) return binding.root } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeUserProfile() + viewModel.loadUserProfile() + + binding.cardBukaToko.setOnClickListener{ + val intentBuka = Intent(requireContext(), TokoSayaActivity::class.java) + startActivity(intentBuka) + } + + binding.btnDetailProfile.setOnClickListener{ + val intentDetail = Intent(requireContext(), DetailProfileActivity::class.java) + startActivity(intentDetail) + } + } + + private fun observeUserProfile() { + viewModel.userProfile.observe(viewLifecycleOwner) { user -> + user?.let { updateUI(it) } + } + viewModel.errorMessage.observe(viewLifecycleOwner) { errorMessage -> + Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show() + } + } + + private fun updateUI(user: UserProfile) = with(binding){ + val fullImageUrl = when (val img = user.image) { + is String -> { + if (img.startsWith("/")) BASE_URL + img.substring(1) else img + } + else -> R.drawable.placeholder_image // Default image for null + } + + Log.d("ProductAdapter", "Loading image: $fullImageUrl") + + tvName.text = user.name.toString() + tvUsername.text = user.username.toString() + + // Load image using Glide + Glide.with(requireContext()) + .load(fullImageUrl) + .placeholder(R.drawable.placeholder_image) + .into(profileImage) + } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/ProfileViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/ProfileViewModel.kt index fb0e34f..da1aed9 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/ProfileViewModel.kt @@ -1,7 +1,28 @@ package com.alya.ecommerce_serang.ui.profile +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.alya.ecommerce_serang.data.api.dto.UserProfile +import com.alya.ecommerce_serang.data.repository.Result +import com.alya.ecommerce_serang.data.repository.UserRepository +import kotlinx.coroutines.launch -class ProfileViewModel : ViewModel() { - // TODO: Implement the ViewModel +class ProfileViewModel(private val userRepository: UserRepository) : ViewModel() { + private val _userProfile = MutableLiveData() + val userProfile: LiveData = _userProfile + + private val _errorMessage = MutableLiveData() + val errorMessage : LiveData = _errorMessage + + fun loadUserProfile(){ + viewModelScope.launch { + when (val result = userRepository.fetchUserProfile()){ + is Result.Success -> _userProfile.postValue(result.data) + is Result.Error -> _errorMessage.postValue(result.exception.message ?: "Unknown Error") + is Result.Loading -> null + } + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_detail_profile.xml b/app/src/main/res/layout/activity_detail_profile.xml index 31bbdb2..389195b 100644 --- a/app/src/main/res/layout/activity_detail_profile.xml +++ b/app/src/main/res/layout/activity_detail_profile.xml @@ -104,6 +104,7 @@ app:layout_constraintTop_toBottomOf="@id/card_profile"> @@ -55,6 +57,8 @@ android:layout_height="wrap_content" android:paddingHorizontal="16dp" android:layout_marginTop="16dp" + android:clickable="true" + android:focusable="true" app:cardElevation="4dp" app:layout_constraintTop_toBottomOf="@id/profileImage">