mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-10 09:22: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">
|
||||
<component name="CodeInsightWorkspaceSettings">
|
||||
<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_FINE_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.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@ -25,7 +24,25 @@
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
<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" />
|
||||
<activity
|
||||
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,
|
||||
|
||||
@SerializedName("items")
|
||||
val itemCost: CostProduct
|
||||
val itemCost: List<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
|
||||
)
|
@ -4,14 +4,128 @@ import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class OrderDetailResponse(
|
||||
|
||||
@field:SerializedName("orders")
|
||||
@field:SerializedName("orders")
|
||||
val orders: Orders,
|
||||
|
||||
@field:SerializedName("message")
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class OrderItemsItem(
|
||||
data class Orders(
|
||||
|
||||
@field:SerializedName("receipt_num")
|
||||
val receiptNum: String? = null,
|
||||
|
||||
@field:SerializedName("payment_upload_at")
|
||||
val paymentUploadAt: String? = null,
|
||||
|
||||
@field:SerializedName("latitude")
|
||||
val latitude: String,
|
||||
|
||||
@field:SerializedName("pay_info_name")
|
||||
val payInfoName: String? = null,
|
||||
|
||||
@field:SerializedName("created_at")
|
||||
val createdAt: String,
|
||||
|
||||
@field:SerializedName("voucher_code")
|
||||
val voucherCode: String? = null,
|
||||
|
||||
@field:SerializedName("updated_at")
|
||||
val updatedAt: String,
|
||||
|
||||
@field:SerializedName("etd")
|
||||
val etd: String,
|
||||
|
||||
@field:SerializedName("street")
|
||||
val street: String,
|
||||
|
||||
@field:SerializedName("cancel_date")
|
||||
val cancelDate: String? = null,
|
||||
|
||||
@field:SerializedName("payment_evidence")
|
||||
val paymentEvidence: String? = null,
|
||||
|
||||
@field:SerializedName("longitude")
|
||||
val longitude: String,
|
||||
|
||||
@field:SerializedName("shipment_status")
|
||||
val shipmentStatus: String,
|
||||
|
||||
@field:SerializedName("order_items")
|
||||
val orderItems: List<OrderListItemsItem>,
|
||||
|
||||
@field:SerializedName("auto_completed_at")
|
||||
val autoCompletedAt: String? = null,
|
||||
|
||||
@field:SerializedName("is_store_location")
|
||||
val isStoreLocation: Boolean? = null,
|
||||
|
||||
@field:SerializedName("qris_image")
|
||||
val qrisImage: String? = null,
|
||||
|
||||
@field:SerializedName("voucher_name")
|
||||
val voucherName: String? = null,
|
||||
|
||||
@field:SerializedName("payment_status")
|
||||
val paymentStatus: String? = null,
|
||||
|
||||
@field:SerializedName("address_id")
|
||||
val addressId: Int,
|
||||
|
||||
@field:SerializedName("payment_amount")
|
||||
val paymentAmount: String? = null,
|
||||
|
||||
@field:SerializedName("cancel_reason")
|
||||
val cancelReason: String? = null,
|
||||
|
||||
@field:SerializedName("total_amount")
|
||||
val totalAmount: String? = null,
|
||||
|
||||
@field:SerializedName("user_id")
|
||||
val userId: Int,
|
||||
|
||||
@field:SerializedName("province_id")
|
||||
val provinceId: Int,
|
||||
|
||||
@field:SerializedName("courier")
|
||||
val courier: String,
|
||||
|
||||
@field:SerializedName("subdistrict")
|
||||
val subdistrict: String,
|
||||
|
||||
@field:SerializedName("service")
|
||||
val service: String,
|
||||
|
||||
@field:SerializedName("pay_info_num")
|
||||
val payInfoNum: String? = null,
|
||||
|
||||
@field:SerializedName("shipment_price")
|
||||
val shipmentPrice: String,
|
||||
|
||||
@field:SerializedName("voucher_id")
|
||||
val voucherId: Int? = null,
|
||||
|
||||
@field:SerializedName("payment_info_id")
|
||||
val paymentInfoId: Int? = null,
|
||||
|
||||
@field:SerializedName("detail")
|
||||
val detail: String,
|
||||
|
||||
@field:SerializedName("postal_code")
|
||||
val postalCode: String,
|
||||
|
||||
@field:SerializedName("order_id")
|
||||
val orderId: Int,
|
||||
|
||||
@field:SerializedName("city_id")
|
||||
val cityId: Int
|
||||
)
|
||||
|
||||
data class OrderListItemsItem(
|
||||
|
||||
@field:SerializedName("order_item_id")
|
||||
val orderItemId: Int,
|
||||
|
||||
@field:SerializedName("review_id")
|
||||
val reviewId: Int? = null,
|
||||
@ -26,7 +140,10 @@ data class OrderItemsItem(
|
||||
val subtotal: Int,
|
||||
|
||||
@field:SerializedName("product_image")
|
||||
val productImage: String? = null,
|
||||
val productImage: String,
|
||||
|
||||
@field:SerializedName("product_id")
|
||||
val productId: Int,
|
||||
|
||||
@field:SerializedName("store_name")
|
||||
val storeName: String,
|
||||
@ -37,93 +154,3 @@ data class OrderItemsItem(
|
||||
@field:SerializedName("product_name")
|
||||
val productName: String
|
||||
)
|
||||
|
||||
data class Orders(
|
||||
|
||||
@field:SerializedName("receipt_num")
|
||||
val receiptNum: String,
|
||||
|
||||
@field:SerializedName("latitude")
|
||||
val latitude: String,
|
||||
|
||||
@field:SerializedName("created_at")
|
||||
val createdAt: String,
|
||||
|
||||
@field:SerializedName("voucher_code")
|
||||
val voucherCode: String? = null,
|
||||
|
||||
@field:SerializedName("updated_at")
|
||||
val updatedAt: String,
|
||||
|
||||
@field:SerializedName("etd")
|
||||
val etd: String,
|
||||
|
||||
@field:SerializedName("street")
|
||||
val street: String,
|
||||
|
||||
@field:SerializedName("cancel_date")
|
||||
val cancelDate: String,
|
||||
|
||||
@field:SerializedName("longitude")
|
||||
val longitude: String,
|
||||
|
||||
@field:SerializedName("shipment_status")
|
||||
val shipmentStatus: String,
|
||||
|
||||
@field:SerializedName("order_items")
|
||||
val orderItems: List<OrderItemsItem>,
|
||||
|
||||
@field:SerializedName("auto_completed_at")
|
||||
val autoCompletedAt: String,
|
||||
|
||||
@field:SerializedName("is_store_location")
|
||||
val isStoreLocation: Boolean,
|
||||
|
||||
@field:SerializedName("voucher_name")
|
||||
val voucherName: String? = null,
|
||||
|
||||
@field:SerializedName("address_id")
|
||||
val addressId: Int,
|
||||
|
||||
@field:SerializedName("payment_method_id")
|
||||
val paymentMethodId: Int,
|
||||
|
||||
@field:SerializedName("cancel_reason")
|
||||
val cancelReason: String,
|
||||
|
||||
@field:SerializedName("total_amount")
|
||||
val totalAmount: String,
|
||||
|
||||
@field:SerializedName("user_id")
|
||||
val userId: Int,
|
||||
|
||||
@field:SerializedName("province_id")
|
||||
val provinceId: Int,
|
||||
|
||||
@field:SerializedName("courier")
|
||||
val courier: String,
|
||||
|
||||
@field:SerializedName("subdistrict")
|
||||
val subdistrict: String,
|
||||
|
||||
@field:SerializedName("service")
|
||||
val service: String,
|
||||
|
||||
@field:SerializedName("shipment_price")
|
||||
val shipmentPrice: String,
|
||||
|
||||
@field:SerializedName("voucher_id")
|
||||
val voucherId: Int? = null,
|
||||
|
||||
@field:SerializedName("detail")
|
||||
val detail: String,
|
||||
|
||||
@field:SerializedName("postal_code")
|
||||
val postalCode: String,
|
||||
|
||||
@field:SerializedName("order_id")
|
||||
val orderId: Int,
|
||||
|
||||
@field:SerializedName("city_id")
|
||||
val cityId: Int
|
||||
)
|
||||
|
@ -11,81 +11,119 @@ data class OrderListResponse(
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class OrdersItem(
|
||||
data class OrderItemsItem(
|
||||
|
||||
@field:SerializedName("receipt_num")
|
||||
val receiptNum: String,
|
||||
@field:SerializedName("review_id")
|
||||
val reviewId: Int? = null,
|
||||
|
||||
@field:SerializedName("latitude")
|
||||
val latitude: String,
|
||||
@field:SerializedName("quantity")
|
||||
val quantity: Int,
|
||||
|
||||
@field:SerializedName("created_at")
|
||||
val createdAt: String,
|
||||
@field:SerializedName("price")
|
||||
val price: Int,
|
||||
|
||||
@field:SerializedName("voucher_code")
|
||||
val voucherCode: String? = null,
|
||||
@field:SerializedName("subtotal")
|
||||
val subtotal: Int,
|
||||
|
||||
@field:SerializedName("updated_at")
|
||||
val updatedAt: String,
|
||||
@field:SerializedName("product_image")
|
||||
val productImage: String,
|
||||
|
||||
@field:SerializedName("street")
|
||||
val street: String,
|
||||
@field:SerializedName("store_name")
|
||||
val storeName: String,
|
||||
|
||||
@field:SerializedName("longitude")
|
||||
val longitude: String,
|
||||
@field:SerializedName("product_price")
|
||||
val productPrice: Int,
|
||||
|
||||
@field:SerializedName("shipment_status")
|
||||
val shipmentStatus: String,
|
||||
|
||||
@field:SerializedName("order_items")
|
||||
val orderItems: List<OrderItemsItem>,
|
||||
|
||||
@field:SerializedName("is_store_location")
|
||||
val isStoreLocation: Boolean,
|
||||
|
||||
@field:SerializedName("voucher_name")
|
||||
val voucherName: String? = null,
|
||||
|
||||
@field:SerializedName("address_id")
|
||||
val addressId: Int,
|
||||
|
||||
@field:SerializedName("payment_method_id")
|
||||
val paymentMethodId: Int,
|
||||
|
||||
@field:SerializedName("total_amount")
|
||||
val totalAmount: String,
|
||||
|
||||
@field:SerializedName("user_id")
|
||||
val userId: Int,
|
||||
|
||||
@field:SerializedName("province_id")
|
||||
val provinceId: Int,
|
||||
|
||||
@field:SerializedName("courier")
|
||||
val courier: String,
|
||||
|
||||
@field:SerializedName("subdistrict")
|
||||
val subdistrict: String,
|
||||
|
||||
@field:SerializedName("service")
|
||||
val service: String,
|
||||
|
||||
@field:SerializedName("shipment_price")
|
||||
val shipmentPrice: String,
|
||||
|
||||
@field:SerializedName("voucher_id")
|
||||
val voucherId: Int? = null,
|
||||
|
||||
@field:SerializedName("detail")
|
||||
val detail: String,
|
||||
|
||||
@field:SerializedName("postal_code")
|
||||
val postalCode: String,
|
||||
|
||||
@field:SerializedName("order_id")
|
||||
val orderId: Int,
|
||||
|
||||
@field:SerializedName("city_id")
|
||||
val cityId: Int
|
||||
@field:SerializedName("product_name")
|
||||
val productName: String
|
||||
)
|
||||
|
||||
data class OrdersItem(
|
||||
|
||||
@field:SerializedName("receipt_num")
|
||||
val receiptNum: Int? = null,
|
||||
|
||||
@field:SerializedName("latitude")
|
||||
val latitude: String,
|
||||
|
||||
@field:SerializedName("created_at")
|
||||
val createdAt: String,
|
||||
|
||||
@field:SerializedName("voucher_code")
|
||||
val voucherCode: String? = null,
|
||||
|
||||
@field:SerializedName("updated_at")
|
||||
val updatedAt: String,
|
||||
|
||||
@field:SerializedName("etd")
|
||||
val etd: String,
|
||||
|
||||
@field:SerializedName("street")
|
||||
val street: String,
|
||||
|
||||
@field:SerializedName("cancel_date")
|
||||
val cancelDate: String? = null,
|
||||
|
||||
@field:SerializedName("longitude")
|
||||
val longitude: String,
|
||||
|
||||
@field:SerializedName("shipment_status")
|
||||
val shipmentStatus: String,
|
||||
|
||||
@field:SerializedName("order_items")
|
||||
val orderItems: List<OrderItemsItem>,
|
||||
|
||||
@field:SerializedName("auto_completed_at")
|
||||
val autoCompletedAt: String? = null,
|
||||
|
||||
@field:SerializedName("is_store_location")
|
||||
val isStoreLocation: Boolean? = null,
|
||||
|
||||
@field:SerializedName("voucher_name")
|
||||
val voucherName: String? = null,
|
||||
|
||||
@field:SerializedName("address_id")
|
||||
val addressId: Int,
|
||||
|
||||
@field:SerializedName("cancel_reason")
|
||||
val cancelReason: String? = null,
|
||||
|
||||
@field:SerializedName("total_amount")
|
||||
val totalAmount: String,
|
||||
|
||||
@field:SerializedName("user_id")
|
||||
val userId: Int,
|
||||
|
||||
@field:SerializedName("province_id")
|
||||
val provinceId: Int,
|
||||
|
||||
@field:SerializedName("courier")
|
||||
val courier: String,
|
||||
|
||||
@field:SerializedName("subdistrict")
|
||||
val subdistrict: String,
|
||||
|
||||
@field:SerializedName("service")
|
||||
val service: String,
|
||||
|
||||
@field:SerializedName("shipment_price")
|
||||
val shipmentPrice: String,
|
||||
|
||||
@field:SerializedName("voucher_id")
|
||||
val voucherId: Int? = null,
|
||||
|
||||
@field:SerializedName("payment_info_id")
|
||||
val paymentInfoId: Int? = null,
|
||||
|
||||
@field:SerializedName("detail")
|
||||
val detail: String,
|
||||
|
||||
@field:SerializedName("postal_code")
|
||||
val postalCode: String,
|
||||
|
||||
@field:SerializedName("order_id")
|
||||
val orderId: Int,
|
||||
|
||||
@field:SerializedName("city_id")
|
||||
val cityId: Int
|
||||
)
|
||||
|
@ -13,6 +13,8 @@ data class DetailStoreProductResponse(
|
||||
|
||||
data class PaymentInfoItem(
|
||||
|
||||
val id: Int = 1,
|
||||
|
||||
@field:SerializedName("qris_image")
|
||||
val qrisImage: String,
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
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.CompletedOrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.LoginRequest
|
||||
@ -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.ListCartResponse
|
||||
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.CreateOrderResponse
|
||||
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.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.CategoryResponse
|
||||
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.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
@ -93,6 +101,29 @@ interface ApiService {
|
||||
@Body request: OrderRequest
|
||||
): 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")
|
||||
suspend fun postOrderBuyNow(
|
||||
@Body request: OrderRequestBuy
|
||||
@ -180,4 +211,29 @@ interface ApiService {
|
||||
suspend fun getOrdersByStatus(
|
||||
@Query("status") status: String
|
||||
): 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
|
||||
|
||||
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.CreateAddressRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItem
|
||||
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.ListCityResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreProduct
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse
|
||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||
import com.alya.ecommerce_serang.data.api.response.cart.DataItem
|
||||
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.order.CourierCostResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.ListCityResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.ListProvinceResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.OrderDetailResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.order.OrderListResponse
|
||||
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 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 java.io.File
|
||||
|
||||
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 {
|
||||
val response = apiService.createAddress(createAddressRequest)
|
||||
if (response.isSuccessful){
|
||||
response.body()?.let {
|
||||
Result.Success(it)
|
||||
} ?: Result.Error(Exception("Add Address failed"))
|
||||
Log.d("OrderRepository", "Adding address: $request")
|
||||
val response = apiService.createAddress(request)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val createAddressResponse = response.body()
|
||||
if (createAddressResponse != null) {
|
||||
Log.d("OrderRepository", "Address added successfully: ${createAddressResponse.message}")
|
||||
Result.Success(createAddressResponse)
|
||||
} else {
|
||||
Log.e("OrderRepository", "Response body was null")
|
||||
Result.Error(Exception("Empty response from server"))
|
||||
}
|
||||
} else {
|
||||
Log.e("OrderRepository", "Error: ${response.errorBody()?.string()}")
|
||||
Result.Error(Exception(response.errorBody()?.string() ?: "Unknown error"))
|
||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
||||
Log.e("OrderRepository", "Error adding address: $errorBody")
|
||||
Result.Error(Exception(errorBody))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("OrderRepository", "Exception adding address", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
@ -238,4 +262,203 @@ class OrderRepository(private val apiService: ApiService) {
|
||||
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.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.data.api.dto.CheckoutData
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
|
||||
@ -42,6 +46,7 @@ class CheckoutActivity : AppCompatActivity() {
|
||||
|
||||
sessionManager = SessionManager(this)
|
||||
|
||||
|
||||
// Setup UI components
|
||||
setupToolbar()
|
||||
setupObservers()
|
||||
@ -74,6 +79,11 @@ class CheckoutActivity : AppCompatActivity() {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.getPaymentMethods { paymentMethods ->
|
||||
// Logging is just for debugging
|
||||
Log.d("CheckoutActivity", "Loaded ${paymentMethods.size} payment methods")
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupToolbar() {
|
||||
@ -87,13 +97,6 @@ class CheckoutActivity : AppCompatActivity() {
|
||||
viewModel.checkoutData.observe(this) { data ->
|
||||
setupProductRecyclerView(data)
|
||||
updateOrderSummary()
|
||||
|
||||
// Load payment methods
|
||||
viewModel.getPaymentMethods { paymentMethods ->
|
||||
if (paymentMethods.isNotEmpty()) {
|
||||
setupPaymentMethodsRecyclerView(paymentMethods)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Observe address details
|
||||
@ -102,14 +105,24 @@ class CheckoutActivity : AppCompatActivity() {
|
||||
binding.tvAddress.text = "${address?.street}, ${address?.subdistrict}"
|
||||
}
|
||||
|
||||
// Observe payment details
|
||||
viewModel.paymentDetails.observe(this) { payment ->
|
||||
if (payment != null) {
|
||||
// Update selected payment in adapter by name instead of ID
|
||||
paymentAdapter?.setSelectedPaymentName(payment.name)
|
||||
viewModel.availablePaymentMethods.observe(this) { paymentMethods ->
|
||||
if (paymentMethods.isNotEmpty()) {
|
||||
setupPaymentMethodsRecyclerView(paymentMethods)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
viewModel.isLoading.observe(this) { 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) {
|
||||
val adapter = if (checkoutData.isBuyNow || checkoutData.cartItems.size <= 1) {
|
||||
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() {
|
||||
viewModel.checkoutData.value?.let { data ->
|
||||
// Update price information
|
||||
@ -251,6 +296,9 @@ class CheckoutActivity : AppCompatActivity() {
|
||||
val addressId = result.data?.getIntExtra(AddressActivity.EXTRA_ADDRESS_ID, 0) ?: 0
|
||||
if (addressId > 0) {
|
||||
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
|
||||
if (viewModel.paymentDetails.value == null) {
|
||||
if (viewModel.selectedPayment.value == null) {
|
||||
Toast.makeText(this, "Silakan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
|
||||
return false
|
||||
}
|
||||
|
@ -24,8 +24,12 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
||||
private val _addressDetails = MutableLiveData<AddressesItem?>()
|
||||
val addressDetails: LiveData<AddressesItem?> = _addressDetails
|
||||
|
||||
private val _paymentDetails = MutableLiveData<PaymentInfoItem?>()
|
||||
val paymentDetails: LiveData<PaymentInfoItem?> = _paymentDetails
|
||||
private val _availablePaymentMethods = MutableLiveData<List<PaymentInfoItem>>()
|
||||
val availablePaymentMethods: LiveData<List<PaymentInfoItem>> = _availablePaymentMethods
|
||||
|
||||
// Selected payment method
|
||||
private val _selectedPayment = MutableLiveData<PaymentInfoItem?>()
|
||||
val selectedPayment: LiveData<PaymentInfoItem?> = _selectedPayment
|
||||
|
||||
private val _isLoading = MutableLiveData<Boolean>()
|
||||
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) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
@ -154,17 +157,78 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
|
||||
val storeResult = repository.fetchStoreDetail(storeId)
|
||||
|
||||
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 {
|
||||
_availablePaymentMethods.value = emptyList()
|
||||
callback(emptyList())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error fetching payment methods", e)
|
||||
_availablePaymentMethods.value = 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
|
||||
fun setSelectedAddress(addressId: Int) {
|
||||
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
|
||||
fun createOrder() {
|
||||
viewModelScope.launch {
|
||||
|
@ -14,8 +14,8 @@ class PaymentMethodAdapter(
|
||||
private val onPaymentSelected: (PaymentInfoItem) -> Unit
|
||||
) : RecyclerView.Adapter<PaymentMethodAdapter.PaymentMethodViewHolder>() {
|
||||
|
||||
// Track the selected position
|
||||
private var selectedPosition = -1
|
||||
// Selected payment name
|
||||
private var selectedPaymentName: String? = null
|
||||
|
||||
class PaymentMethodViewHolder(val binding: ItemPaymentMethodBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
@ -38,14 +38,23 @@ class PaymentMethodAdapter(
|
||||
// Set payment method name
|
||||
tvPaymentMethodName.text = payment.name
|
||||
|
||||
// Set radio button state
|
||||
rbPaymentMethod.isChecked = selectedPosition == position
|
||||
// // Set bank account number if available
|
||||
// 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
|
||||
if (payment.qrisImage.isNotEmpty()) {
|
||||
if (!payment.qrisImage.isNullOrEmpty()) {
|
||||
Glide.with(ivPaymentMethod.context)
|
||||
.load(payment.qrisImage)
|
||||
.apply(RequestOptions()
|
||||
.apply(
|
||||
RequestOptions()
|
||||
.placeholder(R.drawable.outline_store_24)
|
||||
.error(R.drawable.outline_store_24))
|
||||
.into(ivPaymentMethod)
|
||||
@ -56,35 +65,21 @@ class PaymentMethodAdapter(
|
||||
|
||||
// Handle click on the entire item
|
||||
root.setOnClickListener {
|
||||
selectPayment(position)
|
||||
onPaymentSelected(payment)
|
||||
setSelectedPaymentName(payment.name)
|
||||
}
|
||||
|
||||
// Handle click on the radio button
|
||||
rbPaymentMethod.setOnClickListener {
|
||||
selectPayment(position)
|
||||
onPaymentSelected(payment)
|
||||
setSelectedPaymentName(payment.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to handle payment selection
|
||||
private fun selectPayment(position: Int) {
|
||||
if (selectedPosition != position) {
|
||||
val previousPosition = selectedPosition
|
||||
selectedPosition = position
|
||||
|
||||
// Update UI for previous and new selection
|
||||
notifyItemChanged(previousPosition)
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
|
||||
//selected by name
|
||||
// Set selected payment by name and refresh the UI
|
||||
fun setSelectedPaymentName(paymentName: String) {
|
||||
val position = paymentMethods.indexOfFirst { it.name == paymentName }
|
||||
if (position != -1 && position != selectedPosition) {
|
||||
selectPayment(position)
|
||||
}
|
||||
selectedPaymentName = paymentName
|
||||
notifyDataSetChanged() // Update all items to reflect selection change
|
||||
}
|
||||
}
|
@ -2,10 +2,11 @@ package com.alya.ecommerce_serang.ui.order
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
@ -18,6 +19,7 @@ class ShippingActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityShippingBinding
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private lateinit var shippingAdapter: ShippingAdapter
|
||||
private val TAG = "ShippingActivity"
|
||||
|
||||
private val viewModel: ShippingViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
@ -40,8 +42,11 @@ class ShippingActivity : AppCompatActivity() {
|
||||
val productId = intent.getIntExtra(EXTRA_PRODUCT_ID, 0)
|
||||
val quantity = intent.getIntExtra(EXTRA_QUANTITY, 1)
|
||||
|
||||
Log.d(TAG, "Received data: addressId=$addressId, productId=$productId, quantity=$quantity")
|
||||
|
||||
// Validate required information
|
||||
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()
|
||||
finish()
|
||||
return
|
||||
@ -51,9 +56,10 @@ class ShippingActivity : AppCompatActivity() {
|
||||
setupToolbar()
|
||||
setupRecyclerView()
|
||||
setupObservers()
|
||||
setupRetryButton() // Add a retry button for error cases
|
||||
|
||||
// Load shipping options
|
||||
viewModel.loadShippingOptions(addressId, productId, quantity)
|
||||
loadShippingOptions(addressId, productId, quantity)
|
||||
}
|
||||
|
||||
private fun setupToolbar() {
|
||||
@ -65,6 +71,7 @@ class ShippingActivity : AppCompatActivity() {
|
||||
private fun setupRecyclerView() {
|
||||
shippingAdapter = ShippingAdapter { courierCostsItem, service ->
|
||||
// Handle shipping method selection
|
||||
Log.d(TAG, "Selected shipping: ${courierCostsItem.courier} - ${service.service} - ${service.cost} - ${service.etd}")
|
||||
returnSelectedShipping(
|
||||
courierCostsItem.courier,
|
||||
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() {
|
||||
// Observe shipping options
|
||||
viewModel.shippingOptions.observe(this) { courierOptions ->
|
||||
Log.d(TAG, "Received ${courierOptions.size} shipping options")
|
||||
shippingAdapter.submitList(courierOptions)
|
||||
updateEmptyState(courierOptions.isEmpty() || courierOptions.all { it.services.isEmpty() })
|
||||
}
|
||||
|
||||
// Observe loading state
|
||||
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
|
||||
viewModel.errorMessage.observe(this) { message ->
|
||||
if (message.isNotEmpty()) {
|
||||
Log.e(TAG, "Error: $message")
|
||||
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) {
|
||||
// binding.layoutEmptyShipping.isVisible = isEmpty
|
||||
binding.rvShipmentOrder.isVisible = !isEmpty
|
||||
Log.d(TAG, "Updating empty state: isEmpty=$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(
|
||||
@ -116,11 +159,13 @@ class ShippingActivity : AppCompatActivity() {
|
||||
putExtra(EXTRA_SHIP_PRICE, shipPrice)
|
||||
putExtra(EXTRA_SHIP_ETD, shipEtd)
|
||||
}
|
||||
Log.d(TAG, "Returning selected shipping: name=$shipName, service=$shipService, price=$shipPrice, etd=$shipEtd")
|
||||
setResult(RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
// Constants for intent extras
|
||||
const val EXTRA_ADDRESS_ID = "extra_address_id"
|
||||
const val EXTRA_PRODUCT_ID = "extra_product_id"
|
||||
|
@ -36,12 +36,14 @@ class ShippingViewModel(
|
||||
_errorMessage.value = ""
|
||||
|
||||
// Prepare the request
|
||||
val costProduct = CostProduct(
|
||||
productId = productId,
|
||||
quantity = quantity
|
||||
)
|
||||
|
||||
val request = CourierCostRequest(
|
||||
addressId = addressId,
|
||||
itemCost = CostProduct(
|
||||
productId = productId,
|
||||
quantity = quantity
|
||||
)
|
||||
itemCost = listOf(costProduct) // Wrap in a list
|
||||
)
|
||||
|
||||
viewModelScope.launch {
|
||||
|
@ -1,18 +1,22 @@
|
||||
package com.alya.ecommerce_serang.ui.order.address
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.Intent
|
||||
import android.location.Criteria
|
||||
import android.location.Location
|
||||
import android.location.LocationListener
|
||||
import android.location.LocationManager
|
||||
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 androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||
import com.alya.ecommerce_serang.data.api.response.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.ApiService
|
||||
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.utils.SavedStateViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AddAddressActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityAddAddressBinding
|
||||
private lateinit var apiService: ApiService
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private lateinit var profileUser: UserProfile
|
||||
private var profileUser: Int = 1
|
||||
private lateinit var locationManager: LocationManager
|
||||
|
||||
private var isRequestingLocation = false
|
||||
|
||||
private var latitude: Double? = null
|
||||
private var longitude: Double? = null
|
||||
private val provinceAdapter by lazy { ProvinceAdapter(this) }
|
||||
@ -41,7 +47,8 @@ class AddAddressActivity : AppCompatActivity() {
|
||||
SavedStateViewModelFactory(this) { savedStateHandle ->
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val orderRepository = OrderRepository(apiService)
|
||||
AddAddressViewModel(orderRepository, savedStateHandle)
|
||||
val userRepository = UserRepository(apiService)
|
||||
AddAddressViewModel(orderRepository, userRepository, savedStateHandle)
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,45 +61,79 @@ class AddAddressActivity : AppCompatActivity() {
|
||||
apiService = ApiConfig.getApiService(sessionManager)
|
||||
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
||||
|
||||
// Get user profile from session manager
|
||||
// profileUser =UserProfile.
|
||||
viewModel.userProfile.observe(this){ user ->
|
||||
user?.let { updateProfile(it) }
|
||||
}
|
||||
|
||||
setupToolbar()
|
||||
requestLocationPermission()
|
||||
setupReloadButtons()
|
||||
setupAutoComplete()
|
||||
setupButtonListeners()
|
||||
collectFlows()
|
||||
requestLocationPermission()
|
||||
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
|
||||
private fun setupToolbar() {
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
private fun setupToolbar() {
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAutoComplete() {
|
||||
Log.d(TAG, "Setting up AutoComplete dropdowns")
|
||||
// Set adapters
|
||||
binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
|
||||
binding.autoCompleteKabupaten.setAdapter(cityAdapter)
|
||||
|
||||
// Set listeners
|
||||
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
|
||||
provinceAdapter.getProvinceId(position)?.let { provinceId ->
|
||||
viewModel.getCities(provinceId)
|
||||
binding.autoCompleteKabupaten.text.clear()
|
||||
// Make dropdown appear on click (not just when typing)
|
||||
binding.autoCompleteProvinsi.setOnClickListener {
|
||||
Log.d(TAG, "Province dropdown clicked, showing dropdown")
|
||||
binding.autoCompleteProvinsi.showDropDown()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
// Set listeners for selection
|
||||
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
|
||||
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, _ ->
|
||||
cityAdapter.getCityId(position)?.let { cityId ->
|
||||
viewModel.selectedCityId = cityId
|
||||
}
|
||||
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() {
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
viewModel.provincesState.collect { state ->
|
||||
handleProvinceState(state)
|
||||
}
|
||||
}
|
||||
private fun setupObservers() {
|
||||
Log.d(TAG, "Setting up LiveData observers")
|
||||
|
||||
launch {
|
||||
viewModel.citiesState.collect { state ->
|
||||
handleCityState(state)
|
||||
}
|
||||
}
|
||||
// Observe provinces
|
||||
viewModel.provincesState.observe(this) { state ->
|
||||
Log.d(TAG, "Received provincesState update: $state")
|
||||
handleProvinceState(state)
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.addressSubmissionState.collect { state ->
|
||||
handleAddressSubmissionState(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Observe cities
|
||||
viewModel.citiesState.observe(this) { state ->
|
||||
Log.d(TAG, "Received citiesState update: $state")
|
||||
handleCityState(state)
|
||||
}
|
||||
|
||||
// Observe address submission
|
||||
viewModel.addressSubmissionState.observe(this) { state ->
|
||||
Log.d(TAG, "Received addressSubmissionState update: $state")
|
||||
handleAddressSubmissionState(state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleProvinceState(state: ViewState<List<ProvincesItem>>) {
|
||||
when (state) {
|
||||
is ViewState.Loading -> null //showProvinceLoading(true)
|
||||
is ViewState.Loading -> {
|
||||
Log.d("AddAddressActivity", "Loading provinces...")
|
||||
// Show loading indicator
|
||||
}
|
||||
is ViewState.Success -> {
|
||||
provinceAdapter.updateData(state.data)
|
||||
Log.d("AddAddressActivity", "Provinces loaded: ${state.data.size}")
|
||||
// Hide loading indicator
|
||||
if (state.data.isNotEmpty()) {
|
||||
provinceAdapter.updateData(state.data)
|
||||
} else {
|
||||
showError("No provinces available")
|
||||
}
|
||||
}
|
||||
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>>) {
|
||||
when (state) {
|
||||
is ViewState.Loading -> null //showCityLoading(true)
|
||||
is ViewState.Loading -> {
|
||||
Log.d("AddAddressActivity", "Loading cities...")
|
||||
binding.cityProgressBar.visibility = View.VISIBLE
|
||||
}
|
||||
is ViewState.Success -> {
|
||||
// showCityLoading(false)
|
||||
Log.d("AddAddressActivity", "Cities loaded: ${state.data.size}")
|
||||
binding.cityProgressBar.visibility = View.GONE
|
||||
cityAdapter.updateData(state.data)
|
||||
}
|
||||
is ViewState.Error -> {
|
||||
// showCityLoading(false)
|
||||
showError(state.message)
|
||||
binding.cityProgressBar.visibility = View.GONE
|
||||
showError("Failed to load cities: ${state.message}")
|
||||
Log.e("AddAddressActivity", "City error: ${state.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAddressSubmissionState(state: ViewState<String>) {
|
||||
when (state) {
|
||||
is ViewState.Loading -> showSubmitLoading(true)
|
||||
is ViewState.Loading -> {
|
||||
Log.d(TAG, "Address submission: Loading")
|
||||
showSubmitLoading(true)
|
||||
}
|
||||
is ViewState.Success -> {
|
||||
Log.d(TAG, "Address submission: Success - ${state.data}")
|
||||
showSubmitLoading(false)
|
||||
showSuccessAndFinish(state.data)
|
||||
}
|
||||
is ViewState.Error -> {
|
||||
Log.e(TAG, "Address submission: Error - ${state.message}")
|
||||
showSubmitLoading(false)
|
||||
showError(state.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSubmitLoading(isLoading: Boolean) {
|
||||
binding.buttonSimpan.isEnabled = !isLoading
|
||||
binding.buttonSimpan.text = if (isLoading) "Menyimpan..." else "Simpan"
|
||||
// You might want to show a progress bar as well
|
||||
// binding.submitProgressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun showError(message: String) {
|
||||
@ -177,47 +238,83 @@ private fun setupToolbar() {
|
||||
|
||||
private fun showSuccessAndFinish(message: String) {
|
||||
Toast.makeText(this, "Sukses: $message", Toast.LENGTH_SHORT).show()
|
||||
onBackPressed()
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun validateAndSubmitForm() {
|
||||
val lat = latitude
|
||||
val long = longitude
|
||||
Log.d(TAG, "Validating form...")
|
||||
Log.d(TAG, "Current location: lat=$latitude, long=$longitude")
|
||||
|
||||
if (lat == null || long == null) {
|
||||
showError("Lokasi belum terdeteksi")
|
||||
return
|
||||
// Check if we have location - always use default if not available
|
||||
if (latitude == null || longitude == null) {
|
||||
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 subDistrict = binding.etKecamatan.text.toString()
|
||||
val postalCode = binding.etKodePos.text.toString()
|
||||
val recipient = binding.etNamaPenerima.text.toString()
|
||||
val phone = binding.etNomorHp.text.toString()
|
||||
val userId = profileUser.userId
|
||||
val street = binding.etDetailAlamat.text.toString().trim()
|
||||
val subDistrict = binding.etKecamatan.text.toString().trim()
|
||||
val postalCode = binding.etKodePos.text.toString().trim()
|
||||
val recipient = binding.etNamaPenerima.text.toString().trim()
|
||||
val phone = binding.etNomorHp.text.toString().trim()
|
||||
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 provinceId = viewModel.selectedProvinceId
|
||||
val cityId = viewModel.selectedCityId
|
||||
|
||||
if (street.isBlank() || recipient.isBlank() || phone.isBlank()) {
|
||||
showError("Lengkapi semua field wajib")
|
||||
Log.d(TAG, "Form data: street=$street, subDistrict=$subDistrict, postalCode=$postalCode, " +
|
||||
"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
|
||||
}
|
||||
|
||||
if (provinceId == null) {
|
||||
Log.w(TAG, "Validation failed: provinceId is null")
|
||||
showError("Pilih provinsi terlebih dahulu")
|
||||
binding.autoCompleteProvinsi.requestFocus()
|
||||
return
|
||||
}
|
||||
|
||||
if (cityId == null) {
|
||||
Log.w(TAG, "Validation failed: cityId is null")
|
||||
showError("Pilih kota/kabupaten terlebih dahulu")
|
||||
binding.autoCompleteKabupaten.requestFocus()
|
||||
return
|
||||
}
|
||||
|
||||
// Create request with all fields
|
||||
val request = CreateAddressRequest(
|
||||
lat = lat,
|
||||
long = long,
|
||||
lat = latitude!!, // Safe to use !! as we've checked above
|
||||
long = longitude!!,
|
||||
street = street,
|
||||
subDistrict = subDistrict,
|
||||
cityId = cityId,
|
||||
@ -230,12 +327,17 @@ private fun setupToolbar() {
|
||||
isStoreLocation = isStoreLocation
|
||||
)
|
||||
|
||||
Log.d(TAG, "Form validation successful, submitting address: $request")
|
||||
viewModel.addAddress(request)
|
||||
}
|
||||
|
||||
private val locationPermissionLauncher =
|
||||
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() {
|
||||
@ -244,36 +346,164 @@ private fun setupToolbar() {
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun requestLocation() {
|
||||
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
||||
val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||
Log.d(TAG, "Requesting device location")
|
||||
|
||||
if (!isGpsEnabled && !isNetworkEnabled) {
|
||||
Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show()
|
||||
// Check if we're already requesting location to avoid multiple requests
|
||||
if (isRequestingLocation) {
|
||||
Log.w(TAG, "Location request already in progress")
|
||||
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) {
|
||||
Log.d(TAG, "onLocationChanged called: lat=${location.latitude}, long=${location.longitude}")
|
||||
latitude = location.latitude
|
||||
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) {
|
||||
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) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == 100 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
requestLocation()
|
||||
} else {
|
||||
Toast.makeText(this, "Location permission denied", Toast.LENGTH_SHORT).show()
|
||||
try {
|
||||
// Request location updates
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AddAddressViewModel"
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,35 @@
|
||||
package com.alya.ecommerce_serang.ui.order.address
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||
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.repository.OrderRepository
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AddAddressViewModel(private val repository: OrderRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
|
||||
// Flow states for data
|
||||
private val _addressSubmissionState = MutableStateFlow<ViewState<String>>(ViewState.Loading)
|
||||
val addressSubmissionState = _addressSubmissionState.asStateFlow()
|
||||
class AddAddressViewModel(private val repository: OrderRepository, private val userRepo: UserRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
|
||||
private val _addressSubmissionState = MutableLiveData<ViewState<String>>()
|
||||
val addressSubmissionState: LiveData<ViewState<String>> = _addressSubmissionState
|
||||
|
||||
private val _provincesState = MutableStateFlow<ViewState<List<ProvincesItem>>>(ViewState.Loading)
|
||||
val provincesState = _provincesState.asStateFlow()
|
||||
private val _userProfile = MutableLiveData<UserProfile?>()
|
||||
val userProfile: LiveData<UserProfile?> = _userProfile
|
||||
|
||||
private val _citiesState = MutableStateFlow<ViewState<List<CitiesItem>>>(ViewState.Loading)
|
||||
val citiesState = _citiesState.asStateFlow()
|
||||
private val _errorMessageUser = MutableLiveData<String>()
|
||||
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
|
||||
var selectedProvinceId: Int?
|
||||
@ -38,47 +45,82 @@ class AddAddressViewModel(private val repository: OrderRepository, private val s
|
||||
getProvinces()
|
||||
}
|
||||
|
||||
fun addAddress(request: CreateAddressRequest){
|
||||
fun addAddress(request: CreateAddressRequest) {
|
||||
Log.d(TAG, "Starting address submission process")
|
||||
_addressSubmissionState.value = ViewState.Loading
|
||||
viewModelScope.launch {
|
||||
when (val result = repository.addAddress(request)) {
|
||||
is Result.Success -> {
|
||||
val message = result.data.message // Ambil `message` dari CreateAddressResponse
|
||||
_addressSubmissionState.value = ViewState.Success(message)
|
||||
}
|
||||
is Result.Error -> {
|
||||
_addressSubmissionState.value =
|
||||
ViewState.Error(result.exception.message ?: "Unknown error")
|
||||
}
|
||||
is Result.Loading -> {
|
||||
// Optional, karena sudah set Loading di awal
|
||||
try {
|
||||
Log.d(TAG, "Calling repository.addAddress with request: $request")
|
||||
val result = repository.addAddress(request)
|
||||
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
val message = result.data.message
|
||||
Log.d(TAG, "Address added successfully: $message")
|
||||
_addressSubmissionState.postValue(ViewState.Success(message))
|
||||
}
|
||||
is Result.Error -> {
|
||||
val errorMsg = result.exception.message ?: "Unknown error"
|
||||
Log.e(TAG, "Error from repository: $errorMsg", result.exception)
|
||||
_addressSubmissionState.postValue(ViewState.Error(errorMsg))
|
||||
}
|
||||
is Result.Loading -> {
|
||||
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 {
|
||||
try {
|
||||
val result = repository.getListProvinces()
|
||||
result?.let {
|
||||
_provincesState.value = ViewState.Success(it.provinces)
|
||||
if (result?.provinces != null) {
|
||||
_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) {
|
||||
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){
|
||||
_citiesState.value = ViewState.Loading
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
selectedProvinceId = provinceId
|
||||
val result = repository.getListCities(provinceId)
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
private const val TAG = "AddAddressViewModel"
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||
import com.alya.ecommerce_serang.databinding.ActivityAddressBinding
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
@ -14,7 +14,6 @@ import com.alya.ecommerce_serang.utils.SessionManager
|
||||
|
||||
class AddressActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityAddressBinding
|
||||
private lateinit var apiService: ApiService
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private lateinit var adapter: AddressAdapter
|
||||
|
||||
@ -26,55 +25,82 @@ class AddressActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityAddressBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
sessionManager = SessionManager(this)
|
||||
apiService = ApiConfig.getApiService(sessionManager)
|
||||
|
||||
|
||||
setupToolbar()
|
||||
setupRecyclerView()
|
||||
setupObservers()
|
||||
|
||||
adapter = AddressAdapter { selectedId ->
|
||||
viewModel.selectAddress(selectedId)
|
||||
viewModel.fetchAddresses()
|
||||
}
|
||||
|
||||
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 {
|
||||
onBackPressedWithResult()
|
||||
}
|
||||
}
|
||||
|
||||
binding.rvSellerOrder.layoutManager = LinearLayoutManager(this)
|
||||
binding.rvSellerOrder.adapter = adapter
|
||||
private fun setupRecyclerView() {
|
||||
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 ->
|
||||
adapter.submitList(addressList)
|
||||
|
||||
// Show empty state if needed
|
||||
// binding.emptyView?.isVisible = addressList.isEmpty()
|
||||
binding.rvSellerOrder.isVisible = addressList.isNotEmpty()
|
||||
}
|
||||
|
||||
viewModel.selectedAddressId.observe(this) { selectedId ->
|
||||
adapter.setSelectedAddressId(selectedId)
|
||||
}
|
||||
}
|
||||
private fun setupToolbar() {
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
|
||||
private fun onBackPressedWithResult() {
|
||||
// If an address is selected, return it as result
|
||||
val selectedId = viewModel.selectedAddressId.value
|
||||
if (selectedId != null) {
|
||||
returnResultAndFinish(selectedId)
|
||||
finish()
|
||||
} else {
|
||||
// No selection, just finish
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
// private fun updateEmptyState(isEmpty: Boolean) {
|
||||
// binding.layoutEmptyAddresses.isVisible = isEmpty
|
||||
// binding.rvAddresses.isVisible = !isEmpty
|
||||
// }
|
||||
|
||||
private fun onBackPressedWithResult() {
|
||||
viewModel.selectedAddressId.value?.let {
|
||||
val intent = Intent()
|
||||
intent.putExtra(EXTRA_ADDRESS_ID, it)
|
||||
setResult(RESULT_OK, intent)
|
||||
}
|
||||
finish()
|
||||
private fun returnResultAndFinish(addressId: Int) {
|
||||
val intent = Intent()
|
||||
intent.putExtra(EXTRA_ADDRESS_ID, addressId)
|
||||
setResult(RESULT_OK, intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -13,14 +13,25 @@ import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressesIte
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
||||
class AddressAdapter(
|
||||
private val onAddressClick: (Int) -> Unit
|
||||
private val onAddressClick: (AddressesItem) -> Unit
|
||||
) : ListAdapter<AddressesItem, AddressAdapter.AddressViewHolder>(DIFF_CALLBACK) {
|
||||
|
||||
private var selectedAddressId: Int? = null
|
||||
|
||||
fun setSelectedAddressId(id: Int?) {
|
||||
val oldSelectedId = selectedAddressId
|
||||
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 {
|
||||
@ -33,7 +44,8 @@ class AddressAdapter(
|
||||
val address = getItem(position)
|
||||
holder.bind(address, selectedAddressId == address.id)
|
||||
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
|
||||
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(
|
||||
ContextCompat.getColor(
|
||||
itemView.context,
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.alya.ecommerce_serang.ui.order.address
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
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.ProvincesItem
|
||||
@ -20,6 +21,8 @@ class ProvinceAdapter(
|
||||
clear()
|
||||
addAll(provinces.map { it.province })
|
||||
notifyDataSetChanged()
|
||||
|
||||
Log.d("ProvinceAdapter", "Updated with ${provinces.size} provinces")
|
||||
}
|
||||
|
||||
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.repository.UserRepository
|
||||
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.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
@ -63,6 +64,16 @@ class ProfileFragment : Fragment() {
|
||||
val intentDetail = Intent(requireContext(), DetailProfileActivity::class.java)
|
||||
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() {
|
||||
|
@ -52,23 +52,6 @@ class HomeViewModel (
|
||||
loadProducts()
|
||||
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 {
|
||||
|
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_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:focusable="false"
|
||||
android:clickable="true"
|
||||
android:padding="12dp"
|
||||
android:textSize="14sp" />
|
||||
</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 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
@ -138,11 +149,20 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:hint="Masukkan Kabupaten"
|
||||
android:focusable="false"
|
||||
android:clickable="true"
|
||||
android:padding="12dp"
|
||||
android:textSize="14sp" />
|
||||
</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 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
@ -188,6 +208,8 @@
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonSimpan"
|
||||
android:layout_width="match_parent"
|
||||
@ -200,5 +222,60 @@
|
||||
android:textSize="16sp"
|
||||
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>
|
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 " />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/add_address_click"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="textEnd"
|
||||
|
@ -287,8 +287,7 @@
|
||||
android:id="@+id/rv_payment_methods"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_payment_method"
|
||||
tools:itemCount="2" />
|
||||
tools:listitem="@layout/item_payment_method" />
|
||||
</LinearLayout>
|
||||
|
||||
<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" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linear_shipment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
@ -29,4 +30,13 @@
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_shipping_order"/>
|
||||
</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>
|
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="beli_sekarang">Beli Sekarang</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>
|
@ -7,4 +7,22 @@
|
||||
<item name="android:padding">12dp</item>
|
||||
<!-- Add more style attributes as needed -->
|
||||
</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>
|
Reference in New Issue
Block a user