mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-10 17:32:22 +00:00
fix order (minus add address and selectedaddressid)
This commit is contained in:
@ -1,12 +1,16 @@
|
||||
package com.alya.ecommerce_serang.data.api.dto
|
||||
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.CartItemsItem
|
||||
|
||||
data class CheckoutData(
|
||||
val orderRequest: OrderRequest,
|
||||
// Additional UI-related data
|
||||
val productName: String,
|
||||
val productImageUrl: String,
|
||||
val productPrice: Double,
|
||||
val sellerName: String,
|
||||
val sellerImageUrl: String,
|
||||
val sellerId: Int
|
||||
val orderRequest: Any, // Can be OrderRequest or OrderRequestBuy
|
||||
val productName: String? = "",
|
||||
val productImageUrl: String = "",
|
||||
val productPrice: Double = 0.0,
|
||||
val sellerName: String = "",
|
||||
val sellerImageUrl: String? = null,
|
||||
val sellerId: Int = 0,
|
||||
val quantity: Int = 1,
|
||||
val isBuyNow: Boolean = false,
|
||||
val cartItems: List<CartItemsItem> = emptyList()
|
||||
)
|
@ -0,0 +1,33 @@
|
||||
package com.alya.ecommerce_serang.data.api.dto
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class OrderRequestBuy (
|
||||
@SerializedName("address_id")
|
||||
val addressId : Int,
|
||||
|
||||
@SerializedName("payment_method_id")
|
||||
val paymentMethodId : Int,
|
||||
|
||||
@SerializedName("ship_price")
|
||||
val shipPrice : Int,
|
||||
|
||||
@SerializedName("ship_name")
|
||||
val shipName : String,
|
||||
|
||||
@SerializedName("ship_service")
|
||||
val shipService : String,
|
||||
|
||||
@SerializedName("is_negotiable")
|
||||
val isNego: Boolean,
|
||||
|
||||
@SerializedName("product_id")
|
||||
val productId: Int,
|
||||
|
||||
@SerializedName("quantity")
|
||||
val quantity : Int,
|
||||
|
||||
|
||||
@SerializedName("ship_etd")
|
||||
val shipEtd: String
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
package com.alya.ecommerce_serang.data.api.response.order
|
||||
package com.alya.ecommerce_serang.data.api.response.cart
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
@ -4,15 +4,36 @@ import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class DetailStoreProductResponse(
|
||||
|
||||
@field:SerializedName("store")
|
||||
@field:SerializedName("store")
|
||||
val store: StoreProduct,
|
||||
|
||||
@field:SerializedName("message")
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class PaymentInfoItem(
|
||||
|
||||
@field:SerializedName("qris_image")
|
||||
val qrisImage: String,
|
||||
|
||||
@field:SerializedName("bank_num")
|
||||
val bankNum: String,
|
||||
|
||||
@field:SerializedName("name")
|
||||
val name: String
|
||||
)
|
||||
|
||||
data class StoreProduct(
|
||||
|
||||
@field:SerializedName("store_id")
|
||||
val storeId: Int,
|
||||
|
||||
@field:SerializedName("shipping_service")
|
||||
val shippingService: List<ShippingServiceItem>,
|
||||
|
||||
@field:SerializedName("store_rating")
|
||||
val storeRating: String,
|
||||
|
||||
@field:SerializedName("store_name")
|
||||
val storeName: String,
|
||||
|
||||
@ -22,12 +43,21 @@ data class StoreProduct(
|
||||
@field:SerializedName("store_type")
|
||||
val storeType: String,
|
||||
|
||||
@field:SerializedName("payment_info")
|
||||
val paymentInfo: List<PaymentInfoItem>,
|
||||
|
||||
@field:SerializedName("store_location")
|
||||
val storeLocation: String,
|
||||
|
||||
@field:SerializedName("store_image")
|
||||
val storeImage: String? = null,
|
||||
val storeImage: String,
|
||||
|
||||
@field:SerializedName("status")
|
||||
val status: String
|
||||
)
|
||||
|
||||
data class ShippingServiceItem(
|
||||
|
||||
@field:SerializedName("courier")
|
||||
val courier: String
|
||||
)
|
||||
|
@ -5,6 +5,7 @@ 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.LoginRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
|
||||
@ -13,11 +14,11 @@ import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.AddCartResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.ListCartResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.UpdateCartResponse
|
||||
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
|
||||
import com.alya.ecommerce_serang.data.api.response.order.UpdateCartResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.AllProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.CategoryResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.DetailStoreProductResponse
|
||||
@ -80,6 +81,11 @@ interface ApiService {
|
||||
@Body request: OrderRequest
|
||||
): Response<CreateOrderResponse>
|
||||
|
||||
@POST("order")
|
||||
suspend fun postOrderBuyNow(
|
||||
@Body request: OrderRequestBuy
|
||||
): Response<CreateOrderResponse>
|
||||
|
||||
@GET("profile/address")
|
||||
suspend fun getAddress(
|
||||
): Response<AddressResponse>
|
||||
|
@ -4,11 +4,14 @@ 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.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.DataItem
|
||||
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
|
||||
import com.alya.ecommerce_serang.data.api.response.product.ProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.product.StoreProduct
|
||||
import com.alya.ecommerce_serang.data.api.response.product.StoreResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.profile.AddressResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.profile.CreateAddressResponse
|
||||
@ -21,31 +24,163 @@ class OrderRepository(private val apiService: ApiService) {
|
||||
return try {
|
||||
val response = apiService.getDetailProduct(productId)
|
||||
if (response.isSuccessful) {
|
||||
response.body()
|
||||
val productResponse = response.body()
|
||||
Log.d("Order Repository", "Product detail fetched successfully: ${productResponse?.product?.productName}")
|
||||
productResponse
|
||||
} else {
|
||||
Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Error fetching product detail. Code: ${response.code()}, Error: $errorBody")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception fetching product", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createOrder(orderRequest: OrderRequest): Response<CreateOrderResponse> {
|
||||
return apiService.postOrder(orderRequest)
|
||||
return try {
|
||||
Log.d("Order Repository", "Creating order. Request details: $orderRequest")
|
||||
val response = apiService.postOrder(orderRequest)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
Log.d("Order Repository", "Order created successfully. Response: ${response.body()}")
|
||||
} else {
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Order creation failed. Code: ${response.code()}, Error: $errorBody")
|
||||
}
|
||||
|
||||
response
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception creating order", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createOrderBuyNow(orderRequestBuy: OrderRequestBuy): Response<CreateOrderResponse> {
|
||||
return try {
|
||||
Log.d("Order Repository", "Creating buy now order. Request details: $orderRequestBuy")
|
||||
val response = apiService.postOrderBuyNow(orderRequestBuy)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
Log.d("Order Repository", "Buy now order created successfully. Response: ${response.body()}")
|
||||
} else {
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Buy now order creation failed. Code: ${response.code()}, Error: $errorBody")
|
||||
}
|
||||
|
||||
response
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception creating buy now order", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getStore(): StoreResponse? {
|
||||
val response = apiService.getStore()
|
||||
return if (response.isSuccessful) response.body() else null
|
||||
return try {
|
||||
val response = apiService.getStore()
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val storeResponse = response.body()
|
||||
Log.d("Order Repository", "Store information fetched successfully. Store count: ${storeResponse?.store?.storeName}")
|
||||
storeResponse
|
||||
} else {
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Error fetching store. Code: ${response.code()}, Error: $errorBody")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception getting store", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getAddress(): AddressResponse?{
|
||||
val response = apiService.getAddress()
|
||||
return if (response.isSuccessful) response.body() else null
|
||||
suspend fun getAddress(): AddressResponse? {
|
||||
return try {
|
||||
val response = apiService.getAddress()
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val addressResponse = response.body()
|
||||
Log.d("Order Repository", "Address information fetched successfully. Address count: ${addressResponse?.addresses?.size}")
|
||||
addressResponse
|
||||
} else {
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Error fetching addresses. Code: ${response.code()}, Error: $errorBody")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception getting addresses", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getCountCourierCost(courierCost: CourierCostRequest): Result<CourierCostResponse> {
|
||||
return try {
|
||||
Log.d("Order Repository", "Calculating courier cost. Request: $courierCost")
|
||||
val response = apiService.countCourierCost(courierCost)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
response.body()?.let { courierCostResponse ->
|
||||
Log.d("Order Repository", "Courier cost calculation successful. Courier costs: ${courierCostResponse.courierCosts.size}")
|
||||
Result.Success(courierCostResponse)
|
||||
} ?: run {
|
||||
Result.Error(Exception("Failed to get courier cost: Empty response"))
|
||||
}
|
||||
} else {
|
||||
val errorMsg = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Error calculating courier cost. Code: ${response.code()}, Error: $errorMsg")
|
||||
Result.Error(Exception(errorMsg))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception calculating courier cost", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getCart(): Result<List<DataItem>> {
|
||||
return try {
|
||||
val response = apiService.getCart()
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val cartData = response.body()?.data
|
||||
if (!cartData.isNullOrEmpty()) {
|
||||
Result.Success(cartData)
|
||||
} else {
|
||||
Log.e("Order Repository", "Cart data is empty")
|
||||
Result.Error(Exception("Cart is empty"))
|
||||
}
|
||||
} else {
|
||||
val errorMsg = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Error fetching cart: $errorMsg")
|
||||
Result.Error(Exception(errorMsg))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception fetching cart", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchStoreDetail(storeId: Int): Result<StoreProduct?> {
|
||||
return try {
|
||||
val response = apiService.getDetailStore(storeId)
|
||||
if (response.isSuccessful) {
|
||||
val store = response.body()?.store
|
||||
if (store != null) {
|
||||
Result.Success(store)
|
||||
} else {
|
||||
Result.Error(Exception("Store details not found"))
|
||||
}
|
||||
} else {
|
||||
val errorMsg = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("Order Repository", "Error fetching store: $errorMsg")
|
||||
Result.Error(Exception(errorMsg))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Order Repository", "Exception fetching store details", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
//post data with message/response
|
||||
suspend fun addAddress(createAddressRequest: CreateAddressRequest): Result<CreateAddressResponse> {
|
||||
return try {
|
||||
val response = apiService.createAddress(createAddressRequest)
|
||||
@ -72,19 +207,4 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ 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.response.product.StoreProduct
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -21,26 +22,37 @@ class ProductRepository(private val apiService: ApiService) {
|
||||
if (response.isSuccessful) {
|
||||
// Return a Result.Success with the list of products
|
||||
|
||||
Result.Success(response.body()?.products ?: emptyList())
|
||||
val products = response.body()?.products ?: emptyList()
|
||||
Log.d(TAG, "Products fetched successfully. Total products: ${products.size}")
|
||||
|
||||
// Optional: Log some product details
|
||||
products.take(3).forEach { product ->
|
||||
Log.d(TAG, "Sample Product - ID: ${product.id}, Name: ${product.name}, Price: ${product.price}")
|
||||
}
|
||||
|
||||
Result.Success(products)
|
||||
} else {
|
||||
// Return a Result.Error with a custom Exception
|
||||
Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e(TAG, "Failed to fetch products. Code: ${response.code()}, Error: $errorBody")
|
||||
Result.Error(Exception("Failed to fetch products. Code: ${response.code()}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Return a Result.Error with the exception caught
|
||||
Log.e(TAG, "Exception while fetching products", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchProductDetail(productId: Int): ProductResponse? {
|
||||
return try {
|
||||
Log.d(TAG, "Fetching product detail for ID: $productId")
|
||||
val response = apiService.getDetailProduct(productId)
|
||||
if (response.isSuccessful) {
|
||||
response.body()
|
||||
val productResponse = response.body()
|
||||
Log.d(TAG, "Product detail fetched successfully. Product: ${productResponse?.product?.productName}")
|
||||
productResponse
|
||||
} else {
|
||||
Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e(TAG, "Error fetching product detail. Code: ${response.code()}, Error: $errorBody")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@ -51,19 +63,18 @@ class ProductRepository(private val apiService: ApiService) {
|
||||
suspend fun getAllCategories(): Result<List<CategoryItem>> =
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
Log.d("Categories", "Attempting to fetch categories")
|
||||
val response = apiService.allCategory()
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val categories = response.body()?.category ?: emptyList()
|
||||
Log.d("Categories", "Fetched categories: $categories")
|
||||
Log.d("ProductRepository", "Fetched categories: $categories")
|
||||
categories.forEach { Log.d("Category Image", "Category: ${it.name}, Image: ${it.image}") }
|
||||
Result.Success(categories)
|
||||
} else {
|
||||
Result.Error(Exception("Failed to fetch categories. Code: ${response.code()}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Categories", "Error fetching categories", e)
|
||||
Log.e("ProductRepository", "Error fetching categories", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
@ -83,20 +94,46 @@ class ProductRepository(private val apiService: ApiService) {
|
||||
}
|
||||
|
||||
suspend fun addToCart(request: CartItem): Result<AddCartResponse> {
|
||||
return try{
|
||||
return try {
|
||||
val response = apiService.addCart(request)
|
||||
if (response.isSuccessful){
|
||||
if (response.isSuccessful) {
|
||||
response.body()?.let {
|
||||
Result.Success(it)
|
||||
} ?: Result.Error(Exception("Add Cart failed"))
|
||||
} else {
|
||||
Log.e("OrderRepository", "Error: ${response.errorBody()?.string()}")
|
||||
Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||
Result.Error(Exception(response.errorBody()?.string() ?: "Unknown Error"))
|
||||
}
|
||||
} catch (e: Exception){
|
||||
} catch (e: Exception) {
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun fetchStoreDetail(storeId: Int): Result<StoreProduct?> {
|
||||
return try {
|
||||
val response = apiService.getDetailStore(storeId)
|
||||
if (response.isSuccessful) {
|
||||
val store = response.body()?.store
|
||||
if (store != null) {
|
||||
Result.Success(store)
|
||||
} else {
|
||||
Result.Error(Throwable("Empty response body"))
|
||||
}
|
||||
} else {
|
||||
val errorMsg = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("ProductRepository", "Error: $errorMsg")
|
||||
Result.Error(Throwable(errorMsg))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ProductRepository"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// suspend fun fetchStoreDetail(storeId: Int): Store? {
|
||||
|
@ -0,0 +1,79 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.CartItemsItem
|
||||
import com.alya.ecommerce_serang.databinding.ItemOrderProductBinding
|
||||
import com.alya.ecommerce_serang.databinding.ItemOrderSellerBinding
|
||||
import com.bumptech.glide.Glide
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
class CartCheckoutAdapter(private val checkoutData: CheckoutData) :
|
||||
RecyclerView.Adapter<CartCheckoutAdapter.SellerViewHolder>() {
|
||||
|
||||
class SellerViewHolder(val binding: ItemOrderSellerBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SellerViewHolder {
|
||||
val binding = ItemOrderSellerBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return SellerViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 1 // Only one seller
|
||||
|
||||
override fun onBindViewHolder(holder: SellerViewHolder, position: Int) {
|
||||
with(holder.binding) {
|
||||
// Set seller name
|
||||
tvStoreName.text = checkoutData.sellerName
|
||||
|
||||
// Set up products RecyclerView with multiple items
|
||||
rvSellerOrderProduct.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = MultiCartItemsAdapter(checkoutData.cartItems)
|
||||
isNestedScrollingEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MultiCartItemsAdapter(private val cartItems: List<CartItemsItem>) :
|
||||
RecyclerView.Adapter<MultiCartItemsAdapter.CartItemViewHolder>() {
|
||||
|
||||
class CartItemViewHolder(val binding: ItemOrderProductBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartItemViewHolder {
|
||||
val binding = ItemOrderProductBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return CartItemViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = cartItems.size
|
||||
|
||||
override fun onBindViewHolder(holder: CartItemViewHolder, position: Int) {
|
||||
val item = cartItems[position]
|
||||
|
||||
with(holder.binding) {
|
||||
// Set cart item details
|
||||
tvProductName.text = item.productName
|
||||
tvProductQuantity.text = "${item.quantity} buah"
|
||||
tvProductPrice.text = formatCurrency(item.price.toDouble())
|
||||
|
||||
// Load placeholder image
|
||||
Glide.with(ivProduct.context)
|
||||
.load(R.drawable.placeholder_image)
|
||||
.into(ivProduct)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatCurrency(amount: Double): String {
|
||||
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
|
||||
return formatter.format(amount).replace(",00", "")
|
||||
}
|
||||
}
|
@ -1,109 +1,78 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.data.api.response.product.PaymentItem
|
||||
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.ProductRepository
|
||||
import com.alya.ecommerce_serang.databinding.ActivityCheckoutBinding
|
||||
import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding
|
||||
import com.alya.ecommerce_serang.ui.order.address.AddressActivity
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import com.google.gson.Gson
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
class CheckoutActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityCheckoutBinding
|
||||
private lateinit var apiService: ApiService
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private var itemOrderAdapter: CheckoutSellerAdapter? = null
|
||||
private var paymentAdapter: PaymentMethodAdapter? = null
|
||||
|
||||
private val viewModel: CheckoutViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val productRepository = ProductRepository(apiService)
|
||||
val orderRepository = OrderRepository(apiService)
|
||||
CheckoutViewModel(orderRepository)
|
||||
}
|
||||
}
|
||||
|
||||
private var orderRequest: OrderRequest? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityCheckoutBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
sessionManager = SessionManager(this)
|
||||
apiService = ApiConfig.getApiService(sessionManager)
|
||||
|
||||
// Get order request from intent
|
||||
getOrderRequestFromIntent()
|
||||
|
||||
// Setup UI components
|
||||
setupToolbar()
|
||||
setupObservers()
|
||||
setupClickListeners()
|
||||
|
||||
// Load data if order request is available
|
||||
orderRequest?.let {
|
||||
viewModel.loadCheckoutData(it)
|
||||
// Update shipping method display
|
||||
binding.tvShippingMethod.text = "${it.shipName} ${it.shipService} (${it.shipEtd} hari)"
|
||||
} ?: run {
|
||||
// Handle case when order request is not available
|
||||
Toast.makeText(this, "Error: Order request data not found", Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
processIntentData()
|
||||
}
|
||||
|
||||
private fun getOrderRequestFromIntent() {
|
||||
// Check for direct OrderRequest object
|
||||
if (intent.hasExtra(EXTRA_ORDER_REQUEST)) {
|
||||
orderRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getSerializableExtra(EXTRA_ORDER_REQUEST, OrderRequest::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getSerializableExtra(EXTRA_ORDER_REQUEST) as? OrderRequest
|
||||
}
|
||||
}
|
||||
// Check for JSON string
|
||||
else if (intent.hasExtra(EXTRA_ORDER_REQUEST_JSON)) {
|
||||
val jsonString = intent.getStringExtra(EXTRA_ORDER_REQUEST_JSON)
|
||||
try {
|
||||
orderRequest = Gson().fromJson(jsonString, OrderRequest::class.java)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error parsing order request JSON", e)
|
||||
}
|
||||
}
|
||||
// Check for individual fields
|
||||
else if (intent.hasExtra(EXTRA_ADDRESS_ID) && intent.hasExtra(EXTRA_PRODUCT_ID)) {
|
||||
orderRequest = OrderRequest(
|
||||
address_id = intent.getIntExtra(EXTRA_ADDRESS_ID, 0),
|
||||
payment_method_id = intent.getIntExtra(EXTRA_PAYMENT_METHOD_ID, 0),
|
||||
ship_price = intent.getIntExtra(EXTRA_SHIP_PRICE, 0),
|
||||
ship_name = intent.getStringExtra(EXTRA_SHIP_NAME) ?: "",
|
||||
ship_service = intent.getStringExtra(EXTRA_SHIP_SERVICE) ?: "",
|
||||
is_negotiable = intent.getBooleanExtra(EXTRA_IS_NEGOTIABLE, false),
|
||||
product_id = intent.getIntExtra(EXTRA_PRODUCT_ID, 0),
|
||||
quantity = intent.getIntExtra(EXTRA_QUANTITY, 0),
|
||||
ship_etd = intent.getStringExtra(EXTRA_SHIP_ETD) ?: ""
|
||||
private fun processIntentData() {
|
||||
// Determine if this is Buy Now or Cart checkout
|
||||
val isBuyNow = intent.hasExtra(EXTRA_PRODUCT_ID) && !intent.hasExtra(EXTRA_CART_ITEM_IDS)
|
||||
|
||||
if (isBuyNow) {
|
||||
// Process Buy Now flow
|
||||
viewModel.initializeBuyNow(
|
||||
storeId = intent.getIntExtra(EXTRA_STORE_ID, 0),
|
||||
storeName = intent.getStringExtra(EXTRA_STORE_NAME),
|
||||
productId = intent.getIntExtra(EXTRA_PRODUCT_ID, 0),
|
||||
productName = intent.getStringExtra(EXTRA_PRODUCT_NAME),
|
||||
productImage = intent.getStringExtra(EXTRA_PRODUCT_IMAGE),
|
||||
quantity = intent.getIntExtra(EXTRA_QUANTITY, 1),
|
||||
price = intent.getDoubleExtra(EXTRA_PRICE, 0.0)
|
||||
)
|
||||
} else {
|
||||
// Process Cart checkout flow
|
||||
val cartItemIds = intent.getIntArrayExtra(EXTRA_CART_ITEM_IDS)?.toList() ?: emptyList()
|
||||
if (cartItemIds.isNotEmpty()) {
|
||||
viewModel.initializeFromCart(cartItemIds)
|
||||
} else {
|
||||
Toast.makeText(this, "Error: No cart items specified", Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,88 +85,186 @@ class CheckoutActivity : AppCompatActivity() {
|
||||
private fun setupObservers() {
|
||||
// Observe checkout data
|
||||
viewModel.checkoutData.observe(this) { data ->
|
||||
setupSellerOrderRecyclerView(data)
|
||||
setupProductRecyclerView(data)
|
||||
updateOrderSummary()
|
||||
|
||||
// Load payment methods
|
||||
viewModel.getPaymentMethods { paymentMethods ->
|
||||
if (paymentMethods.isNotEmpty()) {
|
||||
setupPaymentMethodsRecyclerView(paymentMethods)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Observe address details
|
||||
viewModel.addressDetails.observe(this) { address ->
|
||||
binding.tvPlacesAddress.text = address.label
|
||||
binding.tvAddress.text = address.fullAddress
|
||||
binding.tvPlacesAddress.text = address?.recipient
|
||||
binding.tvAddress.text = "${address?.street}, ${address?.subdistrict}"
|
||||
}
|
||||
|
||||
// Observe payment details
|
||||
viewModel.paymentDetails.observe(this) { payment ->
|
||||
binding.tvPaymentMethod.text = payment.name
|
||||
// Update selected payment in adapter
|
||||
payment?.id?.let { paymentAdapter?.setSelectedPaymentId(it) }
|
||||
}
|
||||
|
||||
// Observe loading state
|
||||
viewModel.isLoading.observe(this) { isLoading ->
|
||||
// Show/hide loading indicator
|
||||
// binding.progressBar.isVisible = isLoading
|
||||
binding.btnPay.isEnabled = !isLoading
|
||||
// Show/hide loading indicator if you have one
|
||||
}
|
||||
|
||||
// Observe error messages
|
||||
viewModel.errorMessage.observe(this) { message ->
|
||||
if (message.isNotEmpty()) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
// Observe order creation
|
||||
viewModel.orderCreated.observe(this) { created ->
|
||||
if (created) {
|
||||
Toast.makeText(this, "Order successfully created!", Toast.LENGTH_SHORT).show()
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSellerOrderRecyclerView(checkoutData: CheckoutData) {
|
||||
val adapter = CheckoutSellerAdapter(checkoutData)
|
||||
binding.rvSellerOrder.apply {
|
||||
private fun setupProductRecyclerView(checkoutData: CheckoutData) {
|
||||
val adapter = if (checkoutData.isBuyNow || checkoutData.cartItems.size <= 1) {
|
||||
CheckoutSellerAdapter(checkoutData)
|
||||
} else {
|
||||
CartCheckoutAdapter(checkoutData)
|
||||
}
|
||||
|
||||
binding.rvProductItems.apply {
|
||||
layoutManager = LinearLayoutManager(this@CheckoutActivity)
|
||||
this.adapter = adapter
|
||||
isNestedScrollingEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupPaymentMethodsRecyclerView(paymentMethods: List<PaymentItem>) {
|
||||
paymentAdapter = PaymentMethodAdapter(paymentMethods) { payment ->
|
||||
// When a payment method is selected
|
||||
viewModel.setPaymentMethod(payment.id)
|
||||
}
|
||||
|
||||
binding.rvPaymentMethods.apply {
|
||||
layoutManager = LinearLayoutManager(this@CheckoutActivity)
|
||||
adapter = paymentAdapter
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateOrderSummary() {
|
||||
viewModel.checkoutData.value?.let { data ->
|
||||
// Calculate subtotal (product price * quantity)
|
||||
val subtotal = data.productPrice * data.orderRequest.quantity
|
||||
binding.tvSubtotal.text = formatCurrency(subtotal)
|
||||
// Update price information
|
||||
binding.tvItemTotal.text = formatCurrency(viewModel.calculateSubtotal())
|
||||
|
||||
// Calculate total (subtotal + shipping)
|
||||
val total = subtotal + data.orderRequest.ship_price
|
||||
// Get shipping price
|
||||
val shipPrice = if (data.isBuyNow) {
|
||||
(data.orderRequest as OrderRequestBuy).shipPrice.toDouble()
|
||||
} else {
|
||||
(data.orderRequest as OrderRequest).shipPrice.toDouble()
|
||||
}
|
||||
binding.tvShippingFee.text = formatCurrency(shipPrice)
|
||||
|
||||
// Update total
|
||||
val total = viewModel.calculateTotal()
|
||||
binding.tvTotal.text = formatCurrency(total)
|
||||
binding.tvBottomTotal.text = formatCurrency(total)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateShippingUI(shipName: String, shipService: String, shipEtd: String, shipPrice: Int) {
|
||||
if (shipName.isNotEmpty() && shipService.isNotEmpty()) {
|
||||
// Display shipping name and service in one line
|
||||
binding.tvCourierName.text = "$shipName $shipService"
|
||||
binding.tvDeliveryEstimate.text = "$shipEtd hari kerja"
|
||||
binding.tvShippingPrice.text = formatCurrency(shipPrice.toDouble())
|
||||
binding.rbJne.isChecked = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupClickListeners() {
|
||||
// Setup address selection
|
||||
// Address selection
|
||||
binding.tvChangeAddress.setOnClickListener {
|
||||
// Launch address selection activity
|
||||
startActivityForResult(
|
||||
Intent(this, AddressSelectionActivity::class.java),
|
||||
REQUEST_ADDRESS
|
||||
)
|
||||
val intent = Intent(this, AddressActivity::class.java)
|
||||
addressSelectionLauncher.launch(intent)
|
||||
}
|
||||
|
||||
// Setup payment button
|
||||
// Shipping method selection
|
||||
binding.layoutShippingMethod.setOnClickListener {
|
||||
val addressId = viewModel.addressDetails.value?.id ?: 0
|
||||
if (addressId <= 0) {
|
||||
Toast.makeText(this, "Please select delivery address first", Toast.LENGTH_SHORT).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
// Launch shipping selection with address and product info
|
||||
val intent = Intent(this, ShippingActivity::class.java)
|
||||
intent.putExtra(ShippingActivity.EXTRA_ADDRESS_ID, addressId)
|
||||
|
||||
// Add product info for courier cost calculation
|
||||
val currentData = viewModel.checkoutData.value
|
||||
if (currentData != null) {
|
||||
if (currentData.isBuyNow) {
|
||||
val buyRequest = currentData.orderRequest as OrderRequestBuy
|
||||
intent.putExtra(ShippingActivity.EXTRA_PRODUCT_ID, buyRequest.productId)
|
||||
intent.putExtra(ShippingActivity.EXTRA_QUANTITY, buyRequest.quantity)
|
||||
} else {
|
||||
// For cart, we'll pass the first item's info
|
||||
val firstItem = currentData.cartItems.firstOrNull()
|
||||
if (firstItem != null) {
|
||||
intent.putExtra(ShippingActivity.EXTRA_PRODUCT_ID, firstItem.productId)
|
||||
intent.putExtra(ShippingActivity.EXTRA_QUANTITY, firstItem.quantity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shippingSelectionLauncher.launch(intent)
|
||||
}
|
||||
|
||||
// Create order button
|
||||
binding.btnPay.setOnClickListener {
|
||||
// Create the order by sending API request
|
||||
if (validateOrder()) {
|
||||
createOrder()
|
||||
viewModel.createOrder()
|
||||
}
|
||||
}
|
||||
|
||||
// Setup voucher section
|
||||
binding.layoutVoucher.setOnClickListener {
|
||||
Toast.makeText(this, "Select Voucher", Toast.LENGTH_SHORT).show()
|
||||
// Voucher section (if implemented)
|
||||
binding.layoutVoucher?.setOnClickListener {
|
||||
Toast.makeText(this, "Voucher feature not implemented", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
// Setup shipping method
|
||||
binding.layoutShippingMethod.setOnClickListener {
|
||||
// Launch shipping method selection
|
||||
val orderRequest = this.orderRequest ?: return@setOnClickListener
|
||||
val intent = Intent(this, ShippingMethodActivity::class.java)
|
||||
intent.putExtra(ShippingMethodActivity.EXTRA_PRODUCT_ID, orderRequest.product_id)
|
||||
startActivityForResult(intent, REQUEST_SHIPPING)
|
||||
private val addressSelectionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
val addressId = result.data?.getIntExtra(AddressActivity.EXTRA_ADDRESS_ID, 0) ?: 0
|
||||
if (addressId > 0) {
|
||||
viewModel.setSelectedAddress(addressId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup payment method
|
||||
binding.layoutPaymentMethod.setOnClickListener {
|
||||
// Launch payment method selection
|
||||
startActivityForResult(
|
||||
Intent(this, PaymentMethodActivity::class.java),
|
||||
REQUEST_PAYMENT
|
||||
)
|
||||
private val shippingSelectionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
val data = result.data ?: return@registerForActivityResult
|
||||
val shipName = data.getStringExtra(ShippingActivity.EXTRA_SHIP_NAME) ?: return@registerForActivityResult
|
||||
val shipService = data.getStringExtra(ShippingActivity.EXTRA_SHIP_SERVICE) ?: return@registerForActivityResult
|
||||
val shipPrice = data.getIntExtra(ShippingActivity.EXTRA_SHIP_PRICE, 0)
|
||||
val shipEtd = data.getStringExtra(ShippingActivity.EXTRA_SHIP_ETD) ?: ""
|
||||
|
||||
// Update shipping in ViewModel
|
||||
viewModel.setShippingMethod(shipName, shipService, shipPrice, shipEtd)
|
||||
|
||||
// Update UI - display shipping name and service in one line
|
||||
updateShippingUI(shipName, shipService, shipEtd, shipPrice)
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,22 +274,27 @@ class CheckoutActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun validateOrder(): Boolean {
|
||||
val orderRequest = this.orderRequest ?: return false
|
||||
|
||||
// Check address
|
||||
if (orderRequest.address_id <= 0) {
|
||||
// Check if address is selected
|
||||
if (viewModel.addressDetails.value == null) {
|
||||
Toast.makeText(this, "Silakan pilih alamat pengiriman", Toast.LENGTH_SHORT).show()
|
||||
return false
|
||||
}
|
||||
|
||||
// Check shipping method
|
||||
if (orderRequest.ship_name.isEmpty() || orderRequest.ship_service.isEmpty()) {
|
||||
// Check if shipping is selected
|
||||
val checkoutData = viewModel.checkoutData.value ?: return false
|
||||
val shipName = if (checkoutData.isBuyNow) {
|
||||
(checkoutData.orderRequest as OrderRequestBuy).shipName
|
||||
} else {
|
||||
(checkoutData.orderRequest as OrderRequest).shipName
|
||||
}
|
||||
|
||||
if (shipName.isEmpty()) {
|
||||
Toast.makeText(this, "Silakan pilih metode pengiriman", Toast.LENGTH_SHORT).show()
|
||||
return false
|
||||
}
|
||||
|
||||
// Check payment method
|
||||
if (orderRequest.payment_method_id <= 0) {
|
||||
// Check if payment method is selected
|
||||
if (viewModel.paymentDetails.value == null) {
|
||||
Toast.makeText(this, "Silakan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
|
||||
return false
|
||||
}
|
||||
@ -230,163 +302,51 @@ class CheckoutActivity : AppCompatActivity() {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun createOrder() {
|
||||
val orderRequest = this.orderRequest ?: return
|
||||
|
||||
// Show progress dialog
|
||||
val progressDialog = ProgressDialog(this)
|
||||
progressDialog.setMessage("Membuat pesanan...")
|
||||
progressDialog.setCancelable(false)
|
||||
progressDialog.show()
|
||||
|
||||
// In a real app, you would send the order request to your API
|
||||
// For now, we'll simulate an API call
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
progressDialog.dismiss()
|
||||
|
||||
// Show success message
|
||||
Toast.makeText(this, "Pesanan berhasil dibuat!", Toast.LENGTH_SHORT).show()
|
||||
|
||||
// Create intent result with the order request
|
||||
val resultIntent = Intent()
|
||||
resultIntent.putExtra(EXTRA_ORDER_REQUEST, orderRequest)
|
||||
setResult(RESULT_OK, resultIntent)
|
||||
|
||||
// Return to previous screen
|
||||
finish()
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
when (requestCode) {
|
||||
REQUEST_ADDRESS -> {
|
||||
// Handle address selection result
|
||||
val addressId = data?.getIntExtra(AddressSelectionActivity.EXTRA_ADDRESS_ID, 0) ?: 0
|
||||
if (addressId > 0) {
|
||||
orderRequest?.address_id = addressId
|
||||
// Reload address details
|
||||
orderRequest?.let { request ->
|
||||
viewModelScope.launch {
|
||||
val addressDetails = repository.getAddressDetails(request.address_id)
|
||||
binding.tvPlacesAddress.text = addressDetails.label
|
||||
binding.tvAddress.text = addressDetails.fullAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
REQUEST_SHIPPING -> {
|
||||
// Handle shipping method selection result
|
||||
data?.let { intent ->
|
||||
val shipName = intent.getStringExtra(ShippingMethodActivity.EXTRA_SHIP_NAME) ?: return
|
||||
val shipService = intent.getStringExtra(ShippingMethodActivity.EXTRA_SHIP_SERVICE) ?: return
|
||||
val shipPrice = intent.getIntExtra(ShippingMethodActivity.EXTRA_SHIP_PRICE, 0)
|
||||
val shipEtd = intent.getStringExtra(ShippingMethodActivity.EXTRA_SHIP_ETD) ?: ""
|
||||
|
||||
// Update order request
|
||||
orderRequest?.apply {
|
||||
this.ship_name = shipName
|
||||
this.ship_service = shipService
|
||||
this.ship_price = shipPrice
|
||||
this.ship_etd = shipEtd
|
||||
}
|
||||
|
||||
// Update UI
|
||||
binding.tvShippingMethod.text = "$shipName $shipService ($shipEtd hari)"
|
||||
updateOrderSummary()
|
||||
}
|
||||
}
|
||||
REQUEST_PAYMENT -> {
|
||||
// Handle payment method selection result
|
||||
val paymentMethodId = data?.getIntExtra(PaymentMethodActivity.EXTRA_PAYMENT_METHOD_ID, 0) ?: 0
|
||||
if (paymentMethodId > 0) {
|
||||
orderRequest?.payment_method_id = paymentMethodId
|
||||
// Reload payment method details
|
||||
orderRequest?.let { request ->
|
||||
viewModelScope.launch {
|
||||
val paymentDetails = repository.getPaymentMethodDetails(request.payment_method_id)
|
||||
binding.tvPaymentMethod.text = paymentDetails.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "CheckoutActivity"
|
||||
|
||||
// Request codes
|
||||
const val REQUEST_ADDRESS = 100
|
||||
const val REQUEST_SHIPPING = 101
|
||||
const val REQUEST_PAYMENT = 102
|
||||
|
||||
// Intent extras
|
||||
const val EXTRA_ORDER_REQUEST = "extra_order_request"
|
||||
const val EXTRA_ORDER_REQUEST_JSON = "extra_order_request_json"
|
||||
const val EXTRA_CART_ITEM_IDS = "extra_cart_item_ids"
|
||||
const val EXTRA_STORE_ID = "STORE_ID"
|
||||
const val EXTRA_STORE_NAME = "STORE_NAME"
|
||||
const val EXTRA_PRODUCT_ID = "PRODUCT_ID"
|
||||
const val EXTRA_PRODUCT_NAME = "PRODUCT_NAME"
|
||||
const val EXTRA_PRODUCT_IMAGE = "PRODUCT_IMAGE"
|
||||
const val EXTRA_QUANTITY = "QUANTITY"
|
||||
const val EXTRA_PRICE = "PRICE"
|
||||
|
||||
// Individual field extras
|
||||
const val EXTRA_ADDRESS_ID = "extra_address_id"
|
||||
const val EXTRA_PAYMENT_METHOD_ID = "extra_payment_method_id"
|
||||
const val EXTRA_SHIP_PRICE = "extra_ship_price"
|
||||
const val EXTRA_SHIP_NAME = "extra_ship_name"
|
||||
const val EXTRA_SHIP_SERVICE = "extra_ship_service"
|
||||
const val EXTRA_IS_NEGOTIABLE = "extra_is_negotiable"
|
||||
const val EXTRA_PRODUCT_ID = "extra_product_id"
|
||||
const val EXTRA_QUANTITY = "extra_quantity"
|
||||
const val EXTRA_SHIP_ETD = "extra_ship_etd"
|
||||
// Helper methods for starting activity
|
||||
|
||||
// Start methods for various ways to launch the activity
|
||||
|
||||
// Start with OrderRequest object
|
||||
fun start(context: Context, orderRequest: OrderRequest) {
|
||||
val intent = Intent(context, CheckoutActivity::class.java)
|
||||
intent.putExtra(EXTRA_ORDER_REQUEST, orderRequest)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
// Start with OrderRequest JSON
|
||||
fun startWithJson(context: Context, orderRequestJson: String) {
|
||||
val intent = Intent(context, CheckoutActivity::class.java)
|
||||
intent.putExtra(EXTRA_ORDER_REQUEST_JSON, orderRequestJson)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
// Start with individual fields
|
||||
fun start(
|
||||
// For Buy Now
|
||||
fun startForBuyNow(
|
||||
context: Context,
|
||||
addressId: Int,
|
||||
paymentMethodId: Int,
|
||||
shipPrice: Int,
|
||||
shipName: String,
|
||||
shipService: String,
|
||||
isNegotiable: Boolean,
|
||||
storeId: Int,
|
||||
storeName: String?,
|
||||
productId: Int,
|
||||
productName: String?,
|
||||
productImage: String?,
|
||||
quantity: Int,
|
||||
shipEtd: String
|
||||
price: Double
|
||||
) {
|
||||
val intent = Intent(context, CheckoutActivity::class.java).apply {
|
||||
putExtra(EXTRA_ADDRESS_ID, addressId)
|
||||
putExtra(EXTRA_PAYMENT_METHOD_ID, paymentMethodId)
|
||||
putExtra(EXTRA_SHIP_PRICE, shipPrice)
|
||||
putExtra(EXTRA_SHIP_NAME, shipName)
|
||||
putExtra(EXTRA_SHIP_SERVICE, shipService)
|
||||
putExtra(EXTRA_IS_NEGOTIABLE, isNegotiable)
|
||||
putExtra(EXTRA_STORE_ID, storeId)
|
||||
putExtra(EXTRA_STORE_NAME, storeName)
|
||||
putExtra(EXTRA_PRODUCT_ID, productId)
|
||||
putExtra(EXTRA_PRODUCT_NAME, productName)
|
||||
putExtra(EXTRA_PRODUCT_IMAGE, productImage)
|
||||
putExtra(EXTRA_QUANTITY, quantity)
|
||||
putExtra(EXTRA_SHIP_ETD, shipEtd)
|
||||
putExtra(EXTRA_PRICE, price)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
// Launch for result with OrderRequest
|
||||
fun startForResult(activity: Activity, orderRequest: OrderRequest, requestCode: Int) {
|
||||
val intent = Intent(activity, CheckoutActivity::class.java)
|
||||
intent.putExtra(EXTRA_ORDER_REQUEST, orderRequest)
|
||||
activity.startActivityForResult(intent, requestCode)
|
||||
// For Cart checkout
|
||||
fun startForCart(
|
||||
context: Context,
|
||||
cartItemIds: List<Int>
|
||||
) {
|
||||
val intent = Intent(context, CheckoutActivity::class.java).apply {
|
||||
putExtra(EXTRA_CART_ITEM_IDS, cartItemIds.toIntArray())
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
@ -4,10 +4,8 @@ import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
||||
import com.alya.ecommerce_serang.databinding.ItemOrderSellerBinding
|
||||
import com.bumptech.glide.Glide
|
||||
|
||||
// Adapter for seller section that contains the product
|
||||
class CheckoutSellerAdapter(private val checkoutData: CheckoutData) :
|
||||
@ -17,29 +15,28 @@ class CheckoutSellerAdapter(private val checkoutData: CheckoutData) :
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SellerViewHolder {
|
||||
val binding = ItemOrderSellerBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return SellerViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 1 // Only one seller based on your JSON
|
||||
override fun getItemCount(): Int = 1 // Only one seller
|
||||
|
||||
override fun onBindViewHolder(holder: SellerViewHolder, position: Int) {
|
||||
with(holder.binding) {
|
||||
tvListProductOrder.text = checkoutData.sellerName
|
||||
// Set seller name
|
||||
tvStoreName.text = checkoutData.sellerName
|
||||
|
||||
// Load seller image
|
||||
Glide.with(ivSellerOrder.context)
|
||||
.load(checkoutData.sellerImageUrl)
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.into(ivSellerOrder)
|
||||
|
||||
// Set up nested RecyclerView for the product
|
||||
// Set up products RecyclerView
|
||||
rvSellerOrderProduct.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = CheckoutProductAdapter(checkoutData)
|
||||
adapter = if (checkoutData.isBuyNow) {
|
||||
// Single product for Buy Now
|
||||
SingleProductAdapter(checkoutData)
|
||||
} else {
|
||||
// Single cart item
|
||||
SingleCartItemAdapter(checkoutData.cartItems.first())
|
||||
}
|
||||
isNestedScrollingEnabled = false
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,13 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.response.profile.AddressesItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.CartItemsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.DataItem
|
||||
import com.alya.ecommerce_serang.data.api.response.product.PaymentItem
|
||||
import com.alya.ecommerce_serang.data.api.response.profile.AddressesItem
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
||||
@ -17,55 +21,302 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
||||
private val _checkoutData = MutableLiveData<CheckoutData>()
|
||||
val checkoutData: LiveData<CheckoutData> = _checkoutData
|
||||
|
||||
private val _addressDetails = MutableLiveData<AddressesItem>()
|
||||
val addressDetails: LiveData<AddressesItem> = _addressDetails
|
||||
private val _addressDetails = MutableLiveData<AddressesItem?>()
|
||||
val addressDetails: LiveData<AddressesItem?> = _addressDetails
|
||||
|
||||
private val _storePayments = MutableLiveData<List<PaymentItem>>()
|
||||
val storePayments: LiveData<List<PaymentItem>> = _storePayments
|
||||
private val _paymentDetails = MutableLiveData<PaymentItem?>()
|
||||
val paymentDetails: LiveData<PaymentItem?> = _paymentDetails
|
||||
|
||||
private val _isLoading = MutableLiveData<Boolean>()
|
||||
val isLoading: LiveData<Boolean> = _isLoading
|
||||
|
||||
fun loadCheckoutData(orderRequest: OrderRequest) {
|
||||
private val _errorMessage = MutableLiveData<String>()
|
||||
val errorMessage: LiveData<String> = _errorMessage
|
||||
|
||||
private val _orderCreated = MutableLiveData<Boolean>()
|
||||
val orderCreated: LiveData<Boolean> = _orderCreated
|
||||
|
||||
// Initialize "Buy Now" checkout
|
||||
fun initializeBuyNow(
|
||||
storeId: Int,
|
||||
storeName: String?,
|
||||
productId: Int,
|
||||
productName: String?,
|
||||
productImage: String?,
|
||||
quantity: Int,
|
||||
price: Double
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
|
||||
try {
|
||||
// Load all necessary data
|
||||
val productDetails = repository.fetchProductDetail(orderRequest.productIdItem)
|
||||
val storeDetails = repository.getStoreDetails(productDetails.product.storeId)
|
||||
|
||||
// val addressDetails = repository.getAddressDetails(orderRequest.address_id)
|
||||
|
||||
// Update LiveData objects
|
||||
// _addressDetails.value = addressDetails
|
||||
|
||||
// Create CheckoutData object
|
||||
_checkoutData.value = CheckoutData(
|
||||
orderRequest = orderRequest,
|
||||
productName = productDetails?.product?.productName,
|
||||
productImageUrl = productDetails.product.image,
|
||||
productPrice = productDetails.product.price,
|
||||
sellerName = storeDetails.store.storeName,
|
||||
sellerImageUrl = storeDetails.store.storeImage,
|
||||
sellerId = productDetails.product.storeId
|
||||
// Create initial OrderRequestBuy object
|
||||
val orderRequest = OrderRequestBuy(
|
||||
addressId = 0, // Will be set when user selects address
|
||||
paymentMethodId = 0, // Will be set when user selects payment
|
||||
shipPrice = 0, // Will be set when user selects shipping
|
||||
shipName = "",
|
||||
shipService = "",
|
||||
isNego = false, // Default value
|
||||
productId = productId,
|
||||
quantity = quantity,
|
||||
shipEtd = ""
|
||||
)
|
||||
|
||||
storeDetails?.let {
|
||||
_storePayments.value = it.payment
|
||||
}
|
||||
// Create checkout data
|
||||
_checkoutData.value = CheckoutData(
|
||||
orderRequest = orderRequest,
|
||||
productName = productName,
|
||||
productImageUrl = productImage ?: "",
|
||||
productPrice = price,
|
||||
sellerName = storeName ?: "",
|
||||
sellerId = storeId,
|
||||
quantity = quantity,
|
||||
isBuyNow = true
|
||||
)
|
||||
|
||||
} catch (e: Exception) {
|
||||
// Handle errors
|
||||
Log.e("CheckoutViewModel", "Error loading checkout data", e)
|
||||
Log.e(TAG, "Error initializing Buy Now data", e)
|
||||
_errorMessage.value = "Failed to initialize checkout: ${e.message}"
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize checkout from cart
|
||||
fun initializeFromCart(cartItemIds: List<Int>) {
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
|
||||
try {
|
||||
// Get cart data
|
||||
val cartResult = repository.getCart()
|
||||
|
||||
if (cartResult is Result.Success) {
|
||||
// Find matching cart items
|
||||
val matchingItems = mutableListOf<CartItemsItem>()
|
||||
var storeData: DataItem? = null
|
||||
|
||||
for (store in cartResult.data) {
|
||||
val storeItems = store.cartItems.filter { it.cartItemId in cartItemIds }
|
||||
if (storeItems.isNotEmpty()) {
|
||||
matchingItems.addAll(storeItems)
|
||||
storeData = store
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingItems.isNotEmpty() && storeData != null) {
|
||||
// Create initial OrderRequest object
|
||||
val orderRequest = OrderRequest(
|
||||
addressId = 0, // Will be set when user selects address
|
||||
paymentMethodId = 0, // Will be set when user selects payment
|
||||
shipPrice = 0, // Will be set when user selects shipping
|
||||
shipName = "",
|
||||
shipService = "",
|
||||
isNego = false,
|
||||
cartItemId = cartItemIds,
|
||||
shipEtd = ""
|
||||
)
|
||||
|
||||
// Create checkout data
|
||||
_checkoutData.value = CheckoutData(
|
||||
orderRequest = orderRequest,
|
||||
productName = matchingItems.first().productName,
|
||||
sellerName = storeData.storeName,
|
||||
sellerId = storeData.storeId,
|
||||
isBuyNow = false,
|
||||
cartItems = matchingItems
|
||||
)
|
||||
} else {
|
||||
_errorMessage.value = "No matching cart items found"
|
||||
}
|
||||
} else if (cartResult is Result.Error) {
|
||||
_errorMessage.value = "Failed to fetch cart items: ${cartResult.exception.message}"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error initializing cart checkout", e)
|
||||
_errorMessage.value = "Error: ${e.message}"
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get payment methods from API
|
||||
fun getPaymentMethods(callback: (List<PaymentItem>) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val storeResponse = repository.getStore()
|
||||
if (storeResponse != null && storeResponse.payment.isNotEmpty()) {
|
||||
callback(storeResponse.payment)
|
||||
} else {
|
||||
callback(emptyList())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error fetching payment methods", e)
|
||||
callback(emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set selected address
|
||||
fun setSelectedAddress(addressId: Int) {
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
try {
|
||||
// Get address details from API
|
||||
val addressResponse = repository.getAddress()
|
||||
if (addressResponse != null && !addressResponse.addresses.isNullOrEmpty()) {
|
||||
val address = addressResponse.addresses.find { it.id == addressId }
|
||||
if (addressResponse != null && !addressResponse.addresses.isNullOrEmpty()) {
|
||||
val address = addressResponse.addresses.find { it.id == addressId }
|
||||
// No need for null check since _addressDetails now accepts nullable values
|
||||
_addressDetails.value = address
|
||||
|
||||
// Update order request with address ID only if address isn't null
|
||||
if (address != null) {
|
||||
val currentData = _checkoutData.value ?: return@launch
|
||||
if (currentData.isBuyNow) {
|
||||
val buyRequest = currentData.orderRequest as OrderRequestBuy
|
||||
val updatedRequest = buyRequest.copy(addressId = addressId)
|
||||
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||
} else {
|
||||
val cartRequest = currentData.orderRequest as OrderRequest
|
||||
val updatedRequest = cartRequest.copy(addressId = addressId)
|
||||
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_errorMessage.value = "Error loading address: ${e.message}"
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set shipping method
|
||||
fun setShippingMethod(shipName: String, shipService: String, shipPrice: Int, shipEtd: String) {
|
||||
val currentData = _checkoutData.value ?: return
|
||||
|
||||
if (currentData.isBuyNow) {
|
||||
val buyRequest = currentData.orderRequest as OrderRequestBuy
|
||||
val updatedRequest = buyRequest.copy(
|
||||
shipName = shipName,
|
||||
shipService = shipService,
|
||||
shipPrice = shipPrice,
|
||||
shipEtd = shipEtd
|
||||
)
|
||||
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||
} else {
|
||||
val cartRequest = currentData.orderRequest as OrderRequest
|
||||
val updatedRequest = cartRequest.copy(
|
||||
shipName = shipName,
|
||||
shipService = shipService,
|
||||
shipPrice = shipPrice,
|
||||
shipEtd = shipEtd
|
||||
)
|
||||
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||
}
|
||||
}
|
||||
|
||||
// Set payment method
|
||||
fun setPaymentMethod(paymentId: Int) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val storeResponse = repository.getStore()
|
||||
if (storeResponse != null) {
|
||||
val payment = storeResponse.payment.find { it.id == paymentId }
|
||||
_paymentDetails.value = payment
|
||||
|
||||
// Update order request only if payment isn't null
|
||||
if (payment != null) {
|
||||
val currentData = _checkoutData.value ?: return@launch
|
||||
if (currentData.isBuyNow) {
|
||||
val buyRequest = currentData.orderRequest as OrderRequestBuy
|
||||
val updatedRequest = buyRequest.copy(paymentMethodId = paymentId)
|
||||
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||
} else {
|
||||
val cartRequest = currentData.orderRequest as OrderRequest
|
||||
val updatedRequest = cartRequest.copy(paymentMethodId = paymentId)
|
||||
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_errorMessage.value = "Error setting payment method: ${e.message}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create order
|
||||
fun createOrder() {
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
|
||||
try {
|
||||
val data = _checkoutData.value ?: throw Exception("No checkout data available")
|
||||
|
||||
val response = if (data.isBuyNow) {
|
||||
// For Buy Now, use the dedicated endpoint
|
||||
val buyRequest = data.orderRequest as OrderRequestBuy
|
||||
repository.createOrderBuyNow(buyRequest)
|
||||
} else {
|
||||
// For Cart checkout, use the standard order endpoint
|
||||
val cartRequest = data.orderRequest as OrderRequest
|
||||
repository.createOrder(cartRequest)
|
||||
}
|
||||
|
||||
if (response.isSuccessful) {
|
||||
_orderCreated.value = true
|
||||
} else {
|
||||
val errorMsg = response.errorBody()?.string() ?: "Unknown error"
|
||||
_errorMessage.value = "Failed to create order: $errorMsg"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_errorMessage.value = "Error creating order: ${e.message}"
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total price (subtotal + shipping)
|
||||
fun calculateTotal(): Double {
|
||||
val data = checkoutData.value ?: return 0.0
|
||||
return (data.productPrice * data.orderRequest.quantity) + data.orderRequest.shipPrice
|
||||
val data = _checkoutData.value ?: return 0.0
|
||||
|
||||
return calculateSubtotal() + getShippingPrice()
|
||||
}
|
||||
|
||||
// Calculate subtotal (without shipping)
|
||||
fun calculateSubtotal(): Double {
|
||||
val data = _checkoutData.value ?: return 0.0
|
||||
|
||||
return if (data.isBuyNow) {
|
||||
// For Buy Now, use product price * quantity
|
||||
val buyRequest = data.orderRequest as OrderRequestBuy
|
||||
data.productPrice * buyRequest.quantity
|
||||
} else {
|
||||
// For Cart, sum all items
|
||||
data.cartItems.sumOf { it.price * it.quantity.toDouble() }
|
||||
}
|
||||
}
|
||||
|
||||
// Get shipping price
|
||||
private fun getShippingPrice(): Double {
|
||||
val data = _checkoutData.value ?: return 0.0
|
||||
|
||||
return if (data.isBuyNow) {
|
||||
(data.orderRequest as OrderRequestBuy).shipPrice.toDouble()
|
||||
} else {
|
||||
(data.orderRequest as OrderRequest).shipPrice.toDouble()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "CheckoutViewModel"
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.response.product.PaymentItem
|
||||
import com.alya.ecommerce_serang.databinding.ItemPaymentMethodBinding
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
|
||||
class PaymentMethodAdapter(
|
||||
private val paymentMethods: List<PaymentItem>,
|
||||
private val onPaymentSelected: (PaymentItem) -> Unit
|
||||
) : RecyclerView.Adapter<PaymentMethodAdapter.PaymentMethodViewHolder>() {
|
||||
|
||||
// Track the selected position
|
||||
private var selectedPosition = -1
|
||||
|
||||
class PaymentMethodViewHolder(val binding: ItemPaymentMethodBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PaymentMethodViewHolder {
|
||||
val binding = ItemPaymentMethodBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return PaymentMethodViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = paymentMethods.size
|
||||
|
||||
override fun onBindViewHolder(holder: PaymentMethodViewHolder, position: Int) {
|
||||
val payment = paymentMethods[position]
|
||||
|
||||
with(holder.binding) {
|
||||
// Set payment method name
|
||||
tvPaymentMethodName.text = payment.bankName
|
||||
|
||||
// Set radio button state
|
||||
rbPaymentMethod.isChecked = selectedPosition == position
|
||||
|
||||
// Load payment icon if available
|
||||
if (payment.qrisImage.isNotEmpty()) {
|
||||
Glide.with(ivPaymentMethod.context)
|
||||
.load(payment.qrisImage)
|
||||
.apply(
|
||||
RequestOptions()
|
||||
.placeholder(R.drawable.outline_store_24)
|
||||
.error(R.drawable.outline_store_24))
|
||||
.into(ivPaymentMethod)
|
||||
} else {
|
||||
// Default icon for bank transfers
|
||||
ivPaymentMethod.setImageResource(R.drawable.outline_store_24)
|
||||
}
|
||||
|
||||
// Handle click on the entire item
|
||||
root.setOnClickListener {
|
||||
selectPayment(position)
|
||||
onPaymentSelected(payment)
|
||||
}
|
||||
|
||||
// Handle click on the radio button
|
||||
rbPaymentMethod.setOnClickListener {
|
||||
selectPayment(position)
|
||||
onPaymentSelected(payment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to handle payment selection
|
||||
private fun selectPayment(position: Int) {
|
||||
if (selectedPosition != position) {
|
||||
val previousPosition = selectedPosition
|
||||
selectedPosition = position
|
||||
|
||||
// Update UI for previous and new selection
|
||||
notifyItemChanged(previousPosition)
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
|
||||
// Select a payment method programmatically
|
||||
fun setSelectedPaymentId(paymentId: Int) {
|
||||
val position = paymentMethods.indexOfFirst { it.id == paymentId }
|
||||
if (position != -1 && position != selectedPosition) {
|
||||
selectPayment(position)
|
||||
}
|
||||
}
|
||||
}
|
@ -5,82 +5,130 @@ import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.core.view.isVisible
|
||||
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.databinding.ActivityShippingBinding
|
||||
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() {
|
||||
private lateinit var binding: ActivityCheckoutBinding
|
||||
private lateinit var apiService: ApiService
|
||||
|
||||
private lateinit var binding: ActivityShippingBinding
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private lateinit var adapter: ShippingAdapter
|
||||
private lateinit var shippingAdapter: ShippingAdapter
|
||||
|
||||
private val viewModel: ShippingViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val orderRepository = OrderRepository(apiService)
|
||||
ShippingViewModel(orderRepository)
|
||||
val repository = OrderRepository(apiService)
|
||||
ShippingViewModel(repository)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityCheckoutBinding.inflate(layoutInflater)
|
||||
binding = ActivityShippingBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
// Initialize SessionManager
|
||||
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)
|
||||
// Get data from intent
|
||||
val addressId = intent.getIntExtra(EXTRA_ADDRESS_ID, 0)
|
||||
val productId = intent.getIntExtra(EXTRA_PRODUCT_ID, 0)
|
||||
val quantity = intent.getIntExtra(EXTRA_QUANTITY, 1)
|
||||
|
||||
// Validate required information
|
||||
if (addressId <= 0 || productId <= 0) {
|
||||
Toast.makeText(this, "Missing required shipping information", Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
// Setup UI components
|
||||
setupToolbar()
|
||||
setupRecyclerView()
|
||||
setupObservers()
|
||||
|
||||
// Load shipping options
|
||||
viewModel.loadShippingOptions(addressId, productId, quantity)
|
||||
}
|
||||
|
||||
private fun setupToolbar() {
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
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)
|
||||
private fun setupRecyclerView() {
|
||||
shippingAdapter = ShippingAdapter { courierCostsItem, service ->
|
||||
// Handle shipping method selection
|
||||
returnSelectedShipping(
|
||||
courierCostsItem.courier,
|
||||
service.service,
|
||||
service.cost,
|
||||
service.etd
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
viewModel.fetchShippingServices(request)
|
||||
binding.rvShipmentOrder.apply {
|
||||
layoutManager = LinearLayoutManager(this@ShippingActivity)
|
||||
adapter = shippingAdapter
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
private fun setupObservers() {
|
||||
// Observe shipping options
|
||||
viewModel.shippingOptions.observe(this) { courierOptions ->
|
||||
shippingAdapter.submitList(courierOptions)
|
||||
updateEmptyState(courierOptions.isEmpty() || courierOptions.all { it.services.isEmpty() })
|
||||
}
|
||||
|
||||
// Observe loading state
|
||||
viewModel.isLoading.observe(this) { isLoading ->
|
||||
// binding.progressBar.isVisible = isLoading
|
||||
}
|
||||
|
||||
// Observe error messages
|
||||
viewModel.errorMessage.observe(this) { message ->
|
||||
if (message.isNotEmpty()) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
findViewById<MaterialToolbar>(R.id.toolbar).setNavigationOnClickListener {
|
||||
finish()
|
||||
private fun updateEmptyState(isEmpty: Boolean) {
|
||||
// binding.layoutEmptyShipping.isVisible = isEmpty
|
||||
binding.rvShipmentOrder.isVisible = !isEmpty
|
||||
}
|
||||
|
||||
private fun returnSelectedShipping(
|
||||
shipName: String,
|
||||
shipService: String,
|
||||
shipPrice: Int,
|
||||
shipEtd: String
|
||||
) {
|
||||
val intent = Intent().apply {
|
||||
putExtra(EXTRA_SHIP_NAME, shipName)
|
||||
putExtra(EXTRA_SHIP_SERVICE, shipService)
|
||||
putExtra(EXTRA_SHIP_PRICE, shipPrice)
|
||||
putExtra(EXTRA_SHIP_ETD, shipEtd)
|
||||
}
|
||||
setResult(RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Constants for intent extras
|
||||
const val EXTRA_ADDRESS_ID = "extra_address_id"
|
||||
const val EXTRA_PRODUCT_ID = "extra_product_id"
|
||||
const val EXTRA_QUANTITY = "extra_quantity"
|
||||
const val EXTRA_SHIP_NAME = "extra_ship_name"
|
||||
const val EXTRA_SHIP_SERVICE = "extra_ship_service"
|
||||
const val EXTRA_SHIP_PRICE = "extra_ship_price"
|
||||
const val EXTRA_SHIP_ETD = "extra_ship_etd"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,65 +1,96 @@
|
||||
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.CourierCostsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.order.ServicesItem
|
||||
import com.alya.ecommerce_serang.databinding.ItemShippingOrderBinding
|
||||
|
||||
class ShippingAdapter(
|
||||
private val onItemSelected: (ServicesItem) -> Unit
|
||||
private val onItemSelected: (CourierCostsItem, ServicesItem) -> Unit
|
||||
) : RecyclerView.Adapter<ShippingAdapter.ShippingViewHolder>() {
|
||||
|
||||
private var services = listOf<ServicesItem>()
|
||||
private var selectedPosition = -1
|
||||
private val courierCostsList = mutableListOf<CourierCostsItem>()
|
||||
private var selectedPosition = RecyclerView.NO_POSITION
|
||||
private var selectedCourierPosition = RecyclerView.NO_POSITION
|
||||
|
||||
fun submitList(newList: List<ServicesItem>) {
|
||||
services = newList
|
||||
fun submitList(courierCostsList: List<CourierCostsItem>) {
|
||||
this.courierCostsList.clear()
|
||||
this.courierCostsList.addAll(courierCostsList)
|
||||
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)
|
||||
inner class ShippingViewHolder(
|
||||
private val binding: ItemShippingOrderBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
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
|
||||
fun bind(courierCostsItem: CourierCostsItem, service: ServicesItem, isSelected: Boolean) {
|
||||
binding.apply {
|
||||
// Combine courier name and service
|
||||
courierNameCost.text = "${courierCostsItem.courier} - ${service.service}"
|
||||
estDate.text = "Estimasi ${service.etd} hari"
|
||||
costPrice.text = "Rp${service.cost}"
|
||||
|
||||
itemView.setOnClickListener {
|
||||
if (adapterPosition != RecyclerView.NO_POSITION) {
|
||||
selectedPosition = adapterPosition
|
||||
notifyDataSetChanged()
|
||||
onItemSelected(service)
|
||||
// Single click handler for both item and radio button
|
||||
val onClickAction = {
|
||||
val newPosition = adapterPosition
|
||||
if (newPosition != RecyclerView.NO_POSITION) {
|
||||
// Update selected position
|
||||
val oldPosition = selectedPosition
|
||||
selectedPosition = newPosition
|
||||
selectedCourierPosition = getParentCourierPosition(courierCostsItem)
|
||||
|
||||
// Notify only the changed items to improve performance
|
||||
notifyItemChanged(oldPosition)
|
||||
notifyItemChanged(newPosition)
|
||||
|
||||
// Call the callback with both courier and service
|
||||
onItemSelected(courierCostsItem, service)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
radioButton.setOnClickListener {
|
||||
if (adapterPosition != RecyclerView.NO_POSITION) {
|
||||
selectedPosition = adapterPosition
|
||||
notifyDataSetChanged()
|
||||
onItemSelected(service)
|
||||
root.setOnClickListener { onClickAction() }
|
||||
radioBtnCost.apply {
|
||||
isChecked = isSelected
|
||||
setOnClickListener { onClickAction() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getParentCourierPosition(courierCostsItem: CourierCostsItem): Int {
|
||||
return courierCostsList.indexOfFirst { it == courierCostsItem }
|
||||
}
|
||||
|
||||
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)
|
||||
val binding = ItemShippingOrderBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return ShippingViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ShippingViewHolder, position: Int) {
|
||||
val service = services[position]
|
||||
holder.bind(service, position == selectedPosition)
|
||||
// Flatten the nested structure for binding
|
||||
var currentPosition = 0
|
||||
for (courierCostsItem in courierCostsList) {
|
||||
for (service in courierCostsItem.services) {
|
||||
if (currentPosition == position) {
|
||||
holder.bind(
|
||||
courierCostsItem,
|
||||
service,
|
||||
currentPosition == selectedPosition
|
||||
)
|
||||
return
|
||||
}
|
||||
currentPosition++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = services.size
|
||||
override fun getItemCount(): Int {
|
||||
return courierCostsList.sumOf { it.services.size }
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,73 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.response.order.ServicesItem
|
||||
import com.alya.ecommerce_serang.data.api.response.order.CourierCostsItem
|
||||
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() {
|
||||
class ShippingViewModel(
|
||||
private val repository: OrderRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val _shippingServices = MutableStateFlow<Result<List<ServicesItem>>?>(null)
|
||||
val shippingServices: StateFlow<Result<List<ServicesItem>>?> = _shippingServices
|
||||
// Shipping options LiveData
|
||||
private val _shippingOptions = MutableLiveData<List<CourierCostsItem>>()
|
||||
val shippingOptions: LiveData<List<CourierCostsItem>> = _shippingOptions
|
||||
|
||||
// Loading state LiveData
|
||||
private val _isLoading = MutableLiveData<Boolean>()
|
||||
val isLoading: LiveData<Boolean> = _isLoading
|
||||
|
||||
// Error message LiveData
|
||||
private val _errorMessage = MutableLiveData<String>()
|
||||
val errorMessage: LiveData<String> = _errorMessage
|
||||
|
||||
/**
|
||||
* Load shipping options based on address, product, and quantity
|
||||
*/
|
||||
fun loadShippingOptions(addressId: Int, productId: Int, quantity: Int) {
|
||||
// Reset previous state
|
||||
_isLoading.value = true
|
||||
_errorMessage.value = ""
|
||||
|
||||
// Prepare the request
|
||||
val request = CourierCostRequest(
|
||||
addressId = addressId,
|
||||
itemCost = CostProduct(
|
||||
productId = productId,
|
||||
quantity = quantity
|
||||
)
|
||||
)
|
||||
|
||||
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}")
|
||||
try {
|
||||
// Fetch courier costs
|
||||
val result = repository.getCountCourierCost(request)
|
||||
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
// Update shipping options directly with courier costs
|
||||
_shippingOptions.value = result.data.courierCosts
|
||||
}
|
||||
is Result.Error -> {
|
||||
// Handle error case
|
||||
_errorMessage.value = result.exception.message ?: "Unknown error occurred"
|
||||
}
|
||||
is Result.Loading -> {
|
||||
// Typically handled by the loading state
|
||||
}
|
||||
}
|
||||
_shippingServices.value = Result.Success(services)
|
||||
} else if (result is Result.Error) {
|
||||
_shippingServices.value = Result.Error(result.exception)
|
||||
} catch (e: Exception) {
|
||||
// Catch any unexpected exceptions
|
||||
_errorMessage.value = e.localizedMessage ?: "An unexpected error occurred"
|
||||
} finally {
|
||||
// Always set loading to false
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.CartItemsItem
|
||||
import com.alya.ecommerce_serang.databinding.ItemOrderProductBinding
|
||||
import com.bumptech.glide.Glide
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
class SingleCartItemAdapter(private val cartItem: CartItemsItem) :
|
||||
RecyclerView.Adapter<SingleCartItemAdapter.CartItemViewHolder>() {
|
||||
|
||||
class CartItemViewHolder(val binding: ItemOrderProductBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartItemViewHolder {
|
||||
val binding = ItemOrderProductBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return CartItemViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 1
|
||||
|
||||
override fun onBindViewHolder(holder: CartItemViewHolder, position: Int) {
|
||||
with(holder.binding) {
|
||||
// Set cart item details
|
||||
tvProductName.text = cartItem.productName
|
||||
tvProductQuantity.text = "${cartItem.quantity} buah"
|
||||
tvProductPrice.text = formatCurrency(cartItem.price.toDouble())
|
||||
|
||||
// Load placeholder image
|
||||
Glide.with(ivProduct.context)
|
||||
.load(R.drawable.placeholder_image)
|
||||
.into(ivProduct)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatCurrency(amount: Double): String {
|
||||
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
|
||||
return formatter.format(amount).replace(",00", "")
|
||||
}
|
||||
}
|
@ -5,37 +5,44 @@ import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.databinding.ItemOrderProductBinding
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
class CheckoutProductAdapter(private val checkoutData: CheckoutData) :
|
||||
RecyclerView.Adapter<CheckoutProductAdapter.ProductViewHolder>() {
|
||||
class SingleProductAdapter(private val checkoutData: CheckoutData) :
|
||||
RecyclerView.Adapter<SingleProductAdapter.ProductViewHolder>() {
|
||||
|
||||
class ProductViewHolder(val binding: ItemOrderProductBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
|
||||
val binding = ItemOrderProductBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return ProductViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 1 // Only one product based on your JSON
|
||||
override fun getItemCount(): Int = 1
|
||||
|
||||
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
|
||||
with(holder.binding) {
|
||||
// Set product details
|
||||
tvProductName.text = checkoutData.productName
|
||||
tvProductQuantity.text = "${checkoutData.orderRequest.quantity} buah"
|
||||
|
||||
val quantity = (checkoutData.orderRequest as OrderRequestBuy).quantity
|
||||
tvProductQuantity.text = "$quantity buah"
|
||||
|
||||
tvProductPrice.text = formatCurrency(checkoutData.productPrice)
|
||||
|
||||
// Load image with Glide
|
||||
// Load product image
|
||||
Glide.with(ivProduct.context)
|
||||
.load(checkoutData.productImageUrl)
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.apply(
|
||||
RequestOptions()
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.error(R.drawable.placeholder_image))
|
||||
.into(ivProduct)
|
||||
}
|
||||
}
|
@ -91,9 +91,6 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s
|
||||
selectedCityId = id
|
||||
}
|
||||
|
||||
fun getSelectedProvinceId(): Int? = selectedProvinceId
|
||||
fun getSelectedCityId(): Int? = selectedCityId
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AddAddressViewModel"
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ class AddressActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityAddressBinding.inflate(layoutInflater)
|
||||
@ -34,6 +35,8 @@ class AddressActivity : AppCompatActivity() {
|
||||
sessionManager = SessionManager(this)
|
||||
apiService = ApiConfig.getApiService(sessionManager)
|
||||
|
||||
setupToolbar()
|
||||
|
||||
adapter = AddressAdapter { selectedId ->
|
||||
viewModel.selectAddress(selectedId)
|
||||
}
|
||||
@ -55,15 +58,28 @@ class AddressActivity : AppCompatActivity() {
|
||||
adapter.setSelectedAddressId(selectedId)
|
||||
}
|
||||
}
|
||||
private fun setupToolbar() {
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
// private fun updateEmptyState(isEmpty: Boolean) {
|
||||
// binding.layoutEmptyAddresses.isVisible = isEmpty
|
||||
// binding.rvAddresses.isVisible = !isEmpty
|
||||
// }
|
||||
|
||||
private fun onBackPressedWithResult() {
|
||||
viewModel.selectedAddressId.value?.let {
|
||||
val intent = Intent()
|
||||
intent.putExtra("selected_address_id", it)
|
||||
intent.putExtra(EXTRA_ADDRESS_ID, it)
|
||||
setResult(RESULT_OK, intent)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_ADDRESS_ID = "extra_address_id"
|
||||
}
|
||||
}
|
||||
|
||||
//override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.alya.ecommerce_serang.ui.product
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
@ -29,6 +30,8 @@ import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
class DetailProductActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityDetailProductBinding
|
||||
@ -54,14 +57,6 @@ 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()
|
||||
@ -88,8 +83,19 @@ class DetailProductActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.storeDetail.observe(this) { store ->
|
||||
updateStoreInfo(store)
|
||||
viewModel.storeDetail.observe(this) { result ->
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
updateStoreInfo(result.data)
|
||||
}
|
||||
is Result.Error -> {
|
||||
// Show error message, maybe a Toast or Snackbar
|
||||
Toast.makeText(this, "Failed to load store: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
is Result.Loading -> {
|
||||
// Show loading indicator if needed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.otherProducts.observe(this) { products ->
|
||||
@ -99,29 +105,23 @@ class DetailProductActivity : AppCompatActivity() {
|
||||
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.isLoading.observe(this) { isLoading ->
|
||||
binding.progressBarDetailProd.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 Result.Success -> {
|
||||
val cartId = result.data.data.cartId
|
||||
Toast.makeText(this, result.data.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
|
||||
is Result.Error -> {
|
||||
Toast.makeText(this, "Failed to add to cart: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
is Result.Loading -> {
|
||||
@ -134,7 +134,21 @@ class DetailProductActivity : AppCompatActivity() {
|
||||
private fun updateStoreInfo(store: StoreProduct?) {
|
||||
store?.let {
|
||||
binding.tvSellerName.text = it.storeName
|
||||
// Add more store details as needed
|
||||
binding.tvSellerRating.text = it.storeRating
|
||||
binding.tvSellerLocation.text = it.storeLocation
|
||||
|
||||
// Load store image using Glide
|
||||
val fullImageUrl = when (val img = it.storeImage) {
|
||||
is String -> {
|
||||
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
|
||||
}
|
||||
else -> R.drawable.placeholder_image
|
||||
}
|
||||
|
||||
Glide.with(this)
|
||||
.load(fullImageUrl)
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.into(binding.ivSellerImage)
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,14 +190,14 @@ class DetailProductActivity : AppCompatActivity() {
|
||||
|
||||
private fun updateUI(product: Product){
|
||||
binding.tvProductName.text = product.productName
|
||||
binding.tvPrice.text = product.price
|
||||
binding.tvPrice.text = formatCurrency(product.price.toDouble())
|
||||
binding.tvSold.text = product.totalSold.toString()
|
||||
binding.tvRating.text = product.rating
|
||||
binding.tvWeight.text = product.weight.toString()
|
||||
binding.tvStock.text = product.stock.toString()
|
||||
binding.tvCategory.text = product.productCategory
|
||||
binding.tvDescription.text = product.description
|
||||
binding.tvSellerName.text = product.storeId.toString()
|
||||
|
||||
|
||||
|
||||
val fullImageUrl = when (val img = product.image) {
|
||||
@ -298,33 +312,63 @@ class DetailProductActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
btnBuyNow.setOnClickListener {
|
||||
bottomSheetDialog.dismiss()
|
||||
|
||||
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
|
||||
// If it's Buy Now, navigate directly to checkout without adding to cart
|
||||
navigateToCheckout()
|
||||
} else {
|
||||
// If it's Add to Cart, add the item to the cart
|
||||
val cartItem = CartItem(
|
||||
productId = productId,
|
||||
quantity = currentQuantity
|
||||
)
|
||||
viewModel.reqCart(cartItem)
|
||||
}
|
||||
|
||||
// Add to cart in both cases
|
||||
viewModel.reqCart(cartItem)
|
||||
}
|
||||
|
||||
btnClose.setOnClickListener {
|
||||
bottomSheetDialog.dismiss()
|
||||
}
|
||||
|
||||
bottomSheetDialog.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatCurrency(amount: Double): String {
|
||||
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
|
||||
return formatter.format(amount).replace(",00", "")
|
||||
}
|
||||
|
||||
private fun navigateToCheckout() {
|
||||
val intent = Intent(this, CheckoutActivity::class.java)
|
||||
startActivity(intent)
|
||||
val productDetail = viewModel.productDetail.value ?: return
|
||||
val storeDetail = viewModel.storeDetail.value
|
||||
|
||||
if (storeDetail !is Result.Success || storeDetail.data == null) {
|
||||
Toast.makeText(this, "Store information not available", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
// Start checkout activity with buy now flow
|
||||
CheckoutActivity.startForBuyNow(
|
||||
context = this,
|
||||
storeId = productDetail.storeId,
|
||||
storeName = storeDetail.data.storeName,
|
||||
productId = productDetail.productId,
|
||||
productName = productDetail.productName,
|
||||
productImage = productDetail.image,
|
||||
quantity = currentQuantity,
|
||||
price = productDetail.price.toDouble()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_PRODUCT_ID = "extra_product_id"
|
||||
|
||||
fun start(context: Context, productId: Int) {
|
||||
val intent = Intent(context, DetailProductActivity::class.java)
|
||||
intent.putExtra(EXTRA_PRODUCT_ID, productId)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ 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.cart.AddCartResponse
|
||||
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
|
||||
@ -19,8 +20,8 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
|
||||
private val _productDetail = MutableLiveData<Product?>()
|
||||
val productDetail: LiveData<Product?> get() = _productDetail
|
||||
|
||||
private val _storeDetail = MutableLiveData<StoreProduct?>()
|
||||
val storeDetail : LiveData<StoreProduct?> get() = _storeDetail
|
||||
private val _storeDetail = MutableLiveData<Result<StoreProduct?>>()
|
||||
val storeDetail : LiveData<Result<StoreProduct?>> get() = _storeDetail
|
||||
|
||||
private val _reviewProduct = MutableLiveData<List<ReviewsItem>>()
|
||||
val reviewProduct: LiveData<List<ReviewsItem>> get() = _reviewProduct
|
||||
@ -28,8 +29,8 @@ 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 _addCart = MutableLiveData<Result<AddCartResponse>>()
|
||||
val addCart: LiveData<Result<AddCartResponse>> get() = _addCart
|
||||
|
||||
private val _isLoading = MutableLiveData<Boolean>()
|
||||
val isLoading: LiveData<Boolean> get() = _isLoading
|
||||
@ -37,8 +38,6 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
|
||||
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
|
||||
@ -47,10 +46,10 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
|
||||
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)
|
||||
// }
|
||||
//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}"
|
||||
@ -60,16 +59,18 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
|
||||
}
|
||||
}
|
||||
|
||||
// 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 loadStoreDetail(storeId: Int) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_storeDetail.value = Result.Loading
|
||||
val result = repository.fetchStoreDetail(storeId)
|
||||
_storeDetail.value = result
|
||||
} catch (e: Exception) {
|
||||
Log.e("ProductViewModel", "Error loading store details: ${e.message}")
|
||||
_storeDetail.value = Result.Error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadReviews(productId: Int) {
|
||||
viewModelScope.launch {
|
||||
@ -106,17 +107,19 @@ class ProductViewModel(private val repository: ProductRepository) : ViewModel()
|
||||
}
|
||||
fun reqCart(request: CartItem){
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
when (val result = repository.addToCart(request)) {
|
||||
is Result.Success -> {
|
||||
val message = result.data.message
|
||||
_addCart.value =
|
||||
Result.Success(message)
|
||||
_addCart.value = result
|
||||
_isLoading.value = false
|
||||
}
|
||||
is Result.Error -> {
|
||||
_addCart.value = Result.Error(result.exception)
|
||||
_addCart.value = result
|
||||
_error.value = result.exception.message ?: "Unknown error"
|
||||
_isLoading.value = false
|
||||
}
|
||||
is Result.Loading -> {
|
||||
// optional: already emitted above
|
||||
_isLoading.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@android:color/white" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#E0E0E0" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:right="8dp">
|
||||
<bitmap
|
||||
android:gravity="end|center_vertical"
|
||||
android:src="@drawable/ic_dropdown_arrow" />
|
||||
</item>
|
||||
</layer-list>
|
@ -1,17 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/cardview_light_background"
|
||||
android:background="@color/black_800"
|
||||
tools:context=".ui.order.CheckoutActivity">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/white"
|
||||
android:elevation="2dp"
|
||||
app:navigationIcon="@drawable/ic_back_24"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@ -21,199 +24,286 @@
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
app:layout_constraintBottom_toTopOf="@id/btn_pay">
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_payment_bar">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Delivery Address Section -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_delivery_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
android:layout_marginHorizontal="0dp"
|
||||
android:layout_marginTop="0dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:padding="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_location_checkout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:tint="@color/blue_500"
|
||||
android:layout_marginTop="16dp"
|
||||
android:src="@drawable/baseline_location_pin_24"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:background="@color/white">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linear_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintStart_toEndOf="@id/iv_location_checkout"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/tv_change_address">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_location_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/baseline_location_pin_24"
|
||||
android:layout_gravity="center_vertical"
|
||||
app:tint="#3D84FF" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Alamat Pengiriman"
|
||||
android:fontFamily="@font/dmsans_semibold"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_places_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Rumah"
|
||||
android:fontFamily="@font/dmsans_medium"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Jl. Pegangasan Timur"
|
||||
android:fontFamily="@font/dmsans_medium"/>
|
||||
android:textSize="16sp"
|
||||
android:fontFamily="@font/dmsans_medium"
|
||||
android:layout_marginStart="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_change_address"
|
||||
android:id="@+id/tv_places_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rumah"
|
||||
android:textColor="#5A5A5A"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:paddingVertical="2dp"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="Pilih Alamat"
|
||||
android:textColor="@color/blue_500"
|
||||
android:fontFamily="@font/dmsans_medium"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/linear_address"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
android:layout_marginStart="32dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Jl. Pegangasan Timur"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginStart="32dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_change_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pilih Alamat"
|
||||
android:textColor="#3D84FF"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Product Item Section -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:background="@color/black_50" />
|
||||
|
||||
<!-- Product Items Section -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_product"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/card_delivery_address"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_seller_order"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_order_seller"/>
|
||||
android:orientation="vertical"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_product_items"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
tools:listitem="@layout/item_order_seller" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:background="#F5F5F5" />
|
||||
|
||||
<!-- Voucher Section -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_voucher"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/card_product"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:layout_width="24dp"-->
|
||||
<!-- android:layout_height="24dp"-->
|
||||
<!-- android:src="@drawable/ic_voucher_24" />-->
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Gunakan Voucher"
|
||||
android:fontFamily="@font/dmsans_semibold"/>
|
||||
android:textSize="14sp"
|
||||
android:layout_marginStart="8dp" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_arrow_right"/>
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_arrow_right" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:background="@color/black_50" />
|
||||
|
||||
<!-- Shipping Method Section -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_shipping_method"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_voucher"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Metode Pengiriman"
|
||||
android:fontFamily="@font/dmsans_medium"/>
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_shipping_method"
|
||||
android:layout_width="wrap_content"
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Metode Pengiriman"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_shipping_option"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Opsi Pengiriman"
|
||||
android:textColor="#3D84FF"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pilih Pengiriman"
|
||||
android:fontFamily="@font/dmsans_medium"
|
||||
android:layout_marginTop="4dp"/>
|
||||
android:layout_marginTop="8dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="0dp"
|
||||
app:cardBackgroundColor="#F5F5F5">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_jne"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_courier_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="JNE"
|
||||
android:textSize="16sp"
|
||||
android:fontFamily="@font/dmsans_medium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_delivery_estimate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="3 - 4 hari kerja"
|
||||
android:textSize="14sp"
|
||||
android:textColor="#757575" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_shipping_price"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rp15.000"
|
||||
android:textSize="16sp"
|
||||
android:fontFamily="@font/dmsans_medium"
|
||||
android:layout_gravity="center_vertical" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:background="@color/black_50" />
|
||||
|
||||
<!-- Payment Method Section -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_payment_method"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_shipping_method"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Metode Pembayaran"
|
||||
android:fontFamily="@font/dmsans_medium"/>
|
||||
android:textSize="14sp"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_payment_method"
|
||||
android:layout_width="wrap_content"
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_payment_methods"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Bank Transfer"
|
||||
android:fontFamily="@font/dmsans_medium"
|
||||
android:layout_marginTop="4dp"/>
|
||||
tools:listitem="@layout/item_payment_method"
|
||||
tools:itemCount="2" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:background="@color/black_50" />
|
||||
|
||||
<!-- Price Summary Section -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_price_summary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_payment_method"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -224,22 +314,43 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Subtotal"
|
||||
android:fontFamily="@font/dmsans_medium"/>
|
||||
android:text="1 item"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_subtotal"
|
||||
android:id="@+id/tv_item_total"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rp65,000"
|
||||
android:fontFamily="@font/dmsans_medium"/>
|
||||
android:text="Rp65.000"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Biaya Pengiriman"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_shipping_fee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rp15.000"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/soft_gray"
|
||||
android:layout_marginVertical="8dp"/>
|
||||
android:background="@color/black_50"
|
||||
android:layout_marginVertical="12dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -251,28 +362,64 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Total"
|
||||
android:fontFamily="@font/dmsans_bold"/>
|
||||
android:textSize="16sp"
|
||||
android:fontFamily="@font/dmsans_bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_total"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rp65,000"
|
||||
android:fontFamily="@font/dmsans_bold"/>
|
||||
android:text="Rp75.000"
|
||||
android:textColor="#3D84FF"
|
||||
android:textSize="16sp"
|
||||
android:fontFamily="@font/dmsans_bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<!-- Pay Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_pay"
|
||||
android:layout_width="0dp"
|
||||
<!-- Bottom Payment Bar -->
|
||||
<LinearLayout
|
||||
android:id="@+id/bottom_payment_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Bayar"
|
||||
android:layout_margin="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:background="@color/white"
|
||||
android:elevation="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Total:"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_bottom_total"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rp75.000"
|
||||
android:textColor="#3D84FF"
|
||||
android:textSize="18sp"
|
||||
android:fontFamily="@font/dmsans_bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_pay"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Bayar"
|
||||
android:textAllCaps="false"
|
||||
android:paddingHorizontal="32dp"
|
||||
app:cornerRadius="8dp"
|
||||
android:backgroundTint="#3D84FF" />
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -406,6 +406,13 @@
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar_detail_prod"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<!-- Bottom Action Bar -->
|
||||
<com.google.android.material.bottomappbar.BottomAppBar
|
||||
android:id="@+id/bottomAppBar"
|
||||
|
@ -11,33 +11,34 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_margin="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_seller_order"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/placeholder_image"/>
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/outline_store_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_list_product_order"
|
||||
android:id="@+id/tv_store_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Toko Buka"/>
|
||||
android:text="SnackEnak"
|
||||
android:textSize="16sp"
|
||||
android:fontFamily="@font/dmsans_semibold"
|
||||
android:layout_marginStart="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:dividerInsetStart="16dp"
|
||||
app:dividerInsetEnd="16dp"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_seller_order_product"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_order_product"/>
|
||||
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:dividerInsetStart="16dp"
|
||||
app:dividerInsetEnd="16dp"/>
|
||||
|
||||
</LinearLayout>
|
29
app/src/main/res/layout/item_payment_method.xml
Normal file
29
app/src/main/res/layout/item_payment_method.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_payment_method"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/outline_store_24"
|
||||
android:layout_marginEnd="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_payment_method_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Bank Transfer"
|
||||
android:fontFamily="@font/dmsans_medium"/>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_payment_method"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
Reference in New Issue
Block a user