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"
tools:targetApi="31">
<activity
android:name=".ui.order.EditAddressActivity"
android:name=".ui.order.address.EditAddressActivity"
android:exported="false" />
<activity
android:name=".ui.order.AddAddressActivity"
android:name=".ui.order.address.AddAddressActivity"
android:exported="false" />
<activity
android:name=".ui.order.AddressActivity"
android:name=".ui.order.address.AddressActivity"
android:exported="false" />
<activity
android:name=".ui.order.ShippingActivity"

View File

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

View File

@ -1,8 +1,10 @@
package com.alya.ecommerce_serang.data.repository
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.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.ListCityResponse
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
}
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
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.cart.AddCartResponse
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.retrofit.ApiService
@ -79,6 +81,22 @@ class ProductRepository(private val apiService: ApiService) {
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? {

View File

@ -1,21 +1,103 @@
package com.alya.ecommerce_serang.ui.order
import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
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() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_shipping)
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
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?) {
super.onCreate(savedInstanceState)
binding = ActivityCheckoutBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager)
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.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 androidx.lifecycle.SavedStateHandle
@ -15,13 +15,13 @@ import kotlinx.coroutines.launch
class AddAddressViewModel(private val repository: OrderRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
// 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()
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()
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()
// Stored in SavedStateHandle for configuration changes
@ -46,7 +46,8 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s
_addressSubmissionState.value = ViewState.Success(message)
}
is Result.Error -> {
_addressSubmissionState.value = ViewState.Error(result.exception.message ?: "Unknown error")
_addressSubmissionState.value =
ViewState.Error(result.exception.message ?: "Unknown error")
}
is Result.Loading -> {
// Optional, karena sudah set Loading di awal
@ -60,7 +61,7 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s
try {
val result = repository.getListProvinces()
result?.let {
_provincesState.value = com.alya.ecommerce_serang.ui.order.ViewState.Success(it.provinces)
_provincesState.value = ViewState.Success(it.provinces)
}
} catch (e: Exception) {
Log.e("AddAddressViewModel", "Error fetching provinces: ${e.message}")
@ -74,7 +75,7 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s
selectedProvinceId = provinceId
val result = repository.getListCities(provinceId)
result?.let {
_citiesState.value = com.alya.ecommerce_serang.ui.order.ViewState.Success(it.cities)
_citiesState.value = ViewState.Success(it.cities)
}
} catch (e: Exception) {
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> {
object Loading : com.alya.ecommerce_serang.ui.order.ViewState<Nothing>()
data class Success<T>(val data: T) : com.alya.ecommerce_serang.ui.order.ViewState<T>()
data class Error(val message: String) : com.alya.ecommerce_serang.ui.order.ViewState<Nothing>()
object Loading : ViewState<Nothing>()
data class Success<T>(val data: T) : ViewState<T>()
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.os.Bundle
@ -64,4 +64,12 @@ class AddressActivity : AppCompatActivity() {
}
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.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.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 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.widget.ArrayAdapter

View File

@ -3,20 +3,25 @@ package com.alya.ecommerce_serang.ui.product
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.ImageButton
import android.widget.TextView
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
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.response.product.Product
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.ApiService
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.ui.home.HorizontalProductAdapter
import com.alya.ecommerce_serang.ui.order.CheckoutActivity
@ -31,6 +36,8 @@ class DetailProductActivity : AppCompatActivity() {
private lateinit var sessionManager: SessionManager
private var productAdapter: HorizontalProductAdapter? = null
private var reviewsAdapter: ReviewsAdapter? = null
private var currentQuantity = 1
private val viewModel: ProductViewModel by viewModels {
BaseViewModelFactory {
@ -47,43 +54,125 @@ class DetailProductActivity : AppCompatActivity() {
sessionManager = SessionManager(this)
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)
//nanti tambah get store id dari HomeFragment Product.storeId
if (productId == -1) {
Log.e("DetailProductActivity", "Invalid Product ID")
Toast.makeText(this, "Invalid product ID", Toast.LENGTH_SHORT).show()
finish() // Close activity if no valid ID
return
}
viewModel.loadProductDetail(productId)
viewModel.loadReviews(productId)
observeProductDetail()
observeProductReviews()
}
private fun observeProductDetail() {
private fun setupObservers() {
viewModel.productDetail.observe(this) { product ->
product?.let {
updateUI(it)
viewModel.loadOtherProducts(it.storeId)
}
}
viewModel.storeDetail.observe(this) { store ->
updateStoreInfo(store)
}
viewModel.otherProducts.observe(this) { products ->
updateOtherProducts(products)
}
}
private fun observeProductReviews() {
viewModel.reviewProduct.observe(this) { 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>) {
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){
binding.tvProductName.text = product.productName
@ -96,16 +185,6 @@ class DetailProductActivity : AppCompatActivity() {
binding.tvDescription.text = product.description
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) {
is String -> {
@ -119,8 +198,6 @@ class DetailProductActivity : AppCompatActivity() {
.load(fullImageUrl)
.placeholder(R.drawable.placeholder_image)
.into(binding.ivProductImage)
setupRecyclerViewOtherProducts()
}
private fun handleAllReviewsClick(productId: Int) {
@ -132,7 +209,7 @@ class DetailProductActivity : AppCompatActivity() {
private fun setupRecyclerViewOtherProducts(){
productAdapter = HorizontalProductAdapter(
products = emptyList(),
onClick = { productsItem -> handleProductClick(productsItem) }
onClick = { productsItem -> handleProductClick(productsItem) }
)
binding.recyclerViewOtherProducts.apply {
@ -147,7 +224,15 @@ class DetailProductActivity : AppCompatActivity() {
private fun setupRecyclerViewReviewsProduct(reviewList: List<ReviewsItem>){
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(
reviewList = limitedReviewList
)
@ -169,6 +254,14 @@ class DetailProductActivity : AppCompatActivity() {
}
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 view = layoutInflater.inflate(R.layout.dialog_count_buy, null)
bottomSheetDialog.setContentView(view)
@ -179,33 +272,59 @@ class DetailProductActivity : AppCompatActivity() {
val btnBuyNow = view.findViewById<Button>(R.id.btnBuyNow)
val btnClose = view.findViewById<ImageButton>(R.id.btnCloseDialog)
var quantity = 1
tvQuantity.text = quantity.toString()
// Set button text based on action
if (!isBuyNow) {
btnBuyNow.setText(R.string.add_to_cart)
}
currentQuantity = 1
tvQuantity.text = currentQuantity.toString()
val maxStock = viewModel.productDetail.value?.stock ?: 1
btnDecrease.setOnClickListener {
if (quantity > 1) {
quantity--
tvQuantity.text = quantity.toString()
if (currentQuantity > 1) {
currentQuantity--
tvQuantity.text = currentQuantity.toString()
}
}
btnIncrease.setOnClickListener {
quantity++
tvQuantity.text = quantity.toString()
if (currentQuantity < maxStock) {
currentQuantity++
tvQuantity.text = currentQuantity.toString()
} else {
Toast.makeText(this, "Maximum stock reached", Toast.LENGTH_SHORT).show()
}
}
btnBuyNow.setOnClickListener {
bottomSheetDialog.dismiss()
val intent = Intent(this, CheckoutActivity::class.java)
intent.putExtra("PRODUCT_ID", productId)
intent.putExtra("QUANTITY", quantity)
startActivity(intent)
}
val cartItem = CartItem(
productId = productId,
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 {
bottomSheetDialog.dismiss()
}
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.ViewModel
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.response.product.Product
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.Result
import kotlinx.coroutines.launch
@ -18,8 +19,8 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
private val _productDetail = MutableLiveData<Product?>()
val productDetail: LiveData<Product?> get() = _productDetail
private val _storeDetail = MutableLiveData<Store?>()
val storeDetail : LiveData<Store?> get() = _storeDetail
private val _storeDetail = MutableLiveData<StoreProduct?>()
val storeDetail : LiveData<StoreProduct?> get() = _storeDetail
private val _reviewProduct = MutableLiveData<List<ReviewsItem>>()
val reviewProduct: LiveData<List<ReviewsItem>> get() = _reviewProduct
@ -27,31 +28,96 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
private val _otherProducts = MutableLiveData<List<ProductsItem>>()
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) {
_isLoading.value = true
viewModelScope.launch {
val result = repository.fetchProductDetail(productId)
_productDetail.value = result?.product
try {
val result = repository.fetchProductDetail(productId)
_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) {
viewModelScope.launch {
val reviews = repository.fetchProductReview(productId)
_reviewProduct.value = reviews ?: emptyList()
try {
val reviews = repository.fetchProductReview(productId)
_reviewProduct.value = reviews ?: emptyList()
} catch (e: Exception) {
Log.e("ProductViewModel", "Error loading reviews: ${e.message}")
_reviewProduct.value = emptyList()
}
}
}
fun loadOtherProducts(storeId: Int) {
viewModelScope.launch {
val result = repository.getAllProducts() // Fetch products
try {
val result = repository.getAllProducts() // Fetch products
if (result is Result.Success) {
val allProducts = result.data // Extract the list
val filteredProducts = allProducts.filter { it.storeId == storeId } // Filter by storeId
_otherProducts.value = filteredProducts // Update LiveData
} else if (result is Result.Error) {
Log.e("ProductViewModel", "Error loading other products: ${result.exception.message}")
_otherProducts.value = emptyList() // Set empty list on failure
if (result is Result.Success) {
val allProducts = result.data // Extract the list
val filteredProducts = allProducts.filter {
it.storeId == storeId && it.id != _productDetail.value?.productId
} // Filter by storeId and exclude current product
_otherProducts.value = filteredProducts // Update LiveData
} else if (result is Result.Error) {
Log.e("ProductViewModel", "Error loading other products: ${result.exception.message}")
_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_height="match_parent"
android:background="@android:color/white"
tools:context=".ui.order.AddAddressActivity">
tools:context=".ui.order.address.AddAddressActivity">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"

View File

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

View File

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

View File

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

View File

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