fix detail and address

This commit is contained in:
shaulascr
2025-04-10 02:05:20 +07:00
parent 0f5df1f57f
commit e8bb99a8d7
21 changed files with 523 additions and 101 deletions

View File

@ -20,13 +20,13 @@
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:targetApi="31"> tools:targetApi="31">
<activity <activity
android:name=".ui.order.EditAddressActivity" android:name=".ui.order.address.EditAddressActivity"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".ui.order.AddAddressActivity" android:name=".ui.order.address.AddAddressActivity"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".ui.order.AddressActivity" android:name=".ui.order.address.AddressActivity"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".ui.order.ShippingActivity" android:name=".ui.order.ShippingActivity"

View File

@ -21,11 +21,8 @@ data class OrderRequest (
@SerializedName("is_negotiable") @SerializedName("is_negotiable")
val isNego: Boolean, val isNego: Boolean,
@SerializedName("product_id") @SerializedName("cart_items_id")
val productIdItem: Int, val cartItemId: List<Int>,
@SerializedName("quantity")
val quantity: Int,
@SerializedName("ship_etd") @SerializedName("ship_etd")
val shipEtd: String val shipEtd: String

View File

@ -1,8 +1,10 @@
package com.alya.ecommerce_serang.data.repository package com.alya.ecommerce_serang.data.repository
import android.util.Log import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
import com.alya.ecommerce_serang.data.api.dto.OrderRequest import com.alya.ecommerce_serang.data.api.dto.OrderRequest
import com.alya.ecommerce_serang.data.api.response.order.CourierCostResponse
import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse
import com.alya.ecommerce_serang.data.api.response.order.ListCityResponse import com.alya.ecommerce_serang.data.api.response.order.ListCityResponse
import com.alya.ecommerce_serang.data.api.response.order.ListProvinceResponse import com.alya.ecommerce_serang.data.api.response.order.ListProvinceResponse
@ -70,4 +72,19 @@ class OrderRepository(private val apiService: ApiService) {
return if (response.isSuccessful) response.body() else null return if (response.isSuccessful) response.body() else null
} }
suspend fun getCountCourierCost(courierCost: CourierCostRequest): Result<CourierCostResponse>{
return try {
val response = apiService.countCourierCost(courierCost)
if (response.isSuccessful){
response.body()?.let {
Result.Success(it)
} ?: Result.Error(Exception("Add Address failed"))
} else {
Log.e("OrderRepository", "Error: ${response.errorBody()?.string()}")
Result.Error(Exception(response.errorBody()?.string() ?: "Unknown error"))
}
} catch (e: Exception) {
Result.Error(e)
}
}
} }

View File

@ -1,8 +1,10 @@
package com.alya.ecommerce_serang.data.repository package com.alya.ecommerce_serang.data.repository
import android.util.Log 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.CategoryItem
import com.alya.ecommerce_serang.data.api.dto.ProductsItem import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.response.cart.AddCartResponse
import com.alya.ecommerce_serang.data.api.response.product.ProductResponse 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.ReviewsItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.api.retrofit.ApiService
@ -79,6 +81,22 @@ class ProductRepository(private val apiService: ApiService) {
null null
} }
} }
suspend fun addToCart(request: CartItem): Result<AddCartResponse> {
return try{
val response = apiService.addCart(request)
if (response.isSuccessful){
response.body()?.let {
Result.Success(it)
} ?: Result.Error(Exception("Add Cart failed"))
} else {
Log.e("OrderRepository", "Error: ${response.errorBody()?.string()}")
Result.Error(Exception(response.errorBody()?.string() ?: "Unknown Error"))
}
} catch (e: Exception){
Result.Error(e)
}
}
} }
// suspend fun fetchStoreDetail(storeId: Int): Store? { // suspend fun fetchStoreDetail(storeId: Int): Store? {

View File

