Merge branch 'master' into screen-features

# Conflicts:
#	app/src/main/AndroidManifest.xml
#	app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
#	app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt
#	app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt
This commit is contained in:
shaulascr
2025-05-14 04:27:42 +07:00
88 changed files with 5091 additions and 1301 deletions

View File

@ -4,6 +4,14 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-05-08T14:50:55.425322500Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Gracia Hotmauli\.android\avd\Pixel_8_2_2.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>

View File

@ -41,6 +41,18 @@
<activity
android:name=".ui.cart.MainActivity"
android:exported="false" />
<activity
android:name=".ui.profile.mystore.profile.EditStoreProfileActivity"
android:exported="false" />
<activity
android:name=".ui.profile.mystore.sells.shipment.DetailShipmentActivity"
android:exported="false" />
<activity
android:name=".ui.profile.mystore.sells.payment.DetailPaymentActivity"
android:exported="false" />
<activity
android:name=".ui.profile.mystore.sells.order.DetailOrderActivity"
android:exported="false" />
<activity
android:name=".ui.chat.ChatActivity"
android:exported="false" /> <!-- <provider -->
@ -52,7 +64,9 @@
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync" />
<activity
android:name=".ui.profile.mystore.profile.shipping_service.ShippingServiceActivity"
android:exported="false"/>
<activity
android:name=".ui.notif.NotificationActivity"
android:exported="false" />
@ -104,12 +118,6 @@
<activity
android:name=".ui.profile.mystore.balance.BalanceActivity"
android:exported="false" />
<activity
android:name=".ui.profile.mystore.sells.shipment.ShippingConfirmationActivity"
android:exported="false" />
<activity
android:name=".ui.profile.mystore.sells.payment.ClaimPaymentActivity"
android:exported="false" />
<activity
android:name=".ui.profile.mystore.balance.BalanceTopUpActivity"
android:exported="false" />

View File

@ -0,0 +1,31 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class City(
@SerializedName("city_id")
val cityId: String,
@SerializedName("city_name")
val cityName: String,
@SerializedName("province_id")
val provinceId: String,
@SerializedName("province")
val provinceName: String,
@SerializedName("type")
val type: String,
@SerializedName("postal_code")
val postalCode: String
)
data class CityResponse(
@SerializedName("message")
val message: String,
@SerializedName("cities")
val data: List<City>
)

View File

@ -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?
)

View File

@ -0,0 +1,19 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class Province(
@SerializedName("province_id")
val provinceId: String,
@SerializedName("province")
val provinceName: String
)
data class ProvinceResponse(
@SerializedName("message")
val message: String,
@SerializedName("provinces")
val data: List<Province>
)

View File

@ -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>
)

View File

@ -0,0 +1,55 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class StoreAddress(
@SerializedName("id")
val id: String? = null,
@SerializedName("store_id")
val storeId: String? = null,
@SerializedName("province_id")
val provinceId: String = "",
@SerializedName("province_name")
val provinceName: String = "",
@SerializedName("city_id")
val cityId: String = "",
@SerializedName("city_name")
val cityName: String = "",
@SerializedName("street")
val street: String = "",
@SerializedName("subdistrict")
val subdistrict: String = "",
@SerializedName("detail")
val detail: String? = null,
@SerializedName("postal_code")
val postalCode: String = "",
@SerializedName("latitude")
val latitude: Double? = 0.0,
@SerializedName("longitude")
val longitude: Double? = 0.0,
@SerializedName("created_at")
val createdAt: String? = null,
@SerializedName("updated_at")
val updatedAt: String? = null
)
data class StoreAddressResponse(
@SerializedName("message")
val message: String,
@SerializedName("store")
val data: StoreAddress? = null
)

View File

@ -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
)

View File

