mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-10 09:22:21 +00:00
fix detail and address
This commit is contained in:
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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? {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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>()
|
||||
}
|
@ -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
|
||||
// }
|
||||
//}
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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>
|
@ -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"/>
|
||||
|
@ -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>
|
||||
|
Reference in New Issue
Block a user