@ -1,21 +1,103 @@
package com.alya.ecommerce_serang.ui.order package com.alya.ecommerce_serang.ui.order
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.enableEdgeToEdge import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.lifecycle.lifecycleScope
import androidx.core.view.WindowInsetsCompat import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.CostProduct
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
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.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.databinding.ActivityCheckoutBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.google.android.material.appbar.MaterialToolbar
import kotlinx.coroutines.launch
class ShippingActivity : AppCompatActivity() { class ShippingActivity : AppCompatActivity() {
private lateinit var binding: ActivityCheckoutBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
private lateinit var adapter: ShippingAdapter
private val viewModel: ShippingViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val orderRepository = OrderRepository(apiService)
ShippingViewModel(orderRepository)
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() binding = ActivityCheckoutBinding.inflate(layoutInflater)
setContentView(R.layout.activity_shipping) setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) sessionManager = SessionManager(this)
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) apiService = ApiConfig.getApiService(sessionManager)
insets
val recyclerView = findViewById<RecyclerView>(R.id.rv_shipment_order)
adapter = ShippingAdapter { selectedService ->
val intent = Intent().apply {
putExtra("ship_name", selectedService.service)
putExtra("ship_price", selectedService.cost)
putExtra("ship_service", selectedService.description)
}
setResult(RESULT_OK, intent)
finish()
}
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
val request = CourierCostRequest(
addressId = intent.getIntExtra("extra_address_id", 0),
itemCost = CostProduct(
productId = intent.getIntExtra("product_id", 0),
quantity = intent.getIntExtra("quantity", 1)
)
)
viewModel.fetchShippingServices(request)
lifecycleScope.launch {
viewModel.shippingServices.collect { result ->
result?.let {
when (it) {
is Result.Success -> adapter.submitList(it.data)
is Result.Error -> Toast.makeText(this@ShippingActivity, it.exception.message, Toast.LENGTH_SHORT).show()
is Result.Loading -> null
} }
} }
} }
}
findViewById<MaterialToolbar>(R.id.toolbar).setNavigationOnClickListener {
finish()
}
}
}
//val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
// if (result.resultCode == RESULT_OK) {
// val data = result.data
// val shipName = data?.getStringExtra("ship_name")
// val shipPrice = data?.getIntExtra("ship_price", 0)
// val shipService = data?.getStringExtra("ship_service")
// // use the data as needed
// }
//}
//
//// launch the shipping activity
//val intent = Intent(this, ShippingActivity::class.java).apply {
// putExtra("address_id", addressId)
// putExtra("product_id", productId)
// putExtra("quantity", quantity)
//}
//launcher.launch(intent)

View File