@ -1,5 +1,6 @@
package com.alya.ecommerce_serang.data.api.response.store.orders
import com.alya.ecommerce_serang.data.api.dto.UpdatedOrder
import com.google.gson.annotations.SerializedName
data class KonfirmasiTagihanResponse(

View File

@ -1,6 +1,5 @@
package com.alya.ecommerce_serang.data.api.response.store.orders
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
import com.google.gson.annotations.SerializedName
data class OrderListResponse(
@ -12,43 +11,133 @@ data class OrderListResponse(
val message: String? = null
)
data class Voucher(
@field:SerializedName("name")
val name: Any? = null,
@field:SerializedName("voucher_id")
val voucherId: Any? = null,
@field:SerializedName("voucher_code")
val voucherCode: Any? = null
)
data class Shipment(
data class OrdersItem(
@field:SerializedName("receipt_num")
val receiptNum: Any? = null,
@field:SerializedName("courier")
val courier: Any? = null,
@field:SerializedName("payment_upload_at")
val paymentUploadAt: Any? = null,
@field:SerializedName("price")
val price: Any? = null,
@field:SerializedName("latitude")
val latitude: String? = null,
@field:SerializedName("pay_info_name")
val payInfoName: String? = null,
@field:SerializedName("created_at")
val createdAt: String? = null,
@field:SerializedName("voucher_code")
val voucherCode: Any? = null,
@field:SerializedName("updated_at")
val updatedAt: String? = null,
@field:SerializedName("etd")
val etd: String? = null,
@field:SerializedName("street")
val street: String? = null,
@field:SerializedName("cancel_date")
val cancelDate: String? = null,
@field:SerializedName("payment_evidence")
val paymentEvidence: Any? = null,
@field:SerializedName("longitude")
val longitude: String? = null,
@field:SerializedName("shipment_status")
val shipmentStatus: String? = null,
@field:SerializedName("order_items")
val orderItems: List<OrderItemsItem?>? = null,
@field:SerializedName("auto_completed_at")
val autoCompletedAt: Any? = null,
@field:SerializedName("is_store_location")
val isStoreLocation: Boolean? = null,
@field:SerializedName("qris_image")
val qrisImage: String? = null,
@field:SerializedName("voucher_name")
val voucherName: Any? = null,
@field:SerializedName("payment_status")
val paymentStatus: Any? = null,
@field:SerializedName("address_id")
val addressId: Int? = null,
@field:SerializedName("is_negotiable")
val isNegotiable: Boolean? = null,
@field:SerializedName("payment_amount")
val paymentAmount: Any? = null,
@field:SerializedName("cancel_reason")
val cancelReason: String? = null,
@field:SerializedName("user_id")
val userId: Int? = null,
@field:SerializedName("total_amount")
val totalAmount: String? = null,
@field:SerializedName("province_id")
val provinceId: Int? = null,
@field:SerializedName("courier")
val courier: String? = null,
@field:SerializedName("subdistrict")
val subdistrict: String? = null,
@field:SerializedName("service")
val service: Any? = null,
val service: String? = null,
@field:SerializedName("shipment_id")
val shipmentId: Any? = null,
@field:SerializedName("pay_info_num")
val payInfoNum: String? = null,
@field:SerializedName("shipment_price")
val shipmentPrice: String? = null,
@field:SerializedName("voucher_id")
val voucherId: Any? = null,
@field:SerializedName("payment_info_id")
val paymentInfoId: Int? = null,
@field:SerializedName("detail")
val detail: String? = null,
@field:SerializedName("postal_code")
val postalCode: String? = null,
@field:SerializedName("order_id")
val orderId: Int? = null,
@field:SerializedName("username")
val username: String? = null,
@field:SerializedName("status")
val status: Any? = null
val status: String? = null,
@field:SerializedName("city_id")
val cityId: Int? = null
)
data class OrderItemsItem(
@field:SerializedName("order_item_id")
val orderItemId: Int? = null,
@field:SerializedName("review_id")
val reviewId: Int? = null,
val reviewId: Any? = null,
@field:SerializedName("quantity")
val quantity: Int? = null,
@ -62,6 +151,9 @@ data class OrderItemsItem(
@field:SerializedName("product_image")
val productImage: String? = null,
@field:SerializedName("product_id")
val productId: Int? = null,
@field:SerializedName("store_name")
val storeName: String? = null,
@ -71,48 +163,3 @@ data class OrderItemsItem(
@field:SerializedName("product_name")
val productName: String? = null
)
data class Address(
@field:SerializedName("is_store_location")
val isStoreLocation: Boolean? = null,
@field:SerializedName("province_id")
val provinceId: Int? = null,
@field:SerializedName("street")
val street: String? = null,
@field:SerializedName("subdistrict")
val subdistrict: String? = null,
@field:SerializedName("latitude")
val latitude: Any? = null,
@field:SerializedName("address_id")
val addressId: Int? = null,
@field:SerializedName("detail")
val detail: String? = null,
@field:SerializedName("postal_code")
val postalCode: String? = null,
@field:SerializedName("longitude")
val longitude: Any? = null,
@field:SerializedName("city_id")
val cityId: Int? = null
)
data class Payment(
@field:SerializedName("evidence")
val evidence: Any? = null,
@field:SerializedName("uploaded_at")
val uploadedAt: Any? = null,
@field:SerializedName("payment_id")
val paymentId: Any? = null
)

View File

@ -4,72 +4,27 @@ import com.google.gson.annotations.SerializedName
data class UpdateOrderItemResponse(
@field:SerializedName("message")
val message: String? = null,
@field:SerializedName("updatedOrder")
val updatedOrder: UpdatedOrder? = null,
@field:SerializedName("updatedItems")
val updatedItems: List<UpdatedItemsItem?>? = null
)
data class UpdatedItemsItem(
@field:SerializedName("quantity")
val quantity: Int? = null,
@field:SerializedName("price")
val price: String? = null,
@field:SerializedName("subtotal")
val subtotal: String? = null,
@field:SerializedName("product_id")
val productId: Int? = null,
@field:SerializedName("id")
val id: Int? = null,
@field:SerializedName("total_amount")
val totalAmount: Int? = null,
@field:SerializedName("order_id")
val orderId: Int? = null
)
val orderId: Int? = null,
data class UpdatedOrder(
@field:SerializedName("auto_canceled_at")
val autoCanceledAt: String? = null,
@field:SerializedName("payment_method_id")
val paymentMethodId: Int? = null,
@field:SerializedName("auto_completed_at")
val autoCompletedAt: String? = null,
@field:SerializedName("updated_at")
val updatedAt: String? = null,
@field:SerializedName("total_amount")
val totalAmount: String? = null,
@field:SerializedName("user_id")
val userId: Int? = null,
@field:SerializedName("address_id")
val addressId: Int? = null,
@field:SerializedName("is_negotiable")
val isNegotiable: Boolean? = null,
@field:SerializedName("created_at")
val createdAt: String? = null,
@field:SerializedName("voucher_id")
val voucherId: Any? = null,
@field:SerializedName("id")
val id: Int? = null,
@field:SerializedName("items")
val items: List<ItemsItem?>? = null,
@field:SerializedName("status")
val status: String? = null
)
data class ItemsItem(
@field:SerializedName("order_item_id")
val orderItemId: Int? = null,
@field:SerializedName("price")
val price: Int? = null,
@field:SerializedName("subtotal")
val subtotal: Int? = null
)

View File

@ -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
)

View File

@ -0,0 +1,56 @@
package com.alya.ecommerce_serang.data.api.response.store.profile
import com.google.gson.annotations.SerializedName
data class StoreDataResponse(
val message: String,
val store: Store? = null,
val shipping: List<Shipping>? = emptyList(),
val payment: List<Payment> = emptyList()
)
data class Store(
@SerializedName("store_id") val storeId: Int,
@SerializedName("store_status") val storeStatus: String,
@SerializedName("store_name") val storeName: String,
@SerializedName("user_name") val userName: String,
val email: String,
@SerializedName("user_phone") val userPhone: String,
val balance: String,
val ktp: String,
val npwp: String,
val nib: String,
val persetujuan: String?,
@SerializedName("store_image") val storeImage: String,
@SerializedName("store_description") val storeDescription: String,
@SerializedName("is_on_leave") val isOnLeave: Boolean,
@SerializedName("store_type_id") val storeTypeId: Int,
@SerializedName("store_type") val storeType: String,
val id: Int,
val latitude: String,
val longitude: String,
val street: String,
val subdistrict: String,
@SerializedName("postal_code") val postalCode: String,
val detail: String,
@SerializedName("is_store_location") val isStoreLocation: Boolean,
@SerializedName("user_id") val userId: Int,
@SerializedName("city_id") val cityId: Int,
@SerializedName("province_id") val provinceId: Int,
val phone: String?,
val recipient: String?,
@SerializedName("approval_status") val approvalStatus: String,
@SerializedName("approval_reason") val approvalReason: String?
)
data class Shipping(
val courier: String
)
data class Payment(
val id: Int,
@SerializedName("bank_num") val bankNum: String,
@SerializedName("bank_name") val bankName: String,
@SerializedName("qris_image") val qrisImage: String?,
@SerializedName("account_name") val accountName: String?
)

View File

@ -0,0 +1,6 @@
package com.alya.ecommerce_serang.data.api.response.store.topup
data class BalanceTopUpResponse(
val success: Boolean,
val message: String
)

View File

@ -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"
}
}
}

View File

@ -2,7 +2,9 @@ package com.alya.ecommerce_serang.data.api.retrofit
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceRequest
import com.alya.ecommerce_serang.data.api.dto.AddPaymentInfoResponse
import com.alya.ecommerce_serang.data.api.dto.CartItem
import com.alya.ecommerce_serang.data.api.dto.CityResponse
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
@ -10,9 +12,12 @@ import com.alya.ecommerce_serang.data.api.dto.LoginRequest
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
import com.alya.ecommerce_serang.data.api.dto.ProvinceResponse
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.dto.ReviewProductItem
import com.alya.ecommerce_serang.data.api.dto.SearchRequest
import com.alya.ecommerce_serang.data.api.dto.ShippingServiceRequest
import com.alya.ecommerce_serang.data.api.dto.StoreAddressResponse
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
@ -52,6 +57,10 @@ import com.alya.ecommerce_serang.data.api.response.store.product.CreateProductRe
import com.alya.ecommerce_serang.data.api.response.store.product.DeleteProductResponse
import com.alya.ecommerce_serang.data.api.response.store.product.UpdateProductResponse
import com.alya.ecommerce_serang.data.api.response.store.product.ViewStoreProductsResponse
import com.alya.ecommerce_serang.data.api.response.store.profile.GenericResponse
import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse
import com.alya.ecommerce_serang.data.api.response.store.topup.BalanceTopUpResponse
import com.alya.ecommerce_serang.data.api.response.store.topup.TopUpResponse
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.Call
@ -161,7 +170,16 @@ interface ApiService {
): Response<CreateAddressResponse>
@GET("mystore")
suspend fun getStore (): Response<StoreResponse>
suspend fun getStore(): Response<StoreResponse>
@GET("mystore")
suspend fun getStoreData(): Response<StoreDataResponse>
@GET("mystore")
suspend fun getMyStoreData(): Response<com.alya.ecommerce_serang.data.api.response.store.StoreResponse>
@GET("mystore")
suspend fun getStoreAddress(): Response<StoreAddressResponse>
@GET("mystore/product") // Replace with actual endpoint
suspend fun getStoreProduct(): Response<ViewStoreProductsResponse>
@ -231,19 +249,22 @@ interface ApiService {
suspend fun getListProv(
): Response<ListProvinceResponse>
@GET("mystore/orders")
suspend fun getAllOrders(): Response<OrderListResponse>
@GET("mystore/orders/{status}")
suspend fun getOrdersByStatus(
@Query("status") status: String
): Response<OrderListResponse>
@GET("order/{status}")
suspend fun getSellList(
@Path("status") status: String
): Response<com.alya.ecommerce_serang.data.api.response.store.orders.OrderListResponse>
@PUT("store/order/update")
suspend fun confirmOrder(
@Body confirmOrder : CompletedOrderRequest
): Response<CompletedOrderResponse>
@PUT("store/order/update")
suspend fun updateOrder(
@Query("order_id") orderId: Int?,
@Query("status") status: String
): Response<com.alya.ecommerce_serang.data.api.response.store.orders.UpdateOrderItemResponse>
@Multipart
@POST("addcomplaint")
suspend fun addComplaint(
@ -257,6 +278,89 @@ interface ApiService {
@Body contentReview : ReviewProductItem
): Response<CreateReviewResponse>
@GET("store/topup")
suspend fun getTopUpHistory(): Response<TopUpResponse>
@GET("store/topup")
suspend fun getFilteredTopUpHistory(@Query("date") date: String): Response<TopUpResponse>
@Multipart
@POST("store/createtopup")
suspend fun addBalanceTopUp(
@Part topupimg: MultipartBody.Part,
@Part("amount") amount: RequestBody,
@Part("payment_info_id") paymentInfoId: RequestBody,
@Part("transaction_date") transactionDate: RequestBody,
@Part("bank_name") bankName: RequestBody,
@Part("bank_num") bankNum: RequestBody
): Response<BalanceTopUpResponse>
@Multipart
@PUT("mystore/edit")
suspend fun updateStoreProfileMultipart(
@Part("store_name") storeName: RequestBody,
@Part("store_status") storeStatus: RequestBody,
@Part("store_description") storeDescription: RequestBody,
@Part("is_on_leave") isOnLeave: RequestBody,
@Part("city_id") cityId: RequestBody,
@Part("province_id") provinceId: RequestBody,
@Part("street") street: RequestBody,
@Part("subdistrict") subdistrict: RequestBody,
@Part("detail") detail: RequestBody,
@Part("postal_code") postalCode: RequestBody,
@Part("latitude") latitude: RequestBody,
@Part("longitude") longitude: RequestBody,
@Part("user_phone") userPhone: RequestBody,
@Part storeimg: MultipartBody.Part?
): Response<StoreDataResponse>
@Multipart
@POST("mystore/payment/add")
suspend fun addPaymentInfo(
@Part("bank_name") bankName: RequestBody,
@Part("bank_num") bankNum: RequestBody,
@Part("account_name") accountName: RequestBody,
@Part qris: MultipartBody.Part?
): Response<GenericResponse>
@Multipart
@POST("mystore/payment/add")
suspend fun addPaymentInfoDirect(
@Part("bank_name") bankName: RequestBody,
@Part("bank_num") bankNum: RequestBody,
@Part("account_name") accountName: RequestBody,
@Part qris: MultipartBody.Part?
): Response<AddPaymentInfoResponse>
@DELETE("mystore/payment/delete/{id}")
suspend fun deletePaymentInfo(
@Path("id") paymentMethodId: Int
): Response<GenericResponse>
// Shipping Service API endpoints
@POST("mystore/shipping/add")
suspend fun addShippingService(
@Body request: ShippingServiceRequest
): Response<GenericResponse>
@POST("mystore/shipping/delete")
suspend fun deleteShippingService(
@Body request: ShippingServiceRequest
): Response<GenericResponse>
@GET("provinces")
suspend fun getProvinces(): Response<ProvinceResponse>
@GET("cities/{provinceId}")
suspend fun getCities(
@Path("provinceId") provinceId: String
): Response<CityResponse>
@PUT("mystore/edit")
suspend fun updateStoreAddress(
@Body addressData: HashMap<String, Any?>
): Response<StoreAddressResponse>
@POST("search")
suspend fun saveSearchQuery(
@Body searchRequest: SearchRequest

View File

@ -0,0 +1,174 @@
package com.alya.ecommerce_serang.data.repository
import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.City
import com.alya.ecommerce_serang.data.api.dto.Province
import com.alya.ecommerce_serang.data.api.dto.StoreAddress
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
class AddressRepository(private val apiService: ApiService) {
private val TAG = "AddressRepository"
suspend fun getProvinces(): List<Province> = withContext(Dispatchers.IO) {
Log.d(TAG, "getProvinces() called")
try {
val response = apiService.getProvinces()
Log.d(TAG, "getProvinces() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
// Log the raw response body for debugging
val rawBody = response.raw().toString()
Log.d(TAG, "Raw response: $rawBody")
if (response.isSuccessful) {
val responseBody = response.body()
Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}")
val provinces = responseBody?.data ?: emptyList()
Log.d(TAG, "getProvinces() success, got ${provinces.size} provinces")
return@withContext provinces
} else {
val errorBody = response.errorBody()?.string() ?: "Unknown error"
Log.e(TAG, "getProvinces() error: $errorBody")
throw Exception("API Error (${response.code()}): $errorBody")
}
} catch (e: Exception) {
Log.e(TAG, "Exception in getProvinces()", e)
throw Exception("Network error: ${e.message}")
}
}
suspend fun getCities(provinceId: String): List<City> = withContext(Dispatchers.IO) {
Log.d(TAG, "getCities() called with provinceId: $provinceId")
try {
val response = apiService.getCities(provinceId)
Log.d(TAG, "getCities() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
if (response.isSuccessful) {
val responseBody = response.body()
Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}")
val cities = responseBody?.data ?: emptyList()
Log.d(TAG, "getCities() success, got ${cities.size} cities")
return@withContext cities
} else {
val errorBody = response.errorBody()?.string() ?: "Unknown error"
Log.e(TAG, "getCities() error: $errorBody")
throw Exception("API Error (${response.code()}): $errorBody")
}
} catch (e: Exception) {
Log.e(TAG, "Exception in getCities()", e)
throw Exception("Network error: ${e.message}")
}
}
suspend fun getStoreAddress(): StoreAddress? = withContext(Dispatchers.IO) {
Log.d(TAG, "getStoreAddress() called")
try {
val response = apiService.getStoreAddress()
Log.d(TAG, "getStoreAddress() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
if (response.isSuccessful) {
val responseBody = response.body()
val rawJson = Gson().toJson(responseBody)
Log.d(TAG, "Response body: $rawJson")
val address = responseBody?.data
Log.d(TAG, "getStoreAddress() success, address: $address")
// Convert numeric strings to proper types if needed
address?.let {
// Handle city_id if it's a number
if (it.cityId.isBlank() && rawJson.contains("city_id")) {
try {
val cityId = JSONObject(rawJson).getJSONObject("store").optInt("city_id", 0)
if (cityId > 0) {
it.javaClass.getDeclaredField("cityId").apply {
isAccessible = true
set(it, cityId.toString())
}
Log.d(TAG, "Updated cityId to: ${it.cityId}")
}
} catch (e: Exception) {
Log.e(TAG, "Error parsing city_id", e)
}
}
// Handle province_id if it's a number
if (it.provinceId.isBlank() && rawJson.contains("province_id")) {
try {
val provinceId = JSONObject(rawJson).getJSONObject("store").optInt("province_id", 0)
if (provinceId > 0) {
it.javaClass.getDeclaredField("provinceId").apply {
isAccessible = true
set(it, provinceId.toString())
}
Log.d(TAG, "Updated provinceId to: ${it.provinceId}")
}
} catch (e: Exception) {
Log.e(TAG, "Error parsing province_id", e)
}
}
}
return@withContext address
} else {
val errorBody = response.errorBody()?.string() ?: "Unknown error"
Log.e(TAG, "getStoreAddress() error: $errorBody")
throw Exception("API Error (${response.code()}): $errorBody")
}
} catch (e: Exception) {
Log.e(TAG, "Exception in getStoreAddress()", e)
throw Exception("Network error: ${e.message}")
}
}
suspend fun saveStoreAddress(
provinceId: String,
provinceName: String,
cityId: String,
cityName: String,
street: String,
subdistrict: String,
detail: String,
postalCode: String,
latitude: Double,
longitude: Double
): Boolean = withContext(Dispatchers.IO) {
Log.d(TAG, "saveStoreAddress() called with provinceId: $provinceId, cityId: $cityId")
try {
val addressMap = HashMap<String, Any?>()
addressMap["provinceId"] = provinceId
addressMap["provinceName"] = provinceName
addressMap["cityId"] = cityId
addressMap["cityName"] = cityName
addressMap["street"] = street
addressMap["subdistrict"] = subdistrict
addressMap["detail"] = detail
addressMap["postalCode"] = postalCode
addressMap["latitude"] = latitude
addressMap["longitude"] = longitude
Log.d(TAG, "saveStoreAddress() request data: $addressMap")
val response = apiService.updateStoreAddress(addressMap)
Log.d(TAG, "saveStoreAddress() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
if (response.isSuccessful) {
Log.d(TAG, "saveStoreAddress() success")
return@withContext true
} else {
val errorBody = response.errorBody()?.string() ?: "Unknown error"
Log.e(TAG, "saveStoreAddress() error: $errorBody")
throw Exception("API Error (${response.code()}): $errorBody")
}
} catch (e: Exception) {
Log.e(TAG, "Exception in saveStoreAddress()", e)
throw Exception("Network error: ${e.message}")
}
}
}

View File

@ -293,35 +293,6 @@ class OrderRepository(private val apiService: ApiService) {
return if (response.isSuccessful) response.body() else null
}
suspend fun fetchSells(): List<OrdersItem> {
return try {
val response = apiService.getAllOrders() // Replace with the actual method from your ApiService
if (response.isSuccessful) {
response.body()?.orders ?: emptyList() // Assuming the response body has 'orders'
} else {
Log.e("OrderRepository", "Error fetching all sells. Code: ${response.code()}")
emptyList()
}
} catch (e: Exception) {
Log.e("OrderRepository", "Exception fetching sells", e)
emptyList()
}
}
suspend fun fetchOrdersByStatus(status: String): List<OrdersItem?> {
return try {
val response = apiService.getOrdersByStatus(status) // Replace with actual method for status-based fetch
if (response.isSuccessful) {
response.body()?.orders?.filterNotNull() ?: emptyList() // Assuming the response body has 'orders'
} else {
Log.e("OrderRepository", "Error fetching orders by status ($status). Code: ${response.code()}")
emptyList()
}
} catch (e: Exception) {
Log.e("OrderRepository", "Exception fetching orders by status", e)
emptyList()
}
}
suspend fun fetchUserProfile(): Result<UserProfile?> {
return try {
val response = apiService.getUserProfile()

View File

@ -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
}
}
}

View File

@ -0,0 +1,45 @@
package com.alya.ecommerce_serang.data.repository
import android.util.Log
import com.alya.ecommerce_serang.data.api.response.store.orders.OrderListResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
class SellsRepository(private val apiService: ApiService) {
suspend fun getSellList(status: String): Result<OrderListResponse> {
return try {
Log.d("SellsRepository", "Add Evidence : $status")
val response = apiService.getSellList(status)
if (response.isSuccessful) {
val allListSell = response.body()
if (allListSell != null) {
Log.d("SellsRepository", "Add Evidence successfully: ${allListSell.message}")
Result.Success(allListSell)
} else {
Log.e("SellsRepository", "Response body was null")
Result.Error(Exception("Empty response from server"))
}
} else {
val errorBody = response.errorBody()?.string() ?: "Unknown error"
Log.e("SellsRepository", "Error Add Evidence : $errorBody")
Result.Error(Exception(errorBody))
}
} catch (e: Exception) {
Log.e("SellsRepository", "Exception Add Evidence ", e)
Result.Error(e)
}
}
suspend fun updateOrderStatus(orderId: Int?, status: String) {
try {
val response = apiService.updateOrder(orderId, status)
if (response.isSuccessful) {
Log.d("SellsRepository", "Order status updated successfully: orderId=$orderId, status=$status")
} else {
Log.e("SellsRepository", "Error updating order status: orderId=$orderId, status=$status")
}
} catch (e: Exception) {
Log.e("SellsRepository", "Exception updating order status", e)
}
}
}

View File

@ -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
}
}
}

View File

@ -2,10 +2,12 @@ package com.alya.ecommerce_serang.ui.profile.mystore
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.Store
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
@ -16,10 +18,8 @@ import com.alya.ecommerce_serang.ui.profile.mystore.balance.BalanceActivity
import com.alya.ecommerce_serang.ui.profile.mystore.product.ProductActivity
import com.alya.ecommerce_serang.ui.profile.mystore.profile.DetailStoreProfileActivity
import com.alya.ecommerce_serang.ui.profile.mystore.review.ReviewFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.all_sells.AllSellsFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.order.OrderFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.payment.PaymentFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment.ShipmentFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsActivity
import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsListFragment
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel
@ -67,10 +67,17 @@ class MyStoreActivity : AppCompatActivity() {
binding.tvStoreName.text = store.storeName
binding.tvStoreType.text = store.storeType
store.storeImage.let {
if (store.storeImage != null && store.storeImage.toString().isNotEmpty() && store.storeImage.toString() != "null") {
val imageUrl = "http://192.168.100.156:3000${store.storeImage}"
Log.d("MyStoreActivity", "Loading store image from: $imageUrl")
Glide.with(this)
.load(it)
.load(imageUrl)
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
.into(binding.ivProfile)
} else {
Log.d("MyStoreActivity", "No store image available")
}
}
@ -84,31 +91,26 @@ class MyStoreActivity : AppCompatActivity() {
}
binding.tvHistory.setOnClickListener {
supportFragmentManager.beginTransaction()
.replace(android.R.id.content, AllSellsFragment())
.addToBackStack(null)
.commit()
val intent = Intent(this, SellsActivity::class.java)
startActivity(intent)
}
binding.layoutPerluTagihan.setOnClickListener {
supportFragmentManager.beginTransaction()
.replace(android.R.id.content, OrderFragment())
.addToBackStack(null)
.commit()
val intent = Intent(this, SellsActivity::class.java)
startActivity(intent)
//navigateToSellsFragment("pending")
}
binding.layoutPembayaran.setOnClickListener {
supportFragmentManager.beginTransaction()
.replace(android.R.id.content, PaymentFragment())
.addToBackStack(null)
.commit()
val intent = Intent(this, SellsActivity::class.java)
startActivity(intent)
//navigateToSellsFragment("paid")
}
binding.layoutPerluDikirim.setOnClickListener {
supportFragmentManager.beginTransaction()
.replace(android.R.id.content, ShipmentFragment())
.addToBackStack(null)
.commit()
val intent = Intent(this, SellsActivity::class.java)
startActivity(intent)
//navigateToSellsFragment("processed")
}
binding.layoutProductMenu.setOnClickListener {
@ -129,4 +131,25 @@ class MyStoreActivity : AppCompatActivity() {
.commit()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PROFILE_REQUEST_CODE && resultCode == RESULT_OK) {
// Refresh store data
viewModel.loadMyStore()
Toast.makeText(this, "Profil toko berhasil diperbarui", Toast.LENGTH_SHORT).show()
}
}
companion object {
private const val PROFILE_REQUEST_CODE = 100
}
// private fun navigateToSellsFragment(status: String) {
// val sellsFragment = SellsListFragment.newInstance(status)
// supportFragmentManager.beginTransaction()
// .replace(android.R.id.content, sellsFragment)
// .addToBackStack(null)
// .commit()
// }
}

View File

@ -1,21 +1,414 @@
package com.alya.ecommerce_serang.ui.profile.mystore.balance
import android.app.DatePickerDialog
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.store.topup.TopUp
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.databinding.ActivityBalanceBinding
import com.alya.ecommerce_serang.utils.SessionManager
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone
class BalanceActivity : AppCompatActivity() {
private lateinit var binding: ActivityBalanceBinding
private lateinit var topUpAdapter: BalanceTransactionAdapter
private lateinit var sessionManager: SessionManager
private val calendar = Calendar.getInstance()
private var selectedDate: String? = null
private var allTopUps: List<TopUp> = emptyList()
private val TAG = "BalanceActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_balance)
binding = ActivityBalanceBinding.inflate(layoutInflater)
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
// Initialize session manager
sessionManager = SessionManager(this)
// Setup header
val headerTitle = binding.header.headerTitle
headerTitle.text = "Saldo"
val backButton = binding.header.headerLeftIcon
backButton.setOnClickListener {
finish()
}
// Setup RecyclerView
setupRecyclerView()
// Setup DatePicker
setupDatePicker()
// Add clear filter button
setupClearFilter()
// Fetch data
fetchBalance()
fetchTopUpHistory()
// Setup listeners
setupListeners()
}
private fun setupRecyclerView() {
topUpAdapter = BalanceTransactionAdapter()
binding.rvBalanceTransaction.apply {
layoutManager = LinearLayoutManager(this@BalanceActivity)
adapter = topUpAdapter
}
}
private fun setupDatePicker() {
val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth ->
calendar.set(Calendar.YEAR, year)
calendar.set(Calendar.MONTH, month)
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
updateDateInView()
// Store selected date for filtering
selectedDate = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(calendar.time)
// Show debugging information
Log.d(TAG, "Selected date: $selectedDate")
// Display all top-up dates for debugging
allTopUps.forEach { topUp ->
Log.d(TAG, "Top-up ID: ${topUp.id}, transaction_date: ${topUp.transactionDate}, created_at: ${topUp.createdAt}")
}
// Apply filter
filterTopUpsByDate(selectedDate)
// Show clear filter button
binding.btnClearFilter.visibility = View.VISIBLE
}
binding.edtTglTransaksi.setOnClickListener {
showDatePicker(dateSetListener)
}
binding.imgDatePicker.setOnClickListener {
showDatePicker(dateSetListener)
}
binding.iconDatePicker.setOnClickListener {
showDatePicker(dateSetListener)
}
}
private fun setupClearFilter() {
binding.btnClearFilter.setOnClickListener {
// Clear date selection
binding.edtTglTransaksi.text = null
selectedDate = null
// Reset to show all topups
if (allTopUps.isNotEmpty()) {
updateTopUpList(allTopUps)
} else {
fetchTopUpHistory()
}
// Hide clear button
binding.btnClearFilter.visibility = View.GONE
}
}
private fun showDatePicker(dateSetListener: DatePickerDialog.OnDateSetListener) {
DatePickerDialog(
this,
dateSetListener,
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
).show()
}
private fun updateDateInView() {
val format = "dd MMMM yyyy"
val sdf = SimpleDateFormat(format, Locale("id"))
binding.edtTglTransaksi.setText(sdf.format(calendar.time))
}
private fun setupListeners() {
binding.btnTopUp.setOnClickListener {
val intent = Intent(this, BalanceTopUpActivity::class.java)
startActivityForResult(intent, TOP_UP_REQUEST_CODE)
}
}
private fun fetchBalance() {
showLoading(true)
lifecycleScope.launch {
try {
val response = ApiConfig.getApiService(sessionManager).getMyStoreData()
if (response.isSuccessful && response.body() != null) {
val storeData = response.body()!!
val balance = storeData.store.balance
// Format the balance
try {
val balanceValue = balance.toDouble()
binding.tvBalance.text = String.format("Rp%,.0f", balanceValue)
} catch (e: Exception) {
binding.tvBalance.text = "Rp$balance"
}
} else {
Toast.makeText(
this@BalanceActivity,
"Gagal memuat data saldo: ${response.message()}",
Toast.LENGTH_SHORT
).show()
}
} catch (e: Exception) {
Log.e(TAG, "Error fetching balance", e)
Toast.makeText(
this@BalanceActivity,
"Error: ${e.message}",
Toast.LENGTH_SHORT
).show()
} finally {
showLoading(false)
}
}
}
private fun fetchTopUpHistory() {
showLoading(true)
lifecycleScope.launch {
try {
val response = ApiConfig.getApiService(sessionManager).getTopUpHistory()
if (response.isSuccessful && response.body() != null) {
val topUpData = response.body()!!
allTopUps = topUpData.topup
// Apply date filter if selected
if (selectedDate != null) {
filterTopUpsByDate(selectedDate)
} else {
updateTopUpList(allTopUps)
}
} else {
Toast.makeText(
this@BalanceActivity,
"Gagal memuat riwayat isi ulang: ${response.message()}",
Toast.LENGTH_SHORT
).show()
}
} catch (e: Exception) {
Log.e(TAG, "Error fetching top-up history", e)
Toast.makeText(
this@BalanceActivity,
"Error: ${e.message}",
Toast.LENGTH_SHORT
).show()
} finally {
showLoading(false)
}
}
}
private fun filterTopUpsByDate(dateStr: String?) {
if (dateStr == null || allTopUps.isEmpty()) {
return
}
try {
Log.d(TAG, "Filtering by date: $dateStr")
// Parse the selected date - set to start of day
val cal1 = Calendar.getInstance()
cal1.time = parseSelectedDate(dateStr)
cal1.set(Calendar.HOUR_OF_DAY, 0)
cal1.set(Calendar.MINUTE, 0)
cal1.set(Calendar.SECOND, 0)
cal1.set(Calendar.MILLISECOND, 0)
// Extract the date components we care about (year, month, day)
val selectedYear = cal1.get(Calendar.YEAR)
val selectedMonth = cal1.get(Calendar.MONTH)
val selectedDay = cal1.get(Calendar.DAY_OF_MONTH)
Log.d(TAG, "Selected date components: Year=$selectedYear, Month=$selectedMonth, Day=$selectedDay")
// Format for comparing with API dates
val filtered = allTopUps.filter { topUp ->
try {
// Debug logging
Log.d(TAG, "Examining top-up: ID=${topUp.id}")
Log.d(TAG, " - created_at=${topUp.createdAt}")
Log.d(TAG, " - transaction_date=${topUp.transactionDate}")
// Try both dates for more flexibility
val cal2 = Calendar.getInstance()
var matched = false
// Try transaction_date first
if (topUp.transactionDate.isNotEmpty()) {
val transactionDate = parseApiDate(topUp.transactionDate)
if (transactionDate != null) {
cal2.time = transactionDate
val transYear = cal2.get(Calendar.YEAR)
val transMonth = cal2.get(Calendar.MONTH)
val transDay = cal2.get(Calendar.DAY_OF_MONTH)
Log.d(TAG, " - Transaction date components: Year=$transYear, Month=$transMonth, Day=$transDay")
if (transYear == selectedYear &&
transMonth == selectedMonth &&
transDay == selectedDay) {
Log.d(TAG, " - MATCH on transaction_date")
matched = true
}
}
}
// If no match yet, try created_at
if (!matched && topUp.createdAt.isNotEmpty()) {
val createdAtDate = parseApiDate(topUp.createdAt)
if (createdAtDate != null) {
cal2.time = createdAtDate
val createdYear = cal2.get(Calendar.YEAR)
val createdMonth = cal2.get(Calendar.MONTH)
val createdDay = cal2.get(Calendar.DAY_OF_MONTH)
Log.d(TAG, " - Created date components: Year=$createdYear, Month=$createdMonth, Day=$createdDay")
if (createdYear == selectedYear &&
createdMonth == selectedMonth &&
createdDay == selectedDay) {
Log.d(TAG, " - MATCH on created_at")
matched = true
}
}
}
// Final result
Log.d(TAG, " - Match result: $matched")
matched
} catch (e: Exception) {
Log.e(TAG, "Date parsing error for top-up ${topUp.id}: ${e.message}", e)
false
}
}
Log.d(TAG, "Found ${filtered.size} matching records out of ${allTopUps.size}")
updateTopUpList(filtered)
} catch (e: Exception) {
Log.e(TAG, "Error filtering by date", e)
Toast.makeText(
this@BalanceActivity,
"Error filtering data: ${e.message}",
Toast.LENGTH_SHORT
).show()
}
}
private fun parseSelectedDate(dateStr: String): Date {
// Parse the user-selected date
try {
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)
return dateFormat.parse(dateStr) ?: Date()
} catch (e: Exception) {
Log.e(TAG, "Error parsing selected date: $dateStr", e)
return Date()
}
}
/**
* Parse ISO 8601 date format from API (handles multiple formats)
*/
private fun parseApiDate(dateStr: String): Date? {
if (dateStr.isEmpty()) return null
// List of possible date formats to try
val formats = listOf(
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", // Standard ISO with milliseconds
"yyyy-MM-dd'T'HH:mm:ss'Z'", // ISO without milliseconds
"yyyy-MM-dd'T'HH:mm:ss.SSSZ", // ISO with timezone offset
"yyyy-MM-dd'T'HH:mm:ssZ", // ISO with timezone offset, no milliseconds
"yyyy-MM-dd", // Just the date part
"dd-MM-yyyy" // Alternative date format
)
for (format in formats) {
try {
val sdf = SimpleDateFormat(format, Locale.US)
sdf.timeZone = TimeZone.getTimeZone("UTC") // Assuming API dates are in UTC
return sdf.parse(dateStr)
} catch (e: Exception) {
// Try next format
continue
}
}
// If all formats fail, just try to extract the date part and parse it
try {
val datePart = dateStr.split("T").firstOrNull() ?: return null
val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)
return simpleDateFormat.parse(datePart)
} catch (e: Exception) {
Log.e(TAG, "Failed to parse date: $dateStr", e)
return null
}
}
private fun updateTopUpList(topUps: List<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
}
}

