mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-13 10:42: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(
|
data class StoreDataResponse(
|
||||||
val message: String,
|
val message: String,
|
||||||
val store: Store,
|
val store: Store? = null,
|
||||||
val shipping: List<Shipping>,
|
val shipping: List<Shipping>? = emptyList(),
|
||||||
val payment: List<Payment>
|
val payment: List<Payment> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Store(
|
data class Store(
|
||||||
@ -51,5 +51,6 @@ data class Payment(
|
|||||||
val id: Int,
|
val id: Int,
|
||||||
@SerializedName("bank_num") val bankNum: String,
|
@SerializedName("bank_num") val bankNum: String,
|
||||||
@SerializedName("bank_name") val bankName: 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.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.CartItem
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CityResponse
|
import com.alya.ecommerce_serang.data.api.dto.CityResponse
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
|
import com.alya.ecommerce_serang.data.api.dto.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.ProvinceResponse
|
||||||
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
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.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.StoreAddressResponse
|
||||||
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
|
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
|
||||||
import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest
|
import com.alya.ecommerce_serang.data.api.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.DeleteProductResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.store.product.UpdateProductResponse
|
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.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.profile.StoreDataResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.store.topup.BalanceTopUpResponse
|
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.MultipartBody
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
@ -151,8 +155,15 @@ interface ApiService {
|
|||||||
): Response<CreateAddressResponse>
|
): Response<CreateAddressResponse>
|
||||||
|
|
||||||
@GET("mystore")
|
@GET("mystore")
|
||||||
suspend fun getStore (): Response<StoreResponse>
|
suspend fun getStore(): Response<StoreResponse>
|
||||||
|
|
||||||
|
@GET("mystore")
|
||||||
suspend fun getStoreData(): Response<StoreDataResponse>
|
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>
|
suspend fun getStoreAddress(): Response<StoreAddressResponse>
|
||||||
|
|
||||||
@GET("mystore/product") // Replace with actual endpoint
|
@GET("mystore/product") // Replace with actual endpoint
|
||||||
@ -242,6 +253,12 @@ interface ApiService {
|
|||||||
@Part complaintimg: MultipartBody.Part
|
@Part complaintimg: MultipartBody.Part
|
||||||
): Response<ComplaintResponse>
|
): Response<ComplaintResponse>
|
||||||
|
|
||||||
|
@GET("store/topup")
|
||||||
|
suspend fun getTopUpHistory(): Response<TopUpResponse>
|
||||||
|
|
||||||
|
@GET("store/topup")
|
||||||
|
suspend fun getFilteredTopUpHistory(@Query("date") date: String): Response<TopUpResponse>
|
||||||
|
|
||||||
@Multipart
|
@Multipart
|
||||||
@POST("store/createtopup")
|
@POST("store/createtopup")
|
||||||
suspend fun addBalanceTopUp(
|
suspend fun addBalanceTopUp(
|
||||||
@ -253,11 +270,6 @@ interface ApiService {
|
|||||||
@Part("bank_num") bankNum: RequestBody
|
@Part("bank_num") bankNum: RequestBody
|
||||||
): Response<BalanceTopUpResponse>
|
): Response<BalanceTopUpResponse>
|
||||||
|
|
||||||
@PUT("mystore/edit")
|
|
||||||
suspend fun updateStoreProfile(
|
|
||||||
@Body requestBody: okhttp3.RequestBody
|
|
||||||
): Response<StoreDataResponse>
|
|
||||||
|
|
||||||
@Multipart
|
@Multipart
|
||||||
@PUT("mystore/edit")
|
@PUT("mystore/edit")
|
||||||
suspend fun updateStoreProfileMultipart(
|
suspend fun updateStoreProfileMultipart(
|
||||||
@ -277,6 +289,40 @@ interface ApiService {
|
|||||||
@Part storeimg: MultipartBody.Part?
|
@Part storeimg: MultipartBody.Part?
|
||||||
): Response<StoreDataResponse>
|
): 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")
|
@GET("provinces")
|
||||||
suspend fun getProvinces(): Response<ProvinceResponse>
|
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)
|
viewModel.setPaymentMethod(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.rvPaymentMethods.apply {
|
binding.rvPaymentInfo.apply {
|
||||||
layoutManager = LinearLayoutManager(this@CheckoutActivity)
|
layoutManager = LinearLayoutManager(this@CheckoutActivity)
|
||||||
adapter = paymentAdapter
|
adapter = paymentAdapter
|
||||||
}
|
}
|
||||||
@ -187,7 +187,7 @@ class CheckoutActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.rvPaymentMethods.apply {
|
binding.rvPaymentInfo.apply {
|
||||||
layoutManager = LinearLayoutManager(this@CheckoutActivity)
|
layoutManager = LinearLayoutManager(this@CheckoutActivity)
|
||||||
adapter = testAdapter
|
adapter = testAdapter
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,12 @@ package com.alya.ecommerce_serang.ui.profile.mystore
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
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.dto.Store
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
@ -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.product.ProductActivity
|
||||||
import com.alya.ecommerce_serang.ui.profile.mystore.profile.DetailStoreProfileActivity
|
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.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.ui.profile.mystore.sells.SellsListFragment
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
@ -64,10 +67,17 @@ class MyStoreActivity : AppCompatActivity() {
|
|||||||
binding.tvStoreName.text = store.storeName
|
binding.tvStoreName.text = store.storeName
|
||||||
binding.tvStoreType.text = store.storeType
|
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)
|
Glide.with(this)
|
||||||
.load(it)
|
.load(imageUrl)
|
||||||
|
.placeholder(R.drawable.placeholder_image)
|
||||||
|
.error(R.drawable.placeholder_image)
|
||||||
.into(binding.ivProfile)
|
.into(binding.ivProfile)
|
||||||
|
} else {
|
||||||
|
Log.d("MyStoreActivity", "No store image available")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,19 +91,26 @@ class MyStoreActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.tvHistory.setOnClickListener {
|
binding.tvHistory.setOnClickListener {
|
||||||
navigateToSellsFragment("all")
|
val intent = Intent(this, SellsActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.layoutPerluTagihan.setOnClickListener {
|
binding.layoutPerluTagihan.setOnClickListener {
|
||||||
navigateToSellsFragment("pending")
|
val intent = Intent(this, SellsActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
//navigateToSellsFragment("pending")
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.layoutPembayaran.setOnClickListener {
|
binding.layoutPembayaran.setOnClickListener {
|
||||||
navigateToSellsFragment("paid")
|
val intent = Intent(this, SellsActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
//navigateToSellsFragment("paid")
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.layoutPerluDikirim.setOnClickListener {
|
binding.layoutPerluDikirim.setOnClickListener {
|
||||||
navigateToSellsFragment("processed")
|
val intent = Intent(this, SellsActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
//navigateToSellsFragment("processed")
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.layoutProductMenu.setOnClickListener {
|
binding.layoutProductMenu.setOnClickListener {
|
||||||
@ -115,11 +132,24 @@ class MyStoreActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToSellsFragment(status: String) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
val sellsFragment = SellsListFragment.newInstance(status)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
supportFragmentManager.beginTransaction()
|
if (requestCode == PROFILE_REQUEST_CODE && resultCode == RESULT_OK) {
|
||||||
.replace(android.R.id.content, sellsFragment)
|
// Refresh store data
|
||||||
.addToBackStack(null)
|
viewModel.loadMyStore()
|
||||||
.commit()
|
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
|
package com.alya.ecommerce_serang.ui.profile.mystore.balance
|
||||||
|
|
||||||
|
import android.app.DatePickerDialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
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.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.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() {
|
class BalanceActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityBalanceBinding
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -24,13 +46,369 @@ class BalanceActivity : AppCompatActivity() {
|
|||||||
insets
|
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()
|
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() {
|
private fun setupListeners() {
|
||||||
binding.btnTopUp.setOnClickListener {
|
binding.btnTopUp.setOnClickListener {
|
||||||
val intent = Intent(this, BalanceTopUpActivity::class.java)
|
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
|
// Setup back button
|
||||||
val backButton = findViewById<ImageView>(R.id.header_left_icon)
|
val backButton = findViewById<ImageView>(R.id.header_left_icon)
|
||||||
backButton.setOnClickListener {
|
backButton.setOnClickListener {
|
||||||
finish()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup photo selection
|
// Setup photo selection
|
||||||
|
@ -1,7 +1,90 @@
|
|||||||
package com.alya.ecommerce_serang.ui.profile.mystore.balance
|
package com.alya.ecommerce_serang.ui.profile.mystore.balance
|
||||||
|
|
||||||
/* class BalanceTransactionAdapter(private val balanceTransactionList: List<BalanceTransaction>) :
|
import android.view.LayoutInflater
|
||||||
RecyclerView.Adapter<BalanceTransactionAdapter.TransactionViewHolder>() {
|
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.data.repository.MyStoreRepository
|
||||||
import com.alya.ecommerce_serang.databinding.ActivityDetailStoreProfileBinding
|
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.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.viewmodel.MyStoreViewModel
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
@ -59,7 +61,19 @@ class DetailStoreProfileActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
binding.layoutAddress.setOnClickListener {
|
binding.layoutAddress.setOnClickListener {
|
||||||
val intent = Intent(this, DetailStoreAddressActivity::class.java)
|
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()
|
viewModel.loadMyStore()
|
||||||
@ -87,6 +101,20 @@ class DetailStoreProfileActivity : AppCompatActivity() {
|
|||||||
Toast.makeText(this, "Alamat toko berhasil diperbarui", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Alamat toko berhasil diperbarui", Toast.LENGTH_SHORT).show()
|
||||||
viewModel.loadMyStore()
|
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
|
// Pass the result back to parent activity
|
||||||
setResult(Activity.RESULT_OK)
|
setResult(Activity.RESULT_OK)
|
||||||
}
|
}
|
||||||
@ -95,6 +123,8 @@ class DetailStoreProfileActivity : AppCompatActivity() {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val EDIT_PROFILE_REQUEST_CODE = 100
|
private const val EDIT_PROFILE_REQUEST_CODE = 100
|
||||||
private const val ADDRESS_REQUEST_CODE = 101
|
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){
|
private fun updateStoreProfile(store: Store){
|
||||||
@ -105,7 +135,7 @@ class DetailStoreProfileActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
// Update store image if available
|
// Update store image if available
|
||||||
if (store.storeImage != null && store.storeImage.toString().isNotEmpty() && store.storeImage.toString() != "null") {
|
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")
|
Log.d("DetailStoreProfile", "Loading image from: $imageUrl")
|
||||||
|
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package com.alya.ecommerce_serang.ui.profile.mystore.profile.address
|
package com.alya.ecommerce_serang.ui.profile.mystore.profile.address
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -9,6 +8,7 @@ import android.widget.AdapterView
|
|||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.alya.ecommerce_serang.BuildConfig
|
import com.alya.ecommerce_serang.BuildConfig
|
||||||
import com.alya.ecommerce_serang.data.api.dto.City
|
import com.alya.ecommerce_serang.data.api.dto.City
|
||||||
@ -56,7 +56,7 @@ class DetailStoreAddressActivity : AppCompatActivity() {
|
|||||||
binding.tvError.visibility = View.GONE
|
binding.tvError.visibility = View.GONE
|
||||||
|
|
||||||
// Set up header title
|
// Set up header title
|
||||||
binding.header.headerTitle.text = "Alamat Toko"
|
binding.header.headerTitle.text = "Atur Alamat Toko"
|
||||||
|
|
||||||
// Set up back button
|
// Set up back button
|
||||||
binding.header.headerLeftIcon.setOnClickListener {
|
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 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
|
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
|
// Set selected province ID to trigger city loading
|
||||||
if (address.provinceId.isNotEmpty()) {
|
if (address.provinceId.isNotEmpty()) {
|
||||||
selectedProvinceId = address.provinceId
|
selectedProvinceId = address.provinceId
|
||||||
@ -271,8 +274,8 @@ class DetailStoreAddressActivity : AppCompatActivity() {
|
|||||||
val subdistrict = binding.edtSubdistrict.text.toString()
|
val subdistrict = binding.edtSubdistrict.text.toString()
|
||||||
val detail = binding.edtDetailAddress.text.toString()
|
val detail = binding.edtDetailAddress.text.toString()
|
||||||
val postalCode = binding.edtPostalCode.text.toString()
|
val postalCode = binding.edtPostalCode.text.toString()
|
||||||
val latitudeStr = TODO()
|
val latitudeStr = binding.edtLatitude.text.toString()
|
||||||
val longitudeStr = TODO()
|
val longitudeStr = binding.edtLongitude.text.toString()
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
if (selectedProvinceId == null || binding.spinnerCity.selectedItemPosition <= 0 ||
|
if (selectedProvinceId == null || binding.spinnerCity.selectedItemPosition <= 0 ||
|
||||||
|
@ -1,21 +1,282 @@
|
|||||||
package com.alya.ecommerce_serang.ui.profile.mystore.profile.payment_info
|
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 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.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import com.alya.ecommerce_serang.R
|
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() {
|
class PaymentInfoActivity : AppCompatActivity() {
|
||||||
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
binding = ActivityPaymentInfoBinding.inflate(layoutInflater)
|
||||||
setContentView(R.layout.activity_payment_info)
|
setContentView(binding.root)
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
|
||||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
sessionManager = SessionManager(this)
|
||||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
|
||||||
insets
|
// 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
|
package com.alya.ecommerce_serang.ui.profile.mystore.profile.shipping_service
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.os.Bundle
|
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.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import com.alya.ecommerce_serang.data.repository.ShippingServiceRepository
|
||||||
import com.alya.ecommerce_serang.R
|
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() {
|
class ShippingServiceActivity : AppCompatActivity() {
|
||||||
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
binding = ActivityShippingServiceBinding.inflate(layoutInflater)
|
||||||
setContentView(R.layout.activity_shipping_service)
|
setContentView(binding.root)
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
|
||||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
sessionManager = SessionManager(this)
|
||||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
|
||||||
insets
|
// 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,12 +82,18 @@
|
|||||||
android:text="Riwayat Saldo"
|
android:text="Riwayat Saldo"
|
||||||
android:layout_marginTop="10dp"/>
|
android:layout_marginTop="10dp"/>
|
||||||
|
|
||||||
<!-- Date Picker dengan Icon -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<!-- Date Picker dengan Icon -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
android:background="@drawable/bg_text_field"
|
android:background="@drawable/bg_text_field"
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
@ -123,6 +129,17 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</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>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
@ -138,8 +155,25 @@
|
|||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
tools:listitem="@layout/item_balance_transaction" />
|
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>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</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>
|
</LinearLayout>
|
@ -203,48 +203,6 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</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 -->
|
<!-- Tanggal Transaksi -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -284,7 +284,7 @@
|
|||||||
android:layout_marginBottom="8dp" />
|
android:layout_marginBottom="8dp" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/rv_payment_methods"
|
android:id="@+id/rv_payment_info"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:listitem="@layout/item_payment_method" />
|
tools:listitem="@layout/item_payment_method" />
|
||||||
|
@ -175,6 +175,7 @@
|
|||||||
android:background="@drawable/bg_text_field"
|
android:background="@drawable/bg_text_field"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
style="@style/body_small"
|
style="@style/body_small"
|
||||||
|
android:hint="Isi nama jalan di sini"
|
||||||
android:layout_marginTop="10dp"/>
|
android:layout_marginTop="10dp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -200,6 +201,7 @@
|
|||||||
android:background="@drawable/bg_text_field"
|
android:background="@drawable/bg_text_field"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
style="@style/body_small"
|
style="@style/body_small"
|
||||||
|
android:hint="Isi nama kecamatan di sini"
|
||||||
android:layout_marginTop="10dp"/>
|
android:layout_marginTop="10dp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -225,6 +227,7 @@
|
|||||||
android:background="@drawable/bg_text_field"
|
android:background="@drawable/bg_text_field"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
style="@style/body_small"
|
style="@style/body_small"
|
||||||
|
android:hint="Isi kode pos di sini"
|
||||||
android:layout_marginTop="10dp"/>
|
android:layout_marginTop="10dp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -258,31 +261,91 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Pinpoint Lokasi -->
|
<!-- 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
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<!-- Latitude -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_marginBottom="24dp">
|
android:layout_marginEnd="8dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Pinpoint Lokasi"
|
android:text="Latitude"
|
||||||
style="@style/body_medium"
|
style="@style/body_medium"
|
||||||
android:layout_marginEnd="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<!-- Map -->
|
<EditText
|
||||||
<org.osmdroid.views.MapView android:id="@+id/map"
|
android:id="@+id/edt_latitude"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="220dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_location_display"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Lokasi: Tidak dipilih"
|
android:background="@drawable/bg_text_field"
|
||||||
style="@style/body_medium"/>
|
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: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>
|
</LinearLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
@ -2,9 +2,82 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/main"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:background="@android:color/white"
|
||||||
tools:context=".ui.profile.mystore.profile.payment_info.PaymentInfoActivity">
|
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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -2,10 +2,130 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
android:id="@+id/main"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:background="@android:color/white"
|
||||||
tools:context=".ui.profile.mystore.profile.shipping_service.ShippingServiceActivity">
|
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>
|
</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