@ -0,0 +1,65 @@
package com.alya.ecommerce_serang.ui.order
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RadioButton
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.order.ServicesItem
class ShippingAdapter(
private val onItemSelected: (ServicesItem) -> Unit
) : RecyclerView.Adapter<ShippingAdapter.ShippingViewHolder>() {
private var services = listOf<ServicesItem>()
private var selectedPosition = -1
fun submitList(newList: List<ServicesItem>) {
services = newList
notifyDataSetChanged()
}
inner class ShippingViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val courierName = itemView.findViewById<TextView>(R.id.courier_name_cost)
private val estDate = itemView.findViewById<TextView>(R.id.est_date)
private val costPrice = itemView.findViewById<TextView>(R.id.cost_price)
private val radioButton = itemView.findViewById<RadioButton>(R.id.radio_btn_cost)
fun bind(service: ServicesItem, isSelected: Boolean) {
courierName.text = service.service // already includes courier name from ViewModel
estDate.text = "Estimasi ${service.etd}"
costPrice.text = "Rp${service.cost}"
radioButton.isChecked = isSelected
itemView.setOnClickListener {
if (adapterPosition != RecyclerView.NO_POSITION) {
selectedPosition = adapterPosition
notifyDataSetChanged()
onItemSelected(service)
}
}
radioButton.setOnClickListener {
if (adapterPosition != RecyclerView.NO_POSITION) {
selectedPosition = adapterPosition
notifyDataSetChanged()
onItemSelected(service)
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShippingViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_shipping_order, parent, false)
return ShippingViewHolder(view)
}
override fun onBindViewHolder(holder: ShippingViewHolder, position: Int) {
val service = services[position]
holder.bind(service, position == selectedPosition)
}
override fun getItemCount(): Int = services.size
}

View File

@ -0,0 +1,33 @@
package com.alya.ecommerce_serang.ui.order
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
import com.alya.ecommerce_serang.data.api.response.order.ServicesItem
import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class ShippingViewModel(private val repository: OrderRepository): ViewModel() {
private val _shippingServices = MutableStateFlow<Result<List<ServicesItem>>?>(null)
val shippingServices: StateFlow<Result<List<ServicesItem>>?> = _shippingServices
fun fetchShippingServices(request: CourierCostRequest) {
viewModelScope.launch {
val result = repository.getCountCourierCost(request)
if (result is Result.Success) {
val services = result.data.courierCosts.flatMap { courier ->
courier.services.map {
it.copy(service = "${courier.courier} - ${it.service}")
}
}
_shippingServices.value = Result.Success(services)
} else if (result is Result.Error) {
_shippingServices.value = Result.Error(result.exception)
}
}
}
}

View File

@ -1,4 +1,4 @@
package com.alya.ecommerce_serang.ui.order package com.alya.ecommerce_serang.ui.order.address
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.pm.PackageManager import android.content.pm.PackageManager

View File

@ -1,4 +1,4 @@
package com.alya.ecommerce_serang.ui.order package com.alya.ecommerce_serang.ui.order.address
import android.util.Log import android.util.Log
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
@ -15,13 +15,13 @@ import kotlinx.coroutines.launch
class AddAddressViewModel(private val repository: OrderRepository, private val savedStateHandle: SavedStateHandle): ViewModel() { class AddAddressViewModel(private val repository: OrderRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
// Flow states for data // Flow states for data
private val _addressSubmissionState = MutableStateFlow<com.alya.ecommerce_serang.ui.order.ViewState<String>>(com.alya.ecommerce_serang.ui.order.ViewState.Loading) private val _addressSubmissionState = MutableStateFlow<ViewState<String>>(ViewState.Loading)
val addressSubmissionState = _addressSubmissionState.asStateFlow() val addressSubmissionState = _addressSubmissionState.asStateFlow()
private val _provincesState = MutableStateFlow<com.alya.ecommerce_serang.ui.order.ViewState<List<ProvincesItem>>>(com.alya.ecommerce_serang.ui.order.ViewState.Loading) private val _provincesState = MutableStateFlow<ViewState<List<ProvincesItem>>>(ViewState.Loading)
val provincesState = _provincesState.asStateFlow() val provincesState = _provincesState.asStateFlow()
private val _citiesState = MutableStateFlow<com.alya.ecommerce_serang.ui.order.ViewState<List<CitiesItem>>>(com.alya.ecommerce_serang.ui.order.ViewState.Loading) private val _citiesState = MutableStateFlow<ViewState<List<CitiesItem>>>(ViewState.Loading)
val citiesState = _citiesState.asStateFlow() val citiesState = _citiesState.asStateFlow()
// Stored in SavedStateHandle for configuration changes // Stored in SavedStateHandle for configuration changes
@ -46,7 +46,8 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s
_addressSubmissionState.value = ViewState.Success(message) _addressSubmissionState.value = ViewState.Success(message)
} }
is Result.Error -> { is Result.Error -> {
_addressSubmissionState.value = ViewState.Error(result.exception.message ?: "Unknown error") _addressSubmissionState.value =
ViewState.Error(result.exception.message ?: "Unknown error")
} }
is Result.Loading -> { is Result.Loading -> {
// Optional, karena sudah set Loading di awal // Optional, karena sudah set Loading di awal
@ -60,7 +61,7 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s
try { try {
val result = repository.getListProvinces() val result = repository.getListProvinces()
result?.let { result?.let {
_provincesState.value = com.alya.ecommerce_serang.ui.order.ViewState.Success(it.provinces) _provincesState.value = ViewState.Success(it.provinces)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("AddAddressViewModel", "Error fetching provinces: ${e.message}") Log.e("AddAddressViewModel", "Error fetching provinces: ${e.message}")
@ -74,7 +75,7 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s
selectedProvinceId = provinceId selectedProvinceId = provinceId
val result = repository.getListCities(provinceId) val result = repository.getListCities(provinceId)
result?.let { result?.let {
_citiesState.value = com.alya.ecommerce_serang.ui.order.ViewState.Success(it.cities) _citiesState.value = ViewState.Success(it.cities)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("AddAddressViewModel", "Error fetching cities: ${e.message}") Log.e("AddAddressViewModel", "Error fetching cities: ${e.message}")
@ -99,7 +100,7 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s
} }
sealed class ViewState<out T> { sealed class ViewState<out T> {
object Loading : com.alya.ecommerce_serang.ui.order.ViewState<Nothing>() object Loading : ViewState<Nothing>()
data class Success<T>(val data: T) : com.alya.ecommerce_serang.ui.order.ViewState<T>() data class Success<T>(val data: T) : ViewState<T>()
data class Error(val message: String) : com.alya.ecommerce_serang.ui.order.ViewState<Nothing>() data class Error(val message: String) : ViewState<Nothing>()
} }

View File

@ -1,4 +1,4 @@
package com.alya.ecommerce_serang.ui.order package com.alya.ecommerce_serang.ui.order.address
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@ -65,3 +65,11 @@ class AddressActivity : AppCompatActivity() {
finish() finish()
} }
} }
//override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// super.onActivityResult(requestCode, resultCode, data)
// if (requestCode == REQUEST_ADDRESS && resultCode == RESULT_OK) {
// val selectedAddressId = data?.getIntExtra("selected_address_id", -1)
// // Use the selected address ID
// }
//}

View File

@ -1,4 +1,4 @@
package com.alya.ecommerce_serang.ui.order package com.alya.ecommerce_serang.ui.order.address
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View

View File

@ -1,4 +1,4 @@
package com.alya.ecommerce_serang.ui.order package com.alya.ecommerce_serang.ui.order.address
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData

View File

@ -1,4 +1,4 @@
package com.alya.ecommerce_serang.ui.order package com.alya.ecommerce_serang.ui.order.address
import android.os.Bundle import android.os.Bundle
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge

View File

@ -1,4 +1,4 @@
package com.alya.ecommerce_serang.ui.order package com.alya.ecommerce_serang.ui.order.address
import android.content.Context import android.content.Context
import android.widget.ArrayAdapter import android.widget.ArrayAdapter

View File

@ -3,20 +3,25 @@ package com.alya.ecommerce_serang.ui.product
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.BuildConfig.BASE_URL import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.CartItem
import com.alya.ecommerce_serang.data.api.dto.ProductsItem import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.response.product.Product import com.alya.ecommerce_serang.data.api.response.product.Product
import com.alya.ecommerce_serang.data.api.response.product.ReviewsItem import com.alya.ecommerce_serang.data.api.response.product.ReviewsItem
import com.alya.ecommerce_serang.data.api.response.product.StoreProduct
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.data.repository.ProductRepository
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding
import com.alya.ecommerce_serang.ui.home.HorizontalProductAdapter import com.alya.ecommerce_serang.ui.home.HorizontalProductAdapter
import com.alya.ecommerce_serang.ui.order.CheckoutActivity import com.alya.ecommerce_serang.ui.order.CheckoutActivity
@ -31,6 +36,8 @@ class DetailProductActivity : AppCompatActivity() {
private lateinit var sessionManager: SessionManager private lateinit var sessionManager: SessionManager
private var productAdapter: HorizontalProductAdapter? = null private var productAdapter: HorizontalProductAdapter? = null
private var reviewsAdapter: ReviewsAdapter? = null private var reviewsAdapter: ReviewsAdapter? = null
private var currentQuantity = 1
private val viewModel: ProductViewModel by viewModels { private val viewModel: ProductViewModel by viewModels {
BaseViewModelFactory { BaseViewModelFactory {
@ -47,43 +54,125 @@ class DetailProductActivity : AppCompatActivity() {
sessionManager = SessionManager(this) sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager) apiService = ApiConfig.getApiService(sessionManager)
// val productId = intent.getIntExtra("PRODUCT_ID", -1)
// //nanti tambah get store id dari HomeFragment Product.storeId
// if (productId == -1) {
// Log.e("DetailProductActivity", "Invalid Product ID")
// finish() // Close activity if no valid ID
// return
// }
setupUI()
setupObservers()
loadData()
}
private fun loadData() {
val productId = intent.getIntExtra("PRODUCT_ID", -1) val productId = intent.getIntExtra("PRODUCT_ID", -1)
//nanti tambah get store id dari HomeFragment Product.storeId
if (productId == -1) { if (productId == -1) {
Log.e("DetailProductActivity", "Invalid Product ID") Log.e("DetailProductActivity", "Invalid Product ID")
Toast.makeText(this, "Invalid product ID", Toast.LENGTH_SHORT).show()
finish() // Close activity if no valid ID finish() // Close activity if no valid ID
return return
} }
viewModel.loadProductDetail(productId) viewModel.loadProductDetail(productId)
viewModel.loadReviews(productId) viewModel.loadReviews(productId)
observeProductDetail()
observeProductReviews()
} }
private fun observeProductDetail() { private fun setupObservers() {
viewModel.productDetail.observe(this) { product -> viewModel.productDetail.observe(this) { product ->
product?.let { product?.let {
updateUI(it) updateUI(it)
viewModel.loadOtherProducts(it.storeId) viewModel.loadOtherProducts(it.storeId)
} }
} }
viewModel.storeDetail.observe(this) { store ->
updateStoreInfo(store)
}
viewModel.otherProducts.observe(this) { products -> viewModel.otherProducts.observe(this) { products ->
updateOtherProducts(products) updateOtherProducts(products)
} }
}
private fun observeProductReviews() {
viewModel.reviewProduct.observe(this) { reviews -> viewModel.reviewProduct.observe(this) { reviews ->
setupRecyclerViewReviewsProduct(reviews) setupRecyclerViewReviewsProduct(reviews)
} }
//
// viewModel.isLoading.observe(this) { isLoading ->
// binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
// }
//
// viewModel.error.observe(this) { errorMessage ->
// if (errorMessage.isNotEmpty()) {
// Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show()
// }
// }
viewModel.addCart.observe(this) { result ->
when (result) {
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
Toast.makeText(this, result.data, Toast.LENGTH_SHORT).show()
// Check if we need to navigate to checkout (for "Buy Now" flow)
if (viewModel.shouldNavigateToCheckout) {
viewModel.shouldNavigateToCheckout = false
navigateToCheckout()
}
}
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
Toast.makeText(this, "Failed to add to cart: ${result.exception.message}", Toast.LENGTH_SHORT).show()
}
is Result.Loading -> {
// Show loading indicator if needed
}
}
}
}
private fun updateStoreInfo(store: StoreProduct?) {
store?.let {
binding.tvSellerName.text = it.storeName
// Add more store details as needed
}
} }
private fun updateOtherProducts(products: List<ProductsItem>) { private fun updateOtherProducts(products: List<ProductsItem>) {
productAdapter?.updateProducts(products) // Make sure your adapter has a method to update data if (products.isEmpty()) {
binding.recyclerViewOtherProducts.visibility = View.GONE
binding.tvViewAllProducts.visibility = View.GONE
} else {
binding.recyclerViewOtherProducts.visibility = View.VISIBLE
binding.tvViewAllProducts.visibility = View.VISIBLE
productAdapter?.updateProducts(products)
} }
private fun setupUI() {
// binding.btnBack.setOnClickListener {
// finish()
// }
binding.tvViewAllReviews.setOnClickListener {
viewModel.productDetail.value?.productId?.let { productId ->
handleAllReviewsClick(productId)
}
} }
binding.btnBuyNow.setOnClickListener {
viewModel.productDetail.value?.productId?.let { id ->
showBuyNowPopup(id)
}
}
binding.btnAddToCart.setOnClickListener {
viewModel.productDetail.value?.productId?.let { id ->
showAddToCartPopup(id)
}
}
setupRecyclerViewOtherProducts()
}
private fun updateUI(product: Product){ private fun updateUI(product: Product){
binding.tvProductName.text = product.productName binding.tvProductName.text = product.productName
@ -96,16 +185,6 @@ class DetailProductActivity : AppCompatActivity() {
binding.tvDescription.text = product.description binding.tvDescription.text = product.description
binding.tvSellerName.text = product.storeId.toString() binding.tvSellerName.text = product.storeId.toString()
binding.tvViewAllReviews.setOnClickListener{
handleAllReviewsClick(product.productId)
}
binding.btnBuyNow.setOnClickListener {
viewModel.productDetail.value?.productId?.let { id ->
showBuyNowPopup(id)
}
}
val fullImageUrl = when (val img = product.image) { val fullImageUrl = when (val img = product.image) {
is String -> { is String -> {
@ -119,8 +198,6 @@ class DetailProductActivity : AppCompatActivity() {
.load(fullImageUrl) .load(fullImageUrl)
.placeholder(R.drawable.placeholder_image) .placeholder(R.drawable.placeholder_image)
.into(binding.ivProductImage) .into(binding.ivProductImage)
setupRecyclerViewOtherProducts()
} }
private fun handleAllReviewsClick(productId: Int) { private fun handleAllReviewsClick(productId: Int) {
@ -147,7 +224,15 @@ class DetailProductActivity : AppCompatActivity() {
private fun setupRecyclerViewReviewsProduct(reviewList: List<ReviewsItem>){ private fun setupRecyclerViewReviewsProduct(reviewList: List<ReviewsItem>){
val limitedReviewList = if (reviewList.isNotEmpty()) listOf(reviewList.first()) else emptyList() val limitedReviewList = if (reviewList.isNotEmpty()) listOf(reviewList.first()) else emptyList()
if (reviewList.isEmpty()) {
binding.recyclerViewReviews.visibility = View.GONE
binding.tvViewAllReviews.visibility = View.GONE
// binding.tvNoReviews.visibility = View.VISIBLE
} else {
binding.recyclerViewReviews.visibility = View.VISIBLE
binding.tvViewAllReviews.visibility = View.VISIBLE
}
// binding.tvNoReviews.visibility = View.GONE
reviewsAdapter = ReviewsAdapter( reviewsAdapter = ReviewsAdapter(
reviewList = limitedReviewList reviewList = limitedReviewList
) )
@ -169,6 +254,14 @@ class DetailProductActivity : AppCompatActivity() {
} }
private fun showBuyNowPopup(productId: Int) { private fun showBuyNowPopup(productId: Int) {
showQuantityDialog(productId, true)
}
private fun showAddToCartPopup(productId: Int) {
showQuantityDialog(productId, false)
}
private fun showQuantityDialog(productId: Int, isBuyNow: Boolean) {
val bottomSheetDialog = BottomSheetDialog(this) val bottomSheetDialog = BottomSheetDialog(this)
val view = layoutInflater.inflate(R.layout.dialog_count_buy, null) val view = layoutInflater.inflate(R.layout.dialog_count_buy, null)
bottomSheetDialog.setContentView(view) bottomSheetDialog.setContentView(view)
@ -179,33 +272,59 @@ class DetailProductActivity : AppCompatActivity() {
val btnBuyNow = view.findViewById<Button>(R.id.btnBuyNow) val btnBuyNow = view.findViewById<Button>(R.id.btnBuyNow)
val btnClose = view.findViewById<ImageButton>(R.id.btnCloseDialog) val btnClose = view.findViewById<ImageButton>(R.id.btnCloseDialog)
var quantity = 1 // Set button text based on action
tvQuantity.text = quantity.toString() if (!isBuyNow) {
btnBuyNow.setText(R.string.add_to_cart)
}
currentQuantity = 1
tvQuantity.text = currentQuantity.toString()
val maxStock = viewModel.productDetail.value?.stock ?: 1
btnDecrease.setOnClickListener { btnDecrease.setOnClickListener {
if (quantity > 1) { if (currentQuantity > 1) {
quantity-- currentQuantity--
tvQuantity.text = quantity.toString() tvQuantity.text = currentQuantity.toString()
} }
} }
btnIncrease.setOnClickListener { btnIncrease.setOnClickListener {
quantity++ if (currentQuantity < maxStock) {
tvQuantity.text = quantity.toString() currentQuantity++
tvQuantity.text = currentQuantity.toString()
} else {
Toast.makeText(this, "Maximum stock reached", Toast.LENGTH_SHORT).show()
} }
}
btnBuyNow.setOnClickListener { btnBuyNow.setOnClickListener {
bottomSheetDialog.dismiss() bottomSheetDialog.dismiss()
val intent = Intent(this, CheckoutActivity::class.java)
intent.putExtra("PRODUCT_ID", productId) val cartItem = CartItem(
intent.putExtra("QUANTITY", quantity) productId = productId,
startActivity(intent) quantity = currentQuantity
)
// For both Buy Now and Add to Cart, we add to cart first
if (isBuyNow) {
// Set flag to navigate to checkout after adding to cart is successful
viewModel.shouldNavigateToCheckout = true
} }
// Add to cart in both cases
viewModel.reqCart(cartItem)
btnClose.setOnClickListener { btnClose.setOnClickListener {
bottomSheetDialog.dismiss() bottomSheetDialog.dismiss()
} }
bottomSheetDialog.show() bottomSheetDialog.show()
} }
}
private fun navigateToCheckout() {
val intent = Intent(this, CheckoutActivity::class.java)
startActivity(intent)
}
} }

View File

@ -5,10 +5,11 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.CartItem
import com.alya.ecommerce_serang.data.api.dto.ProductsItem import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.response.product.Product import com.alya.ecommerce_serang.data.api.response.product.Product
import com.alya.ecommerce_serang.data.api.response.product.ReviewsItem import com.alya.ecommerce_serang.data.api.response.product.ReviewsItem
import com.alya.ecommerce_serang.data.api.response.product.Store import com.alya.ecommerce_serang.data.api.response.product.StoreProduct
import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.data.repository.ProductRepository
import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.data.repository.Result
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -18,8 +19,8 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
private val _productDetail = MutableLiveData<Product?>() private val _productDetail = MutableLiveData<Product?>()
val productDetail: LiveData<Product?> get() = _productDetail val productDetail: LiveData<Product?> get() = _productDetail
private val _storeDetail = MutableLiveData<Store?>() private val _storeDetail = MutableLiveData<StoreProduct?>()
val storeDetail : LiveData<Store?> get() = _storeDetail val storeDetail : LiveData<StoreProduct?> get() = _storeDetail
private val _reviewProduct = MutableLiveData<List<ReviewsItem>>() private val _reviewProduct = MutableLiveData<List<ReviewsItem>>()
val reviewProduct: LiveData<List<ReviewsItem>> get() = _reviewProduct val reviewProduct: LiveData<List<ReviewsItem>> get() = _reviewProduct
@ -27,32 +28,97 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
private val _otherProducts = MutableLiveData<List<ProductsItem>>() private val _otherProducts = MutableLiveData<List<ProductsItem>>()
val otherProducts: LiveData<List<ProductsItem>> get() = _otherProducts val otherProducts: LiveData<List<ProductsItem>> get() = _otherProducts
private val _addCart = MutableLiveData<Result<String>>()
val addCart: LiveData<Result<String>> get() = _addCart
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> get() = _isLoading
private val _error = MutableLiveData<String>()
val error: LiveData<String> get() = _error
// Flag to indicate if we should navigate to checkout after adding to cart
var shouldNavigateToCheckout: Boolean = false
fun loadProductDetail(productId: Int) { fun loadProductDetail(productId: Int) {
_isLoading.value = true
viewModelScope.launch { viewModelScope.launch {
try {
val result = repository.fetchProductDetail(productId) val result = repository.fetchProductDetail(productId)
_productDetail.value = result?.product _productDetail.value = result?.product
// Load store details if product has a store ID
// result?.product?.storeId?.let { storeId ->
// loadStoreDetail(storeId)
// }
} catch (e: Exception) {
Log.e("ProductViewModel", "Error loading product details: ${e.message}")
_error.value = "Failed to load product details: ${e.message}"
} finally {
_isLoading.value = false
}
} }
} }
// fun loadStoreDetail(storeId: Int) {
// viewModelScope.launch {
// try {
// val result = repository.fetchStoreDetail(storeId)
// _storeDetail.value = result
// } catch (e: Exception) {
// Log.e("ProductViewModel", "Error loading store details: ${e.message}")
// }
// }
// }
fun loadReviews(productId: Int) { fun loadReviews(productId: Int) {
viewModelScope.launch { viewModelScope.launch {
try {
val reviews = repository.fetchProductReview(productId) val reviews = repository.fetchProductReview(productId)
_reviewProduct.value = reviews ?: emptyList() _reviewProduct.value = reviews ?: emptyList()
} catch (e: Exception) {
Log.e("ProductViewModel", "Error loading reviews: ${e.message}")
_reviewProduct.value = emptyList()
}
} }
} }
fun loadOtherProducts(storeId: Int) { fun loadOtherProducts(storeId: Int) {
viewModelScope.launch { viewModelScope.launch {
try {
val result = repository.getAllProducts() // Fetch products val result = repository.getAllProducts() // Fetch products
if (result is Result.Success) { if (result is Result.Success) {
val allProducts = result.data // Extract the list val allProducts = result.data // Extract the list
val filteredProducts = allProducts.filter { it.storeId == storeId } // Filter by storeId val filteredProducts = allProducts.filter {
it.storeId == storeId && it.id != _productDetail.value?.productId
} // Filter by storeId and exclude current product
_otherProducts.value = filteredProducts // Update LiveData _otherProducts.value = filteredProducts // Update LiveData
} else if (result is Result.Error) { } else if (result is Result.Error) {
Log.e("ProductViewModel", "Error loading other products: ${result.exception.message}") Log.e("ProductViewModel", "Error loading other products: ${result.exception.message}")
_otherProducts.value = emptyList() // Set empty list on failure _otherProducts.value = emptyList() // Set empty list on failure
} }
} catch (e: Exception) {
Log.e("ProductViewModel", "Exception loading other products: ${e.message}")
_otherProducts.value = emptyList()
}
}
}
fun reqCart(request: CartItem){
viewModelScope.launch {
when (val result = repository.addToCart(request)) {
is Result.Success -> {
val message = result.data.message
_addCart.value =
Result.Success(message)
}
is Result.Error -> {
_addCart.value = Result.Error(result.exception)
}
is Result.Loading -> {
// optional: already emitted above
}
}
} }
} }

View File

@ -6,7 +6,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@android:color/white" android:background="@android:color/white"
tools:context=".ui.order.AddAddressActivity"> tools:context=".ui.order.address.AddAddressActivity">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"

View File

@ -5,7 +5,7 @@
android:id="@+id/main" android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.order.AddressActivity"> tools:context=".ui.order.address.AddressActivity">
<LinearLayout <LinearLayout
android:id="@+id/linear_toolbar" android:id="@+id/linear_toolbar"

View File

@ -5,6 +5,6 @@
android:id="@+id/main" android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.order.EditAddressActivity"> tools:context=".ui.order.address.EditAddressActivity">
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -24,7 +24,7 @@
app:layout_constraintTop_toBottomOf="@+id/toolbar" app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintStart_toStartOf="parent"> app:layout_constraintStart_toStartOf="parent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_seller_order" android:id="@+id/rv_shipment_order"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:listitem="@layout/item_shipping_order"/> tools:listitem="@layout/item_shipping_order"/>

View File

@ -7,9 +7,10 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:gravity="start" android:gravity="start"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginVertical="8dp"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:orientation="horizontal"> android:orientation="horizontal">
<RadioButton <RadioButton
@ -20,21 +21,36 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView <TextView
android:id="@+id/courier_name_cost" android:id="@+id/courier_name_cost"
android:fontFamily="@font/dmsans_bold" android:fontFamily="@font/dmsans_bold"
android:textSize="20sp" android:textSize="20sp"
android:padding="8dp" android:padding="4dp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="JNE"/> android:text="JNE"/>
<TextView
android:id="@+id/est_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:paddingHorizontal="8dp"
android:text="Estimasi 3-4 hari"/>
</LinearLayout>
<TextView <TextView
android:id="@+id/cost_price" android:id="@+id/cost_price"
android:padding="8dp"
android:textSize="16sp" android:textSize="16sp"
android:layout_width="wrap_content" android:gravity="center_vertical"
android:layout_height="wrap_content" android:layout_margin="16dp"
android:fontFamily="@font/dmsans_semibold"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Rp15.0000"/> android:text="Rp15.0000"/>
</LinearLayout> </LinearLayout>