View File

@ -1,21 +1,395 @@
package com.alya.ecommerce_serang.ui.profile.mystore.balance
import android.app.DatePickerDialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import android.provider.MediaStore
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.store.profile.Payment
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.utils.SessionManager
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
class BalanceTopUpActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_balance_top_up)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
private lateinit var imgPreview: ImageView
private lateinit var addPhotoTextView: TextView
private lateinit var edtNominal: EditText
private lateinit var spinnerPaymentMethod: Spinner
private lateinit var edtTransactionDate: EditText
private lateinit var datePickerIcon: ImageView
private lateinit var btnSend: Button
private lateinit var sessionManager: SessionManager
private var selectedImageUri: Uri? = null
private var paymentMethods: List<Payment> = emptyList()
private var selectedPaymentId: Int = -1
private val calendar = Calendar.getInstance()
private val getImageContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val imageUri = result.data?.data
imageUri?.let {
selectedImageUri = it
imgPreview.setImageURI(it)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_balance_top_up)
// Initialize session manager
sessionManager = SessionManager(this)
// Initialize views
imgPreview = findViewById(R.id.img_preview)
addPhotoTextView = findViewById(R.id.tv_tambah_foto)
edtNominal = findViewById(R.id.edt_nominal_topup)
spinnerPaymentMethod = findViewById(R.id.spinner_metode_bayar)
edtTransactionDate = findViewById(R.id.edt_tgl_transaksi)
datePickerIcon = findViewById(R.id.img_date_picker)
btnSend = findViewById(R.id.btn_send)
// Setup header title
val headerTitle = findViewById<TextView>(R.id.header_title)
headerTitle.text = "Isi Ulang Saldo"
// Setup back button
val backButton = findViewById<ImageView>(R.id.header_left_icon)
backButton.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
// Setup photo selection
addPhotoTextView.setOnClickListener {
openGallery()
}
imgPreview.setOnClickListener {
openGallery()
}
// Setup date picker
setupDatePicker()
// Fetch payment methods
fetchPaymentMethods()
// Setup submit button
btnSend.setOnClickListener {
submitForm()
}
}
private fun openGallery() {
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
getImageContent.launch(intent)
}
private fun setupDatePicker() {
val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth ->
calendar.set(Calendar.YEAR, year)
calendar.set(Calendar.MONTH, month)
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
updateDateInView()
}
edtTransactionDate.setOnClickListener {
showDatePicker(dateSetListener)
}
datePickerIcon.setOnClickListener {
showDatePicker(dateSetListener)
}
}
private fun showDatePicker(dateSetListener: DatePickerDialog.OnDateSetListener) {
DatePickerDialog(
this,
dateSetListener,
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
).show()
}
private fun updateDateInView() {
val format = "yyyy-MM-dd"
val sdf = SimpleDateFormat(format, Locale.US)
edtTransactionDate.setText(sdf.format(calendar.time))
}
private fun fetchPaymentMethods() {
lifecycleScope.launch {
try {
val response = ApiConfig.getApiService(sessionManager).getStoreData()
if (response.isSuccessful && response.body() != null) {
val storeData = response.body()!!
paymentMethods = storeData.payment
setupPaymentMethodSpinner()
} else {
Toast.makeText(
this@BalanceTopUpActivity,
"Gagal memuat metode pembayaran",
Toast.LENGTH_SHORT
).show()
}
} catch (e: Exception) {
Toast.makeText(
this@BalanceTopUpActivity,
"Error: ${e.message}",
Toast.LENGTH_SHORT
).show()
}
}
}
private fun setupPaymentMethodSpinner() {
if (paymentMethods.isEmpty()) {
Toast.makeText(
this,
"Tidak ada metode pembayaran tersedia",
Toast.LENGTH_SHORT
).show()
return
}
// Debug payment methods
for (payment in paymentMethods) {
android.util.Log.d("BalanceTopUp", "Payment Option - ID: ${payment.id}, Bank: ${payment.bankName}, Number: ${payment.bankNum}")
}
val paymentOptions = paymentMethods.map { "${it.bankName} - ${it.bankNum}" }.toTypedArray()
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, paymentOptions)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinnerPaymentMethod.adapter = adapter
spinnerPaymentMethod.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
selectedPaymentId = paymentMethods[position].id
android.util.Log.d("BalanceTopUp", "Selected payment ID: $selectedPaymentId")
}
override fun onNothingSelected(parent: AdapterView<*>?) {
selectedPaymentId = -1
}
}
}
private fun submitForm() {
// Prevent multiple clicks
if (!btnSend.isEnabled) {
return
}
// Validate inputs
if (selectedImageUri == null) {
Toast.makeText(this, "Mohon pilih foto bukti pembayaran", Toast.LENGTH_SHORT).show()
return
}
val nominal = edtNominal.text.toString().trim()
if (nominal.isEmpty()) {
Toast.makeText(this, "Mohon isi nominal top up", Toast.LENGTH_SHORT).show()
return
}
try {
// Validate the amount is a valid number
val amountValue = nominal.replace("[^0-9]".toRegex(), "").toLong()
if (amountValue <= 0) {
Toast.makeText(this, "Nominal harus lebih dari 0", Toast.LENGTH_SHORT).show()
return
}
} catch (e: NumberFormatException) {
Toast.makeText(this, "Format nominal tidak valid", Toast.LENGTH_SHORT).show()
return
}
if (selectedPaymentId == -1) {
Toast.makeText(this, "Mohon pilih metode pembayaran", Toast.LENGTH_SHORT).show()
return
}
val transactionDate = edtTransactionDate.text.toString().trim()
if (transactionDate.isEmpty()) {
Toast.makeText(this, "Mohon pilih tanggal transaksi", Toast.LENGTH_SHORT).show()
return
}
// Show progress indicator
btnSend.text = "Mengirim..."
btnSend.isEnabled = false
// Proceed with the API call
uploadTopUpData(nominal, selectedPaymentId.toString(), transactionDate)
}
private fun uploadTopUpData(amount: String, paymentInfoId: String, transactionDate: String) {
lifecycleScope.launch {
try {
// Log the values being sent
android.util.Log.d("BalanceTopUp", "Amount: $amount")
android.util.Log.d("BalanceTopUp", "Payment ID: $paymentInfoId")
android.util.Log.d("BalanceTopUp", "Transaction Date: $transactionDate")
// Find the selected payment method to get bank name
val selectedPayment = paymentMethods.find { it.id.toString() == paymentInfoId }
if (selectedPayment == null) {
Toast.makeText(
this@BalanceTopUpActivity,
"Metode pembayaran tidak valid",
Toast.LENGTH_SHORT
).show()
return@launch
}
val bankName = selectedPayment.bankName
val bankNum = selectedPayment.bankNum
android.util.Log.d("BalanceTopUp", "Bank Name: $bankName")
android.util.Log.d("BalanceTopUp", "Bank Number: $bankNum")
// Get the actual file from URI
val file = uriToFile(selectedImageUri!!)
android.util.Log.d("BalanceTopUp", "File size: ${file.length()} bytes")
android.util.Log.d("BalanceTopUp", "File name: ${file.name}")
// Create multipart file with specific JPEG content type
val requestFile = file.asRequestBody("image/jpeg".toMediaTypeOrNull())
val imagePart = MultipartBody.Part.createFormData("topupimg", file.name, requestFile)
// Create other request bodies - ensure proper formatting
// Make sure amount has no commas, spaces or currency symbols
val cleanedAmount = amount.replace("[^0-9]".toRegex(), "")
val amountBody = cleanedAmount.toRequestBody("text/plain".toMediaTypeOrNull())
val paymentInfoIdBody = paymentInfoId.toRequestBody("text/plain".toMediaTypeOrNull())
val transactionDateBody = transactionDate.toRequestBody("text/plain".toMediaTypeOrNull())
val bankNameBody = bankName.toRequestBody("text/plain".toMediaTypeOrNull())
val bankNumBody = bankNum.toRequestBody("text/plain".toMediaTypeOrNull())
// Make the API call
val response = ApiConfig.getApiService(sessionManager).addBalanceTopUp(
imagePart,
amountBody,
paymentInfoIdBody,
transactionDateBody,
bankNameBody,
bankNumBody
)
if (response.isSuccessful) {
// Log the complete response
val responseBody = response.body()
android.util.Log.d("BalanceTopUp", "Success response: ${responseBody?.message}")
// Show the actual message from backend
val successMessage = "Top Up Berhasil"
Toast.makeText(
this@BalanceTopUpActivity,
successMessage,
Toast.LENGTH_LONG
).show()
// Show a dialog with the success message
runOnUiThread {
androidx.appcompat.app.AlertDialog.Builder(this@BalanceTopUpActivity)
.setTitle("Berhasil")
.setMessage(successMessage)
.setPositiveButton("OK") { dialog, _ ->
dialog.dismiss()
finish()
}
.show()
}
} else {
// Get more detailed error information
val errorBody = response.errorBody()?.string()
android.util.Log.e("BalanceTopUp", "Error body: $errorBody")
android.util.Log.e("BalanceTopUp", "Error code: ${response.code()}")
// Try to parse the error body to extract the message
var errorMessage = "Gagal mengirim permintaan: ${response.message() ?: "Error ${response.code()}"}"
try {
val jsonObject = org.json.JSONObject(errorBody ?: "{}")
if (jsonObject.has("message")) {
errorMessage = jsonObject.getString("message")
}
} catch (e: Exception) {
android.util.Log.e("BalanceTopUp", "Error parsing error body", e)
}
Toast.makeText(
this@BalanceTopUpActivity,
errorMessage,
Toast.LENGTH_LONG
).show()
// Show a dialog with the error message
runOnUiThread {
androidx.appcompat.app.AlertDialog.Builder(this@BalanceTopUpActivity)
.setTitle("Error Response")
.setMessage(errorMessage)
.setPositiveButton("OK") { dialog, _ ->
dialog.dismiss()
}
.show()
}
}
} catch (e: Exception) {
android.util.Log.e("BalanceTopUp", "Exception: ${e.message}", e)
Toast.makeText(
this@BalanceTopUpActivity,
"Error: ${e.message}",
Toast.LENGTH_SHORT
).show()
} finally {
// Reset button state
btnSend.text = "Kirim"
btnSend.isEnabled = true
}
}
}
private fun uriToFile(uri: Uri): File {
val inputStream = contentResolver.openInputStream(uri)
val tempFile = File.createTempFile("upload", ".jpg", cacheDir)
tempFile.deleteOnExit()
inputStream?.use { input ->
tempFile.outputStream().use { output ->
input.copyTo(output)
}
}
// Validate file isn't empty
if (tempFile.length() == 0L) {
throw IllegalStateException("File is empty")
}
return tempFile
}
}

View File

