call detail product and profile

This commit is contained in:
shaulascr
2025-03-25 17:17:28 +07:00
parent 726b5ab3d3
commit 6239745d82
14 changed files with 303 additions and 28 deletions

View File

@ -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,

View File

@ -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
)

View File

@ -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
)

View File

@ -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,

View File

@ -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<ProductResponse>
@GET("profile")
suspend fun getUserProfile(): Response<ProfileResponse>
@GET("mystore")
fun getStore (): Call<StoreResponse>
}

View File

@ -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) {

View File

@ -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<UserProfile?> {
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<out T> {

View File

@ -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")

View File

@ -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)
}
}

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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<UserProfile?>()
val userProfile: LiveData<UserProfile?> = _userProfile
private val _errorMessage = MutableLiveData<String>()
val errorMessage : LiveData<String> = _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
}
}
}
}

View File

@ -104,6 +104,7 @@
app:layout_constraintTop_toBottomOf="@id/card_profile">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/tv_name_user"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nama"
@ -120,6 +121,7 @@
app:layout_constraintTop_toBottomOf="@id/til_nama">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/tv_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Username"
@ -136,6 +138,7 @@
app:layout_constraintTop_toBottomOf="@id/til_username">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/tv_email_user"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email"
@ -152,6 +155,7 @@
app:layout_constraintTop_toBottomOf="@id/til_email">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/tv_number_phone_user"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nomor Handphone"
@ -168,6 +172,7 @@
app:layout_constraintTop_toBottomOf="@id/til_nomor_handphone">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/tv_date_birth"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Tanggal Lahir"

View File

@ -45,6 +45,8 @@
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="Detail Profil"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/profileImage" />
@ -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">