mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-10 09:22:21 +00:00
edit store profile & topup
This commit is contained in:
@ -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<PaymentInfo>
|
||||
)
|
||||
|
||||
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?
|
||||
)
|
@ -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<String>
|
||||
)
|
@ -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
|
||||
)
|
@ -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
|
||||
)
|
@ -4,9 +4,9 @@ import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class StoreDataResponse(
|
||||
val message: String,
|
||||
val store: Store,
|
||||
val shipping: List<Shipping>,
|
||||
val payment: List<Payment>
|
||||
val store: Store? = null,
|
||||
val shipping: List<Shipping>? = emptyList(),
|
||||
val payment: List<Payment> = emptyList()
|
||||
)
|
||||
|
||||
data class Store(
|
||||
@ -51,5 +51,6 @@ 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("qris_image") val qrisImage: String?,
|
||||
@SerializedName("account_name") val accountName: String?
|
||||
)
|
@ -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<TopUp>
|
||||
)
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ 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
|
||||
@ -14,6 +15,7 @@ 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.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
|
||||
@ -51,8 +53,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
|
||||
@ -151,8 +155,15 @@ interface ApiService {
|
||||
): Response<CreateAddressResponse>
|
||||
|
||||
@GET("mystore")
|
||||
suspend fun getStore (): Response<StoreResponse>
|
||||
suspend fun getStore(): Response<StoreResponse>
|
||||
|
||||
@GET("mystore")
|
||||
suspend fun getStoreData(): Response<StoreDataResponse>
|
||||
|
||||
@GET("mystore")
|
||||
suspend fun getMyStoreData(): Response<com.alya.ecommerce_serang.data.api.response.store.StoreResponse>
|
||||
|
||||
@GET("mystore")
|
||||
suspend fun getStoreAddress(): Response<StoreAddressResponse>
|
||||
|
||||
@GET("mystore/product") // Replace with actual endpoint
|
||||
@ -242,6 +253,12 @@ interface ApiService {
|
||||
@Part complaintimg: MultipartBody.Part
|
||||
): Response<ComplaintResponse>
|
||||
|
||||
@GET("store/topup")
|
||||
suspend fun getTopUpHistory(): Response<TopUpResponse>
|
||||
|
||||
@GET("store/topup")
|
||||
suspend fun getFilteredTopUpHistory(@Query("date") date: String): Response<TopUpResponse>
|
||||
|
||||
@Multipart
|
||||
@POST("store/createtopup")
|
||||
suspend fun addBalanceTopUp(
|
||||
@ -253,11 +270,6 @@ interface ApiService {
|
||||
@Part("bank_num") bankNum: RequestBody
|
||||
): Response<BalanceTopUpResponse>
|
||||
|
||||
@PUT("mystore/edit")
|
||||
suspend fun updateStoreProfile(
|
||||
@Body requestBody: okhttp3.RequestBody
|
||||
): Response<StoreDataResponse>
|
||||
|
||||
@Multipart
|
||||
@PUT("mystore/edit")
|
||||
suspend fun updateStoreProfileMultipart(
|
||||
@ -277,6 +289,40 @@ interface ApiService {
|
||||
@Part storeimg: MultipartBody.Part?
|
||||
): Response<StoreDataResponse>
|
||||
|
||||
@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<GenericResponse>
|
||||
|
||||
@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<AddPaymentInfoResponse>
|
||||
|
||||
@DELETE("mystore/payment/delete/{id}")
|
||||
suspend fun deletePaymentInfo(
|
||||
@Path("id") paymentMethodId: Int
|
||||
): Response<GenericResponse>
|
||||
|
||||
// Shipping Service API endpoints
|
||||
@POST("mystore/shipping/add")
|
||||
suspend fun addShippingService(
|
||||
@Body request: ShippingServiceRequest
|
||||
): Response<GenericResponse>
|
||||
|
||||
@POST("mystore/shipping/delete")
|
||||
suspend fun deleteShippingService(
|
||||
@Body request: ShippingServiceRequest
|
||||
): Response<GenericResponse>
|
||||
|
||||
@GET("provinces")
|
||||
suspend fun getProvinces(): Response<ProvinceResponse>
|
||||
|
||||
|
@ -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<PaymentInfo> = 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<PaymentInfo>()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String> = 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<String>): 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<String>): 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
|
||||
}
|
||||
}
|
||||
}
|
@ -161,7 +161,7 @@ class CheckoutActivity : AppCompatActivity() {
|
||||
viewModel.setPaymentMethod(1)
|
||||
}
|
||||
|
||||
binding.rvPaymentMethods.apply {
|
||||
binding.rvPaymentInfo.apply {
|
||||
layoutManager = LinearLayoutManager(this@CheckoutActivity)
|
||||
adapter = paymentAdapter
|
||||
}
|
||||
@ -187,7 +187,7 @@ class CheckoutActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
binding.rvPaymentMethods.apply {
|
||||
binding.rvPaymentInfo.apply {
|
||||
layoutManager = LinearLayoutManager(this@CheckoutActivity)
|
||||
adapter = testAdapter
|
||||
}
|
||||
|
@ -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,6 +18,7 @@ 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.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
|
||||
@ -64,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")
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,19 +91,26 @@ class MyStoreActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
binding.tvHistory.setOnClickListener {
|
||||
navigateToSellsFragment("all")
|
||||
val intent = Intent(this, SellsActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
binding.layoutPerluTagihan.setOnClickListener {
|
||||
navigateToSellsFragment("pending")
|
||||
val intent = Intent(this, SellsActivity::class.java)
|
||||
startActivity(intent)
|
||||
//navigateToSellsFragment("pending")
|
||||
}
|
||||
|
||||
binding.layoutPembayaran.setOnClickListener {
|
||||
navigateToSellsFragment("paid")
|
||||
val intent = Intent(this, SellsActivity::class.java)
|
||||
startActivity(intent)
|
||||
//navigateToSellsFragment("paid")
|
||||
}
|
||||
|
||||
binding.layoutPerluDikirim.setOnClickListener {
|
||||
navigateToSellsFragment("processed")
|
||||
val intent = Intent(this, SellsActivity::class.java)
|
||||
startActivity(intent)
|
||||
//navigateToSellsFragment("processed")
|
||||
}
|
||||
|
||||
binding.layoutProductMenu.setOnClickListener {
|
||||
@ -115,11 +132,24 @@ class MyStoreActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToSellsFragment(status: String) {
|
||||
val sellsFragment = SellsListFragment.newInstance(status)
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(android.R.id.content, sellsFragment)
|
||||
.addToBackStack(null)
|
||||
.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()
|
||||
// }
|
||||
}
|
@ -1,16 +1,38 @@
|
||||
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<TopUp> = emptyList()
|
||||
|
||||
private val TAG = "BalanceActivity"
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -24,13 +46,369 @@ class BalanceActivity : AppCompatActivity() {
|
||||
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)
|
||||
startActivity(intent)
|
||||
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<TopUp>) {
|
||||
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
|
||||
}
|
||||
}
|
@ -80,7 +80,7 @@ class BalanceTopUpActivity : AppCompatActivity() {
|
||||
// Setup back button
|
||||
val backButton = findViewById<ImageView>(R.id.header_left_icon)
|
||||
backButton.setOnClickListener {
|
||||
finish()
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
// Setup photo selection
|
||||
|
@ -1,7 +1,90 @@
|
||||
package com.alya.ecommerce_serang.ui.profile.mystore.balance
|
||||
|
||||
/* class BalanceTransactionAdapter(private val balanceTransactionList: List<BalanceTransaction>) :
|
||||
RecyclerView.Adapter<BalanceTransactionAdapter.TransactionViewHolder>() {
|
||||
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<TopUp, BalanceTransactionViewHolder>(DIFF_CALLBACK) {
|
||||
|
||||
}*/
|
||||
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<TopUp>() {
|
||||
override fun areItemsTheSame(oldItem: TopUp, newItem: TopUp): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: TopUp, newItem: TopUp): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,8 @@ 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
|
||||
@ -59,7 +61,19 @@ class DetailStoreProfileActivity : AppCompatActivity() {
|
||||
|
||||
binding.layoutAddress.setOnClickListener {
|
||||
val intent = Intent(this, DetailStoreAddressActivity::class.java)
|
||||
startActivity(intent)
|
||||
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()
|
||||
@ -87,6 +101,20 @@ class DetailStoreProfileActivity : AppCompatActivity() {
|
||||
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)
|
||||
}
|
||||
@ -95,6 +123,8 @@ class DetailStoreProfileActivity : AppCompatActivity() {
|
||||
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){
|
||||
@ -105,7 +135,7 @@ class DetailStoreProfileActivity : AppCompatActivity() {
|
||||
|
||||
// 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}"
|
||||
val imageUrl = "http://192.168.100.156:3000${store.storeImage}"
|
||||
Log.d("DetailStoreProfile", "Loading image from: $imageUrl")
|
||||
|
||||
Glide.with(this)
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.alya.ecommerce_serang.ui.profile.mystore.profile.address
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
@ -9,6 +8,7 @@ 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 com.alya.ecommerce_serang.BuildConfig
|
||||
import com.alya.ecommerce_serang.data.api.dto.City
|
||||
@ -56,7 +56,7 @@ class DetailStoreAddressActivity : AppCompatActivity() {
|
||||
binding.tvError.visibility = View.GONE
|
||||
|
||||
// Set up header title
|
||||
binding.header.headerTitle.text = "Alamat Toko"
|
||||
binding.header.headerTitle.text = "Atur Alamat Toko"
|
||||
|
||||
// Set up back button
|
||||
binding.header.headerLeftIcon.setOnClickListener {
|
||||
@ -182,6 +182,9 @@ class DetailStoreAddressActivity : AppCompatActivity() {
|
||||
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
|
||||
@ -271,8 +274,8 @@ class DetailStoreAddressActivity : AppCompatActivity() {
|
||||
val subdistrict = binding.edtSubdistrict.text.toString()
|
||||
val detail = binding.edtDetailAddress.text.toString()
|
||||
val postalCode = binding.edtPostalCode.text.toString()
|
||||
val latitudeStr = TODO()
|
||||
val longitudeStr = TODO()
|
||||
val latitudeStr = binding.edtLatitude.text.toString()
|
||||
val longitudeStr = binding.edtLongitude.text.toString()
|
||||
|
||||
// Validate required fields
|
||||
if (selectedProvinceId == null || binding.spinnerCity.selectedItemPosition <= 0 ||
|
||||
|
@ -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<Button>(R.id.btn_add_qris)
|
||||
val bankNameEditText = dialogView.findViewById<EditText>(R.id.edt_bank_name)
|
||||
val bankNumberEditText = dialogView.findViewById<EditText>(R.id.edt_bank_number)
|
||||
val accountNameEditText = dialogView.findViewById<EditText>(R.id.edt_account_name)
|
||||
val qrisPreview = dialogView.findViewById<ImageView>(R.id.iv_qris_preview)
|
||||
val btnCancel = dialogView.findViewById<Button>(R.id.btn_cancel)
|
||||
val btnSave = dialogView.findViewById<Button>(R.id.btn_save)
|
||||
|
||||
// When reopening, restore the previously entered values
|
||||
if (isReopened) {
|
||||
bankNameEditText.setText(savedBankName)
|
||||
bankNumberEditText.setText(savedBankNumber)
|
||||
accountNameEditText.setText(savedAccountName)
|
||||
|
||||
if (selectedQrisImageUri != null) {
|
||||
Log.d(TAG, "Showing selected QRIS image: $selectedQrisImageUri")
|
||||
qrisPreview.setImageURI(selectedQrisImageUri)
|
||||
qrisPreview.visibility = View.VISIBLE
|
||||
showSnackbar("Gambar QRIS berhasil dipilih")
|
||||
}
|
||||
}
|
||||
|
||||
btnAddQris.setOnClickListener {
|
||||
// Save the current values before dismissing
|
||||
savedBankName = bankNameEditText.text.toString().trim()
|
||||
savedBankNumber = bankNumberEditText.text.toString().trim()
|
||||
savedAccountName = accountNameEditText.text.toString().trim()
|
||||
|
||||
getContent.launch("image/*")
|
||||
dialog.dismiss() // Dismiss the current dialog as we'll reopen it
|
||||
}
|
||||
|
||||
btnCancel.setOnClickListener {
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
btnSave.setOnClickListener {
|
||||
val bankName = bankNameEditText.text.toString().trim()
|
||||
val bankNumber = bankNumberEditText.text.toString().trim()
|
||||
val accountName = accountNameEditText.text.toString().trim()
|
||||
|
||||
// Validation
|
||||
if (bankName.isEmpty()) {
|
||||
showSnackbar("Nama bank tidak boleh kosong")
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
if (bankNumber.isEmpty()) {
|
||||
showSnackbar("Nomor rekening tidak boleh kosong")
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
if (accountName.isEmpty()) {
|
||||
showSnackbar("Nama pemilik rekening tidak boleh kosong")
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
if (bankNumber.any { !it.isDigit() }) {
|
||||
showSnackbar("Nomor rekening hanya boleh berisi angka")
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
// Log the data being sent
|
||||
Log.d(TAG, "====== SENDING PAYMENT METHOD DATA ======")
|
||||
Log.d(TAG, "Bank Name: $bankName")
|
||||
Log.d(TAG, "Bank Number: $bankNumber")
|
||||
Log.d(TAG, "Account Name: $accountName")
|
||||
if (selectedQrisImageFile != null) {
|
||||
Log.d(TAG, "QRIS file path: ${selectedQrisImageFile?.absolutePath}")
|
||||
Log.d(TAG, "QRIS file exists: ${selectedQrisImageFile?.exists()}")
|
||||
Log.d(TAG, "QRIS file size: ${selectedQrisImageFile?.length()} bytes")
|
||||
} else {
|
||||
Log.d(TAG, "No QRIS file selected")
|
||||
}
|
||||
|
||||
// Temporarily disable the save button
|
||||
btnSave.isEnabled = false
|
||||
btnSave.text = "Menyimpan..."
|
||||
|
||||
// Add payment info
|
||||
viewModel.addPaymentInfo(
|
||||
bankName = bankName,
|
||||
bankNumber = bankNumber,
|
||||
accountName = accountName,
|
||||
qrisImageUri = selectedQrisImageUri,
|
||||
qrisImageFile = selectedQrisImageFile
|
||||
)
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun showDeleteConfirmationDialog(paymentInfo: PaymentInfo) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Hapus Metode Pembayaran")
|
||||
.setMessage("Apakah Anda yakin ingin menghapus metode pembayaran ini?")
|
||||
.setPositiveButton("Hapus") { _, _ ->
|
||||
viewModel.deletePaymentInfo(paymentInfo.id)
|
||||
}
|
||||
.setNegativeButton("Batal", null)
|
||||
.show()
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package com.alya.ecommerce_serang.ui.profile.mystore.profile.payment_info
|
||||
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
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.dto.PaymentInfo
|
||||
import com.bumptech.glide.Glide
|
||||
|
||||
class PaymentInfoAdapter(
|
||||
private val onDeleteClick: (PaymentInfo) -> Unit
|
||||
) : ListAdapter<PaymentInfo, PaymentInfoAdapter.PaymentInfoViewHolder>(DIFF_CALLBACK) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PaymentInfoViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_payment_info, parent, false)
|
||||
return PaymentInfoViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PaymentInfoViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
inner class PaymentInfoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val tvBankName: TextView = itemView.findViewById(R.id.tv_bank_name)
|
||||
private val tvAccountName: TextView = itemView.findViewById(R.id.tv_account_name)
|
||||
private val tvBankNumber: TextView = itemView.findViewById(R.id.tv_bank_number)
|
||||
private val ivDelete: ImageView = itemView.findViewById(R.id.iv_delete)
|
||||
private val layoutQris: LinearLayout = itemView.findViewById(R.id.layout_qris)
|
||||
private val ivQris: ImageView = itemView.findViewById(R.id.iv_qris)
|
||||
|
||||
fun bind(paymentInfo: PaymentInfo) {
|
||||
tvBankName.text = paymentInfo.bankName
|
||||
tvAccountName.text = paymentInfo.accountName ?: ""
|
||||
tvBankNumber.text = paymentInfo.bankNum
|
||||
|
||||
// Handle QRIS image if available
|
||||
if (paymentInfo.qrisImage != null && paymentInfo.qrisImage.isNotEmpty() && paymentInfo.qrisImage != "null") {
|
||||
layoutQris.visibility = View.VISIBLE
|
||||
// Make sure the URL is correct by handling both relative and absolute paths
|
||||
val imageUrl = if (paymentInfo.qrisImage.startsWith("http")) {
|
||||
paymentInfo.qrisImage
|
||||
} else {
|
||||
"http://192.168.100.156:3000${paymentInfo.qrisImage}"
|
||||
}
|
||||
|
||||
Log.d("PaymentMethodAdapter", "Loading QRIS image from: $imageUrl")
|
||||
|
||||
Glide.with(itemView.context)
|
||||
.load(imageUrl)
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.error(R.drawable.placeholder_image)
|
||||
.into(ivQris)
|
||||
} else {
|
||||
layoutQris.visibility = View.GONE
|
||||
}
|
||||
|
||||
ivDelete.setOnClickListener {
|
||||
onDeleteClick(paymentInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<PaymentInfo>() {
|
||||
override fun areItemsTheSame(oldItem: PaymentInfo, newItem: PaymentInfo): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: PaymentInfo, newItem: PaymentInfo): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +1,108 @@
|
||||
package com.alya.ecommerce_serang.ui.profile.mystore.profile.shipping_service
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import android.view.View
|
||||
import android.widget.CheckBox
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
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.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.repository.ShippingServiceRepository
|
||||
import com.alya.ecommerce_serang.databinding.ActivityShippingServiceBinding
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import com.alya.ecommerce_serang.utils.viewmodel.ShippingServiceViewModel
|
||||
|
||||
class ShippingServiceActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContentView(R.layout.activity_shipping_service)
|
||||
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 binding: ActivityShippingServiceBinding
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private val courierCheckboxes = mutableListOf<Pair<CheckBox, String>>()
|
||||
|
||||
private val viewModel: ShippingServiceViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val repository = ShippingServiceRepository(apiService)
|
||||
ShippingServiceViewModel(repository)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityShippingServiceBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
sessionManager = SessionManager(this)
|
||||
|
||||
// Configure header
|
||||
binding.header.headerTitle.text = "Atur Layanan Pengiriman"
|
||||
|
||||
binding.header.headerLeftIcon.setOnClickListener {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
setupCourierCheckboxes()
|
||||
setupObservers()
|
||||
|
||||
binding.btnSave.setOnClickListener {
|
||||
saveShippingServices()
|
||||
}
|
||||
|
||||
// Load shipping services
|
||||
viewModel.getAvailableCouriers()
|
||||
}
|
||||
|
||||
private fun setupCourierCheckboxes() {
|
||||
// Add all courier checkboxes to the list for easy management
|
||||
courierCheckboxes.add(Pair(binding.checkboxJne, "jne"))
|
||||
courierCheckboxes.add(Pair(binding.checkboxPos, "pos"))
|
||||
courierCheckboxes.add(Pair(binding.checkboxTiki, "tiki"))
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
viewModel.availableCouriers.observe(this) { couriers ->
|
||||
// Check the appropriate checkboxes based on available couriers
|
||||
for (pair in courierCheckboxes) {
|
||||
val checkbox = pair.first
|
||||
val courierCode = pair.second
|
||||
checkbox.isChecked = couriers.contains(courierCode)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isLoading.observe(this) { isLoading ->
|
||||
binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
|
||||
binding.contentLayout.visibility = if (isLoading) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
viewModel.errorMessage.observe(this) { errorMessage ->
|
||||
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
viewModel.saveSuccess.observe(this) { success ->
|
||||
if (success) {
|
||||
Toast.makeText(this, "Layanan pengiriman berhasil disimpan", Toast.LENGTH_SHORT).show()
|
||||
setResult(Activity.RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveShippingServices() {
|
||||
val selectedCouriers = mutableListOf<String>()
|
||||
|
||||
for (pair in courierCheckboxes) {
|
||||
val checkbox = pair.first
|
||||
val courierCode = pair.second
|
||||
if (checkbox.isChecked) {
|
||||
selectedCouriers.add(courierCode)
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedCouriers.isEmpty()) {
|
||||
Toast.makeText(this, "Pilih minimal satu layanan pengiriman", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.saveShippingServices(selectedCouriers)
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package com.alya.ecommerce_serang.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import kotlin.random.Random
|
||||
|
||||
object UriToFileConverter {
|
||||
private const val TAG = "UriToFileConverter"
|
||||
|
||||
fun uriToFile(uri: Uri, context: Context): File? {
|
||||
return try {
|
||||
Log.d(TAG, "Converting URI to file: $uri")
|
||||
|
||||
// Try to get original filename
|
||||
val fileName = getFileNameFromUri(uri, context) ?: "upload_${System.currentTimeMillis()}"
|
||||
val extension = getFileExtension(fileName) ?: ".jpg"
|
||||
|
||||
// Create a temporary file in the cache directory with proper name
|
||||
val tempFile = File.createTempFile(
|
||||
"upload_${Random.nextInt(10000)}",
|
||||
extension,
|
||||
context.cacheDir
|
||||
)
|
||||
|
||||
Log.d(TAG, "Created temp file: ${tempFile.absolutePath}")
|
||||
|
||||
// Open the input stream and copy content
|
||||
var inputStream: InputStream? = null
|
||||
try {
|
||||
inputStream = context.contentResolver.openInputStream(uri)
|
||||
if (inputStream == null) {
|
||||
Log.e(TAG, "Failed to open input stream for URI: $uri")
|
||||
return null
|
||||
}
|
||||
|
||||
// Copy content using a buffer
|
||||
val outputStream = FileOutputStream(tempFile)
|
||||
val buffer = ByteArray(4 * 1024) // 4 KB buffer
|
||||
var bytesRead: Int
|
||||
var totalBytesRead = 0
|
||||
|
||||
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead)
|
||||
totalBytesRead += bytesRead
|
||||
}
|
||||
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
|
||||
Log.d(TAG, "Successfully copied $totalBytesRead bytes to file")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error copying file data", e)
|
||||
return null
|
||||
} finally {
|
||||
inputStream?.close()
|
||||
}
|
||||
|
||||
// Verify the file
|
||||
if (!tempFile.exists() || tempFile.length() == 0L) {
|
||||
Log.e(TAG, "Created file doesn't exist or is empty: ${tempFile.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
Log.d(TAG, "Successfully converted URI to file: ${tempFile.absolutePath}, size: ${tempFile.length()} bytes")
|
||||
tempFile
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error converting URI to file", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFileNameFromUri(uri: Uri, context: Context): String? {
|
||||
// Try the OpenableColumns query method first
|
||||
val cursor = context.contentResolver.query(uri, null, null, null, null)
|
||||
cursor?.use { c ->
|
||||
if (c.moveToFirst()) {
|
||||
val nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
if (nameIndex != -1) {
|
||||
val fileName = c.getString(nameIndex)
|
||||
Log.d(TAG, "Retrieved filename from OpenableColumns: $fileName")
|
||||
return fileName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try MediaStore method
|
||||
val projection = arrayOf(MediaStore.Images.Media.DISPLAY_NAME)
|
||||
try {
|
||||
context.contentResolver.query(uri, projection, null, null, null)?.use { c ->
|
||||
if (c.moveToFirst()) {
|
||||
val nameIndex = c.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
|
||||
val fileName = c.getString(nameIndex)
|
||||
Log.d(TAG, "Retrieved filename from MediaStore: $fileName")
|
||||
return fileName
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting filename from MediaStore", e)
|
||||
}
|
||||
|
||||
// Last resort: extract from URI path
|
||||
uri.path?.let { path ->
|
||||
val fileName = path.substring(path.lastIndexOf('/') + 1)
|
||||
Log.d(TAG, "Retrieved filename from URI path: $fileName")
|
||||
return fileName
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getFileExtension(fileName: String): String? {
|
||||
val lastDot = fileName.lastIndexOf('.')
|
||||
return if (lastDot >= 0) {
|
||||
fileName.substring(lastDot)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun getFilePathFromUri(uri: Uri, context: Context): String? {
|
||||
// For Media Gallery
|
||||
val projection = arrayOf(MediaStore.Images.Media.DATA)
|
||||
try {
|
||||
val cursor = context.contentResolver.query(uri, projection, null, null, null)
|
||||
cursor?.use {
|
||||
if (it.moveToFirst()) {
|
||||
val columnIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
|
||||
return it.getString(columnIndex)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting file path from URI", e)
|
||||
}
|
||||
|
||||
// If the above method fails, try direct conversion
|
||||
return uri.path
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package com.alya.ecommerce_serang.utils.viewmodel
|
||||
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.dto.PaymentInfo
|
||||
import com.alya.ecommerce_serang.data.repository.PaymentInfoRepository
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
class PaymentInfoViewModel(private val repository: PaymentInfoRepository) : ViewModel() {
|
||||
|
||||
private val TAG = "PaymentInfoViewModel"
|
||||
|
||||
private val _paymentInfos = MutableLiveData<List<PaymentInfo>>()
|
||||
val paymentInfos: LiveData<List<PaymentInfo>> = _paymentInfos
|
||||
|
||||
private val _isLoading = MutableLiveData<Boolean>()
|
||||
val isLoading: LiveData<Boolean> = _isLoading
|
||||
|
||||
private val _errorMessage = MutableLiveData<String>()
|
||||
val errorMessage: LiveData<String> = _errorMessage
|
||||
|
||||
private val _addPaymentSuccess = MutableLiveData<Boolean>()
|
||||
val addPaymentSuccess: LiveData<Boolean> = _addPaymentSuccess
|
||||
|
||||
private val _deletePaymentSuccess = MutableLiveData<Boolean>()
|
||||
val deletePaymentSuccess: LiveData<Boolean> = _deletePaymentSuccess
|
||||
|
||||
fun getPaymentInfo() {
|
||||
_isLoading.value = true
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
Log.d(TAG, "Loading payment info...")
|
||||
val result = repository.getPaymentInfo()
|
||||
|
||||
if (result.isEmpty()) {
|
||||
Log.d(TAG, "No payment info found")
|
||||
} else {
|
||||
Log.d(TAG, "Successfully loaded ${result.size} payment info")
|
||||
for (method in result) {
|
||||
Log.d(TAG, "Payment method: id=${method.id}, bank=${method.bankName}, account=${method.accountName}")
|
||||
}
|
||||
}
|
||||
|
||||
_paymentInfos.value = result
|
||||
_isLoading.value = false
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting payment info", e)
|
||||
_errorMessage.value = "Gagal memuat metode pembayaran: ${e.message?.take(100) ?: "Unknown error"}"
|
||||
_isLoading.value = false
|
||||
// Still set empty payment info to show empty state
|
||||
_paymentInfos.value = emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addPaymentInfo(bankName: String, bankNumber: String, accountName: String, qrisImageUri: Uri?, qrisImageFile: File?) {
|
||||
_isLoading.value = true
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
Log.d(TAG, "Adding payment info: bankName=$bankName, bankNumber=$bankNumber, accountName=$accountName")
|
||||
Log.d(TAG, "Image file: ${qrisImageFile?.absolutePath}, exists: ${qrisImageFile?.exists()}, size: ${qrisImageFile?.length() ?: 0} bytes")
|
||||
|
||||
// Validate the file if it was provided
|
||||
if (qrisImageUri != null && qrisImageFile == null) {
|
||||
_errorMessage.value = "Gagal memproses gambar. Silakan pilih gambar lain."
|
||||
_isLoading.value = false
|
||||
_addPaymentSuccess.value = false
|
||||
return@launch
|
||||
}
|
||||
|
||||
// If we have a file, make sure it exists and has some content
|
||||
if (qrisImageFile != null && (!qrisImageFile.exists() || qrisImageFile.length() == 0L)) {
|
||||
Log.e(TAG, "Image file does not exist or is empty: ${qrisImageFile.absolutePath}")
|
||||
_errorMessage.value = "File gambar tidak valid. Silakan pilih gambar lain."
|
||||
_isLoading.value = false
|
||||
_addPaymentSuccess.value = false
|
||||
return@launch
|
||||
}
|
||||
|
||||
val success = repository.addPaymentMethod(bankName, bankNumber, accountName, qrisImageFile)
|
||||
_addPaymentSuccess.value = success
|
||||
_isLoading.value = false
|
||||
|
||||
if (success) {
|
||||
// Refresh the payment info list
|
||||
getPaymentInfo()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error adding payment info", e)
|
||||
_errorMessage.value = "Gagal menambahkan metode pembayaran: ${e.message?.take(100) ?: "Unknown error"}"
|
||||
_isLoading.value = false
|
||||
_addPaymentSuccess.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deletePaymentInfo(paymentInfoId: Int) {
|
||||
_isLoading.value = true
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val success = repository.deletePaymentMethod(paymentInfoId)
|
||||
_deletePaymentSuccess.value = success
|
||||
_isLoading.value = false
|
||||
if (success) {
|
||||
// Refresh the payment info list
|
||||
getPaymentInfo()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error deleting payment info", e)
|
||||
_errorMessage.value = "Gagal menghapus metode pembayaran: ${e.message?.take(100) ?: "Unknown error"}"
|
||||
_isLoading.value = false
|
||||
_deletePaymentSuccess.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package com.alya.ecommerce_serang.utils.viewmodel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.repository.ShippingServiceRepository
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ShippingServiceViewModel(private val repository: ShippingServiceRepository) : ViewModel() {
|
||||
|
||||
private val TAG = "ShippingServicesVM"
|
||||
|
||||
private val _availableCouriers = MutableLiveData<List<String>>()
|
||||
val availableCouriers: LiveData<List<String>> = _availableCouriers
|
||||
|
||||
private val _isLoading = MutableLiveData<Boolean>()
|
||||
val isLoading: LiveData<Boolean> = _isLoading
|
||||
|
||||
private val _errorMessage = MutableLiveData<String>()
|
||||
val errorMessage: LiveData<String> = _errorMessage
|
||||
|
||||
private val _saveSuccess = MutableLiveData<Boolean>()
|
||||
val saveSuccess: LiveData<Boolean> = _saveSuccess
|
||||
|
||||
fun getAvailableCouriers() {
|
||||
_isLoading.value = true
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val result = repository.getAvailableCouriers()
|
||||
_availableCouriers.value = result
|
||||
_isLoading.value = false
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting available couriers", e)
|
||||
_errorMessage.value = "Failed to load shipping services: ${e.message}"
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveShippingServices(selectedCouriers: List<String>) {
|
||||
if (selectedCouriers.isEmpty()) {
|
||||
_errorMessage.value = "Please select at least one courier"
|
||||
return
|
||||
}
|
||||
|
||||
_isLoading.value = true
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
// First get current couriers to determine what to add/delete
|
||||
val currentCouriers = repository.getAvailableCouriers()
|
||||
|
||||
// Calculate couriers to add (selected but not in current)
|
||||
val couriersToAdd = selectedCouriers.filter { !currentCouriers.contains(it) }
|
||||
|
||||
// Calculate couriers to delete (in current but not selected)
|
||||
val couriersToDelete = currentCouriers.filter { !selectedCouriers.contains(it) }
|
||||
|
||||
// Perform additions if needed
|
||||
if (couriersToAdd.isNotEmpty()) {
|
||||
repository.addShippingServices(couriersToAdd)
|
||||
}
|
||||
|
||||
// Perform deletions if needed
|
||||
if (couriersToDelete.isNotEmpty()) {
|
||||
repository.deleteShippingServices(couriersToDelete)
|
||||
}
|
||||
|
||||
_saveSuccess.value = true
|
||||
_isLoading.value = false
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error saving shipping services", e)
|
||||
_errorMessage.value = "Failed to save shipping services: ${e.message}"
|
||||
_isLoading.value = false
|
||||
_saveSuccess.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -82,44 +82,61 @@
|
||||
android:text="Riwayat Saldo"
|
||||
android:layout_marginTop="10dp"/>
|
||||
|
||||
<!-- Date Picker dengan Icon -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_text_field"
|
||||
android:layout_marginTop="10dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
android:layout_marginTop="10dp">
|
||||
|
||||
<!-- Icon Kalender -->
|
||||
<ImageView
|
||||
android:id="@+id/iconDatePicker"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:src="@drawable/ic_calendar"
|
||||
android:contentDescription="Pilih Tanggal" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edt_tgl_transaksi"
|
||||
<!-- Date Picker dengan Icon -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="Pilih tanggal di sini"
|
||||
android:padding="8dp"
|
||||
style="@style/body_small"
|
||||
android:background="@null"
|
||||
android:focusable="false"
|
||||
android:clickable="true" />
|
||||
android:background="@drawable/bg_text_field"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/img_date_picker"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:src="@drawable/ic_navigate_next"
|
||||
android:contentDescription="Pilih Tanggal"
|
||||
app:tint="@color/black_300" />
|
||||
<!-- Icon Kalender -->
|
||||
<ImageView
|
||||
android:id="@+id/iconDatePicker"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:src="@drawable/ic_calendar"
|
||||
android:contentDescription="Pilih Tanggal" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edt_tgl_transaksi"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="Pilih tanggal di sini"
|
||||
android:padding="8dp"
|
||||
style="@style/body_small"
|
||||
android:background="@null"
|
||||
android:focusable="false"
|
||||
android:clickable="true" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/img_date_picker"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:src="@drawable/ic_navigate_next"
|
||||
android:contentDescription="Pilih Tanggal"
|
||||
app:tint="@color/black_300" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Clear Filter Button -->
|
||||
<Button
|
||||
android:id="@+id/btn_clear_filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="Clear"
|
||||
android:layout_marginStart="8dp"
|
||||
style="@style/button.small.secondary.short"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -138,8 +155,25 @@
|
||||
android:scrollbars="vertical"
|
||||
tools:listitem="@layout/item_balance_transaction" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_empty_state"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tidak ada riwayat transaksi"
|
||||
android:gravity="center"
|
||||
android:padding="24dp"
|
||||
style="@style/body_medium"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
@ -203,48 +203,6 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Nomor Rekening -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Nomor Rekening"
|
||||
style="@style/body_medium"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="*"
|
||||
style="@style/body_medium"
|
||||
android:textColor="@color/red_required"
|
||||
android:layout_gravity="end"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edt_no_rekening"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_text_field"
|
||||
android:hint="Isi nomor rekening Anda di sini"
|
||||
android:padding="8dp"
|
||||
style="@style/body_small"
|
||||
android:layout_marginTop="10dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Tanggal Transaksi -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -284,7 +284,7 @@
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_payment_methods"
|
||||
android:id="@+id/rv_payment_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_payment_method" />
|
||||
|
@ -175,6 +175,7 @@
|
||||
android:background="@drawable/bg_text_field"
|
||||
android:padding="8dp"
|
||||
style="@style/body_small"
|
||||
android:hint="Isi nama jalan di sini"
|
||||
android:layout_marginTop="10dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
@ -200,6 +201,7 @@
|
||||
android:background="@drawable/bg_text_field"
|
||||
android:padding="8dp"
|
||||
style="@style/body_small"
|
||||
android:hint="Isi nama kecamatan di sini"
|
||||
android:layout_marginTop="10dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
@ -225,6 +227,7 @@
|
||||
android:background="@drawable/bg_text_field"
|
||||
android:padding="8dp"
|
||||
style="@style/body_small"
|
||||
android:hint="Isi kode pos di sini"
|
||||
android:layout_marginTop="10dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
@ -258,31 +261,91 @@
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Pinpoint Lokasi -->
|
||||
<!-- <LinearLayout-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:orientation="vertical"-->
|
||||
<!-- android:layout_marginBottom="24dp">-->
|
||||
|
||||
<!-- <TextView-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="Pinpoint Lokasi"-->
|
||||
<!-- style="@style/body_medium"-->
|
||||
<!-- android:layout_marginEnd="4dp"/>-->
|
||||
|
||||
<!-- <!– Map –>-->
|
||||
<!-- <org.osmdroid.views.MapView android:id="@+id/map"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="220dp" />-->
|
||||
|
||||
<!-- <TextView-->
|
||||
<!-- android:id="@+id/tv_location_display"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="Lokasi: Tidak dipilih"-->
|
||||
<!-- style="@style/body_medium"/>-->
|
||||
|
||||
<!-- </LinearLayout>-->
|
||||
|
||||
<!-- Coordinates -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="24dp">
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
<!-- Latitude -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pinpoint Lokasi"
|
||||
style="@style/body_medium"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginEnd="8dp">
|
||||
|
||||
<!-- Map -->
|
||||
<org.osmdroid.views.MapView android:id="@+id/map"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="220dp" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Latitude"
|
||||
style="@style/body_medium"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_location_display"
|
||||
android:layout_width="match_parent"
|
||||
<EditText
|
||||
android:id="@+id/edt_latitude"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_text_field"
|
||||
android:padding="12dp"
|
||||
android:hint="Latitude"
|
||||
android:inputType="numberDecimal|numberSigned"
|
||||
style="@style/body_small"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Longitude -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Lokasi: Tidak dipilih"
|
||||
style="@style/body_medium"/>
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Longitude"
|
||||
style="@style/body_medium"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edt_longitude"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_text_field"
|
||||
android:padding="12dp"
|
||||
android:hint="Longitude"
|
||||
android:inputType="numberDecimal|numberSigned"
|
||||
style="@style/body_small"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
|
@ -2,9 +2,82 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white"
|
||||
tools:context=".ui.profile.mystore.profile.payment_info.PaymentInfoActivity">
|
||||
|
||||
<include
|
||||
android:id="@+id/header"
|
||||
layout="@layout/header"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/tv_empty_state"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/header">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:src="@drawable/placeholder_image"
|
||||
android:alpha="0.5" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Belum ada metode pembayaran"
|
||||
android:textAlignment="center"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Tambahkan metode pembayaran untuk memudahkan pembeli melakukan transaksi"
|
||||
android:textAlignment="center"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_payment_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:paddingHorizontal="@dimen/horizontal_safe_area"
|
||||
android:paddingVertical="8dp"
|
||||
android:clipToPadding="false"
|
||||
app:layout_constraintTop_toBottomOf="@id/header"
|
||||
app:layout_constraintBottom_toTopOf="@id/btn_add_payment"
|
||||
tools:listitem="@layout/item_payment_info"
|
||||
tools:itemCount="2" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/header" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_add_payment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="Tambahkan Metode Pembayaran"
|
||||
android:paddingVertical="12dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -2,10 +2,130 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:fitsSystemWindows="true"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white"
|
||||
tools:context=".ui.profile.mystore.profile.shipping_service.ShippingServiceActivity">
|
||||
|
||||
<include
|
||||
android:id="@+id/header"
|
||||
layout="@layout/header"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/content_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/header"
|
||||
app:layout_constraintBottom_toTopOf="@id/btn_save">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pilih Layanan Pengiriman"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Layanan pengiriman yang dipilih akan tersedia untuk pembeli saat checkout"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_jne"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="JNE"
|
||||
android:textSize="16sp"
|
||||
android:paddingStart="8dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_pos"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="POS Indonesia"
|
||||
android:textSize="16sp"
|
||||
android:paddingStart="8dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_tiki"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TIKI"
|
||||
android:textSize="16sp"
|
||||
android:paddingStart="8dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_sicepat"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="SiCepat"
|
||||
android:textSize="16sp"
|
||||
android:paddingStart="8dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_jnt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="J&T Express"
|
||||
android:textSize="16sp"
|
||||
android:paddingStart="8dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_ninja"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ninja Express"
|
||||
android:textSize="16sp"
|
||||
android:paddingStart="8dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_antaraja"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="AnterAja"
|
||||
android:textSize="16sp"
|
||||
android:paddingStart="8dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_spx"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Shopee Express (SPX)"
|
||||
android:textSize="16sp"
|
||||
android:paddingStart="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/header" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_save"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:text="Simpan"
|
||||
android:paddingVertical="12dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
102
app/src/main/res/layout/dialog_add_payment_info.xml
Normal file
102
app/src/main/res/layout/dialog_add_payment_info.xml
Normal file
@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tambah Metode Pembayaran"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/edt_bank_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Nama Bank" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/edt_account_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Nama Pemilik Rekening" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/edt_bank_number"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Nomor Rekening"
|
||||
android:inputType="number" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="QRIS (Opsional)"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_add_qris"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tambah Gambar QRIS"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_qris_preview"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="150dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:visibility="gone"
|
||||
android:contentDescription="QRIS Preview" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_cancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Batal"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_marginEnd="8dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_save"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Simpan" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
92
app/src/main/res/layout/item_payment_info.xml
Normal file
92
app/src/main/res/layout/item_payment_info.xml
Normal file
@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_info"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/iv_delete"
|
||||
app:layout_constraintBottom_toTopOf="@id/layout_qris">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_bank_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="Mandiri" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_account_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginTop="4dp"
|
||||
tools:text="Kemas" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_bank_number"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginTop="4dp"
|
||||
tools:text="941281212313" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_delete"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_delete"
|
||||
android:contentDescription="Delete payment method"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_qris"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="16dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_info"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="QRIS"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_qris"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:contentDescription="QRIS" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
Reference in New Issue
Block a user