@ -1,7 +1,90 @@
package com.alya.ecommerce_serang.ui.profile.mystore.balance
/* class BalanceTransactionAdapter(private val balanceTransactionList: List<BalanceTransaction>) :
RecyclerView.Adapter<BalanceTransactionAdapter.TransactionViewHolder>() {
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.store.topup.TopUp
import com.alya.ecommerce_serang.ui.profile.mystore.balance.BalanceTransactionAdapter.BalanceTransactionViewHolder
class BalanceTransactionAdapter : ListAdapter<TopUp, BalanceTransactionViewHolder>(DIFF_CALLBACK) {
}*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BalanceTransactionViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_balance_transaction, parent, false)
return BalanceTransactionViewHolder(view)
}
override fun onBindViewHolder(holder: BalanceTransactionViewHolder, position: Int) {
holder.bind(getItem(position))
}
inner class BalanceTransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvDate: TextView = itemView.findViewById(R.id.tv_balance_trans_date)
private val tvTitle: TextView = itemView.findViewById(R.id.tv_balance_trans_title)
private val tvDesc: TextView = itemView.findViewById(R.id.tv_balance_trans_desc)
private val tvAmount: TextView = itemView.findViewById(R.id.tv_balance_trans_amount)
private val ivIcon: ImageView = itemView.findViewById(R.id.iv_balance_trans_icon)
private val divider: View = itemView.findViewById(R.id.divider_balance_trans)
fun bind(topUp: TopUp) {
// Set date
tvDate.text = topUp.getFormattedDate()
// Set title
tvTitle.text = "Isi Ulang Saldo"
// Set description - payment details
val paymentMethod = topUp.paymentMethod
val accountName = topUp.accountName ?: ""
val desc = if (accountName.isNotEmpty()) {
"Isi ulang dari $paymentMethod $accountName"
} else {
"Isi ulang dari $paymentMethod"
}
tvDesc.text = desc
// Set amount
tvAmount.text = topUp.getFormattedAmount()
// Set color based on status
val context = itemView.context
val activeColor = ContextCompat.getColor(context, R.color.blue_500)
val pendingColor = ContextCompat.getColor(context, R.color.black_500)
when (topUp.status.lowercase()) {
"approved" -> {
tvAmount.setTextColor(activeColor)
ivIcon.setImageResource(R.drawable.ic_graph_arrow_increase)
}
"pending" -> {
tvAmount.setTextColor(pendingColor)
}
else -> {
tvAmount.setTextColor(activeColor)
}
}
// Show divider for all items except the last one
divider.visibility = if (bindingAdapterPosition == itemCount - 1) View.GONE else View.VISIBLE
}
}
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<TopUp>() {
override fun areItemsTheSame(oldItem: TopUp, newItem: TopUp): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: TopUp, newItem: TopUp): Boolean {
return oldItem == newItem
}
}
}
}

View File

@ -1,15 +1,22 @@
package com.alya.ecommerce_serang.ui.profile.mystore.profile
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.Store
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.MyStoreRepository
import com.alya.ecommerce_serang.databinding.ActivityDetailStoreProfileBinding
import com.alya.ecommerce_serang.ui.profile.mystore.profile.address.DetailStoreAddressActivity
import com.alya.ecommerce_serang.ui.profile.mystore.profile.payment_info.PaymentInfoActivity
import com.alya.ecommerce_serang.ui.profile.mystore.profile.shipping_service.ShippingServiceActivity
import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
@ -39,6 +46,36 @@ class DetailStoreProfileActivity : AppCompatActivity() {
enableEdgeToEdge()
// Set up header title
binding.header.headerTitle.text = "Profil Toko"
// Set up back button
binding.header.headerLeftIcon.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
binding.btnEditStoreProfile.setOnClickListener {
val intent = Intent(this, EditStoreProfileActivity::class.java)
startActivityForResult(intent, EDIT_PROFILE_REQUEST_CODE)
}
binding.layoutAddress.setOnClickListener {
val intent = Intent(this, DetailStoreAddressActivity::class.java)
startActivityForResult(intent, ADDRESS_REQUEST_CODE)
}
// Set up payment method layout click listener
binding.layoutPaymentMethod.setOnClickListener {
val intent = Intent(this, PaymentInfoActivity::class.java)
startActivityForResult(intent, PAYMENT_INFO_REQUEST_CODE)
}
// Set up shipping services layout click listener
binding.layoutShipServices.setOnClickListener {
val intent = Intent(this, ShippingServiceActivity::class.java)
startActivityForResult(intent, SHIPPING_SERVICES_REQUEST_CODE)
}
viewModel.loadMyStore()
viewModel.myStoreProfile.observe(this){ user ->
@ -50,11 +87,64 @@ class DetailStoreProfileActivity : AppCompatActivity() {
}
}
private fun updateStoreProfile(store: Store){
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == EDIT_PROFILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
// Refresh the profile data
Toast.makeText(this, "Profil toko berhasil diperbarui", Toast.LENGTH_SHORT).show()
viewModel.loadMyStore()
// Pass the result back to parent activity
setResult(Activity.RESULT_OK)
} else if (requestCode == ADDRESS_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
// Refresh the profile data after address update
Toast.makeText(this, "Alamat toko berhasil diperbarui", Toast.LENGTH_SHORT).show()
viewModel.loadMyStore()
// Pass the result back to parent activity
setResult(Activity.RESULT_OK)
} else if (requestCode == PAYMENT_INFO_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
// Refresh the profile data after payment method update
Toast.makeText(this, "Metode pembayaran berhasil diperbarui", Toast.LENGTH_SHORT).show()
viewModel.loadMyStore()
// Pass the result back to parent activity
setResult(Activity.RESULT_OK)
} else if (requestCode == SHIPPING_SERVICES_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
// Refresh the profile data after shipping services update
Toast.makeText(this, "Layanan pengiriman berhasil diperbarui", Toast.LENGTH_SHORT).show()
viewModel.loadMyStore()
// Pass the result back to parent activity
setResult(Activity.RESULT_OK)
}
}
companion object {
private const val EDIT_PROFILE_REQUEST_CODE = 100
private const val ADDRESS_REQUEST_CODE = 101
private const val PAYMENT_INFO_REQUEST_CODE = 102
private const val SHIPPING_SERVICES_REQUEST_CODE = 103
}
private fun updateStoreProfile(store: Store){
// Update text fields
binding.edtNamaToko.setText(store.storeName.toString())
binding.edtJenisToko.setText(store.storeType.toString())
binding.edtDeskripsiToko.setText(store.storeDescription.toString())
// Update store image if available
if (store.storeImage != null && store.storeImage.toString().isNotEmpty() && store.storeImage.toString() != "null") {
val imageUrl = "http://192.168.100.156:3000${store.storeImage}"
Log.d("DetailStoreProfile", "Loading image from: $imageUrl")
Glide.with(this)
.load(imageUrl)
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
.into(binding.ivProfile)
} else {
Log.d("DetailStoreProfile", "No store image available")
}
}
}

View File

@ -0,0 +1,304 @@
package com.alya.ecommerce_serang.ui.profile.mystore.profile
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.Store
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.databinding.ActivityEditStoreProfileBinding
import com.alya.ecommerce_serang.utils.SessionManager
import com.bumptech.glide.Glide
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import java.io.FileOutputStream
class EditStoreProfileActivity : AppCompatActivity() {
private lateinit var binding: ActivityEditStoreProfileBinding
private lateinit var sessionManager: SessionManager
private var storeImageUri: Uri? = null
private lateinit var currentStore: Store
private val pickImage = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let { uri ->
storeImageUri = uri
Log.d("EditStoreProfile", "Image selected: $uri")
// Set the image to the ImageView for immediate preview
try {
binding.ivStoreImage.setImageURI(null) // Clear any previous image
binding.ivStoreImage.setImageURI(uri)
// Alternative way using Glide for more reliable preview
Glide.with(this)
.load(uri)
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
.into(binding.ivStoreImage)
Toast.makeText(this, "Gambar berhasil dipilih", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Log.e("EditStoreProfile", "Error displaying image preview", e)
Toast.makeText(this, "Gagal menampilkan preview gambar", Toast.LENGTH_SHORT).show()
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityEditStoreProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
// Set up header
binding.header.headerTitle.text = "Edit Profil Toko"
binding.header.headerLeftIcon.setOnClickListener { finish() }
loadStoreData()
setupClickListeners()
}
private fun loadStoreData() {
binding.progressBar.visibility = View.VISIBLE
lifecycleScope.launch {
try {
val response = ApiConfig.getApiService(sessionManager).getStore()
binding.progressBar.visibility = View.GONE
if (response.isSuccessful && response.body() != null) {
currentStore = response.body()!!.store
populateFields(currentStore)
} else {
showError("Gagal memuat profil toko")
}
} catch (e: Exception) {
binding.progressBar.visibility = View.GONE
showError("Terjadi kesalahan: ${e.message}")
}
}
}
private fun populateFields(store: Store) {
// Load store image
if (store.storeImage != null && store.storeImage.toString().isNotEmpty() && store.storeImage.toString() != "null") {
Glide.with(this)
.load(store.storeImage.toString())
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
.into(binding.ivStoreImage)
}
// Set other fields
binding.edtStoreName.setText(store.storeName)
binding.edtDescription.setText(store.storeDescription)
binding.edtUserPhone.setText(store.userPhone)
// Set is on leave
binding.switchIsOnLeave.isChecked = store.isOnLeave
}
private fun setupClickListeners() {
binding.btnSelectStoreImage.setOnClickListener {
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
pickImage.launch(intent)
}
binding.btnSave.setOnClickListener {
saveStoreProfile()
}
}
private fun saveStoreProfile() {
val storeName = binding.edtStoreName.text.toString()
val storeDescription = binding.edtDescription.text.toString()
val userPhone = binding.edtUserPhone.text.toString()
val storeStatus = currentStore.storeStatus // Keep the current status
val isOnLeave = binding.switchIsOnLeave.isChecked
if (storeName.isEmpty() || userPhone.isEmpty()) {
showError("Nama toko dan nomor telepon harus diisi")
return
}
binding.progressBar.visibility = View.VISIBLE
binding.btnSave.isEnabled = false
// Show progress indicator on the image if we're uploading one
if (storeImageUri != null) {
binding.progressImage.visibility = View.VISIBLE
}
lifecycleScope.launch {
try {
Log.d("EditStoreProfile", "Starting profile update process")
// Create multipart request for image if selected
var storeImagePart: MultipartBody.Part? = null
if (storeImageUri != null) {
try {
val storeImageFile = uriToFile(storeImageUri!!)
Log.d("EditStoreProfile", "Image file created: ${storeImageFile.name}, size: ${storeImageFile.length()}")
// Get the MIME type
val mimeType = contentResolver.getType(storeImageUri!!) ?: "image/jpeg"
Log.d("EditStoreProfile", "MIME type: $mimeType")
val storeImageRequestBody = storeImageFile.asRequestBody(mimeType.toMediaTypeOrNull())
storeImagePart = MultipartBody.Part.createFormData("storeimg", storeImageFile.name, storeImageRequestBody)
Log.d("EditStoreProfile", "Image part created with name: storeimg, filename: ${storeImageFile.name}")
} catch (e: Exception) {
Log.e("EditStoreProfile", "Error creating image part", e)
runOnUiThread {
Toast.makeText(this@EditStoreProfileActivity, "Error preparing image: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
// Create text parts
val nameRequestBody = storeName.toRequestBody("text/plain".toMediaTypeOrNull())
val descriptionRequestBody = storeDescription.toRequestBody("text/plain".toMediaTypeOrNull())
val userPhoneRequestBody = userPhone.toRequestBody("text/plain".toMediaTypeOrNull())
val statusRequestBody = storeStatus.toRequestBody("text/plain".toMediaTypeOrNull())
val onLeaveRequestBody = isOnLeave.toString().toRequestBody("text/plain".toMediaTypeOrNull())
// Log request parameters
Log.d("EditStoreProfile", "Request parameters: " +
"\nstore_name: $storeName" +
"\nstore_status: $storeStatus" +
"\nstore_description: $storeDescription" +
"\nis_on_leave: $isOnLeave" +
"\nuser_phone: $userPhone" +
"\nimage: ${storeImageUri != null}")
// Log all parts for debugging
Log.d("EditStoreProfile", "Request parts:" +
"\nstoreName: $nameRequestBody" +
"\nstoreStatus: $statusRequestBody" +
"\nstoreDescription: $descriptionRequestBody" +
"\nisOnLeave: $onLeaveRequestBody" +
"\nuserPhone: $userPhoneRequestBody" +
"\nstoreimg: ${storeImagePart != null}")
val response = ApiConfig.getApiService(sessionManager).updateStoreProfileMultipart(
storeName = nameRequestBody,
storeStatus = statusRequestBody,
storeDescription = descriptionRequestBody,
isOnLeave = onLeaveRequestBody,
cityId = currentStore.cityId.toString().toRequestBody("text/plain".toMediaTypeOrNull()),
provinceId = currentStore.provinceId.toString().toRequestBody("text/plain".toMediaTypeOrNull()),
street = currentStore.street.toRequestBody("text/plain".toMediaTypeOrNull()),
subdistrict = currentStore.subdistrict.toRequestBody("text/plain".toMediaTypeOrNull()),
detail = currentStore.detail.toRequestBody("text/plain".toMediaTypeOrNull()),
postalCode = currentStore.postalCode.toRequestBody("text/plain".toMediaTypeOrNull()),
latitude = currentStore.latitude.toRequestBody("text/plain".toMediaTypeOrNull()),
longitude = currentStore.longitude.toRequestBody("text/plain".toMediaTypeOrNull()),
userPhone = userPhoneRequestBody,
storeimg = storeImagePart
)
Log.d("EditStoreProfile", "Response received: isSuccessful=${response.isSuccessful}, code=${response.code()}")
runOnUiThread {
binding.progressBar.visibility = View.GONE
binding.progressImage.visibility = View.GONE
binding.btnSave.isEnabled = true
if (response.isSuccessful) {
Log.d("EditStoreProfile", "Response body: ${response.body()?.toString()}")
// Try to log the updated store image URL
response.body()?.let { responseBody ->
val updatedStoreImage = responseBody.store?.storeImage
Log.d("EditStoreProfile", "Updated store image URL: $updatedStoreImage")
}
showSuccess("Profil toko berhasil diperbarui")
setResult(Activity.RESULT_OK)
finish()
} else {
val errorBodyString = response.errorBody()?.string() ?: "Error body is null"
Log.e("EditStoreProfile", "Full error response: $errorBodyString")
Log.e("EditStoreProfile", "Response headers: ${response.headers()}")
showError("Gagal memperbarui profil toko (${response.code()})")
}
}
} catch (e: Exception) {
Log.e("EditStoreProfile", "Exception during API call", e)
runOnUiThread {
binding.progressBar.visibility = View.GONE
binding.progressImage.visibility = View.GONE
binding.btnSave.isEnabled = true
showError("Error: ${e.message}")
}
}
}
}
private fun uriToFile(uri: Uri): File {
val contentResolver = applicationContext.contentResolver
val fileExtension = getFileExtension(contentResolver, uri)
val timeStamp = System.currentTimeMillis()
val fileName = "IMG_${timeStamp}.$fileExtension"
val tempFile = File(cacheDir, fileName)
Log.d("EditStoreProfile", "Creating temp file: ${tempFile.absolutePath}")
try {
contentResolver.openInputStream(uri)?.use { inputStream ->
FileOutputStream(tempFile).use { outputStream ->
val buffer = ByteArray(4 * 1024) // 4k buffer
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
}
outputStream.flush()
}
}
Log.d("EditStoreProfile", "File created successfully: ${tempFile.name}, size: ${tempFile.length()} bytes")
return tempFile
} catch (e: Exception) {
Log.e("EditStoreProfile", "Error creating file from URI", e)
throw e
}
}
private fun getFileExtension(contentResolver: android.content.ContentResolver, uri: Uri): String {
val mimeType = contentResolver.getType(uri)
return if (mimeType != null) {
val mime = android.webkit.MimeTypeMap.getSingleton()
mime.getExtensionFromMimeType(mimeType) ?: "jpg"
} else {
// If mime type is null, try to get from URI path
val path = uri.path
if (path != null) {
val extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(path)
if (!extension.isNullOrEmpty()) {
extension
} else "jpg"
} else "jpg"
}
}
private fun showSuccess(message: String) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
private fun showError(message: String) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
}

View File

@ -1,21 +1,315 @@
package com.alya.ecommerce_serang.ui.profile.mystore.profile.address
import android.app.Activity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import android.util.Log
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.BuildConfig
import com.alya.ecommerce_serang.data.api.dto.City
import com.alya.ecommerce_serang.data.api.dto.Province
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.AddressRepository
import com.alya.ecommerce_serang.databinding.ActivityDetailStoreAddressBinding
import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.google.android.material.snackbar.Snackbar
class DetailStoreAddressActivity : AppCompatActivity() {
private lateinit var binding: ActivityDetailStoreAddressBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
private var selectedProvinceId: String? = null
private var provinces: List<Province> = emptyList()
private var cities: List<City> = emptyList()
private val TAG = "StoreAddressActivity"
private val viewModel: AddressViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val addressRepository = AddressRepository(apiService)
AddressViewModel(addressRepository)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_detail_store_address)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
binding = ActivityDetailStoreAddressBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager)
// Log the base URL
Log.d(TAG, "BASE_URL: ${BuildConfig.BASE_URL}")
// Add error text view
binding.tvError.visibility = View.GONE
// Set up header title
binding.header.headerTitle.text = "Atur Alamat Toko"
// Set up back button
binding.header.headerLeftIcon.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
setupSpinners()
setupObservers()
setupSaveButton()
// Add retry button
binding.btnRetry.setOnClickListener {
binding.tvError.visibility = View.GONE
binding.progressBar.visibility = View.VISIBLE
Log.d(TAG, "Retrying to fetch provinces...")
viewModel.fetchProvinces()
}
// Show loading spinners initially
showProvinceLoading(true)
// Load existing address data first
Log.d(TAG, "Fetching store address...")
viewModel.fetchStoreAddress()
// Load provinces data
Log.d(TAG, "Fetching provinces...")
viewModel.fetchProvinces()
}
private fun setupSpinners() {
// Province spinner listener
binding.spinnerProvince.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
Log.d(TAG, "Province selected at position: $position")
if (position > 0 && provinces.isNotEmpty()) {
selectedProvinceId = provinces[position - 1].provinceId
Log.d(TAG, "Selected province ID: $selectedProvinceId")
selectedProvinceId?.let {
Log.d(TAG, "Fetching cities for province ID: $it")
showCityLoading(true)
viewModel.fetchCities(it)
}
}
}
override fun onNothingSelected(p0: AdapterView<*>?) {
// Do nothing
}
}
}
private fun setupObservers() {
// Observe provinces data
viewModel.provinces.observe(this) { provinceList ->
Log.d(TAG, "Received provinces: ${provinceList.size}")
showProvinceLoading(false)
if (provinceList.isEmpty()) {
showError("No provinces available")
return@observe
}
provinces = provinceList
val provinceNames = mutableListOf("Pilih Provinsi")
provinceNames.addAll(provinceList.map { it.provinceName })
Log.d(TAG, "Province names: $provinceNames")
val adapter = ArrayAdapter(
this,
android.R.layout.simple_spinner_item,
provinceNames
)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.spinnerProvince.adapter = adapter
}
// Observe cities data
viewModel.cities.observe(this) { cityList ->
Log.d(TAG, "Received cities: ${cityList.size}")
showCityLoading(false)
cities = cityList
val cityNames = mutableListOf("Pilih Kota/Kabupaten")
cityNames.addAll(cityList.map { it.cityName })
Log.d(TAG, "City names: $cityNames")
val adapter = ArrayAdapter(
this,
android.R.layout.simple_spinner_item,
cityNames
)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.spinnerCity.adapter = adapter
// If we have a stored city_id, select it
viewModel.storeAddress.value?.let { address ->
if (address.cityId.isNotEmpty()) {
val cityIndex = cities.indexOfFirst { city ->
city.cityId == address.cityId
}
Log.d(TAG, "City index for ID ${address.cityId}: $cityIndex")
if (cityIndex != -1) {
binding.spinnerCity.setSelection(cityIndex + 1) // +1 because of "Pilih Kota/Kabupaten"
}
}
}
}
// Observe store address data
viewModel.storeAddress.observe(this) { address ->
Log.d(TAG, "Received store address: $address")
address?.let {
// Set the fields
binding.edtStreet.setText(address.street)
binding.edtSubdistrict.setText(address.subdistrict)
binding.edtDetailAddress.setText(address.detail ?: "")
binding.edtPostalCode.setText(address.postalCode)
// Handle latitude and longitude
val lat = if (address.latitude == null || address.latitude.toString() == "NaN") 0.0 else address.latitude
val lng = if (address.longitude == null || address.longitude.toString() == "NaN") 0.0 else address.longitude
binding.edtLatitude.setText(lat.toString())
binding.edtLongitude.setText(lng.toString())
// Set selected province ID to trigger city loading
if (address.provinceId.isNotEmpty()) {
selectedProvinceId = address.provinceId
// Find province index and select it after provinces are loaded
if (provinces.isNotEmpty()) {
val provinceIndex = provinces.indexOfFirst { province ->
province.provinceId == address.provinceId
}
Log.d(TAG, "Province index for ID ${address.provinceId}: $provinceIndex")
if (provinceIndex != -1) {
binding.spinnerProvince.setSelection(provinceIndex + 1) // +1 because of "Pilih Provinsi"
// Now fetch cities for this province
showCityLoading(true)
viewModel.fetchCities(address.provinceId)
}
}
}
}
}
// Observe loading state
viewModel.isLoading.observe(this) { isLoading ->
binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
}
// Observe error messages
viewModel.errorMessage.observe(this) { errorMsg ->
Log.e(TAG, "Error: $errorMsg")
showError(errorMsg)
}
// Observe save success
viewModel.saveSuccess.observe(this) { success ->
if (success) {
Toast.makeText(this, "Alamat berhasil disimpan", Toast.LENGTH_SHORT).show()
setResult(Activity.RESULT_OK)
finish()
}
}
}
private fun showProvinceLoading(isLoading: Boolean) {
binding.provinceProgressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
binding.spinnerProvince.visibility = if (isLoading) View.GONE else View.VISIBLE
}
private fun showCityLoading(isLoading: Boolean) {
binding.cityProgressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
binding.spinnerCity.visibility = if (isLoading) View.GONE else View.VISIBLE
}
private fun showError(message: String) {
binding.progressBar.visibility = View.GONE
binding.tvError.visibility = View.VISIBLE
binding.tvError.text = "Error: $message\nURL: ${BuildConfig.BASE_URL}/provinces"
binding.btnRetry.visibility = View.VISIBLE
// Also show in a dialog for immediate attention
AlertDialog.Builder(this)
.setTitle("Error")
.setMessage("$message\n\nAPI URL: ${BuildConfig.BASE_URL}/provinces")
.setPositiveButton("Retry") { _, _ ->
binding.tvError.visibility = View.GONE
binding.progressBar.visibility = View.VISIBLE
viewModel.fetchProvinces()
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
.show()
// Also show a snackbar
Snackbar.make(binding.root, "Error: $message", Snackbar.LENGTH_LONG)
.setAction("Retry") {
binding.tvError.visibility = View.GONE
binding.progressBar.visibility = View.VISIBLE
viewModel.fetchProvinces()
}
.show()
}
private fun setupSaveButton() {
binding.btnSaveAddress.setOnClickListener {
val street = binding.edtStreet.text.toString()
val subdistrict = binding.edtSubdistrict.text.toString()
val detail = binding.edtDetailAddress.text.toString()
val postalCode = binding.edtPostalCode.text.toString()
val latitudeStr = binding.edtLatitude.text.toString()
val longitudeStr = binding.edtLongitude.text.toString()
// Validate required fields
if (selectedProvinceId == null || binding.spinnerCity.selectedItemPosition <= 0 ||
street.isEmpty() || subdistrict.isEmpty() || postalCode.isEmpty()) {
Toast.makeText(this, "Mohon lengkapi data yang wajib diisi", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
// Get selected city
val cityPosition = binding.spinnerCity.selectedItemPosition
if (cityPosition <= 0 || cities.isEmpty() || cityPosition > cities.size) {
Toast.makeText(this, "Mohon pilih kota/kabupaten", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val selectedCity = cities[cityPosition - 1]
// Parse coordinates
val latitude = latitudeStr.toDoubleOrNull() ?: 0.0
val longitude = longitudeStr.toDoubleOrNull() ?: 0.0
// Save address
viewModel.saveStoreAddress(
provinceId = selectedProvinceId!!,
provinceName = provinces.find { it.provinceId == selectedProvinceId }?.provinceName ?: "",
cityId = selectedCity.cityId,
cityName = selectedCity.cityName,
street = street,
subdistrict = subdistrict,
detail = detail,
postalCode = postalCode,
latitude = latitude,
longitude = longitude
)
}
}
}

View File

@ -1,21 +1,282 @@
package com.alya.ecommerce_serang.ui.profile.mystore.profile.payment_info
import android.app.Activity
import android.app.AlertDialog
import android.net.Uri
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.PaymentInfo
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.PaymentInfoRepository
import com.alya.ecommerce_serang.databinding.ActivityPaymentInfoBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.UriToFileConverter
import com.alya.ecommerce_serang.utils.viewmodel.PaymentInfoViewModel
import com.google.android.material.snackbar.Snackbar
import java.io.File
class PaymentInfoActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_payment_info)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
private val TAG = "PaymentInfoActivity"
private lateinit var binding: ActivityPaymentInfoBinding
private lateinit var adapter: PaymentInfoAdapter
private lateinit var sessionManager: SessionManager
private var selectedQrisImageUri: Uri? = null
private var selectedQrisImageFile: File? = null
// Store form data between dialog reopenings
private var savedBankName: String = ""
private var savedBankNumber: String = ""
private var savedAccountName: String = ""
private val viewModel: PaymentInfoViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val repository = PaymentInfoRepository(apiService)
PaymentInfoViewModel(repository)
}
}
private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let {
try {
Log.d(TAG, "Selected image URI: $uri")
selectedQrisImageUri = it
// Convert URI to File
selectedQrisImageFile = UriToFileConverter.uriToFile(it, this)
if (selectedQrisImageFile == null) {
Log.e(TAG, "Failed to convert URI to file")
showSnackbar("Failed to process image. Please try another image.")
return@let
}
Log.d(TAG, "Converted to file: ${selectedQrisImageFile?.absolutePath}, size: ${selectedQrisImageFile?.length()} bytes")
// Check if file exists and has content
if (!selectedQrisImageFile!!.exists() || selectedQrisImageFile!!.length() == 0L) {
Log.e(TAG, "File doesn't exist or is empty: ${selectedQrisImageFile?.absolutePath}")
showSnackbar("Failed to process image. Please try another image.")
selectedQrisImageFile = null
return@let
}
showAddPaymentDialog(true) // Reopen dialog with selected image
} catch (e: Exception) {
Log.e(TAG, "Error processing selected image", e)
showSnackbar("Error processing image: ${e.message}")
selectedQrisImageUri = null
selectedQrisImageFile = null
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPaymentInfoBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
// Configure header
binding.header.headerTitle.text = "Atur Metode Pembayaran"
binding.header.headerLeftIcon.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
setupRecyclerView()
setupObservers()
binding.btnAddPayment.setOnClickListener {
// Clear saved values when opening a new dialog
savedBankName = ""
savedBankNumber = ""
savedAccountName = ""
selectedQrisImageUri = null
selectedQrisImageFile = null
showAddPaymentDialog(false)
}
// Load payment info
viewModel.getPaymentInfo()
}
private fun setupRecyclerView() {
adapter = PaymentInfoAdapter(
onDeleteClick = { paymentMethod ->
showDeleteConfirmationDialog(paymentMethod)
}
)
binding.rvPaymentInfo.layoutManager = LinearLayoutManager(this)
binding.rvPaymentInfo.adapter = adapter
}
private fun setupObservers() {
viewModel.paymentInfos.observe(this) { paymentInfo ->
binding.progressBar.visibility = View.GONE
if (paymentInfo.isEmpty()) {
binding.tvEmptyState.visibility = View.VISIBLE
binding.rvPaymentInfo.visibility = View.GONE
} else {
binding.tvEmptyState.visibility = View.GONE
binding.rvPaymentInfo.visibility = View.VISIBLE
adapter.submitList(paymentInfo)
}
}
viewModel.isLoading.observe(this) { isLoading ->
binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
binding.btnAddPayment.isEnabled = !isLoading
}
viewModel.errorMessage.observe(this) { errorMessage ->
if (errorMessage.isNotEmpty()) {
showSnackbar(errorMessage)
Log.e(TAG, "Error: $errorMessage")
}
}
viewModel.addPaymentSuccess.observe(this) { success ->
if (success) {
showSnackbar("Metode pembayaran berhasil ditambahkan")
setResult(Activity.RESULT_OK)
}
}
viewModel.deletePaymentSuccess.observe(this) { success ->
if (success) {
showSnackbar("Metode pembayaran berhasil dihapus")
setResult(Activity.RESULT_OK)
}
}
}
private fun showSnackbar(message: String) {
Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show()
}
private fun showAddPaymentDialog(isReopened: Boolean) {
val builder = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_add_payment_info, null)
builder.setView(dialogView)
val dialog = builder.create()
// Get references to views in the dialog
val btnAddQris = dialogView.findViewById<Button>(R.id.btn_add_qris)
val bankNameEditText = dialogView.findViewById<EditText>(R.id.edt_bank_name)
val bankNumberEditText = dialogView.findViewById<EditText>(R.id.edt_bank_number)
val accountNameEditText = dialogView.findViewById<EditText>(R.id.edt_account_name)
val qrisPreview = dialogView.findViewById<ImageView>(R.id.iv_qris_preview)
val btnCancel = dialogView.findViewById<Button>(R.id.btn_cancel)
val btnSave = dialogView.findViewById<Button>(R.id.btn_save)
// When reopening, restore the previously entered values
if (isReopened) {
bankNameEditText.setText(savedBankName)
bankNumberEditText.setText(savedBankNumber)
accountNameEditText.setText(savedAccountName)
if (selectedQrisImageUri != null) {
Log.d(TAG, "Showing selected QRIS image: $selectedQrisImageUri")
qrisPreview.setImageURI(selectedQrisImageUri)
qrisPreview.visibility = View.VISIBLE
showSnackbar("Gambar QRIS berhasil dipilih")
}
}
btnAddQris.setOnClickListener {
// Save the current values before dismissing
savedBankName = bankNameEditText.text.toString().trim()
savedBankNumber = bankNumberEditText.text.toString().trim()
savedAccountName = accountNameEditText.text.toString().trim()
getContent.launch("image/*")
dialog.dismiss() // Dismiss the current dialog as we'll reopen it
}
btnCancel.setOnClickListener {
dialog.dismiss()
}
btnSave.setOnClickListener {
val bankName = bankNameEditText.text.toString().trim()
val bankNumber = bankNumberEditText.text.toString().trim()
val accountName = accountNameEditText.text.toString().trim()
// Validation
if (bankName.isEmpty()) {
showSnackbar("Nama bank tidak boleh kosong")
return@setOnClickListener
}
if (bankNumber.isEmpty()) {
showSnackbar("Nomor rekening tidak boleh kosong")
return@setOnClickListener
}
if (accountName.isEmpty()) {
showSnackbar("Nama pemilik rekening tidak boleh kosong")
return@setOnClickListener
}
if (bankNumber.any { !it.isDigit() }) {
showSnackbar("Nomor rekening hanya boleh berisi angka")
return@setOnClickListener
}
// Log the data being sent
Log.d(TAG, "====== SENDING PAYMENT METHOD DATA ======")
Log.d(TAG, "Bank Name: $bankName")
Log.d(TAG, "Bank Number: $bankNumber")
Log.d(TAG, "Account Name: $accountName")
if (selectedQrisImageFile != null) {
Log.d(TAG, "QRIS file path: ${selectedQrisImageFile?.absolutePath}")
Log.d(TAG, "QRIS file exists: ${selectedQrisImageFile?.exists()}")
Log.d(TAG, "QRIS file size: ${selectedQrisImageFile?.length()} bytes")
} else {
Log.d(TAG, "No QRIS file selected")
}
// Temporarily disable the save button
btnSave.isEnabled = false
btnSave.text = "Menyimpan..."
// Add payment info
viewModel.addPaymentInfo(
bankName = bankName,
bankNumber = bankNumber,
accountName = accountName,
qrisImageUri = selectedQrisImageUri,
qrisImageFile = selectedQrisImageFile
)
dialog.dismiss()
}
dialog.show()
}
private fun showDeleteConfirmationDialog(paymentInfo: PaymentInfo) {
AlertDialog.Builder(this)
.setTitle("Hapus Metode Pembayaran")
.setMessage("Apakah Anda yakin ingin menghapus metode pembayaran ini?")
.setPositiveButton("Hapus") { _, _ ->
viewModel.deletePaymentInfo(paymentInfo.id)
}
.setNegativeButton("Batal", null)
.show()
}
}

View File

@ -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
}
}
}
}

View File

@ -1,21 +1,108 @@
package com.alya.ecommerce_serang.ui.profile.mystore.profile.shipping_service
import android.app.Activity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import android.view.View
import android.widget.CheckBox
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.ShippingServiceRepository
import com.alya.ecommerce_serang.databinding.ActivityShippingServiceBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.ShippingServiceViewModel
class ShippingServiceActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_shipping_service)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
private lateinit var binding: ActivityShippingServiceBinding
private lateinit var sessionManager: SessionManager
private val courierCheckboxes = mutableListOf<Pair<CheckBox, String>>()
private val viewModel: ShippingServiceViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val repository = ShippingServiceRepository(apiService)
ShippingServiceViewModel(repository)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityShippingServiceBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
// Configure header
binding.header.headerTitle.text = "Atur Layanan Pengiriman"
binding.header.headerLeftIcon.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
setupCourierCheckboxes()
setupObservers()
binding.btnSave.setOnClickListener {
saveShippingServices()
}
// Load shipping services
viewModel.getAvailableCouriers()
}
private fun setupCourierCheckboxes() {
// Add all courier checkboxes to the list for easy management
courierCheckboxes.add(Pair(binding.checkboxJne, "jne"))
courierCheckboxes.add(Pair(binding.checkboxPos, "pos"))
courierCheckboxes.add(Pair(binding.checkboxTiki, "tiki"))
}
private fun setupObservers() {
viewModel.availableCouriers.observe(this) { couriers ->
// Check the appropriate checkboxes based on available couriers
for (pair in courierCheckboxes) {
val checkbox = pair.first
val courierCode = pair.second
checkbox.isChecked = couriers.contains(courierCode)
}
}
viewModel.isLoading.observe(this) { isLoading ->
binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
binding.contentLayout.visibility = if (isLoading) View.GONE else View.VISIBLE
}
viewModel.errorMessage.observe(this) { errorMessage ->
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show()
}
viewModel.saveSuccess.observe(this) { success ->
if (success) {
Toast.makeText(this, "Layanan pengiriman berhasil disimpan", Toast.LENGTH_SHORT).show()
setResult(Activity.RESULT_OK)
finish()
}
}
}
private fun saveShippingServices() {
val selectedCouriers = mutableListOf<String>()
for (pair in courierCheckboxes) {
val checkbox = pair.first
val courierCode = pair.second
if (checkbox.isChecked) {
selectedCouriers.add(courierCode)
}
}
if (selectedCouriers.isEmpty()) {
Toast.makeText(this, "Pilih minimal satu layanan pengiriman", Toast.LENGTH_SHORT).show()
return
}
viewModel.saveShippingServices(selectedCouriers)
}
}

View File

@ -2,18 +2,48 @@ package com.alya.ecommerce_serang.ui.profile.mystore.sells
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.ui.profile.mystore.sells.all_sells.AllSellsFragment
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.SellsRepository
import com.alya.ecommerce_serang.databinding.ActivitySellsBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
class SellsActivity : AppCompatActivity() {
private lateinit var binding: ActivitySellsBinding
private lateinit var sessionManager: SessionManager
private val viewModel: SellsViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val sellsRepository = SellsRepository(apiService)
SellsViewModel(sellsRepository)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sells)
binding = ActivitySellsBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
setupHeader()
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.sells_fragment_container, AllSellsFragment())
.replace(R.id.fragment_container_sells, SellsFragment())
.commit()
}
}
private fun setupHeader() {
binding.header.headerTitle.text = "Penjualan Saya"
binding.header.headerLeftIcon.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
}
}

View File

@ -1,152 +1,188 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
import com.alya.ecommerce_serang.data.api.response.store.orders.OrdersItem
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
import com.bumptech.glide.Glide
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
class SellsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var orderList: List<OrdersItem?> = emptyList()
class SellsAdapter(
private val onOrderClickListener: (OrdersItem) -> Unit,
private val viewModel: SellsViewModel
) : RecyclerView.Adapter<SellsAdapter.SellsViewHolder>() {
// View Types for different statuses
private val TYPE_PENDING = 0
private val TYPE_PAYMENT = 1
private val TYPE_SHIPMENT = 2
private val TYPE_COMPLETED = 3
private val TYPE_FAILED_PAYMENT = 4
private val TYPE_FAILED_SHIPMENT = 5
private val sells = mutableListOf<OrdersItem>()
private var fragmentStatus: String = "all"
// Method to submit list to the adapter
fun submitList(orders: List<OrdersItem?>?) {
orderList = orders ?: emptyList()
fun setFragmentStatus(status: String) {
fragmentStatus = status
}
fun submitList(newSells: List<OrdersItem>) {
sells.clear()
sells.addAll(newSells)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
TYPE_PENDING -> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_sells_order, parent, false)
OrderViewHolder(view)
fun findResource(status: String): Int {
return when (status) {
"pending" -> R.layout.item_sells_order
"paid" -> R.layout.item_sells_payment
"processed" -> R.layout.item_sells_shipment
else -> R.layout.item_sells_payment
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SellsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(findResource(fragmentStatus), parent, false)
return SellsViewHolder(view)
}
override fun onBindViewHolder(holder: SellsViewHolder, position: Int) {
holder.bind(sells[position])
}
override fun getItemCount(): Int = sells.size
inner class SellsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvSellsTitle: TextView = itemView.findViewById(R.id.tv_sells_title)
private val tvSellsNumber: TextView = itemView.findViewById(R.id.tv_sells_number)
private val tvSellsDueDesc: TextView = itemView.findViewById(R.id.tv_sells_due_desc)
private val tvSellsDue: TextView = itemView.findViewById(R.id.tv_sells_due)
private val tvSellsLocation: TextView = itemView.findViewById(R.id.tv_sells_location)
private val tvSellsCustomer: TextView = itemView.findViewById(R.id.tv_sells_customer)
private val ivSellsProduct: ImageView = itemView.findViewById(R.id.iv_sells_product)
private val tvSellsProductName: TextView = itemView.findViewById(R.id.tv_sells_product_name)
private val tvSellsProductQty: TextView = itemView.findViewById(R.id.tv_sells_product_qty)
private val tvSellsProductPrice: TextView = itemView.findViewById(R.id.tv_sells_product_price)
private val tvSeeMore: TextView = itemView.findViewById(R.id.tv_see_more)
private val tvSellsQty: TextView = itemView.findViewById(R.id.tv_sells_qty)
private val tvSellsPrice: TextView = itemView.findViewById(R.id.tv_sells_price)
private val btnEditOrder: Button = itemView.findViewById(R.id.btn_edit_order)
private val btnConfirmOrder: Button = itemView.findViewById(R.id.btn_confirm_order)
private val btnConfirmPayment: Button = itemView.findViewById(R.id.btn_confirm_payment)
private val btnConfirmShipment: Button = itemView.findViewById(R.id.btn_confirm_shipment)
fun bind(sells: OrdersItem) {
tvSellsNumber.text = "No. Pesanan: ${sells.orderId}"
tvSellsLocation.text = sells.subdistrict
tvSellsCustomer.text = sells.username
val product = sells.orderItems?.get(0)
product?.let {
tvSellsProductName.text = it.productName
tvSellsProductQty.text = "x${it.quantity}"
tvSellsProductPrice.text = "Rp${it.price}"
Glide.with(itemView.context)
.load(it.productImage)
.placeholder(R.drawable.placeholder_image)
.into(ivSellsProduct)
}
TYPE_PAYMENT -> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_sells_payment, parent, false)
PaymentViewHolder(view)
sells.orderItems?.size?.let {
if (it > 1) {
tvSeeMore.visibility = View.VISIBLE
tvSeeMore.text = "Lihat ${it.minus(1)} produk lainnya"
} else {
tvSeeMore.visibility = View.GONE
}
}
TYPE_SHIPMENT -> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_sells_shipment, parent, false)
ShipmentViewHolder(view)
}
// TYPE_COMPLETED -> {
// val view = LayoutInflater.from(parent.context).inflate(R.layout.item_sells_completed, parent, false)
// CompletedViewHolder(view)
// }
// TYPE_FAILED_PAYMENT -> {
// val view = LayoutInflater.from(parent.context).inflate(R.layout.item_sells_failed_payment, parent, false)
// FailedPaymentViewHolder(view)
// }
// TYPE_FAILED_SHIPMENT -> {
// val view = LayoutInflater.from(parent.context).inflate(R.layout.item_sells_failed_shipment, parent, false)
// FailedShipmentViewHolder(view)
// }
else -> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_sells_order, parent, false)
OrderViewHolder(view)
tvSellsQty.text = "${sells.orderItems?.size} produk"
tvSellsPrice.text = "Rp${sells.totalAmount}"
adjustDisplay(fragmentStatus, sells)
}
private fun adjustDisplay(status: String, sells: OrdersItem) {
Log.d("SellsAdapter", "Adjusting display for status: $status")
when (status) {
"pending" -> {
tvSellsDue.text = formatDueDate(sells.updatedAt.toString(), 3)
btnEditOrder.setOnClickListener {
TODO("Go to DetailOrderActivity")
}
btnConfirmOrder.setOnClickListener {
viewModel.updateOrderStatus(sells.orderId, "unpaid")
}
}
"paid" -> {
tvSellsDue.text = formatDueDate(sells.updatedAt.toString(), 1)
btnConfirmPayment.setOnClickListener {
TODO("Go to DetailPaymentActivity")
}
}
"processed" -> {
tvSellsDue.text = formatDueDate(sells.updatedAt.toString(), 2)
btnConfirmShipment.setOnClickListener {
TODO("Go to DetailShipmentActivity")
}
}
"shipped" -> {
tvSellsTitle.text = "Pesanan Telah Dikirim"
tvSellsDueDesc.text = "Dikirimkan pada"
tvSellsDue.text = formatDueDate(sells.updatedAt.toString(), 0)
tvSellsDue.background = itemView.context.getDrawable(R.drawable.bg_product_inactive)
btnConfirmPayment.visibility = View.GONE
}
"delivered" -> {
tvSellsTitle.text = "Pesanan Telah Dikirim"
tvSellsDueDesc.text = "Dikirimkan pada"
tvSellsDue.text = formatDueDate(sells.updatedAt.toString(), 0)
tvSellsDue.background = itemView.context.getDrawable(R.drawable.bg_product_inactive)
btnConfirmPayment.visibility = View.GONE
}
"completed" -> {
tvSellsTitle.text = "Pesanan Selesai"
tvSellsDueDesc.text = "Selesai pada"
tvSellsDue.text = formatDueDate(sells.updatedAt.toString(), 0)
tvSellsDue.background = itemView.context.getDrawable(R.drawable.bg_product_inactive)
btnConfirmPayment.visibility = View.GONE
}
"canceled" -> {
tvSellsTitle.text = "Pesanan Dibatalkan"
tvSellsDueDesc.text = "Dibatalkan pada"
tvSellsDue.text = formatDueDate(sells.updatedAt.toString(), 0)
tvSellsDue.background = itemView.context.getDrawable(R.drawable.bg_product_inactive)
btnConfirmPayment.visibility = View.GONE
}
}
}
}
// Determine the view type based on the order status
override fun getItemViewType(position: Int): Int {
val order = orderList[position]
return when (order?.shipmentStatus) {
"pending" -> TYPE_PENDING
"paid" -> TYPE_PAYMENT
"shipped" -> TYPE_SHIPMENT
"completed" -> TYPE_COMPLETED
"failedPayment" -> TYPE_FAILED_PAYMENT
"failedShipment" -> TYPE_FAILED_SHIPMENT
else -> TYPE_PENDING // Default to pending if no status is matched
}
}
private fun formatDueDate(date: String, dueDay: Int): String {
return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val order = orderList[position]
when (holder) {
is OrderViewHolder -> holder.bind(order)
is PaymentViewHolder -> holder.bind(order)
is ShipmentViewHolder -> holder.bind(order)
is CompletedViewHolder -> holder.bind(order)
is FailedPaymentViewHolder -> holder.bind(order)
is FailedShipmentViewHolder -> holder.bind(order)
}
}
val outputFormat = SimpleDateFormat("dd MM; HH.mm", Locale("id", "ID"))
override fun getItemCount(): Int = orderList.size
val date = inputFormat.parse(date)
// ViewHolder for 'pending' status (Order)
class OrderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvOrderNumber: TextView = itemView.findViewById(R.id.tv_order_number)
private val tvOrderCustomer: TextView = itemView.findViewById(R.id.tv_order_customer)
private val tvOrderPrice: TextView = itemView.findViewById(R.id.tv_order_price)
date?.let {
val calendar = Calendar.getInstance()
calendar.time = it
calendar.add(Calendar.DATE, dueDay)
fun bind(order: OrdersItem?) {
tvOrderNumber.text = "Order #${order?.orderId}"
tvOrderCustomer.text = order?.userId.toString()
tvOrderPrice.text = "Total: ${order?.totalAmount}"
}
}
// ViewHolder for 'paid' status (Payment)
class PaymentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvPaymentNumber: TextView = itemView.findViewById(R.id.tv_payment_number)
private val tvPaymentCustomer: TextView = itemView.findViewById(R.id.tv_payment_customer)
private val tvPaymentPrice: TextView = itemView.findViewById(R.id.tv_payment_price)
fun bind(order: OrdersItem?) {
tvPaymentNumber.text = "Order #${order?.orderId}"
tvPaymentCustomer.text = order?.userId.toString()
tvPaymentPrice.text = "Paid: ${order?.totalAmount}"
}
}
// ViewHolder for 'shipped' status (Shipment)
class ShipmentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvShipmentNumber: TextView = itemView.findViewById(R.id.tv_shipment_number)
private val tvShipmentLocation: TextView = itemView.findViewById(R.id.tv_shipment_location)
fun bind(order: OrdersItem?) {
tvShipmentNumber.text = "Shipment #${order?.orderId}"
tvShipmentLocation.text = "Location: ${order?.addressId.toString()}"
}
}
// ViewHolder for 'completed' status
class CompletedViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
//private val tvCompletedNumber: TextView = itemView.findViewById(R.id.tv_completed_number)
fun bind(order: OrdersItem?) {
// tvCompletedNumber.text = "Completed Order #${order?.orderId}"
}
}
// ViewHolder for 'failedPayment' status
class FailedPaymentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
//private val tvFailedPaymentNumber: TextView = itemView.findViewById(R.id.tv_failed_payment_number)
fun bind(order: OrdersItem?) {
//tvFailedPaymentNumber.text = "Failed Payment Order #${order?.orderId}"
}
}
// ViewHolder for 'failedShipment' status
class FailedShipmentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
//private val tvFailedShipmentNumber: TextView = itemView.findViewById(R.id.tv_failed_shipment_number)
fun bind(order: OrdersItem?) {
//tvFailedShipmentNumber.text = "Failed Shipment Order #${order?.orderId}"
outputFormat.format(calendar.time)
} ?: date
} catch (e: Exception) {
Log.e("DueDateFormatting", "Error formatting date: ${e.message}")
date
}.toString()
}
}
}

View File

@ -1,45 +1,57 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells
import androidx.lifecycle.ViewModelProvider
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayoutMediator
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
import com.alya.ecommerce_serang.databinding.FragmentSellsBinding
import com.alya.ecommerce_serang.utils.SessionManager
class SellsFragment : Fragment() {
private lateinit var viewModel: SellsViewModel
private var _binding: FragmentSellsBinding? = null
private val binding get() = _binding!!
private lateinit var sessionManager: SessionManager
private lateinit var viewPagerAdapter: SellsViewPagerAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_sells, container, false)
_binding = FragmentSellsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sessionManager = SessionManager(requireContext())
// val repository = OrderRepository(ApiService.create())
viewModel = ViewModelProvider(this)[SellsViewModel::class.java]
viewPagerAdapter = SellsViewPagerAdapter(requireActivity())
binding.viewPagerSells.adapter = viewPagerAdapter
val tabs = listOf(
"Semua Pesanan", "Perlu Tagihan", "Konfirmasi Pembayaran",
"Perlu Dikirim", "Dikirim", "Selesai",
"Pembatalan", "Klaim Pembayaran", "Pengiriman Gagal"
"Semua Pesanan",
"Perlu Tagihan",
"Konfirmasi Pembayaran",
"Perlu Dikirim",
"Dikirim",
"Selesai",
"Pembatalan",
"Klaim Pembayaran",
"Pengiriman Gagal"
)
val adapter = SellsPagerAdapter(this, tabs.size)
val viewPager: ViewPager2 = view.findViewById(R.id.view_pager_sells)
viewPager.adapter = adapter
TabLayoutMediator(view.findViewById(R.id.tab_layout_sells), viewPager) { tab, position ->
TabLayoutMediator(binding.tabLayoutSells, binding.viewPagerSells) { tab, position ->
tab.text = tabs[position]
}.attach()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -0,0 +1,131 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.data.api.response.store.orders.OrdersItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.SellsRepository
import com.alya.ecommerce_serang.databinding.FragmentSellsListBinding
import com.alya.ecommerce_serang.ui.order.address.ViewState
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
class SellsListFragment : Fragment() {
private var _binding: FragmentSellsListBinding? = null
private val binding get() = _binding!!
private lateinit var sessionManager: SessionManager
private val viewModel: SellsViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val sellsRepository = SellsRepository(apiService)
SellsViewModel(sellsRepository)
}
}
private lateinit var sellsAdapter: SellsAdapter
private var status: String = "all"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sessionManager = SessionManager(requireContext())
arguments?.let {
status = it.getString(ARG_STATUS) ?: "all"
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentSellsListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
observeSellsList()
loadSells()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
private const val ARG_STATUS = "status"
fun newInstance(status: String): SellsListFragment {
return SellsListFragment().apply {
arguments = Bundle().apply {
putString(ARG_STATUS, status)
}
}
}
}
private fun setupRecyclerView() {
sellsAdapter = SellsAdapter(
onOrderClickListener = { sells ->
// Handle order click
navigateToSellsDetail(sells)
},
viewModel = viewModel
)
sellsAdapter.setFragmentStatus(status)
binding.rvSells.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = sellsAdapter
}
}
private fun observeSellsList() {
viewModel.sells.observe(viewLifecycleOwner) { result ->
when (result) {
is ViewState.Success -> {
binding.progressBar.visibility = View.GONE
if (result.data.isNullOrEmpty()) {
binding.tvEmptyState.visibility = View.VISIBLE
binding.rvSells.visibility = View.GONE
} else {
binding.tvEmptyState.visibility = View.GONE
binding.rvSells.visibility = View.VISIBLE
//sellsAdapter.submitList(result.data)
}
}
is ViewState.Error -> {
binding.progressBar.visibility = View.GONE
binding.tvEmptyState.visibility = View.VISIBLE
Toast.makeText(requireContext(), result.message, Toast.LENGTH_SHORT).show()
}
is ViewState.Loading -> {
null
}
}
}
}
private fun loadSells() {
viewModel.getSellList(status)
}
private fun navigateToSellsDetail(sells: OrdersItem) {
// In a real app, you would navigate to sells detail screen
// For example: findNavController().navigate(SellsListFragmentDirections.actionToSellsDetail(sells.orderId))
Toast.makeText(requireContext(), "Order ID: ${sells.orderId}", Toast.LENGTH_SHORT).show()
}
}

View File

@ -1,34 +0,0 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.alya.ecommerce_serang.ui.profile.mystore.sells.all_sells.AllSellsFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.cancellation.CancellationFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.failed_payment.FailedPaymentFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.failed_shipment.FailedShipmentFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.completed.CompletedFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.order.OrderFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.payment.PaymentFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment.ShipmentFragment
import com.alya.ecommerce_serang.ui.profile.mystore.sells.shipped.ShippedFragment
class SellsPagerAdapter(fragment: Fragment, private val itemCount: Int) :
FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = itemCount
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> AllSellsFragment()
1 -> OrderFragment()
2 -> PaymentFragment()
3 -> ShipmentFragment()
4 -> ShippedFragment()
5 -> CompletedFragment()
6 -> CancellationFragment()
7 -> FailedPaymentFragment()
8 -> FailedShipmentFragment()
else -> Fragment()
}
}
}

View File

@ -0,0 +1,27 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
class SellsViewPagerAdapter(fragmentActivity: FragmentActivity)
: FragmentStateAdapter(fragmentActivity) {
private val sellsStatuses = listOf(
"all", // Semua Pesanan
"pending", // Perlu Tagihan
"processed", // Konfirmasi Pembayaran
"paid", // Perlu Dikirim
"shipped", // Dikirim
"delivered", // Dikirim
"completed", // Selesai
"canceled", // Dibatalkan
TODO("Klaim Pembayaran dan Pengajuan Komplain belum ada statusnya")
)
override fun getItemCount(): Int = sellsStatuses.size
override fun createFragment(position: Int): Fragment {
return SellsListFragment.newInstance(sellsStatuses[position])
}
}

View File

@ -1,41 +0,0 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.all_sells
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.databinding.FragmentAllSellsBinding
import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsAdapter
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
class AllSellsFragment : Fragment() {
private lateinit var viewModel: SellsViewModel
private lateinit var binding: FragmentAllSellsBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentAllSellsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(SellsViewModel::class.java)
val adapter = SellsAdapter()
binding.rvAllSells.layoutManager = LinearLayoutManager(context)
binding.rvAllSells.adapter = adapter
viewModel.loadOrdersByStatus("all")
viewModel.sellsList.observe(viewLifecycleOwner, Observer { sells ->
adapter.submitList(sells)
})
}
}

View File

@ -1,41 +0,0 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.cancellation
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.databinding.FragmentCancellationBinding
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
class CancellationFragment : Fragment() {
private lateinit var viewModel: SellsViewModel
private lateinit var binding: FragmentCancellationBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentCancellationBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(SellsViewModel::class.java)
// val adapter = SellsAdapter()
// binding.rvCancellation.layoutManager = LinearLayoutManager(context)
// binding.rvCancellation.adapter = adapter
//
// viewModel.loadOrdersByStatus("cancelled")
// viewModel.sellsList.observe(viewLifecycleOwner, Observer { cancellations ->
// adapter.submitList(cancellations)
// })
}
}

View File

@ -1,41 +0,0 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.completed
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.databinding.FragmentCompletedBinding
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
class CompletedFragment : Fragment() {
private lateinit var viewModel: SellsViewModel
private lateinit var binding: FragmentCompletedBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentCompletedBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(SellsViewModel::class.java)
// val adapter = SellsAdapter()
// binding.rvCompleted.layoutManager = LinearLayoutManager(context)
// binding.rvCompleted.adapter = adapter
//
// viewModel.loadOrdersByStatus("delivered")
// viewModel.sellsList.observe(viewLifecycleOwner, Observer { completed ->
// adapter.submitList(completed)
// })
}
}

View File

@ -1,42 +0,0 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.failed_payment
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.databinding.FragmentFailedPaymentBinding
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
class FailedPaymentFragment : Fragment() {
private lateinit var viewModel: SellsViewModel
private lateinit var binding: FragmentFailedPaymentBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentFailedPaymentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(SellsViewModel::class.java)
// val adapter = SellsAdapter()
// binding.rvFailedPayment.layoutManager = LinearLayoutManager(context)
// binding.rvFailedPayment.adapter = adapter
//
// viewModel.loadOrdersByStatus("failedPayment")
// viewModel.sellsList.observe(viewLifecycleOwner, Observer { failedPayments ->
// adapter.submitList(failedPayments)
// })
}
}

View File

@ -1,41 +0,0 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.failed_shipment
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.databinding.FragmentFailedShipmentBinding
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
class FailedShipmentFragment : Fragment() {
private lateinit var viewModel: SellsViewModel
private lateinit var binding: FragmentFailedShipmentBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentFailedShipmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(SellsViewModel::class.java)
// val adapter = SellsAdapter()
// binding.rvFailedShipment.layoutManager = LinearLayoutManager(context)
// binding.rvFailedShipment.adapter = adapter
//
// viewModel.loadOrdersByStatus("failedShipment")
// viewModel.sellsList.observe(viewLifecycleOwner, Observer { failedShipments ->
// adapter.submitList(failedShipments)
// })
}
}

View File

@ -0,0 +1,21 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.order
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R
class DetailOrderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_detail_order)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
}
}

View File

@ -1,60 +0,0 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.order
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
class OrderAdapter : RecyclerView.Adapter<OrderAdapter.OrderViewHolder>() {
private var orderList: List<OrdersItem?>? = emptyList()
fun submitList(orders: List<OrdersItem?>?) {
orderList = orders
notifyDataSetChanged()
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): OrderViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_sells_order, parent, false)
return OrderViewHolder(view)
}
override fun onBindViewHolder(holder: OrderViewHolder, position: Int) {
val order = orderList?.get(position)
holder.bind(order)
}
override fun getItemCount(): Int = orderList?.size ?: 0
class OrderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvOrderNumber: TextView = itemView.findViewById(R.id.tv_order_number)
private val tvOrderCustomer: TextView = itemView.findViewById(R.id.tv_order_customer)
private val tvOrderDue: TextView = itemView.findViewById(R.id.tv_order_due)
private val ivOrderProduct: ImageView = itemView.findViewById(R.id.iv_order_product)
private val tvOrderProductName: TextView = itemView.findViewById(R.id.tv_order_product_name)
private val tvOrderProductVariant: TextView = itemView.findViewById(R.id.tv_order_product_variant)
private val tvOrderProductQty: TextView = itemView.findViewById(R.id.tv_order_product_qty)
private val tvOrderProductPrice: TextView = itemView.findViewById(R.id.tv_order_product_price)
private val tvOrderQty: TextView = itemView.findViewById(R.id.tv_order_qty)
private val tvOrderPrice: TextView = itemView.findViewById(R.id.tv_order_price)
private val tvSeeMore: TextView = itemView.findViewById(R.id.tv_see_more)
private val btnEditOrder: Button = itemView.findViewById(R.id.btn_edit_order)
private val btnConfirmOrder: Button = itemView.findViewById(R.id.btn_confirm_order)
fun bind(order: OrdersItem?) {
tvOrderNumber.text = "No. Pesanan: ${order?.orderId}"
tvOrderCustomer.text = order?.userId.toString()
tvOrderDue.text = order?.createdAt + 7
tvOrderQty.text = "${order?.orderItems?.size} produk"
tvOrderPrice.text = "Rp${order?.totalAmount}"
}
}
}

View File

@ -1,40 +0,0 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.order
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.databinding.FragmentOrderBinding
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
class OrderFragment : Fragment() {
private lateinit var viewModel: SellsViewModel
private lateinit var binding: FragmentOrderBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentOrderBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(SellsViewModel::class.java)
val adapter = OrderAdapter()
binding.rvOrder.layoutManager = LinearLayoutManager(context)
binding.rvOrder.adapter = adapter
viewModel.loadOrdersByStatus("pending")
viewModel.sellsList.observe(viewLifecycleOwner, Observer { orders ->
adapter.submitList(orders)
})
}
}

View File

@ -7,11 +7,11 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R
class ClaimPaymentActivity : AppCompatActivity() {
class DetailPaymentActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_claim_payment)
setContentView(R.layout.activity_detail_payment)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)

View File

@ -1,61 +0,0 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.payment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
class PaymentAdapter : RecyclerView.Adapter<PaymentAdapter.PaymentViewHolder>() {
private var paymentList: List<OrdersItem?>? = emptyList()
fun submitList(orders: List<OrdersItem?>?) {
paymentList = orders
notifyDataSetChanged()
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): PaymentViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_sells_payment, parent, false)
return PaymentViewHolder(view)
}
override fun onBindViewHolder(holder: PaymentViewHolder, position: Int) {
val order = paymentList?.get(position)
holder.bind(order)
}
override fun getItemCount(): Int = paymentList?.size ?: 0
class PaymentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
private val tvPaymentNumber: TextView = itemView.findViewById(R.id.tv_payment_number)
private val tvPaymentDue: TextView = itemView.findViewById(R.id.tv_payment_due)
private val ivPaymentProduct: ImageView = itemView.findViewById(R.id.iv_payment_product)
private val tvPaymentProductName: TextView = itemView.findViewById(R.id.tv_payment_product_name)
private val tvPaymentProductVariant: TextView = itemView.findViewById(R.id.tv_payment_product_variant)
private val tvPaymentProductQty: TextView = itemView.findViewById(R.id.tv_payment_product_qty)
private val tvPaymentProductPrice: TextView = itemView.findViewById(R.id.tv_payment_product_price)
private val tvPaymentQty: TextView = itemView.findViewById(R.id.tv_payment_qty)
private val tvPaymentPrice: TextView = itemView.findViewById(R.id.tv_payment_price)
private val tvPaymentCustomer: TextView = itemView.findViewById(R.id.tv_payment_customer)
private val tvPaymentLocation: TextView = itemView.findViewById(R.id.tv_payment_location)
private val tvSeeMore: TextView = itemView.findViewById(R.id.tv_see_more)
private val btnConfirmPayment: Button = itemView.findViewById(R.id.btn_confirm_payment)
fun bind(order: OrdersItem?) {
tvPaymentNumber.text = "No. Pesanan: ${order?.orderId}"
tvPaymentDue.text = order?.createdAt + 7
tvPaymentQty.text = "${order?.orderItems?.size} produk"
tvPaymentPrice.text = "Rp${order?.totalAmount}"
tvPaymentCustomer.text = order?.userId.toString()
tvPaymentLocation.text = order?.addressId.toString()
}
}
}

View File

@ -1,40 +0,0 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.payment
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.databinding.FragmentPaymentBinding
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
class PaymentFragment : Fragment() {
private lateinit var viewModel: SellsViewModel
private lateinit var binding: FragmentPaymentBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentPaymentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(SellsViewModel::class.java)
val adapter = PaymentAdapter()
binding.rvPayment.layoutManager = LinearLayoutManager(context)
binding.rvPayment.adapter = adapter
viewModel.loadOrdersByStatus("paid")
viewModel.sellsList.observe(viewLifecycleOwner, Observer { payments ->
adapter.submitList(payments)
})
}
}

View File

@ -7,11 +7,11 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R
class ShippingConfirmationActivity : AppCompatActivity() {
class DetailShipmentActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_shipping_confirmation)
setContentView(R.layout.activity_detail_shipment)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)

View File

@ -1,56 +0,0 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
class ShipmentAdapter : RecyclerView.Adapter<ShipmentAdapter.ShipmentViewHolder>() {
private var shipmentList: List<OrdersItem> = emptyList()
fun submitList(orders: List<OrdersItem>) {
shipmentList = orders
notifyDataSetChanged()
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ShipmentAdapter.ShipmentViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_sells_shipment, parent, false)
return ShipmentViewHolder(view)
}
override fun onBindViewHolder(holder: ShipmentAdapter.ShipmentViewHolder, position: Int) {
val order = shipmentList[position]
holder.bind(order)
}
override fun getItemCount(): Int = shipmentList.size
class ShipmentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvShipmentNumber: TextView = itemView.findViewById(R.id.tv_shipment_number)
private val tvShipmentDue: TextView = itemView.findViewById(R.id.tv_shipment_due)
private val ivShipmentProduct: ImageView = itemView.findViewById(R.id.iv_shipment_product)
private val tvShipmentProductName: TextView = itemView.findViewById(R.id.tv_shipment_product_name)
private val tvShipmentProductVariant: TextView = itemView.findViewById(R.id.tv_shipment_product_variant)
private val tvShipmentProductQty: TextView = itemView.findViewById(R.id.tv_shipment_product_qty)
private val tvShipmentCustomer: TextView = itemView.findViewById(R.id.tv_shipment_customer)
private val tvShipmentLocation: TextView = itemView.findViewById(R.id.tv_shipment_location)
private val tvSeeMore: TextView = itemView.findViewById(R.id.tv_see_more)
private val btnConfirmPayment: Button = itemView.findViewById(R.id.btn_confirm_payment)
fun bind(order: OrdersItem) {
tvShipmentNumber.text = "No. Pesanan: ${order.orderId}"
tvShipmentDue.text = order.createdAt + 7
tvShipmentCustomer.text = order.userId.toString()
tvShipmentLocation.text = order.addressId.toString()
}
}
}

View File

@ -1,41 +0,0 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.databinding.FragmentShipmentBinding
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
class ShipmentFragment : Fragment() {
private lateinit var viewModel: SellsViewModel
private lateinit var binding: FragmentShipmentBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentShipmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(SellsViewModel::class.java)
val adapter = ShipmentAdapter()
binding.rvShipment.layoutManager = LinearLayoutManager(context)
// binding.rvShipment.adapter = adapter
//
// viewModel.loadOrdersByStatus("processed")
// viewModel.sellsList.observe(viewLifecycleOwner, Observer { shipments ->
// adapter.submitList(shipments)
// })
}
}

View File

@ -1,41 +0,0 @@
package com.alya.ecommerce_serang.ui.profile.mystore.sells.shipped
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.databinding.FragmentShippedBinding
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
class ShippedFragment : Fragment() {
private lateinit var viewModel: SellsViewModel
private lateinit var binding: FragmentShippedBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentShippedBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(SellsViewModel::class.java)
// val adapter = SellsAdapter()
// binding.rvShipped.layoutManager = LinearLayoutManager(context)
// binding.rvShipped.adapter = adapter
//
// viewModel.loadOrdersByStatus("shipped")
// viewModel.sellsList.observe(viewLifecycleOwner, Observer { shipped ->
// adapter.submitList(shipped)
// })
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,129 @@
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.api.dto.City
import com.alya.ecommerce_serang.data.api.dto.Province
import com.alya.ecommerce_serang.data.api.dto.StoreAddress
import com.alya.ecommerce_serang.data.repository.AddressRepository
import kotlinx.coroutines.launch
class AddressViewModel(private val addressRepository: AddressRepository) : ViewModel() {
private val TAG = "AddressViewModel"
private val _provinces = MutableLiveData<List<Province>>()
val provinces: LiveData<List<Province>> = _provinces
private val _cities = MutableLiveData<List<City>>()
val cities: LiveData<List<City>> = _cities
private val _storeAddress = MutableLiveData<StoreAddress?>()
val storeAddress: LiveData<StoreAddress?> = _storeAddress
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 fetchProvinces() {
Log.d(TAG, "fetchProvinces() called")
_isLoading.value = true
viewModelScope.launch {
try {
Log.d(TAG, "Calling addressRepository.getProvinces()")
val response = addressRepository.getProvinces()
Log.d(TAG, "Received provinces response: ${response.size} provinces")
_provinces.value = response
_isLoading.value = false
} catch (e: Exception) {
Log.e(TAG, "Error fetching provinces", e)
_errorMessage.value = "Failed to load provinces: ${e.message}"
_isLoading.value = false
}
}
}
fun fetchCities(provinceId: String) {
Log.d(TAG, "fetchCities() called with provinceId: $provinceId")
_isLoading.value = true
viewModelScope.launch {
try {
Log.d(TAG, "Calling addressRepository.getCities()")
val response = addressRepository.getCities(provinceId)
Log.d(TAG, "Received cities response: ${response.size} cities")
_cities.value = response
_isLoading.value = false
} catch (e: Exception) {
Log.e(TAG, "Error fetching cities", e)
_errorMessage.value = "Failed to load cities: ${e.message}"
_isLoading.value = false
}
}
}
fun fetchStoreAddress() {
Log.d(TAG, "fetchStoreAddress() called")
_isLoading.value = true
viewModelScope.launch {
try {
Log.d(TAG, "Calling addressRepository.getStoreAddress()")
val response = addressRepository.getStoreAddress()
Log.d(TAG, "Received store address response: $response")
_storeAddress.value = response
_isLoading.value = false
} catch (e: Exception) {
Log.e(TAG, "Error fetching store address", e)
_errorMessage.value = "Failed to load store address: ${e.message}"
_isLoading.value = false
}
}
}
fun saveStoreAddress(
provinceId: String,
provinceName: String,
cityId: String,
cityName: String,
street: String,
subdistrict: String,
detail: String,
postalCode: String,
latitude: Double,
longitude: Double
) {
Log.d(TAG, "saveStoreAddress() called with provinceId: $provinceId, cityId: $cityId")
_isLoading.value = true
viewModelScope.launch {
try {
Log.d(TAG, "Calling addressRepository.saveStoreAddress()")
val success = addressRepository.saveStoreAddress(
provinceId = provinceId,
provinceName = provinceName,
cityId = cityId,
cityName = cityName,
street = street,
subdistrict = subdistrict,
detail = detail,
postalCode = postalCode,
latitude = latitude,
longitude = longitude
)
Log.d(TAG, "Save store address result: $success")
_saveSuccess.value = success
_isLoading.value = false
} catch (e: Exception) {
Log.e(TAG, "Error saving store address", e)
_errorMessage.value = "Failed to save address: ${e.message}"
_isLoading.value = false
}
}
}
}

View File

@ -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
}
}
}
}

View File

@ -1,26 +1,61 @@
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.liveData
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.data.api.response.store.orders.OrdersItem
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.SellsRepository
import com.alya.ecommerce_serang.ui.order.address.ViewState
import kotlinx.coroutines.launch
class SellsViewModel(private val repository: OrderRepository) : ViewModel() {
private val _sellsList = MutableLiveData<List<OrdersItem?>>()
val sellsList: LiveData<List<OrdersItem?>> get() = _sellsList
class SellsViewModel(private val repository: SellsRepository) : ViewModel() {
companion object {
private const val TAG = "SellsViewModel"
}
private val _sells = MutableLiveData<ViewState<List<OrdersItem?>?>>()
val sells: LiveData<ViewState<List<OrdersItem?>?>> = _sells
fun getSellList(status: String) {
_sells.value = ViewState.Loading
fun loadOrdersByStatus(status: String) {
viewModelScope.launch {
val result = if (status == "all") {
repository.fetchSells()
} else {
repository.fetchOrdersByStatus(status)
_sells.value = ViewState.Loading
try {
when (val result = repository.getSellList(status)) {
is Result.Success -> {
_sells.value = ViewState.Success(result.data.orders)
Log.d("SellsViewModel", "Sells loaded successfully: ${result.data.orders?.size} items")
}
is Result.Error -> {
_sells.value = ViewState.Error(result.exception.message ?: "Unknown error occurred")
Log.e("SellsViewModel", "Error loading sells", result.exception)
}
is Result.Loading -> {
null
}
}
} catch (e: Exception) {
_sells.value = ViewState.Error("An unexpected error occurred: ${e.message}")
Log.e("SellsViewModel", "Exception in getOrderList", e)
}
}
}
fun updateOrderStatus(orderId: Int?, status: String) {
Log.d(TAG, "Updating order status: orderId=$orderId, status=$status")
viewModelScope.launch {
try {
repository.updateOrderStatus(orderId, status)
Log.d(TAG, "Order status updated successfully: orderId=$orderId, status=$status")
} catch (e: Exception) {
Log.e(TAG, "Error updating order status", e)
}
_sellsList.value = result
}
}
}

View File

@ -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
}
}
}
}

View File

@ -82,44 +82,61 @@
android:text="Riwayat Saldo"
android:layout_marginTop="10dp"/>
<!-- Date Picker dengan Icon -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:layout_marginTop="10dp"
android:orientation="horizontal"
android:gravity="center_vertical">
android:layout_marginTop="10dp">
<!-- Icon Kalender -->
<ImageView
android:id="@+id/iconDatePicker"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="8dp"
android:src="@drawable/ic_calendar"
android:contentDescription="Pilih Tanggal" />
<EditText
android:id="@+id/edt_tgl_transaksi"
<!-- Date Picker dengan Icon -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Pilih tanggal di sini"
android:padding="8dp"
style="@style/body_small"
android:background="@null"
android:focusable="false"
android:clickable="true" />
android:background="@drawable/bg_text_field"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:id="@+id/img_date_picker"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_navigate_next"
android:contentDescription="Pilih Tanggal"
app:tint="@color/black_300" />
<!-- Icon Kalender -->
<ImageView
android:id="@+id/iconDatePicker"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="8dp"
android:src="@drawable/ic_calendar"
android:contentDescription="Pilih Tanggal" />
<EditText
android:id="@+id/edt_tgl_transaksi"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Pilih tanggal di sini"
android:padding="8dp"
style="@style/body_small"
android:background="@null"
android:focusable="false"
android:clickable="true" />
<ImageView
android:id="@+id/img_date_picker"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_navigate_next"
android:contentDescription="Pilih Tanggal"
app:tint="@color/black_300" />
</LinearLayout>
<!-- Clear Filter Button -->
<Button
android:id="@+id/btn_clear_filter"
android:layout_width="wrap_content"
android:text="Clear"
android:layout_marginStart="8dp"
style="@style/button.small.secondary.short"
android:visibility="gone"/>
</LinearLayout>
@ -138,8 +155,25 @@
android:scrollbars="vertical"
tools:listitem="@layout/item_balance_transaction" />
<TextView
android:id="@+id/tv_empty_state"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Tidak ada riwayat transaksi"
android:gravity="center"
android:padding="24dp"
style="@style/body_medium"
android:visibility="gone" />
</LinearLayout>
</ScrollView>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</LinearLayout>

View File

@ -203,48 +203,6 @@
</LinearLayout>
<!-- Nomor Rekening -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nomor Rekening"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="*"
style="@style/body_medium"
android:textColor="@color/red_required"
android:layout_gravity="end"/>
</LinearLayout>
<EditText
android:id="@+id/edt_no_rekening"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:hint="Isi nomor rekening Anda di sini"
android:padding="8dp"
style="@style/body_small"
android:layout_marginTop="10dp"/>
</LinearLayout>
<!-- Tanggal Transaksi -->
<LinearLayout
android:layout_width="match_parent"
@ -322,7 +280,8 @@
<Button
android:id="@+id/btn_send"
android:text="Kirim"
style="@style/button.large.disabled.long"/>
style="@style/button.large.disabled.long"
android:layout_marginBottom="16dp"/>
</LinearLayout>

View File

@ -284,7 +284,7 @@
android:layout_marginBottom="8dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_payment_methods"
android:id="@+id/rv_payment_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/item_payment_method" />

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.profile.mystore.sells.order.DetailOrderActivity">
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.profile.mystore.sells.payment.DetailPaymentActivity">
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.profile.mystore.sells.shipment.DetailShipmentActivity">
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -26,34 +26,33 @@
android:paddingHorizontal="@dimen/horizontal_safe_area"
android:layout_marginTop="19dp">
<!-- Nama Lokasi -->
<!-- Error display -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
android:orientation="vertical">
<TextView
android:id="@+id/tv_error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Nama Lokasi"
android:textColor="@color/red_required"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
<EditText
android:id="@+id/edt_nama_lokasi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field_disabled"
android:text="Alamat Toko"
android:padding="8dp"
style="@style/body_small"
android:layout_marginTop="10dp"
android:enabled="false"/>
android:padding="12dp"
android:background="#FFEEEE"
android:visibility="gone"
android:text="Error message goes here" />
<Button
android:id="@+id/btn_retry"
style="@style/button.small.active.short"
android:text="Retry"
android:layout_gravity="center"
android:layout_marginTop="8dp"
android:visibility="gone"/>
</LinearLayout>
<!-- Jalan -->
<!-- Provinsi -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -63,43 +62,44 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Jalan"
android:text="Provinsi"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
<EditText
android:id="@+id/edt_street"
<!-- Spinner Dropdown dengan Chevron -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/bg_text_field"
android:padding="8dp"
style="@style/body_small"
android:layout_marginTop="10dp"/>
android:gravity="center_vertical"
android:layout_marginTop="10dp">
</LinearLayout>
<Spinner
android:id="@+id/spinner_province"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="8dp"
style="@style/body_small"
android:background="@null"/>
<!-- Kecamatan -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
<ProgressBar
android:id="@+id/province_progress_bar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:visibility="gone"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Kecamatan"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
<!-- Chevron Down Icon -->
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_down"
android:layout_marginEnd="8dp"
android:contentDescription="Chevron Down" />
<EditText
android:id="@+id/edt_subdistrict"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:padding="8dp"
style="@style/body_small"
android:layout_marginTop="10dp"/>
</LinearLayout>
</LinearLayout>
@ -135,6 +135,13 @@
style="@style/body_small"
android:background="@null"/>
<ProgressBar
android:id="@+id/city_progress_bar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:visibility="gone"/>
<!-- Chevron Down Icon -->
<ImageView
android:layout_width="16dp"
@ -147,7 +154,7 @@
</LinearLayout>
<!-- Provinsi -->
<!-- Jalan -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -157,37 +164,45 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Provinsi"
android:text="Jalan"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
<!-- Spinner Dropdown dengan Chevron -->
<LinearLayout
<EditText
android:id="@+id/edt_street"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/bg_text_field"
android:gravity="center_vertical"
android:layout_marginTop="10dp">
android:padding="8dp"
style="@style/body_small"
android:hint="Isi nama jalan di sini"
android:layout_marginTop="10dp"/>
<Spinner
android:id="@+id/spinner_province"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="8dp"
style="@style/body_small"
android:background="@null"/>
</LinearLayout>
<!-- Chevron Down Icon -->
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_down"
android:layout_marginEnd="8dp"
android:contentDescription="Chevron Down" />
<!-- Kecamatan -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Kecamatan"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
<EditText
android:id="@+id/edt_subdistrict"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:padding="8dp"
style="@style/body_small"
android:hint="Isi nama kecamatan di sini"
android:layout_marginTop="10dp"/>
</LinearLayout>
@ -212,6 +227,7 @@
android:background="@drawable/bg_text_field"
android:padding="8dp"
style="@style/body_small"
android:hint="Isi kode pos di sini"
android:layout_marginTop="10dp"/>
</LinearLayout>
@ -245,38 +261,106 @@
</LinearLayout>
<!-- Pinpoint Lokasi -->
<!-- <LinearLayout-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:orientation="vertical"-->
<!-- android:layout_marginBottom="24dp">-->
<!-- <TextView-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="Pinpoint Lokasi"-->
<!-- style="@style/body_medium"-->
<!-- android:layout_marginEnd="4dp"/>-->
<!-- &lt;!&ndash; Map &ndash;&gt;-->
<!-- <org.osmdroid.views.MapView android:id="@+id/map"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="220dp" />-->
<!-- <TextView-->
<!-- android:id="@+id/tv_location_display"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="Lokasi: Tidak dipilih"-->
<!-- style="@style/body_medium"/>-->
<!-- </LinearLayout>-->
<!-- Coordinates -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
android:orientation="horizontal"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="match_parent"
<!-- Latitude -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Pinpoint Lokasi"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginEnd="8dp">
<!-- Map -->
<org.osmdroid.views.MapView android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="220dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Latitude"
style="@style/body_medium"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/tv_location_display"
android:layout_width="match_parent"
<EditText
android:id="@+id/edt_latitude"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:padding="12dp"
android:hint="Latitude"
android:inputType="numberDecimal|numberSigned"
style="@style/body_small"/>
</LinearLayout>
<!-- Longitude -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Lokasi: Tidak dipilih"
style="@style/body_medium"/>
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Longitude"
style="@style/body_medium"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/edt_longitude"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:padding="12dp"
android:hint="Longitude"
android:inputType="numberDecimal|numberSigned"
style="@style/body_small"/>
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/btn_save_address"
android:text="Simpan Perubahan"
style="@style/button.large.disabled.long"
android:enabled="false"/>
android:enabled="false"
android:layout_marginBottom="16dp"/>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"/>
</LinearLayout>

View File

@ -847,7 +847,8 @@
android:id="@+id/btn_save_product"
android:text="Simpan Produk"
style="@style/button.large.disabled.long"
android:enabled="false"/>
android:enabled="false"
android:layout_marginBottom="16dp"/>
</LinearLayout>

View File

@ -8,7 +8,9 @@
android:orientation="vertical"
tools:context=".ui.profile.mystore.profile.DetailStoreProfileActivity">
<include layout="@layout/header" />
<include
android:id="@+id/header"
layout="@layout/header" />
<ScrollView
android:layout_width="match_parent"

View File

@ -0,0 +1,183 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.profile.mystore.profile.EditStoreProfileActivity">
<include
android:id="@+id/header"
layout="@layout/header" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingVertical="@dimen/vertical_safe_area"
android:paddingHorizontal="@dimen/horizontal_safe_area">
<!-- Store Image -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Foto Toko"
style="@style/body_medium"
android:layout_marginBottom="8dp"/>
<ImageView
android:id="@+id/iv_store_image"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/placeholder_image"
android:scaleType="centerCrop"
android:layout_gravity="center_horizontal"
android:background="@drawable/bg_text_field"/>
<ProgressBar
android:id="@+id/progress_image"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:visibility="gone"
android:layout_marginTop="-80dp"
android:layout_marginBottom="40dp"/>
<Button
android:id="@+id/btn_select_store_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pilih Foto"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"/>
</LinearLayout>
<!-- Store Name -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nama Toko *"
style="@style/body_medium"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/edt_store_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:padding="12dp"
android:hint="Masukkan nama toko"
android:inputType="text"
style="@style/body_small"/>
</LinearLayout>
<!-- Store Description -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Deskripsi Toko"
style="@style/body_medium"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/edt_description"
android:layout_width="match_parent"
android:layout_height="120dp"
android:background="@drawable/bg_text_field"
android:padding="12dp"
android:hint="Masukkan deskripsi toko"
android:inputType="textMultiLine"
android:gravity="top"
style="@style/body_small"/>
</LinearLayout>
<!-- Is On Leave -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Toko Sedang Cuti"
style="@style/body_medium"/>
<Switch
android:id="@+id/switch_is_on_leave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- User Phone -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nomor Telepon *"
style="@style/body_medium"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/edt_user_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:padding="12dp"
android:hint="Masukkan nomor telepon"
android:inputType="phone"
style="@style/body_small"/>
</LinearLayout>
<!-- Save Button -->
<Button
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Simpan"
android:layout_marginTop="24dp"
android:layout_marginBottom="16dp"/>
<!-- Progress Bar -->
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"/>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@ -2,9 +2,82 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
tools:context=".ui.profile.mystore.profile.payment_info.PaymentInfoActivity">
<include
android:id="@+id/header"
layout="@layout/header"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/tv_empty_state"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/header">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="16dp"
android:src="@drawable/placeholder_image"
android:alpha="0.5" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Belum ada metode pembayaran"
android:textAlignment="center"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Tambahkan metode pembayaran untuk memudahkan pembeli melakukan transaksi"
android:textAlignment="center"
android:textSize="14sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_payment_info"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingHorizontal="@dimen/horizontal_safe_area"
android:paddingVertical="8dp"
android:clipToPadding="false"
app:layout_constraintTop_toBottomOf="@id/header"
app:layout_constraintBottom_toTopOf="@id/btn_add_payment"
tools:listitem="@layout/item_payment_info"
tools:itemCount="2" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/header" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_add_payment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="Tambahkan Metode Pembayaran"
android:paddingVertical="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,14 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.profile.mystore.sells.SellsActivity">
<FrameLayout
android:id="@+id/sells_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include
android:id="@+id/header"
layout="@layout/header" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<!-- Search Bar -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="@color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:background="@drawable/bg_text_field"
android:paddingHorizontal="6dp"
android:paddingVertical="10dp">
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@drawable/ic_search"
android:contentDescription="Search Icon" />
<EditText
android:id="@+id/edt_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:hint="Cari No. Pesanan, Nama Pemesan, Nama Produk, atau Lokasi di sini..."
android:inputType="text"
style="@style/body_small"
android:layout_marginStart="6dp"/>
</LinearLayout>
</LinearLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container_sells"
android:layout_width="match_parent"
android:layout_height="0dp" />
</LinearLayout>

View File

@ -2,10 +2,130 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:fitsSystemWindows="true"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
tools:context=".ui.profile.mystore.profile.shipping_service.ShippingServiceActivity">
<include
android:id="@+id/header"
layout="@layout/header"
app:layout_constraintTop_toTopOf="parent" />
<ScrollView
android:id="@+id/content_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/header"
app:layout_constraintBottom_toTopOf="@id/btn_save">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pilih Layanan Pengiriman"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="16dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Layanan pengiriman yang dipilih akan tersedia untuk pembeli saat checkout"
android:textSize="14sp"
android:layout_marginBottom="24dp" />
<CheckBox
android:id="@+id/checkbox_jne"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="JNE"
android:textSize="16sp"
android:paddingStart="8dp" />
<CheckBox
android:id="@+id/checkbox_pos"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="POS Indonesia"
android:textSize="16sp"
android:paddingStart="8dp" />
<CheckBox
android:id="@+id/checkbox_tiki"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TIKI"
android:textSize="16sp"
android:paddingStart="8dp" />
<CheckBox
android:id="@+id/checkbox_sicepat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SiCepat"
android:textSize="16sp"
android:paddingStart="8dp" />
<CheckBox
android:id="@+id/checkbox_jnt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="J&amp;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>

View 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>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rv_all_sells"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rv_cancellation"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rv_completed"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rv_failed_payment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rv_failed_shipment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rv_order"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_sells_order" />

View File

@ -11,10 +11,12 @@
android:background="@android:color/white"
app:layout_constraintTop_toTopOf="parent"
app:tabGravity="fill"
app:tabIndicatorColor="@color/blue_200"
app:tabMode="scrollable"
app:tabSelectedTextColor="@color/blue_200"
app:tabTextColor="@android:color/darker_gray" />
app:tabTextAppearance="@style/label_medium_prominent"
app:tabSelectedTextAppearance="@style/label_medium_prominent"
app:tabIndicatorColor="@color/blue_500"
app:tabSelectedTextColor="@color/blue_500"
app:tabTextColor="@color/black_300" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rv_payment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_sells_payment"/>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.profile.mystore.sells.SellsListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_sells"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_sells_order" />
<TextView
android:id="@+id/tv_empty_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TIdak ada penjualan"
android:textSize="16sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/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_toTopOf="parent" />
</FrameLayout>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rv_shipment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_sells_shipment" />

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rv_shipped"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View 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>

View File

@ -16,7 +16,7 @@
app:layout_constraintTop_toTopOf="parent"/>
<LinearLayout
android:id="@+id/layout_order_header"
android:id="@+id/layout_sells_header"
android:layout_width="220dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
@ -25,7 +25,7 @@
android:orientation="vertical">
<TextView
android:id="@+id/tv_order_title"
android:id="@+id/tv_sells_title"
style="@style/label_medium_prominent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -33,7 +33,7 @@
android:text="Pesanan Perlu Dibuat Tagihan"/>
<TextView
android:id="@+id/tv_order_number"
android:id="@+id/tv_sells_number"
style="@style/label_small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -52,7 +52,7 @@
android:src="@drawable/ic_person"/>
<TextView
android:id="@+id/tv_order_customer"
android:id="@+id/tv_sells_customer"
style="@style/label_small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -84,7 +84,7 @@
android:textColor="@color/black_300" />
<TextView
android:id="@+id/tv_order_due"
android:id="@+id/tv_sells_due"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="25 Okt; 23.59"
@ -97,11 +97,11 @@
<!-- Order Detail -->
<LinearLayout
android:id="@+id/layout_order_detail"
android:id="@+id/layout_sells_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_order_header"
app:layout_constraintTop_toBottomOf="@id/layout_sells_header"
android:paddingHorizontal="16dp"
android:layout_marginTop="8dp"
android:orientation="vertical">
@ -113,13 +113,13 @@
<!-- Product Detail -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_order_product_detail"
android:id="@+id/layout_sells_product_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="12dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_order_product"
android:id="@+id/iv_sells_product"
android:layout_width="95dp"
android:layout_height="64dp"
android:src="@drawable/placeholder_image"
@ -134,26 +134,26 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginStart="13dp"
app:layout_constraintStart_toEndOf="@id/iv_order_product"
app:layout_constraintStart_toEndOf="@id/iv_sells_product"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_order_product_name"
android:id="@+id/tv_sells_product_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="Jaket Pink Fuschia"
style="@style/label_medium_prominent"/>
<TextView
android:id="@+id/tv_order_product_variant"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="S"
style="@style/label_medium"
android:textColor="@color/black_300"/>
<!-- <TextView-->
<!-- android:id="@+id/tv_sells_product_variant"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:maxLines="1"-->
<!-- android:text="S"-->
<!-- style="@style/label_medium"-->
<!-- android:textColor="@color/black_300"/>-->
</LinearLayout>
@ -163,12 +163,12 @@
android:orientation="vertical"
android:layout_marginStart="13dp"
android:gravity="end"
app:layout_constraintStart_toEndOf="@id/iv_order_product"
app:layout_constraintStart_toEndOf="@id/iv_sells_product"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/iv_order_product">
app:layout_constraintBottom_toBottomOf="@id/iv_sells_product">
<TextView
android:id="@+id/tv_order_product_qty"
android:id="@+id/tv_sells_product_qty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
@ -178,7 +178,7 @@
android:textAlignment="textEnd"/>
<TextView
android:id="@+id/tv_order_product_price"
android:id="@+id/tv_sells_product_price"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
@ -198,7 +198,7 @@
style="@style/label_small"
android:fontFamily="@font/dmsans_italic"
android:textColor="@color/black_300"
app:layout_constraintTop_toBottomOf="@id/iv_order_product"
app:layout_constraintTop_toBottomOf="@id/iv_sells_product"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginVertical="8dp"
@ -219,7 +219,7 @@
android:paddingVertical="8dp">
<TextView
android:id="@+id/tv_order_qty"
android:id="@+id/tv_sells_qty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2 produk"
@ -240,7 +240,7 @@
android:textAlignment="textEnd"/>
<TextView
android:id="@+id/tv_order_price"
android:id="@+id/tv_sells_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp300.000"
@ -287,6 +287,6 @@
android:background="@color/black_50"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_order_detail"/>
app:layout_constraintTop_toBottomOf="@id/layout_sells_detail"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -16,7 +16,7 @@
app:layout_constraintTop_toTopOf="parent"/>
<LinearLayout
android:id="@+id/layout_payment_header"
android:id="@+id/layout_sells_header"
android:layout_width="220dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
@ -25,7 +25,7 @@
android:orientation="vertical">
<TextView
android:id="@+id/tv_payment_title"
android:id="@+id/tv_sells_title"
style="@style/label_medium_prominent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -33,7 +33,7 @@
android:text="Pesanan Telah Dibayar"/>
<TextView
android:id="@+id/tv_payment_number"
android:id="@+id/tv_sells_number"
style="@style/label_small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -53,6 +53,7 @@
android:gravity="end">
<TextView
android:id="@+id/tv_sells_due_desc"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:maxLines="1"
@ -62,7 +63,7 @@
android:textColor="@color/black_300" />
<TextView
android:id="@+id/tv_payment_due"
android:id="@+id/tv_sells_due"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="25 Okt; 23.59"
@ -75,11 +76,11 @@
<!-- Order Detail -->
<LinearLayout
android:id="@+id/layout_payment_detail"
android:id="@+id/layout_sells_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_payment_header"
app:layout_constraintTop_toBottomOf="@id/layout_sells_header"
android:paddingHorizontal="16dp"
android:layout_marginTop="8dp"
android:orientation="vertical">
@ -91,13 +92,13 @@
<!-- Product Detail -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_payment_product_detail"
android:id="@+id/layout_sells_product_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="12dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_payment_product"
android:id="@+id/iv_sells_product"
android:layout_width="95dp"
android:layout_height="64dp"
android:src="@drawable/placeholder_image"
@ -112,26 +113,26 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginStart="13dp"
app:layout_constraintStart_toEndOf="@id/iv_payment_product"
app:layout_constraintStart_toEndOf="@id/iv_sells_product"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_payment_product_name"
android:id="@+id/tv_sells_product_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="Jaket Pink Fuschia"
style="@style/label_medium_prominent"/>
<TextView
android:id="@+id/tv_payment_product_variant"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="S"
style="@style/label_medium"
android:textColor="@color/black_300"/>
<!-- <TextView-->
<!-- android:id="@+id/tv_sells_product_variant"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:maxLines="1"-->
<!-- android:text="S"-->
<!-- style="@style/label_medium"-->
<!-- android:textColor="@color/black_300"/>-->
</LinearLayout>
@ -141,12 +142,12 @@
android:orientation="vertical"
android:layout_marginStart="13dp"
android:gravity="end"
app:layout_constraintStart_toEndOf="@id/iv_payment_product"
app:layout_constraintStart_toEndOf="@id/iv_sells_product"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/iv_payment_product">
app:layout_constraintBottom_toBottomOf="@id/iv_sells_product">
<TextView
android:id="@+id/tv_payment_product_qty"
android:id="@+id/tv_sells_product_qty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
@ -156,7 +157,7 @@
android:textAlignment="textEnd"/>
<TextView
android:id="@+id/tv_payment_product_price"
android:id="@+id/tv_sells_product_price"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
@ -176,7 +177,7 @@
style="@style/label_small"
android:fontFamily="@font/dmsans_italic"
android:textColor="@color/black_300"
app:layout_constraintTop_toBottomOf="@id/iv_payment_product"
app:layout_constraintTop_toBottomOf="@id/iv_sells_product"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginVertical="8dp"
@ -197,7 +198,7 @@
android:paddingVertical="8dp">
<TextView
android:id="@+id/tv_payment_qty"
android:id="@+id/tv_sells_qty"
style="@style/label_large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -218,7 +219,7 @@
android:textAlignment="textEnd"/>
<TextView
android:id="@+id/tv_payment_price"
android:id="@+id/tv_sells_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp300.000"
@ -262,7 +263,7 @@
app:tint="@color/black_500" />
<TextView
android:id="@+id/tv_payment_customer"
android:id="@+id/tv_sells_customer"
style="@style/label_small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -284,7 +285,7 @@
android:src="@drawable/ic_location" />
<TextView
android:id="@+id/tv_payment_location"
android:id="@+id/tv_sells_location"
style="@style/label_small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -313,6 +314,6 @@
android:background="@color/black_50"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_payment_detail"/>
app:layout_constraintTop_toBottomOf="@id/layout_sells_detail"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -16,7 +16,7 @@
app:layout_constraintTop_toTopOf="parent"/>
<LinearLayout
android:id="@+id/layout_shipment_header"
android:id="@+id/layout_sells_header"
android:layout_width="220dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
@ -25,7 +25,7 @@
android:orientation="vertical">
<TextView
android:id="@+id/tv_shipment_title"
android:id="@+id/tv_sells_title"
style="@style/label_medium_prominent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -33,7 +33,7 @@
android:text="Pesanan Perlu Dikirim"/>
<TextView
android:id="@+id/tv_shipment_number"
android:id="@+id/tv_sells_number"
style="@style/label_small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -62,7 +62,7 @@
android:textColor="@color/black_300" />
<TextView
android:id="@+id/tv_shipment_due"
android:id="@+id/tv_sells_due"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="25 Okt; 23.59"
@ -75,11 +75,11 @@
<!-- Order Detail -->
<LinearLayout
android:id="@+id/layout_shipment_detail"
android:id="@+id/layout_sells_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_shipment_header"
app:layout_constraintTop_toBottomOf="@id/layout_sells_header"
android:paddingHorizontal="16dp"
android:layout_marginTop="8dp"
android:orientation="vertical">
@ -91,13 +91,13 @@
<!-- Product Detail -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_shipment_product_detail"
android:id="@+id/layout_sells_product_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="12dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_shipment_product"
android:id="@+id/iv_sells_product"
android:layout_width="95dp"
android:layout_height="48dp"
android:src="@drawable/placeholder_image"
@ -112,31 +112,31 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginStart="13dp"
app:layout_constraintStart_toEndOf="@id/iv_shipment_product"
app:layout_constraintStart_toEndOf="@id/iv_sells_product"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_shipment_product_name"
android:id="@+id/tv_sells_product_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="Jaket Pink Fuschia"
style="@style/label_medium_prominent"/>
<TextView
android:id="@+id/tv_shipment_product_variant"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="S"
style="@style/label_medium"
android:textColor="@color/black_300"/>
<!-- <TextView-->
<!-- android:id="@+id/tv_sells_product_variant"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:maxLines="1"-->
<!-- android:text="S"-->
<!-- style="@style/label_medium"-->
<!-- android:textColor="@color/black_300"/>-->
</LinearLayout>
<TextView
android:id="@+id/tv_shipment_product_qty"
android:id="@+id/tv_sells_product_qty"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxLines="1"
@ -145,9 +145,9 @@
android:layout_marginStart="13dp"
android:gravity="end"
android:textAlignment="textEnd"
app:layout_constraintStart_toEndOf="@id/iv_shipment_product"
app:layout_constraintStart_toEndOf="@id/iv_sells_product"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/iv_shipment_product"/>
app:layout_constraintBottom_toBottomOf="@id/iv_sells_product"/>
<TextView
android:id="@+id/tv_see_more"
@ -159,7 +159,7 @@
style="@style/label_small"
android:fontFamily="@font/dmsans_italic"
android:textColor="@color/black_300"
app:layout_constraintTop_toBottomOf="@id/iv_shipment_product"
app:layout_constraintTop_toBottomOf="@id/iv_sells_product"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginVertical="8dp"
@ -199,7 +199,7 @@
app:tint="@color/black_500" />
<TextView
android:id="@+id/tv_shipment_customer"
android:id="@+id/tv_sells_customer"
style="@style/label_small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -221,7 +221,7 @@
android:src="@drawable/ic_location" />
<TextView
android:id="@+id/tv_shipment_location"
android:id="@+id/tv_sells_location"
style="@style/label_small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -250,6 +250,6 @@
android:background="@color/black_50"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_shipment_detail"/>
app:layout_constraintTop_toBottomOf="@id/layout_sells_detail"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -10,7 +10,7 @@
<string name="item_price_txt">Rp%.1f</string>
<string name="retry">Coba lagi\n</string>
<string name="error_loading">Terdapat error...</string>
<string name="error_loading">Terdapat error</string>
<string name="new_products_text">Produk Terbaru</string>
<string name="rating">4.5</string>
<string name="open_store">Buka Toko</string>

View File

@ -1,5 +1,5 @@
[versions]
agp = "8.5.2"
agp = "8.10.0"
glide = "4.16.0"
hiltAndroid = "2.48" # Updated from 2.44 for better compatibility
hiltLifecycleViewmodel = "1.0.0-alpha03"