diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index b268ef3..199a303 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,6 +4,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 24db662..f2c8352 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -41,6 +41,18 @@
+
+
+
+
@@ -52,7 +64,9 @@
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync" />
-
+
@@ -104,12 +118,6 @@
-
-
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/City.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/City.kt
new file mode 100644
index 0000000..f8160a2
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/City.kt
@@ -0,0 +1,31 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class City(
+ @SerializedName("city_id")
+ val cityId: String,
+
+ @SerializedName("city_name")
+ val cityName: String,
+
+ @SerializedName("province_id")
+ val provinceId: String,
+
+ @SerializedName("province")
+ val provinceName: String,
+
+ @SerializedName("type")
+ val type: String,
+
+ @SerializedName("postal_code")
+ val postalCode: String
+)
+
+data class CityResponse(
+ @SerializedName("message")
+ val message: String,
+
+ @SerializedName("cities")
+ val data: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/PaymentInfo.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/PaymentInfo.kt
new file mode 100644
index 0000000..2e5cdab
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/PaymentInfo.kt
@@ -0,0 +1,60 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class PaymentInfo(
+ @SerializedName("id")
+ val id: Int,
+
+ @SerializedName("bank_num")
+ val bankNum: String,
+
+ @SerializedName("bank_name")
+ val bankName: String,
+
+ @SerializedName("qris_image")
+ val qrisImage: String?,
+
+ @SerializedName("account_name")
+ val accountName: String?
+)
+
+data class PaymentInfoResponse(
+ @SerializedName("message")
+ val message: String,
+
+ @SerializedName("payment")
+ val payment: List
+)
+
+data class AddPaymentInfoRequest(
+ @SerializedName("bank_name")
+ val bankName: String,
+
+ @SerializedName("bank_num")
+ val bankNum: String,
+
+ @SerializedName("account_name")
+ val accountName: String
+
+ // qris will be sent as multipart form data
+)
+
+data class DeletePaymentInfoResponse(
+ @SerializedName("message")
+ val message: String,
+
+ @SerializedName("success")
+ val success: Boolean
+)
+
+data class AddPaymentInfoResponse(
+ @SerializedName("message")
+ val message: String,
+
+ @SerializedName("success")
+ val success: Boolean,
+
+ @SerializedName("payment_method")
+ val paymentInfo: PaymentInfo?
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Province.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Province.kt
new file mode 100644
index 0000000..056d517
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Province.kt
@@ -0,0 +1,19 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class Province(
+ @SerializedName("province_id")
+ val provinceId: String,
+
+ @SerializedName("province")
+ val provinceName: String
+)
+
+data class ProvinceResponse(
+ @SerializedName("message")
+ val message: String,
+
+ @SerializedName("provinces")
+ val data: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/ShippingService.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/ShippingService.kt
new file mode 100644
index 0000000..f373219
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/ShippingService.kt
@@ -0,0 +1,13 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class ShippingService(
+ @SerializedName("courier")
+ val courier: String
+)
+
+data class ShippingServiceRequest(
+ @SerializedName("couriers")
+ val couriers: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/StoreAddress.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/StoreAddress.kt
new file mode 100644
index 0000000..f403fc2
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/StoreAddress.kt
@@ -0,0 +1,55 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class StoreAddress(
+ @SerializedName("id")
+ val id: String? = null,
+
+ @SerializedName("store_id")
+ val storeId: String? = null,
+
+ @SerializedName("province_id")
+ val provinceId: String = "",
+
+ @SerializedName("province_name")
+ val provinceName: String = "",
+
+ @SerializedName("city_id")
+ val cityId: String = "",
+
+ @SerializedName("city_name")
+ val cityName: String = "",
+
+ @SerializedName("street")
+ val street: String = "",
+
+ @SerializedName("subdistrict")
+ val subdistrict: String = "",
+
+ @SerializedName("detail")
+ val detail: String? = null,
+
+ @SerializedName("postal_code")
+ val postalCode: String = "",
+
+ @SerializedName("latitude")
+ val latitude: Double? = 0.0,
+
+ @SerializedName("longitude")
+ val longitude: Double? = 0.0,
+
+ @SerializedName("created_at")
+ val createdAt: String? = null,
+
+ @SerializedName("updated_at")
+ val updatedAt: String? = null
+)
+
+data class StoreAddressResponse(
+ @SerializedName("message")
+ val message: String,
+
+ @SerializedName("store")
+ val data: StoreAddress? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/StoreResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/StoreResponse.kt
new file mode 100644
index 0000000..7637538
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/StoreResponse.kt
@@ -0,0 +1,18 @@
+package com.alya.ecommerce_serang.data.api.response.store
+
+import com.google.gson.annotations.SerializedName
+
+data class StoreResponse(
+ val message: String,
+ val store: Store
+)
+
+data class Store(
+ @SerializedName("store_id") val storeId: Int,
+ @SerializedName("store_status") val storeStatus: String,
+ @SerializedName("store_name") val storeName: String,
+ @SerializedName("user_name") val userName: String,
+ val email: String,
+ @SerializedName("user_phone") val userPhone: String,
+ val balance: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/orders/KonfirmasiTagihanResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/orders/KonfirmasiTagihanResponse.kt
index af331d4..2e3df6a 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/orders/KonfirmasiTagihanResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/orders/KonfirmasiTagihanResponse.kt
@@ -1,5 +1,6 @@
package com.alya.ecommerce_serang.data.api.response.store.orders
+import com.alya.ecommerce_serang.data.api.dto.UpdatedOrder
import com.google.gson.annotations.SerializedName
data class KonfirmasiTagihanResponse(
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/orders/OrderListResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/orders/OrderListResponse.kt
index 64725ae..e9b00c3 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/orders/OrderListResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/orders/OrderListResponse.kt
@@ -1,6 +1,5 @@
package com.alya.ecommerce_serang.data.api.response.store.orders
-import com.alya.ecommerce_serang.data.api.dto.OrdersItem
import com.google.gson.annotations.SerializedName
data class OrderListResponse(
@@ -12,43 +11,133 @@ data class OrderListResponse(
val message: String? = null
)
-data class Voucher(
-
- @field:SerializedName("name")
- val name: Any? = null,
-
- @field:SerializedName("voucher_id")
- val voucherId: Any? = null,
-
- @field:SerializedName("voucher_code")
- val voucherCode: Any? = null
-)
-
-data class Shipment(
+data class OrdersItem(
@field:SerializedName("receipt_num")
val receiptNum: Any? = null,
- @field:SerializedName("courier")
- val courier: Any? = null,
+ @field:SerializedName("payment_upload_at")
+ val paymentUploadAt: Any? = null,
- @field:SerializedName("price")
- val price: Any? = null,
+ @field:SerializedName("latitude")
+ val latitude: String? = null,
+
+ @field:SerializedName("pay_info_name")
+ val payInfoName: String? = null,
+
+ @field:SerializedName("created_at")
+ val createdAt: String? = null,
+
+ @field:SerializedName("voucher_code")
+ val voucherCode: Any? = null,
+
+ @field:SerializedName("updated_at")
+ val updatedAt: String? = null,
+
+ @field:SerializedName("etd")
+ val etd: String? = null,
+
+ @field:SerializedName("street")
+ val street: String? = null,
+
+ @field:SerializedName("cancel_date")
+ val cancelDate: String? = null,
+
+ @field:SerializedName("payment_evidence")
+ val paymentEvidence: Any? = null,
+
+ @field:SerializedName("longitude")
+ val longitude: String? = null,
+
+ @field:SerializedName("shipment_status")
+ val shipmentStatus: String? = null,
+
+ @field:SerializedName("order_items")
+ val orderItems: List? = null,
+
+ @field:SerializedName("auto_completed_at")
+ val autoCompletedAt: Any? = null,
+
+ @field:SerializedName("is_store_location")
+ val isStoreLocation: Boolean? = null,
+
+ @field:SerializedName("qris_image")
+ val qrisImage: String? = null,
+
+ @field:SerializedName("voucher_name")
+ val voucherName: Any? = null,
+
+ @field:SerializedName("payment_status")
+ val paymentStatus: Any? = null,
+
+ @field:SerializedName("address_id")
+ val addressId: Int? = null,
+
+ @field:SerializedName("is_negotiable")
+ val isNegotiable: Boolean? = null,
+
+ @field:SerializedName("payment_amount")
+ val paymentAmount: Any? = null,
+
+ @field:SerializedName("cancel_reason")
+ val cancelReason: String? = null,
+
+ @field:SerializedName("user_id")
+ val userId: Int? = null,
+
+ @field:SerializedName("total_amount")
+ val totalAmount: String? = null,
+
+ @field:SerializedName("province_id")
+ val provinceId: Int? = null,
+
+ @field:SerializedName("courier")
+ val courier: String? = null,
+
+ @field:SerializedName("subdistrict")
+ val subdistrict: String? = null,
@field:SerializedName("service")
- val service: Any? = null,
+ val service: String? = null,
- @field:SerializedName("shipment_id")
- val shipmentId: Any? = null,
+ @field:SerializedName("pay_info_num")
+ val payInfoNum: String? = null,
+
+ @field:SerializedName("shipment_price")
+ val shipmentPrice: String? = null,
+
+ @field:SerializedName("voucher_id")
+ val voucherId: Any? = null,
+
+ @field:SerializedName("payment_info_id")
+ val paymentInfoId: Int? = null,
+
+ @field:SerializedName("detail")
+ val detail: String? = null,
+
+ @field:SerializedName("postal_code")
+ val postalCode: String? = null,
+
+ @field:SerializedName("order_id")
+ val orderId: Int? = null,
+
+ @field:SerializedName("username")
+ val username: String? = null,
@field:SerializedName("status")
- val status: Any? = null
+ val status: String? = null,
+
+ @field:SerializedName("city_id")
+ val cityId: Int? = null
)
data class OrderItemsItem(
+ @field:SerializedName("order_item_id")
+ val orderItemId: Int? = null,
+
@field:SerializedName("review_id")
- val reviewId: Int? = null,
+ val reviewId: Any? = null,
@field:SerializedName("quantity")
val quantity: Int? = null,
@@ -62,6 +151,9 @@ data class OrderItemsItem(
@field:SerializedName("product_image")
val productImage: String? = null,
+ @field:SerializedName("product_id")
+ val productId: Int? = null,
+
@field:SerializedName("store_name")
val storeName: String? = null,
@@ -71,48 +163,3 @@ data class OrderItemsItem(
@field:SerializedName("product_name")
val productName: String? = null
)
-
-data class Address(
-
- @field:SerializedName("is_store_location")
- val isStoreLocation: Boolean? = null,
-
- @field:SerializedName("province_id")
- val provinceId: Int? = null,
-
- @field:SerializedName("street")
- val street: String? = null,
-
- @field:SerializedName("subdistrict")
- val subdistrict: String? = null,
-
- @field:SerializedName("latitude")
- val latitude: Any? = null,
-
- @field:SerializedName("address_id")
- val addressId: Int? = null,
-
- @field:SerializedName("detail")
- val detail: String? = null,
-
- @field:SerializedName("postal_code")
- val postalCode: String? = null,
-
- @field:SerializedName("longitude")
- val longitude: Any? = null,
-
- @field:SerializedName("city_id")
- val cityId: Int? = null
-)
-
-data class Payment(
-
- @field:SerializedName("evidence")
- val evidence: Any? = null,
-
- @field:SerializedName("uploaded_at")
- val uploadedAt: Any? = null,
-
- @field:SerializedName("payment_id")
- val paymentId: Any? = null
-)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/orders/UpdateOrderItemResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/orders/UpdateOrderItemResponse.kt
index 9e6b08c..f56b81b 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/orders/UpdateOrderItemResponse.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/orders/UpdateOrderItemResponse.kt
@@ -4,72 +4,27 @@ import com.google.gson.annotations.SerializedName
data class UpdateOrderItemResponse(
- @field:SerializedName("message")
- val message: String? = null,
-
- @field:SerializedName("updatedOrder")
- val updatedOrder: UpdatedOrder? = null,
-
- @field:SerializedName("updatedItems")
- val updatedItems: List? = null
-)
-
-data class UpdatedItemsItem(
-
- @field:SerializedName("quantity")
- val quantity: Int? = null,
-
- @field:SerializedName("price")
- val price: String? = null,
-
- @field:SerializedName("subtotal")
- val subtotal: String? = null,
-
- @field:SerializedName("product_id")
- val productId: Int? = null,
-
- @field:SerializedName("id")
- val id: Int? = null,
+ @field:SerializedName("total_amount")
+ val totalAmount: Int? = null,
@field:SerializedName("order_id")
- val orderId: Int? = null
-)
+ val orderId: Int? = null,
-data class UpdatedOrder(
-
- @field:SerializedName("auto_canceled_at")
- val autoCanceledAt: String? = null,
-
- @field:SerializedName("payment_method_id")
- val paymentMethodId: Int? = null,
-
- @field:SerializedName("auto_completed_at")
- val autoCompletedAt: String? = null,
-
- @field:SerializedName("updated_at")
- val updatedAt: String? = null,
-
- @field:SerializedName("total_amount")
- val totalAmount: String? = null,
-
- @field:SerializedName("user_id")
- val userId: Int? = null,
-
- @field:SerializedName("address_id")
- val addressId: Int? = null,
-
- @field:SerializedName("is_negotiable")
- val isNegotiable: Boolean? = null,
-
- @field:SerializedName("created_at")
- val createdAt: String? = null,
-
- @field:SerializedName("voucher_id")
- val voucherId: Any? = null,
-
- @field:SerializedName("id")
- val id: Int? = null,
+ @field:SerializedName("items")
+ val items: List? = null,
@field:SerializedName("status")
val status: String? = null
)
+
+data class ItemsItem(
+
+ @field:SerializedName("order_item_id")
+ val orderItemId: Int? = null,
+
+ @field:SerializedName("price")
+ val price: Int? = null,
+
+ @field:SerializedName("subtotal")
+ val subtotal: Int? = null
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/profile/GenericResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/profile/GenericResponse.kt
new file mode 100644
index 0000000..6a665b5
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/profile/GenericResponse.kt
@@ -0,0 +1,11 @@
+package com.alya.ecommerce_serang.data.api.response.store.profile
+
+import com.google.gson.annotations.SerializedName
+
+data class GenericResponse(
+ @SerializedName("message")
+ val message: String,
+
+ @SerializedName("success")
+ val success: Boolean = true
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/profile/StoreDataResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/profile/StoreDataResponse.kt
new file mode 100644
index 0000000..fa7ad97
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/profile/StoreDataResponse.kt
@@ -0,0 +1,56 @@
+package com.alya.ecommerce_serang.data.api.response.store.profile
+
+import com.google.gson.annotations.SerializedName
+
+data class StoreDataResponse(
+ val message: String,
+ val store: Store? = null,
+ val shipping: List? = emptyList(),
+ val payment: List = emptyList()
+)
+
+data class Store(
+ @SerializedName("store_id") val storeId: Int,
+ @SerializedName("store_status") val storeStatus: String,
+ @SerializedName("store_name") val storeName: String,
+ @SerializedName("user_name") val userName: String,
+ val email: String,
+ @SerializedName("user_phone") val userPhone: String,
+ val balance: String,
+ val ktp: String,
+ val npwp: String,
+ val nib: String,
+ val persetujuan: String?,
+ @SerializedName("store_image") val storeImage: String,
+ @SerializedName("store_description") val storeDescription: String,
+ @SerializedName("is_on_leave") val isOnLeave: Boolean,
+ @SerializedName("store_type_id") val storeTypeId: Int,
+ @SerializedName("store_type") val storeType: String,
+ val id: Int,
+ val latitude: String,
+ val longitude: String,
+ val street: String,
+ val subdistrict: String,
+ @SerializedName("postal_code") val postalCode: String,
+ val detail: String,
+ @SerializedName("is_store_location") val isStoreLocation: Boolean,
+ @SerializedName("user_id") val userId: Int,
+ @SerializedName("city_id") val cityId: Int,
+ @SerializedName("province_id") val provinceId: Int,
+ val phone: String?,
+ val recipient: String?,
+ @SerializedName("approval_status") val approvalStatus: String,
+ @SerializedName("approval_reason") val approvalReason: String?
+)
+
+data class Shipping(
+ val courier: String
+)
+
+data class Payment(
+ val id: Int,
+ @SerializedName("bank_num") val bankNum: String,
+ @SerializedName("bank_name") val bankName: String,
+ @SerializedName("qris_image") val qrisImage: String?,
+ @SerializedName("account_name") val accountName: String?
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/topup/BalanceTopUpResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/topup/BalanceTopUpResponse.kt
new file mode 100644
index 0000000..a6965ce
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/topup/BalanceTopUpResponse.kt
@@ -0,0 +1,6 @@
+package com.alya.ecommerce_serang.data.api.response.store.topup
+
+data class BalanceTopUpResponse(
+ val success: Boolean,
+ val message: String
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/topup/TopUpResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/topup/TopUpResponse.kt
new file mode 100644
index 0000000..c7cc1bb
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/topup/TopUpResponse.kt
@@ -0,0 +1,87 @@
+package com.alya.ecommerce_serang.data.api.response.store.topup
+
+import android.util.Log
+import com.google.gson.annotations.SerializedName
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+import java.util.TimeZone
+
+data class TopUpResponse(
+ val message: String,
+ val topup: List
+)
+
+data class TopUp(
+ val id: Int,
+ val amount: String,
+ @SerializedName("store_id") val storeId: Int,
+ val status: String,
+ @SerializedName("created_at") val createdAt: String,
+ val image: String,
+ @SerializedName("payment_info_id") val paymentInfoId: Int,
+ @SerializedName("transaction_date") val transactionDate: String,
+ @SerializedName("payment_method") val paymentMethod: String,
+ @SerializedName("account_name") val accountName: String?
+) {
+ fun getFormattedDate(): String {
+ try {
+ // Try to use transaction_date first, then fall back to created_at
+ val dateStr = if (transactionDate.isNotEmpty()) transactionDate else createdAt
+
+ // Try different formats to parse the date
+ val parsedDate = parseApiDate(dateStr) ?: return dateStr
+
+ // Format with Indonesian locale for month names
+ val outputFormat = SimpleDateFormat("dd MMM yyyy", Locale("id"))
+ return outputFormat.format(parsedDate)
+ } catch (e: Exception) {
+ Log.e("TopUp", "Error formatting date: ${e.message}")
+ return createdAt
+ }
+ }
+
+ private fun parseApiDate(dateStr: String): Date? {
+ if (dateStr.isEmpty()) return null
+
+ // List of possible date formats to try
+ val formats = listOf(
+ "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", // Standard ISO with milliseconds
+ "yyyy-MM-dd'T'HH:mm:ss'Z'", // ISO without milliseconds
+ "yyyy-MM-dd'T'HH:mm:ss.SSSZ", // ISO with timezone offset
+ "yyyy-MM-dd'T'HH:mm:ssZ", // ISO with timezone offset, no milliseconds
+ "yyyy-MM-dd", // Just the date part
+ "dd-MM-yyyy" // Alternative date format
+ )
+
+ for (format in formats) {
+ try {
+ val sdf = SimpleDateFormat(format, Locale.US)
+ sdf.timeZone = TimeZone.getTimeZone("UTC") // Assuming API dates are in UTC
+ return sdf.parse(dateStr)
+ } catch (e: Exception) {
+ // Try next format
+ continue
+ }
+ }
+
+ // If all formats fail, just try to extract the date part and parse it
+ try {
+ val datePart = dateStr.split("T").firstOrNull() ?: return null
+ val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)
+ return simpleDateFormat.parse(datePart)
+ } catch (e: Exception) {
+ Log.e("TopUp", "Failed to parse date: $dateStr", e)
+ return null
+ }
+ }
+
+ fun getFormattedAmount(): String {
+ return try {
+ val amountValue = amount.toDouble()
+ String.format("+ Rp%,.0f", amountValue)
+ } catch (e: Exception) {
+ "Rp$amount"
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
index e1c09b7..6e8497e 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
@@ -2,7 +2,9 @@ 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.AddPaymentInfoResponse
import com.alya.ecommerce_serang.data.api.dto.CartItem
+import com.alya.ecommerce_serang.data.api.dto.CityResponse
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
@@ -10,9 +12,12 @@ import com.alya.ecommerce_serang.data.api.dto.LoginRequest
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
+import com.alya.ecommerce_serang.data.api.dto.ProvinceResponse
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.dto.ReviewProductItem
import com.alya.ecommerce_serang.data.api.dto.SearchRequest
+import com.alya.ecommerce_serang.data.api.dto.ShippingServiceRequest
+import com.alya.ecommerce_serang.data.api.dto.StoreAddressResponse
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
@@ -52,6 +57,10 @@ import com.alya.ecommerce_serang.data.api.response.store.product.CreateProductRe
import com.alya.ecommerce_serang.data.api.response.store.product.DeleteProductResponse
import com.alya.ecommerce_serang.data.api.response.store.product.UpdateProductResponse
import com.alya.ecommerce_serang.data.api.response.store.product.ViewStoreProductsResponse
+import com.alya.ecommerce_serang.data.api.response.store.profile.GenericResponse
+import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse
+import com.alya.ecommerce_serang.data.api.response.store.topup.BalanceTopUpResponse
+import com.alya.ecommerce_serang.data.api.response.store.topup.TopUpResponse
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.Call
@@ -161,7 +170,16 @@ interface ApiService {
): Response
@GET("mystore")
- suspend fun getStore (): Response
+ suspend fun getStore(): Response
+
+ @GET("mystore")
+ suspend fun getStoreData(): Response
+
+ @GET("mystore")
+ suspend fun getMyStoreData(): Response
+
+ @GET("mystore")
+ suspend fun getStoreAddress(): Response
@GET("mystore/product") // Replace with actual endpoint
suspend fun getStoreProduct(): Response
@@ -231,19 +249,22 @@ interface ApiService {
suspend fun getListProv(
): Response
- @GET("mystore/orders")
- suspend fun getAllOrders(): Response
-
- @GET("mystore/orders/{status}")
- suspend fun getOrdersByStatus(
- @Query("status") status: String
- ): Response
+ @GET("order/{status}")
+ suspend fun getSellList(
+ @Path("status") status: String
+ ): Response
@PUT("store/order/update")
suspend fun confirmOrder(
@Body confirmOrder : CompletedOrderRequest
): Response
+ @PUT("store/order/update")
+ suspend fun updateOrder(
+ @Query("order_id") orderId: Int?,
+ @Query("status") status: String
+ ): Response
+
@Multipart
@POST("addcomplaint")
suspend fun addComplaint(
@@ -257,6 +278,89 @@ interface ApiService {
@Body contentReview : ReviewProductItem
): Response
+ @GET("store/topup")
+ suspend fun getTopUpHistory(): Response
+
+ @GET("store/topup")
+ suspend fun getFilteredTopUpHistory(@Query("date") date: String): Response
+
+ @Multipart
+ @POST("store/createtopup")
+ suspend fun addBalanceTopUp(
+ @Part topupimg: MultipartBody.Part,
+ @Part("amount") amount: RequestBody,
+ @Part("payment_info_id") paymentInfoId: RequestBody,
+ @Part("transaction_date") transactionDate: RequestBody,
+ @Part("bank_name") bankName: RequestBody,
+ @Part("bank_num") bankNum: RequestBody
+ ): Response
+
+ @Multipart
+ @PUT("mystore/edit")
+ suspend fun updateStoreProfileMultipart(
+ @Part("store_name") storeName: RequestBody,
+ @Part("store_status") storeStatus: RequestBody,
+ @Part("store_description") storeDescription: RequestBody,
+ @Part("is_on_leave") isOnLeave: RequestBody,
+ @Part("city_id") cityId: RequestBody,
+ @Part("province_id") provinceId: RequestBody,
+ @Part("street") street: RequestBody,
+ @Part("subdistrict") subdistrict: RequestBody,
+ @Part("detail") detail: RequestBody,
+ @Part("postal_code") postalCode: RequestBody,
+ @Part("latitude") latitude: RequestBody,
+ @Part("longitude") longitude: RequestBody,
+ @Part("user_phone") userPhone: RequestBody,
+ @Part storeimg: MultipartBody.Part?
+ ): Response
+
+ @Multipart
+ @POST("mystore/payment/add")
+ suspend fun addPaymentInfo(
+ @Part("bank_name") bankName: RequestBody,
+ @Part("bank_num") bankNum: RequestBody,
+ @Part("account_name") accountName: RequestBody,
+ @Part qris: MultipartBody.Part?
+ ): Response
+
+ @Multipart
+ @POST("mystore/payment/add")
+ suspend fun addPaymentInfoDirect(
+ @Part("bank_name") bankName: RequestBody,
+ @Part("bank_num") bankNum: RequestBody,
+ @Part("account_name") accountName: RequestBody,
+ @Part qris: MultipartBody.Part?
+ ): Response
+
+ @DELETE("mystore/payment/delete/{id}")
+ suspend fun deletePaymentInfo(
+ @Path("id") paymentMethodId: Int
+ ): Response
+
+ // Shipping Service API endpoints
+ @POST("mystore/shipping/add")
+ suspend fun addShippingService(
+ @Body request: ShippingServiceRequest
+ ): Response
+
+ @POST("mystore/shipping/delete")
+ suspend fun deleteShippingService(
+ @Body request: ShippingServiceRequest
+ ): Response
+
+ @GET("provinces")
+ suspend fun getProvinces(): Response
+
+ @GET("cities/{provinceId}")
+ suspend fun getCities(
+ @Path("provinceId") provinceId: String
+ ): Response
+
+ @PUT("mystore/edit")
+ suspend fun updateStoreAddress(
+ @Body addressData: HashMap
+ ): Response
+
@POST("search")
suspend fun saveSearchQuery(
@Body searchRequest: SearchRequest
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/AddressRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/AddressRepository.kt
new file mode 100644
index 0000000..8659ef7
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/AddressRepository.kt
@@ -0,0 +1,174 @@
+package com.alya.ecommerce_serang.data.repository
+
+import android.util.Log
+import com.alya.ecommerce_serang.data.api.dto.City
+import com.alya.ecommerce_serang.data.api.dto.Province
+import com.alya.ecommerce_serang.data.api.dto.StoreAddress
+import com.alya.ecommerce_serang.data.api.retrofit.ApiService
+import com.google.gson.Gson
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.json.JSONObject
+
+class AddressRepository(private val apiService: ApiService) {
+
+ private val TAG = "AddressRepository"
+
+ suspend fun getProvinces(): List = withContext(Dispatchers.IO) {
+ Log.d(TAG, "getProvinces() called")
+ try {
+ val response = apiService.getProvinces()
+ Log.d(TAG, "getProvinces() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
+
+ // Log the raw response body for debugging
+ val rawBody = response.raw().toString()
+ Log.d(TAG, "Raw response: $rawBody")
+
+ if (response.isSuccessful) {
+ val responseBody = response.body()
+ Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}")
+
+ val provinces = responseBody?.data ?: emptyList()
+ Log.d(TAG, "getProvinces() success, got ${provinces.size} provinces")
+ return@withContext provinces
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "getProvinces() error: $errorBody")
+ throw Exception("API Error (${response.code()}): $errorBody")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception in getProvinces()", e)
+ throw Exception("Network error: ${e.message}")
+ }
+ }
+
+ suspend fun getCities(provinceId: String): List = withContext(Dispatchers.IO) {
+ Log.d(TAG, "getCities() called with provinceId: $provinceId")
+ try {
+ val response = apiService.getCities(provinceId)
+ Log.d(TAG, "getCities() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
+
+ if (response.isSuccessful) {
+ val responseBody = response.body()
+ Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}")
+
+ val cities = responseBody?.data ?: emptyList()
+ Log.d(TAG, "getCities() success, got ${cities.size} cities")
+ return@withContext cities
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "getCities() error: $errorBody")
+ throw Exception("API Error (${response.code()}): $errorBody")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception in getCities()", e)
+ throw Exception("Network error: ${e.message}")
+ }
+ }
+
+ suspend fun getStoreAddress(): StoreAddress? = withContext(Dispatchers.IO) {
+ Log.d(TAG, "getStoreAddress() called")
+ try {
+ val response = apiService.getStoreAddress()
+ Log.d(TAG, "getStoreAddress() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
+
+ if (response.isSuccessful) {
+ val responseBody = response.body()
+ val rawJson = Gson().toJson(responseBody)
+ Log.d(TAG, "Response body: $rawJson")
+
+ val address = responseBody?.data
+ Log.d(TAG, "getStoreAddress() success, address: $address")
+
+ // Convert numeric strings to proper types if needed
+ address?.let {
+ // Handle city_id if it's a number
+ if (it.cityId.isBlank() && rawJson.contains("city_id")) {
+ try {
+ val cityId = JSONObject(rawJson).getJSONObject("store").optInt("city_id", 0)
+ if (cityId > 0) {
+ it.javaClass.getDeclaredField("cityId").apply {
+ isAccessible = true
+ set(it, cityId.toString())
+ }
+ Log.d(TAG, "Updated cityId to: ${it.cityId}")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Error parsing city_id", e)
+ }
+ }
+
+ // Handle province_id if it's a number
+ if (it.provinceId.isBlank() && rawJson.contains("province_id")) {
+ try {
+ val provinceId = JSONObject(rawJson).getJSONObject("store").optInt("province_id", 0)
+ if (provinceId > 0) {
+ it.javaClass.getDeclaredField("provinceId").apply {
+ isAccessible = true
+ set(it, provinceId.toString())
+ }
+ Log.d(TAG, "Updated provinceId to: ${it.provinceId}")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Error parsing province_id", e)
+ }
+ }
+ }
+
+ return@withContext address
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "getStoreAddress() error: $errorBody")
+ throw Exception("API Error (${response.code()}): $errorBody")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception in getStoreAddress()", e)
+ throw Exception("Network error: ${e.message}")
+ }
+ }
+
+ suspend fun saveStoreAddress(
+ provinceId: String,
+ provinceName: String,
+ cityId: String,
+ cityName: String,
+ street: String,
+ subdistrict: String,
+ detail: String,
+ postalCode: String,
+ latitude: Double,
+ longitude: Double
+ ): Boolean = withContext(Dispatchers.IO) {
+ Log.d(TAG, "saveStoreAddress() called with provinceId: $provinceId, cityId: $cityId")
+
+ try {
+ val addressMap = HashMap()
+ addressMap["provinceId"] = provinceId
+ addressMap["provinceName"] = provinceName
+ addressMap["cityId"] = cityId
+ addressMap["cityName"] = cityName
+ addressMap["street"] = street
+ addressMap["subdistrict"] = subdistrict
+ addressMap["detail"] = detail
+ addressMap["postalCode"] = postalCode
+ addressMap["latitude"] = latitude
+ addressMap["longitude"] = longitude
+
+ Log.d(TAG, "saveStoreAddress() request data: $addressMap")
+ val response = apiService.updateStoreAddress(addressMap)
+ Log.d(TAG, "saveStoreAddress() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
+
+ if (response.isSuccessful) {
+ Log.d(TAG, "saveStoreAddress() success")
+ return@withContext true
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "saveStoreAddress() error: $errorBody")
+ throw Exception("API Error (${response.code()}): $errorBody")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception in saveStoreAddress()", e)
+ throw Exception("Network error: ${e.message}")
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt
index 9ab9afa..080c3cb 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt
@@ -293,35 +293,6 @@ class OrderRepository(private val apiService: ApiService) {
return if (response.isSuccessful) response.body() else null
}
- suspend fun fetchSells(): List {
- return try {
- val response = apiService.getAllOrders() // Replace with the actual method from your ApiService
- if (response.isSuccessful) {
- response.body()?.orders ?: emptyList() // Assuming the response body has 'orders'
- } else {
- Log.e("OrderRepository", "Error fetching all sells. Code: ${response.code()}")
- emptyList()
- }
- } catch (e: Exception) {
- Log.e("OrderRepository", "Exception fetching sells", e)
- emptyList()
- }
- }
-
- suspend fun fetchOrdersByStatus(status: String): List {
- return try {
- val response = apiService.getOrdersByStatus(status) // Replace with actual method for status-based fetch
- if (response.isSuccessful) {
- response.body()?.orders?.filterNotNull() ?: emptyList() // Assuming the response body has 'orders'
- } else {
- Log.e("OrderRepository", "Error fetching orders by status ($status). Code: ${response.code()}")
- emptyList()
- }
- } catch (e: Exception) {
- Log.e("OrderRepository", "Exception fetching orders by status", e)
- emptyList()
- }
- }
suspend fun fetchUserProfile(): Result {
return try {
val response = apiService.getUserProfile()
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/PaymentInfoRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/PaymentInfoRepository.kt
new file mode 100644
index 0000000..b8803d4
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/PaymentInfoRepository.kt
@@ -0,0 +1,168 @@
+package com.alya.ecommerce_serang.data.repository
+
+import android.util.Log
+import com.alya.ecommerce_serang.data.api.dto.PaymentInfo
+import com.alya.ecommerce_serang.data.api.retrofit.ApiService
+import com.google.gson.Gson
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
+import okhttp3.RequestBody.Companion.asRequestBody
+import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.File
+
+class PaymentInfoRepository(private val apiService: ApiService) {
+
+ private val TAG = "PaymentInfoRepository"
+ private val gson = Gson()
+
+ suspend fun getPaymentInfo(): List = withContext(Dispatchers.IO) {
+ try {
+ Log.d(TAG, "Getting payment info")
+ val response = apiService.getStoreData()
+
+ if (response.isSuccessful) {
+ val result = response.body()
+
+ // Log the raw response
+ Log.d(TAG, "API Response body: ${gson.toJson(result)}")
+
+ // Check if payment list is null or empty
+ val paymentList = result?.payment
+ if (paymentList.isNullOrEmpty()) {
+ Log.d(TAG, "Payment list is null or empty in response")
+ return@withContext emptyList()
+ }
+
+ Log.d(TAG, "Raw payment list: ${gson.toJson(paymentList)}")
+ Log.d(TAG, "Get payment methods success: ${paymentList.size} methods")
+
+ // Convert Payment objects to PaymentMethod objects
+ val convertedPayments = paymentList.map { payment ->
+ PaymentInfo(
+ id = payment.id,
+ bankNum = payment.bankNum,
+ bankName = payment.bankName,
+ qrisImage = payment.qrisImage,
+ accountName = payment.accountName
+ )
+ }
+
+ return@withContext convertedPayments
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "Get payment methods error: $errorBody, HTTP code: ${response.code()}")
+ throw Exception("Failed to get payment methods: ${response.message()}, code: ${response.code()}, error: $errorBody")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception getting payment methods", e)
+ throw e
+ }
+ }
+
+ suspend fun addPaymentMethod(
+ bankName: String,
+ bankNumber: String,
+ accountName: String,
+ qrisImageFile: File?
+ ): Boolean = withContext(Dispatchers.IO) {
+ try {
+ Log.d(TAG, "===== START PAYMENT METHOD ADD =====")
+ Log.d(TAG, "Adding payment method with parameters:")
+ Log.d(TAG, "Bank Name: $bankName")
+ Log.d(TAG, "Bank Number: $bankNumber")
+ Log.d(TAG, "Account Name: $accountName")
+ Log.d(TAG, "QRIS Image File: ${qrisImageFile?.absolutePath}")
+ Log.d(TAG, "QRIS File exists: ${qrisImageFile?.exists()}")
+ Log.d(TAG, "QRIS File size: ${qrisImageFile?.length() ?: 0} bytes")
+
+ // Create text RequestBody objects with explicit content type
+ val contentType = "text/plain".toMediaTypeOrNull()
+ val bankNamePart = bankName.toRequestBody(contentType)
+ val bankNumPart = bankNumber.toRequestBody(contentType)
+ val accountNamePart = accountName.toRequestBody(contentType)
+
+ // Log request parameters details
+ Log.d(TAG, "Request parameters details:")
+ Log.d(TAG, "bank_name RequestBody created with value: $bankName")
+ Log.d(TAG, "bank_num RequestBody created with value: $bankNumber")
+ Log.d(TAG, "account_name RequestBody created with value: $accountName")
+
+ // Create image part if file exists
+ var qrisPart: MultipartBody.Part? = null
+ if (qrisImageFile != null && qrisImageFile.exists() && qrisImageFile.length() > 0) {
+ // Use image/* content type to ensure proper MIME type for images
+ val imageContentType = "image/jpeg".toMediaTypeOrNull()
+ val requestFile = qrisImageFile.asRequestBody(imageContentType)
+ qrisPart = MultipartBody.Part.createFormData("qris", qrisImageFile.name, requestFile)
+ Log.d(TAG, "qris MultipartBody.Part created with filename: ${qrisImageFile.name}")
+ Log.d(TAG, "qris file size: ${qrisImageFile.length()} bytes")
+ } else {
+ Log.d(TAG, "No qris image part will be included in the request")
+ }
+
+ // Example input data being sent to API
+ Log.d(TAG, "Example input data sent to API endpoint http://192.168.100.31:3000/mystore/payment/add:")
+ Log.d(TAG, "Method: POST")
+ Log.d(TAG, "Content-Type: multipart/form-data")
+ Log.d(TAG, "Form fields:")
+ Log.d(TAG, "- bank_name: $bankName")
+ Log.d(TAG, "- bank_num: $bankNumber")
+ Log.d(TAG, "- account_name: $accountName")
+ if (qrisPart != null) {
+ Log.d(TAG, "- qris: [binary image file: ${qrisImageFile?.name}, size: ${qrisImageFile?.length()} bytes]")
+ }
+
+ try {
+ // Use the direct API method call
+ val response = apiService.addPaymentInfoDirect(
+ bankName = bankNamePart,
+ bankNum = bankNumPart,
+ accountName = accountNamePart,
+ qris = qrisPart
+ )
+
+ if (response.isSuccessful) {
+ val result = response.body()
+ Log.d(TAG, "API response: ${gson.toJson(result)}")
+ Log.d(TAG, "Add payment method success")
+ Log.d(TAG, "===== END PAYMENT METHOD ADD - SUCCESS =====")
+ return@withContext true
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "Add payment method error: $errorBody, HTTP code: ${response.code()}")
+ Log.e(TAG, "===== END PAYMENT METHOD ADD - FAILURE =====")
+ throw Exception("Failed to add payment method: ${response.message()}, code: ${response.code()}, error: $errorBody")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "API call exception", e)
+ throw e
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception adding payment method", e)
+ Log.e(TAG, "===== END PAYMENT METHOD ADD - EXCEPTION =====")
+ throw e
+ }
+ }
+
+ suspend fun deletePaymentMethod(paymentMethodId: Int): Boolean = withContext(Dispatchers.IO) {
+ try {
+ Log.d(TAG, "Deleting payment method with ID: $paymentMethodId")
+
+ val response = apiService.deletePaymentInfo(paymentMethodId)
+
+ if (response.isSuccessful) {
+ Log.d(TAG, "Delete payment method success: ${response.body()?.message}")
+ return@withContext true
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "Delete payment method error: $errorBody, HTTP code: ${response.code()}")
+ throw Exception("Failed to delete payment method: ${response.message()}, code: ${response.code()}, error: $errorBody")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception deleting payment method", e)
+ throw e
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/SellsRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/SellsRepository.kt
new file mode 100644
index 0000000..f384565
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/SellsRepository.kt
@@ -0,0 +1,45 @@
+package com.alya.ecommerce_serang.data.repository
+
+import android.util.Log
+import com.alya.ecommerce_serang.data.api.response.store.orders.OrderListResponse
+import com.alya.ecommerce_serang.data.api.retrofit.ApiService
+
+class SellsRepository(private val apiService: ApiService) {
+ suspend fun getSellList(status: String): Result {
+ return try {
+ Log.d("SellsRepository", "Add Evidence : $status")
+ val response = apiService.getSellList(status)
+
+ if (response.isSuccessful) {
+ val allListSell = response.body()
+ if (allListSell != null) {
+ Log.d("SellsRepository", "Add Evidence successfully: ${allListSell.message}")
+ Result.Success(allListSell)
+ } else {
+ Log.e("SellsRepository", "Response body was null")
+ Result.Error(Exception("Empty response from server"))
+ }
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e("SellsRepository", "Error Add Evidence : $errorBody")
+ Result.Error(Exception(errorBody))
+ }
+ } catch (e: Exception) {
+ Log.e("SellsRepository", "Exception Add Evidence ", e)
+ Result.Error(e)
+ }
+ }
+
+ suspend fun updateOrderStatus(orderId: Int?, status: String) {
+ try {
+ val response = apiService.updateOrder(orderId, status)
+ if (response.isSuccessful) {
+ Log.d("SellsRepository", "Order status updated successfully: orderId=$orderId, status=$status")
+ } else {
+ Log.e("SellsRepository", "Error updating order status: orderId=$orderId, status=$status")
+ }
+ } catch (e: Exception) {
+ Log.e("SellsRepository", "Exception updating order status", e)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/ShippingServiceRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ShippingServiceRepository.kt
new file mode 100644
index 0000000..770613b
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ShippingServiceRepository.kt
@@ -0,0 +1,78 @@
+package com.alya.ecommerce_serang.data.repository
+
+import android.util.Log
+import com.alya.ecommerce_serang.data.api.dto.ShippingServiceRequest
+import com.alya.ecommerce_serang.data.api.retrofit.ApiService
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class ShippingServiceRepository(private val apiService: ApiService) {
+
+ private val TAG = "ShippingServiceRepo"
+
+ suspend fun getAvailableCouriers(): List = withContext(Dispatchers.IO) {
+ try {
+ Log.d(TAG, "Getting available shipping services")
+ val response = apiService.getStoreData()
+
+ if (response.isSuccessful) {
+ val result = response.body()
+ val shippingList = result?.shipping
+
+ val couriers = shippingList?.map { it.courier } ?: emptyList()
+
+ Log.d(TAG, "Get shipping services success: ${couriers.size} couriers")
+ return@withContext couriers
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "Get shipping services error: $errorBody")
+ throw Exception("Failed to get shipping services: ${response.message()}")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception getting shipping services", e)
+ throw e
+ }
+ }
+
+ suspend fun addShippingServices(couriers: List): Boolean = withContext(Dispatchers.IO) {
+ try {
+ Log.d(TAG, "Adding shipping services: $couriers")
+
+ val request = ShippingServiceRequest(couriers = couriers)
+ val response = apiService.addShippingService(request)
+
+ if (response.isSuccessful) {
+ Log.d(TAG, "Add shipping services success: ${response.body()?.message}")
+ return@withContext true
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "Add shipping services error: $errorBody")
+ throw Exception("Failed to add shipping services: ${response.message()}")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception adding shipping services", e)
+ throw e
+ }
+ }
+
+ suspend fun deleteShippingServices(couriers: List): Boolean = withContext(Dispatchers.IO) {
+ try {
+ Log.d(TAG, "Deleting shipping services: $couriers")
+
+ val request = ShippingServiceRequest(couriers = couriers)
+ val response = apiService.deleteShippingService(request)
+
+ if (response.isSuccessful) {
+ Log.d(TAG, "Delete shipping services success: ${response.body()?.message}")
+ return@withContext true
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "Delete shipping services error: $errorBody")
+ throw Exception("Failed to delete shipping services: ${response.message()}")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception deleting shipping services", e)
+ throw e
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt
index af8a8c4..0b040d3 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt
@@ -2,10 +2,12 @@ package com.alya.ecommerce_serang.ui.profile.mystore
import android.content.Intent
import android.os.Bundle
+import android.util.Log
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
+import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.Store
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
@@ -16,10 +18,8 @@ import com.alya.ecommerce_serang.ui.profile.mystore.balance.BalanceActivity
import com.alya.ecommerce_serang.ui.profile.mystore.product.ProductActivity
import com.alya.ecommerce_serang.ui.profile.mystore.profile.DetailStoreProfileActivity
import com.alya.ecommerce_serang.ui.profile.mystore.review.ReviewFragment
-import com.alya.ecommerce_serang.ui.profile.mystore.sells.all_sells.AllSellsFragment
-import com.alya.ecommerce_serang.ui.profile.mystore.sells.order.OrderFragment
-import com.alya.ecommerce_serang.ui.profile.mystore.sells.payment.PaymentFragment
-import com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment.ShipmentFragment
+import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsActivity
+import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsListFragment
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel
@@ -67,10 +67,17 @@ class MyStoreActivity : AppCompatActivity() {
binding.tvStoreName.text = store.storeName
binding.tvStoreType.text = store.storeType
- store.storeImage.let {
+ if (store.storeImage != null && store.storeImage.toString().isNotEmpty() && store.storeImage.toString() != "null") {
+ val imageUrl = "http://192.168.100.156:3000${store.storeImage}"
+ Log.d("MyStoreActivity", "Loading store image from: $imageUrl")
+
Glide.with(this)
- .load(it)
+ .load(imageUrl)
+ .placeholder(R.drawable.placeholder_image)
+ .error(R.drawable.placeholder_image)
.into(binding.ivProfile)
+ } else {
+ Log.d("MyStoreActivity", "No store image available")
}
}
@@ -84,31 +91,26 @@ class MyStoreActivity : AppCompatActivity() {
}
binding.tvHistory.setOnClickListener {
- supportFragmentManager.beginTransaction()
- .replace(android.R.id.content, AllSellsFragment())
- .addToBackStack(null)
- .commit()
+ val intent = Intent(this, SellsActivity::class.java)
+ startActivity(intent)
}
binding.layoutPerluTagihan.setOnClickListener {
- supportFragmentManager.beginTransaction()
- .replace(android.R.id.content, OrderFragment())
- .addToBackStack(null)
- .commit()
+ val intent = Intent(this, SellsActivity::class.java)
+ startActivity(intent)
+ //navigateToSellsFragment("pending")
}
binding.layoutPembayaran.setOnClickListener {
- supportFragmentManager.beginTransaction()
- .replace(android.R.id.content, PaymentFragment())
- .addToBackStack(null)
- .commit()
+ val intent = Intent(this, SellsActivity::class.java)
+ startActivity(intent)
+ //navigateToSellsFragment("paid")
}
binding.layoutPerluDikirim.setOnClickListener {
- supportFragmentManager.beginTransaction()
- .replace(android.R.id.content, ShipmentFragment())
- .addToBackStack(null)
- .commit()
+ val intent = Intent(this, SellsActivity::class.java)
+ startActivity(intent)
+ //navigateToSellsFragment("processed")
}
binding.layoutProductMenu.setOnClickListener {
@@ -129,4 +131,25 @@ class MyStoreActivity : AppCompatActivity() {
.commit()
}
}
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (requestCode == PROFILE_REQUEST_CODE && resultCode == RESULT_OK) {
+ // Refresh store data
+ viewModel.loadMyStore()
+ Toast.makeText(this, "Profil toko berhasil diperbarui", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ companion object {
+ private const val PROFILE_REQUEST_CODE = 100
+ }
+
+// private fun navigateToSellsFragment(status: String) {
+// val sellsFragment = SellsListFragment.newInstance(status)
+// supportFragmentManager.beginTransaction()
+// .replace(android.R.id.content, sellsFragment)
+// .addToBackStack(null)
+// .commit()
+// }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceActivity.kt
index 4192943..b0c36ea 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceActivity.kt
@@ -1,21 +1,414 @@
package com.alya.ecommerce_serang.ui.profile.mystore.balance
+import android.app.DatePickerDialog
+import android.content.Intent
import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.response.store.topup.TopUp
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.databinding.ActivityBalanceBinding
+import com.alya.ecommerce_serang.utils.SessionManager
+import kotlinx.coroutines.launch
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Date
+import java.util.Locale
+import java.util.TimeZone
class BalanceActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityBalanceBinding
+ private lateinit var topUpAdapter: BalanceTransactionAdapter
+ private lateinit var sessionManager: SessionManager
+ private val calendar = Calendar.getInstance()
+ private var selectedDate: String? = null
+ private var allTopUps: List = emptyList()
+
+ private val TAG = "BalanceActivity"
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
- setContentView(R.layout.activity_balance)
+ binding = ActivityBalanceBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
+
+ // Initialize session manager
+ sessionManager = SessionManager(this)
+
+ // Setup header
+ val headerTitle = binding.header.headerTitle
+ headerTitle.text = "Saldo"
+
+ val backButton = binding.header.headerLeftIcon
+ backButton.setOnClickListener {
+ finish()
+ }
+
+ // Setup RecyclerView
+ setupRecyclerView()
+
+ // Setup DatePicker
+ setupDatePicker()
+
+ // Add clear filter button
+ setupClearFilter()
+
+ // Fetch data
+ fetchBalance()
+ fetchTopUpHistory()
+
+ // Setup listeners
+ setupListeners()
+ }
+
+ private fun setupRecyclerView() {
+ topUpAdapter = BalanceTransactionAdapter()
+ binding.rvBalanceTransaction.apply {
+ layoutManager = LinearLayoutManager(this@BalanceActivity)
+ adapter = topUpAdapter
+ }
+ }
+
+ private fun setupDatePicker() {
+ val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth ->
+ calendar.set(Calendar.YEAR, year)
+ calendar.set(Calendar.MONTH, month)
+ calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
+ updateDateInView()
+
+ // Store selected date for filtering
+ selectedDate = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(calendar.time)
+
+ // Show debugging information
+ Log.d(TAG, "Selected date: $selectedDate")
+
+ // Display all top-up dates for debugging
+ allTopUps.forEach { topUp ->
+ Log.d(TAG, "Top-up ID: ${topUp.id}, transaction_date: ${topUp.transactionDate}, created_at: ${topUp.createdAt}")
+ }
+
+ // Apply filter
+ filterTopUpsByDate(selectedDate)
+
+ // Show clear filter button
+ binding.btnClearFilter.visibility = View.VISIBLE
+ }
+
+ binding.edtTglTransaksi.setOnClickListener {
+ showDatePicker(dateSetListener)
+ }
+
+ binding.imgDatePicker.setOnClickListener {
+ showDatePicker(dateSetListener)
+ }
+
+ binding.iconDatePicker.setOnClickListener {
+ showDatePicker(dateSetListener)
+ }
+ }
+
+ private fun setupClearFilter() {
+ binding.btnClearFilter.setOnClickListener {
+ // Clear date selection
+ binding.edtTglTransaksi.text = null
+ selectedDate = null
+
+ // Reset to show all topups
+ if (allTopUps.isNotEmpty()) {
+ updateTopUpList(allTopUps)
+ } else {
+ fetchTopUpHistory()
+ }
+
+ // Hide clear button
+ binding.btnClearFilter.visibility = View.GONE
+ }
+ }
+
+ private fun showDatePicker(dateSetListener: DatePickerDialog.OnDateSetListener) {
+ DatePickerDialog(
+ this,
+ dateSetListener,
+ calendar.get(Calendar.YEAR),
+ calendar.get(Calendar.MONTH),
+ calendar.get(Calendar.DAY_OF_MONTH)
+ ).show()
+ }
+
+ private fun updateDateInView() {
+ val format = "dd MMMM yyyy"
+ val sdf = SimpleDateFormat(format, Locale("id"))
+ binding.edtTglTransaksi.setText(sdf.format(calendar.time))
+ }
+
+ private fun setupListeners() {
+ binding.btnTopUp.setOnClickListener {
+ val intent = Intent(this, BalanceTopUpActivity::class.java)
+ startActivityForResult(intent, TOP_UP_REQUEST_CODE)
+ }
+ }
+
+ private fun fetchBalance() {
+ showLoading(true)
+ lifecycleScope.launch {
+ try {
+ val response = ApiConfig.getApiService(sessionManager).getMyStoreData()
+ if (response.isSuccessful && response.body() != null) {
+ val storeData = response.body()!!
+ val balance = storeData.store.balance
+
+ // Format the balance
+ try {
+ val balanceValue = balance.toDouble()
+ binding.tvBalance.text = String.format("Rp%,.0f", balanceValue)
+ } catch (e: Exception) {
+ binding.tvBalance.text = "Rp$balance"
+ }
+ } else {
+ Toast.makeText(
+ this@BalanceActivity,
+ "Gagal memuat data saldo: ${response.message()}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Error fetching balance", e)
+ Toast.makeText(
+ this@BalanceActivity,
+ "Error: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ } finally {
+ showLoading(false)
+ }
+ }
+ }
+
+ private fun fetchTopUpHistory() {
+ showLoading(true)
+ lifecycleScope.launch {
+ try {
+ val response = ApiConfig.getApiService(sessionManager).getTopUpHistory()
+
+ if (response.isSuccessful && response.body() != null) {
+ val topUpData = response.body()!!
+ allTopUps = topUpData.topup
+
+ // Apply date filter if selected
+ if (selectedDate != null) {
+ filterTopUpsByDate(selectedDate)
+ } else {
+ updateTopUpList(allTopUps)
+ }
+ } else {
+ Toast.makeText(
+ this@BalanceActivity,
+ "Gagal memuat riwayat isi ulang: ${response.message()}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Error fetching top-up history", e)
+ Toast.makeText(
+ this@BalanceActivity,
+ "Error: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ } finally {
+ showLoading(false)
+ }
+ }
+ }
+
+ private fun filterTopUpsByDate(dateStr: String?) {
+ if (dateStr == null || allTopUps.isEmpty()) {
+ return
+ }
+
+ try {
+ Log.d(TAG, "Filtering by date: $dateStr")
+
+ // Parse the selected date - set to start of day
+ val cal1 = Calendar.getInstance()
+ cal1.time = parseSelectedDate(dateStr)
+ cal1.set(Calendar.HOUR_OF_DAY, 0)
+ cal1.set(Calendar.MINUTE, 0)
+ cal1.set(Calendar.SECOND, 0)
+ cal1.set(Calendar.MILLISECOND, 0)
+
+ // Extract the date components we care about (year, month, day)
+ val selectedYear = cal1.get(Calendar.YEAR)
+ val selectedMonth = cal1.get(Calendar.MONTH)
+ val selectedDay = cal1.get(Calendar.DAY_OF_MONTH)
+
+ Log.d(TAG, "Selected date components: Year=$selectedYear, Month=$selectedMonth, Day=$selectedDay")
+
+ // Format for comparing with API dates
+ val filtered = allTopUps.filter { topUp ->
+ try {
+ // Debug logging
+ Log.d(TAG, "Examining top-up: ID=${topUp.id}")
+ Log.d(TAG, " - created_at=${topUp.createdAt}")
+ Log.d(TAG, " - transaction_date=${topUp.transactionDate}")
+
+ // Try both dates for more flexibility
+ val cal2 = Calendar.getInstance()
+ var matched = false
+
+ // Try transaction_date first
+ if (topUp.transactionDate.isNotEmpty()) {
+ val transactionDate = parseApiDate(topUp.transactionDate)
+ if (transactionDate != null) {
+ cal2.time = transactionDate
+ val transYear = cal2.get(Calendar.YEAR)
+ val transMonth = cal2.get(Calendar.MONTH)
+ val transDay = cal2.get(Calendar.DAY_OF_MONTH)
+
+ Log.d(TAG, " - Transaction date components: Year=$transYear, Month=$transMonth, Day=$transDay")
+
+ if (transYear == selectedYear &&
+ transMonth == selectedMonth &&
+ transDay == selectedDay) {
+ Log.d(TAG, " - MATCH on transaction_date")
+ matched = true
+ }
+ }
+ }
+
+ // If no match yet, try created_at
+ if (!matched && topUp.createdAt.isNotEmpty()) {
+ val createdAtDate = parseApiDate(topUp.createdAt)
+ if (createdAtDate != null) {
+ cal2.time = createdAtDate
+ val createdYear = cal2.get(Calendar.YEAR)
+ val createdMonth = cal2.get(Calendar.MONTH)
+ val createdDay = cal2.get(Calendar.DAY_OF_MONTH)
+
+ Log.d(TAG, " - Created date components: Year=$createdYear, Month=$createdMonth, Day=$createdDay")
+
+ if (createdYear == selectedYear &&
+ createdMonth == selectedMonth &&
+ createdDay == selectedDay) {
+ Log.d(TAG, " - MATCH on created_at")
+ matched = true
+ }
+ }
+ }
+
+ // Final result
+ Log.d(TAG, " - Match result: $matched")
+ matched
+ } catch (e: Exception) {
+ Log.e(TAG, "Date parsing error for top-up ${topUp.id}: ${e.message}", e)
+ false
+ }
+ }
+
+ Log.d(TAG, "Found ${filtered.size} matching records out of ${allTopUps.size}")
+ updateTopUpList(filtered)
+ } catch (e: Exception) {
+ Log.e(TAG, "Error filtering by date", e)
+ Toast.makeText(
+ this@BalanceActivity,
+ "Error filtering data: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+
+ private fun parseSelectedDate(dateStr: String): Date {
+ // Parse the user-selected date
+ try {
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)
+ return dateFormat.parse(dateStr) ?: Date()
+ } catch (e: Exception) {
+ Log.e(TAG, "Error parsing selected date: $dateStr", e)
+ return Date()
+ }
+ }
+
+ /**
+ * Parse ISO 8601 date format from API (handles multiple formats)
+ */
+ private fun parseApiDate(dateStr: String): Date? {
+ if (dateStr.isEmpty()) return null
+
+ // List of possible date formats to try
+ val formats = listOf(
+ "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", // Standard ISO with milliseconds
+ "yyyy-MM-dd'T'HH:mm:ss'Z'", // ISO without milliseconds
+ "yyyy-MM-dd'T'HH:mm:ss.SSSZ", // ISO with timezone offset
+ "yyyy-MM-dd'T'HH:mm:ssZ", // ISO with timezone offset, no milliseconds
+ "yyyy-MM-dd", // Just the date part
+ "dd-MM-yyyy" // Alternative date format
+ )
+
+ for (format in formats) {
+ try {
+ val sdf = SimpleDateFormat(format, Locale.US)
+ sdf.timeZone = TimeZone.getTimeZone("UTC") // Assuming API dates are in UTC
+ return sdf.parse(dateStr)
+ } catch (e: Exception) {
+ // Try next format
+ continue
+ }
+ }
+
+ // If all formats fail, just try to extract the date part and parse it
+ try {
+ val datePart = dateStr.split("T").firstOrNull() ?: return null
+ val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)
+ return simpleDateFormat.parse(datePart)
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to parse date: $dateStr", e)
+ return null
+ }
+ }
+
+ private fun updateTopUpList(topUps: List) {
+ if (topUps.isEmpty()) {
+ binding.rvBalanceTransaction.visibility = View.GONE
+ binding.tvEmptyState.visibility = View.VISIBLE
+ } else {
+ binding.rvBalanceTransaction.visibility = View.VISIBLE
+ binding.tvEmptyState.visibility = View.GONE
+ topUpAdapter.submitList(topUps)
+ }
+ }
+
+ private fun showLoading(isLoading: Boolean) {
+ if (isLoading) {
+ binding.progressBar.visibility = View.VISIBLE
+ } else {
+ binding.progressBar.visibility = View.GONE
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (requestCode == TOP_UP_REQUEST_CODE && resultCode == RESULT_OK) {
+ // Refresh balance and top-up history after successful top-up
+ fetchBalance()
+ fetchTopUpHistory()
+ Toast.makeText(this, "Top up berhasil", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ companion object {
+ private const val TOP_UP_REQUEST_CODE = 101
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTopUpActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTopUpActivity.kt
index 12c437a..fd85cbb 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTopUpActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTopUpActivity.kt
@@ -1,21 +1,395 @@
package com.alya.ecommerce_serang.ui.profile.mystore.balance
+import android.app.DatePickerDialog
+import android.content.Intent
+import android.net.Uri
import android.os.Bundle
-import androidx.activity.enableEdgeToEdge
+import android.provider.MediaStore
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Button
+import android.widget.EditText
+import android.widget.ImageView
+import android.widget.Spinner
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.lifecycleScope
import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.response.store.profile.Payment
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.utils.SessionManager
+import kotlinx.coroutines.launch
+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 BalanceTopUpActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContentView(R.layout.activity_balance_top_up)
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
- val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
- v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
- insets
+ private lateinit var imgPreview: ImageView
+ private lateinit var addPhotoTextView: TextView
+ private lateinit var edtNominal: EditText
+ private lateinit var spinnerPaymentMethod: Spinner
+ private lateinit var edtTransactionDate: EditText
+ private lateinit var datePickerIcon: ImageView
+ private lateinit var btnSend: Button
+ private lateinit var sessionManager: SessionManager
+
+ private var selectedImageUri: Uri? = null
+ private var paymentMethods: List = emptyList()
+ private var selectedPaymentId: Int = -1
+
+ private val calendar = Calendar.getInstance()
+
+ private val getImageContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == RESULT_OK) {
+ val imageUri = result.data?.data
+ imageUri?.let {
+ selectedImageUri = it
+ imgPreview.setImageURI(it)
+ }
}
}
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_balance_top_up)
+
+ // Initialize session manager
+ sessionManager = SessionManager(this)
+
+ // Initialize views
+ imgPreview = findViewById(R.id.img_preview)
+ addPhotoTextView = findViewById(R.id.tv_tambah_foto)
+ edtNominal = findViewById(R.id.edt_nominal_topup)
+ spinnerPaymentMethod = findViewById(R.id.spinner_metode_bayar)
+ edtTransactionDate = findViewById(R.id.edt_tgl_transaksi)
+ datePickerIcon = findViewById(R.id.img_date_picker)
+ btnSend = findViewById(R.id.btn_send)
+
+ // Setup header title
+ val headerTitle = findViewById(R.id.header_title)
+ headerTitle.text = "Isi Ulang Saldo"
+
+ // Setup back button
+ val backButton = findViewById(R.id.header_left_icon)
+ backButton.setOnClickListener {
+ onBackPressedDispatcher.onBackPressed()
+ }
+
+ // Setup photo selection
+ addPhotoTextView.setOnClickListener {
+ openGallery()
+ }
+
+ imgPreview.setOnClickListener {
+ openGallery()
+ }
+
+ // Setup date picker
+ setupDatePicker()
+
+ // Fetch payment methods
+ fetchPaymentMethods()
+
+ // Setup submit button
+ btnSend.setOnClickListener {
+ submitForm()
+ }
+ }
+
+ private fun openGallery() {
+ val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
+ getImageContent.launch(intent)
+ }
+
+ private fun setupDatePicker() {
+ val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth ->
+ calendar.set(Calendar.YEAR, year)
+ calendar.set(Calendar.MONTH, month)
+ calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
+ updateDateInView()
+ }
+
+ edtTransactionDate.setOnClickListener {
+ showDatePicker(dateSetListener)
+ }
+
+ datePickerIcon.setOnClickListener {
+ showDatePicker(dateSetListener)
+ }
+ }
+
+ private fun showDatePicker(dateSetListener: DatePickerDialog.OnDateSetListener) {
+ DatePickerDialog(
+ this,
+ dateSetListener,
+ calendar.get(Calendar.YEAR),
+ calendar.get(Calendar.MONTH),
+ calendar.get(Calendar.DAY_OF_MONTH)
+ ).show()
+ }
+
+ private fun updateDateInView() {
+ val format = "yyyy-MM-dd"
+ val sdf = SimpleDateFormat(format, Locale.US)
+ edtTransactionDate.setText(sdf.format(calendar.time))
+ }
+
+ private fun fetchPaymentMethods() {
+ lifecycleScope.launch {
+ try {
+ val response = ApiConfig.getApiService(sessionManager).getStoreData()
+ if (response.isSuccessful && response.body() != null) {
+ val storeData = response.body()!!
+ paymentMethods = storeData.payment
+
+ setupPaymentMethodSpinner()
+ } else {
+ Toast.makeText(
+ this@BalanceTopUpActivity,
+ "Gagal memuat metode pembayaran",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ } catch (e: Exception) {
+ Toast.makeText(
+ this@BalanceTopUpActivity,
+ "Error: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+
+ private fun setupPaymentMethodSpinner() {
+ if (paymentMethods.isEmpty()) {
+ Toast.makeText(
+ this,
+ "Tidak ada metode pembayaran tersedia",
+ Toast.LENGTH_SHORT
+ ).show()
+ return
+ }
+
+ // Debug payment methods
+ for (payment in paymentMethods) {
+ android.util.Log.d("BalanceTopUp", "Payment Option - ID: ${payment.id}, Bank: ${payment.bankName}, Number: ${payment.bankNum}")
+ }
+
+ val paymentOptions = paymentMethods.map { "${it.bankName} - ${it.bankNum}" }.toTypedArray()
+ val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, paymentOptions)
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+
+ spinnerPaymentMethod.adapter = adapter
+ spinnerPaymentMethod.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ selectedPaymentId = paymentMethods[position].id
+ android.util.Log.d("BalanceTopUp", "Selected payment ID: $selectedPaymentId")
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {
+ selectedPaymentId = -1
+ }
+ }
+ }
+
+ private fun submitForm() {
+ // Prevent multiple clicks
+ if (!btnSend.isEnabled) {
+ return
+ }
+
+ // Validate inputs
+ if (selectedImageUri == null) {
+ Toast.makeText(this, "Mohon pilih foto bukti pembayaran", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ val nominal = edtNominal.text.toString().trim()
+ if (nominal.isEmpty()) {
+ Toast.makeText(this, "Mohon isi nominal top up", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ try {
+ // Validate the amount is a valid number
+ val amountValue = nominal.replace("[^0-9]".toRegex(), "").toLong()
+ if (amountValue <= 0) {
+ Toast.makeText(this, "Nominal harus lebih dari 0", Toast.LENGTH_SHORT).show()
+ return
+ }
+ } catch (e: NumberFormatException) {
+ Toast.makeText(this, "Format nominal tidak valid", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ if (selectedPaymentId == -1) {
+ Toast.makeText(this, "Mohon pilih metode pembayaran", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ val transactionDate = edtTransactionDate.text.toString().trim()
+ if (transactionDate.isEmpty()) {
+ Toast.makeText(this, "Mohon pilih tanggal transaksi", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ // Show progress indicator
+ btnSend.text = "Mengirim..."
+ btnSend.isEnabled = false
+
+ // Proceed with the API call
+ uploadTopUpData(nominal, selectedPaymentId.toString(), transactionDate)
+ }
+
+ private fun uploadTopUpData(amount: String, paymentInfoId: String, transactionDate: String) {
+ lifecycleScope.launch {
+ try {
+ // Log the values being sent
+ android.util.Log.d("BalanceTopUp", "Amount: $amount")
+ android.util.Log.d("BalanceTopUp", "Payment ID: $paymentInfoId")
+ android.util.Log.d("BalanceTopUp", "Transaction Date: $transactionDate")
+
+ // Find the selected payment method to get bank name
+ val selectedPayment = paymentMethods.find { it.id.toString() == paymentInfoId }
+ if (selectedPayment == null) {
+ Toast.makeText(
+ this@BalanceTopUpActivity,
+ "Metode pembayaran tidak valid",
+ Toast.LENGTH_SHORT
+ ).show()
+ return@launch
+ }
+
+ val bankName = selectedPayment.bankName
+ val bankNum = selectedPayment.bankNum
+ android.util.Log.d("BalanceTopUp", "Bank Name: $bankName")
+ android.util.Log.d("BalanceTopUp", "Bank Number: $bankNum")
+
+ // Get the actual file from URI
+ val file = uriToFile(selectedImageUri!!)
+ android.util.Log.d("BalanceTopUp", "File size: ${file.length()} bytes")
+ android.util.Log.d("BalanceTopUp", "File name: ${file.name}")
+
+ // Create multipart file with specific JPEG content type
+ val requestFile = file.asRequestBody("image/jpeg".toMediaTypeOrNull())
+ val imagePart = MultipartBody.Part.createFormData("topupimg", file.name, requestFile)
+
+ // Create other request bodies - ensure proper formatting
+ // Make sure amount has no commas, spaces or currency symbols
+ val cleanedAmount = amount.replace("[^0-9]".toRegex(), "")
+ val amountBody = cleanedAmount.toRequestBody("text/plain".toMediaTypeOrNull())
+ val paymentInfoIdBody = paymentInfoId.toRequestBody("text/plain".toMediaTypeOrNull())
+ val transactionDateBody = transactionDate.toRequestBody("text/plain".toMediaTypeOrNull())
+ val bankNameBody = bankName.toRequestBody("text/plain".toMediaTypeOrNull())
+ val bankNumBody = bankNum.toRequestBody("text/plain".toMediaTypeOrNull())
+
+ // Make the API call
+ val response = ApiConfig.getApiService(sessionManager).addBalanceTopUp(
+ imagePart,
+ amountBody,
+ paymentInfoIdBody,
+ transactionDateBody,
+ bankNameBody,
+ bankNumBody
+ )
+
+ if (response.isSuccessful) {
+ // Log the complete response
+ val responseBody = response.body()
+ android.util.Log.d("BalanceTopUp", "Success response: ${responseBody?.message}")
+
+ // Show the actual message from backend
+ val successMessage = "Top Up Berhasil"
+ Toast.makeText(
+ this@BalanceTopUpActivity,
+ successMessage,
+ Toast.LENGTH_LONG
+ ).show()
+
+ // Show a dialog with the success message
+ runOnUiThread {
+ androidx.appcompat.app.AlertDialog.Builder(this@BalanceTopUpActivity)
+ .setTitle("Berhasil")
+ .setMessage(successMessage)
+ .setPositiveButton("OK") { dialog, _ ->
+ dialog.dismiss()
+ finish()
+ }
+ .show()
+ }
+ } else {
+ // Get more detailed error information
+ val errorBody = response.errorBody()?.string()
+ android.util.Log.e("BalanceTopUp", "Error body: $errorBody")
+ android.util.Log.e("BalanceTopUp", "Error code: ${response.code()}")
+
+ // Try to parse the error body to extract the message
+ var errorMessage = "Gagal mengirim permintaan: ${response.message() ?: "Error ${response.code()}"}"
+ try {
+ val jsonObject = org.json.JSONObject(errorBody ?: "{}")
+ if (jsonObject.has("message")) {
+ errorMessage = jsonObject.getString("message")
+ }
+ } catch (e: Exception) {
+ android.util.Log.e("BalanceTopUp", "Error parsing error body", e)
+ }
+
+ Toast.makeText(
+ this@BalanceTopUpActivity,
+ errorMessage,
+ Toast.LENGTH_LONG
+ ).show()
+
+ // Show a dialog with the error message
+ runOnUiThread {
+ androidx.appcompat.app.AlertDialog.Builder(this@BalanceTopUpActivity)
+ .setTitle("Error Response")
+ .setMessage(errorMessage)
+ .setPositiveButton("OK") { dialog, _ ->
+ dialog.dismiss()
+ }
+ .show()
+ }
+ }
+ } catch (e: Exception) {
+ android.util.Log.e("BalanceTopUp", "Exception: ${e.message}", e)
+ Toast.makeText(
+ this@BalanceTopUpActivity,
+ "Error: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ } finally {
+ // Reset button state
+ btnSend.text = "Kirim"
+ btnSend.isEnabled = true
+ }
+ }
+ }
+
+ private fun uriToFile(uri: Uri): File {
+ val inputStream = contentResolver.openInputStream(uri)
+ val tempFile = File.createTempFile("upload", ".jpg", cacheDir)
+ tempFile.deleteOnExit()
+
+ inputStream?.use { input ->
+ tempFile.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+
+ // Validate file isn't empty
+ if (tempFile.length() == 0L) {
+ throw IllegalStateException("File is empty")
+ }
+
+ return tempFile
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTransactionAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTransactionAdapter.kt
index e106b1d..0b96498 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTransactionAdapter.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTransactionAdapter.kt
@@ -1,7 +1,90 @@
package com.alya.ecommerce_serang.ui.profile.mystore.balance
-/* class BalanceTransactionAdapter(private val balanceTransactionList: List) :
- RecyclerView.Adapter() {
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.response.store.topup.TopUp
+import com.alya.ecommerce_serang.ui.profile.mystore.balance.BalanceTransactionAdapter.BalanceTransactionViewHolder
+class BalanceTransactionAdapter : ListAdapter(DIFF_CALLBACK) {
-}*/
\ No newline at end of file
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BalanceTransactionViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item_balance_transaction, parent, false)
+ return BalanceTransactionViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: BalanceTransactionViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+
+ inner class BalanceTransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ private val tvDate: TextView = itemView.findViewById(R.id.tv_balance_trans_date)
+ private val tvTitle: TextView = itemView.findViewById(R.id.tv_balance_trans_title)
+ private val tvDesc: TextView = itemView.findViewById(R.id.tv_balance_trans_desc)
+ private val tvAmount: TextView = itemView.findViewById(R.id.tv_balance_trans_amount)
+ private val ivIcon: ImageView = itemView.findViewById(R.id.iv_balance_trans_icon)
+ private val divider: View = itemView.findViewById(R.id.divider_balance_trans)
+
+ fun bind(topUp: TopUp) {
+ // Set date
+ tvDate.text = topUp.getFormattedDate()
+
+ // Set title
+ tvTitle.text = "Isi Ulang Saldo"
+
+ // Set description - payment details
+ val paymentMethod = topUp.paymentMethod
+ val accountName = topUp.accountName ?: ""
+ val desc = if (accountName.isNotEmpty()) {
+ "Isi ulang dari $paymentMethod $accountName"
+ } else {
+ "Isi ulang dari $paymentMethod"
+ }
+ tvDesc.text = desc
+
+ // Set amount
+ tvAmount.text = topUp.getFormattedAmount()
+
+ // Set color based on status
+ val context = itemView.context
+ val activeColor = ContextCompat.getColor(context, R.color.blue_500)
+ val pendingColor = ContextCompat.getColor(context, R.color.black_500)
+
+ when (topUp.status.lowercase()) {
+ "approved" -> {
+ tvAmount.setTextColor(activeColor)
+ ivIcon.setImageResource(R.drawable.ic_graph_arrow_increase)
+ }
+ "pending" -> {
+ tvAmount.setTextColor(pendingColor)
+ }
+ else -> {
+ tvAmount.setTextColor(activeColor)
+ }
+ }
+
+ // Show divider for all items except the last one
+ divider.visibility = if (bindingAdapterPosition == itemCount - 1) View.GONE else View.VISIBLE
+ }
+ }
+
+ companion object {
+ private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: TopUp, newItem: TopUp): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(oldItem: TopUp, newItem: TopUp): Boolean {
+ return oldItem == newItem
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/DetailStoreProfileActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/DetailStoreProfileActivity.kt
index 8e098d3..2200295 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/DetailStoreProfileActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/DetailStoreProfileActivity.kt
@@ -1,15 +1,22 @@
package com.alya.ecommerce_serang.ui.profile.mystore.profile
+import android.app.Activity
+import android.content.Intent
import android.os.Bundle
+import android.util.Log
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
+import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.Store
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.MyStoreRepository
import com.alya.ecommerce_serang.databinding.ActivityDetailStoreProfileBinding
+import com.alya.ecommerce_serang.ui.profile.mystore.profile.address.DetailStoreAddressActivity
+import com.alya.ecommerce_serang.ui.profile.mystore.profile.payment_info.PaymentInfoActivity
+import com.alya.ecommerce_serang.ui.profile.mystore.profile.shipping_service.ShippingServiceActivity
import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
@@ -39,6 +46,36 @@ class DetailStoreProfileActivity : AppCompatActivity() {
enableEdgeToEdge()
+ // Set up header title
+ binding.header.headerTitle.text = "Profil Toko"
+
+ // Set up back button
+ binding.header.headerLeftIcon.setOnClickListener {
+ onBackPressedDispatcher.onBackPressed()
+ }
+
+ binding.btnEditStoreProfile.setOnClickListener {
+ val intent = Intent(this, EditStoreProfileActivity::class.java)
+ startActivityForResult(intent, EDIT_PROFILE_REQUEST_CODE)
+ }
+
+ binding.layoutAddress.setOnClickListener {
+ val intent = Intent(this, DetailStoreAddressActivity::class.java)
+ startActivityForResult(intent, ADDRESS_REQUEST_CODE)
+ }
+
+ // Set up payment method layout click listener
+ binding.layoutPaymentMethod.setOnClickListener {
+ val intent = Intent(this, PaymentInfoActivity::class.java)
+ startActivityForResult(intent, PAYMENT_INFO_REQUEST_CODE)
+ }
+
+ // Set up shipping services layout click listener
+ binding.layoutShipServices.setOnClickListener {
+ val intent = Intent(this, ShippingServiceActivity::class.java)
+ startActivityForResult(intent, SHIPPING_SERVICES_REQUEST_CODE)
+ }
+
viewModel.loadMyStore()
viewModel.myStoreProfile.observe(this){ user ->
@@ -50,11 +87,64 @@ class DetailStoreProfileActivity : AppCompatActivity() {
}
}
- private fun updateStoreProfile(store: Store){
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (requestCode == EDIT_PROFILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+ // Refresh the profile data
+ Toast.makeText(this, "Profil toko berhasil diperbarui", Toast.LENGTH_SHORT).show()
+ viewModel.loadMyStore()
+ // Pass the result back to parent activity
+ setResult(Activity.RESULT_OK)
+ } else if (requestCode == ADDRESS_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+ // Refresh the profile data after address update
+ Toast.makeText(this, "Alamat toko berhasil diperbarui", Toast.LENGTH_SHORT).show()
+ viewModel.loadMyStore()
+
+ // Pass the result back to parent activity
+ setResult(Activity.RESULT_OK)
+ } else if (requestCode == PAYMENT_INFO_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+ // Refresh the profile data after payment method update
+ Toast.makeText(this, "Metode pembayaran berhasil diperbarui", Toast.LENGTH_SHORT).show()
+ viewModel.loadMyStore()
+
+ // Pass the result back to parent activity
+ setResult(Activity.RESULT_OK)
+ } else if (requestCode == SHIPPING_SERVICES_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+ // Refresh the profile data after shipping services update
+ Toast.makeText(this, "Layanan pengiriman berhasil diperbarui", Toast.LENGTH_SHORT).show()
+ viewModel.loadMyStore()
+
+ // Pass the result back to parent activity
+ setResult(Activity.RESULT_OK)
+ }
+ }
+
+ companion object {
+ private const val EDIT_PROFILE_REQUEST_CODE = 100
+ private const val ADDRESS_REQUEST_CODE = 101
+ private const val PAYMENT_INFO_REQUEST_CODE = 102
+ private const val SHIPPING_SERVICES_REQUEST_CODE = 103
+ }
+
+ private fun updateStoreProfile(store: Store){
+ // Update text fields
binding.edtNamaToko.setText(store.storeName.toString())
binding.edtJenisToko.setText(store.storeType.toString())
binding.edtDeskripsiToko.setText(store.storeDescription.toString())
+ // Update store image if available
+ if (store.storeImage != null && store.storeImage.toString().isNotEmpty() && store.storeImage.toString() != "null") {
+ val imageUrl = "http://192.168.100.156:3000${store.storeImage}"
+ Log.d("DetailStoreProfile", "Loading image from: $imageUrl")
+
+ Glide.with(this)
+ .load(imageUrl)
+ .placeholder(R.drawable.placeholder_image)
+ .error(R.drawable.placeholder_image)
+ .into(binding.ivProfile)
+ } else {
+ Log.d("DetailStoreProfile", "No store image available")
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/EditStoreProfileActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/EditStoreProfileActivity.kt
new file mode 100644
index 0000000..2d31f3b
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/EditStoreProfileActivity.kt
@@ -0,0 +1,304 @@
+package com.alya.ecommerce_serang.ui.profile.mystore.profile
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.provider.MediaStore
+import android.util.Log
+import android.view.View
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.dto.Store
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.databinding.ActivityEditStoreProfileBinding
+import com.alya.ecommerce_serang.utils.SessionManager
+import com.bumptech.glide.Glide
+import kotlinx.coroutines.launch
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
+import okhttp3.RequestBody.Companion.asRequestBody
+import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.File
+import java.io.FileOutputStream
+
+class EditStoreProfileActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityEditStoreProfileBinding
+ private lateinit var sessionManager: SessionManager
+ private var storeImageUri: Uri? = null
+ private lateinit var currentStore: Store
+
+ private val pickImage = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ result.data?.data?.let { uri ->
+ storeImageUri = uri
+ Log.d("EditStoreProfile", "Image selected: $uri")
+
+ // Set the image to the ImageView for immediate preview
+ try {
+ binding.ivStoreImage.setImageURI(null) // Clear any previous image
+ binding.ivStoreImage.setImageURI(uri)
+
+ // Alternative way using Glide for more reliable preview
+ Glide.with(this)
+ .load(uri)
+ .placeholder(R.drawable.placeholder_image)
+ .error(R.drawable.placeholder_image)
+ .into(binding.ivStoreImage)
+
+ Toast.makeText(this, "Gambar berhasil dipilih", Toast.LENGTH_SHORT).show()
+ } catch (e: Exception) {
+ Log.e("EditStoreProfile", "Error displaying image preview", e)
+ Toast.makeText(this, "Gagal menampilkan preview gambar", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityEditStoreProfileBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ sessionManager = SessionManager(this)
+
+ // Set up header
+ binding.header.headerTitle.text = "Edit Profil Toko"
+ binding.header.headerLeftIcon.setOnClickListener { finish() }
+
+ loadStoreData()
+ setupClickListeners()
+ }
+
+ private fun loadStoreData() {
+ binding.progressBar.visibility = View.VISIBLE
+ lifecycleScope.launch {
+ try {
+ val response = ApiConfig.getApiService(sessionManager).getStore()
+ binding.progressBar.visibility = View.GONE
+
+ if (response.isSuccessful && response.body() != null) {
+ currentStore = response.body()!!.store
+ populateFields(currentStore)
+ } else {
+ showError("Gagal memuat profil toko")
+ }
+ } catch (e: Exception) {
+ binding.progressBar.visibility = View.GONE
+ showError("Terjadi kesalahan: ${e.message}")
+ }
+ }
+ }
+
+ private fun populateFields(store: Store) {
+ // Load store image
+ if (store.storeImage != null && store.storeImage.toString().isNotEmpty() && store.storeImage.toString() != "null") {
+ Glide.with(this)
+ .load(store.storeImage.toString())
+ .placeholder(R.drawable.placeholder_image)
+ .error(R.drawable.placeholder_image)
+ .into(binding.ivStoreImage)
+ }
+
+ // Set other fields
+ binding.edtStoreName.setText(store.storeName)
+ binding.edtDescription.setText(store.storeDescription)
+ binding.edtUserPhone.setText(store.userPhone)
+
+ // Set is on leave
+ binding.switchIsOnLeave.isChecked = store.isOnLeave
+ }
+
+ private fun setupClickListeners() {
+ binding.btnSelectStoreImage.setOnClickListener {
+ val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
+ pickImage.launch(intent)
+ }
+
+ binding.btnSave.setOnClickListener {
+ saveStoreProfile()
+ }
+ }
+
+ private fun saveStoreProfile() {
+ val storeName = binding.edtStoreName.text.toString()
+ val storeDescription = binding.edtDescription.text.toString()
+ val userPhone = binding.edtUserPhone.text.toString()
+ val storeStatus = currentStore.storeStatus // Keep the current status
+ val isOnLeave = binding.switchIsOnLeave.isChecked
+
+ if (storeName.isEmpty() || userPhone.isEmpty()) {
+ showError("Nama toko dan nomor telepon harus diisi")
+ return
+ }
+
+ binding.progressBar.visibility = View.VISIBLE
+ binding.btnSave.isEnabled = false
+
+ // Show progress indicator on the image if we're uploading one
+ if (storeImageUri != null) {
+ binding.progressImage.visibility = View.VISIBLE
+ }
+
+ lifecycleScope.launch {
+ try {
+ Log.d("EditStoreProfile", "Starting profile update process")
+
+ // Create multipart request for image if selected
+ var storeImagePart: MultipartBody.Part? = null
+ if (storeImageUri != null) {
+ try {
+ val storeImageFile = uriToFile(storeImageUri!!)
+ Log.d("EditStoreProfile", "Image file created: ${storeImageFile.name}, size: ${storeImageFile.length()}")
+
+ // Get the MIME type
+ val mimeType = contentResolver.getType(storeImageUri!!) ?: "image/jpeg"
+ Log.d("EditStoreProfile", "MIME type: $mimeType")
+
+ val storeImageRequestBody = storeImageFile.asRequestBody(mimeType.toMediaTypeOrNull())
+ storeImagePart = MultipartBody.Part.createFormData("storeimg", storeImageFile.name, storeImageRequestBody)
+ Log.d("EditStoreProfile", "Image part created with name: storeimg, filename: ${storeImageFile.name}")
+ } catch (e: Exception) {
+ Log.e("EditStoreProfile", "Error creating image part", e)
+ runOnUiThread {
+ Toast.makeText(this@EditStoreProfileActivity, "Error preparing image: ${e.message}", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ // Create text parts
+ val nameRequestBody = storeName.toRequestBody("text/plain".toMediaTypeOrNull())
+ val descriptionRequestBody = storeDescription.toRequestBody("text/plain".toMediaTypeOrNull())
+ val userPhoneRequestBody = userPhone.toRequestBody("text/plain".toMediaTypeOrNull())
+ val statusRequestBody = storeStatus.toRequestBody("text/plain".toMediaTypeOrNull())
+ val onLeaveRequestBody = isOnLeave.toString().toRequestBody("text/plain".toMediaTypeOrNull())
+
+ // Log request parameters
+ Log.d("EditStoreProfile", "Request parameters: " +
+ "\nstore_name: $storeName" +
+ "\nstore_status: $storeStatus" +
+ "\nstore_description: $storeDescription" +
+ "\nis_on_leave: $isOnLeave" +
+ "\nuser_phone: $userPhone" +
+ "\nimage: ${storeImageUri != null}")
+
+ // Log all parts for debugging
+ Log.d("EditStoreProfile", "Request parts:" +
+ "\nstoreName: $nameRequestBody" +
+ "\nstoreStatus: $statusRequestBody" +
+ "\nstoreDescription: $descriptionRequestBody" +
+ "\nisOnLeave: $onLeaveRequestBody" +
+ "\nuserPhone: $userPhoneRequestBody" +
+ "\nstoreimg: ${storeImagePart != null}")
+
+ val response = ApiConfig.getApiService(sessionManager).updateStoreProfileMultipart(
+ storeName = nameRequestBody,
+ storeStatus = statusRequestBody,
+ storeDescription = descriptionRequestBody,
+ isOnLeave = onLeaveRequestBody,
+ cityId = currentStore.cityId.toString().toRequestBody("text/plain".toMediaTypeOrNull()),
+ provinceId = currentStore.provinceId.toString().toRequestBody("text/plain".toMediaTypeOrNull()),
+ street = currentStore.street.toRequestBody("text/plain".toMediaTypeOrNull()),
+ subdistrict = currentStore.subdistrict.toRequestBody("text/plain".toMediaTypeOrNull()),
+ detail = currentStore.detail.toRequestBody("text/plain".toMediaTypeOrNull()),
+ postalCode = currentStore.postalCode.toRequestBody("text/plain".toMediaTypeOrNull()),
+ latitude = currentStore.latitude.toRequestBody("text/plain".toMediaTypeOrNull()),
+ longitude = currentStore.longitude.toRequestBody("text/plain".toMediaTypeOrNull()),
+ userPhone = userPhoneRequestBody,
+ storeimg = storeImagePart
+ )
+
+ Log.d("EditStoreProfile", "Response received: isSuccessful=${response.isSuccessful}, code=${response.code()}")
+
+ runOnUiThread {
+ binding.progressBar.visibility = View.GONE
+ binding.progressImage.visibility = View.GONE
+ binding.btnSave.isEnabled = true
+
+ if (response.isSuccessful) {
+ Log.d("EditStoreProfile", "Response body: ${response.body()?.toString()}")
+ // Try to log the updated store image URL
+ response.body()?.let { responseBody ->
+ val updatedStoreImage = responseBody.store?.storeImage
+ Log.d("EditStoreProfile", "Updated store image URL: $updatedStoreImage")
+ }
+ showSuccess("Profil toko berhasil diperbarui")
+ setResult(Activity.RESULT_OK)
+ finish()
+ } else {
+ val errorBodyString = response.errorBody()?.string() ?: "Error body is null"
+ Log.e("EditStoreProfile", "Full error response: $errorBodyString")
+ Log.e("EditStoreProfile", "Response headers: ${response.headers()}")
+ showError("Gagal memperbarui profil toko (${response.code()})")
+ }
+ }
+ } catch (e: Exception) {
+ Log.e("EditStoreProfile", "Exception during API call", e)
+
+ runOnUiThread {
+ binding.progressBar.visibility = View.GONE
+ binding.progressImage.visibility = View.GONE
+ binding.btnSave.isEnabled = true
+ showError("Error: ${e.message}")
+ }
+ }
+ }
+ }
+
+ private fun uriToFile(uri: Uri): File {
+ val contentResolver = applicationContext.contentResolver
+ val fileExtension = getFileExtension(contentResolver, uri)
+ val timeStamp = System.currentTimeMillis()
+ val fileName = "IMG_${timeStamp}.$fileExtension"
+ val tempFile = File(cacheDir, fileName)
+
+ Log.d("EditStoreProfile", "Creating temp file: ${tempFile.absolutePath}")
+
+ try {
+ contentResolver.openInputStream(uri)?.use { inputStream ->
+ FileOutputStream(tempFile).use { outputStream ->
+ val buffer = ByteArray(4 * 1024) // 4k buffer
+ var bytesRead: Int
+ while (inputStream.read(buffer).also { bytesRead = it } != -1) {
+ outputStream.write(buffer, 0, bytesRead)
+ }
+ outputStream.flush()
+ }
+ }
+
+ Log.d("EditStoreProfile", "File created successfully: ${tempFile.name}, size: ${tempFile.length()} bytes")
+ return tempFile
+ } catch (e: Exception) {
+ Log.e("EditStoreProfile", "Error creating file from URI", e)
+ throw e
+ }
+ }
+
+ private fun getFileExtension(contentResolver: android.content.ContentResolver, uri: Uri): String {
+ val mimeType = contentResolver.getType(uri)
+ return if (mimeType != null) {
+ val mime = android.webkit.MimeTypeMap.getSingleton()
+ mime.getExtensionFromMimeType(mimeType) ?: "jpg"
+ } else {
+ // If mime type is null, try to get from URI path
+ val path = uri.path
+ if (path != null) {
+ val extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(path)
+ if (!extension.isNullOrEmpty()) {
+ extension
+ } else "jpg"
+ } else "jpg"
+ }
+ }
+
+ private fun showSuccess(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show()
+ }
+
+ private fun showError(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/address/DetailStoreAddressActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/address/DetailStoreAddressActivity.kt
index 94f258c..9ad1f8c 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/address/DetailStoreAddressActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/address/DetailStoreAddressActivity.kt
@@ -1,21 +1,315 @@
package com.alya.ecommerce_serang.ui.profile.mystore.profile.address
+import android.app.Activity
import android.os.Bundle
-import androidx.activity.enableEdgeToEdge
+import android.util.Log
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Toast
+import androidx.activity.viewModels
+import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowInsetsCompat
-import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.BuildConfig
+import com.alya.ecommerce_serang.data.api.dto.City
+import com.alya.ecommerce_serang.data.api.dto.Province
+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.AddressRepository
+import com.alya.ecommerce_serang.databinding.ActivityDetailStoreAddressBinding
+import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel
+import com.alya.ecommerce_serang.utils.BaseViewModelFactory
+import com.alya.ecommerce_serang.utils.SessionManager
+import com.google.android.material.snackbar.Snackbar
class DetailStoreAddressActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityDetailStoreAddressBinding
+ private lateinit var apiService: ApiService
+ private lateinit var sessionManager: SessionManager
+
+ private var selectedProvinceId: String? = null
+ private var provinces: List = emptyList()
+ private var cities: List = emptyList()
+
+ private val TAG = "StoreAddressActivity"
+
+ private val viewModel: AddressViewModel by viewModels {
+ BaseViewModelFactory {
+ val apiService = ApiConfig.getApiService(sessionManager)
+ val addressRepository = AddressRepository(apiService)
+ AddressViewModel(addressRepository)
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContentView(R.layout.activity_detail_store_address)
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
- val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
- v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
- insets
+ binding = ActivityDetailStoreAddressBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ sessionManager = SessionManager(this)
+ apiService = ApiConfig.getApiService(sessionManager)
+
+ // Log the base URL
+ Log.d(TAG, "BASE_URL: ${BuildConfig.BASE_URL}")
+
+ // Add error text view
+ binding.tvError.visibility = View.GONE
+
+ // Set up header title
+ binding.header.headerTitle.text = "Atur Alamat Toko"
+
+ // Set up back button
+ binding.header.headerLeftIcon.setOnClickListener {
+ onBackPressedDispatcher.onBackPressed()
+ }
+
+ setupSpinners()
+ setupObservers()
+ setupSaveButton()
+
+ // Add retry button
+ binding.btnRetry.setOnClickListener {
+ binding.tvError.visibility = View.GONE
+ binding.progressBar.visibility = View.VISIBLE
+ Log.d(TAG, "Retrying to fetch provinces...")
+ viewModel.fetchProvinces()
+ }
+
+ // Show loading spinners initially
+ showProvinceLoading(true)
+
+ // Load existing address data first
+ Log.d(TAG, "Fetching store address...")
+ viewModel.fetchStoreAddress()
+
+ // Load provinces data
+ Log.d(TAG, "Fetching provinces...")
+ viewModel.fetchProvinces()
+ }
+
+ private fun setupSpinners() {
+ // Province spinner listener
+ binding.spinnerProvince.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ Log.d(TAG, "Province selected at position: $position")
+ if (position > 0 && provinces.isNotEmpty()) {
+ selectedProvinceId = provinces[position - 1].provinceId
+ Log.d(TAG, "Selected province ID: $selectedProvinceId")
+ selectedProvinceId?.let {
+ Log.d(TAG, "Fetching cities for province ID: $it")
+ showCityLoading(true)
+ viewModel.fetchCities(it)
+ }
+ }
+ }
+
+ override fun onNothingSelected(p0: AdapterView<*>?) {
+ // Do nothing
+ }
+ }
+ }
+
+ private fun setupObservers() {
+ // Observe provinces data
+ viewModel.provinces.observe(this) { provinceList ->
+ Log.d(TAG, "Received provinces: ${provinceList.size}")
+ showProvinceLoading(false)
+
+ if (provinceList.isEmpty()) {
+ showError("No provinces available")
+ return@observe
+ }
+
+ provinces = provinceList
+ val provinceNames = mutableListOf("Pilih Provinsi")
+ provinceNames.addAll(provinceList.map { it.provinceName })
+
+ Log.d(TAG, "Province names: $provinceNames")
+
+ val adapter = ArrayAdapter(
+ this,
+ android.R.layout.simple_spinner_item,
+ provinceNames
+ )
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ binding.spinnerProvince.adapter = adapter
+ }
+
+ // Observe cities data
+ viewModel.cities.observe(this) { cityList ->
+ Log.d(TAG, "Received cities: ${cityList.size}")
+ showCityLoading(false)
+
+ cities = cityList
+ val cityNames = mutableListOf("Pilih Kota/Kabupaten")
+ cityNames.addAll(cityList.map { it.cityName })
+
+ Log.d(TAG, "City names: $cityNames")
+
+ val adapter = ArrayAdapter(
+ this,
+ android.R.layout.simple_spinner_item,
+ cityNames
+ )
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ binding.spinnerCity.adapter = adapter
+
+ // If we have a stored city_id, select it
+ viewModel.storeAddress.value?.let { address ->
+ if (address.cityId.isNotEmpty()) {
+ val cityIndex = cities.indexOfFirst { city ->
+ city.cityId == address.cityId
+ }
+ Log.d(TAG, "City index for ID ${address.cityId}: $cityIndex")
+ if (cityIndex != -1) {
+ binding.spinnerCity.setSelection(cityIndex + 1) // +1 because of "Pilih Kota/Kabupaten"
+ }
+ }
+ }
+ }
+
+ // Observe store address data
+ viewModel.storeAddress.observe(this) { address ->
+ Log.d(TAG, "Received store address: $address")
+ address?.let {
+ // Set the fields
+ binding.edtStreet.setText(address.street)
+ binding.edtSubdistrict.setText(address.subdistrict)
+ binding.edtDetailAddress.setText(address.detail ?: "")
+ binding.edtPostalCode.setText(address.postalCode)
+
+ // Handle latitude and longitude
+ val lat = if (address.latitude == null || address.latitude.toString() == "NaN") 0.0 else address.latitude
+ val lng = if (address.longitude == null || address.longitude.toString() == "NaN") 0.0 else address.longitude
+
+ binding.edtLatitude.setText(lat.toString())
+ binding.edtLongitude.setText(lng.toString())
+
+ // Set selected province ID to trigger city loading
+ if (address.provinceId.isNotEmpty()) {
+ selectedProvinceId = address.provinceId
+
+ // Find province index and select it after provinces are loaded
+ if (provinces.isNotEmpty()) {
+ val provinceIndex = provinces.indexOfFirst { province ->
+ province.provinceId == address.provinceId
+ }
+ Log.d(TAG, "Province index for ID ${address.provinceId}: $provinceIndex")
+ if (provinceIndex != -1) {
+ binding.spinnerProvince.setSelection(provinceIndex + 1) // +1 because of "Pilih Provinsi"
+
+ // Now fetch cities for this province
+ showCityLoading(true)
+ viewModel.fetchCities(address.provinceId)
+ }
+ }
+ }
+ }
+ }
+
+ // Observe loading state
+ viewModel.isLoading.observe(this) { isLoading ->
+ binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
+ }
+
+ // Observe error messages
+ viewModel.errorMessage.observe(this) { errorMsg ->
+ Log.e(TAG, "Error: $errorMsg")
+ showError(errorMsg)
+ }
+
+ // Observe save success
+ viewModel.saveSuccess.observe(this) { success ->
+ if (success) {
+ Toast.makeText(this, "Alamat berhasil disimpan", Toast.LENGTH_SHORT).show()
+ setResult(Activity.RESULT_OK)
+ finish()
+ }
+ }
+ }
+
+ private fun showProvinceLoading(isLoading: Boolean) {
+ binding.provinceProgressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
+ binding.spinnerProvince.visibility = if (isLoading) View.GONE else View.VISIBLE
+ }
+
+ private fun showCityLoading(isLoading: Boolean) {
+ binding.cityProgressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
+ binding.spinnerCity.visibility = if (isLoading) View.GONE else View.VISIBLE
+ }
+
+ private fun showError(message: String) {
+ binding.progressBar.visibility = View.GONE
+ binding.tvError.visibility = View.VISIBLE
+ binding.tvError.text = "Error: $message\nURL: ${BuildConfig.BASE_URL}/provinces"
+ binding.btnRetry.visibility = View.VISIBLE
+
+ // Also show in a dialog for immediate attention
+ AlertDialog.Builder(this)
+ .setTitle("Error")
+ .setMessage("$message\n\nAPI URL: ${BuildConfig.BASE_URL}/provinces")
+ .setPositiveButton("Retry") { _, _ ->
+ binding.tvError.visibility = View.GONE
+ binding.progressBar.visibility = View.VISIBLE
+ viewModel.fetchProvinces()
+ }
+ .setNegativeButton("Cancel") { dialog, _ ->
+ dialog.dismiss()
+ }
+ .show()
+
+ // Also show a snackbar
+ Snackbar.make(binding.root, "Error: $message", Snackbar.LENGTH_LONG)
+ .setAction("Retry") {
+ binding.tvError.visibility = View.GONE
+ binding.progressBar.visibility = View.VISIBLE
+ viewModel.fetchProvinces()
+ }
+ .show()
+ }
+
+ private fun setupSaveButton() {
+ binding.btnSaveAddress.setOnClickListener {
+ val street = binding.edtStreet.text.toString()
+ val subdistrict = binding.edtSubdistrict.text.toString()
+ val detail = binding.edtDetailAddress.text.toString()
+ val postalCode = binding.edtPostalCode.text.toString()
+ val latitudeStr = binding.edtLatitude.text.toString()
+ val longitudeStr = binding.edtLongitude.text.toString()
+
+ // Validate required fields
+ if (selectedProvinceId == null || binding.spinnerCity.selectedItemPosition <= 0 ||
+ street.isEmpty() || subdistrict.isEmpty() || postalCode.isEmpty()) {
+ Toast.makeText(this, "Mohon lengkapi data yang wajib diisi", Toast.LENGTH_SHORT).show()
+ return@setOnClickListener
+ }
+
+ // Get selected city
+ val cityPosition = binding.spinnerCity.selectedItemPosition
+ if (cityPosition <= 0 || cities.isEmpty() || cityPosition > cities.size) {
+ Toast.makeText(this, "Mohon pilih kota/kabupaten", Toast.LENGTH_SHORT).show()
+ return@setOnClickListener
+ }
+
+ val selectedCity = cities[cityPosition - 1]
+
+ // Parse coordinates
+ val latitude = latitudeStr.toDoubleOrNull() ?: 0.0
+ val longitude = longitudeStr.toDoubleOrNull() ?: 0.0
+
+ // Save address
+ viewModel.saveStoreAddress(
+ provinceId = selectedProvinceId!!,
+ provinceName = provinces.find { it.provinceId == selectedProvinceId }?.provinceName ?: "",
+ cityId = selectedCity.cityId,
+ cityName = selectedCity.cityName,
+ street = street,
+ subdistrict = subdistrict,
+ detail = detail,
+ postalCode = postalCode,
+ latitude = latitude,
+ longitude = longitude
+ )
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/payment_info/PaymentInfoActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/payment_info/PaymentInfoActivity.kt
index ad7c97d..bcb692a 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/payment_info/PaymentInfoActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/payment_info/PaymentInfoActivity.kt
@@ -1,21 +1,282 @@
package com.alya.ecommerce_serang.ui.profile.mystore.profile.payment_info
+import android.app.Activity
+import android.app.AlertDialog
+import android.net.Uri
import android.os.Bundle
-import androidx.activity.enableEdgeToEdge
+import android.util.Log
+import android.view.View
+import android.widget.Button
+import android.widget.EditText
+import android.widget.ImageView
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowInsetsCompat
+import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.dto.PaymentInfo
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.data.repository.PaymentInfoRepository
+import com.alya.ecommerce_serang.databinding.ActivityPaymentInfoBinding
+import com.alya.ecommerce_serang.utils.BaseViewModelFactory
+import com.alya.ecommerce_serang.utils.SessionManager
+import com.alya.ecommerce_serang.utils.UriToFileConverter
+import com.alya.ecommerce_serang.utils.viewmodel.PaymentInfoViewModel
+import com.google.android.material.snackbar.Snackbar
+import java.io.File
class PaymentInfoActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContentView(R.layout.activity_payment_info)
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
- val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
- v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
- insets
+ private val TAG = "PaymentInfoActivity"
+ private lateinit var binding: ActivityPaymentInfoBinding
+ private lateinit var adapter: PaymentInfoAdapter
+ private lateinit var sessionManager: SessionManager
+ private var selectedQrisImageUri: Uri? = null
+ private var selectedQrisImageFile: File? = null
+
+ // Store form data between dialog reopenings
+ private var savedBankName: String = ""
+ private var savedBankNumber: String = ""
+ private var savedAccountName: String = ""
+
+ private val viewModel: PaymentInfoViewModel by viewModels {
+ BaseViewModelFactory {
+ val apiService = ApiConfig.getApiService(sessionManager)
+ val repository = PaymentInfoRepository(apiService)
+ PaymentInfoViewModel(repository)
}
}
+
+ private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
+ uri?.let {
+ try {
+ Log.d(TAG, "Selected image URI: $uri")
+ selectedQrisImageUri = it
+
+ // Convert URI to File
+ selectedQrisImageFile = UriToFileConverter.uriToFile(it, this)
+
+ if (selectedQrisImageFile == null) {
+ Log.e(TAG, "Failed to convert URI to file")
+ showSnackbar("Failed to process image. Please try another image.")
+ return@let
+ }
+
+ Log.d(TAG, "Converted to file: ${selectedQrisImageFile?.absolutePath}, size: ${selectedQrisImageFile?.length()} bytes")
+
+ // Check if file exists and has content
+ if (!selectedQrisImageFile!!.exists() || selectedQrisImageFile!!.length() == 0L) {
+ Log.e(TAG, "File doesn't exist or is empty: ${selectedQrisImageFile?.absolutePath}")
+ showSnackbar("Failed to process image. Please try another image.")
+ selectedQrisImageFile = null
+ return@let
+ }
+
+ showAddPaymentDialog(true) // Reopen dialog with selected image
+ } catch (e: Exception) {
+ Log.e(TAG, "Error processing selected image", e)
+ showSnackbar("Error processing image: ${e.message}")
+ selectedQrisImageUri = null
+ selectedQrisImageFile = null
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityPaymentInfoBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ sessionManager = SessionManager(this)
+
+ // Configure header
+ binding.header.headerTitle.text = "Atur Metode Pembayaran"
+
+ binding.header.headerLeftIcon.setOnClickListener {
+ onBackPressedDispatcher.onBackPressed()
+ }
+
+ setupRecyclerView()
+ setupObservers()
+
+ binding.btnAddPayment.setOnClickListener {
+ // Clear saved values when opening a new dialog
+ savedBankName = ""
+ savedBankNumber = ""
+ savedAccountName = ""
+ selectedQrisImageUri = null
+ selectedQrisImageFile = null
+ showAddPaymentDialog(false)
+ }
+
+ // Load payment info
+ viewModel.getPaymentInfo()
+ }
+
+ private fun setupRecyclerView() {
+ adapter = PaymentInfoAdapter(
+ onDeleteClick = { paymentMethod ->
+ showDeleteConfirmationDialog(paymentMethod)
+ }
+ )
+ binding.rvPaymentInfo.layoutManager = LinearLayoutManager(this)
+ binding.rvPaymentInfo.adapter = adapter
+ }
+
+ private fun setupObservers() {
+ viewModel.paymentInfos.observe(this) { paymentInfo ->
+ binding.progressBar.visibility = View.GONE
+
+ if (paymentInfo.isEmpty()) {
+ binding.tvEmptyState.visibility = View.VISIBLE
+ binding.rvPaymentInfo.visibility = View.GONE
+ } else {
+ binding.tvEmptyState.visibility = View.GONE
+ binding.rvPaymentInfo.visibility = View.VISIBLE
+ adapter.submitList(paymentInfo)
+ }
+ }
+
+ viewModel.isLoading.observe(this) { isLoading ->
+ binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
+ binding.btnAddPayment.isEnabled = !isLoading
+ }
+
+ viewModel.errorMessage.observe(this) { errorMessage ->
+ if (errorMessage.isNotEmpty()) {
+ showSnackbar(errorMessage)
+ Log.e(TAG, "Error: $errorMessage")
+ }
+ }
+
+ viewModel.addPaymentSuccess.observe(this) { success ->
+ if (success) {
+ showSnackbar("Metode pembayaran berhasil ditambahkan")
+ setResult(Activity.RESULT_OK)
+ }
+ }
+
+ viewModel.deletePaymentSuccess.observe(this) { success ->
+ if (success) {
+ showSnackbar("Metode pembayaran berhasil dihapus")
+ setResult(Activity.RESULT_OK)
+ }
+ }
+ }
+
+ private fun showSnackbar(message: String) {
+ Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show()
+ }
+
+ private fun showAddPaymentDialog(isReopened: Boolean) {
+ val builder = AlertDialog.Builder(this)
+ val dialogView = layoutInflater.inflate(R.layout.dialog_add_payment_info, null)
+ builder.setView(dialogView)
+
+ val dialog = builder.create()
+
+ // Get references to views in the dialog
+ val btnAddQris = dialogView.findViewById