diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 16b52f7..efacb99 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -6,13 +6,14 @@
-
+
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 7394d18..6d8fb78 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index f856b99..329ea8a 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,5 +1,4 @@
-import org.gradle.api.tasks.compile.JavaCompile
-
+import java.util.Properties
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
@@ -9,9 +8,17 @@ plugins {
// id("com.google.dagger.hilt.android")
}
+val localProperties = Properties().apply {
+ val localPropertiesFile = rootProject.file("local.properties")
+ if (localPropertiesFile.exists()) {
+ load(localPropertiesFile.inputStream())
+ }
+}
+
+
android {
namespace = "com.alya.ecommerce_serang"
- compileSdk = 35
+ compileSdk = 34
defaultConfig {
applicationId = "com.alya.ecommerce_serang"
@@ -21,11 +28,19 @@ android {
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ buildConfigField(
+ "String",
+ "BASE_URL",
+ "\"${localProperties["BASE_URL"] ?: "http://default-url.com/"}\""
+ )
}
buildTypes {
release {
- buildConfigField("String", "BASE_URL", "\"http://192.168.1.15:3000/\"")
+ buildConfigField("String",
+ "BASE_URL",
+ "\"${localProperties["BASE_URL"] ?: "http://default-url.com/"}\"")
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
@@ -33,7 +48,9 @@ android {
)
}
debug {
- buildConfigField("String", "BASE_URL", "\"http://192.168.1.15:3000/\"")
+ buildConfigField("String",
+ "BASE_URL",
+ "\"${localProperties["BASE_URL"] ?: "http://default-url.com/"}\"")
}
}
compileOptions {
@@ -86,4 +103,3 @@ dependencies {
// kapt("androidx.hilt:hilt-compiler:1.0.0")
}
-
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 978c7db..c1cfce9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,6 +4,8 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CartItem.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CartItem.kt
new file mode 100644
index 0000000..89bd4d0
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CartItem.kt
@@ -0,0 +1,11 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class CartItem (
+ @SerializedName("product_id")
+ val productId: Int,
+
+ @SerializedName("quantity")
+ val quantity: Int
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CheckoutData.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CheckoutData.kt
new file mode 100644
index 0000000..81b9332
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CheckoutData.kt
@@ -0,0 +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: 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 = emptyList()
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CourierCostRequest.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CourierCostRequest.kt
new file mode 100644
index 0000000..0387371
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CourierCostRequest.kt
@@ -0,0 +1,19 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class CourierCostRequest(
+ @SerializedName("address_id")
+ val addressId: Int,
+
+ @SerializedName("items")
+ val itemCost: CostProduct
+)
+
+data class CostProduct (
+ @SerializedName("product_id")
+ val productId: Int,
+
+ @SerializedName("quantity")
+ val quantity: Int
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CreateAddressRequest.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CreateAddressRequest.kt
new file mode 100644
index 0000000..658ce61
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CreateAddressRequest.kt
@@ -0,0 +1,41 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class CreateAddressRequest (
+ @SerializedName("latitude")
+ val lat: Double,
+
+ @SerializedName("longitude")
+ val long: Double,
+
+ @SerializedName("street")
+ val street: String,
+
+ @SerializedName("subdistrict")
+ val subDistrict: String,
+
+ @SerializedName("city_id")
+ val cityId: Int,
+
+ @SerializedName("province_id")
+ val provId: Int,
+ @SerializedName("postal_code")
+ val postCode: String? = null,
+
+ @SerializedName("detail")
+ val detailAddress: String? = null,
+
+ @SerializedName("user_id")
+ val userId: Int,
+
+ @SerializedName("recipient")
+ val recipient: String,
+
+ @SerializedName("phone")
+ val phone: String,
+
+ @SerializedName("is_store_location")
+ val isStoreLocation: Boolean
+
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/OrderRequest.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/OrderRequest.kt
new file mode 100644
index 0000000..13be574
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/OrderRequest.kt
@@ -0,0 +1,29 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class OrderRequest (
+ @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("cart_items_id")
+ val cartItemId: List,
+
+ @SerializedName("ship_etd")
+ val shipEtd: String
+ )
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/OrderRequestBuy.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/OrderRequestBuy.kt
new file mode 100644
index 0000000..8e0b154
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/OrderRequestBuy.kt
@@ -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
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/UpdateCart.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/UpdateCart.kt
new file mode 100644
index 0000000..cf61072
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/UpdateCart.kt
@@ -0,0 +1,11 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class UpdateCart (
+ @SerializedName("cart_item_id")
+ val cartItemId: Int,
+
+ @SerializedName("quantity")
+ val quantity: Int
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/CreateProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/CreateProductResponse.kt
index e859cd6..f8fd202 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/CreateProductResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/CreateProductResponse.kt
@@ -1,5 +1,6 @@
package com.alya.ecommerce_serang.data.api.response
+import com.alya.ecommerce_serang.data.api.response.product.Product
import com.google.gson.annotations.SerializedName
data class CreateProductResponse(
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/DetailStoreProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/DetailStoreProductResponse.kt
deleted file mode 100644
index 4f83751..0000000
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/DetailStoreProductResponse.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.alya.ecommerce_serang.data.api.response
-
-import com.google.gson.annotations.SerializedName
-
-data class DetailStoreProductResponse(
-
- @field:SerializedName("store")
- val store: StoreProduct,
-
- @field:SerializedName("message")
- val message: String
-)
-
-data class StoreProduct(
-
- @field:SerializedName("store_name")
- val storeName: String,
-
- @field:SerializedName("description")
- val description: String,
-
- @field:SerializedName("store_type")
- val storeType: String,
-
- @field:SerializedName("store_location")
- val storeLocation: String,
-
- @field:SerializedName("store_image")
- val storeImage: Any,
-
- @field:SerializedName("status")
- val status: String
-)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/StoreResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/StoreResponse.kt
deleted file mode 100644
index 9895f15..0000000
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/StoreResponse.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.alya.ecommerce_serang.data.api.response
-
-import com.alya.ecommerce_serang.data.api.dto.Store
-import com.google.gson.annotations.SerializedName
-
-data class StoreResponse(
-
- @field:SerializedName("shipping")
- val shipping: List,
-
- @field:SerializedName("payment")
- val payment: List,
-
- @field:SerializedName("store")
- val store: Store,
-
- @field:SerializedName("message")
- val message: String
-)
-
-data class ShippingItem(
-
- @field:SerializedName("courier")
- val courier: String
-)
-
-data class PaymentItem(
-
- @field:SerializedName("qris_image")
- val qrisImage: String,
-
- @field:SerializedName("bank_num")
- val bankNum: String,
-
- @field:SerializedName("bank_name")
- val bankName: String,
-
- @field:SerializedName("id")
- val id: Int
-)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/LoginResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/LoginResponse.kt
similarity index 81%
rename from app/src/main/java/com/alya/ecommerce_serang/data/api/response/LoginResponse.kt
rename to app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/LoginResponse.kt
index 58c9206..4e8a8f6 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/LoginResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/LoginResponse.kt
@@ -1,4 +1,4 @@
-package com.alya.ecommerce_serang.data.api.response
+package com.alya.ecommerce_serang.data.api.response.auth
import com.google.gson.annotations.SerializedName
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/OtpResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/OtpResponse.kt
similarity index 70%
rename from app/src/main/java/com/alya/ecommerce_serang/data/api/response/OtpResponse.kt
rename to app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/OtpResponse.kt
index 60a87f5..4132711 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/OtpResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/OtpResponse.kt
@@ -1,4 +1,4 @@
-package com.alya.ecommerce_serang.data.api.response
+package com.alya.ecommerce_serang.data.api.response.auth
import com.google.gson.annotations.SerializedName
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/RegisterResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/RegisterResponse.kt
similarity index 92%
rename from app/src/main/java/com/alya/ecommerce_serang/data/api/response/RegisterResponse.kt
rename to app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/RegisterResponse.kt
index 1136015..7d0b9a8 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/RegisterResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/RegisterResponse.kt
@@ -1,4 +1,4 @@
-package com.alya.ecommerce_serang.data.api.response
+package com.alya.ecommerce_serang.data.api.response.auth
import com.google.gson.annotations.SerializedName
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/cart/AddCartResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/cart/AddCartResponse.kt
new file mode 100644
index 0000000..2656167
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/cart/AddCartResponse.kt
@@ -0,0 +1,30 @@
+package com.alya.ecommerce_serang.data.api.response.cart
+
+import com.google.gson.annotations.SerializedName
+
+data class AddCartResponse(
+
+ @field:SerializedName("data")
+ val data: Data,
+
+ @field:SerializedName("message")
+ val message: String
+)
+
+data class Data(
+
+ @field:SerializedName("cart_id")
+ val cartId: Int,
+
+ @field:SerializedName("quantity")
+ val quantity: Int,
+
+ @field:SerializedName("product_id")
+ val productId: Int,
+
+ @field:SerializedName("created_at")
+ val createdAt: String,
+
+ @field:SerializedName("id")
+ val id: Int
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/cart/CartActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/cart/CartActivity.kt
new file mode 100644
index 0000000..2f52e8a
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/cart/CartActivity.kt
@@ -0,0 +1,21 @@
+package com.alya.ecommerce_serang.data.api.response.cart
+
+import android.os.Bundle
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import com.alya.ecommerce_serang.R
+
+class CartActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContentView(R.layout.activity_cart)
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/cart/ListCartResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/cart/ListCartResponse.kt
new file mode 100644
index 0000000..5aa77c2
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/cart/ListCartResponse.kt
@@ -0,0 +1,48 @@
+package com.alya.ecommerce_serang.data.api.response.cart
+
+import com.google.gson.annotations.SerializedName
+
+data class ListCartResponse(
+
+ @field:SerializedName("data")
+ val data: List,
+
+ @field:SerializedName("message")
+ val message: String
+)
+
+data class DataItem(
+
+ @field:SerializedName("store_id")
+ val storeId: Int,
+
+ @field:SerializedName("cart_items")
+ val cartItems: List,
+
+ @field:SerializedName("store_name")
+ val storeName: String,
+
+ @field:SerializedName("most_recent_item")
+ val mostRecentItem: String
+)
+
+data class CartItemsItem(
+
+ @field:SerializedName("quantity")
+ val quantity: Int,
+
+ @field:SerializedName("price")
+ val price: Int,
+
+ @field:SerializedName("product_id")
+ val productId: Int,
+
+ @field:SerializedName("created_at")
+ val createdAt: String,
+
+ @field:SerializedName("cart_item_id")
+ val cartItemId: Int,
+
+ @field:SerializedName("product_name")
+ val productName: String
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/cart/UpdateCartResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/cart/UpdateCartResponse.kt
new file mode 100644
index 0000000..d831255
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/cart/UpdateCartResponse.kt
@@ -0,0 +1,9 @@
+package com.alya.ecommerce_serang.data.api.response.cart
+
+import com.google.gson.annotations.SerializedName
+
+data class UpdateCartResponse(
+
+ @field:SerializedName("message")
+ val message: String
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/CourierCostResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/CourierCostResponse.kt
new file mode 100644
index 0000000..b24f03f
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/CourierCostResponse.kt
@@ -0,0 +1,33 @@
+package com.alya.ecommerce_serang.data.api.response.order
+
+import com.google.gson.annotations.SerializedName
+
+data class CourierCostResponse(
+
+ @field:SerializedName("courierCosts")
+ val courierCosts: List
+)
+
+data class CourierCostsItem(
+
+ @field:SerializedName("courier")
+ val courier: String,
+
+ @field:SerializedName("services")
+ val services: List
+)
+
+data class ServicesItem(
+
+ @field:SerializedName("cost")
+ val cost: Int,
+
+ @field:SerializedName("etd")
+ val etd: String,
+
+ @field:SerializedName("service")
+ val service: String,
+
+ @field:SerializedName("description")
+ val description: String
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/CreateOrderResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/CreateOrderResponse.kt
new file mode 100644
index 0000000..17ba185
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/CreateOrderResponse.kt
@@ -0,0 +1,105 @@
+package com.alya.ecommerce_serang.data.api.response.order
+
+import com.google.gson.annotations.SerializedName
+
+data class CreateOrderResponse(
+
+ @field:SerializedName("shipping")
+ val shipping: Shipping,
+
+ @field:SerializedName("order_item")
+ val orderItem: List,
+
+ @field:SerializedName("message")
+ val message: String,
+
+ @field:SerializedName("order")
+ val order: Order
+)
+
+data class Shipping(
+
+ @field:SerializedName("receipt_num")
+ val receiptNum: Int? = null,
+
+ @field:SerializedName("etd")
+ val etd: String,
+
+ @field:SerializedName("price")
+ val price: String,
+
+ @field:SerializedName("service")
+ val service: String,
+
+ @field:SerializedName("name")
+ val name: String,
+
+ @field:SerializedName("created_at")
+ val createdAt: String,
+
+ @field:SerializedName("id")
+ val id: Int,
+
+ @field:SerializedName("order_id")
+ val orderId: Int,
+
+ @field:SerializedName("status")
+ val status: String
+)
+
+data class OrderItemItem(
+
+ @field:SerializedName("quantity")
+ val quantity: Int,
+
+ @field:SerializedName("price")
+ val price: String,
+
+ @field:SerializedName("subtotal")
+ val subtotal: String,
+
+ @field:SerializedName("product_id")
+ val productId: Int,
+
+ @field:SerializedName("id")
+ val id: Int,
+
+ @field:SerializedName("order_id")
+ val orderId: Int
+)
+
+data class Order(
+
+ @field:SerializedName("payment_method_id")
+ val paymentMethodId: Int,
+
+ @field:SerializedName("auto_completed_at")
+ val autoCompletedAt: String? = null,
+
+ @field:SerializedName("updated_at")
+ val updatedAt: String,
+
+ @field:SerializedName("total_amount")
+ val totalAmount: String,
+
+ @field:SerializedName("user_id")
+ val userId: Int,
+
+ @field:SerializedName("address_id")
+ val addressId: Int,
+
+ @field:SerializedName("is_negotiable")
+ val isNegotiable: Boolean,
+
+ @field:SerializedName("created_at")
+ val createdAt: String,
+
+ @field:SerializedName("voucher_id")
+ val voucherId: String? = null,
+
+ @field:SerializedName("id")
+ val id: Int,
+
+ @field:SerializedName("status")
+ val status: String
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/ListCityResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/ListCityResponse.kt
new file mode 100644
index 0000000..228426d
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/ListCityResponse.kt
@@ -0,0 +1,21 @@
+package com.alya.ecommerce_serang.data.api.response.order
+
+import com.google.gson.annotations.SerializedName
+
+data class ListCityResponse(
+
+ @field:SerializedName("cities")
+ val cities: List,
+
+ @field:SerializedName("message")
+ val message: String
+)
+
+data class CitiesItem(
+
+ @field:SerializedName("city_name")
+ val cityName: String,
+
+ @field:SerializedName("city_id")
+ val cityId: String
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/ListProvinceResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/ListProvinceResponse.kt
new file mode 100644
index 0000000..013cd4f
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/ListProvinceResponse.kt
@@ -0,0 +1,21 @@
+package com.alya.ecommerce_serang.data.api.response.order
+
+import com.google.gson.annotations.SerializedName
+
+data class ListProvinceResponse(
+
+ @field:SerializedName("provinces")
+ val provinces: List,
+
+ @field:SerializedName("message")
+ val message: String
+)
+
+data class ProvincesItem(
+
+ @field:SerializedName("province")
+ val province: String,
+
+ @field:SerializedName("province_id")
+ val provinceId: String
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/OrderDetailResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/OrderDetailResponse.kt
new file mode 100644
index 0000000..fd145f6
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/OrderDetailResponse.kt
@@ -0,0 +1,129 @@
+package com.alya.ecommerce_serang.data.api.response.order
+
+import com.google.gson.annotations.SerializedName
+
+data class OrderDetailResponse(
+
+ @field:SerializedName("orders")
+ val orders: Orders,
+
+ @field:SerializedName("message")
+ val message: String
+)
+
+data class OrderItemsItem(
+
+ @field:SerializedName("review_id")
+ val reviewId: Int? = null,
+
+ @field:SerializedName("quantity")
+ val quantity: Int,
+
+ @field:SerializedName("price")
+ val price: Int,
+
+ @field:SerializedName("subtotal")
+ val subtotal: Int,
+
+ @field:SerializedName("product_image")
+ val productImage: String? = null,
+
+ @field:SerializedName("store_name")
+ val storeName: String,
+
+ @field:SerializedName("product_price")
+ val productPrice: Int,
+
+ @field:SerializedName("product_name")
+ val productName: String
+)
+
+data class Orders(
+
+ @field:SerializedName("receipt_num")
+ val receiptNum: String,
+
+ @field:SerializedName("latitude")
+ val latitude: String,
+
+ @field:SerializedName("created_at")
+ val createdAt: String,
+
+ @field:SerializedName("voucher_code")
+ val voucherCode: String? = null,
+
+ @field:SerializedName("updated_at")
+ val updatedAt: String,
+
+ @field:SerializedName("etd")
+ val etd: String,
+
+ @field:SerializedName("street")
+ val street: String,
+
+ @field:SerializedName("cancel_date")
+ val cancelDate: String,
+
+ @field:SerializedName("longitude")
+ val longitude: String,
+
+ @field:SerializedName("shipment_status")
+ val shipmentStatus: String,
+
+ @field:SerializedName("order_items")
+ val orderItems: List,
+
+ @field:SerializedName("auto_completed_at")
+ val autoCompletedAt: String,
+
+ @field:SerializedName("is_store_location")
+ val isStoreLocation: Boolean,
+
+ @field:SerializedName("voucher_name")
+ val voucherName: String? = null,
+
+ @field:SerializedName("address_id")
+ val addressId: Int,
+
+ @field:SerializedName("payment_method_id")
+ val paymentMethodId: Int,
+
+ @field:SerializedName("cancel_reason")
+ val cancelReason: String,
+
+ @field:SerializedName("total_amount")
+ val totalAmount: String,
+
+ @field:SerializedName("user_id")
+ val userId: Int,
+
+ @field:SerializedName("province_id")
+ val provinceId: Int,
+
+ @field:SerializedName("courier")
+ val courier: String,
+
+ @field:SerializedName("subdistrict")
+ val subdistrict: String,
+
+ @field:SerializedName("service")
+ val service: String,
+
+ @field:SerializedName("shipment_price")
+ val shipmentPrice: String,
+
+ @field:SerializedName("voucher_id")
+ val voucherId: Int? = null,
+
+ @field:SerializedName("detail")
+ val detail: String,
+
+ @field:SerializedName("postal_code")
+ val postalCode: String,
+
+ @field:SerializedName("order_id")
+ val orderId: Int,
+
+ @field:SerializedName("city_id")
+ val cityId: Int
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/OrderListResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/OrderListResponse.kt
new file mode 100644
index 0000000..a962817
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/order/OrderListResponse.kt
@@ -0,0 +1,91 @@
+package com.alya.ecommerce_serang.data.api.response.order
+
+import com.google.gson.annotations.SerializedName
+
+data class OrderListResponse(
+
+ @field:SerializedName("orders")
+ val orders: List,
+
+ @field:SerializedName("message")
+ val message: String
+)
+
+data class OrdersItem(
+
+ @field:SerializedName("receipt_num")
+ val receiptNum: String,
+
+ @field:SerializedName("latitude")
+ val latitude: String,
+
+ @field:SerializedName("created_at")
+ val createdAt: String,
+
+ @field:SerializedName("voucher_code")
+ val voucherCode: String? = null,
+
+ @field:SerializedName("updated_at")
+ val updatedAt: String,
+
+ @field:SerializedName("street")
+ val street: String,
+
+ @field:SerializedName("longitude")
+ val longitude: String,
+
+ @field:SerializedName("shipment_status")
+ val shipmentStatus: String,
+
+ @field:SerializedName("order_items")
+ val orderItems: List,
+
+ @field:SerializedName("is_store_location")
+ val isStoreLocation: Boolean,
+
+ @field:SerializedName("voucher_name")
+ val voucherName: String? = null,
+
+ @field:SerializedName("address_id")
+ val addressId: Int,
+
+ @field:SerializedName("payment_method_id")
+ val paymentMethodId: Int,
+
+ @field:SerializedName("total_amount")
+ val totalAmount: String,
+
+ @field:SerializedName("user_id")
+ val userId: Int,
+
+ @field:SerializedName("province_id")
+ val provinceId: Int,
+
+ @field:SerializedName("courier")
+ val courier: String,
+
+ @field:SerializedName("subdistrict")
+ val subdistrict: String,
+
+ @field:SerializedName("service")
+ val service: String,
+
+ @field:SerializedName("shipment_price")
+ val shipmentPrice: String,
+
+ @field:SerializedName("voucher_id")
+ val voucherId: Int? = null,
+
+ @field:SerializedName("detail")
+ val detail: String,
+
+ @field:SerializedName("postal_code")
+ val postalCode: String,
+
+ @field:SerializedName("order_id")
+ val orderId: Int,
+
+ @field:SerializedName("city_id")
+ val cityId: Int
+)
+
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/AllProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/AllProductResponse.kt
similarity index 81%
rename from app/src/main/java/com/alya/ecommerce_serang/data/api/response/AllProductResponse.kt
rename to app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/AllProductResponse.kt
index e45093f..000d52c 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/AllProductResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/AllProductResponse.kt
@@ -1,4 +1,4 @@
-package com.alya.ecommerce_serang.data.api.response
+package com.alya.ecommerce_serang.data.api.response.product
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.google.gson.annotations.SerializedName
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/AllStoreResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/AllStoreResponse.kt
similarity index 79%
rename from app/src/main/java/com/alya/ecommerce_serang/data/api/response/AllStoreResponse.kt
rename to app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/AllStoreResponse.kt
index 4d2f860..dd02833 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/AllStoreResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/AllStoreResponse.kt
@@ -1,13 +1,13 @@
-package com.alya.ecommerce_serang.data.api.response
+package com.alya.ecommerce_serang.data.api.response.product
import com.google.gson.annotations.SerializedName
data class AllStoreResponse(
- @field:SerializedName("store")
+ @field:SerializedName("store")
val store: AllStore,
- @field:SerializedName("message")
+ @field:SerializedName("message")
val message: String
)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/CategoryResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/CategoryResponse.kt
similarity index 81%
rename from app/src/main/java/com/alya/ecommerce_serang/data/api/response/CategoryResponse.kt
rename to app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/CategoryResponse.kt
index 26867ca..1411ff2 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/CategoryResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/CategoryResponse.kt
@@ -1,4 +1,4 @@
-package com.alya.ecommerce_serang.data.api.response
+package com.alya.ecommerce_serang.data.api.response.product
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
import com.google.gson.annotations.SerializedName
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/DetailStoreProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/DetailStoreProductResponse.kt
new file mode 100644
index 0000000..08df515
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/DetailStoreProductResponse.kt
@@ -0,0 +1,63 @@
+package com.alya.ecommerce_serang.data.api.response.product
+
+import com.google.gson.annotations.SerializedName
+
+data class DetailStoreProductResponse(
+
+ @field:SerializedName("store")
+ val store: StoreProduct,
+
+ @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,
+
+ @field:SerializedName("store_rating")
+ val storeRating: String,
+
+ @field:SerializedName("store_name")
+ val storeName: String,
+
+ @field:SerializedName("description")
+ val description: String,
+
+ @field:SerializedName("store_type")
+ val storeType: String,
+
+ @field:SerializedName("payment_info")
+ val paymentInfo: List,
+
+ @field:SerializedName("store_location")
+ val storeLocation: String,
+
+ @field:SerializedName("store_image")
+ val storeImage: String,
+
+ @field:SerializedName("status")
+ val status: String
+)
+
+data class ShippingServiceItem(
+
+ @field:SerializedName("courier")
+ val courier: String
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/ProductResponse.kt
similarity index 88%
rename from app/src/main/java/com/alya/ecommerce_serang/data/api/response/ProductResponse.kt
rename to app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/ProductResponse.kt
index 448ad92..7e9bfc0 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ProductResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/ProductResponse.kt
@@ -1,13 +1,13 @@
-package com.alya.ecommerce_serang.data.api.response
+package com.alya.ecommerce_serang.data.api.response.product
import com.google.gson.annotations.SerializedName
data class ProductResponse(
- @field:SerializedName("product")
+ @field:SerializedName("product")
val product: Product,
- @field:SerializedName("message")
+ @field:SerializedName("message")
val message: String
)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ReviewProductResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/ReviewProductResponse.kt
similarity index 83%
rename from app/src/main/java/com/alya/ecommerce_serang/data/api/response/ReviewProductResponse.kt
rename to app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/ReviewProductResponse.kt
index f9a927c..2ab569f 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ReviewProductResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/ReviewProductResponse.kt
@@ -1,13 +1,13 @@
-package com.alya.ecommerce_serang.data.api.response
+package com.alya.ecommerce_serang.data.api.response.product
import com.google.gson.annotations.SerializedName
data class ReviewProductResponse(
- @field:SerializedName("reviews")
+ @field:SerializedName("reviews")
val reviews: List,
- @field:SerializedName("message")
+ @field:SerializedName("message")
val message: String
)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/StoreResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/StoreResponse.kt
new file mode 100644
index 0000000..8f6faf4
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/product/StoreResponse.kt
@@ -0,0 +1,139 @@
+package com.alya.ecommerce_serang.data.api.response.product
+
+import com.alya.ecommerce_serang.data.api.dto.Store
+import com.google.gson.annotations.SerializedName
+
+data class StoreResponse(
+
+ @field:SerializedName("shipping")
+ val shipping: List,
+
+ @field:SerializedName("payment")
+ val payment: List,
+
+ @field:SerializedName("store")
+ val store: Store,
+
+ @field:SerializedName("message")
+ val message: String
+)
+
+data class Store(
+
+ @field:SerializedName("approval_reason")
+ val approvalReason: String,
+
+ @field:SerializedName("store_status")
+ val storeStatus: String,
+
+ @field:SerializedName("sppirt")
+ val sppirt: String,
+
+ @field:SerializedName("user_name")
+ val userName: String,
+
+ @field:SerializedName("nib")
+ val nib: String,
+
+ @field:SerializedName("latitude")
+ val latitude: String,
+
+ @field:SerializedName("store_type_id")
+ val storeTypeId: Int,
+
+ @field:SerializedName("balance")
+ val balance: String,
+
+ @field:SerializedName("street")
+ val street: String,
+
+ @field:SerializedName("store_name")
+ val storeName: String,
+
+ @field:SerializedName("user_phone")
+ val userPhone: String,
+
+ @field:SerializedName("halal")
+ val halal: String,
+
+ @field:SerializedName("id")
+ val id: Int,
+
+ @field:SerializedName("email")
+ val email: String,
+
+ @field:SerializedName("store_image")
+ val storeImage: String? = null,
+
+ @field:SerializedName("longitude")
+ val longitude: String,
+
+ @field:SerializedName("store_id")
+ val storeId: Int,
+
+ @field:SerializedName("is_store_location")
+ val isStoreLocation: Boolean,
+
+ @field:SerializedName("ktp")
+ val ktp: String,
+
+ @field:SerializedName("approval_status")
+ val approvalStatus: String,
+
+ @field:SerializedName("npwp")
+ val npwp: String,
+
+ @field:SerializedName("store_type")
+ val storeType: String,
+
+ @field:SerializedName("is_on_leave")
+ val isOnLeave: Boolean,
+
+ @field:SerializedName("user_id")
+ val userId: Int,
+
+ @field:SerializedName("province_id")
+ val provinceId: Int,
+
+ @field:SerializedName("phone")
+ val phone: String,
+
+ @field:SerializedName("subdistrict")
+ val subdistrict: String,
+
+ @field:SerializedName("recipient")
+ val recipient: String,
+
+ @field:SerializedName("detail")
+ val detail: String,
+
+ @field:SerializedName("postal_code")
+ val postalCode: String,
+
+ @field:SerializedName("store_description")
+ val storeDescription: String,
+
+ @field:SerializedName("city_id")
+ val cityId: Int
+)
+
+data class ShippingItem(
+
+ @field:SerializedName("courier")
+ val courier: String
+)
+
+data class PaymentItem(
+
+ @field:SerializedName("qris_image")
+ val qrisImage: String,
+
+ @field:SerializedName("bank_num")
+ val bankNum: String,
+
+ @field:SerializedName("bank_name")
+ val bankName: String,
+
+ @field:SerializedName("id")
+ val id: Int
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/profile/AddressResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/profile/AddressResponse.kt
new file mode 100644
index 0000000..5332064
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/profile/AddressResponse.kt
@@ -0,0 +1,54 @@
+package com.alya.ecommerce_serang.data.api.response.profile
+
+import com.google.gson.annotations.SerializedName
+
+data class AddressResponse(
+
+ @field:SerializedName("addresses")
+ val addresses: List,
+
+ @field:SerializedName("message")
+ val message: String
+)
+
+data class AddressesItem(
+
+ @field:SerializedName("is_store_location")
+ val isStoreLocation: Boolean,
+
+ @field:SerializedName("latitude")
+ val latitude: String,
+
+ @field:SerializedName("user_id")
+ val userId: Int,
+
+ @field:SerializedName("province_id")
+ val provinceId: Int,
+
+ @field:SerializedName("phone")
+ val phone: String,
+
+ @field:SerializedName("street")
+ val street: String,
+
+ @field:SerializedName("subdistrict")
+ val subdistrict: String,
+
+ @field:SerializedName("recipient")
+ val recipient: String,
+
+ @field:SerializedName("id")
+ val id: Int,
+
+ @field:SerializedName("detail")
+ val detail: String,
+
+ @field:SerializedName("postal_code")
+ val postalCode: String,
+
+ @field:SerializedName("longitude")
+ val longitude: String,
+
+ @field:SerializedName("city_id")
+ val cityId: Int
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/profile/CreateAddressResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/profile/CreateAddressResponse.kt
new file mode 100644
index 0000000..430dc8b
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/profile/CreateAddressResponse.kt
@@ -0,0 +1,9 @@
+package com.alya.ecommerce_serang.data.api.response.profile
+
+import com.google.gson.annotations.SerializedName
+
+data class CreateAddressResponse(
+
+ @field:SerializedName("message")
+ val message: String
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ProfileResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/profile/ProfileResponse.kt
similarity index 80%
rename from app/src/main/java/com/alya/ecommerce_serang/data/api/response/ProfileResponse.kt
rename to app/src/main/java/com/alya/ecommerce_serang/data/api/response/profile/ProfileResponse.kt
index d9fe08d..fa48907 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/ProfileResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/profile/ProfileResponse.kt
@@ -1,4 +1,4 @@
-package com.alya.ecommerce_serang.data.api.response
+package com.alya.ecommerce_serang.data.api.response.profile
import com.alya.ecommerce_serang.data.api.dto.UserProfile
import com.google.gson.annotations.SerializedName
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
index 8e22988..2367f28 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
@@ -1,19 +1,34 @@
package com.alya.ecommerce_serang.data.api.retrofit
+import com.alya.ecommerce_serang.data.api.dto.CartItem
+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.response.AllProductResponse
-import com.alya.ecommerce_serang.data.api.response.CategoryResponse
-import com.alya.ecommerce_serang.data.api.response.DetailStoreProductResponse
-import com.alya.ecommerce_serang.data.api.response.LoginResponse
-import com.alya.ecommerce_serang.data.api.response.OtpResponse
-import com.alya.ecommerce_serang.data.api.response.ProductResponse
-import com.alya.ecommerce_serang.data.api.response.ProfileResponse
-import com.alya.ecommerce_serang.data.api.response.RegisterResponse
-import com.alya.ecommerce_serang.data.api.response.ReviewProductResponse
-import com.alya.ecommerce_serang.data.api.response.StoreResponse
+import com.alya.ecommerce_serang.data.api.dto.UpdateCart
import com.alya.ecommerce_serang.data.api.response.ViewStoreProductsResponse
+import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
+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.product.AllProductResponse
+import com.alya.ecommerce_serang.data.api.response.product.CategoryResponse
+import com.alya.ecommerce_serang.data.api.response.product.DetailStoreProductResponse
+import com.alya.ecommerce_serang.data.api.response.product.ProductResponse
+import com.alya.ecommerce_serang.data.api.response.product.ReviewProductResponse
+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
+import com.alya.ecommerce_serang.data.api.response.profile.ProfileResponse
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body
@@ -21,6 +36,7 @@ import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.POST
+import retrofit2.http.PUT
import retrofit2.http.Path
interface ApiService {
@@ -64,6 +80,24 @@ interface ApiService {
@Path("id") storeId: Int
): Response
+ @POST("order")
+ suspend fun postOrder(
+ @Body request: OrderRequest
+ ): Response
+
+ @POST("order")
+ suspend fun postOrderBuyNow(
+ @Body request: OrderRequestBuy
+ ): Response
+
+ @GET("profile/address")
+ suspend fun getAddress(
+ ): Response
+
+ @POST("profile/addaddress")
+ suspend fun createAddress(
+ @Body createAddressRequest: CreateAddressRequest
+ ): Response
@GET("mystore")
suspend fun getStore (): Response
@@ -89,4 +123,30 @@ interface ApiService {
@Field("is_active") isActive: String
): Response
+ @GET("cart_item")
+ suspend fun getCart (): Response
+
+ @POST("cart/add")
+ suspend fun addCart(
+ @Body cartRequest: CartItem
+ ): Response
+
+ @PUT("cart/update")
+ suspend fun updateCart(
+ @Body updateCart: UpdateCart
+ ): Response
+
+ @POST("couriercost")
+ suspend fun countCourierCost(
+ @Body courierCost : CourierCostRequest
+ ): Response
+
+ @GET("cities/{id}")
+ suspend fun getCityProvId(
+ @Path("id") provId : Int
+ ): Response
+
+ @GET("provinces")
+ suspend fun getListProv(
+ ): Response
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt
index 003f772..71dd35c 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt
@@ -2,7 +2,7 @@ package com.alya.ecommerce_serang.data.repository
import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.Store
-import com.alya.ecommerce_serang.data.api.response.StoreResponse
+import com.alya.ecommerce_serang.data.api.response.product.StoreResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import retrofit2.HttpException
import java.io.IOException
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt
new file mode 100644
index 0000000..d2aad68
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt
@@ -0,0 +1,210 @@
+package com.alya.ecommerce_serang.data.repository
+
+import android.util.Log
+import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
+import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
+import com.alya.ecommerce_serang.data.api.dto.OrderRequest
+import com.alya.ecommerce_serang.data.api.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
+import com.alya.ecommerce_serang.data.api.retrofit.ApiService
+import retrofit2.Response
+
+class OrderRepository(private val apiService: ApiService) {
+
+ suspend fun fetchProductDetail(productId: Int): ProductResponse? {
+ return try {
+ val response = apiService.getDetailProduct(productId)
+ if (response.isSuccessful) {
+ val productResponse = response.body()
+ Log.d("Order Repository", "Product detail fetched successfully: ${productResponse?.product?.productName}")
+ productResponse
+ } else {
+ 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 {
+ 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 {
+ 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? {
+ 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? {
+ 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 {
+ 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> {
+ 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 {
+ 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)
+ }
+ }
+
+ suspend fun addAddress(createAddressRequest: CreateAddressRequest): Result {
+ return try {
+ val response = apiService.createAddress(createAddressRequest)
+ 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)
+ }
+ }
+
+ suspend fun getListProvinces(): ListProvinceResponse? {
+ val response = apiService.getListProv()
+ return if (response.isSuccessful) response.body() else null
+ }
+
+ suspend fun getListCities(provId : Int): ListCityResponse?{
+ val response = apiService.getCityProvId(provId)
+ return if (response.isSuccessful) response.body() else null
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt
index c218ef2..dad6bef 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ProductRepository.kt
@@ -1,10 +1,13 @@
package com.alya.ecommerce_serang.data.repository
import android.util.Log
+import com.alya.ecommerce_serang.data.api.dto.CartItem
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
-import com.alya.ecommerce_serang.data.api.response.ProductResponse
-import com.alya.ecommerce_serang.data.api.response.ReviewsItem
+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
@@ -19,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) {
@@ -54,14 +68,14 @@ class ProductRepository(private val apiService: ApiService) {
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)
}
}
@@ -80,6 +94,42 @@ class ProductRepository(private val apiService: ApiService) {
}
}
+ suspend fun addToCart(request: CartItem): Result {
+ return try {
+ val response = apiService.addCart(request)
+ if (response.isSuccessful) {
+ response.body()?.let {
+ Result.Success(it)
+ } ?: Result.Error(Exception("Add Cart failed"))
+ } else {
+ Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
+ Result.Error(Exception(response.errorBody()?.string() ?: "Unknown Error"))
+ }
+ } catch (e: Exception) {
+ Result.Error(e)
+ }
+ }
+
+ suspend fun fetchStoreDetail(storeId: Int): Result {
+ 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)
+ }
+ }
+
suspend fun fetchMyStoreProducts(): List {
val response = apiService.getStoreProduct()
if (response.isSuccessful) {
@@ -127,6 +177,9 @@ class ProductRepository(private val apiService: ApiService) {
}
}
+ companion object {
+ private const val TAG = "ProductRepository"
+ }
}
// suspend fun fetchStoreDetail(storeId: Int): Store? {
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt
index 0977624..5b4c97c 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt
@@ -4,18 +4,15 @@ import com.alya.ecommerce_serang.data.api.dto.LoginRequest
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.dto.UserProfile
-import com.alya.ecommerce_serang.data.api.response.LoginResponse
-import com.alya.ecommerce_serang.data.api.response.OtpResponse
+import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
+import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
class UserRepository(private val apiService: ApiService) {
+ //post data without message/response
suspend fun requestOtpRep(email: String): OtpResponse {
-
-// fun requestOtpRep(email: String): Result {
-
return apiService.getOTP(OtpRequest(email))
-
}
suspend fun registerUser(request: RegisterRequest): String {
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/home/HorizontalProductAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/home/HorizontalProductAdapter.kt
index 8af4b5f..a23f60a 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/home/HorizontalProductAdapter.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/home/HorizontalProductAdapter.kt
@@ -61,6 +61,7 @@ class HorizontalProductAdapter(
val diffResult = DiffUtil.calculateDiff(diffCallback)
products = newProducts
diffResult.dispatchUpdatesTo(this)
+ notifyDataSetChanged()
}
fun updateLimitedProducts(newProducts: List) {
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CartCheckoutAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CartCheckoutAdapter.kt
new file mode 100644
index 0000000..ee71da9
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CartCheckoutAdapter.kt
@@ -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() {
+
+ 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) :
+ RecyclerView.Adapter() {
+
+ 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", "")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt
new file mode 100644
index 0000000..19cc599
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt
@@ -0,0 +1,357 @@
+package com.alya.ecommerce_serang.ui.order
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+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.PaymentInfoItem
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.data.repository.OrderRepository
+import com.alya.ecommerce_serang.databinding.ActivityCheckoutBinding
+import com.alya.ecommerce_serang.ui.order.address.AddressActivity
+import com.alya.ecommerce_serang.utils.BaseViewModelFactory
+import com.alya.ecommerce_serang.utils.SessionManager
+import java.text.NumberFormat
+import java.util.Locale
+
+class CheckoutActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityCheckoutBinding
+ private lateinit var sessionManager: SessionManager
+ private var paymentAdapter: PaymentMethodAdapter? = null
+
+ private val viewModel: CheckoutViewModel by viewModels {
+ BaseViewModelFactory {
+ val apiService = ApiConfig.getApiService(sessionManager)
+ val orderRepository = OrderRepository(apiService)
+ CheckoutViewModel(orderRepository)
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityCheckoutBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ sessionManager = SessionManager(this)
+
+ // Setup UI components
+ setupToolbar()
+ setupObservers()
+ setupClickListeners()
+ processIntentData()
+ }
+
+ 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()
+ }
+ }
+ }
+
+ private fun setupToolbar() {
+ binding.toolbar.setNavigationOnClickListener {
+ finish()
+ }
+ }
+
+ private fun setupObservers() {
+ // Observe checkout data
+ viewModel.checkoutData.observe(this) { 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?.recipient
+ binding.tvAddress.text = "${address?.street}, ${address?.subdistrict}"
+ }
+
+ // Observe payment details
+ viewModel.paymentDetails.observe(this) { payment ->
+ if (payment != null) {
+ // Update selected payment in adapter by name instead of ID
+ paymentAdapter?.setSelectedPaymentName(payment.name)
+ }
+ }
+
+ // Observe loading state
+ viewModel.isLoading.observe(this) { 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 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) {
+ paymentAdapter = PaymentMethodAdapter(paymentMethods) { payment ->
+ // When a payment method is selected
+ // Since PaymentInfoItem doesn't have an id field, we'll use the name as identifier
+ // You might need to convert the name to an ID if your backend expects an integer
+ val paymentId = payment.name.toIntOrNull() ?: 0
+ viewModel.setPaymentMethod(paymentId)
+ }
+
+ binding.rvPaymentMethods.apply {
+ layoutManager = LinearLayoutManager(this@CheckoutActivity)
+ adapter = paymentAdapter
+ }
+ }
+
+ private fun updateOrderSummary() {
+ viewModel.checkoutData.value?.let { data ->
+ // Update price information
+ binding.tvItemTotal.text = formatCurrency(viewModel.calculateSubtotal())
+
+ // 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() {
+ // Address selection
+ binding.tvChangeAddress.setOnClickListener {
+ val intent = Intent(this, AddressActivity::class.java)
+ addressSelectionLauncher.launch(intent)
+ }
+
+ // 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 {
+ if (validateOrder()) {
+ viewModel.createOrder()
+ }
+ }
+
+ // Voucher section (if implemented)
+ binding.layoutVoucher?.setOnClickListener {
+ Toast.makeText(this, "Voucher feature not implemented", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ 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)
+ }
+ }
+ }
+
+ 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)
+ }
+ }
+
+ private fun formatCurrency(amount: Double): String {
+ val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
+ return formatter.format(amount).replace(",00", "")
+ }
+
+ private fun validateOrder(): Boolean {
+ // Check if address is selected
+ if (viewModel.addressDetails.value == null) {
+ Toast.makeText(this, "Silakan pilih alamat pengiriman", Toast.LENGTH_SHORT).show()
+ return false
+ }
+
+ // 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 if payment method is selected
+ if (viewModel.paymentDetails.value == null) {
+ Toast.makeText(this, "Silakan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
+ return false
+ }
+
+ return true
+ }
+
+ companion object {
+ // Intent extras
+ 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"
+
+ // Helper methods for starting activity
+
+ // For Buy Now
+ fun startForBuyNow(
+ context: Context,
+ storeId: Int,
+ storeName: String?,
+ productId: Int,
+ productName: String?,
+ productImage: String?,
+ quantity: Int,
+ price: Double
+ ) {
+ val intent = Intent(context, CheckoutActivity::class.java).apply {
+ 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_PRICE, price)
+ }
+ context.startActivity(intent)
+ }
+
+ // For Cart checkout
+ fun startForCart(
+ context: Context,
+ cartItemIds: List
+ ) {
+ val intent = Intent(context, CheckoutActivity::class.java).apply {
+ putExtra(EXTRA_CART_ITEM_IDS, cartItemIds.toIntArray())
+ }
+ context.startActivity(intent)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutSellerAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutSellerAdapter.kt
new file mode 100644
index 0000000..1d436b4
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutSellerAdapter.kt
@@ -0,0 +1,44 @@
+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.data.api.dto.CheckoutData
+import com.alya.ecommerce_serang.databinding.ItemOrderSellerBinding
+
+// Adapter for seller section that contains the product
+class CheckoutSellerAdapter(private val checkoutData: CheckoutData) :
+ RecyclerView.Adapter() {
+
+ 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
+ rvSellerOrderProduct.apply {
+ layoutManager = LinearLayoutManager(context)
+ adapter = if (checkoutData.isBuyNow) {
+ // Single product for Buy Now
+ SingleProductAdapter(checkoutData)
+ } else {
+ // Single cart item
+ SingleCartItemAdapter(checkoutData.cartItems.first())
+ }
+ isNestedScrollingEnabled = false
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt
new file mode 100644
index 0000000..e470bcf
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt
@@ -0,0 +1,330 @@
+package com.alya.ecommerce_serang.ui.order
+
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+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.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.PaymentInfoItem
+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() {
+
+ private val _checkoutData = MutableLiveData()
+ val checkoutData: LiveData = _checkoutData
+
+ private val _addressDetails = MutableLiveData()
+ val addressDetails: LiveData = _addressDetails
+
+ private val _paymentDetails = MutableLiveData()
+ val paymentDetails: LiveData = _paymentDetails
+
+ private val _isLoading = MutableLiveData()
+ val isLoading: LiveData = _isLoading
+
+ private val _errorMessage = MutableLiveData()
+ val errorMessage: LiveData = _errorMessage
+
+ private val _orderCreated = MutableLiveData()
+ val orderCreated: LiveData = _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 {
+ // 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 = ""
+ )
+
+ // 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) {
+ 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) {
+ 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()
+ 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) -> Unit) {
+ viewModelScope.launch {
+ try {
+ val storeId = _checkoutData.value?.sellerId ?: return@launch
+
+ // Use fetchStoreDetail instead of getStore
+ val storeResult = repository.fetchStoreDetail(storeId)
+
+ if (storeResult is Result.Success && storeResult.data != null) {
+ callback(storeResult.data.paymentInfo)
+ } 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 storeId = _checkoutData.value?.sellerId ?: return@launch
+
+ // Use fetchStoreDetail instead of getStore
+ val storeResult = repository.fetchStoreDetail(storeId)
+ if (storeResult is Result.Success && storeResult.data != null) {
+ // Find the selected payment in the payment info list
+ val payment = storeResult.data.paymentInfo.find { it.name == paymentId.toString() }
+ _paymentDetails.value = payment
+
+ // Update order request 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 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"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt
new file mode 100644
index 0000000..8621f0e
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/PaymentMethodAdapter.kt
@@ -0,0 +1,90 @@
+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.PaymentInfoItem
+import com.alya.ecommerce_serang.databinding.ItemPaymentMethodBinding
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.RequestOptions
+
+class PaymentMethodAdapter(
+ private val paymentMethods: List,
+ private val onPaymentSelected: (PaymentInfoItem) -> Unit
+) : RecyclerView.Adapter() {
+
+ // 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.name
+
+ // 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)
+ }
+ }
+
+ //selected by name
+ fun setSelectedPaymentName(paymentName: String) {
+ val position = paymentMethods.indexOfFirst { it.name == paymentName }
+ if (position != -1 && position != selectedPosition) {
+ selectPayment(position)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingActivity.kt
new file mode 100644
index 0000000..bc35d35
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingActivity.kt
@@ -0,0 +1,151 @@
+package com.alya.ecommerce_serang.ui.order
+
+import android.content.Intent
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.data.repository.OrderRepository
+import com.alya.ecommerce_serang.databinding.ActivityShippingBinding
+import com.alya.ecommerce_serang.utils.BaseViewModelFactory
+import com.alya.ecommerce_serang.utils.SessionManager
+
+class ShippingActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityShippingBinding
+ private lateinit var sessionManager: SessionManager
+ private lateinit var shippingAdapter: ShippingAdapter
+
+ private val viewModel: ShippingViewModel by viewModels {
+ BaseViewModelFactory {
+ val apiService = ApiConfig.getApiService(sessionManager)
+ val repository = OrderRepository(apiService)
+ ShippingViewModel(repository)
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityShippingBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ // Initialize SessionManager
+ sessionManager = SessionManager(this)
+
+ // 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()
+ }
+ }
+
+ private fun setupRecyclerView() {
+ shippingAdapter = ShippingAdapter { courierCostsItem, service ->
+ // Handle shipping method selection
+ returnSelectedShipping(
+ courierCostsItem.courier,
+ service.service,
+ service.cost,
+ service.etd
+ )
+ }
+
+ binding.rvShipmentOrder.apply {
+ layoutManager = LinearLayoutManager(this@ShippingActivity)
+ adapter = shippingAdapter
+ }
+ }
+
+ 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()
+ }
+ }
+ }
+
+ 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"
+ }
+}
+
+//val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+// if (result.resultCode == RESULT_OK) {
+// val data = result.data
+// val shipName = data?.getStringExtra("ship_name")
+// val shipPrice = data?.getIntExtra("ship_price", 0)
+// val shipService = data?.getStringExtra("ship_service")
+// // use the data as needed
+// }
+//}
+//
+//// launch the shipping activity
+//val intent = Intent(this, ShippingActivity::class.java).apply {
+// putExtra("address_id", addressId)
+// putExtra("product_id", productId)
+// putExtra("quantity", quantity)
+//}
+//launcher.launch(intent)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingAdapter.kt
new file mode 100644
index 0000000..cfbf401
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingAdapter.kt
@@ -0,0 +1,96 @@
+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.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: (CourierCostsItem, ServicesItem) -> Unit
+) : RecyclerView.Adapter() {
+
+ private val courierCostsList = mutableListOf()
+ private var selectedPosition = RecyclerView.NO_POSITION
+ private var selectedCourierPosition = RecyclerView.NO_POSITION
+
+ fun submitList(courierCostsList: List) {
+ this.courierCostsList.clear()
+ this.courierCostsList.addAll(courierCostsList)
+ notifyDataSetChanged()
+ }
+
+ inner class ShippingViewHolder(
+ private val binding: ItemShippingOrderBinding
+ ) : RecyclerView.ViewHolder(binding.root) {
+
+ 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}"
+
+ // 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)
+ }
+ }
+
+ 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 binding = ItemShippingOrderBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ )
+ return ShippingViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: ShippingViewHolder, position: Int) {
+ // 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 {
+ return courierCostsList.sumOf { it.services.size }
+ }
+}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingViewModel.kt
new file mode 100644
index 0000000..17696c7
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingViewModel.kt
@@ -0,0 +1,74 @@
+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.CourierCostsItem
+import com.alya.ecommerce_serang.data.repository.OrderRepository
+import com.alya.ecommerce_serang.data.repository.Result
+import kotlinx.coroutines.launch
+
+class ShippingViewModel(
+ private val repository: OrderRepository
+) : ViewModel() {
+
+ // Shipping options LiveData
+ private val _shippingOptions = MutableLiveData>()
+ val shippingOptions: LiveData> = _shippingOptions
+
+ // Loading state LiveData
+ private val _isLoading = MutableLiveData()
+ val isLoading: LiveData = _isLoading
+
+ // Error message LiveData
+ private val _errorMessage = MutableLiveData()
+ val errorMessage: LiveData = _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
+ )
+ )
+
+ viewModelScope.launch {
+ 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
+ }
+ }
+ } catch (e: Exception) {
+ // Catch any unexpected exceptions
+ _errorMessage.value = e.localizedMessage ?: "An unexpected error occurred"
+ } finally {
+ // Always set loading to false
+ _isLoading.value = false
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/SingleItemCartAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/SingleItemCartAdapter.kt
new file mode 100644
index 0000000..7f4d17e
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/SingleItemCartAdapter.kt
@@ -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() {
+
+ 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", "")
+ }
+}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/SingleProductAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/SingleProductAdapter.kt
new file mode 100644
index 0000000..0255a93
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/SingleProductAdapter.kt
@@ -0,0 +1,54 @@
+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.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 SingleProductAdapter(private val checkoutData: CheckoutData) :
+ RecyclerView.Adapter() {
+
+ 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
+ )
+ return ProductViewHolder(binding)
+ }
+
+ override fun getItemCount(): Int = 1
+
+ override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
+ with(holder.binding) {
+ // Set product details
+ tvProductName.text = checkoutData.productName
+
+ val quantity = (checkoutData.orderRequest as OrderRequestBuy).quantity
+ tvProductQuantity.text = "$quantity buah"
+
+ tvProductPrice.text = formatCurrency(checkoutData.productPrice)
+
+ // Load product image
+ Glide.with(ivProduct.context)
+ .load(checkoutData.productImageUrl)
+ .apply(
+ RequestOptions()
+ .placeholder(R.drawable.placeholder_image)
+ .error(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", "")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt
new file mode 100644
index 0000000..c4b3587
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt
@@ -0,0 +1,279 @@
+package com.alya.ecommerce_serang.ui.order.address
+
+import android.annotation.SuppressLint
+import android.content.pm.PackageManager
+import android.location.Location
+import android.location.LocationListener
+import android.location.LocationManager
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
+import com.alya.ecommerce_serang.data.api.dto.UserProfile
+import com.alya.ecommerce_serang.data.api.response.order.CitiesItem
+import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem
+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.databinding.ActivityAddAddressBinding
+import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory
+import com.alya.ecommerce_serang.utils.SessionManager
+import kotlinx.coroutines.launch
+
+class AddAddressActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityAddAddressBinding
+ private lateinit var apiService: ApiService
+ private lateinit var sessionManager: SessionManager
+ private lateinit var profileUser: UserProfile
+ private lateinit var locationManager: LocationManager
+
+ private var latitude: Double? = null
+ private var longitude: Double? = null
+ private val provinceAdapter by lazy { ProvinceAdapter(this) }
+ private val cityAdapter by lazy { CityAdapter(this) }
+
+ private val viewModel: AddAddressViewModel by viewModels {
+ SavedStateViewModelFactory(this) { savedStateHandle ->
+ val apiService = ApiConfig.getApiService(sessionManager)
+ val orderRepository = OrderRepository(apiService)
+ AddAddressViewModel(orderRepository, savedStateHandle)
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityAddAddressBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ sessionManager = SessionManager(this)
+ apiService = ApiConfig.getApiService(sessionManager)
+ locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
+
+ setupToolbar()
+ setupAutoComplete()
+ setupButtonListeners()
+ collectFlows()
+ requestLocationPermission()
+
+
+ }
+
+// private fun viewModelAddAddress(request: CreateAddressRequest) {
+// // Call the private fun in your ViewModel using reflection or expose it in ViewModel
+// val method = AddAddressViewModel::class.java.getDeclaredMethod("addAddress", CreateAddressRequest::class.java)
+// method.isAccessible = true
+// method.invoke(viewModel, request)
+// }
+ // UI setup methods
+private fun setupToolbar() {
+ binding.toolbar.setNavigationOnClickListener {
+ onBackPressedDispatcher.onBackPressed()
+ }
+}
+
+ private fun setupAutoComplete() {
+ // Set adapters
+ binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
+ binding.autoCompleteKabupaten.setAdapter(cityAdapter)
+
+ // Set listeners
+ binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
+ provinceAdapter.getProvinceId(position)?.let { provinceId ->
+ viewModel.getCities(provinceId)
+ binding.autoCompleteKabupaten.text.clear()
+ }
+ }
+
+ binding.autoCompleteKabupaten.setOnItemClickListener { _, _, position, _ ->
+ cityAdapter.getCityId(position)?.let { cityId ->
+ viewModel.selectedCityId = cityId
+ }
+ }
+ }
+
+ private fun setupButtonListeners() {
+ binding.buttonSimpan.setOnClickListener {
+ validateAndSubmitForm()
+ }
+ }
+
+ private fun collectFlows() {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.provincesState.collect { state ->
+ handleProvinceState(state)
+ }
+ }
+
+ launch {
+ viewModel.citiesState.collect { state ->
+ handleCityState(state)
+ }
+ }
+
+ launch {
+ viewModel.addressSubmissionState.collect { state ->
+ handleAddressSubmissionState(state)
+ }
+ }
+ }
+ }
+ }
+
+ private fun handleProvinceState(state: ViewState>) {
+ when (state) {
+ is ViewState.Loading -> null //showProvinceLoading(true)
+ is ViewState.Success -> {
+ provinceAdapter.updateData(state.data)
+ }
+ is ViewState.Error -> {
+ showError(state.message)
+ }
+ }
+ }
+
+ private fun handleCityState(state: ViewState>) {
+ when (state) {
+ is ViewState.Loading -> null //showCityLoading(true)
+ is ViewState.Success -> {
+// showCityLoading(false)
+ cityAdapter.updateData(state.data)
+ }
+ is ViewState.Error -> {
+// showCityLoading(false)
+ showError(state.message)
+ }
+ }
+ }
+
+ private fun handleAddressSubmissionState(state: ViewState) {
+ when (state) {
+ is ViewState.Loading -> showSubmitLoading(true)
+ is ViewState.Success -> {
+ showSubmitLoading(false)
+ showSuccessAndFinish(state.data)
+ }
+ is ViewState.Error -> {
+ showSubmitLoading(false)
+ showError(state.message)
+ }
+ }
+ }
+ private fun showSubmitLoading(isLoading: Boolean) {
+ binding.buttonSimpan.isEnabled = !isLoading
+ binding.buttonSimpan.text = if (isLoading) "Menyimpan..." else "Simpan"
+ // You might want to show a progress bar as well
+ }
+
+ private fun showError(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
+ }
+
+ private fun showSuccessAndFinish(message: String) {
+ Toast.makeText(this, "Sukses: $message", Toast.LENGTH_SHORT).show()
+ onBackPressed()
+ }
+
+ private fun validateAndSubmitForm() {
+ val lat = latitude
+ val long = longitude
+
+ if (lat == null || long == null) {
+ showError("Lokasi belum terdeteksi")
+ return
+ }
+
+ val street = binding.etDetailAlamat.text.toString()
+ val subDistrict = binding.etKecamatan.text.toString()
+ val postalCode = binding.etKodePos.text.toString()
+ val recipient = binding.etNamaPenerima.text.toString()
+ val phone = binding.etNomorHp.text.toString()
+ val userId = profileUser.userId
+ val isStoreLocation = false
+
+ val provinceId = viewModel.selectedProvinceId
+ val cityId = viewModel.selectedCityId
+
+ if (street.isBlank() || recipient.isBlank() || phone.isBlank()) {
+ showError("Lengkapi semua field wajib")
+ return
+ }
+
+ if (provinceId == null) {
+ showError("Pilih provinsi terlebih dahulu")
+ return
+ }
+
+ if (cityId == null) {
+ showError("Pilih kota/kabupaten terlebih dahulu")
+ return
+ }
+
+ val request = CreateAddressRequest(
+ lat = lat,
+ long = long,
+ street = street,
+ subDistrict = subDistrict,
+ cityId = cityId,
+ provId = provinceId,
+ postCode = postalCode,
+ detailAddress = street,
+ userId = userId,
+ recipient = recipient,
+ phone = phone,
+ isStoreLocation = isStoreLocation
+ )
+
+ viewModel.addAddress(request)
+ }
+
+ private val locationPermissionLauncher =
+ registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
+ if (granted) requestLocation() else Toast.makeText(this, "Izin lokasi ditolak",Toast.LENGTH_SHORT).show()
+ }
+
+ private fun requestLocationPermission() {
+ locationPermissionLauncher.launch(android.Manifest.permission.ACCESS_FINE_LOCATION)
+ }
+
+ @SuppressLint("MissingPermission")
+ private fun requestLocation() {
+ val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
+ val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
+
+ if (!isGpsEnabled && !isNetworkEnabled) {
+ Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ val provider = if (isGpsEnabled) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER
+
+ locationManager.requestSingleUpdate(provider, object : LocationListener {
+ override fun onLocationChanged(location: Location) {
+ latitude = location.latitude
+ longitude = location.longitude
+ }
+
+ override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
+ override fun onProviderEnabled(provider: String) {}
+ override fun onProviderDisabled(provider: String) {
+ Toast.makeText(this@AddAddressActivity, "Provider dimatikan", Toast.LENGTH_SHORT).show()
+ }
+ }, null)
+ }
+
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == 100 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ requestLocation()
+ } else {
+ Toast.makeText(this, "Location permission denied", Toast.LENGTH_SHORT).show()
+ }
+ }
+}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressViewModel.kt
new file mode 100644
index 0000000..fd462d2
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressViewModel.kt
@@ -0,0 +1,103 @@
+package com.alya.ecommerce_serang.ui.order.address
+
+import android.util.Log
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
+import com.alya.ecommerce_serang.data.api.response.order.CitiesItem
+import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem
+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.asStateFlow
+import kotlinx.coroutines.launch
+
+class AddAddressViewModel(private val repository: OrderRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
+ // Flow states for data
+ private val _addressSubmissionState = MutableStateFlow>(ViewState.Loading)
+ val addressSubmissionState = _addressSubmissionState.asStateFlow()
+
+ private val _provincesState = MutableStateFlow>>(ViewState.Loading)
+ val provincesState = _provincesState.asStateFlow()
+
+ private val _citiesState = MutableStateFlow>>(ViewState.Loading)
+ val citiesState = _citiesState.asStateFlow()
+
+ // Stored in SavedStateHandle for configuration changes
+ var selectedProvinceId: Int?
+ get() = savedStateHandle.get("selectedProvinceId")
+ set(value) { savedStateHandle["selectedProvinceId"] = value }
+
+ var selectedCityId: Int?
+ get() = savedStateHandle.get("selectedCityId")
+ set(value) { savedStateHandle["selectedCityId"] = value }
+
+ init {
+ // Load provinces on initialization
+ getProvinces()
+ }
+
+ fun addAddress(request: CreateAddressRequest){
+ viewModelScope.launch {
+ when (val result = repository.addAddress(request)) {
+ is Result.Success -> {
+ val message = result.data.message // Ambil `message` dari CreateAddressResponse
+ _addressSubmissionState.value = ViewState.Success(message)
+ }
+ is Result.Error -> {
+ _addressSubmissionState.value =
+ ViewState.Error(result.exception.message ?: "Unknown error")
+ }
+ is Result.Loading -> {
+ // Optional, karena sudah set Loading di awal
+ }
+ }
+ }
+ }
+
+ fun getProvinces(){
+ viewModelScope.launch {
+ try {
+ val result = repository.getListProvinces()
+ result?.let {
+ _provincesState.value = ViewState.Success(it.provinces)
+ }
+ } catch (e: Exception) {
+ Log.e("AddAddressViewModel", "Error fetching provinces: ${e.message}")
+ }
+ }
+ }
+
+ fun getCities(provinceId: Int){
+ viewModelScope.launch {
+ try {
+ selectedProvinceId = provinceId
+ val result = repository.getListCities(provinceId)
+ result?.let {
+ _citiesState.value = ViewState.Success(it.cities)
+ }
+ } catch (e: Exception) {
+ Log.e("AddAddressViewModel", "Error fetching cities: ${e.message}")
+ }
+ }
+ }
+
+ fun setSelectedProvinceId(id: Int) {
+ selectedProvinceId = id
+ }
+
+ fun setSelectedCityId(id: Int) {
+ selectedCityId = id
+ }
+
+ companion object {
+ private const val TAG = "AddAddressViewModel"
+ }
+}
+
+sealed class ViewState {
+ object Loading : ViewState()
+ data class Success(val data: T) : ViewState()
+ data class Error(val message: String) : ViewState()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressActivity.kt
new file mode 100644
index 0000000..7fe2b85
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressActivity.kt
@@ -0,0 +1,91 @@
+package com.alya.ecommerce_serang.ui.order.address
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+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.databinding.ActivityAddressBinding
+import com.alya.ecommerce_serang.utils.BaseViewModelFactory
+import com.alya.ecommerce_serang.utils.SessionManager
+
+class AddressActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityAddressBinding
+ private lateinit var apiService: ApiService
+ private lateinit var sessionManager: SessionManager
+ private lateinit var adapter: AddressAdapter
+
+ private val viewModel: AddressViewModel by viewModels {
+ BaseViewModelFactory {
+ val apiService = ApiConfig.getApiService(sessionManager)
+ val orderRepository = OrderRepository(apiService)
+ AddressViewModel(orderRepository)
+ }
+ }
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityAddressBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ sessionManager = SessionManager(this)
+ apiService = ApiConfig.getApiService(sessionManager)
+
+ setupToolbar()
+
+ adapter = AddressAdapter { selectedId ->
+ viewModel.selectAddress(selectedId)
+ }
+
+ binding.toolbar.setNavigationOnClickListener {
+ onBackPressedWithResult()
+ }
+
+ binding.rvSellerOrder.layoutManager = LinearLayoutManager(this)
+ binding.rvSellerOrder.adapter = adapter
+
+ viewModel.fetchAddresses()
+
+ viewModel.addresses.observe(this) { addressList ->
+ adapter.submitList(addressList)
+ }
+
+ viewModel.selectedAddressId.observe(this) { selectedId ->
+ 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(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?) {
+// super.onActivityResult(requestCode, resultCode, data)
+// if (requestCode == REQUEST_ADDRESS && resultCode == RESULT_OK) {
+// val selectedAddressId = data?.getIntExtra("selected_address_id", -1)
+// // Use the selected address ID
+// }
+//}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressAdapter.kt
new file mode 100644
index 0000000..297a3e7
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressAdapter.kt
@@ -0,0 +1,67 @@
+package com.alya.ecommerce_serang.ui.order.address
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.response.profile.AddressesItem
+import com.google.android.material.card.MaterialCardView
+
+class AddressAdapter(
+ private val onAddressClick: (Int) -> Unit
+) : ListAdapter(DIFF_CALLBACK) {
+
+ private var selectedAddressId: Int? = null
+
+ fun setSelectedAddressId(id: Int?) {
+ selectedAddressId = id
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddressViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_card_address, parent, false)
+ return AddressViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: AddressViewHolder, position: Int) {
+ val address = getItem(position)
+ holder.bind(address, selectedAddressId == address.id)
+ holder.itemView.setOnClickListener {
+ onAddressClick(address.id)
+ }
+ }
+
+ class AddressViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ private val tvName: TextView = itemView.findViewById(R.id.tv_name_address)
+ private val tvDetail: TextView = itemView.findViewById(R.id.tv_detail_address)
+ private val card: MaterialCardView = itemView as MaterialCardView
+
+ fun bind(address: AddressesItem, isSelected: Boolean) {
+ tvName.text = address.recipient
+ tvDetail.text = "${address.street}, ${address.subdistrict}, ${address.phone}"
+
+ card.setCardBackgroundColor(
+ ContextCompat.getColor(
+ itemView.context,
+ if (isSelected) R.color.blue_50 else R.color.white
+ )
+ )
+ }
+ }
+
+ companion object {
+ val DIFF_CALLBACK = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: AddressesItem, newItem: AddressesItem) =
+ oldItem.id == newItem.id
+
+ override fun areContentsTheSame(oldItem: AddressesItem, newItem: AddressesItem) =
+ oldItem == newItem
+ }
+ }
+}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressViewModel.kt
new file mode 100644
index 0000000..0898e2f
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressViewModel.kt
@@ -0,0 +1,31 @@
+package com.alya.ecommerce_serang.ui.order.address
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.alya.ecommerce_serang.data.api.response.profile.AddressesItem
+import com.alya.ecommerce_serang.data.repository.OrderRepository
+import kotlinx.coroutines.launch
+
+class AddressViewModel(private val repository: OrderRepository): ViewModel() {
+
+ private val _addresses = MutableLiveData>()
+ val addresses: LiveData> get() = _addresses
+
+ private val _selectedAddressId = MutableLiveData()
+ val selectedAddressId: LiveData get() = _selectedAddressId
+
+ fun fetchAddresses() {
+ viewModelScope.launch {
+ val response = repository.getAddress()
+ response?.let {
+ _addresses.value = it.addresses
+ }
+ }
+ }
+
+ fun selectAddress(id: Int) {
+ _selectedAddressId.value = id
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/EditAddressActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/EditAddressActivity.kt
new file mode 100644
index 0000000..574a7a8
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/EditAddressActivity.kt
@@ -0,0 +1,21 @@
+package com.alya.ecommerce_serang.ui.order.address
+
+import android.os.Bundle
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import com.alya.ecommerce_serang.R
+
+class EditAddressActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContentView(R.layout.activity_edit_address)
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt
new file mode 100644
index 0000000..44f7c29
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt
@@ -0,0 +1,49 @@
+package com.alya.ecommerce_serang.ui.order.address
+
+import android.content.Context
+import android.widget.ArrayAdapter
+import com.alya.ecommerce_serang.data.api.response.order.CitiesItem
+import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem
+
+// UI adapters and helpers
+class ProvinceAdapter(
+ context: Context,
+ resource: Int = android.R.layout.simple_dropdown_item_1line
+) : ArrayAdapter(context, resource, ArrayList()) {
+
+ private val provinces = ArrayList()
+
+ fun updateData(newProvinces: List) {
+ provinces.clear()
+ provinces.addAll(newProvinces)
+
+ clear()
+ addAll(provinces.map { it.province })
+ notifyDataSetChanged()
+ }
+
+ fun getProvinceId(position: Int): Int? {
+ return provinces.getOrNull(position)?.provinceId?.toIntOrNull()
+ }
+}
+
+class CityAdapter(
+ context: Context,
+ resource: Int = android.R.layout.simple_dropdown_item_1line
+) : ArrayAdapter(context, resource, ArrayList()) {
+
+ private val cities = ArrayList()
+
+ fun updateData(newCities: List) {
+ cities.clear()
+ cities.addAll(newCities)
+
+ clear()
+ addAll(cities.map { it.cityName })
+ notifyDataSetChanged()
+ }
+
+ fun getCityId(position: Int): Int? {
+ return cities.getOrNull(position)?.cityId?.toIntOrNull()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/product/DetailProductActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/product/DetailProductActivity.kt
index d577bde..bb10bdb 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/product/DetailProductActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/product/DetailProductActivity.kt
@@ -1,25 +1,37 @@
package com.alya.ecommerce_serang.ui.product
+import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
+import android.view.View
+import android.widget.Button
+import android.widget.ImageButton
+import android.widget.TextView
+import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.dto.CartItem
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
-import com.alya.ecommerce_serang.data.api.response.Product
-import com.alya.ecommerce_serang.data.api.response.ReviewsItem
+import com.alya.ecommerce_serang.data.api.response.product.Product
+import com.alya.ecommerce_serang.data.api.response.product.ReviewsItem
+import com.alya.ecommerce_serang.data.api.response.product.StoreProduct
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.ProductRepository
+import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding
import com.alya.ecommerce_serang.ui.home.HorizontalProductAdapter
+import com.alya.ecommerce_serang.ui.order.CheckoutActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
-import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel
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
@@ -27,12 +39,14 @@ class DetailProductActivity : AppCompatActivity() {
private lateinit var sessionManager: SessionManager
private var productAdapter: HorizontalProductAdapter? = null
private var reviewsAdapter: ReviewsAdapter? = null
+ private var currentQuantity = 1
- private val viewModel: ProductViewModel by viewModels {
+
+ private val viewModel: ProductUserViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val productRepository = ProductRepository(apiService)
- ProductViewModel(productRepository)
+ ProductUserViewModel(productRepository)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -43,56 +57,148 @@ class DetailProductActivity : AppCompatActivity() {
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager)
+ setupUI()
+ setupObservers()
+ loadData()
+ }
+
+ private fun loadData() {
val productId = intent.getIntExtra("PRODUCT_ID", -1)
- //nanti tambah get store id dari HomeFragment Product.storeId
if (productId == -1) {
Log.e("DetailProductActivity", "Invalid Product ID")
+ Toast.makeText(this, "Invalid product ID", Toast.LENGTH_SHORT).show()
finish() // Close activity if no valid ID
return
}
viewModel.loadProductDetail(productId)
viewModel.loadReviews(productId)
+ }
+ private fun setupObservers() {
viewModel.productDetail.observe(this) { product ->
- if (product != null) {
- Log.d("ProductDetail", "Name: ${product.productName}, Price: ${product.price}")
- // Update UI here, e.g., show in a TextView or ImageView
- viewModel.loadProductDetail(productId)
-
- } else {
- Log.e("ProductDetail", "Failed to fetch product details")
+ product?.let {
+ updateUI(it)
+ viewModel.loadOtherProducts(it.storeId)
}
}
- observeProductDetail()
- observeProductReviews()
- }
- private fun observeProductDetail() {
- viewModel.productDetail.observe(this) { product ->
- product?.let { updateUI(it) }
- }
- }
- private fun observeProductReviews() {
+ 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 ->
+ updateOtherProducts(products)
+ }
+
viewModel.reviewProduct.observe(this) { reviews ->
setupRecyclerViewReviewsProduct(reviews)
}
+
+ 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 Result.Success -> {
+ val cartId = result.data.data.cartId
+ Toast.makeText(this, result.data.message, Toast.LENGTH_SHORT).show()
+ }
+ is Result.Error -> {
+ Toast.makeText(this, "Failed to add to cart: ${result.exception.message}", Toast.LENGTH_SHORT).show()
+ }
+ is Result.Loading -> {
+ // Show loading indicator if needed
+ }
+ }
+ }
+ }
+
+ private fun updateStoreInfo(store: StoreProduct?) {
+ store?.let {
+ binding.tvSellerName.text = it.storeName
+ 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)
+ }
+ }
+
+ private fun updateOtherProducts(products: List) {
+ if (products.isEmpty()) {
+ binding.recyclerViewOtherProducts.visibility = View.GONE
+ binding.tvViewAllProducts.visibility = View.GONE
+ } else {
+ binding.recyclerViewOtherProducts.visibility = View.VISIBLE
+ binding.tvViewAllProducts.visibility = View.VISIBLE
+ productAdapter?.updateProducts(products)
+ } }
+
+ private fun setupUI() {
+// binding.btnBack.setOnClickListener {
+// finish()
+// }
+
+ binding.tvViewAllReviews.setOnClickListener {
+ viewModel.productDetail.value?.productId?.let { productId ->
+ handleAllReviewsClick(productId)
+ }
+ }
+
+ binding.btnBuyNow.setOnClickListener {
+ viewModel.productDetail.value?.productId?.let { id ->
+ showBuyNowPopup(id)
+ }
+ }
+
+ binding.btnAddToCart.setOnClickListener {
+ viewModel.productDetail.value?.productId?.let { id ->
+ showAddToCartPopup(id)
+ }
+ }
+
+ setupRecyclerViewOtherProducts()
}
private fun updateUI(product: Product){
binding.tvProductName.text = product.productName
- 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()
- binding.tvViewAllReviews.setOnClickListener{
- handleAllReviewsClick(product.productId)
- }
+
val fullImageUrl = when (val img = product.image) {
is String -> {
@@ -106,8 +212,6 @@ class DetailProductActivity : AppCompatActivity() {
.load(fullImageUrl)
.placeholder(R.drawable.placeholder_image)
.into(binding.ivProductImage)
-
- setupRecyclerViewOtherProducts()
}
private fun handleAllReviewsClick(productId: Int) {
@@ -119,7 +223,7 @@ class DetailProductActivity : AppCompatActivity() {
private fun setupRecyclerViewOtherProducts(){
productAdapter = HorizontalProductAdapter(
products = emptyList(),
- onClick = { productsItem -> handleProductClick(productsItem) }
+ onClick = { productsItem -> handleProductClick(productsItem) }
)
binding.recyclerViewOtherProducts.apply {
@@ -134,7 +238,15 @@ class DetailProductActivity : AppCompatActivity() {
private fun setupRecyclerViewReviewsProduct(reviewList: List){
val limitedReviewList = if (reviewList.isNotEmpty()) listOf(reviewList.first()) else emptyList()
-
+ if (reviewList.isEmpty()) {
+ binding.recyclerViewReviews.visibility = View.GONE
+ binding.tvViewAllReviews.visibility = View.GONE
+// binding.tvNoReviews.visibility = View.VISIBLE
+ } else {
+ binding.recyclerViewReviews.visibility = View.VISIBLE
+ binding.tvViewAllReviews.visibility = View.VISIBLE
+ }
+// binding.tvNoReviews.visibility = View.GONE
reviewsAdapter = ReviewsAdapter(
reviewList = limitedReviewList
)
@@ -154,4 +266,109 @@ class DetailProductActivity : AppCompatActivity() {
intent.putExtra("PRODUCT_ID", product.id) // Pass product ID
startActivity(intent)
}
+
+ private fun showBuyNowPopup(productId: Int) {
+ showQuantityDialog(productId, true)
+ }
+
+ private fun showAddToCartPopup(productId: Int) {
+ showQuantityDialog(productId, false)
+ }
+
+ private fun showQuantityDialog(productId: Int, isBuyNow: Boolean) {
+ val bottomSheetDialog = BottomSheetDialog(this)
+ val view = layoutInflater.inflate(R.layout.dialog_count_buy, null)
+ bottomSheetDialog.setContentView(view)
+
+ val btnDecrease = view.findViewById