mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-13 10:42:21 +00:00
Merge remote-tracking branch 'origin/master' into gracia
This commit is contained in:
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@ -1,4 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CodeInsightWorkspaceSettings">
|
<component name="CodeInsightWorkspaceSettings">
|
||||||
<option name="optimizeImportsOnTheFly" value="true" />
|
<option name="optimizeImportsOnTheFly" value="true" />
|
||||||
|
@ -6,10 +6,9 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@ -25,7 +24,25 @@
|
|||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".data.api.response.customer.cart.CartActivity"
|
android:name=".ui.order.detail.AddEvidencePaymentActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.order.history.HistoryActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.order.detail.PaymentActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.order.detail.AddEvidencePaymentActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.order.history.HistoryActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.order.detail.PaymentActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".data.api.response.cart.CartActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.order.address.EditAddressActivity"
|
android:name=".ui.order.address.EditAddressActivity"
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.dto
|
||||||
|
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
|
||||||
|
data class AddEvidenceMultipartRequest(
|
||||||
|
val orderId: RequestBody,
|
||||||
|
val amount: RequestBody,
|
||||||
|
val evidence: MultipartBody.Part
|
||||||
|
)
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.dto
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
|
||||||
|
data class AddEvidenceRequest (
|
||||||
|
@SerializedName("orer_id")
|
||||||
|
val orderId : Int,
|
||||||
|
|
||||||
|
@SerializedName("amount")
|
||||||
|
val amount : String,
|
||||||
|
|
||||||
|
@SerializedName("evidence")
|
||||||
|
val evidence: MultipartBody.Part
|
||||||
|
)
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.dto
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
|
||||||
|
data class ComplaintRequest (
|
||||||
|
@SerializedName("order_id")
|
||||||
|
val orderId: Int,
|
||||||
|
|
||||||
|
@SerializedName("description")
|
||||||
|
val description: String,
|
||||||
|
|
||||||
|
@SerializedName("complaintimg")
|
||||||
|
val complaintImg: MultipartBody.Part
|
||||||
|
|
||||||
|
)
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.dto
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class CompletedOrderRequest (
|
||||||
|
@SerializedName("order_id")
|
||||||
|
val orderId : Int,
|
||||||
|
|
||||||
|
@SerializedName("status")
|
||||||
|
val statusComplete: String
|
||||||
|
|
||||||
|
)
|
@ -7,7 +7,7 @@ data class CourierCostRequest(
|
|||||||
val addressId: Int,
|
val addressId: Int,
|
||||||
|
|
||||||
@SerializedName("items")
|
@SerializedName("items")
|
||||||
val itemCost: CostProduct
|
val itemCost: List<CostProduct>
|
||||||
)
|
)
|
||||||
|
|
||||||
data class CostProduct (
|
data class CostProduct (
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response.order
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class AddEvidenceResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("evidence")
|
||||||
|
val evidence: Evidence,
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Evidence(
|
||||||
|
|
||||||
|
@field:SerializedName("amount")
|
||||||
|
val amount: String,
|
||||||
|
|
||||||
|
@field:SerializedName("evidence")
|
||||||
|
val evidence: String,
|
||||||
|
|
||||||
|
@field:SerializedName("uploaded_at")
|
||||||
|
val uploadedAt: String,
|
||||||
|
|
||||||
|
@field:SerializedName("id")
|
||||||
|
val id: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("order_id")
|
||||||
|
val orderId: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("status")
|
||||||
|
val status: String
|
||||||
|
)
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response.order
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class ComplaintResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("voucher")
|
||||||
|
val voucher: Voucher,
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Voucher(
|
||||||
|
|
||||||
|
@field:SerializedName("solution")
|
||||||
|
val solution: Any,
|
||||||
|
|
||||||
|
@field:SerializedName("evidence")
|
||||||
|
val evidence: String,
|
||||||
|
|
||||||
|
@field:SerializedName("description")
|
||||||
|
val description: 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
|
||||||
|
)
|
@ -0,0 +1,51 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response.order
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class CompletedOrderResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String,
|
||||||
|
|
||||||
|
@field:SerializedName("updatedOrder")
|
||||||
|
val updatedOrder: UpdatedOrder,
|
||||||
|
|
||||||
|
@field:SerializedName("updatedItems")
|
||||||
|
val updatedItems: List<Any>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class UpdatedOrder(
|
||||||
|
|
||||||
|
@field:SerializedName("auto_completed_at")
|
||||||
|
val autoCompletedAt: Any,
|
||||||
|
|
||||||
|
@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: Any,
|
||||||
|
|
||||||
|
@field:SerializedName("payment_info_id")
|
||||||
|
val paymentInfoId: Any,
|
||||||
|
|
||||||
|
@field:SerializedName("id")
|
||||||
|
val id: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("status")
|
||||||
|
val status: String
|
||||||
|
)
|
@ -11,41 +11,20 @@ data class OrderDetailResponse(
|
|||||||
val message: String
|
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(
|
data class Orders(
|
||||||
|
|
||||||
@field:SerializedName("receipt_num")
|
@field:SerializedName("receipt_num")
|
||||||
val receiptNum: String,
|
val receiptNum: String? = null,
|
||||||
|
|
||||||
|
@field:SerializedName("payment_upload_at")
|
||||||
|
val paymentUploadAt: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("latitude")
|
@field:SerializedName("latitude")
|
||||||
val latitude: String,
|
val latitude: String,
|
||||||
|
|
||||||
|
@field:SerializedName("pay_info_name")
|
||||||
|
val payInfoName: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("created_at")
|
@field:SerializedName("created_at")
|
||||||
val createdAt: String,
|
val createdAt: String,
|
||||||
|
|
||||||
@ -62,7 +41,10 @@ data class Orders(
|
|||||||
val street: String,
|
val street: String,
|
||||||
|
|
||||||
@field:SerializedName("cancel_date")
|
@field:SerializedName("cancel_date")
|
||||||
val cancelDate: String,
|
val cancelDate: String? = null,
|
||||||
|
|
||||||
|
@field:SerializedName("payment_evidence")
|
||||||
|
val paymentEvidence: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("longitude")
|
@field:SerializedName("longitude")
|
||||||
val longitude: String,
|
val longitude: String,
|
||||||
@ -71,28 +53,34 @@ data class Orders(
|
|||||||
val shipmentStatus: String,
|
val shipmentStatus: String,
|
||||||
|
|
||||||
@field:SerializedName("order_items")
|
@field:SerializedName("order_items")
|
||||||
val orderItems: List<OrderItemsItem>,
|
val orderItems: List<OrderListItemsItem>,
|
||||||
|
|
||||||
@field:SerializedName("auto_completed_at")
|
@field:SerializedName("auto_completed_at")
|
||||||
val autoCompletedAt: String,
|
val autoCompletedAt: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("is_store_location")
|
@field:SerializedName("is_store_location")
|
||||||
val isStoreLocation: Boolean,
|
val isStoreLocation: Boolean? = null,
|
||||||
|
|
||||||
|
@field:SerializedName("qris_image")
|
||||||
|
val qrisImage: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("voucher_name")
|
@field:SerializedName("voucher_name")
|
||||||
val voucherName: String? = null,
|
val voucherName: String? = null,
|
||||||
|
|
||||||
|
@field:SerializedName("payment_status")
|
||||||
|
val paymentStatus: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("address_id")
|
@field:SerializedName("address_id")
|
||||||
val addressId: Int,
|
val addressId: Int,
|
||||||
|
|
||||||
@field:SerializedName("payment_method_id")
|
@field:SerializedName("payment_amount")
|
||||||
val paymentMethodId: Int,
|
val paymentAmount: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("cancel_reason")
|
@field:SerializedName("cancel_reason")
|
||||||
val cancelReason: String,
|
val cancelReason: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("total_amount")
|
@field:SerializedName("total_amount")
|
||||||
val totalAmount: String,
|
val totalAmount: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("user_id")
|
@field:SerializedName("user_id")
|
||||||
val userId: Int,
|
val userId: Int,
|
||||||
@ -109,12 +97,18 @@ data class Orders(
|
|||||||
@field:SerializedName("service")
|
@field:SerializedName("service")
|
||||||
val service: String,
|
val service: String,
|
||||||
|
|
||||||
|
@field:SerializedName("pay_info_num")
|
||||||
|
val payInfoNum: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("shipment_price")
|
@field:SerializedName("shipment_price")
|
||||||
val shipmentPrice: String,
|
val shipmentPrice: String,
|
||||||
|
|
||||||
@field:SerializedName("voucher_id")
|
@field:SerializedName("voucher_id")
|
||||||
val voucherId: Int? = null,
|
val voucherId: Int? = null,
|
||||||
|
|
||||||
|
@field:SerializedName("payment_info_id")
|
||||||
|
val paymentInfoId: Int? = null,
|
||||||
|
|
||||||
@field:SerializedName("detail")
|
@field:SerializedName("detail")
|
||||||
val detail: String,
|
val detail: String,
|
||||||
|
|
||||||
@ -127,3 +121,36 @@ data class Orders(
|
|||||||
@field:SerializedName("city_id")
|
@field:SerializedName("city_id")
|
||||||
val cityId: Int
|
val cityId: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class OrderListItemsItem(
|
||||||
|
|
||||||
|
@field:SerializedName("order_item_id")
|
||||||
|
val orderItemId: Int,
|
||||||
|
|
||||||
|
@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,
|
||||||
|
|
||||||
|
@field:SerializedName("product_id")
|
||||||
|
val productId: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("store_name")
|
||||||
|
val storeName: String,
|
||||||
|
|
||||||
|
@field:SerializedName("product_price")
|
||||||
|
val productPrice: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("product_name")
|
||||||
|
val productName: String
|
||||||
|
)
|
||||||
|
@ -11,10 +11,37 @@ data class OrderListResponse(
|
|||||||
val message: String
|
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,
|
||||||
|
|
||||||
|
@field:SerializedName("store_name")
|
||||||
|
val storeName: String,
|
||||||
|
|
||||||
|
@field:SerializedName("product_price")
|
||||||
|
val productPrice: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("product_name")
|
||||||
|
val productName: String
|
||||||
|
)
|
||||||
|
|
||||||
data class OrdersItem(
|
data class OrdersItem(
|
||||||
|
|
||||||
@field:SerializedName("receipt_num")
|
@field:SerializedName("receipt_num")
|
||||||
val receiptNum: String,
|
val receiptNum: Int? = null,
|
||||||
|
|
||||||
@field:SerializedName("latitude")
|
@field:SerializedName("latitude")
|
||||||
val latitude: String,
|
val latitude: String,
|
||||||
@ -28,9 +55,15 @@ data class OrdersItem(
|
|||||||
@field:SerializedName("updated_at")
|
@field:SerializedName("updated_at")
|
||||||
val updatedAt: String,
|
val updatedAt: String,
|
||||||
|
|
||||||
|
@field:SerializedName("etd")
|
||||||
|
val etd: String,
|
||||||
|
|
||||||
@field:SerializedName("street")
|
@field:SerializedName("street")
|
||||||
val street: String,
|
val street: String,
|
||||||
|
|
||||||
|
@field:SerializedName("cancel_date")
|
||||||
|
val cancelDate: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("longitude")
|
@field:SerializedName("longitude")
|
||||||
val longitude: String,
|
val longitude: String,
|
||||||
|
|
||||||
@ -40,8 +73,11 @@ data class OrdersItem(
|
|||||||
@field:SerializedName("order_items")
|
@field:SerializedName("order_items")
|
||||||
val orderItems: List<OrderItemsItem>,
|
val orderItems: List<OrderItemsItem>,
|
||||||
|
|
||||||
|
@field:SerializedName("auto_completed_at")
|
||||||
|
val autoCompletedAt: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("is_store_location")
|
@field:SerializedName("is_store_location")
|
||||||
val isStoreLocation: Boolean,
|
val isStoreLocation: Boolean? = null,
|
||||||
|
|
||||||
@field:SerializedName("voucher_name")
|
@field:SerializedName("voucher_name")
|
||||||
val voucherName: String? = null,
|
val voucherName: String? = null,
|
||||||
@ -49,8 +85,8 @@ data class OrdersItem(
|
|||||||
@field:SerializedName("address_id")
|
@field:SerializedName("address_id")
|
||||||
val addressId: Int,
|
val addressId: Int,
|
||||||
|
|
||||||
@field:SerializedName("payment_method_id")
|
@field:SerializedName("cancel_reason")
|
||||||
val paymentMethodId: Int,
|
val cancelReason: String? = null,
|
||||||
|
|
||||||
@field:SerializedName("total_amount")
|
@field:SerializedName("total_amount")
|
||||||
val totalAmount: String,
|
val totalAmount: String,
|
||||||
@ -76,6 +112,9 @@ data class OrdersItem(
|
|||||||
@field:SerializedName("voucher_id")
|
@field:SerializedName("voucher_id")
|
||||||
val voucherId: Int? = null,
|
val voucherId: Int? = null,
|
||||||
|
|
||||||
|
@field:SerializedName("payment_info_id")
|
||||||
|
val paymentInfoId: Int? = null,
|
||||||
|
|
||||||
@field:SerializedName("detail")
|
@field:SerializedName("detail")
|
||||||
val detail: String,
|
val detail: String,
|
||||||
|
|
||||||
@ -88,4 +127,3 @@ data class OrdersItem(
|
|||||||
@field:SerializedName("city_id")
|
@field:SerializedName("city_id")
|
||||||
val cityId: Int
|
val cityId: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ data class DetailStoreProductResponse(
|
|||||||
|
|
||||||
data class PaymentInfoItem(
|
data class PaymentInfoItem(
|
||||||
|
|
||||||
|
val id: Int = 1,
|
||||||
|
|
||||||
@field:SerializedName("qris_image")
|
@field:SerializedName("qris_image")
|
||||||
val qrisImage: String,
|
val qrisImage: String,
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.alya.ecommerce_serang.data.api.retrofit
|
package com.alya.ecommerce_serang.data.api.retrofit
|
||||||
|
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CartItem
|
import com.alya.ecommerce_serang.data.api.dto.CartItem
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
|
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.LoginRequest
|
import com.alya.ecommerce_serang.data.api.dto.LoginRequest
|
||||||
@ -19,10 +21,15 @@ import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
|
|||||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.AddCartResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.cart.AddCartResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.ListCartResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.cart.ListCartResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.UpdateCartResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.cart.UpdateCartResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateOrderResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateOrderResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.OrderDetailResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.OrderListResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.product.AllProductResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.product.AllProductResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.product.CategoryResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.product.CategoryResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.product.DetailStoreProductResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.product.DetailStoreProductResponse
|
||||||
@ -38,12 +45,13 @@ import com.alya.ecommerce_serang.data.api.response.store.product.UpdateProductRe
|
|||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.DELETE
|
import retrofit2.http.Field
|
||||||
|
import retrofit2.http.FormUrlEncoded
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Multipart
|
import retrofit2.http.Multipart
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
import retrofit2.http.Part
|
|
||||||
import retrofit2.http.PUT
|
import retrofit2.http.PUT
|
||||||
|
import retrofit2.http.Part
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
|
||||||
@ -93,6 +101,29 @@ interface ApiService {
|
|||||||
@Body request: OrderRequest
|
@Body request: OrderRequest
|
||||||
): Response<CreateOrderResponse>
|
): Response<CreateOrderResponse>
|
||||||
|
|
||||||
|
@GET("order/detail/{id}")
|
||||||
|
suspend fun getDetailOrder(
|
||||||
|
@Path("id") orderId: Int
|
||||||
|
): Response<OrderDetailResponse>
|
||||||
|
|
||||||
|
@POST("order/addevidence")
|
||||||
|
suspend fun addEvidence(
|
||||||
|
@Body request : AddEvidenceRequest,
|
||||||
|
): Response<AddEvidenceResponse>
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@POST("order/addevidence")
|
||||||
|
suspend fun addEvidenceMultipart(
|
||||||
|
@Part("order_id") orderId: RequestBody,
|
||||||
|
@Part("amount") amount: RequestBody,
|
||||||
|
@Part evidence: MultipartBody.Part
|
||||||
|
): Response<AddEvidenceResponse>
|
||||||
|
|
||||||
|
@GET("order/{status}")
|
||||||
|
suspend fun getOrderList(
|
||||||
|
@Path("status") status: String
|
||||||
|
):Response<OrderListResponse>
|
||||||
|
|
||||||
@POST("order")
|
@POST("order")
|
||||||
suspend fun postOrderBuyNow(
|
suspend fun postOrderBuyNow(
|
||||||
@Body request: OrderRequestBuy
|
@Body request: OrderRequestBuy
|
||||||
@ -180,4 +211,29 @@ interface ApiService {
|
|||||||
suspend fun getOrdersByStatus(
|
suspend fun getOrdersByStatus(
|
||||||
@Query("status") status: String
|
@Query("status") status: String
|
||||||
): Response<OrderListResponse>
|
): Response<OrderListResponse>
|
||||||
|
@PUT("store/order/update")
|
||||||
|
suspend fun confirmOrder(
|
||||||
|
@Body confirmOrder : CompletedOrderRequest
|
||||||
|
): Response<CompletedOrderResponse>
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@POST("addcomplaint")
|
||||||
|
suspend fun addComplaint(
|
||||||
|
@Part("order_id") orderId: RequestBody,
|
||||||
|
@Part("description") description: RequestBody,
|
||||||
|
@Part complaintimg: MultipartBody.Part
|
||||||
|
): Response<ComplaintResponse>
|
||||||
|
|
||||||
|
@PUT("store/order/update")
|
||||||
|
suspend fun confirmOrder(
|
||||||
|
@Body confirmOrder : CompletedOrderRequest
|
||||||
|
): Response<CompletedOrderResponse>
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@POST("addcomplaint")
|
||||||
|
suspend fun addComplaint(
|
||||||
|
@Part("order_id") orderId: RequestBody,
|
||||||
|
@Part("description") description: RequestBody,
|
||||||
|
@Part complaintimg: MultipartBody.Part
|
||||||
|
): Response<ComplaintResponse>
|
||||||
}
|
}
|
@ -1,24 +1,39 @@
|
|||||||
package com.alya.ecommerce_serang.data.repository
|
package com.alya.ecommerce_serang.data.repository
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceMultipartRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
|
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
|
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
import com.alya.ecommerce_serang.data.api.response.cart.DataItem
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItem
|
import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse
|
import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateOrderResponse
|
import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse
|
import com.alya.ecommerce_serang.data.api.response.order.CourierCostResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse
|
import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse
|
import com.alya.ecommerce_serang.data.api.response.order.ListCityResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreProduct
|
import com.alya.ecommerce_serang.data.api.response.order.ListProvinceResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse
|
import com.alya.ecommerce_serang.data.api.response.order.OrderDetailResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse
|
import com.alya.ecommerce_serang.data.api.response.order.OrderListResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse
|
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 com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class OrderRepository(private val apiService: ApiService) {
|
class OrderRepository(private val apiService: ApiService) {
|
||||||
|
|
||||||
@ -183,18 +198,27 @@ class OrderRepository(private val apiService: ApiService) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addAddress(createAddressRequest: CreateAddressRequest): Result<CreateAddressResponse> {
|
suspend fun addAddress(request: CreateAddressRequest): Result<CreateAddressResponse> {
|
||||||
return try {
|
return try {
|
||||||
val response = apiService.createAddress(createAddressRequest)
|
Log.d("OrderRepository", "Adding address: $request")
|
||||||
|
val response = apiService.createAddress(request)
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
response.body()?.let {
|
val createAddressResponse = response.body()
|
||||||
Result.Success(it)
|
if (createAddressResponse != null) {
|
||||||
} ?: Result.Error(Exception("Add Address failed"))
|
Log.d("OrderRepository", "Address added successfully: ${createAddressResponse.message}")
|
||||||
|
Result.Success(createAddressResponse)
|
||||||
} else {
|
} else {
|
||||||
Log.e("OrderRepository", "Error: ${response.errorBody()?.string()}")
|
Log.e("OrderRepository", "Response body was null")
|
||||||
Result.Error(Exception(response.errorBody()?.string() ?: "Unknown error"))
|
Result.Error(Exception("Empty response from server"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||||
|
Log.e("OrderRepository", "Error adding address: $errorBody")
|
||||||
|
Result.Error(Exception(errorBody))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.e("OrderRepository", "Exception adding address", e)
|
||||||
Result.Error(e)
|
Result.Error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -238,4 +262,203 @@ class OrderRepository(private val apiService: ApiService) {
|
|||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
suspend fun fetchUserProfile(): Result<UserProfile?> {
|
||||||
|
return try {
|
||||||
|
val response = apiService.getUserProfile()
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
response.body()?.user?.let {
|
||||||
|
Result.Success(it) // ✅ Returning only UserProfile
|
||||||
|
} ?: Result.Error(Exception("User data not found"))
|
||||||
|
} else {
|
||||||
|
Result.Error(Exception("Error fetching profile: ${response.code()}"))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getOrderDetails(orderId: Int): OrderDetailResponse? {
|
||||||
|
return try {
|
||||||
|
val response = apiService.getDetailOrder(orderId)
|
||||||
|
if (response.isSuccessful) response.body() else null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("OrderRepository", "Error getting order details", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// suspend fun uploadPaymentProof(request : AddEvidenceRequest): Result<AddEvidenceResponse> {
|
||||||
|
// return try {
|
||||||
|
// Log.d("OrderRepository", "Add Evidence : $request")
|
||||||
|
// val response = apiService.addEvidence(request)
|
||||||
|
//
|
||||||
|
// if (response.isSuccessful) {
|
||||||
|
// val addEvidenceResponse = response.body()
|
||||||
|
// if (addEvidenceResponse != null) {
|
||||||
|
// Log.d("OrderRepository", "Add Evidence successfully: ${addEvidenceResponse.message}")
|
||||||
|
// Result.Success(addEvidenceResponse)
|
||||||
|
// } else {
|
||||||
|
// Log.e("OrderRepository", "Response body was null")
|
||||||
|
// Result.Error(Exception("Empty response from server"))
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||||
|
// Log.e("OrderRepository", "Error Add Evidence : $errorBody")
|
||||||
|
// Result.Error(Exception(errorBody))
|
||||||
|
// }
|
||||||
|
// } catch (e: Exception) {
|
||||||
|
// Log.e("OrderRepository", "Exception Add Evidence ", e)
|
||||||
|
// Result.Error(e)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
suspend fun uploadPaymentProof(request: AddEvidenceMultipartRequest): Result<AddEvidenceResponse> {
|
||||||
|
return try {
|
||||||
|
Log.d("OrderRepository", "Uploading payment proof...")
|
||||||
|
|
||||||
|
val response = apiService.addEvidenceMultipart(
|
||||||
|
orderId = request.orderId,
|
||||||
|
amount = request.amount,
|
||||||
|
evidence = request.evidence
|
||||||
|
)
|
||||||
|
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val addEvidenceResponse = response.body()
|
||||||
|
if (addEvidenceResponse != null) {
|
||||||
|
Log.d("OrderRepository", "Payment proof uploaded successfully: ${addEvidenceResponse.message}")
|
||||||
|
Result.Success(addEvidenceResponse)
|
||||||
|
} else {
|
||||||
|
Log.e("OrderRepository", "Response body was null")
|
||||||
|
Result.Error(Exception("Empty response from server"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||||
|
Log.e("OrderRepository", "Error uploading payment proof: $errorBody")
|
||||||
|
Result.Error(Exception(errorBody))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("OrderRepository", "Exception uploading payment proof", e)
|
||||||
|
Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getOrderList(status: String): Result<OrderListResponse> {
|
||||||
|
return try {
|
||||||
|
Log.d("OrderRepository", "Add Evidence : $status")
|
||||||
|
val response = apiService.getOrderList(status)
|
||||||
|
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val allListOrder = response.body()
|
||||||
|
if (allListOrder != null) {
|
||||||
|
Log.d("OrderRepository", "Add Evidence successfully: ${allListOrder.message}")
|
||||||
|
Result.Success(allListOrder)
|
||||||
|
} else {
|
||||||
|
Log.e("OrderRepository", "Response body was null")
|
||||||
|
Result.Error(Exception("Empty response from server"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||||
|
Log.e("OrderRepository", "Error Add Evidence : $errorBody")
|
||||||
|
Result.Error(Exception(errorBody))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("OrderRepository", "Exception Add Evidence ", e)
|
||||||
|
Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun confirmOrderCompleted(request: CompletedOrderRequest): Result<CompletedOrderResponse> {
|
||||||
|
return try {
|
||||||
|
Log.d("OrderRepository", "Cinfroming order request completed: $request")
|
||||||
|
val response = apiService.confirmOrder(request)
|
||||||
|
|
||||||
|
if(response.isSuccessful) {
|
||||||
|
val completedOrderResponse = response.body()
|
||||||
|
if (completedOrderResponse != null) {
|
||||||
|
Log.d("OrderRepository", "Order confirmed successfully: ${completedOrderResponse.message}")
|
||||||
|
Result.Success(completedOrderResponse)
|
||||||
|
} else {
|
||||||
|
Log.e("OrderRepository", "Response body was null")
|
||||||
|
Result.Error(Exception("Empty response from server"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val errorBody = response.errorBody()?.string() ?: "Unknown Error"
|
||||||
|
Log.e("OrderRepository", "Error confirming order: $errorBody")
|
||||||
|
Result.Error(Exception(errorBody))
|
||||||
|
}
|
||||||
|
} catch (e: Exception){
|
||||||
|
Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitComplaint(
|
||||||
|
orderId: String,
|
||||||
|
reason: String,
|
||||||
|
imageFile: File?
|
||||||
|
): Flow<Result<ComplaintResponse>> = flow {
|
||||||
|
emit(Result.Loading)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Debug logging
|
||||||
|
Log.d("OrderRepository", "Submitting complaint for order: $orderId")
|
||||||
|
Log.d("OrderRepository", "Reason: $reason")
|
||||||
|
Log.d("OrderRepository", "Image file: ${imageFile?.absolutePath ?: "null"}")
|
||||||
|
|
||||||
|
// Create form data for the multipart request
|
||||||
|
// Explicitly convert orderId to string to ensure correct formatting
|
||||||
|
val orderIdRequestBody = orderId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
val reasonRequestBody = reason.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
|
||||||
|
// Create the image part for the API
|
||||||
|
val imagePart = if (imageFile != null && imageFile.exists()) {
|
||||||
|
// Use the actual image file
|
||||||
|
// Use asRequestBody() for files which is more efficient
|
||||||
|
val imageRequestBody = imageFile.asRequestBody("image/*".toMediaTypeOrNull())
|
||||||
|
MultipartBody.Part.createFormData(
|
||||||
|
"complaintimg",
|
||||||
|
imageFile.name,
|
||||||
|
imageRequestBody
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Create a simple empty part if no image
|
||||||
|
val dummyRequestBody = "".toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
MultipartBody.Part.createFormData(
|
||||||
|
"complaintimg",
|
||||||
|
"",
|
||||||
|
dummyRequestBody
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log request details before making the API call
|
||||||
|
Log.d("OrderRepository", "Making API call to add complaint")
|
||||||
|
Log.d("OrderRepository", "orderId: $orderId (as string)")
|
||||||
|
|
||||||
|
val response = apiService.addComplaint(
|
||||||
|
orderIdRequestBody,
|
||||||
|
reasonRequestBody,
|
||||||
|
imagePart
|
||||||
|
)
|
||||||
|
|
||||||
|
Log.d("OrderRepository", "Response code: ${response.code()}")
|
||||||
|
Log.d("OrderRepository", "Response message: ${response.message()}")
|
||||||
|
|
||||||
|
if (response.isSuccessful && response.body() != null) {
|
||||||
|
val complaintResponse = response.body() as ComplaintResponse
|
||||||
|
emit(Result.Success(complaintResponse))
|
||||||
|
} else {
|
||||||
|
// Get the error message from the response if possible
|
||||||
|
val errorBody = response.errorBody()?.string()
|
||||||
|
val errorMessage = if (!errorBody.isNullOrEmpty()) {
|
||||||
|
"Server error: $errorBody"
|
||||||
|
} else {
|
||||||
|
"Failed to submit complaint: ${response.code()} ${response.message()}"
|
||||||
|
}
|
||||||
|
Log.e("OrderRepository", errorMessage)
|
||||||
|
emit(Result.Error(Exception(errorMessage)))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("OrderRepository", "Error submitting complaint: ${e.message}")
|
||||||
|
emit(Result.Error(e))
|
||||||
|
}
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
}
|
}
|
@ -3,11 +3,15 @@ package com.alya.ecommerce_serang.ui.order
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
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.OrderRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||||
@ -42,6 +46,7 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
sessionManager = SessionManager(this)
|
sessionManager = SessionManager(this)
|
||||||
|
|
||||||
|
|
||||||
// Setup UI components
|
// Setup UI components
|
||||||
setupToolbar()
|
setupToolbar()
|
||||||
setupObservers()
|
setupObservers()
|
||||||
@ -74,6 +79,11 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.getPaymentMethods { paymentMethods ->
|
||||||
|
// Logging is just for debugging
|
||||||
|
Log.d("CheckoutActivity", "Loaded ${paymentMethods.size} payment methods")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupToolbar() {
|
private fun setupToolbar() {
|
||||||
@ -87,13 +97,6 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
viewModel.checkoutData.observe(this) { data ->
|
viewModel.checkoutData.observe(this) { data ->
|
||||||
setupProductRecyclerView(data)
|
setupProductRecyclerView(data)
|
||||||
updateOrderSummary()
|
updateOrderSummary()
|
||||||
|
|
||||||
// Load payment methods
|
|
||||||
viewModel.getPaymentMethods { paymentMethods ->
|
|
||||||
if (paymentMethods.isNotEmpty()) {
|
|
||||||
setupPaymentMethodsRecyclerView(paymentMethods)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Observe address details
|
// Observe address details
|
||||||
@ -102,14 +105,24 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
binding.tvAddress.text = "${address?.street}, ${address?.subdistrict}"
|
binding.tvAddress.text = "${address?.street}, ${address?.subdistrict}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Observe payment details
|
viewModel.availablePaymentMethods.observe(this) { paymentMethods ->
|
||||||
viewModel.paymentDetails.observe(this) { payment ->
|
if (paymentMethods.isNotEmpty()) {
|
||||||
if (payment != null) {
|
setupPaymentMethodsRecyclerView(paymentMethods)
|
||||||
// Update selected payment in adapter by name instead of ID
|
|
||||||
paymentAdapter?.setSelectedPaymentName(payment.name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Observe selected payment
|
||||||
|
viewModel.selectedPayment.observe(this) { selectedPayment ->
|
||||||
|
if (selectedPayment != null) {
|
||||||
|
// Update the adapter to show the selected payment
|
||||||
|
paymentAdapter?.setSelectedPaymentName(selectedPayment.name)
|
||||||
|
|
||||||
|
// Optional: Update other UI elements to show the selected payment
|
||||||
|
// For example: binding.tvSelectedPaymentMethod.text = selectedPayment.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Observe loading state
|
// Observe loading state
|
||||||
viewModel.isLoading.observe(this) { isLoading ->
|
viewModel.isLoading.observe(this) { isLoading ->
|
||||||
binding.btnPay.isEnabled = !isLoading
|
binding.btnPay.isEnabled = !isLoading
|
||||||
@ -133,6 +146,53 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupPaymentMethodsRecyclerView(paymentMethods: List<PaymentInfoItem>) {
|
||||||
|
if (paymentMethods.isEmpty()) {
|
||||||
|
Log.e("CheckoutActivity", "Payment methods list is empty")
|
||||||
|
Toast.makeText(this, "No payment methods available", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
Log.d("CheckoutActivity", "Setting up payment methods: ${paymentMethods.size} methods available")
|
||||||
|
|
||||||
|
paymentAdapter = PaymentMethodAdapter(paymentMethods) { payment ->
|
||||||
|
// We're using a hardcoded ID for now
|
||||||
|
viewModel.setPaymentMethod(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.rvPaymentMethods.apply {
|
||||||
|
layoutManager = LinearLayoutManager(this@CheckoutActivity)
|
||||||
|
adapter = paymentAdapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePaymentMethodsAdapter(paymentMethods: List<PaymentInfoItem>, selectedId: Int?) {
|
||||||
|
Log.d("CheckoutActivity", "Updating payment adapter with ${paymentMethods.size} methods")
|
||||||
|
|
||||||
|
// Simple test adapter
|
||||||
|
val testAdapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val textView = TextView(parent.context)
|
||||||
|
textView.setPadding(16, 16, 16, 16)
|
||||||
|
textView.textSize = 16f
|
||||||
|
return object : RecyclerView.ViewHolder(textView) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = paymentMethods.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
val payment = paymentMethods[position]
|
||||||
|
(holder.itemView as TextView).text = "Payment: ${payment.name}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.rvPaymentMethods.apply {
|
||||||
|
layoutManager = LinearLayoutManager(this@CheckoutActivity)
|
||||||
|
adapter = testAdapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupProductRecyclerView(checkoutData: CheckoutData) {
|
private fun setupProductRecyclerView(checkoutData: CheckoutData) {
|
||||||
val adapter = if (checkoutData.isBuyNow || checkoutData.cartItems.size <= 1) {
|
val adapter = if (checkoutData.isBuyNow || checkoutData.cartItems.size <= 1) {
|
||||||
CheckoutSellerAdapter(checkoutData)
|
CheckoutSellerAdapter(checkoutData)
|
||||||
@ -147,21 +207,6 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPaymentMethodsRecyclerView(paymentMethods: List<PaymentInfoItem>) {
|
|
||||||
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() {
|
private fun updateOrderSummary() {
|
||||||
viewModel.checkoutData.value?.let { data ->
|
viewModel.checkoutData.value?.let { data ->
|
||||||
// Update price information
|
// Update price information
|
||||||
@ -251,6 +296,9 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
val addressId = result.data?.getIntExtra(AddressActivity.EXTRA_ADDRESS_ID, 0) ?: 0
|
val addressId = result.data?.getIntExtra(AddressActivity.EXTRA_ADDRESS_ID, 0) ?: 0
|
||||||
if (addressId > 0) {
|
if (addressId > 0) {
|
||||||
viewModel.setSelectedAddress(addressId)
|
viewModel.setSelectedAddress(addressId)
|
||||||
|
|
||||||
|
// You might want to show a toast or some UI feedback
|
||||||
|
Toast.makeText(this, "Address selected successfully", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,7 +347,7 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if payment method is selected
|
// Check if payment method is selected
|
||||||
if (viewModel.paymentDetails.value == null) {
|
if (viewModel.selectedPayment.value == null) {
|
||||||
Toast.makeText(this, "Silakan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Silakan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,12 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
|||||||
private val _addressDetails = MutableLiveData<AddressesItem?>()
|
private val _addressDetails = MutableLiveData<AddressesItem?>()
|
||||||
val addressDetails: LiveData<AddressesItem?> = _addressDetails
|
val addressDetails: LiveData<AddressesItem?> = _addressDetails
|
||||||
|
|
||||||
private val _paymentDetails = MutableLiveData<PaymentInfoItem?>()
|
private val _availablePaymentMethods = MutableLiveData<List<PaymentInfoItem>>()
|
||||||
val paymentDetails: LiveData<PaymentInfoItem?> = _paymentDetails
|
val availablePaymentMethods: LiveData<List<PaymentInfoItem>> = _availablePaymentMethods
|
||||||
|
|
||||||
|
// Selected payment method
|
||||||
|
private val _selectedPayment = MutableLiveData<PaymentInfoItem?>()
|
||||||
|
val selectedPayment: LiveData<PaymentInfoItem?> = _selectedPayment
|
||||||
|
|
||||||
private val _isLoading = MutableLiveData<Boolean>()
|
private val _isLoading = MutableLiveData<Boolean>()
|
||||||
val isLoading: LiveData<Boolean> = _isLoading
|
val isLoading: LiveData<Boolean> = _isLoading
|
||||||
@ -144,7 +148,6 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get payment methods from API
|
|
||||||
fun getPaymentMethods(callback: (List<PaymentInfoItem>) -> Unit) {
|
fun getPaymentMethods(callback: (List<PaymentInfoItem>) -> Unit) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
@ -154,17 +157,78 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
|||||||
val storeResult = repository.fetchStoreDetail(storeId)
|
val storeResult = repository.fetchStoreDetail(storeId)
|
||||||
|
|
||||||
if (storeResult is Result.Success && storeResult.data != null) {
|
if (storeResult is Result.Success && storeResult.data != null) {
|
||||||
callback(storeResult.data.paymentInfo)
|
// For now, we'll use hardcoded payment ID (1) for all payment methods
|
||||||
|
// This will be updated once the backend provides proper IDs
|
||||||
|
val paymentMethodsList = storeResult.data.paymentInfo.map { paymentInfo ->
|
||||||
|
PaymentInfoItem(
|
||||||
|
id = 1, // Hardcoded payment ID
|
||||||
|
name = paymentInfo.name,
|
||||||
|
bankNum = paymentInfo.bankNum,
|
||||||
|
qrisImage = paymentInfo.qrisImage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Fetched ${paymentMethodsList.size} payment methods")
|
||||||
|
|
||||||
|
_availablePaymentMethods.value = paymentMethodsList
|
||||||
|
callback(paymentMethodsList)
|
||||||
} else {
|
} else {
|
||||||
|
_availablePaymentMethods.value = emptyList()
|
||||||
callback(emptyList())
|
callback(emptyList())
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error fetching payment methods", e)
|
Log.e(TAG, "Error fetching payment methods", e)
|
||||||
|
_availablePaymentMethods.value = emptyList()
|
||||||
callback(emptyList())
|
callback(emptyList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updated setPaymentMethod function
|
||||||
|
fun setPaymentMethod(paymentId: Int) {
|
||||||
|
// We'll use the hardcoded ID (1) for now
|
||||||
|
val currentPaymentId = 1
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
// Get the available payment methods
|
||||||
|
val paymentMethods = _availablePaymentMethods.value
|
||||||
|
|
||||||
|
if (paymentMethods.isNullOrEmpty()) {
|
||||||
|
// If no payment methods available, try to fetch them
|
||||||
|
getPaymentMethods { /* do nothing here */ }
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the first payment method (or specific one if you prefer)
|
||||||
|
val selectedPayment = paymentMethods.first()
|
||||||
|
|
||||||
|
// Set the selected payment
|
||||||
|
_selectedPayment.value = selectedPayment
|
||||||
|
Log.d(TAG, "Payment selected: Name=${selectedPayment.name}")
|
||||||
|
|
||||||
|
// Update the order request with the payment method ID (hardcoded for now)
|
||||||
|
val currentData = _checkoutData.value ?: return@launch
|
||||||
|
|
||||||
|
// Different handling for Buy Now vs Cart checkout
|
||||||
|
if (currentData.isBuyNow) {
|
||||||
|
// For Buy Now checkout
|
||||||
|
val buyRequest = currentData.orderRequest as OrderRequestBuy
|
||||||
|
val updatedRequest = buyRequest.copy(paymentMethodId = currentPaymentId)
|
||||||
|
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||||
|
} else {
|
||||||
|
// For Cart checkout
|
||||||
|
val cartRequest = currentData.orderRequest as OrderRequest
|
||||||
|
val updatedRequest = cartRequest.copy(paymentMethodId = currentPaymentId)
|
||||||
|
_checkoutData.value = currentData.copy(orderRequest = updatedRequest)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_errorMessage.value = "Error setting payment method: ${e.message}"
|
||||||
|
Log.e(TAG, "Error setting payment method", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set selected address
|
// Set selected address
|
||||||
fun setSelectedAddress(addressId: Int) {
|
fun setSelectedAddress(addressId: Int) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
@ -227,39 +291,6 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// Create order
|
||||||
fun createOrder() {
|
fun createOrder() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
@ -14,8 +14,8 @@ class PaymentMethodAdapter(
|
|||||||
private val onPaymentSelected: (PaymentInfoItem) -> Unit
|
private val onPaymentSelected: (PaymentInfoItem) -> Unit
|
||||||
) : RecyclerView.Adapter<PaymentMethodAdapter.PaymentMethodViewHolder>() {
|
) : RecyclerView.Adapter<PaymentMethodAdapter.PaymentMethodViewHolder>() {
|
||||||
|
|
||||||
// Track the selected position
|
// Selected payment name
|
||||||
private var selectedPosition = -1
|
private var selectedPaymentName: String? = null
|
||||||
|
|
||||||
class PaymentMethodViewHolder(val binding: ItemPaymentMethodBinding) :
|
class PaymentMethodViewHolder(val binding: ItemPaymentMethodBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root)
|
RecyclerView.ViewHolder(binding.root)
|
||||||
@ -38,14 +38,23 @@ class PaymentMethodAdapter(
|
|||||||
// Set payment method name
|
// Set payment method name
|
||||||
tvPaymentMethodName.text = payment.name
|
tvPaymentMethodName.text = payment.name
|
||||||
|
|
||||||
// Set radio button state
|
// // Set bank account number if available
|
||||||
rbPaymentMethod.isChecked = selectedPosition == position
|
// if (!payment.bankNum.isNullOrEmpty()) {
|
||||||
|
// tvPaymentAccountNumber.visibility = View.VISIBLE
|
||||||
|
// tvPaymentAccountNumber.text = payment.bankNum
|
||||||
|
// } else {
|
||||||
|
// tvPaymentAccountNumber.visibility = View.GONE
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Set radio button state based on selected payment name
|
||||||
|
rbPaymentMethod.isChecked = payment.name == selectedPaymentName
|
||||||
|
|
||||||
// Load payment icon if available
|
// Load payment icon if available
|
||||||
if (payment.qrisImage.isNotEmpty()) {
|
if (!payment.qrisImage.isNullOrEmpty()) {
|
||||||
Glide.with(ivPaymentMethod.context)
|
Glide.with(ivPaymentMethod.context)
|
||||||
.load(payment.qrisImage)
|
.load(payment.qrisImage)
|
||||||
.apply(RequestOptions()
|
.apply(
|
||||||
|
RequestOptions()
|
||||||
.placeholder(R.drawable.outline_store_24)
|
.placeholder(R.drawable.outline_store_24)
|
||||||
.error(R.drawable.outline_store_24))
|
.error(R.drawable.outline_store_24))
|
||||||
.into(ivPaymentMethod)
|
.into(ivPaymentMethod)
|
||||||
@ -56,35 +65,21 @@ class PaymentMethodAdapter(
|
|||||||
|
|
||||||
// Handle click on the entire item
|
// Handle click on the entire item
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
selectPayment(position)
|
|
||||||
onPaymentSelected(payment)
|
onPaymentSelected(payment)
|
||||||
|
setSelectedPaymentName(payment.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle click on the radio button
|
// Handle click on the radio button
|
||||||
rbPaymentMethod.setOnClickListener {
|
rbPaymentMethod.setOnClickListener {
|
||||||
selectPayment(position)
|
|
||||||
onPaymentSelected(payment)
|
onPaymentSelected(payment)
|
||||||
|
setSelectedPaymentName(payment.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to handle payment selection
|
// Set selected payment by name and refresh the UI
|
||||||
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) {
|
fun setSelectedPaymentName(paymentName: String) {
|
||||||
val position = paymentMethods.indexOfFirst { it.name == paymentName }
|
selectedPaymentName = paymentName
|
||||||
if (position != -1 && position != selectedPosition) {
|
notifyDataSetChanged() // Update all items to reflect selection change
|
||||||
selectPayment(position)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,10 +2,11 @@ package com.alya.ecommerce_serang.ui.order
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
@ -18,6 +19,7 @@ class ShippingActivity : AppCompatActivity() {
|
|||||||
private lateinit var binding: ActivityShippingBinding
|
private lateinit var binding: ActivityShippingBinding
|
||||||
private lateinit var sessionManager: SessionManager
|
private lateinit var sessionManager: SessionManager
|
||||||
private lateinit var shippingAdapter: ShippingAdapter
|
private lateinit var shippingAdapter: ShippingAdapter
|
||||||
|
private val TAG = "ShippingActivity"
|
||||||
|
|
||||||
private val viewModel: ShippingViewModel by viewModels {
|
private val viewModel: ShippingViewModel by viewModels {
|
||||||
BaseViewModelFactory {
|
BaseViewModelFactory {
|
||||||
@ -40,8 +42,11 @@ class ShippingActivity : AppCompatActivity() {
|
|||||||
val productId = intent.getIntExtra(EXTRA_PRODUCT_ID, 0)
|
val productId = intent.getIntExtra(EXTRA_PRODUCT_ID, 0)
|
||||||
val quantity = intent.getIntExtra(EXTRA_QUANTITY, 1)
|
val quantity = intent.getIntExtra(EXTRA_QUANTITY, 1)
|
||||||
|
|
||||||
|
Log.d(TAG, "Received data: addressId=$addressId, productId=$productId, quantity=$quantity")
|
||||||
|
|
||||||
// Validate required information
|
// Validate required information
|
||||||
if (addressId <= 0 || productId <= 0) {
|
if (addressId <= 0 || productId <= 0) {
|
||||||
|
Log.e(TAG, "Missing required shipping information: addressId=$addressId, productId=$productId")
|
||||||
Toast.makeText(this, "Missing required shipping information", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Missing required shipping information", Toast.LENGTH_SHORT).show()
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
@ -51,9 +56,10 @@ class ShippingActivity : AppCompatActivity() {
|
|||||||
setupToolbar()
|
setupToolbar()
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setupObservers()
|
setupObservers()
|
||||||
|
setupRetryButton() // Add a retry button for error cases
|
||||||
|
|
||||||
// Load shipping options
|
// Load shipping options
|
||||||
viewModel.loadShippingOptions(addressId, productId, quantity)
|
loadShippingOptions(addressId, productId, quantity)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupToolbar() {
|
private fun setupToolbar() {
|
||||||
@ -65,6 +71,7 @@ class ShippingActivity : AppCompatActivity() {
|
|||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
shippingAdapter = ShippingAdapter { courierCostsItem, service ->
|
shippingAdapter = ShippingAdapter { courierCostsItem, service ->
|
||||||
// Handle shipping method selection
|
// Handle shipping method selection
|
||||||
|
Log.d(TAG, "Selected shipping: ${courierCostsItem.courier} - ${service.service} - ${service.cost} - ${service.etd}")
|
||||||
returnSelectedShipping(
|
returnSelectedShipping(
|
||||||
courierCostsItem.courier,
|
courierCostsItem.courier,
|
||||||
service.service,
|
service.service,
|
||||||
@ -79,29 +86,65 @@ class ShippingActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupRetryButton() {
|
||||||
|
// If you have a retry button in your layout
|
||||||
|
// binding.btnRetry?.setOnClickListener {
|
||||||
|
// val addressId = intent.getIntExtra(EXTRA_ADDRESS_ID, 0)
|
||||||
|
// val productId = intent.getIntExtra(EXTRA_PRODUCT_ID, 0)
|
||||||
|
// val quantity = intent.getIntExtra(EXTRA_QUANTITY, 1)
|
||||||
|
// loadShippingOptions(addressId, productId, quantity)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadShippingOptions(addressId: Int, productId: Int, quantity: Int) {
|
||||||
|
// Show loading state
|
||||||
|
binding.progressBar?.visibility = View.VISIBLE
|
||||||
|
binding.rvShipmentOrder.visibility = View.GONE
|
||||||
|
// binding.layoutEmptyShipping?.visibility = View.GONE
|
||||||
|
|
||||||
|
// Load shipping options
|
||||||
|
Log.d(TAG, "Loading shipping options: addressId=$addressId, productId=$productId, quantity=$quantity")
|
||||||
|
viewModel.loadShippingOptions(addressId, productId, quantity)
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupObservers() {
|
private fun setupObservers() {
|
||||||
// Observe shipping options
|
// Observe shipping options
|
||||||
viewModel.shippingOptions.observe(this) { courierOptions ->
|
viewModel.shippingOptions.observe(this) { courierOptions ->
|
||||||
|
Log.d(TAG, "Received ${courierOptions.size} shipping options")
|
||||||
shippingAdapter.submitList(courierOptions)
|
shippingAdapter.submitList(courierOptions)
|
||||||
updateEmptyState(courierOptions.isEmpty() || courierOptions.all { it.services.isEmpty() })
|
updateEmptyState(courierOptions.isEmpty() || courierOptions.all { it.services.isEmpty() })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Observe loading state
|
// Observe loading state
|
||||||
viewModel.isLoading.observe(this) { isLoading ->
|
viewModel.isLoading.observe(this) { isLoading ->
|
||||||
// binding.progressBar.isVisible = isLoading
|
binding.progressBar?.visibility = if (isLoading) View.VISIBLE else View.GONE
|
||||||
|
Log.d(TAG, "Loading state: $isLoading")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Observe error messages
|
// Observe error messages
|
||||||
viewModel.errorMessage.observe(this) { message ->
|
viewModel.errorMessage.observe(this) { message ->
|
||||||
if (message.isNotEmpty()) {
|
if (message.isNotEmpty()) {
|
||||||
|
Log.e(TAG, "Error: $message")
|
||||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
// Show error view if you have one
|
||||||
|
// binding.layoutError?.visibility = View.VISIBLE
|
||||||
|
// binding.tvErrorMessage?.text = message
|
||||||
|
} else {
|
||||||
|
// binding.layoutError?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateEmptyState(isEmpty: Boolean) {
|
private fun updateEmptyState(isEmpty: Boolean) {
|
||||||
// binding.layoutEmptyShipping.isVisible = isEmpty
|
Log.d(TAG, "Updating empty state: isEmpty=$isEmpty")
|
||||||
binding.rvShipmentOrder.isVisible = !isEmpty
|
// binding.layoutEmptyShipping?.visibility = if (isEmpty) View.VISIBLE else View.GONE
|
||||||
|
binding.rvShipmentOrder.visibility = if (isEmpty) View.GONE else View.VISIBLE
|
||||||
|
|
||||||
|
// If empty, show appropriate message
|
||||||
|
if (isEmpty) {
|
||||||
|
// binding.tvEmptyMessage?.text = "No shipping options available for this address and product"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun returnSelectedShipping(
|
private fun returnSelectedShipping(
|
||||||
@ -116,11 +159,13 @@ class ShippingActivity : AppCompatActivity() {
|
|||||||
putExtra(EXTRA_SHIP_PRICE, shipPrice)
|
putExtra(EXTRA_SHIP_PRICE, shipPrice)
|
||||||
putExtra(EXTRA_SHIP_ETD, shipEtd)
|
putExtra(EXTRA_SHIP_ETD, shipEtd)
|
||||||
}
|
}
|
||||||
|
Log.d(TAG, "Returning selected shipping: name=$shipName, service=$shipService, price=$shipPrice, etd=$shipEtd")
|
||||||
setResult(RESULT_OK, intent)
|
setResult(RESULT_OK, intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
// Constants for intent extras
|
// Constants for intent extras
|
||||||
const val EXTRA_ADDRESS_ID = "extra_address_id"
|
const val EXTRA_ADDRESS_ID = "extra_address_id"
|
||||||
const val EXTRA_PRODUCT_ID = "extra_product_id"
|
const val EXTRA_PRODUCT_ID = "extra_product_id"
|
||||||
|
@ -36,12 +36,14 @@ class ShippingViewModel(
|
|||||||
_errorMessage.value = ""
|
_errorMessage.value = ""
|
||||||
|
|
||||||
// Prepare the request
|
// Prepare the request
|
||||||
val request = CourierCostRequest(
|
val costProduct = CostProduct(
|
||||||
addressId = addressId,
|
|
||||||
itemCost = CostProduct(
|
|
||||||
productId = productId,
|
productId = productId,
|
||||||
quantity = quantity
|
quantity = quantity
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val request = CourierCostRequest(
|
||||||
|
addressId = addressId,
|
||||||
|
itemCost = listOf(costProduct) // Wrap in a list
|
||||||
)
|
)
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
package com.alya.ecommerce_serang.ui.order.address
|
package com.alya.ecommerce_serang.ui.order.address
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.pm.PackageManager
|
import android.content.Intent
|
||||||
|
import android.location.Criteria
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationListener
|
import android.location.LocationListener
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
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.CreateAddressRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
|
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
|
||||||
@ -20,18 +24,20 @@ import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem
|
|||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
import com.alya.ecommerce_serang.databinding.ActivityAddAddressBinding
|
import com.alya.ecommerce_serang.databinding.ActivityAddAddressBinding
|
||||||
import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory
|
import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class AddAddressActivity : AppCompatActivity() {
|
class AddAddressActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityAddAddressBinding
|
private lateinit var binding: ActivityAddAddressBinding
|
||||||
private lateinit var apiService: ApiService
|
private lateinit var apiService: ApiService
|
||||||
private lateinit var sessionManager: SessionManager
|
private lateinit var sessionManager: SessionManager
|
||||||
private lateinit var profileUser: UserProfile
|
private var profileUser: Int = 1
|
||||||
private lateinit var locationManager: LocationManager
|
private lateinit var locationManager: LocationManager
|
||||||
|
|
||||||
|
private var isRequestingLocation = false
|
||||||
|
|
||||||
private var latitude: Double? = null
|
private var latitude: Double? = null
|
||||||
private var longitude: Double? = null
|
private var longitude: Double? = null
|
||||||
private val provinceAdapter by lazy { ProvinceAdapter(this) }
|
private val provinceAdapter by lazy { ProvinceAdapter(this) }
|
||||||
@ -41,7 +47,8 @@ class AddAddressActivity : AppCompatActivity() {
|
|||||||
SavedStateViewModelFactory(this) { savedStateHandle ->
|
SavedStateViewModelFactory(this) { savedStateHandle ->
|
||||||
val apiService = ApiConfig.getApiService(sessionManager)
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
val orderRepository = OrderRepository(apiService)
|
val orderRepository = OrderRepository(apiService)
|
||||||
AddAddressViewModel(orderRepository, savedStateHandle)
|
val userRepository = UserRepository(apiService)
|
||||||
|
AddAddressViewModel(orderRepository, userRepository, savedStateHandle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,21 +61,29 @@ class AddAddressActivity : AppCompatActivity() {
|
|||||||
apiService = ApiConfig.getApiService(sessionManager)
|
apiService = ApiConfig.getApiService(sessionManager)
|
||||||
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
||||||
|
|
||||||
setupToolbar()
|
// Get user profile from session manager
|
||||||
setupAutoComplete()
|
// profileUser =UserProfile.
|
||||||
setupButtonListeners()
|
viewModel.userProfile.observe(this){ user ->
|
||||||
collectFlows()
|
user?.let { updateProfile(it) }
|
||||||
requestLocationPermission()
|
}
|
||||||
|
|
||||||
|
setupToolbar()
|
||||||
|
requestLocationPermission()
|
||||||
|
setupReloadButtons()
|
||||||
|
setupAutoComplete()
|
||||||
|
setupButtonListeners()
|
||||||
|
setupObservers()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Force trigger province loading to ensure it happens
|
||||||
|
viewModel.getProvinces()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateProfile(userProfile: UserProfile){
|
||||||
|
profileUser = userProfile.userId
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// UI setup methods
|
||||||
private fun setupToolbar() {
|
private fun setupToolbar() {
|
||||||
binding.toolbar.setNavigationOnClickListener {
|
binding.toolbar.setNavigationOnClickListener {
|
||||||
@ -77,22 +92,48 @@ private fun setupToolbar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setupAutoComplete() {
|
private fun setupAutoComplete() {
|
||||||
|
Log.d(TAG, "Setting up AutoComplete dropdowns")
|
||||||
// Set adapters
|
// Set adapters
|
||||||
binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
|
binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
|
||||||
binding.autoCompleteKabupaten.setAdapter(cityAdapter)
|
binding.autoCompleteKabupaten.setAdapter(cityAdapter)
|
||||||
|
|
||||||
// Set listeners
|
// Make dropdown appear on click (not just when typing)
|
||||||
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
|
binding.autoCompleteProvinsi.setOnClickListener {
|
||||||
provinceAdapter.getProvinceId(position)?.let { provinceId ->
|
Log.d(TAG, "Province dropdown clicked, showing dropdown")
|
||||||
viewModel.getCities(provinceId)
|
binding.autoCompleteProvinsi.showDropDown()
|
||||||
binding.autoCompleteKabupaten.text.clear()
|
}
|
||||||
|
|
||||||
|
binding.autoCompleteKabupaten.setOnClickListener {
|
||||||
|
// Only show dropdown if we have cities loaded
|
||||||
|
if (cityAdapter.count > 0) {
|
||||||
|
Log.d(TAG, "City dropdown clicked, showing dropdown with ${cityAdapter.count} items")
|
||||||
|
binding.autoCompleteKabupaten.showDropDown()
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "City dropdown clicked but no cities available")
|
||||||
|
Toast.makeText(this, "Pilih provinsi terlebih dahulu", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.autoCompleteKabupaten.setOnItemClickListener { _, _, position, _ ->
|
// Set listeners for selection
|
||||||
cityAdapter.getCityId(position)?.let { cityId ->
|
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
|
||||||
viewModel.selectedCityId = cityId
|
val provinceId = provinceAdapter.getProvinceId(position)
|
||||||
|
Log.d(TAG, "Province selected at position $position, provinceId=$provinceId")
|
||||||
|
|
||||||
|
provinceId?.let { id ->
|
||||||
|
Log.d(TAG, "Getting cities for provinceId=$id")
|
||||||
|
viewModel.getCities(id)
|
||||||
|
binding.autoCompleteKabupaten.text.clear()
|
||||||
|
} ?: Log.e(TAG, "Could not get provinceId for position $position")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.autoCompleteKabupaten.setOnItemClickListener { _, _, position, _ ->
|
||||||
|
val cityId = cityAdapter.getCityId(position)
|
||||||
|
Log.d(TAG, "City selected at position $position, cityId=$cityId")
|
||||||
|
|
||||||
|
cityId?.let { id ->
|
||||||
|
Log.d(TAG, "Setting selectedCityId=$id")
|
||||||
|
viewModel.selectedCityId = id
|
||||||
|
} ?: Log.e(TAG, "Could not get cityId for position $position")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,73 +143,93 @@ private fun setupToolbar() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectFlows() {
|
private fun setupObservers() {
|
||||||
lifecycleScope.launch {
|
Log.d(TAG, "Setting up LiveData observers")
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
launch {
|
// Observe provinces
|
||||||
viewModel.provincesState.collect { state ->
|
viewModel.provincesState.observe(this) { state ->
|
||||||
|
Log.d(TAG, "Received provincesState update: $state")
|
||||||
handleProvinceState(state)
|
handleProvinceState(state)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
launch {
|
// Observe cities
|
||||||
viewModel.citiesState.collect { state ->
|
viewModel.citiesState.observe(this) { state ->
|
||||||
|
Log.d(TAG, "Received citiesState update: $state")
|
||||||
handleCityState(state)
|
handleCityState(state)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
launch {
|
// Observe address submission
|
||||||
viewModel.addressSubmissionState.collect { state ->
|
viewModel.addressSubmissionState.observe(this) { state ->
|
||||||
|
Log.d(TAG, "Received addressSubmissionState update: $state")
|
||||||
handleAddressSubmissionState(state)
|
handleAddressSubmissionState(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleProvinceState(state: ViewState<List<ProvincesItem>>) {
|
private fun handleProvinceState(state: ViewState<List<ProvincesItem>>) {
|
||||||
when (state) {
|
when (state) {
|
||||||
is ViewState.Loading -> null //showProvinceLoading(true)
|
is ViewState.Loading -> {
|
||||||
|
Log.d("AddAddressActivity", "Loading provinces...")
|
||||||
|
// Show loading indicator
|
||||||
|
}
|
||||||
is ViewState.Success -> {
|
is ViewState.Success -> {
|
||||||
|
Log.d("AddAddressActivity", "Provinces loaded: ${state.data.size}")
|
||||||
|
// Hide loading indicator
|
||||||
|
if (state.data.isNotEmpty()) {
|
||||||
provinceAdapter.updateData(state.data)
|
provinceAdapter.updateData(state.data)
|
||||||
|
} else {
|
||||||
|
showError("No provinces available")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is ViewState.Error -> {
|
is ViewState.Error -> {
|
||||||
showError(state.message)
|
// Hide loading indicator
|
||||||
|
showError("Failed to load provinces: ${state.message}")
|
||||||
|
Log.e("AddAddressActivity", "Province error: ${state.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleCityState(state: ViewState<List<CitiesItem>>) {
|
private fun handleCityState(state: ViewState<List<CitiesItem>>) {
|
||||||
when (state) {
|
when (state) {
|
||||||
is ViewState.Loading -> null //showCityLoading(true)
|
is ViewState.Loading -> {
|
||||||
|
Log.d("AddAddressActivity", "Loading cities...")
|
||||||
|
binding.cityProgressBar.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
is ViewState.Success -> {
|
is ViewState.Success -> {
|
||||||
// showCityLoading(false)
|
Log.d("AddAddressActivity", "Cities loaded: ${state.data.size}")
|
||||||
|
binding.cityProgressBar.visibility = View.GONE
|
||||||
cityAdapter.updateData(state.data)
|
cityAdapter.updateData(state.data)
|
||||||
}
|
}
|
||||||
is ViewState.Error -> {
|
is ViewState.Error -> {
|
||||||
// showCityLoading(false)
|
binding.cityProgressBar.visibility = View.GONE
|
||||||
showError(state.message)
|
showError("Failed to load cities: ${state.message}")
|
||||||
|
Log.e("AddAddressActivity", "City error: ${state.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAddressSubmissionState(state: ViewState<String>) {
|
private fun handleAddressSubmissionState(state: ViewState<String>) {
|
||||||
when (state) {
|
when (state) {
|
||||||
is ViewState.Loading -> showSubmitLoading(true)
|
is ViewState.Loading -> {
|
||||||
|
Log.d(TAG, "Address submission: Loading")
|
||||||
|
showSubmitLoading(true)
|
||||||
|
}
|
||||||
is ViewState.Success -> {
|
is ViewState.Success -> {
|
||||||
|
Log.d(TAG, "Address submission: Success - ${state.data}")
|
||||||
showSubmitLoading(false)
|
showSubmitLoading(false)
|
||||||
showSuccessAndFinish(state.data)
|
showSuccessAndFinish(state.data)
|
||||||
}
|
}
|
||||||
is ViewState.Error -> {
|
is ViewState.Error -> {
|
||||||
|
Log.e(TAG, "Address submission: Error - ${state.message}")
|
||||||
showSubmitLoading(false)
|
showSubmitLoading(false)
|
||||||
showError(state.message)
|
showError(state.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showSubmitLoading(isLoading: Boolean) {
|
private fun showSubmitLoading(isLoading: Boolean) {
|
||||||
binding.buttonSimpan.isEnabled = !isLoading
|
binding.buttonSimpan.isEnabled = !isLoading
|
||||||
binding.buttonSimpan.text = if (isLoading) "Menyimpan..." else "Simpan"
|
binding.buttonSimpan.text = if (isLoading) "Menyimpan..." else "Simpan"
|
||||||
// You might want to show a progress bar as well
|
// binding.submitProgressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showError(message: String) {
|
private fun showError(message: String) {
|
||||||
@ -177,47 +238,83 @@ private fun setupToolbar() {
|
|||||||
|
|
||||||
private fun showSuccessAndFinish(message: String) {
|
private fun showSuccessAndFinish(message: String) {
|
||||||
Toast.makeText(this, "Sukses: $message", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Sukses: $message", Toast.LENGTH_SHORT).show()
|
||||||
onBackPressed()
|
setResult(RESULT_OK)
|
||||||
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateAndSubmitForm() {
|
private fun validateAndSubmitForm() {
|
||||||
val lat = latitude
|
Log.d(TAG, "Validating form...")
|
||||||
val long = longitude
|
Log.d(TAG, "Current location: lat=$latitude, long=$longitude")
|
||||||
|
|
||||||
if (lat == null || long == null) {
|
// Check if we have location - always use default if not available
|
||||||
showError("Lokasi belum terdeteksi")
|
if (latitude == null || longitude == null) {
|
||||||
return
|
Log.w(TAG, "No location detected, using default location")
|
||||||
|
// Default location for Jakarta
|
||||||
|
latitude = -6.200000
|
||||||
|
longitude = 106.816666
|
||||||
|
binding.tvLocationStatus.text = "Menggunakan lokasi default: Jakarta"
|
||||||
}
|
}
|
||||||
|
|
||||||
val street = binding.etDetailAlamat.text.toString()
|
val street = binding.etDetailAlamat.text.toString().trim()
|
||||||
val subDistrict = binding.etKecamatan.text.toString()
|
val subDistrict = binding.etKecamatan.text.toString().trim()
|
||||||
val postalCode = binding.etKodePos.text.toString()
|
val postalCode = binding.etKodePos.text.toString().trim()
|
||||||
val recipient = binding.etNamaPenerima.text.toString()
|
val recipient = binding.etNamaPenerima.text.toString().trim()
|
||||||
val phone = binding.etNomorHp.text.toString()
|
val phone = binding.etNomorHp.text.toString().trim()
|
||||||
val userId = profileUser.userId
|
val userId = try {
|
||||||
|
profileUser
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Error getting userId, using default", e)
|
||||||
|
1 // Default userId for testing
|
||||||
|
}
|
||||||
val isStoreLocation = false
|
val isStoreLocation = false
|
||||||
|
|
||||||
val provinceId = viewModel.selectedProvinceId
|
val provinceId = viewModel.selectedProvinceId
|
||||||
val cityId = viewModel.selectedCityId
|
val cityId = viewModel.selectedCityId
|
||||||
|
|
||||||
if (street.isBlank() || recipient.isBlank() || phone.isBlank()) {
|
Log.d(TAG, "Form data: street=$street, subDistrict=$subDistrict, postalCode=$postalCode, " +
|
||||||
showError("Lengkapi semua field wajib")
|
"recipient=$recipient, phone=$phone, userId=$userId, provinceId=$provinceId, cityId=$cityId, " +
|
||||||
|
"lat=$latitude, long=$longitude")
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (street.isBlank()) {
|
||||||
|
Log.w(TAG, "Validation failed: street is blank")
|
||||||
|
binding.etDetailAlamat.error = "Alamat tidak boleh kosong"
|
||||||
|
binding.etDetailAlamat.requestFocus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipient.isBlank()) {
|
||||||
|
Log.w(TAG, "Validation failed: recipient is blank")
|
||||||
|
binding.etNamaPenerima.error = "Nama penerima tidak boleh kosong"
|
||||||
|
binding.etNamaPenerima.requestFocus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phone.isBlank()) {
|
||||||
|
Log.w(TAG, "Validation failed: phone is blank")
|
||||||
|
binding.etNomorHp.error = "Nomor HP tidak boleh kosong"
|
||||||
|
binding.etNomorHp.requestFocus()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provinceId == null) {
|
if (provinceId == null) {
|
||||||
|
Log.w(TAG, "Validation failed: provinceId is null")
|
||||||
showError("Pilih provinsi terlebih dahulu")
|
showError("Pilih provinsi terlebih dahulu")
|
||||||
|
binding.autoCompleteProvinsi.requestFocus()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cityId == null) {
|
if (cityId == null) {
|
||||||
|
Log.w(TAG, "Validation failed: cityId is null")
|
||||||
showError("Pilih kota/kabupaten terlebih dahulu")
|
showError("Pilih kota/kabupaten terlebih dahulu")
|
||||||
|
binding.autoCompleteKabupaten.requestFocus()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create request with all fields
|
||||||
val request = CreateAddressRequest(
|
val request = CreateAddressRequest(
|
||||||
lat = lat,
|
lat = latitude!!, // Safe to use !! as we've checked above
|
||||||
long = long,
|
long = longitude!!,
|
||||||
street = street,
|
street = street,
|
||||||
subDistrict = subDistrict,
|
subDistrict = subDistrict,
|
||||||
cityId = cityId,
|
cityId = cityId,
|
||||||
@ -230,12 +327,17 @@ private fun setupToolbar() {
|
|||||||
isStoreLocation = isStoreLocation
|
isStoreLocation = isStoreLocation
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Log.d(TAG, "Form validation successful, submitting address: $request")
|
||||||
viewModel.addAddress(request)
|
viewModel.addAddress(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val locationPermissionLauncher =
|
private val locationPermissionLauncher =
|
||||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
||||||
if (granted) requestLocation() else Toast.makeText(this, "Izin lokasi ditolak",Toast.LENGTH_SHORT).show()
|
if (granted) {
|
||||||
|
requestLocation()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "Izin lokasi ditolak", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requestLocationPermission() {
|
private fun requestLocationPermission() {
|
||||||
@ -244,36 +346,164 @@ private fun setupToolbar() {
|
|||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
private fun requestLocation() {
|
private fun requestLocation() {
|
||||||
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
Log.d(TAG, "Requesting device location")
|
||||||
val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
|
||||||
|
|
||||||
if (!isGpsEnabled && !isNetworkEnabled) {
|
// Check if we're already requesting location to avoid multiple requests
|
||||||
Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show()
|
if (isRequestingLocation) {
|
||||||
|
Log.w(TAG, "Location request already in progress")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val provider = if (isGpsEnabled) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER
|
isRequestingLocation = true
|
||||||
|
binding.locationProgressBar.visibility = View.VISIBLE
|
||||||
|
binding.tvLocationStatus.text = "Mencari lokasi..."
|
||||||
|
|
||||||
locationManager.requestSingleUpdate(provider, object : LocationListener {
|
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
||||||
|
val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||||
|
|
||||||
|
Log.d(TAG, "Location providers: GPS=$isGpsEnabled, Network=$isNetworkEnabled")
|
||||||
|
|
||||||
|
if (!isGpsEnabled && !isNetworkEnabled) {
|
||||||
|
Log.w(TAG, "No location providers enabled")
|
||||||
|
binding.locationProgressBar.visibility = View.GONE
|
||||||
|
binding.tvLocationStatus.text = "Provider lokasi tidak tersedia"
|
||||||
|
isRequestingLocation = false
|
||||||
|
Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show()
|
||||||
|
showEnableLocationDialog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create location criteria
|
||||||
|
val criteria = Criteria()
|
||||||
|
criteria.accuracy = Criteria.ACCURACY_FINE
|
||||||
|
criteria.isBearingRequired = false
|
||||||
|
criteria.isAltitudeRequired = false
|
||||||
|
criteria.isSpeedRequired = false
|
||||||
|
criteria.powerRequirement = Criteria.POWER_LOW
|
||||||
|
|
||||||
|
// Get the best provider based on criteria
|
||||||
|
val provider = locationManager.getBestProvider(criteria, true) ?:
|
||||||
|
if (isGpsEnabled) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER
|
||||||
|
|
||||||
|
Log.d(TAG, "Using location provider: $provider")
|
||||||
|
|
||||||
|
// Set timeout for location
|
||||||
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
if (isRequestingLocation) {
|
||||||
|
Log.w(TAG, "Location timeout, using default")
|
||||||
|
binding.locationProgressBar.visibility = View.GONE
|
||||||
|
binding.tvLocationStatus.text = "Lokasi default: Jakarta"
|
||||||
|
latitude = -6.200000
|
||||||
|
longitude = 106.816666
|
||||||
|
isRequestingLocation = false
|
||||||
|
Toast.makeText(this, "Timeout lokasi, menggunakan lokasi default", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}, 15000) // 15 seconds timeout
|
||||||
|
|
||||||
|
// Try getting last known location first
|
||||||
|
try {
|
||||||
|
val lastLocation = locationManager.getLastKnownLocation(provider)
|
||||||
|
if (lastLocation != null) {
|
||||||
|
Log.d(TAG, "Using last known location")
|
||||||
|
latitude = lastLocation.latitude
|
||||||
|
longitude = lastLocation.longitude
|
||||||
|
binding.locationProgressBar.visibility = View.GONE
|
||||||
|
binding.tvLocationStatus.text = "Lokasi terdeteksi: ${lastLocation.latitude}, ${lastLocation.longitude}"
|
||||||
|
isRequestingLocation = false
|
||||||
|
Toast.makeText(this, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "No last known location, requesting updates")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error getting last known location", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a location listener
|
||||||
|
val locationListener = object : LocationListener {
|
||||||
override fun onLocationChanged(location: Location) {
|
override fun onLocationChanged(location: Location) {
|
||||||
|
Log.d(TAG, "onLocationChanged called: lat=${location.latitude}, long=${location.longitude}")
|
||||||
latitude = location.latitude
|
latitude = location.latitude
|
||||||
longitude = location.longitude
|
longitude = location.longitude
|
||||||
|
binding.locationProgressBar.visibility = View.GONE
|
||||||
|
binding.tvLocationStatus.text = "Lokasi terdeteksi: ${location.latitude}, ${location.longitude}"
|
||||||
|
isRequestingLocation = false
|
||||||
|
Toast.makeText(this@AddAddressActivity, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
// Remove location updates after receiving a location
|
||||||
|
try {
|
||||||
|
locationManager.removeUpdates(this)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error removing location updates", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
|
||||||
|
Log.d(TAG, "Location provider status changed: provider=$provider, status=$status")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProviderEnabled(provider: String) {
|
||||||
|
Log.d(TAG, "Location provider enabled: $provider")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
|
|
||||||
override fun onProviderEnabled(provider: String) {}
|
|
||||||
override fun onProviderDisabled(provider: String) {
|
override fun onProviderDisabled(provider: String) {
|
||||||
Toast.makeText(this@AddAddressActivity, "Provider dimatikan", Toast.LENGTH_SHORT).show()
|
Log.w(TAG, "Location provider disabled: $provider")
|
||||||
|
binding.locationProgressBar.visibility = View.GONE
|
||||||
|
binding.tvLocationStatus.text = "Provider lokasi dimatikan"
|
||||||
|
isRequestingLocation = false
|
||||||
|
Toast.makeText(this@AddAddressActivity, "Provider $provider dimatikan", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
try {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
// Request location updates
|
||||||
if (requestCode == 100 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
Log.d(TAG, "Requesting location updates from $provider")
|
||||||
|
locationManager.requestLocationUpdates(
|
||||||
|
provider,
|
||||||
|
0, // minimum time interval between updates (in milliseconds)
|
||||||
|
0f, // minimum distance between updates (in meters)
|
||||||
|
locationListener,
|
||||||
|
Looper.getMainLooper()
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Exception requesting location update", e)
|
||||||
|
binding.locationProgressBar.visibility = View.GONE
|
||||||
|
binding.tvLocationStatus.text = "Error: ${e.message}"
|
||||||
|
isRequestingLocation = false
|
||||||
|
Toast.makeText(this, "Error mendapatkan lokasi: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
// Set default location
|
||||||
|
latitude = -6.200000
|
||||||
|
longitude = 106.816666
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showEnableLocationDialog() {
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle("Aktifkan Lokasi")
|
||||||
|
.setMessage("Aplikasi memerlukan akses lokasi. Silakan aktifkan lokasi di pengaturan.")
|
||||||
|
.setPositiveButton("Pengaturan") { _, _ ->
|
||||||
|
startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
|
||||||
|
}
|
||||||
|
.setNegativeButton("Batal") { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupReloadButtons() {
|
||||||
|
// Add button to reload provinces (add this button to your layout)
|
||||||
|
|
||||||
|
// Add button to reload location (add this button to your layout)
|
||||||
|
binding.btnReloadLocation.setOnClickListener {
|
||||||
|
Log.d(TAG, "Reload location button clicked")
|
||||||
|
Toast.makeText(this, "Memuat ulang lokasi...", Toast.LENGTH_SHORT).show()
|
||||||
requestLocation()
|
requestLocation()
|
||||||
} else {
|
|
||||||
Toast.makeText(this, "Location permission denied", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "AddAddressViewModel"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,35 @@
|
|||||||
package com.alya.ecommerce_serang.ui.order.address
|
package com.alya.ecommerce_serang.ui.order.address
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
|
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem
|
import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem
|
||||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
import com.alya.ecommerce_serang.data.repository.Result
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class AddAddressViewModel(private val repository: OrderRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
|
class AddAddressViewModel(private val repository: OrderRepository, private val userRepo: UserRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
|
||||||
// Flow states for data
|
private val _addressSubmissionState = MutableLiveData<ViewState<String>>()
|
||||||
private val _addressSubmissionState = MutableStateFlow<ViewState<String>>(ViewState.Loading)
|
val addressSubmissionState: LiveData<ViewState<String>> = _addressSubmissionState
|
||||||
val addressSubmissionState = _addressSubmissionState.asStateFlow()
|
|
||||||
|
|
||||||
private val _provincesState = MutableStateFlow<ViewState<List<ProvincesItem>>>(ViewState.Loading)
|
private val _userProfile = MutableLiveData<UserProfile?>()
|
||||||
val provincesState = _provincesState.asStateFlow()
|
val userProfile: LiveData<UserProfile?> = _userProfile
|
||||||
|
|
||||||
private val _citiesState = MutableStateFlow<ViewState<List<CitiesItem>>>(ViewState.Loading)
|
private val _errorMessageUser = MutableLiveData<String>()
|
||||||
val citiesState = _citiesState.asStateFlow()
|
val errorMessageUser : LiveData<String> = _errorMessageUser
|
||||||
|
|
||||||
|
private val _provincesState = MutableLiveData<ViewState<List<ProvincesItem>>>()
|
||||||
|
val provincesState: LiveData<ViewState<List<ProvincesItem>>> = _provincesState
|
||||||
|
|
||||||
|
private val _citiesState = MutableLiveData<ViewState<List<CitiesItem>>>()
|
||||||
|
val citiesState: LiveData<ViewState<List<CitiesItem>>> = _citiesState
|
||||||
|
|
||||||
// Stored in SavedStateHandle for configuration changes
|
// Stored in SavedStateHandle for configuration changes
|
||||||
var selectedProvinceId: Int?
|
var selectedProvinceId: Int?
|
||||||
@ -39,46 +46,81 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addAddress(request: CreateAddressRequest) {
|
fun addAddress(request: CreateAddressRequest) {
|
||||||
|
Log.d(TAG, "Starting address submission process")
|
||||||
|
_addressSubmissionState.value = ViewState.Loading
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
when (val result = repository.addAddress(request)) {
|
try {
|
||||||
|
Log.d(TAG, "Calling repository.addAddress with request: $request")
|
||||||
|
val result = repository.addAddress(request)
|
||||||
|
|
||||||
|
when (result) {
|
||||||
is Result.Success -> {
|
is Result.Success -> {
|
||||||
val message = result.data.message // Ambil `message` dari CreateAddressResponse
|
val message = result.data.message
|
||||||
_addressSubmissionState.value = ViewState.Success(message)
|
Log.d(TAG, "Address added successfully: $message")
|
||||||
|
_addressSubmissionState.postValue(ViewState.Success(message))
|
||||||
}
|
}
|
||||||
is Result.Error -> {
|
is Result.Error -> {
|
||||||
_addressSubmissionState.value =
|
val errorMsg = result.exception.message ?: "Unknown error"
|
||||||
ViewState.Error(result.exception.message ?: "Unknown error")
|
Log.e(TAG, "Error from repository: $errorMsg", result.exception)
|
||||||
|
_addressSubmissionState.postValue(ViewState.Error(errorMsg))
|
||||||
}
|
}
|
||||||
is Result.Loading -> {
|
is Result.Loading -> {
|
||||||
// Optional, karena sudah set Loading di awal
|
Log.d(TAG, "Repository returned Loading state")
|
||||||
|
// We already set Loading at the beginning
|
||||||
}
|
}
|
||||||
|
else -> {
|
||||||
|
Log.e(TAG, "Repository returned unexpected result type: $result")
|
||||||
|
_addressSubmissionState.postValue(ViewState.Error("Unexpected error occurred"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Exception occurred during address submission", e)
|
||||||
|
val errorMessage = e.message ?: "Unknown error occurred"
|
||||||
|
Log.e(TAG, "Error message: $errorMessage")
|
||||||
|
|
||||||
|
// Log the exception stack trace
|
||||||
|
e.printStackTrace()
|
||||||
|
|
||||||
|
_addressSubmissionState.postValue(ViewState.Error(errorMessage))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getProvinces() {
|
fun getProvinces() {
|
||||||
|
_provincesState.value = ViewState.Loading
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val result = repository.getListProvinces()
|
val result = repository.getListProvinces()
|
||||||
result?.let {
|
if (result?.provinces != null) {
|
||||||
_provincesState.value = ViewState.Success(it.provinces)
|
_provincesState.postValue(ViewState.Success(result.provinces))
|
||||||
|
Log.d(TAG, "Provinces loaded: ${result.provinces.size}")
|
||||||
|
} else {
|
||||||
|
_provincesState.postValue(ViewState.Error("Failed to load provinces"))
|
||||||
|
Log.e(TAG, "Province result was null or empty")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("AddAddressViewModel", "Error fetching provinces: ${e.message}")
|
_provincesState.postValue(ViewState.Error(e.message ?: "Error loading provinces"))
|
||||||
|
Log.e(TAG, "Error fetching provinces", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCities(provinceId: Int){
|
fun getCities(provinceId: Int){
|
||||||
|
_citiesState.value = ViewState.Loading
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
selectedProvinceId = provinceId
|
selectedProvinceId = provinceId
|
||||||
val result = repository.getListCities(provinceId)
|
val result = repository.getListCities(provinceId)
|
||||||
result?.let {
|
result?.let {
|
||||||
_citiesState.value = ViewState.Success(it.cities)
|
_citiesState.postValue(ViewState.Success(it.cities))
|
||||||
|
Log.d(TAG, "Cities loaded for province $provinceId: ${it.cities.size}")
|
||||||
|
} ?: run {
|
||||||
|
_citiesState.postValue(ViewState.Error("Failed to load cities"))
|
||||||
|
Log.e(TAG, "City result was null for province $provinceId")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("AddAddressViewModel", "Error fetching cities: ${e.message}")
|
_citiesState.postValue(ViewState.Error(e.message ?: "Error loading cities"))
|
||||||
|
Log.e(TAG, "Error fetching cities for province $provinceId", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,6 +133,16 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s
|
|||||||
selectedCityId = id
|
selectedCityId = id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadUserProfile(){
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (val result = userRepo.fetchUserProfile()){
|
||||||
|
is Result.Success -> _userProfile.postValue(result.data)
|
||||||
|
is Result.Error -> _errorMessageUser.postValue(result.exception.message ?: "Unknown Error")
|
||||||
|
is Result.Loading -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "AddAddressViewModel"
|
private const val TAG = "AddAddressViewModel"
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
|
||||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
import com.alya.ecommerce_serang.databinding.ActivityAddressBinding
|
import com.alya.ecommerce_serang.databinding.ActivityAddressBinding
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
@ -14,7 +14,6 @@ import com.alya.ecommerce_serang.utils.SessionManager
|
|||||||
|
|
||||||
class AddressActivity : AppCompatActivity() {
|
class AddressActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityAddressBinding
|
private lateinit var binding: ActivityAddressBinding
|
||||||
private lateinit var apiService: ApiService
|
|
||||||
private lateinit var sessionManager: SessionManager
|
private lateinit var sessionManager: SessionManager
|
||||||
private lateinit var adapter: AddressAdapter
|
private lateinit var adapter: AddressAdapter
|
||||||
|
|
||||||
@ -26,55 +25,82 @@ class AddressActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityAddressBinding.inflate(layoutInflater)
|
binding = ActivityAddressBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
sessionManager = SessionManager(this)
|
sessionManager = SessionManager(this)
|
||||||
apiService = ApiConfig.getApiService(sessionManager)
|
|
||||||
|
|
||||||
setupToolbar()
|
setupToolbar()
|
||||||
|
setupRecyclerView()
|
||||||
|
setupObservers()
|
||||||
|
|
||||||
adapter = AddressAdapter { selectedId ->
|
viewModel.fetchAddresses()
|
||||||
viewModel.selectAddress(selectedId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addAddressClicked(){
|
||||||
|
binding.addAddressClick.setOnClickListener{
|
||||||
|
val intent = Intent(this, AddAddressActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupToolbar() {
|
||||||
|
// Remove duplicate toolbar setup
|
||||||
|
addAddressClicked()
|
||||||
binding.toolbar.setNavigationOnClickListener {
|
binding.toolbar.setNavigationOnClickListener {
|
||||||
onBackPressedWithResult()
|
onBackPressedWithResult()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding.rvSellerOrder.layoutManager = LinearLayoutManager(this)
|
private fun setupRecyclerView() {
|
||||||
binding.rvSellerOrder.adapter = adapter
|
adapter = AddressAdapter { address ->
|
||||||
|
// Select the address in the ViewModel
|
||||||
|
viewModel.selectAddress(address.id)
|
||||||
|
|
||||||
viewModel.fetchAddresses()
|
// Return immediately with the selected address
|
||||||
|
returnResultAndFinish(address.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.rvSellerOrder.apply {
|
||||||
|
layoutManager = LinearLayoutManager(this@AddressActivity)
|
||||||
|
adapter = this@AddressActivity.adapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupObservers() {
|
||||||
viewModel.addresses.observe(this) { addressList ->
|
viewModel.addresses.observe(this) { addressList ->
|
||||||
adapter.submitList(addressList)
|
adapter.submitList(addressList)
|
||||||
|
|
||||||
|
// Show empty state if needed
|
||||||
|
// binding.emptyView?.isVisible = addressList.isEmpty()
|
||||||
|
binding.rvSellerOrder.isVisible = addressList.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.selectedAddressId.observe(this) { selectedId ->
|
viewModel.selectedAddressId.observe(this) { selectedId ->
|
||||||
adapter.setSelectedAddressId(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() {
|
private fun onBackPressedWithResult() {
|
||||||
viewModel.selectedAddressId.value?.let {
|
// If an address is selected, return it as result
|
||||||
val intent = Intent()
|
val selectedId = viewModel.selectedAddressId.value
|
||||||
intent.putExtra(EXTRA_ADDRESS_ID, it)
|
if (selectedId != null) {
|
||||||
setResult(RESULT_OK, intent)
|
returnResultAndFinish(selectedId)
|
||||||
}
|
|
||||||
finish()
|
finish()
|
||||||
|
} else {
|
||||||
|
// No selection, just finish
|
||||||
|
setResult(RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun returnResultAndFinish(addressId: Int) {
|
||||||
|
val intent = Intent()
|
||||||
|
intent.putExtra(EXTRA_ADDRESS_ID, addressId)
|
||||||
|
setResult(RESULT_OK, intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -13,14 +13,25 @@ import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressesIte
|
|||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
|
||||||
class AddressAdapter(
|
class AddressAdapter(
|
||||||
private val onAddressClick: (Int) -> Unit
|
private val onAddressClick: (AddressesItem) -> Unit
|
||||||
) : ListAdapter<AddressesItem, AddressAdapter.AddressViewHolder>(DIFF_CALLBACK) {
|
) : ListAdapter<AddressesItem, AddressAdapter.AddressViewHolder>(DIFF_CALLBACK) {
|
||||||
|
|
||||||
private var selectedAddressId: Int? = null
|
private var selectedAddressId: Int? = null
|
||||||
|
|
||||||
fun setSelectedAddressId(id: Int?) {
|
fun setSelectedAddressId(id: Int?) {
|
||||||
|
val oldSelectedId = selectedAddressId
|
||||||
selectedAddressId = id
|
selectedAddressId = id
|
||||||
notifyDataSetChanged()
|
|
||||||
|
// Only refresh the changed items
|
||||||
|
if (oldSelectedId != null) {
|
||||||
|
val oldPosition = currentList.indexOfFirst { it.id == oldSelectedId }
|
||||||
|
if (oldPosition >= 0) notifyItemChanged(oldPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id != null) {
|
||||||
|
val newPosition = currentList.indexOfFirst { it.id == id }
|
||||||
|
if (newPosition >= 0) notifyItemChanged(newPosition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddressViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddressViewHolder {
|
||||||
@ -33,7 +44,8 @@ class AddressAdapter(
|
|||||||
val address = getItem(position)
|
val address = getItem(position)
|
||||||
holder.bind(address, selectedAddressId == address.id)
|
holder.bind(address, selectedAddressId == address.id)
|
||||||
holder.itemView.setOnClickListener {
|
holder.itemView.setOnClickListener {
|
||||||
onAddressClick(address.id)
|
// Pass the whole address object to provide more context
|
||||||
|
onAddressClick(address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +58,12 @@ class AddressAdapter(
|
|||||||
tvName.text = address.recipient
|
tvName.text = address.recipient
|
||||||
tvDetail.text = "${address.street}, ${address.subdistrict}, ${address.phone}"
|
tvDetail.text = "${address.street}, ${address.subdistrict}, ${address.phone}"
|
||||||
|
|
||||||
|
// Make selection more visible
|
||||||
|
card.strokeWidth = if (isSelected) 3 else 0
|
||||||
|
card.strokeColor = if (isSelected)
|
||||||
|
ContextCompat.getColor(itemView.context, R.color.blue_400)
|
||||||
|
else 0
|
||||||
|
|
||||||
card.setCardBackgroundColor(
|
card.setCardBackgroundColor(
|
||||||
ContextCompat.getColor(
|
ContextCompat.getColor(
|
||||||
itemView.context,
|
itemView.context,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.alya.ecommerce_serang.ui.order.address
|
package com.alya.ecommerce_serang.ui.order.address
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
|
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem
|
import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem
|
||||||
@ -20,6 +21,8 @@ class ProvinceAdapter(
|
|||||||
clear()
|
clear()
|
||||||
addAll(provinces.map { it.province })
|
addAll(provinces.map { it.province })
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
|
|
||||||
|
Log.d("ProvinceAdapter", "Updated with ${provinces.size} provinces")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getProvinceId(position: Int): Int? {
|
fun getProvinceId(position: Int): Int? {
|
||||||
|
@ -0,0 +1,340 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order.detail
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.R
|
||||||
|
import android.app.DatePickerDialog
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceMultipartRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
|
import com.alya.ecommerce_serang.databinding.ActivityAddEvidencePaymentBinding
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class AddEvidencePaymentActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityAddEvidencePaymentBinding
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
private var orderId: Int = 0
|
||||||
|
private var paymentInfoId: Int = 0
|
||||||
|
private lateinit var productPrice: String
|
||||||
|
private var selectedImageUri: Uri? = null
|
||||||
|
|
||||||
|
private val viewModel: PaymentViewModel by viewModels {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
val orderRepository = OrderRepository(apiService)
|
||||||
|
PaymentViewModel(orderRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val paymentMethods = arrayOf(
|
||||||
|
"Pilih metode pembayaran",
|
||||||
|
"Transfer Bank",
|
||||||
|
"E-Wallet",
|
||||||
|
"Virtual Account",
|
||||||
|
"Cash on Delivery"
|
||||||
|
)
|
||||||
|
|
||||||
|
private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
|
||||||
|
uri?.let {
|
||||||
|
selectedImageUri = it
|
||||||
|
binding.ivUploadedImage.setImageURI(selectedImageUri)
|
||||||
|
binding.ivUploadedImage.visibility = View.VISIBLE
|
||||||
|
binding.layoutUploadPlaceholder.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityAddEvidencePaymentBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
sessionManager = SessionManager(this)
|
||||||
|
|
||||||
|
intent.extras?.let { bundle ->
|
||||||
|
orderId = bundle.getInt("ORDER_ID", 0)
|
||||||
|
paymentInfoId = bundle.getInt("PAYMENT_INFO_ID", 0)
|
||||||
|
productPrice = intent.getStringExtra("TOTAL_AMOUNT") ?: "Rp0"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setupUI()
|
||||||
|
viewModel.getOrderDetails(orderId)
|
||||||
|
|
||||||
|
|
||||||
|
setupListeners()
|
||||||
|
setupObservers()
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupUI() {
|
||||||
|
// Set product details\
|
||||||
|
|
||||||
|
// Setup payment methods spinner
|
||||||
|
val adapter = ArrayAdapter(this, R.layout.simple_spinner_item, paymentMethods)
|
||||||
|
adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item)
|
||||||
|
binding.spinnerPaymentMethod.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupListeners() {
|
||||||
|
|
||||||
|
// Upload image button
|
||||||
|
binding.tvAddPhoto.setOnClickListener {
|
||||||
|
checkPermissionAndPickImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.frameUploadImage.setOnClickListener {
|
||||||
|
checkPermissionAndPickImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date picker
|
||||||
|
binding.tvPaymentDate.setOnClickListener {
|
||||||
|
showDatePicker()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payment method spinner
|
||||||
|
binding.spinnerPaymentMethod.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
// Skip the hint (first item)
|
||||||
|
if (position > 0) {
|
||||||
|
val selectedMethod = paymentMethods[position]
|
||||||
|
|
||||||
|
// You can also use it for further processing
|
||||||
|
Log.d(TAG, "Selected payment method: $selectedMethod")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit button
|
||||||
|
binding.btnSubmit.setOnClickListener {
|
||||||
|
validateAndUpload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupObservers() {
|
||||||
|
viewModel.uploadResult.observe(this) { result ->
|
||||||
|
when (result) {
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
|
||||||
|
Toast.makeText(this, "Bukti pembayaran berhasil dikirim", Toast.LENGTH_SHORT).show()
|
||||||
|
Log.d(TAG, "Upload successful: ${result.data}")
|
||||||
|
// Navigate back or to confirmation screen
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
|
||||||
|
Log.e(TAG, "Upload failed: ${result.exception.message}")
|
||||||
|
Toast.makeText(this, "Gagal mengirim bukti pembayaran: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
is Result.Loading -> {
|
||||||
|
// Show loading indicator if needed
|
||||||
|
Log.d(TAG, "Uploading payment proof...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateAndUpload() {
|
||||||
|
// Validate all fields
|
||||||
|
if (selectedImageUri == null) {
|
||||||
|
Toast.makeText(this, "Silahkan pilih bukti pembayaran", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding.spinnerPaymentMethod.selectedItemPosition == 0) {
|
||||||
|
Toast.makeText(this, "Silahkan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding.etAccountNumber.text.toString().trim().isEmpty()) {
|
||||||
|
Toast.makeText(this, "Silahkan isi nomor rekening/HP", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding.tvPaymentDate.text.toString() == "Pilih tanggal") {
|
||||||
|
Toast.makeText(this, "Silahkan pilih tanggal pembayaran", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// All validations passed, proceed with upload
|
||||||
|
uploadPaymentProof()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun uploadPaymentProof() {
|
||||||
|
selectedImageUri?.let { uri ->
|
||||||
|
// Convert URI to File
|
||||||
|
val file = getFileFromUri(uri)
|
||||||
|
file?.let {
|
||||||
|
try {
|
||||||
|
// Create MultipartBody.Part from File
|
||||||
|
val requestFile = file.asRequestBody("image/jpeg".toMediaTypeOrNull())
|
||||||
|
val evidencePart = MultipartBody.Part.createFormData("evidence", file.name, requestFile)
|
||||||
|
|
||||||
|
// Create RequestBody for order ID and amount
|
||||||
|
val orderIdPart = orderId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
|
||||||
|
// Clean up the price string to get only the numeric value
|
||||||
|
val amountPart = productPrice.replace("Rp", "").replace(".", "").trim()
|
||||||
|
.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
|
||||||
|
// Create the request object with the parts we need
|
||||||
|
val request = AddEvidenceMultipartRequest(
|
||||||
|
orderId = orderIdPart,
|
||||||
|
amount = amountPart,
|
||||||
|
evidence = evidencePart
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log request details for debugging
|
||||||
|
Log.d(TAG, "Uploading payment proof - OrderID: $orderId, Amount: ${productPrice.replace("Rp", "").replace(".", "").trim()}")
|
||||||
|
Log.d(TAG, "File details - Name: ${file.name}, Size: ${file.length()} bytes, MIME: image/jpeg")
|
||||||
|
|
||||||
|
// Call the viewModel method
|
||||||
|
viewModel.uploadPaymentProof(request)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error creating upload request: ${e.message}", e)
|
||||||
|
Toast.makeText(this, "Error preparing upload: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFileFromUri(uri: Uri): File? {
|
||||||
|
val contentResolver = applicationContext.contentResolver
|
||||||
|
val mimeType = contentResolver.getType(uri) ?: "image/jpeg"
|
||||||
|
val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: "jpg"
|
||||||
|
|
||||||
|
Log.d("UploadEvidence", "URI: $uri")
|
||||||
|
Log.d("UploadEvidence", "Detected MIME type: $mimeType, extension: $extension")
|
||||||
|
|
||||||
|
// Ensure it's an image (either PNG, JPG, or JPEG)
|
||||||
|
if (mimeType != "image/png" && mimeType != "image/jpeg" && mimeType != "image/jpg") {
|
||||||
|
Log.e("UploadEvidence", "Invalid image MIME type: $mimeType. Only images are allowed.")
|
||||||
|
Toast.makeText(applicationContext, "Only image files are allowed", Toast.LENGTH_SHORT).show()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val inputStream = contentResolver.openInputStream(uri)
|
||||||
|
|
||||||
|
if (inputStream == null) {
|
||||||
|
Log.e("UploadEvidence", "Failed to open input stream from URI: $uri")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a temporary file with the correct extension
|
||||||
|
val tempFile = File.createTempFile("evidence_", ".$extension", cacheDir)
|
||||||
|
Log.d("UploadEvidence", "Temp file created at: ${tempFile.absolutePath}")
|
||||||
|
|
||||||
|
// Copy the content from inputStream to the temporary file
|
||||||
|
tempFile.outputStream().use { outputStream ->
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
inputStream.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify if the file is a valid image
|
||||||
|
val bitmap = BitmapFactory.decodeFile(tempFile.absolutePath)
|
||||||
|
if (bitmap == null) {
|
||||||
|
Log.e("UploadEvidence", "File is not a valid image!")
|
||||||
|
tempFile.delete()
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
bitmap.recycle() // Free memory
|
||||||
|
Log.d("UploadEvidence", "Valid image detected.")
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("UploadEvidence", "File copied successfully. Size: ${tempFile.length()} bytes")
|
||||||
|
return tempFile
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("UploadEvidence", "Error processing file: ${e.message}", e)
|
||||||
|
Toast.makeText(applicationContext, "Error processing image: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private fun checkPermissionAndPickImage() {
|
||||||
|
val permission = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
Manifest.permission.READ_MEDIA_IMAGES
|
||||||
|
} else {
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
ActivityCompat.requestPermissions(this, arrayOf(permission), REQUEST_CODE_STORAGE_PERMISSION)
|
||||||
|
} else {
|
||||||
|
pickImage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<out String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
if (requestCode == REQUEST_CODE_STORAGE_PERMISSION && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
pickImage()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "Izin dibutuhkan untuk memilih gambar", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pickImage() {
|
||||||
|
getContent.launch("image/*")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun showDatePicker() {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
val year = calendar.get(Calendar.YEAR)
|
||||||
|
val month = calendar.get(Calendar.MONTH)
|
||||||
|
val day = calendar.get(Calendar.DAY_OF_MONTH)
|
||||||
|
|
||||||
|
DatePickerDialog(
|
||||||
|
this,
|
||||||
|
{ _, selectedYear, selectedMonth, selectedDay ->
|
||||||
|
calendar.set(selectedYear, selectedMonth, selectedDay)
|
||||||
|
val sdf = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault())
|
||||||
|
binding.tvPaymentDate.text = sdf.format(calendar.time)
|
||||||
|
},
|
||||||
|
year, month, day
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val PERMISSION_REQUEST_CODE = 100
|
||||||
|
private const val TAG = "AddEvidenceActivity"
|
||||||
|
private const val REQUEST_CODE_STORAGE_PERMISSION = 100
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,198 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order.detail
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.ActivityPaymentBinding
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
class PaymentActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityPaymentBinding
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "PaymentActivity"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val viewModel: PaymentViewModel by viewModels {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
val orderRepository = OrderRepository(apiService)
|
||||||
|
PaymentViewModel(orderRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityPaymentBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
sessionManager = SessionManager(this)
|
||||||
|
|
||||||
|
// Mengambil data dari intent
|
||||||
|
val orderId = intent.getIntExtra("ORDER_ID", 0)
|
||||||
|
val paymentInfoId = intent.getIntExtra("ORDER_PAYMENT_ID", 0)
|
||||||
|
|
||||||
|
if (orderId == 0) {
|
||||||
|
Toast.makeText(this, "ID pesanan tidak valid", Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup toolbar
|
||||||
|
binding.toolbar.setNavigationOnClickListener {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup petunjuk transfer
|
||||||
|
binding.layoutMBankingInstructions.setOnClickListener {
|
||||||
|
// Tampilkan instruksi mBanking
|
||||||
|
showInstructions("mBanking")
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.layoutATMInstructions.setOnClickListener {
|
||||||
|
// Tampilkan instruksi ATM
|
||||||
|
showInstructions("ATM")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup button upload bukti bayar
|
||||||
|
binding.btnUploadPaymentProof.setOnClickListener {
|
||||||
|
// Intent ke activity upload bukti bayar
|
||||||
|
val intent = Intent(this, AddEvidencePaymentActivity::class.java)
|
||||||
|
intent.putExtra("ORDER_ID", orderId)
|
||||||
|
intent.putExtra("PAYMENT_INFO_ID", paymentInfoId)
|
||||||
|
intent.putExtra("TOTAL_AMOUNT", binding.tvTotalAmount.text.toString())
|
||||||
|
Log.d(TAG, "Received Order ID: $orderId, Payment Info ID: $paymentInfoId, Total Amount: ${binding.tvTotalAmount.text}")
|
||||||
|
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup button negosiasi harga
|
||||||
|
binding.btnNegotiatePrice.setOnClickListener {
|
||||||
|
// Intent ke activity negosiasi harga
|
||||||
|
// val intent = Intent(this, NegotiatePriceActivity::class.java)
|
||||||
|
// intent.putExtra("ORDER_ID", orderId)
|
||||||
|
// startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe data
|
||||||
|
observeData()
|
||||||
|
|
||||||
|
// Load data
|
||||||
|
Log.d(TAG, "Fetching order details for Order ID: $orderId")
|
||||||
|
viewModel.getOrderDetails(orderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeData() {
|
||||||
|
// Observe Order Details
|
||||||
|
viewModel.orderDetails.observe(this) { order ->
|
||||||
|
Log.d(TAG, "Order details received: $order")
|
||||||
|
|
||||||
|
// Set total amount
|
||||||
|
binding.tvTotalAmount.text = order.totalAmount ?: "Rp0"
|
||||||
|
Log.d(TAG, "Total Amount: ${order.totalAmount}")
|
||||||
|
|
||||||
|
|
||||||
|
// Set bank information
|
||||||
|
binding.tvBankName.text = order.payInfoName ?: "Bank BCA"
|
||||||
|
binding.tvAccountNumber.text = order.payInfoNum ?: "0123456789"
|
||||||
|
Log.d(TAG, "Bank Name: ${order.payInfoName}, Account Number: ${order.payInfoNum}")
|
||||||
|
|
||||||
|
|
||||||
|
// Calculate remaining time and due date
|
||||||
|
setupPaymentDueDate(order.updatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe loading state
|
||||||
|
viewModel.isLoading.observe(this) { isLoading ->
|
||||||
|
// Show loading indicator if needed
|
||||||
|
// binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observe error
|
||||||
|
viewModel.error.observe(this) { error ->
|
||||||
|
if (error.isNotEmpty()) {
|
||||||
|
Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupPaymentDueDate(createdAt: String) {
|
||||||
|
Log.d(TAG, "Setting up payment due date from updated at: $createdAt")
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Parse the ISO 8601 date
|
||||||
|
val isoDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
|
isoDateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
val createdDate = isoDateFormat.parse(createdAt) ?: return
|
||||||
|
|
||||||
|
// Add 24 hours to get due date
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.time = createdDate
|
||||||
|
calendar.add(Calendar.HOUR, 24)
|
||||||
|
val dueDate = calendar.time
|
||||||
|
|
||||||
|
// Format due date for display
|
||||||
|
val dueDateFormat = SimpleDateFormat("dd MMM yyyy", Locale.getDefault())
|
||||||
|
binding.tvDueDate.text = "Jatuh tempo: ${dueDateFormat.format(dueDate)}"
|
||||||
|
Log.d(TAG, "Due Date: ${dueDateFormat.format(dueDate)}")
|
||||||
|
|
||||||
|
// Calculate remaining time
|
||||||
|
val now = Calendar.getInstance().time
|
||||||
|
val diff = dueDate.time - now.time
|
||||||
|
|
||||||
|
if (diff > 0) {
|
||||||
|
val hours = diff / (60 * 60 * 1000)
|
||||||
|
val minutes = (diff % (60 * 60 * 1000)) / (60 * 1000)
|
||||||
|
binding.tvRemainingTime.text = "$hours jam $minutes menit"
|
||||||
|
Log.d(TAG, "Remaining Time: $hours hours $minutes minutes")
|
||||||
|
} else {
|
||||||
|
binding.tvRemainingTime.text = "Waktu habis"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error parsing date", e)
|
||||||
|
binding.tvDueDate.text = "Jatuh tempo: -"
|
||||||
|
binding.tvRemainingTime.text = "-"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showInstructions(type: String) {
|
||||||
|
// Implementasi tampilkan instruksi
|
||||||
|
val instructions = when (type) {
|
||||||
|
"mBanking" -> listOf(
|
||||||
|
"1. Login ke aplikasi mobile banking",
|
||||||
|
"2. Pilih menu Transfer",
|
||||||
|
"3. Pilih menu Antar Rekening",
|
||||||
|
"4. Masukkan nomor rekening tujuan",
|
||||||
|
"5. Masukkan nominal transfer sesuai tagihan",
|
||||||
|
"6. Konfirmasi dan selesaikan transfer"
|
||||||
|
)
|
||||||
|
"ATM" -> listOf(
|
||||||
|
"1. Masukkan kartu ATM dan PIN",
|
||||||
|
"2. Pilih menu Transfer",
|
||||||
|
"3. Pilih menu Antar Rekening",
|
||||||
|
"4. Masukkan kode bank dan nomor rekening tujuan",
|
||||||
|
"5. Masukkan nominal transfer sesuai tagihan",
|
||||||
|
"6. Konfirmasi dan selesaikan transfer"
|
||||||
|
)
|
||||||
|
else -> emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tampilkan instruksi dalam dialog
|
||||||
|
val dialog = AlertDialog.Builder(this)
|
||||||
|
.setTitle("Petunjuk Transfer $type")
|
||||||
|
.setItems(instructions.toTypedArray(), null)
|
||||||
|
.setPositiveButton("Tutup", null)
|
||||||
|
.create()
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order.detail
|
||||||
|
|
||||||
|
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.AddEvidenceMultipartRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.OrderListItemsItem
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.Orders
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class PaymentViewModel(private val repository: OrderRepository) : ViewModel() {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "PaymentViewModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
// LiveData untuk Order
|
||||||
|
private val _orderDetails = MutableLiveData<Orders>()
|
||||||
|
val orderDetails: LiveData<Orders> get() = _orderDetails
|
||||||
|
|
||||||
|
// LiveData untuk OrderItems
|
||||||
|
private val _orderItems = MutableLiveData<List<OrderListItemsItem>>()
|
||||||
|
val orderItems: LiveData<List<OrderListItemsItem>> get() = _orderItems
|
||||||
|
|
||||||
|
private val _orderCompletionStatus = MutableLiveData<Result<CompletedOrderResponse>>()
|
||||||
|
val orderCompletionStatus: LiveData<Result<CompletedOrderResponse>> = _orderCompletionStatus
|
||||||
|
|
||||||
|
// LiveData untuk status loading
|
||||||
|
private val _isLoading = MutableLiveData<Boolean>()
|
||||||
|
val isLoading: LiveData<Boolean> get() = _isLoading
|
||||||
|
|
||||||
|
// LiveData untuk error
|
||||||
|
private val _error = MutableLiveData<String>()
|
||||||
|
val error: LiveData<String> get() = _error
|
||||||
|
|
||||||
|
private val _uploadResult = MutableLiveData<Result<AddEvidenceResponse>>()
|
||||||
|
val uploadResult: LiveData<com.alya.ecommerce_serang.data.repository.Result<AddEvidenceResponse>> = _uploadResult
|
||||||
|
|
||||||
|
fun getOrderDetails(orderId: Int) {
|
||||||
|
_isLoading.value = true
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val response = repository.getOrderDetails(orderId)
|
||||||
|
if (response != null) {
|
||||||
|
_orderDetails.value = response.orders
|
||||||
|
_orderItems.value = response.orders.orderItems
|
||||||
|
} else {
|
||||||
|
_error.value = "Gagal memuat detail pesanan"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_error.value = "Terjadi kesalahan: ${e.message}"
|
||||||
|
Log.e(TAG, "Error fetching order details", e)
|
||||||
|
} finally {
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun uploadPaymentProof(request: AddEvidenceMultipartRequest) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_uploadResult.value = com.alya.ecommerce_serang.data.repository.Result.Loading
|
||||||
|
try {
|
||||||
|
val result = repository.uploadPaymentProof(request)
|
||||||
|
_uploadResult.value = result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("PaymentProofViewModel", "Error uploading payment proof", e)
|
||||||
|
_uploadResult.value = com.alya.ecommerce_serang.data.repository.Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order.history
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.commit
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.ActivityHistoryBinding
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
|
||||||
|
class HistoryActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityHistoryBinding
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
|
||||||
|
private val viewModel: HistoryViewModel by viewModels {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
val orderRepository = OrderRepository(apiService)
|
||||||
|
HistoryViewModel(orderRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityHistoryBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
sessionManager = SessionManager(this)
|
||||||
|
|
||||||
|
setupToolbar()
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
showOrderFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupToolbar() {
|
||||||
|
setSupportActionBar(binding.toolbar)
|
||||||
|
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||||
|
|
||||||
|
binding.btnBack.setOnClickListener {
|
||||||
|
onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun showOrderFragment() {
|
||||||
|
supportFragmentManager.commit {
|
||||||
|
replace(R.id.fragment_container_history, OrderHistoryFragment())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order.history
|
||||||
|
|
||||||
|
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.CompletedOrderRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.OrdersItem
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
|
import com.alya.ecommerce_serang.ui.order.address.ViewState
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class HistoryViewModel(private val repository: OrderRepository) : ViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "HistoryViewModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _orders = MutableLiveData<ViewState<List<OrdersItem>>>()
|
||||||
|
val orders: LiveData<ViewState<List<OrdersItem>>> = _orders
|
||||||
|
|
||||||
|
private val _orderCompletionStatus = MutableLiveData<Result<CompletedOrderResponse>>()
|
||||||
|
val orderCompletionStatus: LiveData<Result<CompletedOrderResponse>> = _orderCompletionStatus
|
||||||
|
|
||||||
|
private val _isLoading = MutableLiveData<Boolean>()
|
||||||
|
val isLoading: LiveData<Boolean> = _isLoading
|
||||||
|
|
||||||
|
private val _message = MutableLiveData<String>()
|
||||||
|
val message: LiveData<String> = _message
|
||||||
|
|
||||||
|
private val _isSuccess = MutableLiveData<Boolean>()
|
||||||
|
val isSuccess: LiveData<Boolean> = _isSuccess
|
||||||
|
|
||||||
|
fun getOrderList(status: String) {
|
||||||
|
_orders.value = ViewState.Loading
|
||||||
|
viewModelScope.launch {
|
||||||
|
_orders.value = ViewState.Loading
|
||||||
|
|
||||||
|
try {
|
||||||
|
when (val result = repository.getOrderList(status)) {
|
||||||
|
is Result.Success -> {
|
||||||
|
_orders.value = ViewState.Success(result.data.orders)
|
||||||
|
Log.d("HistoryViewModel", "Orders loaded successfully: ${result.data.orders.size} items")
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
_orders.value = ViewState.Error(result.exception.message ?: "Unknown error occurred")
|
||||||
|
Log.e("HistoryViewModel", "Error loading orders", result.exception)
|
||||||
|
}
|
||||||
|
is Result.Loading -> {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_orders.value = ViewState.Error("An unexpected error occurred: ${e.message}")
|
||||||
|
Log.e("HistoryViewModel", "Exception in getOrderList", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun confirmOrderCompleted(orderId: Int, status: String) {
|
||||||
|
Log.d(TAG, "Confirming order completed: orderId=$orderId, status=$status")
|
||||||
|
viewModelScope.launch {
|
||||||
|
_orderCompletionStatus.value = Result.Loading
|
||||||
|
val request = CompletedOrderRequest(orderId, status)
|
||||||
|
|
||||||
|
Log.d(TAG, "Sending order completion request: $request")
|
||||||
|
val result = repository.confirmOrderCompleted(request)
|
||||||
|
Log.d(TAG, "Order completion result: $result")
|
||||||
|
_orderCompletionStatus.value = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelOrderWithImage(orderId: String, reason: String, imageFile: File?) {
|
||||||
|
Log.d(TAG, "Cancelling order with image: orderId=$orderId, reason=$reason, hasImage=${imageFile != null}")
|
||||||
|
viewModelScope.launch {
|
||||||
|
repository.submitComplaint(orderId, reason, imageFile).collect { result ->
|
||||||
|
when (result) {
|
||||||
|
is Result.Loading -> {
|
||||||
|
Log.d(TAG, "Submitting complaint: Loading")
|
||||||
|
_isLoading.value = true
|
||||||
|
}
|
||||||
|
is Result.Success -> {
|
||||||
|
Log.d(TAG, "Complaint submitted successfully: ${result.data.message}")
|
||||||
|
_message.value = result.data.message
|
||||||
|
_isSuccess.value = true
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
val errorMessage = result.exception.message ?: "Error submitting complaint"
|
||||||
|
Log.e(TAG, "Error submitting complaint: $errorMessage", result.exception)
|
||||||
|
_message.value = errorMessage
|
||||||
|
_isSuccess.value = false
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,504 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order.history
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.Window
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.AutoCompleteTextView
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.OrdersItem
|
||||||
|
import com.alya.ecommerce_serang.ui.order.detail.PaymentActivity
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
class OrderHistoryAdapter(
|
||||||
|
private val onOrderClickListener: (OrdersItem) -> Unit,
|
||||||
|
private val viewModel: HistoryViewModel // Add this parameter
|
||||||
|
) : RecyclerView.Adapter<OrderHistoryAdapter.OrderViewHolder>() {
|
||||||
|
|
||||||
|
private val orders = mutableListOf<OrdersItem>()
|
||||||
|
|
||||||
|
private var fragmentStatus: String = "all"
|
||||||
|
|
||||||
|
fun setFragmentStatus(status: String) {
|
||||||
|
fragmentStatus = status
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submitList(newOrders: List<OrdersItem>) {
|
||||||
|
orders.clear()
|
||||||
|
orders.addAll(newOrders)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OrderViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_order_history, parent, false)
|
||||||
|
return OrderViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: OrderViewHolder, position: Int) {
|
||||||
|
holder.bind(orders[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = orders.size
|
||||||
|
|
||||||
|
inner class OrderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
private val tvStoreName: TextView = itemView.findViewById(R.id.tvStoreName)
|
||||||
|
private val rvOrderItems: RecyclerView = itemView.findViewById(R.id.rvOrderItems)
|
||||||
|
private val tvShowMore: TextView = itemView.findViewById(R.id.tvShowMore)
|
||||||
|
private val tvTotalAmount: TextView = itemView.findViewById(R.id.tvTotalAmount)
|
||||||
|
private val tvItemCountLabel: TextView = itemView.findViewById(R.id.tv_count_total_item)
|
||||||
|
// private val tvDeadlineDate: TextView = itemView.findViewById(R.id.tvDeadlineDate)
|
||||||
|
|
||||||
|
fun bind(order: OrdersItem) {
|
||||||
|
// Get store name from the first order item
|
||||||
|
val storeName = if (order.orderItems.isNotEmpty()) order.orderItems[0].storeName else ""
|
||||||
|
tvStoreName.text = storeName
|
||||||
|
|
||||||
|
// Set total amount
|
||||||
|
tvTotalAmount.text = order.totalAmount
|
||||||
|
|
||||||
|
// Set item count
|
||||||
|
val itemCount = order.orderItems.size
|
||||||
|
tvItemCountLabel.text = itemView.context.getString(R.string.item_count_prod, itemCount)
|
||||||
|
|
||||||
|
// Set deadline date, adjust to each status
|
||||||
|
// tvDeadlineDate.text = formatDate(order.updatedAt)
|
||||||
|
|
||||||
|
// Set up the order items RecyclerView
|
||||||
|
val productAdapter = OrderProductAdapter()
|
||||||
|
rvOrderItems.apply {
|
||||||
|
layoutManager = LinearLayoutManager(itemView.context)
|
||||||
|
adapter = productAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display only the first product and show "View more" for the rest
|
||||||
|
if (order.orderItems.isNotEmpty()) {
|
||||||
|
productAdapter.submitList(order.orderItems.take(1))
|
||||||
|
|
||||||
|
// Show or hide the "View more" text based on number of items
|
||||||
|
if (order.orderItems.size > 1) {
|
||||||
|
val itemString = order.orderItems.size - 1
|
||||||
|
tvShowMore.visibility = View.VISIBLE
|
||||||
|
tvShowMore.text = itemView.context.getString(R.string.show_more_product, itemString)
|
||||||
|
} else {
|
||||||
|
tvShowMore.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tvShowMore.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set click listener for the entire order item
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
onOrderClickListener(order)
|
||||||
|
}
|
||||||
|
|
||||||
|
//adjust each fragment
|
||||||
|
adjustButtonsAndText(fragmentStatus, order)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun adjustButtonsAndText(status: String, order: OrdersItem) {
|
||||||
|
Log.d("OrderHistoryAdapter", "Adjusting buttons for status: $status")
|
||||||
|
// Mendapatkan referensi ke tombol-tombol
|
||||||
|
val btnLeft = itemView.findViewById<MaterialButton>(R.id.btn_left)
|
||||||
|
val btnRight = itemView.findViewById<MaterialButton>(R.id.btn_right)
|
||||||
|
val statusOrder = itemView.findViewById<TextView>(R.id.tvOrderStatus)
|
||||||
|
val deadlineLabel = itemView.findViewById<TextView>(R.id.tvDeadlineLabel)
|
||||||
|
val deadlineDate = itemView.findViewById<TextView>(R.id.tvDeadlineDate)
|
||||||
|
|
||||||
|
// Reset visibility
|
||||||
|
btnLeft.visibility = View.GONE
|
||||||
|
btnRight.visibility = View.GONE
|
||||||
|
statusOrder.visibility = View.GONE
|
||||||
|
deadlineLabel.visibility = View.GONE
|
||||||
|
|
||||||
|
when (status) {
|
||||||
|
"pending" -> {
|
||||||
|
statusOrder.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.pending_orders)
|
||||||
|
}
|
||||||
|
deadlineLabel.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.dl_pending)
|
||||||
|
}
|
||||||
|
btnLeft.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.canceled_order_btn)
|
||||||
|
setOnClickListener {
|
||||||
|
showCancelOrderDialog(order.orderId.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deadlineDate.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = formatDate(order.createdAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"unpaid" -> {
|
||||||
|
statusOrder.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.unpaid_orders)
|
||||||
|
}
|
||||||
|
deadlineLabel.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.dl_unpaid)
|
||||||
|
}
|
||||||
|
btnLeft.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.canceled_order_btn)
|
||||||
|
setOnClickListener {
|
||||||
|
showCancelOrderDialog(order.orderId.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btnRight.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.sent_evidence)
|
||||||
|
setOnClickListener {
|
||||||
|
val intent = Intent(itemView.context, PaymentActivity::class.java)
|
||||||
|
// Menambahkan data yang diperlukan
|
||||||
|
intent.putExtra("ORDER_ID", order.orderId)
|
||||||
|
intent.putExtra("ORDER_PAYMENT_ID", order.paymentInfoId)
|
||||||
|
|
||||||
|
// Memulai aktivitas
|
||||||
|
itemView.context.startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deadlineDate.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = formatDatePay(order.updatedAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"processed" -> {
|
||||||
|
// Untuk status processed, tampilkan "Hubungi Penjual"
|
||||||
|
statusOrder.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.processed_orders)
|
||||||
|
}
|
||||||
|
deadlineLabel.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.dl_processed)
|
||||||
|
}
|
||||||
|
btnLeft.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.canceled_order_btn)
|
||||||
|
setOnClickListener {
|
||||||
|
showCancelOrderDialog(order.orderId.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"shipped" -> {
|
||||||
|
// Untuk status shipped, tampilkan "Lacak Pengiriman" dan "Terima Barang"
|
||||||
|
statusOrder.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.shipped_orders)
|
||||||
|
}
|
||||||
|
deadlineLabel.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.dl_shipped)
|
||||||
|
}
|
||||||
|
btnLeft.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.claim_complaint)
|
||||||
|
setOnClickListener {
|
||||||
|
showCancelOrderDialog(order.orderId.toString())
|
||||||
|
// Handle click event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btnRight.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.claim_order)
|
||||||
|
setOnClickListener {
|
||||||
|
// Handle click event
|
||||||
|
viewModel.confirmOrderCompleted(order.orderId, "completed")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deadlineDate.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = formatShipmentDate(order.updatedAt, order.etd.toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"delivered" -> {
|
||||||
|
// Untuk status delivered, tampilkan "Beri Ulasan"
|
||||||
|
btnRight.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.add_review)
|
||||||
|
setOnClickListener {
|
||||||
|
// Handle click event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"completed" -> {
|
||||||
|
statusOrder.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.shipped_orders)
|
||||||
|
}
|
||||||
|
deadlineLabel.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.dl_shipped)
|
||||||
|
}
|
||||||
|
btnRight.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.add_review)
|
||||||
|
setOnClickListener {
|
||||||
|
// Handle click event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"canceled" -> {
|
||||||
|
statusOrder.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
text = itemView.context.getString(R.string.canceled_orders)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatDate(dateString: String): String {
|
||||||
|
return try {
|
||||||
|
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
|
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
|
||||||
|
val outputFormat = SimpleDateFormat("HH:mm dd MMMM yyyy", Locale("id", "ID"))
|
||||||
|
|
||||||
|
val date = inputFormat.parse(dateString)
|
||||||
|
|
||||||
|
date?.let {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.time = it
|
||||||
|
calendar.set(Calendar.HOUR_OF_DAY, 23)
|
||||||
|
calendar.set(Calendar.MINUTE, 59)
|
||||||
|
|
||||||
|
outputFormat.format(calendar.time)
|
||||||
|
} ?: dateString
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DateFormatting", "Error formatting date: ${e.message}")
|
||||||
|
dateString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatDatePay(dateString: String): String {
|
||||||
|
return try {
|
||||||
|
// Parse the ISO 8601 date
|
||||||
|
val isoDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
|
isoDateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
|
||||||
|
val createdDate = isoDateFormat.parse(dateString)
|
||||||
|
|
||||||
|
// Add 24 hours to get due date
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.time = createdDate
|
||||||
|
calendar.add(Calendar.HOUR, 24)
|
||||||
|
val dueDate = calendar.time
|
||||||
|
|
||||||
|
// Format due date for display
|
||||||
|
val dueDateFormat = SimpleDateFormat("dd MMM yyyy", Locale.getDefault())
|
||||||
|
dueDateFormat.format(calendar.time)
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("DateFormatting", "Error formatting date: ${e.message}")
|
||||||
|
dateString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatShipmentDate(dateString: String, estimate: Int): String {
|
||||||
|
return try {
|
||||||
|
// Parse the input date
|
||||||
|
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
|
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
|
||||||
|
// Output format
|
||||||
|
val outputFormat = SimpleDateFormat("dd MMMM yyyy", Locale("id", "ID"))
|
||||||
|
|
||||||
|
// Parse the input date
|
||||||
|
val date = inputFormat.parse(dateString)
|
||||||
|
|
||||||
|
date?.let {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.time = it
|
||||||
|
|
||||||
|
// Add estimated days
|
||||||
|
calendar.add(Calendar.DAY_OF_MONTH, estimate)
|
||||||
|
outputFormat.format(calendar.time)
|
||||||
|
} ?: dateString
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("ShipmentDateFormatting", "Error formatting shipment date: ${e.message}")
|
||||||
|
dateString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showCancelOrderDialog(orderId: String) {
|
||||||
|
val context = itemView.context
|
||||||
|
val dialog = Dialog(context)
|
||||||
|
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
dialog.setContentView(R.layout.dialog_cancel_order)
|
||||||
|
dialog.setCancelable(true)
|
||||||
|
|
||||||
|
// Set the dialog width to match parent
|
||||||
|
val window = dialog.window
|
||||||
|
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||||
|
|
||||||
|
// Get references to the views in the dialog
|
||||||
|
val spinnerCancelReason = dialog.findViewById<AutoCompleteTextView>(R.id.spinnerCancelReason)
|
||||||
|
val tilCancelReason = dialog.findViewById<TextInputLayout>(R.id.tilCancelReason)
|
||||||
|
val btnCancelDialog = dialog.findViewById<MaterialButton>(R.id.btnCancelDialog)
|
||||||
|
val btnConfirmCancel = dialog.findViewById<MaterialButton>(R.id.btnConfirmCancel)
|
||||||
|
val ivComplaintImage = dialog.findViewById<ImageView>(R.id.ivComplaintImage)
|
||||||
|
val tvSelectImage = dialog.findViewById<TextView>(R.id.tvSelectImage)
|
||||||
|
|
||||||
|
// Set up the reasons dropdown
|
||||||
|
val reasons = context.resources.getStringArray(R.array.cancellation_reasons)
|
||||||
|
val adapter = ArrayAdapter(context, android.R.layout.simple_dropdown_item_1line, reasons)
|
||||||
|
spinnerCancelReason.setAdapter(adapter)
|
||||||
|
|
||||||
|
// For storing the selected image URI
|
||||||
|
var selectedImageUri: Uri? = null
|
||||||
|
|
||||||
|
// Set click listener for image selection
|
||||||
|
ivComplaintImage.setOnClickListener {
|
||||||
|
// Create an intent to open the image picker
|
||||||
|
val galleryIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
|
||||||
|
(context as? Activity)?.startActivityForResult(galleryIntent, REQUEST_IMAGE_PICK)
|
||||||
|
|
||||||
|
// Set up result handler in the activity
|
||||||
|
val activity = context as? Activity
|
||||||
|
activity?.let {
|
||||||
|
// Remove any existing callbacks to avoid memory leaks
|
||||||
|
if (imagePickCallback != null) {
|
||||||
|
imagePickCallback = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new callback for this specific dialog
|
||||||
|
imagePickCallback = { uri ->
|
||||||
|
selectedImageUri = uri
|
||||||
|
|
||||||
|
// Load and display the selected image
|
||||||
|
ivComplaintImage.setImageURI(uri)
|
||||||
|
tvSelectImage.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set click listeners for buttons
|
||||||
|
btnCancelDialog.setOnClickListener {
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
btnConfirmCancel.setOnClickListener {
|
||||||
|
val reason = spinnerCancelReason.text.toString().trim()
|
||||||
|
|
||||||
|
if (reason.isEmpty()) {
|
||||||
|
tilCancelReason.error = context.getString(R.string.please_select_cancellation_reason)
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear error if any
|
||||||
|
tilCancelReason.error = null
|
||||||
|
|
||||||
|
// Convert selected image to file if available
|
||||||
|
val imageFile = selectedImageUri?.let { uri ->
|
||||||
|
try {
|
||||||
|
// Get the file path from URI
|
||||||
|
val filePathColumn = arrayOf(MediaStore.Images.Media.DATA)
|
||||||
|
val cursor = context.contentResolver.query(uri, filePathColumn, null, null, null)
|
||||||
|
cursor?.use {
|
||||||
|
if (it.moveToFirst()) {
|
||||||
|
val columnIndex = it.getColumnIndex(filePathColumn[0])
|
||||||
|
val filePath = it.getString(columnIndex)
|
||||||
|
return@let File(filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("OrderHistoryAdapter", "Error getting file from URI: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show loading indicator
|
||||||
|
val loadingView = View(context).apply {
|
||||||
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
setBackgroundColor(Color.parseColor("#80000000"))
|
||||||
|
|
||||||
|
val progressBar = ProgressBar(context).apply {
|
||||||
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addView(progressBar)
|
||||||
|
// (progressBar.layoutParams as? ViewGroup.MarginLayoutParams)?.apply {
|
||||||
|
// gravity = Gravity.CENTER
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.addContentView(loadingView, loadingView.layoutParams)
|
||||||
|
|
||||||
|
// Call the ViewModel to cancel the order with image
|
||||||
|
viewModel.cancelOrderWithImage(orderId, reason, imageFile)
|
||||||
|
|
||||||
|
// Observe for success/failure
|
||||||
|
viewModel.isSuccess.observe(itemView.findViewTreeLifecycleOwner()!!) { isSuccess ->
|
||||||
|
// Remove loading indicator
|
||||||
|
(loadingView.parent as? ViewGroup)?.removeView(loadingView)
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
Toast.makeText(context, context.getString(R.string.order_canceled_successfully), Toast.LENGTH_SHORT).show()
|
||||||
|
dialog.dismiss()
|
||||||
|
|
||||||
|
// Find the order in the list and remove it or update its status
|
||||||
|
val position = orders.indexOfFirst { it.orderId.toString() == orderId }
|
||||||
|
if (position != -1) {
|
||||||
|
orders.removeAt(position)
|
||||||
|
notifyItemRemoved(position)
|
||||||
|
notifyItemRangeChanged(position, orders.size)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, viewModel.message.value ?: context.getString(R.string.failed_to_cancel_order), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val REQUEST_IMAGE_PICK = 100
|
||||||
|
private var imagePickCallback: ((Uri) -> Unit)? = null
|
||||||
|
|
||||||
|
// This method should be called from the activity's onActivityResult
|
||||||
|
fun handleImageResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (requestCode == REQUEST_IMAGE_PICK && resultCode == Activity.RESULT_OK && data != null) {
|
||||||
|
val selectedImageUri = data.data
|
||||||
|
selectedImageUri?.let { uri ->
|
||||||
|
imagePickCallback?.invoke(uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order.history
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.databinding.FragmentOrderHistoryBinding
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
|
|
||||||
|
class OrderHistoryFragment : Fragment() {
|
||||||
|
|
||||||
|
private var _binding: FragmentOrderHistoryBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
|
||||||
|
|
||||||
|
private lateinit var viewPagerAdapter: OrderViewPagerAdapter
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentOrderHistoryBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
sessionManager = SessionManager(requireContext())
|
||||||
|
|
||||||
|
setupViewPager()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupViewPager() {
|
||||||
|
// Initialize the ViewPager adapter
|
||||||
|
viewPagerAdapter = OrderViewPagerAdapter(requireActivity())
|
||||||
|
binding.viewPager.adapter = viewPagerAdapter
|
||||||
|
|
||||||
|
// Connect TabLayout with ViewPager2
|
||||||
|
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
|
||||||
|
tab.text = when (position) {
|
||||||
|
0 -> getString(R.string.all_orders)
|
||||||
|
1 -> getString(R.string.pending_orders)
|
||||||
|
2 -> getString(R.string.unpaid_orders)
|
||||||
|
3 -> getString(R.string.processed_orders)
|
||||||
|
4 -> getString(R.string.paid_orders)
|
||||||
|
5 -> getString(R.string.shipped_orders)
|
||||||
|
6 -> getString(R.string.delivered_orders)
|
||||||
|
7 -> getString(R.string.completed_orders)
|
||||||
|
8 -> getString(R.string.canceled_orders)
|
||||||
|
else -> "Tab $position"
|
||||||
|
}
|
||||||
|
}.attach()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,151 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order.history
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.OrdersItem
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
|
import com.alya.ecommerce_serang.databinding.FragmentOrderListBinding
|
||||||
|
import com.alya.ecommerce_serang.ui.order.address.ViewState
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
|
||||||
|
class OrderListFragment : Fragment() {
|
||||||
|
|
||||||
|
private var _binding: FragmentOrderListBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
|
||||||
|
|
||||||
|
private val viewModel: HistoryViewModel by viewModels {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
val orderRepository = OrderRepository(apiService)
|
||||||
|
HistoryViewModel(orderRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private lateinit var orderAdapter: OrderHistoryAdapter
|
||||||
|
|
||||||
|
private var status: String = "all"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ARG_STATUS = "status"
|
||||||
|
|
||||||
|
fun newInstance(status: String): OrderListFragment {
|
||||||
|
return OrderListFragment().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putString(ARG_STATUS, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
sessionManager = SessionManager(requireContext())
|
||||||
|
arguments?.let {
|
||||||
|
status = it.getString(ARG_STATUS) ?: "all"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentOrderListBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
setupRecyclerView()
|
||||||
|
observeOrderList()
|
||||||
|
observeOrderCompletionStatus()
|
||||||
|
loadOrders()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRecyclerView() {
|
||||||
|
orderAdapter = OrderHistoryAdapter(
|
||||||
|
onOrderClickListener = { order ->
|
||||||
|
// Handle order click
|
||||||
|
navigateToOrderDetail(order)
|
||||||
|
},
|
||||||
|
viewModel = viewModel // Pass the ViewModel to the adapter
|
||||||
|
)
|
||||||
|
|
||||||
|
orderAdapter.setFragmentStatus(status)
|
||||||
|
|
||||||
|
binding.rvOrders.apply {
|
||||||
|
layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
adapter = orderAdapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeOrderList() {
|
||||||
|
viewModel.orders.observe(viewLifecycleOwner) { result ->
|
||||||
|
when (result) {
|
||||||
|
is ViewState.Success -> {
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
|
||||||
|
if (result.data.isNullOrEmpty()) {
|
||||||
|
binding.tvEmptyState.visibility = View.VISIBLE
|
||||||
|
binding.rvOrders.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.tvEmptyState.visibility = View.GONE
|
||||||
|
binding.rvOrders.visibility = View.VISIBLE
|
||||||
|
orderAdapter.submitList(result.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ViewState.Error -> {
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
binding.tvEmptyState.visibility = View.VISIBLE
|
||||||
|
Toast.makeText(requireContext(), result.message, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
is ViewState.Loading -> {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadOrders() {
|
||||||
|
viewModel.getOrderList(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateToOrderDetail(order: OrdersItem) {
|
||||||
|
// In a real app, you would navigate to order detail screen
|
||||||
|
// For example: findNavController().navigate(OrderListFragmentDirections.actionToOrderDetail(order.orderId))
|
||||||
|
Toast.makeText(requireContext(), "Order ID: ${order.orderId}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeOrderCompletionStatus(){
|
||||||
|
viewModel.orderCompletionStatus.observe(viewLifecycleOwner){ result ->
|
||||||
|
when(result){
|
||||||
|
is Result.Loading -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
is Result.Success -> {
|
||||||
|
Toast.makeText(requireContext(), "Order completed successfully!", Toast.LENGTH_SHORT).show()
|
||||||
|
loadOrders()
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
Toast.makeText(requireContext(), "Failed to complete order: ${result.exception.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order.history
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.OrderItemsItem
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
|
||||||
|
class OrderProductAdapter : RecyclerView.Adapter<OrderProductAdapter.ProductViewHolder>() {
|
||||||
|
|
||||||
|
private val products = mutableListOf<OrderItemsItem>()
|
||||||
|
|
||||||
|
fun submitList(newProducts: List<OrderItemsItem>) {
|
||||||
|
products.clear()
|
||||||
|
products.addAll(newProducts)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_order_product, parent, false)
|
||||||
|
return ProductViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
|
||||||
|
holder.bind(products[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = products.size
|
||||||
|
|
||||||
|
inner class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
private val ivProductImage: ImageView = itemView.findViewById(R.id.iv_product)
|
||||||
|
private val tvProductName: TextView = itemView.findViewById(R.id.tv_product_name)
|
||||||
|
private val tvQuantity: TextView = itemView.findViewById(R.id.tv_product_quantity)
|
||||||
|
private val tvProductPrice: TextView = itemView.findViewById(R.id.tv_product_price)
|
||||||
|
|
||||||
|
fun bind(product: OrderItemsItem) {
|
||||||
|
// Set product name
|
||||||
|
tvProductName.text = product.productName
|
||||||
|
|
||||||
|
// Set quantity with suffix
|
||||||
|
tvQuantity.text = "${product.quantity} buah"
|
||||||
|
|
||||||
|
// Set price with currency format
|
||||||
|
tvProductPrice.text = formatCurrency(product.price)
|
||||||
|
|
||||||
|
// Load product image using Glide
|
||||||
|
Glide.with(itemView.context)
|
||||||
|
.load(product.productImage)
|
||||||
|
.placeholder(R.drawable.placeholder_image)
|
||||||
|
// .error(R.drawable.error_image)
|
||||||
|
.into(ivProductImage)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private fun formatCurrency(amount: Int): String {
|
||||||
|
// In a real app, you would use NumberFormat for proper currency formatting
|
||||||
|
// For simplicity, just return a basic formatted string
|
||||||
|
return "Rp${amount}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order.history
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
|
||||||
|
class OrderViewPagerAdapter(
|
||||||
|
fragmentActivity: FragmentActivity
|
||||||
|
) : FragmentStateAdapter(fragmentActivity) {
|
||||||
|
|
||||||
|
// Define all possible order statuses
|
||||||
|
private val orderStatuses = listOf(
|
||||||
|
"all", // All orders
|
||||||
|
"pending", // Menunggu Tagihan
|
||||||
|
"unpaid", // Belum Dibayar
|
||||||
|
"processed", // Diproses
|
||||||
|
"paid", // Dibayar
|
||||||
|
"shipped", // Dikirim
|
||||||
|
"delivered", // Diterima
|
||||||
|
"completed", // Selesai
|
||||||
|
"canceled" // Dibatalkan
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = orderStatuses.size
|
||||||
|
|
||||||
|
override fun createFragment(position: Int): Fragment {
|
||||||
|
// Create a new instance of OrderListFragment with the appropriate status
|
||||||
|
return OrderListFragment.newInstance(orderStatuses[position])
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
|||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
import com.alya.ecommerce_serang.databinding.FragmentProfileBinding
|
import com.alya.ecommerce_serang.databinding.FragmentProfileBinding
|
||||||
|
import com.alya.ecommerce_serang.ui.order.history.HistoryActivity
|
||||||
import com.alya.ecommerce_serang.ui.profile.mystore.MyStoreActivity
|
import com.alya.ecommerce_serang.ui.profile.mystore.MyStoreActivity
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
@ -63,6 +64,16 @@ class ProfileFragment : Fragment() {
|
|||||||
val intentDetail = Intent(requireContext(), DetailProfileActivity::class.java)
|
val intentDetail = Intent(requireContext(), DetailProfileActivity::class.java)
|
||||||
startActivity(intentDetail)
|
startActivity(intentDetail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.tvLihatRiwayat.setOnClickListener{
|
||||||
|
val intent = Intent(requireContext(), HistoryActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.cardPesanan.setOnClickListener{
|
||||||
|
val intent = Intent(requireContext(), HistoryActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeUserProfile() {
|
private fun observeUserProfile() {
|
||||||
|
@ -52,23 +52,6 @@ class HomeViewModel (
|
|||||||
loadProducts()
|
loadProducts()
|
||||||
loadCategories()
|
loadCategories()
|
||||||
}
|
}
|
||||||
|
|
||||||
// private fun fetchUserData() {
|
|
||||||
// viewModelScope.launch {
|
|
||||||
// try {
|
|
||||||
// val response = apiService.getProtectedData() // Example API request
|
|
||||||
// if (response.isSuccessful) {
|
|
||||||
// val data = response.body()
|
|
||||||
// Log.d("HomeFragment", "User Data: $data")
|
|
||||||
// // Update UI with data
|
|
||||||
// } else {
|
|
||||||
// Log.e("HomeFragment", "Error: ${response.message()}")
|
|
||||||
// }
|
|
||||||
// } catch (e: Exception) {
|
|
||||||
// Log.e("HomeFragment", "Exception: ${e.message}")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class HomeUiState {
|
sealed class HomeUiState {
|
||||||
|
5
app/src/main/res/drawable/baseline_upload_file_24.xml
Normal file
5
app/src/main/res/drawable/baseline_upload_file_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM18,20L6,20L6,4h7v5h5v11zM8,15.01l1.41,1.41L11,14.84L11,19h2v-4.16l1.59,1.59L16,15.01 12.01,11z"/>
|
||||||
|
|
||||||
|
</vector>
|
6
app/src/main/res/drawable/bg_button_filled.xml
Normal file
6
app/src/main/res/drawable/bg_button_filled.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@color/blue_500" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
8
app/src/main/res/drawable/bg_button_outline.xml
Normal file
8
app/src/main/res/drawable/bg_button_outline.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="@color/white" />
|
||||||
|
</shape>
|
11
app/src/main/res/drawable/bg_dashboard_border.xml
Normal file
11
app/src/main/res/drawable/bg_dashboard_border.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="#BCBCBC"
|
||||||
|
android:dashWidth="10dp"
|
||||||
|
android:dashGap="6dp" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
<solid android:color="#F5F5F5" />
|
||||||
|
</shape>
|
@ -115,10 +115,21 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="none"
|
android:inputType="none"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
android:textSize="14sp" />
|
android:textSize="14sp" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
|
||||||
<!-- Kabupaten / Kota -->
|
<!-- Kabupaten / Kota -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -138,11 +149,20 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="none"
|
android:inputType="none"
|
||||||
android:hint="Masukkan Kabupaten"
|
android:focusable="false"
|
||||||
|
android:clickable="true"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
android:textSize="14sp" />
|
android:textSize="14sp" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/cityProgressBar"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<!-- Kecamatan / Desa -->
|
<!-- Kecamatan / Desa -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -188,6 +208,8 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/buttonSimpan"
|
android:id="@+id/buttonSimpan"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -200,5 +222,60 @@
|
|||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent" />
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/submitProgressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/buttonSimpan"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/buttonSimpan"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Status Lokasi"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLocationStatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="Menunggu lokasi..."
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/locationProgressBar"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnReloadLocation"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:text="Reload"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
</LinearLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
185
app/src/main/res/layout/activity_add_evidence_payment.xml
Normal file
185
app/src/main/res/layout/activity_add_evidence_payment.xml
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ui.order.detail.AddEvidencePaymentActivity">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="#FFFFFF"
|
||||||
|
app:navigationIcon="@drawable/ic_back_24"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:text="Kirim Bukti Bayar"
|
||||||
|
android:textColor="#000000"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginVertical="8dp"
|
||||||
|
android:fontFamily="@font/dmsans_bold" />
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#EEEEEE"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/divider">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Unggah Foto *"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/cardAddPhoto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="0dp"
|
||||||
|
app:cardBackgroundColor="#FFFFFF">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAddPhoto"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Tambah Foto"
|
||||||
|
android:textColor="#1E88E5"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/frameUploadImage"
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/edit_text_background"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivUploadedImage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layoutUploadPlaceholder"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/edit_text_background"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@drawable/baseline_upload_file_24" />
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Metode Pembayaran *"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerPaymentMethod"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/edit_text_background"
|
||||||
|
android:minHeight="50dp"
|
||||||
|
android:padding="12dp" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Nomor Rekening / Nomor HP *"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etAccountNumber"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/edit_text_background"
|
||||||
|
android:hint="Isi nomor rekening atau nomor hp pembayaran"
|
||||||
|
android:inputType="text"
|
||||||
|
android:minHeight="50dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:padding="12dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Tanggal Pembayaran *"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPaymentDate"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/edit_text_background"
|
||||||
|
android:drawableEnd="@drawable/ic_calendar"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:hint="Pilih tanggal"
|
||||||
|
android:minHeight="50dp"
|
||||||
|
android:padding="12dp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSubmit"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:background="@drawable/bg_button_filled"
|
||||||
|
android:text="Kirim"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:padding="12dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -24,6 +24,7 @@
|
|||||||
app:title="Alamat Pengiriman " />
|
app:title="Alamat Pengiriman " />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/add_address_click"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAlignment="textEnd"
|
android:textAlignment="textEnd"
|
||||||
|
@ -287,8 +287,7 @@
|
|||||||
android:id="@+id/rv_payment_methods"
|
android:id="@+id/rv_payment_methods"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:listitem="@layout/item_payment_method"
|
tools:listitem="@layout/item_payment_method" />
|
||||||
tools:itemCount="2" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
58
app/src/main/res/layout/activity_history.xml
Normal file
58
app/src/main/res/layout/activity_history.xml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ui.order.history.HistoryActivity">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/white"
|
||||||
|
android:elevation="4dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnBack"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="Kembali"
|
||||||
|
android:src="@drawable/ic_back_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:text="Riwayat Pesanan"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/btnBack"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/fragment_container_history"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
244
app/src/main/res/layout/activity_payment.xml
Normal file
244
app/src/main/res/layout/activity_payment.xml
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/white"
|
||||||
|
tools:context=".ui.order.detail.PaymentActivity">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appBarLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="@android:color/white"
|
||||||
|
app:navigationIcon="@drawable/ic_back_24">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Pembayaran"
|
||||||
|
android:fontFamily="@font/dmsans_bold"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/buttonLayout"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/appBarLayout">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Total Bayar"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTotalAmount"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="end"
|
||||||
|
android:text="Rp75.000"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="#E0E0E0" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Bayar Dalam"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvRemainingTime"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="end"
|
||||||
|
android:text="23 jam 15 menit"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvDueDate"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="end"
|
||||||
|
android:text="Jatuh tempo 15 Nov 2024"
|
||||||
|
android:textColor="#808080"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="4dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivBankLogo"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:src="@drawable/outline_store_24" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Nomor Rekening:"
|
||||||
|
android:textColor="#808080"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAccountNumber"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="01233435436363757537856"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvBankName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Bank BCA"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="#E0E0E0" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layoutMBankingInstructions"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Petunjuk Transfer mBanking"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/ic_arrow_right" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#E0E0E0" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layoutATMInstructions"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Petunjuk Transfer ATM"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/ic_arrow_right" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#E0E0E0" />
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/buttonLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnNegotiatePrice"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:background="@drawable/bg_button_outline"
|
||||||
|
android:text="Negosiasi Harga"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@color/blue_500" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnUploadPaymentProof"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_button_filled"
|
||||||
|
android:text="Kirim Bukti Bayar"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@android:color/white" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -18,6 +18,7 @@
|
|||||||
app:title="Pengiriman" />
|
app:title="Pengiriman" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/linear_shipment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
@ -29,4 +30,13 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:listitem="@layout/item_shipping_order"/>
|
tools:listitem="@layout/item_shipping_order"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/linear_shipment"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
103
app/src/main/res/layout/dialog_cancel_order.xml
Normal file
103
app/src/main/res/layout/dialog_cancel_order.xml
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardCornerRadius="16dp"
|
||||||
|
app:cardElevation="8dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="24dp">
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:text="@string/cancel_order_confirmation"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/tilCancelReason"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:hint="@string/reason_for_cancellation">
|
||||||
|
|
||||||
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/spinnerCancelReason"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="none"
|
||||||
|
android:focusable="false" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<!-- Image Upload Section -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/upload_evidence"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:fontFamily="@font/dmsans_medium"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivComplaintImage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="150dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:background="@drawable/bg_dashboard_border"
|
||||||
|
android:contentDescription="@string/complaint_image" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSelectImage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:text="@string/tap_to_select_image"
|
||||||
|
android:drawableTop="@drawable/baseline_upload_file_24"
|
||||||
|
android:drawablePadding="8dp"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnCancelDialog"
|
||||||
|
style="@style/RoundedBorderStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:text="@string/cancel" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnConfirmCancel"
|
||||||
|
style="@style/RoundedBorderStyleFilled"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
android:text="@string/confirm" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
26
app/src/main/res/layout/fragment_order_history.xml
Normal file
26
app/src/main/res/layout/fragment_order_history.xml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tabLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/white"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:tabGravity="fill"
|
||||||
|
app:tabIndicatorColor="@color/blue_200"
|
||||||
|
app:tabMode="scrollable"
|
||||||
|
app:tabSelectedTextColor="@color/blue_200"
|
||||||
|
app:tabTextColor="@android:color/darker_gray" />
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/viewPager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tabLayout" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
40
app/src/main/res/layout/fragment_order_list.xml
Normal file
40
app/src/main/res/layout/fragment_order_list.xml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#F5F5F5">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvOrders"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:padding="8dp"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:listitem="@layout/item_order_history" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvEmptyState"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/no_available_orders"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
158
app/src/main/res/layout/item_order_history.xml
Normal file
158
app/src/main/res/layout/item_order_history.xml
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="2dp">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvStoreName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="SnackEnak" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvOrderStatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Menunggu Tagihan" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="#E0E0E0"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tvStoreName" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvOrderItems"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/divider"
|
||||||
|
tools:itemCount="1"
|
||||||
|
tools:listitem="@layout/item_order_product" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvShowMore"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textColor="@android:color/darker_gray"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/rvOrderItems"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
tools:text="@string/show_more_product" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/divider2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="#E0E0E0"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tvShowMore" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_count_total_item"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/item_count_prod"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:fontFamily="@font/dmsans_bold"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/divider2" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTotalLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/total_order"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:fontFamily="@font/dmsans_bold"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/tvTotalAmount"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/divider2"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTotalAmount"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/divider2"
|
||||||
|
tools:text="Rp500.000" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvDeadlineLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/batas_tagihan"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tv_count_total_item" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvDeadlineDate"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/tvDeadlineLabel"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tv_count_total_item"
|
||||||
|
tools:text="28 Oktober 2024" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btn_left"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_button_outline"
|
||||||
|
android:backgroundTint="@color/white"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvDeadlineDate"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:layout_marginTop="8dp"/>
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btn_right"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_button_filled"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
android:text="Kirim Bukti Bayar"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvDeadlineDate"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:layout_marginTop="8dp"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
57
app/src/main/res/layout/item_order_history_prod.xml
Normal file
57
app/src/main/res/layout/item_order_history_prod.xml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivProductImage"
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:background="#F0F0F0"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvProductName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/tvProductPrice"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/ivProductImage"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Jaket Pink Fuschia" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvQuantity"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="@android:color/darker_gray"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/ivProductImage"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tvProductName"
|
||||||
|
tools:text="2 buah" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvProductPrice"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Rp150.000" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
75
app/src/main/res/layout/item_order_payment.xml
Normal file
75
app/src/main/res/layout/item_order_payment.xml
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="2dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="12dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivProductImage"
|
||||||
|
android:layout_width="80dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
tools:src="@drawable/placeholder_image" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvStoreName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#808080"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="Store Name" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvProductName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="Product Name with potentially long description that might get truncated" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvProductPrice"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="Rp50.000" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvQuantity"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#808080"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="x2" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
@ -61,4 +61,61 @@
|
|||||||
<string name="add_to_cart">Keranjang</string>
|
<string name="add_to_cart">Keranjang</string>
|
||||||
<string name="beli_sekarang">Beli Sekarang</string>
|
<string name="beli_sekarang">Beli Sekarang</string>
|
||||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||||
|
<string name="batas_tagihan">Batas Tagihan :</string>
|
||||||
|
<string name="total_order">Total: </string>
|
||||||
|
<string name="dl_date_wait">23:59 15 April 2025</string>
|
||||||
|
<string name="no_available_orders">Tidak ada order</string>
|
||||||
|
<string name="item_count_prod">%d produk</string>
|
||||||
|
<string name="show_more_product">%d produk lainnya</string>
|
||||||
|
<!-- status order-->
|
||||||
|
<string name="all_orders">Semua Pesanan </string>
|
||||||
|
<string name="pending_orders">Menunggu Tagihan</string>
|
||||||
|
<string name="unpaid_orders">Konfrimasi Bayar</string>
|
||||||
|
<string name="processed_orders">Diproses</string>
|
||||||
|
<string name="paid_orders">Sudah Dibayar</string>
|
||||||
|
<string name="delivered_orders">Pesanan Sampai</string>
|
||||||
|
<string name="completed_orders">Selesai</string>
|
||||||
|
<string name="shipped_orders">Dikirim</string>
|
||||||
|
<string name="canceled_orders">Dibatalkan</string>
|
||||||
|
<!-- deadline label -->
|
||||||
|
<string name="dl_pending">Batas Tagihan</string>
|
||||||
|
<string name="dl_unpaid">Batas Pembayaran</string>
|
||||||
|
<string name="dl_processed">Batas Pengiriman</string>
|
||||||
|
<string name="dal_paid">Semua Pesanan </string>
|
||||||
|
<string name="dl_delivered">Semua Pesanan </string>
|
||||||
|
<string name="dl_completed">Semua Pesanan </string>
|
||||||
|
<string name="dl_shipped">Tanggal Pesanan Sampai</string>
|
||||||
|
<string name="dl_canceled">Semua Pesanan </string>
|
||||||
|
|
||||||
|
<string name="sent_evidence">Kirim Bukti Pembayaran </string>
|
||||||
|
<string name="canceled_order_btn">Batalkan Pesanan </string>
|
||||||
|
<string name="claim_complaint">Ajukan Komplain </string>
|
||||||
|
<string name="claim_order">Pesanan Diterima </string>
|
||||||
|
<string name="add_review">Beri Ulasan </string>
|
||||||
|
|
||||||
|
<string name="warning_icon">Warning Icon</string>
|
||||||
|
<string name="cancel_order_confirmation">Apakah anda yakin ingin membatalkan pesanan?</string>
|
||||||
|
<string name="reason_for_cancellation">Alasan Batalkan Pesanan</string>
|
||||||
|
<string name="cancel">Kembali</string>
|
||||||
|
<string name="confirm">Batalkan Pesanan</string>
|
||||||
|
<string name="order_canceled_successfully">Order canceled successfully</string>
|
||||||
|
<string name="failed_to_cancel_order">Failed to cancel order</string>
|
||||||
|
<string name="please_select_cancellation_reason">Please select a reason for cancellation</string>
|
||||||
|
<string name="upload_evidence">Unggah Bukti Komplain</string>
|
||||||
|
<string name="complaint_image">Complaint evidence image</string>
|
||||||
|
<string name="tap_to_select_image">Tekan untuk unggah foto</string>
|
||||||
|
<string name="please_select_image">Please select an image as evidence</string>
|
||||||
|
<string name="image_too_large">Image is too large. Please select a smaller image.</string>
|
||||||
|
|
||||||
|
<!-- Cancellation Reasons -->
|
||||||
|
<string-array name="cancellation_reasons">
|
||||||
|
<item>Found a better price elsewhere</item>
|
||||||
|
<item>Changed my mind about the product</item>
|
||||||
|
<item>Ordered the wrong item</item>
|
||||||
|
<item>Shipping time is too long</item>
|
||||||
|
<item>Financial reasons</item>
|
||||||
|
<item>Other reason</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
@ -7,4 +7,22 @@
|
|||||||
<item name="android:padding">12dp</item>
|
<item name="android:padding">12dp</item>
|
||||||
<!-- Add more style attributes as needed -->
|
<!-- Add more style attributes as needed -->
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="RoundedBorderStyle">
|
||||||
|
<!-- This style can be applied to views -->
|
||||||
|
<!-- <item name="android:background">@drawable/bg_button_outline</item>-->
|
||||||
|
<item name="strokeColor">@color/blue_500</item>
|
||||||
|
<item name="strokeWidth">2dp</item>
|
||||||
|
<item name="cornerRadius">8dp</item>
|
||||||
|
<item name="backgroundTint">@android:color/transparent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="RoundedBorderStyleFilled">
|
||||||
|
<!-- This style can be applied to views -->
|
||||||
|
<!-- <item name="android:background">@drawable/bg_button_outline</item>-->
|
||||||
|
<item name="strokeColor">@color/blue_500</item>
|
||||||
|
<item name="strokeWidth">2dp</item>
|
||||||
|
<item name="cornerRadius">8dp</item>
|
||||||
|
<item name="backgroundTint">@color/blue_500</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
Reference in New Issue
Block a user