Compare commits

30 Commits

Author SHA1 Message Date
29fb55e3c0 fix restore 2025-08-22 14:54:28 +07:00
2f16542e5e fix UI evidence 2025-08-22 14:38:02 +07:00
2f28a23114 Merge remote-tracking branch 'origin/master' 2025-08-22 13:47:20 +07:00
94c081e839 fix UI 2025-08-22 13:46:42 +07:00
d32bdf65fe update checkout 2025-08-22 13:08:06 +07:00
9162b2cc60 update detail store 2025-08-22 12:11:54 +07:00
8eac90311e fix search bar 2025-08-22 11:31:52 +07:00
9cd0675d82 fix progress bar loading 2025-08-22 11:21:07 +07:00
f88a5a46ad Merge remote-tracking branch 'origin/master' 2025-08-22 04:14:04 +07:00
421c20cc4b approval status 2025-08-22 04:13:37 +07:00
792e247eaa fix fcm and update toast 2025-08-22 01:08:25 +07:00
b6b701fa3b fix add and edit address 2025-08-20 00:52:56 +07:00
94bd32d6b0 Merge remote-tracking branch 'origin/master' 2025-08-19 07:32:25 +07:00
6baf4ee5ce fix add address 2025-08-19 07:27:48 +07:00
ff8654d12a fix wholesale price and edit profile 2025-08-18 16:01:07 +07:00
22122d631b Add files via upload 2025-08-15 07:16:45 +07:00
6efbcde784 fix 2025-08-15 05:29:54 +07:00
a6d5d10e78 fix address, edit address 2025-08-15 02:11:16 +07:00
f373035f8e fix address store, address user, add 2025-08-14 16:52:22 +07:00
8d9815d89f fix address store, address user, add 2025-08-14 16:47:11 +07:00
1f41b4681f fix address store, add checkbox, forget password 2025-08-14 13:21:13 +07:00
3d8e82e3b5 Merge branch 'screen-features' 2025-08-12 16:32:53 +07:00
d4eacf6c7c fix detail order, kecamata, bank name, checkbox tnc 2025-08-12 16:28:18 +07:00
7351f9c5b7 Merge remote-tracking branch 'origin/master' 2025-08-12 15:02:06 +07:00
9fefe1d818 update apps name 2025-08-12 15:01:50 +07:00
0a8dac4d23 update kecamatan dan bank name 2025-08-12 15:00:37 +07:00
77ea5ed90a top-up 2025-08-12 13:51:19 +07:00
c303b419ed Merge pull request #36
gracia
2025-08-12 02:24:22 +07:00
ed60528049 add account name 2025-08-12 00:44:35 +07:00
a9c2f9c103 fix validation file upliad 2025-08-11 22:47:00 +07:00
107 changed files with 4783 additions and 1164 deletions

View File

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

View File

@ -124,4 +124,7 @@ dependencies {
implementation(platform("com.google.firebase:firebase-bom:33.13.0")) implementation(platform("com.google.firebase:firebase-bom:33.13.0"))
implementation("com.google.firebase:firebase-analytics") implementation("com.google.firebase:firebase-analytics")
implementation("com.google.firebase:firebase-messaging-ktx") implementation("com.google.firebase:firebase-messaging-ktx")
//Splash screen
implementation("androidx.core:core-splashscreen:1.0.0")
} }

View File

@ -29,6 +29,12 @@
android:theme="@style/Theme.Ecommerce_serang" android:theme="@style/Theme.Ecommerce_serang"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:targetApi="31"> tools:targetApi="31">
<activity
android:name=".ui.profile.ChangePasswordActivity"
android:exported="false" />
<activity
android:name=".ui.auth.ResetPassActivity"
android:exported="false" />
<activity <activity
android:name=".ui.profile.mystore.StoreSuspendedActivity" android:name=".ui.profile.mystore.StoreSuspendedActivity"
android:exported="false" /> android:exported="false" />

View File

@ -2,30 +2,20 @@ package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class City( data class CityResponse(
@SerializedName("city_id")
val cityId: String,
@SerializedName("city_name") @field:SerializedName("cities")
val cities: List<City>,
@field:SerializedName("message")
val message: String
)
data class City(
@field:SerializedName("city_name")
val cityName: String, val cityName: String,
@SerializedName("province_id") @field:SerializedName("city_id")
val provinceId: String, val cityId: 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

@ -90,7 +90,7 @@ data class OrdersItem(
val orderId: Int, val orderId: Int,
@field:SerializedName("city_id") @field:SerializedName("city_id")
val cityId: Int, val cityId: String,
var displayStatus: String? = null var displayStatus: String? = null
) )

View File

@ -0,0 +1,8 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class ResetPassReq (
@SerializedName("emailOrPhone")
val emailOrPhone: String
)

View File

@ -10,9 +10,6 @@ data class Store(
@field:SerializedName("store_status") @field:SerializedName("store_status")
val storeStatus: String, val storeStatus: String,
@field:SerializedName("sppirt")
val sppirt: String,
@field:SerializedName("user_name") @field:SerializedName("user_name")
val userName: String, val userName: String,
@ -37,9 +34,6 @@ data class Store(
@field:SerializedName("user_phone") @field:SerializedName("user_phone")
val userPhone: String, val userPhone: String,
@field:SerializedName("halal")
val halal: String,
@field:SerializedName("id") @field:SerializedName("id")
val id: Int, val id: Int,

View File

@ -0,0 +1,28 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class UpdateAddressReq(
@SerializedName("street")
val street: String? = "",
@SerializedName("subdistrict")
val subdistrict: String? = "",
@SerializedName("postal_code")
val postalCCode: String? = "",
@SerializedName("detail")
val detail: String? = "",
@SerializedName("city_id")
val cityId: String? = "",
@SerializedName("province_id")
val provId: String? = "",
@SerializedName("phone")
val phone: String? = ""
)

View File

@ -0,0 +1,9 @@
package com.alya.ecommerce_serang.data.api.response.auth
import com.google.gson.annotations.SerializedName
data class ResetPassResponse(
@field:SerializedName("message")
val message: String
)

View File

@ -119,7 +119,7 @@ data class Orders(
val orderId: Int, val orderId: Int,
@field:SerializedName("city_id") @field:SerializedName("city_id")
val cityId: Int val cityId: String
) )
data class OrderListItemsItem( data class OrderListItemsItem(

View File

@ -82,7 +82,8 @@ data class Product(
data class CartItemCheckoutInfo( data class CartItemCheckoutInfo(
val cartItem: CartItemsItem, val cartItem: CartItemsItem,
val isWholesale: Boolean val isWholesale: Boolean,
val wholesalePrice: Int? = null
) )

View File

@ -114,7 +114,7 @@ data class Store(
val storeDescription: String, val storeDescription: String,
@field:SerializedName("city_id") @field:SerializedName("city_id")
val cityId: Int val cityId: String
) )
data class ShippingItem( data class ShippingItem(

View File

@ -0,0 +1,69 @@
package com.alya.ecommerce_serang.data.api.response.customer.profile
import com.google.gson.annotations.SerializedName
data class AddressDetailResponse(
@field:SerializedName("address")
val address: AddressDetail,
@field:SerializedName("message")
val message: String
)
data class AddressDetail(
@field:SerializedName("village_id")
val villageId: String?,
@field:SerializedName("is_store_location")
val isStoreLocation: Boolean,
@field:SerializedName("latitude")
val latitude: String,
@field:SerializedName("province_name")
val provinceName: String?,
@field:SerializedName("subdistrict_id")
val subdistrictId: String?,
@field:SerializedName("city_name")
val cityName: String?,
@field:SerializedName("user_id")
val userId: Int,
@field:SerializedName("province_id")
val provinceId: String,
@field:SerializedName("phone")
val phone: String?,
@field:SerializedName("street")
val street: String,
@field:SerializedName("subdistrict")
val subdistrict: String,
@field:SerializedName("recipient")
val recipient: String?,
@field:SerializedName("id")
val id: Int,
@field:SerializedName("detail")
val detail: String,
@field:SerializedName("village_name")
val villageName: String,
@field:SerializedName("postal_code")
val postalCode: String,
@field:SerializedName("longitude")
val longitude: String,
@field:SerializedName("city_id")
val cityId: String
)

View File

@ -14,7 +14,7 @@ data class AddressResponse(
data class AddressesItem( data class AddressesItem(
@field:SerializedName("village_id") @field:SerializedName("village_id")
val villageId: String, val villageId: String?,
@field:SerializedName("is_store_location") @field:SerializedName("is_store_location")
val isStoreLocation: Boolean, val isStoreLocation: Boolean,
@ -29,7 +29,7 @@ data class AddressesItem(
val provinceId: String, val provinceId: String,
@field:SerializedName("phone") @field:SerializedName("phone")
val phone: String, val phone: String?,
@field:SerializedName("street") @field:SerializedName("street")
val street: String, val street: String,
@ -38,7 +38,7 @@ data class AddressesItem(
val subdistrict: String, val subdistrict: String,
@field:SerializedName("recipient") @field:SerializedName("recipient")
val recipient: String, val recipient: String?,
@field:SerializedName("id") @field:SerializedName("id")
val id: Int, val id: Int,

View File

@ -0,0 +1,57 @@
package com.alya.ecommerce_serang.data.api.response.customer.profile
import com.google.gson.annotations.SerializedName
data class UpdateAddressResponse(
@field:SerializedName("address")
val address: Address,
@field:SerializedName("message")
val message: String
)
data class Address(
@field:SerializedName("village_id")
val villageId: String,
@field:SerializedName("is_store_location")
val isStoreLocation: Boolean,
@field:SerializedName("latitude")
val latitude: String,
@field:SerializedName("user_id")
val userId: Int,
@field:SerializedName("province_id")
val provinceId: String,
@field:SerializedName("phone")
val phone: Any,
@field:SerializedName("street")
val street: String,
@field:SerializedName("subdistrict")
val subdistrict: String,
@field:SerializedName("recipient")
val recipient: Any,
@field:SerializedName("id")
val id: Int,
@field:SerializedName("detail")
val detail: String,
@field:SerializedName("postal_code")
val postalCode: String,
@field:SerializedName("longitude")
val longitude: String,
@field:SerializedName("city_id")
val cityId: String
)

View File

@ -1,18 +1,13 @@
package com.alya.ecommerce_serang.data.api.response.store package com.alya.ecommerce_serang.data.api.response.store
import com.alya.ecommerce_serang.data.api.dto.Store
import com.alya.ecommerce_serang.data.api.response.store.profile.Payment
import com.alya.ecommerce_serang.data.api.response.store.profile.Shipping
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class StoreResponse( data class StoreResponse(
val message: String, val message: String,
val store: Store val store: Store,
) 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
) )

View File

@ -35,7 +35,7 @@ data class Store(
val detail: String, val detail: String,
@SerializedName("is_store_location") val isStoreLocation: Boolean, @SerializedName("is_store_location") val isStoreLocation: Boolean,
@SerializedName("user_id") val userId: Int, @SerializedName("user_id") val userId: Int,
@SerializedName("city_id") val cityId: Int, @SerializedName("city_id") val cityId: String,
@SerializedName("province_id") val provinceId: Int, @SerializedName("province_id") val provinceId: Int,
val phone: String?, val phone: String?,
val recipient: String?, val recipient: String?,

View File

@ -132,5 +132,5 @@ data class Orders(
val username: String? = null, val username: String? = null,
@field:SerializedName("city_id") @field:SerializedName("city_id")
val cityId: Int? = null val cityId: String? = null
) )

View File

@ -129,7 +129,7 @@ data class OrdersItem(
val status: String? = null, val status: String? = null,
@field:SerializedName("city_id") @field:SerializedName("city_id")
val cityId: Int? = null, val cityId: String? = null,
var displayStatus: String? = null var displayStatus: String? = null
) )

View File

@ -19,6 +19,7 @@ import com.alya.ecommerce_serang.data.api.dto.OtpRequest
import com.alya.ecommerce_serang.data.api.dto.PaymentConfirmRequest import com.alya.ecommerce_serang.data.api.dto.PaymentConfirmRequest
import com.alya.ecommerce_serang.data.api.dto.ProvinceResponse import com.alya.ecommerce_serang.data.api.dto.ProvinceResponse
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.dto.ResetPassReq
import com.alya.ecommerce_serang.data.api.dto.ReviewProductItem 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.SearchRequest
import com.alya.ecommerce_serang.data.api.dto.ShippingServiceRequest import com.alya.ecommerce_serang.data.api.dto.ShippingServiceRequest
@ -36,6 +37,7 @@ import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse
import com.alya.ecommerce_serang.data.api.response.auth.ResetPassResponse
import com.alya.ecommerce_serang.data.api.response.auth.VerifRegisterResponse import com.alya.ecommerce_serang.data.api.response.auth.VerifRegisterResponse
import com.alya.ecommerce_serang.data.api.response.chat.ChatHistoryResponse import com.alya.ecommerce_serang.data.api.response.chat.ChatHistoryResponse
import com.alya.ecommerce_serang.data.api.response.chat.ChatListResponse import com.alya.ecommerce_serang.data.api.response.chat.ChatListResponse
@ -61,10 +63,12 @@ import com.alya.ecommerce_serang.data.api.response.customer.product.DetailStoreP
import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.ReviewProductResponse import com.alya.ecommerce_serang.data.api.response.customer.product.ReviewProductResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressDetailResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.EditProfileResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.EditProfileResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.ProfileResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.ProfileResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.UpdateAddressResponse
import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse
import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse
import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse
@ -391,18 +395,8 @@ interface ApiService {
@PUT("mystore/edit") @PUT("mystore/edit")
suspend fun updateStoreProfileMultipart( suspend fun updateStoreProfileMultipart(
@Part("store_name") storeName: RequestBody, @Part("store_name") storeName: RequestBody,
@Part("store_status") storeStatus: RequestBody,
@Part("store_description") storeDescription: RequestBody, @Part("store_description") storeDescription: RequestBody,
@Part("is_on_leave") isOnLeave: 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("store_type_id") storeTypeId: RequestBody, @Part("store_type_id") storeTypeId: RequestBody,
@Part storeimg: MultipartBody.Part? @Part storeimg: MultipartBody.Part?
): Response<StoreDataResponse> ): Response<StoreDataResponse>
@ -454,6 +448,12 @@ interface ApiService {
@Body addressData: HashMap<String, Any?> @Body addressData: HashMap<String, Any?>
): Response<StoreAddressResponse> ): Response<StoreAddressResponse>
@PUT("profile/address/edit/{id}")
suspend fun updateAddress(
@Path("id") addressId: Int,
@Body params: Map<String, @JvmSuppressWildcards Any>
): Response<UpdateAddressResponse>
@POST("search") @POST("search")
suspend fun saveSearchQuery( suspend fun saveSearchQuery(
@Body searchRequest: SearchRequest @Body searchRequest: SearchRequest
@ -524,4 +524,14 @@ interface ApiService {
suspend fun getVillages( suspend fun getVillages(
@Path("subdistrictId") subdistrictId: String @Path("subdistrictId") subdistrictId: String
): Response<VillagesResponse> ): Response<VillagesResponse>
@POST("resetpass")
suspend fun postResetPass(
@Body request: ResetPassReq
): Response<ResetPassResponse>
@GET("profile/address/detail/{id}")
suspend fun getDetailAddress(
@Path("id") addressId: Int
): Response<AddressDetailResponse>
} }

View File

@ -1,131 +1,140 @@
package com.alya.ecommerce_serang.data.repository package com.alya.ecommerce_serang.data.repository
import android.util.Log import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.City import com.alya.ecommerce_serang.data.api.dto.CityResponse
import com.alya.ecommerce_serang.data.api.dto.Province import com.alya.ecommerce_serang.data.api.dto.ProvinceResponse
import com.alya.ecommerce_serang.data.api.dto.StoreAddress import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.UpdateAddressResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.json.JSONObject import retrofit2.Response
class AddressRepository(private val apiService: ApiService) { class AddressRepository(private val apiService: ApiService) {
private val TAG = "AddressRepository" private val TAG = "AddressRepository"
suspend fun getProvinces(): List<Province> = withContext(Dispatchers.IO) { // suspend fun getProvinces(): List<Province> = withContext(Dispatchers.IO) {
Log.d(TAG, "getProvinces() called") // Log.d(TAG, "getProvinces() called")
try { // try {
val response = apiService.getProvinces() // val response = apiService.getProvinces()
Log.d(TAG, "getProvinces() response: isSuccessful=${response.isSuccessful}, code=${response.code()}") // 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}")
// }
// }
// Log the raw response body for debugging // suspend fun getCities(provinceId: String): List<City> = withContext(Dispatchers.IO) {
val rawBody = response.raw().toString() // Log.d(TAG, "getCities() called with provinceId: $provinceId")
Log.d(TAG, "Raw response: $rawBody") // 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}")
// }
// }
if (response.isSuccessful) { suspend fun getProvinces(): Response<ProvinceResponse> {
val responseBody = response.body() return apiService.getProvinces()
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) { suspend fun getCities(provinceId: String): Response<CityResponse> {
Log.d(TAG, "getCities() called with provinceId: $provinceId") return apiService.getCities(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) { // suspend fun getStoreAddress(): StoreAddress? = withContext(Dispatchers.IO) {
Log.d(TAG, "getStoreAddress() called") // Log.d(TAG, "getStoreAddress() called")
try { // try {
val response = apiService.getStoreAddress() // val response = apiService.getStoreAddress()
Log.d(TAG, "getStoreAddress() response: isSuccessful=${response.isSuccessful}, code=${response.code()}") // Log.d(TAG, "getStoreAddress() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
//
if (response.isSuccessful) { // if (response.isSuccessful) {
val responseBody = response.body() // val responseBody = response.body()
val rawJson = Gson().toJson(responseBody) // val rawJson = Gson().toJson(responseBody)
Log.d(TAG, "Response body: $rawJson") // Log.d(TAG, "Response body: $rawJson")
//
val address = responseBody?.data // val address = responseBody?.data
Log.d(TAG, "getStoreAddress() success, address: $address") // Log.d(TAG, "getStoreAddress() success, address: $address")
//
// Convert numeric strings to proper types if needed // // Convert numeric strings to proper types if needed
address?.let { // address?.let {
// Handle city_id if it's a number // // Handle city_id if it's a number
if (it.cityId.isBlank() && rawJson.contains("city_id")) { // if (it.cityId.isBlank() && rawJson.contains("city_id")) {
try { // try {
val cityId = JSONObject(rawJson).getJSONObject("store").optInt("city_id", 0) // val cityId = JSONObject(rawJson).getJSONObject("store").optInt("city_id", 0)
if (cityId > 0) { // if (cityId > 0) {
it.javaClass.getDeclaredField("cityId").apply { // it.javaClass.getDeclaredField("cityId").apply {
isAccessible = true // isAccessible = true
set(it, cityId.toString()) // set(it, cityId.toString())
} // }
Log.d(TAG, "Updated cityId to: ${it.cityId}") // Log.d(TAG, "Updated cityId to: ${it.cityId}")
} // }
} catch (e: Exception) { // } catch (e: Exception) {
Log.e(TAG, "Error parsing city_id", e) // Log.e(TAG, "Error parsing city_id", e)
} // }
} // }
//
// Handle province_id if it's a number // // Handle province_id if it's a number
if (it.provinceId.isBlank() && rawJson.contains("province_id")) { // if (it.provinceId.isBlank() && rawJson.contains("province_id")) {
try { // try {
val provinceId = JSONObject(rawJson).getJSONObject("store").optInt("province_id", 0) // val provinceId = JSONObject(rawJson).getJSONObject("store").optInt("province_id", 0)
if (provinceId > 0) { // if (provinceId > 0) {
it.javaClass.getDeclaredField("provinceId").apply { // it.javaClass.getDeclaredField("provinceId").apply {
isAccessible = true // isAccessible = true
set(it, provinceId.toString()) // set(it, provinceId.toString())
} // }
Log.d(TAG, "Updated provinceId to: ${it.provinceId}") // Log.d(TAG, "Updated provinceId to: ${it.provinceId}")
} // }
} catch (e: Exception) { // } catch (e: Exception) {
Log.e(TAG, "Error parsing province_id", e) // Log.e(TAG, "Error parsing province_id", e)
} // }
} // }
} // }
//
return@withContext address // return@withContext address
} else { // } else {
val errorBody = response.errorBody()?.string() ?: "Unknown error" // val errorBody = response.errorBody()?.string() ?: "Unknown error"
Log.e(TAG, "getStoreAddress() error: $errorBody") // Log.e(TAG, "getStoreAddress() error: $errorBody")
throw Exception("API Error (${response.code()}): $errorBody") // throw Exception("API Error (${response.code()}): $errorBody")
} // }
} catch (e: Exception) { // } catch (e: Exception) {
Log.e(TAG, "Exception in getStoreAddress()", e) // Log.e(TAG, "Exception in getStoreAddress()", e)
throw Exception("Network error: ${e.message}") // throw Exception("Network error: ${e.message}")
} // }
} // }
suspend fun saveStoreAddress( suspend fun saveStoreAddress(
provinceId: String, provinceId: String,
@ -171,4 +180,17 @@ class AddressRepository(private val apiService: ApiService) {
throw Exception("Network error: ${e.message}") throw Exception("Network error: ${e.message}")
} }
} }
suspend fun getStoreAddress(): Response<AddressResponse> {
return apiService.getAddress()
}
suspend fun updateAddress(addressId: Int, params: Map<String, Any>): Response<UpdateAddressResponse> {
return apiService.updateAddress(addressId, params)
}
suspend fun getListSubdistrict(cityId : String): SubdistrictResponse? {
val response = apiService.getSubdistrict(cityId)
return if (response.isSuccessful) response.body() else null
}
} }

View File

@ -4,7 +4,7 @@ import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.ProductsItem import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.dto.Store import com.alya.ecommerce_serang.data.api.dto.Store
import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse import com.alya.ecommerce_serang.data.api.response.store.StoreResponse
import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse
import com.alya.ecommerce_serang.data.api.response.store.sells.OrderListResponse import com.alya.ecommerce_serang.data.api.response.store.sells.OrderListResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.api.retrofit.ApiService
@ -15,13 +15,13 @@ import retrofit2.Response
import java.io.IOException import java.io.IOException
class MyStoreRepository(private val apiService: ApiService) { class MyStoreRepository(private val apiService: ApiService) {
suspend fun fetchMyStoreProfile(): Result<Store?> { suspend fun fetchMyStoreProfile(): Result<StoreResponse?> {
return try { return try {
val response = apiService.getStore() val response = apiService.getMyStoreData()
if (response.isSuccessful) { if (response.isSuccessful) {
val storeResponse: StoreResponse? = response.body() val storeResponse = response.body()
Result.Success(storeResponse?.store) Result.Success(storeResponse)
} else { } else {
val errorMessage = response.errorBody()?.string() ?: "Unknown API error" val errorMessage = response.errorBody()?.string() ?: "Unknown API error"
Log.e("MyStoreRepository", "Error: $errorMessage") Log.e("MyStoreRepository", "Error: $errorMessage")
@ -53,48 +53,49 @@ class MyStoreRepository(private val apiService: ApiService) {
suspend fun updateStoreProfile( suspend fun updateStoreProfile(
storeName: RequestBody, storeName: RequestBody,
storeStatus: RequestBody,
storeDescription: RequestBody, storeDescription: RequestBody,
isOnLeave: RequestBody, isOnLeave: RequestBody,
cityId: RequestBody,
provinceId: RequestBody,
street: RequestBody,
subdistrict: RequestBody,
detail: RequestBody,
postalCode: RequestBody,
latitude: RequestBody,
longitude: RequestBody,
userPhone: RequestBody,
storeType: RequestBody, storeType: RequestBody,
storeimg: MultipartBody.Part? storeimg: MultipartBody.Part?
): Response<StoreDataResponse> { ): Response<StoreDataResponse>? {
return apiService.updateStoreProfileMultipart(
storeName, storeStatus, storeDescription, isOnLeave, cityId, provinceId, return try {
street, subdistrict, detail, postalCode, latitude, longitude, userPhone, storeType, storeimg Log.d(TAG, "storeName: $storeName")
Log.d(TAG, "storeDescription: $storeDescription")
Log.d(TAG, "isOnLeave: $isOnLeave")
Log.d(TAG, "storeType: $storeType")
Log.d(TAG, "storeimg: ${storeimg?.headers}")
apiService.updateStoreProfileMultipart(
storeName, storeDescription, isOnLeave, storeType, storeimg
) )
} catch (e: Exception) {
Log.e(TAG, "Error updating store profile", e)
null
}
} }
suspend fun getSellList(status: String): Result<OrderListResponse> { suspend fun getSellList(status: String): Result<OrderListResponse> {
return try { return try {
Log.d("SellsRepository", "Add Evidence : $status") Log.d(TAG, "Add Evidence : $status")
val response = apiService.getSellList(status) val response = apiService.getSellList(status)
if (response.isSuccessful) { if (response.isSuccessful) {
val allListSell = response.body() val allListSell = response.body()
if (allListSell != null) { if (allListSell != null) {
Log.d("SellsRepository", "Add Evidence successfully: ${allListSell.message}") Log.d(TAG, "Add Evidence successfully: ${allListSell.message}")
Result.Success(allListSell) Result.Success(allListSell)
} else { } else {
Log.e("SellsRepository", "Response body was null") Log.e(TAG, "Response body was null")
Result.Error(Exception("Empty response from server")) Result.Error(Exception("Empty response from server"))
} }
} else { } else {
val errorBody = response.errorBody()?.string() ?: "Unknown error" val errorBody = response.errorBody()?.string() ?: "Unknown error"
Log.e("SellsRepository", "Error Add Evidence : $errorBody") Log.e(TAG, "Error Add Evidence : $errorBody")
Result.Error(Exception(errorBody)) Result.Error(Exception(errorBody))
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("SellsRepository", "Exception Add Evidence ", e) Log.e(TAG, "Exception Add Evidence ", e)
Result.Error(e) Result.Error(e)
} }
} }
@ -119,7 +120,7 @@ class MyStoreRepository(private val apiService: ApiService) {
) )
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("MyStoreRepository", "Error fetching balance", e) Log.e(TAG, "Error fetching balance", e)
Result.Error(e) Result.Error(e)
} }
} }
@ -133,11 +134,15 @@ class MyStoreRepository(private val apiService: ApiService) {
throw Exception("Failed to fetch store products: ${response.message()}") throw Exception("Failed to fetch store products: ${response.message()}")
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ProductRepository", "Error fetching store products", e) Log.e(TAG, "Error fetching store products", e)
throw e throw e
} }
} }
companion object {
private var TAG = "MyStoreRepository"
}
// private fun fetchBalance() { // private fun fetchBalance() {
// showLoading(true) // showLoading(true)
// lifecycleScope.launch { // lifecycleScope.launch {

View File

@ -20,12 +20,16 @@ import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityRespon
import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.OrderDetailResponse import com.alya.ecommerce_serang.data.api.response.customer.order.OrderDetailResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.OrderListResponse import com.alya.ecommerce_serang.data.api.response.customer.order.OrderListResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.VillagesResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.DetailPaymentItem import com.alya.ecommerce_serang.data.api.response.customer.product.DetailPaymentItem
import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse import com.alya.ecommerce_serang.data.api.response.customer.product.ProductResponse
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem import com.alya.ecommerce_serang.data.api.response.customer.product.StoreItem
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressDetailResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.UpdateAddressResponse
import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse
import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse
import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse
@ -294,6 +298,43 @@ class OrderRepository(private val apiService: ApiService) {
return if (response.isSuccessful) response.body() else null return if (response.isSuccessful) response.body() else null
} }
suspend fun getListSubdistrict(cityId : String): SubdistrictResponse? {
val response = apiService.getSubdistrict(cityId)
return if (response.isSuccessful) response.body() else null
}
suspend fun getListVillages(subId: String): VillagesResponse? {
val response = apiService.getVillages(subId)
return if (response.isSuccessful) response.body() else null
}
suspend fun getAddressDetail(addressId: Int): AddressDetailResponse? {
return try {
Log.d("Order Repository", "Fetching address detail for ID: $addressId")
val response = apiService.getDetailAddress(addressId)
Log.d("Order Repository", "Response code: ${response.code()}")
Log.d("Order Repository", "Response message: ${response.message()}")
if (response.isSuccessful) {
val body = response.body()
Log.d("Order Repository", "Address detail response body: $body")
body
} else {
Log.w("Order Repository", "Failed to get address detail. Error body: ${response.errorBody()?.string()}")
null
}
} catch (e: Exception) {
Log.e("Order Repository", "Error getting address detail", e)
null
}
}
suspend fun updateAddress(addressId: Int, params: Map<String, Any>): Response<UpdateAddressResponse> {
return apiService.updateAddress(addressId, params)
}
suspend fun fetchUserProfile(): Result<UserProfile?> { suspend fun fetchUserProfile(): Result<UserProfile?> {
return try { return try {
val response = apiService.getUserProfile() val response = apiService.getUserProfile()
@ -319,6 +360,7 @@ class OrderRepository(private val apiService: ApiService) {
} }
} }
suspend fun uploadPaymentProof(request: AddEvidenceMultipartRequest): Result<AddEvidenceResponse> { suspend fun uploadPaymentProof(request: AddEvidenceMultipartRequest): Result<AddEvidenceResponse> {
return try { return try {
Log.d("OrderRepository", "Uploading payment proof...") Log.d("OrderRepository", "Uploading payment proof...")

View File

@ -7,6 +7,7 @@ import com.alya.ecommerce_serang.data.api.dto.FcmReq
import com.alya.ecommerce_serang.data.api.dto.LoginRequest import com.alya.ecommerce_serang.data.api.dto.LoginRequest
import com.alya.ecommerce_serang.data.api.dto.OtpRequest import com.alya.ecommerce_serang.data.api.dto.OtpRequest
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.dto.ResetPassReq
import com.alya.ecommerce_serang.data.api.dto.UserProfile import com.alya.ecommerce_serang.data.api.dto.UserProfile
import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
@ -18,6 +19,7 @@ import com.alya.ecommerce_serang.data.api.response.auth.NotifstoreItem
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse
import com.alya.ecommerce_serang.data.api.response.auth.ResetPassResponse
import com.alya.ecommerce_serang.data.api.response.auth.VerifRegisterResponse import com.alya.ecommerce_serang.data.api.response.auth.VerifRegisterResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse
@ -278,6 +280,11 @@ class UserRepository(private val apiService: ApiService) {
val requestFile = compressedFile.asRequestBody(mimeType.toMediaTypeOrNull()) val requestFile = compressedFile.asRequestBody(mimeType.toMediaTypeOrNull())
Log.d(TAG, "$formName compressed size: ${compressedFile.length() / 1024} KB") Log.d(TAG, "$formName compressed size: ${compressedFile.length() / 1024} KB")
val compressedSizeMB = compressedFile.length().toDouble() / (1024 * 1024)
if (compressedSizeMB > 1) {
throw IllegalArgumentException("$formName lebih dari 1 MB setelah kompresi")
}
MultipartBody.Part.createFormData(formName, compressedFile.name, requestFile) MultipartBody.Part.createFormData(formName, compressedFile.name, requestFile)
} else { } else {
throw IllegalArgumentException("$formName harus berupa file gambar (JPEG, JPG, atau PNG)") throw IllegalArgumentException("$formName harus berupa file gambar (JPEG, JPG, atau PNG)")
@ -485,6 +492,30 @@ class UserRepository(private val apiService: ApiService) {
Result.Error(e) Result.Error(e)
} }
} }
suspend fun resetPassword(request: ResetPassReq): Result<ResetPassResponse>{
return try {
val response = apiService.postResetPass(request)
if (response.isSuccessful){
val resetPassResponse = response.body()
if (resetPassResponse != null) {
Result.Success(resetPassResponse)
}
else {
Result.Error(Exception("Empty response from server"))
}
}
else {
val errorBody = response.errorBody()?.string() ?: "Unknown error"
Log.e(TAG, "Error RESET PASS address: $errorBody")
Result.Error(Exception(errorBody))
}
}
catch (e: Exception){
Result.Error(e)
}
}
companion object{ companion object{
private const val TAG = "UserRepository" private const val TAG = "UserRepository"
} }

View File

@ -72,7 +72,7 @@ class LoginActivity : AppCompatActivity() {
val password = binding.etLoginPassword.text.toString() val password = binding.etLoginPassword.text.toString()
if (email.isEmpty() || password.isEmpty()) { if (email.isEmpty() || password.isEmpty()) {
Toast.makeText(this, "Please fill in all fields", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Mohon masukkan email atau password dengan benar", Toast.LENGTH_SHORT).show()
} else { } else {
loginViewModel.login(email, password) loginViewModel.login(email, password)
} }
@ -82,6 +82,11 @@ class LoginActivity : AppCompatActivity() {
startActivity(Intent(this, RegisterActivity::class.java)) startActivity(Intent(this, RegisterActivity::class.java))
finish() finish()
} }
binding.tvForgetPassword.setOnClickListener {
startActivity(Intent(this, ResetPassActivity::class.java))
finish()
}
} }
private fun observeLoginState() { private fun observeLoginState() {
@ -95,14 +100,14 @@ class LoginActivity : AppCompatActivity() {
retrieveFCMToken() retrieveFCMToken()
// sessionManager.saveUserId(response.userId) // sessionManager.saveUserId(response.userId)
Toast.makeText(this, "Login Successful", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Berhasil masuk", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, MainActivity::class.java)) startActivity(Intent(this, MainActivity::class.java))
finish() finish()
} }
is com.alya.ecommerce_serang.data.repository.Result.Error -> { is com.alya.ecommerce_serang.data.repository.Result.Error -> {
Log.e("LoginActivity", "Login Failed: ${result.exception.message}") Log.e("LoginActivity", "Login Failed: ${result.exception.message}")
Toast.makeText(this, "Login Failed: ${result.exception.message}", Toast.LENGTH_LONG).show() Toast.makeText(this, "Gagal masuk", Toast.LENGTH_LONG).show()
} }
is Result.Loading -> { is Result.Loading -> {
// Show loading state // Show loading state

View File

@ -29,7 +29,7 @@ class OtpBottomSheetDialog(
onRegister(updatedUserData) // Send full data to ViewModel onRegister(updatedUserData) // Send full data to ViewModel
dismiss() // Close dialog dismiss() // Close dialog
} else { } else {
Toast.makeText(requireContext(), "Please enter OTP", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Silahkan masukkan kode OTP", Toast.LENGTH_SHORT).show()
} }
} }
return view return view

View File

@ -86,20 +86,72 @@ class RegisterActivity : AppCompatActivity() {
} }
} }
// navigate step register in fragment // In RegisterActivity, add debug to navigateToStep:
fun navigateToStep(step: Int, userData: RegisterRequest?) { fun navigateToStep(step: Int, userData: RegisterRequest?) {
val fragment = when (step) { Log.d("RegisterActivity", "=== NAVIGATE TO STEP START ===")
1 -> RegisterStep1Fragment.newInstance() Log.d("RegisterActivity", "Target step: $step")
2 -> RegisterStep2Fragment.newInstance(userData) Log.d("RegisterActivity", "Current fragment count: ${supportFragmentManager.fragments.size}")
3 -> RegisterStep3Fragment.newInstance() Log.d("RegisterActivity", "UserData: ${userData?.email}")
else -> null
Log.d("RegisterActivity", "Navigation called from:")
Thread.currentThread().stackTrace.take(10).forEach { element ->
Log.d("RegisterActivity", " at ${element.className}.${element.methodName}(${element.fileName}:${element.lineNumber})")
} }
fragment?.let {
supportFragmentManager.beginTransaction() try {
.replace(R.id.fragment_container, it) val fragment = when (step) {
.addToBackStack(null) 1 -> {
.commit() Log.d("RegisterActivity", "Creating RegisterStep1Fragment")
RegisterStep1Fragment.newInstance()
}
2 -> {
Log.d("RegisterActivity", "Creating RegisterStep2Fragment")
RegisterStep2Fragment.newInstance(userData)
}
3 -> {
Log.d("RegisterActivity", "Creating RegisterStep3Fragment")
RegisterStep3Fragment.newInstance()
}
else -> {
Log.e("RegisterActivity", "Invalid step: $step")
return
}
}
Log.d("RegisterActivity", "Fragment created, starting transaction")
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, fragment)
Log.d("RegisterActivity", "About to commit transaction")
transaction.commit()
Log.d("RegisterActivity", "Transaction committed")
// Update ViewModel step
registerViewModel.setStep(step)
Log.d("RegisterActivity", "ViewModel step updated to: $step")
} catch (e: Exception) {
Log.e("RegisterActivity", "Exception in navigateToStep: ${e.message}", e)
e.printStackTrace()
}
Log.d("RegisterActivity", "=== NAVIGATE TO STEP END ===")
}
// Handle Android back button - close activity or go to step 1
override fun onBackPressed() {
val currentStep = registerViewModel.currentStep.value ?: 1
if (currentStep == 1) {
// On step 1, exit the activity
super.onBackPressed()
} else {
// On other steps, go back to step 1
navigateToStep(1, null)
} }
} }
} }

View File

@ -0,0 +1,123 @@
package com.alya.ecommerce_serang.ui.auth
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityResetPassBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.viewmodel.LoginViewModel
class ResetPassActivity : AppCompatActivity() {
private val TAG = "ResetPassActivity"
private lateinit var binding: ActivityResetPassBinding
private val loginViewModel: LoginViewModel by viewModels{
BaseViewModelFactory {
val apiService = ApiConfig.getUnauthenticatedApiService()
val userRepository = UserRepository(apiService)
LoginViewModel(userRepository, this)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityResetPassBinding.inflate(layoutInflater)
setContentView(binding.root)
// enableEdgeToEdge()
setupToolbar()
setupUI()
}
private fun setupToolbar(){
binding.headerResetPass.headerLeftIcon.setOnClickListener{
finish()
}
binding.headerResetPass.headerTitle.text = "Lupa Password"
}
private fun setupUI(){
binding.btnReset.setOnClickListener {
val email = binding.etEmail.text.toString().trim()
if (email.isNotEmpty()) {
loginViewModel.resetPassword(email)
} else {
binding.etEmail.error = "Masukkan Email Anda"
}
}
}
private fun observeResetPassword() {
loginViewModel.resetPasswordState.observe(this) { result ->
when (result) {
is com.alya.ecommerce_serang.data.repository.Result.Loading -> {
showLoading(true)
}
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
showLoading(false)
handleSuccess("Silahkan cek email anda untuk melihat password anda.")
Log.d(TAG, "Success rest password: ${result.data.message}")
}
is Result.Error -> {
showLoading(false)
handleError("Email anda salah atau tidak ditemukan.")
Log.e(TAG, "Error reset password ${result.exception.message}")
}
null -> {
// Initial state
}
}
}
}
private fun showLoading(isLoading: Boolean) {
if (isLoading) {
binding.progressBar.visibility = View.VISIBLE
binding.btnReset.isEnabled = false
binding.etEmail.isEnabled = false
} else {
binding.progressBar.visibility = View.GONE
binding.btnReset.isEnabled = true
binding.etEmail.isEnabled = true
}
}
private fun handleSuccess(message: String) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
// Show success dialog and navigate back to login
AlertDialog.Builder(this)
.setTitle("Berhasil Ubah Password")
.setMessage(message)
.setPositiveButton("OK") { _, _ ->
// Navigate back to login activity
finish()
}
.setCancelable(false)
.show()
}
private fun handleError(errorMessage: String) {
Log.e(TAG, "Error: $errorMessage")
// Optionally show error dialog
AlertDialog.Builder(this)
.setTitle("Gagal Ubah Password")
.setMessage(errorMessage)
.setPositiveButton("OK", null)
.show()
}
}

View File

@ -155,19 +155,20 @@ class RegisterStep1Fragment : Fragment() {
"email" -> { "email" -> {
isEmailValid = isValid isEmailValid = isValid
if (!isValid) { if (!isValid) {
Toast.makeText(requireContext(), "Email is already registered", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Email sudah digunakan. Gunakan email lainnya.", Toast.LENGTH_SHORT).show()
} }
} }
"phone" -> { "phone" -> {
isPhoneValid = isValid isPhoneValid = isValid
if (!isValid) { if (!isValid) {
Toast.makeText(requireContext(), "Phone number is already registered", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Nomor handphone sudah digunakan. Gunakan nomor lainnya. ", Toast.LENGTH_SHORT).show()
} }
} }
} }
} }
is com.alya.ecommerce_serang.data.repository.Result.Error -> { is com.alya.ecommerce_serang.data.repository.Result.Error -> {
Toast.makeText(requireContext(), "Validation failed: ${result.exception.message}", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Gagal melakukan validasi", Toast.LENGTH_SHORT).show()
Log.e(TAG, "Validation failed: ${result.exception.message}")
} }
} }
} }
@ -200,7 +201,8 @@ class RegisterStep1Fragment : Fragment() {
is Result.Error -> { is Result.Error -> {
binding.progressBar.visibility = View.GONE binding.progressBar.visibility = View.GONE
binding.btnNext.isEnabled = true binding.btnNext.isEnabled = true
Toast.makeText(requireContext(), "OTP Request Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show() Log.e(TAG, "OTP Request Failed: ${result.exception.message}")
Toast.makeText(requireContext(), "Gagal mendapatkan OTP. Kirim ulang OTP", Toast.LENGTH_SHORT).show()
} }
} }
} }
@ -229,13 +231,13 @@ class RegisterStep1Fragment : Fragment() {
// Check if all fields are filled // Check if all fields are filled
if (email.isEmpty() || password.isEmpty() || confirmPassword.isEmpty() || phone.isEmpty() || if (email.isEmpty() || password.isEmpty() || confirmPassword.isEmpty() || phone.isEmpty() ||
username.isEmpty() || fullName.isEmpty() || birthDate.isEmpty()) { username.isEmpty() || fullName.isEmpty() || birthDate.isEmpty()) {
Toast.makeText(requireContext(), "Please fill all required fields", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Silahkan lengkapi seluruh isian", Toast.LENGTH_SHORT).show()
return return
} }
// Check if passwords match // Check if passwords match
if (password != confirmPassword) { if (password != confirmPassword) {
Toast.makeText(requireContext(), "Passwords do not match", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Konfirmasi kata sandi tidak sesua. Periksa kembali", Toast.LENGTH_SHORT).show()
return return
} }
@ -253,7 +255,7 @@ class RegisterStep1Fragment : Fragment() {
if (isEmailValid && isPhoneValid) { if (isEmailValid && isPhoneValid) {
requestOtp(email) requestOtp(email)
} else { } else {
Toast.makeText(requireContext(), "Please fix validation errors before proceeding", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Silahkan perbaiki data yang dimasukkan", Toast.LENGTH_SHORT).show()
} }
} }

View File

@ -1,5 +1,7 @@
package com.alya.ecommerce_serang.ui.auth.fragments package com.alya.ecommerce_serang.ui.auth.fragments
import android.content.Context
import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.CountDownTimer import android.os.CountDownTimer
@ -13,6 +15,7 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.FcmReq
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.OrderRepository import com.alya.ecommerce_serang.data.repository.OrderRepository
@ -24,11 +27,15 @@ import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.RegisterViewModel import com.alya.ecommerce_serang.utils.viewmodel.RegisterViewModel
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.firebase.messaging.FirebaseMessaging
class RegisterStep2Fragment : Fragment() { class RegisterStep2Fragment : Fragment() {
private var _binding: FragmentRegisterStep2Binding? = null private var _binding: FragmentRegisterStep2Binding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private lateinit var sessionManager: SessionManager private lateinit var sessionManager: SessionManager
private var countDownTimer: CountDownTimer? = null
private var timeRemaining = 30
private var isTimerRunning = false
// In RegisterStep2Fragment AND RegisterStep3Fragment: // In RegisterStep2Fragment AND RegisterStep3Fragment:
private val registerViewModel: RegisterViewModel by activityViewModels { private val registerViewModel: RegisterViewModel by activityViewModels {
@ -39,8 +46,8 @@ class RegisterStep2Fragment : Fragment() {
RegisterViewModel(userRepository, orderRepository, requireContext()) RegisterViewModel(userRepository, orderRepository, requireContext())
} }
} }
private var countDownTimer: CountDownTimer? = null // private var countDownTimer: CountDownTimer? = null
private var timeRemaining = 30 // 30 seconds cooldown for resend // private var timeRemaining = 30 // 30 seconds cooldown for resend
companion object { companion object {
@ -112,6 +119,20 @@ class RegisterStep2Fragment : Fragment() {
observeRegistrationState() observeRegistrationState()
observeLoginState() observeLoginState()
Log.d(TAG, "Registration and login state observers set up") Log.d(TAG, "Registration and login state observers set up")
binding.btnBack.setOnClickListener {
Log.d(TAG, "Back button clicked - cleaning up timer and going to step 1")
// Stop the timer before navigating
stopTimer()
// Small delay to ensure timer is properly canceled
binding.root.postDelayed({
// (activity as? RegisterActivity)?.navigateToStep(1, null)
val intent = Intent(requireContext(), RegisterActivity::class.java)
startActivity(intent)
requireActivity().finish()
}, 100)
}
} }
private fun verifyOtp(userData: RegisterRequest?) { private fun verifyOtp(userData: RegisterRequest?) {
@ -129,11 +150,6 @@ class RegisterStep2Fragment : Fragment() {
Log.d(TAG, "Updating user data with OTP: $otp") Log.d(TAG, "Updating user data with OTP: $otp")
registerViewModel.updateUserData(updatedUserData) registerViewModel.updateUserData(updatedUserData)
// For demo purposes, we're just proceeding to Step 3
// In a real app, you would verify the OTP with the server first
// registerViewModel.setStep(3)
// (activity as? RegisterActivity)?.navigateToStep(3, updatedUserData)
registerViewModel.registerUser(updatedUserData) registerViewModel.registerUser(updatedUserData)
} ?: Log.e(TAG, "userData is null, cannot proceed with verification") } ?: Log.e(TAG, "userData is null, cannot proceed with verification")
} }
@ -170,37 +186,9 @@ class RegisterStep2Fragment : Fragment() {
} ?: Log.e(TAG, "Cannot resend OTP: email is null") } ?: Log.e(TAG, "Cannot resend OTP: email is null")
} }
private fun startResendCooldown() {
Log.d(TAG, "startResendCooldown called")
timeRemaining = 30
binding.tvResendOtp.isEnabled = false
binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.soft_gray))
countDownTimer?.cancel()
countDownTimer = object : CountDownTimer(30000, 1000) {
override fun onTick(millisUntilFinished: Long) {
timeRemaining = (millisUntilFinished / 1000).toInt()
binding.tvTimer.text = "Kirim ulang OTP dalam waktu 00:${String.format("%02d", timeRemaining)}"
if (timeRemaining % 5 == 0) {
Log.d(TAG, "Cooldown remaining: $timeRemaining seconds")
}
}
override fun onFinish() {
Log.d(TAG, "Cooldown finished, enabling resend button")
binding.tvTimer.text = "Dapat mengirim ulang kode OTP"
binding.tvResendOtp.isEnabled = true
binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.blue1))
timeRemaining = 0
}
}.start()
}
private fun observeRegistrationState() { private fun observeRegistrationState() {
registerViewModel.message.observe(viewLifecycleOwner) { message -> registerViewModel.message.observe(viewLifecycleOwner) { message ->
Log.d(TAG, "Message from server: $message") Log.d(TAG, "Message from server: $message")
// You can use the message here if needed, e.g., for showing in a specific UI element
// or for storing for later use
} }
registerViewModel.registerState.observe(viewLifecycleOwner) { result -> registerViewModel.registerState.observe(viewLifecycleOwner) { result ->
when (result) { when (result) {
@ -250,6 +238,8 @@ class RegisterStep2Fragment : Fragment() {
// Save the token in fragment // Save the token in fragment
val accessToken = result.data.accessToken val accessToken = result.data.accessToken
sessionManager.saveToken(accessToken) sessionManager.saveToken(accessToken)
retrieveFCMToken()
Log.d(TAG, "Token saved to SessionManager: $accessToken") Log.d(TAG, "Token saved to SessionManager: $accessToken")
// Proceed to Step 3 // Proceed to Step 3
@ -279,9 +269,148 @@ class RegisterStep2Fragment : Fragment() {
} }
} }
override fun onDestroyView() { private fun retrieveFCMToken() {
super.onDestroyView() FirebaseMessaging.getInstance().token
.addOnCompleteListener { task ->
if (!task.isSuccessful) {
Log.e(TAG, "Failed to get FCM token", task.exception)
return@addOnCompleteListener
}
val token = task.result
// tokenTes = token
Log.d(TAG, "FCM token retrieved: $token")
// Save token locally
val sharedPreferences = requireContext().getSharedPreferences("FCM_PREFS", Context.MODE_PRIVATE)
sharedPreferences.edit().putString("FCM_TOKEN", token).apply()
// Send to your server
sendTokenToServer(token)
}
}
private fun sendTokenToServer(token: String) {
Log.d(TAG, "Would send token to server: $token")
val tokenFcm=FcmReq(
fcmToken = token
)
registerViewModel.sendFcm(tokenFcm)
Log.d(TAG, "Sent token fcm: $token")
}
private fun startResendCooldown() {
Log.d(TAG, "startResendCooldown called")
// Cancel any existing timer first
stopTimer()
timeRemaining = 30
isTimerRunning = true
binding.tvResendOtp.isEnabled = false
binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.soft_gray))
countDownTimer = object : CountDownTimer(30000, 1000) {
override fun onTick(millisUntilFinished: Long) {
if (!isTimerRunning) {
cancel()
return
}
timeRemaining = (millisUntilFinished / 1000).toInt()
// Check if fragment is still attached before updating UI
if (isAdded && _binding != null) {
binding.tvTimer.text = "Kirim ulang OTP dalam waktu 00:${String.format("%02d", timeRemaining)}"
if (timeRemaining % 5 == 0) {
Log.d(TAG, "Cooldown remaining: $timeRemaining seconds")
}
}
}
override fun onFinish() {
if (!isTimerRunning) return
Log.d(TAG, "Cooldown finished, enabling resend button")
// Check if fragment is still attached before updating UI
if (isAdded && _binding != null) {
binding.tvTimer.text = "Dapat mengirim ulang kode OTP"
binding.tvResendOtp.isEnabled = true
binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.blue1))
timeRemaining = 0
}
isTimerRunning = false
}
}.start()
}
private fun stopTimer() {
Log.d(TAG, "stopTimer called")
isTimerRunning = false
countDownTimer?.cancel() countDownTimer?.cancel()
countDownTimer = null
}
override fun onPause() {
super.onPause()
Log.d(TAG, "onPause - stopping timer")
stopTimer()
}
override fun onStop() {
super.onStop()
Log.d(TAG, "onStop - stopping timer")
stopTimer()
}
override fun onDestroyView() {
Log.d(TAG, "onDestroyView - cleaning up")
super.onDestroyView()
// Ensure timer is stopped
stopTimer()
_binding = null _binding = null
} }
override fun onDetach() {
super.onDetach()
Log.d(TAG, "onDetach - final cleanup")
stopTimer()
} }
}
// override fun onDestroyView() {
// super.onDestroyView()
// countDownTimer?.cancel()
// _binding = null
// }
// private fun startResendCooldown() {
// Log.d(TAG, "startResendCooldown called")
// timeRemaining = 30
// binding.tvResendOtp.isEnabled = false
// binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.soft_gray))
//
// countDownTimer?.cancel()
// countDownTimer = object : CountDownTimer(30000, 1000) {
// override fun onTick(millisUntilFinished: Long) {
// timeRemaining = (millisUntilFinished / 1000).toInt()
// binding.tvTimer.text = "Kirim ulang OTP dalam waktu 00:${String.format("%02d", timeRemaining)}"
// if (timeRemaining % 5 == 0) {
// Log.d(TAG, "Cooldown remaining: $timeRemaining seconds")
// }
// }
//
// override fun onFinish() {
// Log.d(TAG, "Cooldown finished, enabling resend button")
// binding.tvTimer.text = "Dapat mengirim ulang kode OTP"
// binding.tvResendOtp.isEnabled = true
// binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.blue1))
// timeRemaining = 0
// }
// }.start()
// }

View File

@ -104,11 +104,20 @@ class RegisterStep3Fragment : Fragment() {
// Set up button listeners // Set up button listeners
binding.btnPrevious.setOnClickListener { binding.btnPrevious.setOnClickListener {
// Go back to the previous step // Go back to the previous step
parentFragmentManager.popBackStack() // parentFragmentManager.popBackStack()
// (activity as? RegisterActivity)?.navigateToStep(2, null)
// (activity as? RegisterActivity)?.goBackToPreviousStep()
// Option 2: Direct navigation to step 1
val step2Fragment = RegisterStep2Fragment()
parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container, step2Fragment)
.commit()
} }
binding.btnRegister.setOnClickListener { binding.btnRegister.setOnClickListener {
submitAddress() submitAddress()
sessionManager.clearAll()
} }
// If user skips address entry // If user skips address entry
@ -238,7 +247,8 @@ class RegisterStep3Fragment : Fragment() {
binding.autoCompleteKecamatan.setOnItemClickListener{ _, _, position, _ -> binding.autoCompleteKecamatan.setOnItemClickListener{ _, _, position, _ ->
val subdistrictId = subdistrictAdapter.getSubdistrictId(position) val subdistrictId = subdistrictAdapter.getSubdistrictId(position)
Log.d(TAG, "Subdistrict selected at position $position, ID: $subdistrictId") val subdistictName = subdistrictAdapter.getSubdistrictName(position)
Log.d(TAG, "Subdistrict selected at position $position, ID: $subdistrictId, name: $subdistictName")
subdistrictId?.let { id -> subdistrictId?.let { id ->
Log.d(TAG, "Selected subdistrict ID set to: $id") Log.d(TAG, "Selected subdistrict ID set to: $id")
@ -246,6 +256,11 @@ class RegisterStep3Fragment : Fragment() {
registerViewModel.getVillages(id) registerViewModel.getVillages(id)
binding.autoCompleteDesa.text.clear() binding.autoCompleteDesa.text.clear()
} }
subdistictName?.let { name ->
Log.d(TAG, "Selected name subdistrict set to: $name")
registerViewModel.subdistrictName = name
}
} }
binding.autoCompleteDesa.setOnItemClickListener{ _, _, position, _ -> binding.autoCompleteDesa.setOnItemClickListener{ _, _, position, _ ->
@ -375,7 +390,7 @@ class RegisterStep3Fragment : Fragment() {
val provinceId = registerViewModel.selectedProvinceId?.toInt() ?: 0 val provinceId = registerViewModel.selectedProvinceId?.toInt() ?: 0
val cityId = registerViewModel.selectedCityId.toString() val cityId = registerViewModel.selectedCityId.toString()
val subDistrict = registerViewModel.selectedSubdistrict.toString() val subDistrict = registerViewModel.subdistrictName.toString()
// val postalCode = registerViewModel.selectedPostalCode.toString() // val postalCode = registerViewModel.selectedPostalCode.toString()
val villageId = registerViewModel.selectedVillages ?: "" val villageId = registerViewModel.selectedVillages ?: ""
@ -423,7 +438,7 @@ class RegisterStep3Fragment : Fragment() {
val provinceId = registerViewModel.selectedProvinceId val provinceId = registerViewModel.selectedProvinceId
val cityId = registerViewModel.selectedCityId val cityId = registerViewModel.selectedCityId
val subDistrict = registerViewModel.selectedSubdistrict.toString() val subDistrict = registerViewModel.selectedSubdistrict
val postalCode = registerViewModel.selectedPostalCode val postalCode = registerViewModel.selectedPostalCode
Log.d(TAG, "Validating - Street: $street, SubDistrict: $subDistrict, PostalCode: $postalCode") Log.d(TAG, "Validating - Street: $street, SubDistrict: $subDistrict, PostalCode: $postalCode")
@ -462,6 +477,12 @@ class RegisterStep3Fragment : Fragment() {
return false return false
} }
if (subDistrict == null) {
showError("Pilih kota/kabupaten terlebih dahulu")
binding.autoCompleteKecamatan.requestFocus()
return false
}
return true return true
} }
@ -491,7 +512,7 @@ class RegisterStep3Fragment : Fragment() {
private fun showRegistrationSuccess() { private fun showRegistrationSuccess() {
// Now we can show the success message for the overall registration process // Now we can show the success message for the overall registration process
Toast.makeText(requireContext(), "Registration completed successfully!", Toast.LENGTH_LONG).show() Toast.makeText(requireContext(), "Berhasil mendaftarkan akun", Toast.LENGTH_LONG).show()
// Navigate to login screen // Navigate to login screen
startActivity(Intent(requireContext(), LoginActivity::class.java)) startActivity(Intent(requireContext(), LoginActivity::class.java))
@ -509,4 +530,5 @@ class RegisterStep3Fragment : Fragment() {
ViewCompat.setWindowInsetsAnimationCallback(binding.root, null) ViewCompat.setWindowInsetsAnimationCallback(binding.root, null)
_binding = null _binding = null
} }
} }

View File

@ -30,6 +30,8 @@ class CartActivity : AppCompatActivity() {
private lateinit var sessionManager: SessionManager private lateinit var sessionManager: SessionManager
private lateinit var storeAdapter: StoreAdapter private lateinit var storeAdapter: StoreAdapter
private var TAG = "Cart Activity"
private val viewModel: CartViewModel by viewModels { private val viewModel: CartViewModel by viewModels {
BaseViewModelFactory { BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager) val apiService = ApiConfig.getApiService(sessionManager)
@ -39,11 +41,16 @@ class CartActivity : AppCompatActivity() {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager)
binding = ActivityCartBinding.inflate(layoutInflater) binding = ActivityCartBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
sessionManager = SessionManager(this) if (!sessionManager.isLoggedIn()){
apiService = ApiConfig.getApiService(sessionManager) binding.emptyCart.text = "Silahkan masuk terlebih dahulu"
}
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
@ -116,7 +123,7 @@ class CartActivity : AppCompatActivity() {
// Start checkout with the prepared items // Start checkout with the prepared items
startCheckoutWithWholesaleInfo(selectedItems) startCheckoutWithWholesaleInfo(selectedItems)
} else { } else {
Toast.makeText(this, "Please select items from a single store only", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Pilih produk yang sama dengan toko", Toast.LENGTH_SHORT).show()
} }
} }
} else { } else {
@ -135,12 +142,44 @@ class CartActivity : AppCompatActivity() {
} }
private fun startCheckoutWithWholesaleInfo(checkoutItems: List<CartItemCheckoutInfo>) { private fun startCheckoutWithWholesaleInfo(checkoutItems: List<CartItemCheckoutInfo>) {
// Extract cart item IDs and wholesale status val wholesalePriceMap = viewModel.cartItemWholesalePrice.value ?: emptyMap()
val cartItemIds = checkoutItems.map { it.cartItem.cartItemId }
val wholesaleArray = checkoutItems.map { it.isWholesale }.toBooleanArray()
// Start checkout activity with the cart items and wholesale info val updatedItems = checkoutItems.map { info ->
CheckoutActivity.startForCart(this, cartItemIds, wholesaleArray) val wholesalePrice = wholesalePriceMap[info.cartItem.cartItemId]
val updatedCartItem = if (info.isWholesale && wholesalePrice != null) {
// Replace the price with wholesale price
info.cartItem.copy(price = wholesalePrice.toInt())
} else {
info.cartItem
}
// Debug log
Log.d(
TAG,
"cartItemId: ${updatedCartItem.cartItemId}, " +
"isWholesale: ${info.isWholesale}, " +
"wholesalePrice: $wholesalePrice, " +
"finalPrice: ${updatedCartItem.price}"
)
info.copy(cartItem = updatedCartItem)
}
val cartItemIds = updatedItems.map { it.cartItem.cartItemId }
val wholesaleArray = updatedItems.map { it.isWholesale }.toBooleanArray()
// FIX: Pass wholesale prices as IntArray
val wholesalePricesArray = updatedItems.map { info ->
if (info.isWholesale) {
val wholesalePrice = wholesalePriceMap[info.cartItem.cartItemId]
wholesalePrice?.toInt() ?: info.cartItem.price
} else {
info.cartItem.price
}
}.toIntArray()
CheckoutActivity.startForCart(this, cartItemIds, wholesaleArray, wholesalePricesArray)
} }
private fun observeViewModel() { private fun observeViewModel() {
@ -233,7 +272,5 @@ class CartActivity : AppCompatActivity() {
val format = NumberFormat.getCurrencyInstance(Locale("id", "ID")) val format = NumberFormat.getCurrencyInstance(Locale("id", "ID"))
return format.format(amount).replace("Rp", "Rp ") return format.format(amount).replace("Rp", "Rp ")
} }
} }

View File

@ -124,7 +124,7 @@ class ChatActivity : AppCompatActivity() {
if (token.isEmpty()) { if (token.isEmpty()) {
// User not logged in, redirect to login // User not logged in, redirect to login
Toast.makeText(this, "Please login first", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Silahkan masuk terlebih dahulu", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, LoginActivity::class.java)) startActivity(Intent(this, LoginActivity::class.java))
finish() finish()
return return
@ -506,7 +506,7 @@ class ChatActivity : AppCompatActivity() {
} }
startActivity(intent) startActivity(intent)
} catch (e: Exception) { } catch (e: Exception) {
Toast.makeText(this, "Cannot open product details", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Gagal memuat produk", Toast.LENGTH_SHORT).show()
Log.e(TAG, "Error navigating to product detail", e) Log.e(TAG, "Error navigating to product detail", e)
} }
} }
@ -622,7 +622,7 @@ class ChatActivity : AppCompatActivity() {
if (outputFile.exists() && outputFile.length() > 0) { if (outputFile.exists() && outputFile.length() > 0) {
if (outputFile.length() > 5 * 1024 * 1024) { if (outputFile.length() > 5 * 1024 * 1024) {
Log.e(TAG, "File too large: ${outputFile.length()} bytes") Log.e(TAG, "File too large: ${outputFile.length()} bytes")
Toast.makeText(this, "Image too large (max 5MB)", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Gambar terlalu besar. Maksimal 1MB", Toast.LENGTH_SHORT).show()
return return
} }

View File

@ -5,7 +5,6 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
@ -80,8 +79,10 @@ class ChatListFragment : Fragment() {
} }
} }
is Result.Error -> { is Result.Error -> {
binding.tvEmptyChat.visibility = View.VISIBLE // binding.tvEmptyChat.visibility = View.VISIBLE
Toast.makeText(requireContext(), "Failed to load chats", Toast.LENGTH_SHORT).show() binding.progressBarChat.visibility = View.VISIBLE
// Toast.makeText(requireContext(), "Failed to load chats", Toast.LENGTH_SHORT).show()
Log.e(TAG, "Failed to load chats")
} }
Result.Loading -> { Result.Loading -> {
binding.progressBarChat.visibility = View.VISIBLE binding.progressBarChat.visibility = View.VISIBLE

View File

@ -6,6 +6,7 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
@ -32,6 +33,7 @@ import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.setLightStatusBar import com.alya.ecommerce_serang.utils.setLightStatusBar
import com.alya.ecommerce_serang.utils.viewmodel.HomeUiState import com.alya.ecommerce_serang.utils.viewmodel.HomeUiState
import com.alya.ecommerce_serang.utils.viewmodel.HomeViewModel import com.alya.ecommerce_serang.utils.viewmodel.HomeViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
//@AndroidEntryPoint //@AndroidEntryPoint
@ -140,12 +142,13 @@ class HomeFragment : Fragment() {
viewModel.uiState.collect { state -> viewModel.uiState.collect { state ->
when (state) { when (state) {
is HomeUiState.Loading -> { is HomeUiState.Loading -> {
binding.loading.root.isVisible = true binding.loadingAll.root.visibility = View.VISIBLE
binding.error.root.isVisible = false binding.error.root.isVisible = false
binding.home.isVisible = false binding.home.isVisible = false
delay(5000)
} }
is HomeUiState.Success -> { is HomeUiState.Success -> {
binding.loading.root.isVisible = false binding.loadingAll.root.visibility = View.GONE
binding.error.root.isVisible = false binding.error.root.isVisible = false
binding.home.isVisible = true binding.home.isVisible = true
val products = state.products val products = state.products
@ -154,10 +157,12 @@ class HomeFragment : Fragment() {
productAdapter?.updateLimitedProducts(products) productAdapter?.updateLimitedProducts(products)
} }
is HomeUiState.Error -> { is HomeUiState.Error -> {
binding.loading.root.isVisible = false binding.loadingAll.root.visibility = View.GONE
binding.error.root.isVisible = true binding.error.root.isVisible = true
binding.home.isVisible = false binding.home.isVisible = false
binding.error.errorMessage.text = state.message // binding.error.errorMessage.text = state.message
Log.e("HomeFragment", "Error load data: ${state.message}")
Toast.makeText(requireContext(), "Terjadi kendala. Muat ulang halaman", Toast.LENGTH_SHORT) .show()
binding.error.retryButton.setOnClickListener { binding.error.retryButton.setOnClickListener {
viewModel.retry() viewModel.retry()
} }

View File

@ -7,12 +7,15 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.ProductsItem import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.data.repository.ProductRepository
@ -106,6 +109,11 @@ class SearchHomeFragment : Fragment() {
} }
}) })
val searchText = findViewById<TextView>(androidx.appcompat.R.id.search_src_text)
searchText.textSize = 14f // in sp
searchText.setHintTextColor(ContextCompat.getColor(context, R.color.black_200))
searchText.setTextColor(ContextCompat.getColor(context, R.color.black))
if (args.query.isNullOrEmpty()) { if (args.query.isNullOrEmpty()) {
requestFocus() requestFocus()
post { post {

View File

@ -96,18 +96,40 @@ class CheckoutActivity : AppCompatActivity() {
// Process Cart checkout flow // Process Cart checkout flow
val cartItemIds = intent.getIntArrayExtra(EXTRA_CART_ITEM_IDS)?.toList() ?: emptyList() val cartItemIds = intent.getIntArrayExtra(EXTRA_CART_ITEM_IDS)?.toList() ?: emptyList()
val isWholesaleArray = intent.getBooleanArrayExtra(EXTRA_CART_ITEM_WHOLESALE) val isWholesaleArray = intent.getBooleanArrayExtra(EXTRA_CART_ITEM_WHOLESALE)
val wholesalePricesArray = intent.getIntArrayExtra(EXTRA_CART_ITEM_WHOLESALE_PRICES)
if (cartItemIds.isNotEmpty()) { if (cartItemIds.isNotEmpty()) {
// Create a map of cart item IDs to wholesale status if available // Build map of cartItemId -> isWholesale
val wholesaleMap = if (isWholesaleArray != null && isWholesaleArray.size == cartItemIds.size) { val isWholesaleMap = if (isWholesaleArray != null && isWholesaleArray.size == cartItemIds.size) {
cartItemIds.mapIndexed { index, id -> id to isWholesaleArray[index] }.toMap() cartItemIds.mapIndexed { index, id ->
id to isWholesaleArray[index]
}.toMap()
} else { } else {
emptyMap() emptyMap()
} }
viewModel.initializeFromCart(cartItemIds, wholesaleMap) // Build wholesalePriceMap - FIX: Map cartItemId to wholesale price
val wholesalePriceMap = if (wholesalePricesArray != null && wholesalePricesArray.size == cartItemIds.size) {
cartItemIds.mapIndexed { index, id ->
id to wholesalePricesArray[index]
}.toMap()
} else { } else {
Toast.makeText(this, "Error: No cart items specified", Toast.LENGTH_SHORT).show() emptyMap()
}
viewModel.initializeFromCart(
cartItemIds,
isWholesaleMap,
wholesalePriceMap
)
Log.d("CheckoutActivity", "Cart IDs: $cartItemIds")
Log.d("CheckoutActivity", "IsWholesaleArray: ${isWholesaleArray?.joinToString()}")
Log.d("CheckoutActivity", "WholesalePricesArray: ${wholesalePricesArray?.joinToString()}")
Log.d("CheckoutActivity", "IsWholesaleMap: $isWholesaleMap")
Log.d("CheckoutActivity", "WholesalePriceMap: $wholesalePriceMap")
} else {
Toast.makeText(this, "Tidak ada item keranjang", Toast.LENGTH_SHORT).show()
finish() finish()
} }
} }
@ -132,8 +154,18 @@ class CheckoutActivity : AppCompatActivity() {
// Observe address details // Observe address details
viewModel.addressDetails.observe(this) { address -> viewModel.addressDetails.observe(this) { address ->
binding.tvPlacesAddress.text = address?.recipient if (address != null) {
binding.tvAddress.text = "${address?.street}, ${address?.subdistrict}" // Show selected address
binding.containerEmptyAddress.visibility = View.GONE
binding.containerAddress.visibility = View.VISIBLE
binding.tvPlacesAddress.text = address.recipient
binding.tvAddress.text = "${address.street}, ${address.subdistrict}"
} else {
// Show empty address state
binding.containerEmptyAddress.visibility = View.VISIBLE
binding.containerAddress.visibility = View.GONE
}
} }
viewModel.availablePaymentMethods.observe(this) { paymentMethods -> viewModel.availablePaymentMethods.observe(this) { paymentMethods ->
@ -150,9 +182,7 @@ class CheckoutActivity : AppCompatActivity() {
// Update the adapter ONLY if it exists // Update the adapter ONLY if it exists
paymentAdapter?.let { adapter -> paymentAdapter?.let { adapter ->
// This line was causing issues - using setSelectedPayment instead of setSelectedPaymentName
adapter.setSelectedPaymentId(selectedPayment.id) adapter.setSelectedPaymentId(selectedPayment.id)
Log.d("CheckoutActivity", "Updated adapter with selected payment: ${selectedPayment.id}") Log.d("CheckoutActivity", "Updated adapter with selected payment: ${selectedPayment.id}")
} }
} }
@ -161,20 +191,20 @@ class CheckoutActivity : AppCompatActivity() {
// Observe loading state // Observe loading state
viewModel.isLoading.observe(this) { isLoading -> viewModel.isLoading.observe(this) { isLoading ->
binding.btnPay.isEnabled = !isLoading binding.btnPay.isEnabled = !isLoading
} }
// Observe error messages // Observe error messages
viewModel.errorMessage.observe(this) { message -> viewModel.errorMessage.observe(this) { message ->
if (message.isNotEmpty()) { if (message.isNotEmpty()) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show() Toast.makeText(this, "Terdapat kendala di pemesanan", Toast.LENGTH_SHORT).show()
Log.e("CheckoutActivity", "Error from errorMessage: $message")
} }
} }
// Observe order creation // Observe order creation
viewModel.orderCreated.observe(this) { created -> viewModel.orderCreated.observe(this) { created ->
if (created) { if (created) {
Toast.makeText(this, "Order successfully created!", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Berhasil membuat pesanan", Toast.LENGTH_SHORT).show()
setResult(RESULT_OK) setResult(RESULT_OK)
finish() finish()
} }
@ -184,10 +214,17 @@ class CheckoutActivity : AppCompatActivity() {
private fun setupPaymentMethodsRecyclerView(paymentMethods: List<DetailPaymentItem>) { private fun setupPaymentMethodsRecyclerView(paymentMethods: List<DetailPaymentItem>) {
if (paymentMethods.isEmpty()) { if (paymentMethods.isEmpty()) {
Log.e("CheckoutActivity", "Payment methods list is empty") Log.e("CheckoutActivity", "Payment methods list is empty")
Toast.makeText(this, "No payment methods available", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Tidak ditemukan metode pembayaran", Toast.LENGTH_SHORT).show()
// Show empty payment state
binding.containerEmptyPayment.visibility = View.VISIBLE
binding.rvPaymentInfo.visibility = View.GONE
return return
} }
binding.containerEmptyPayment.visibility = View.GONE
binding.rvPaymentInfo.visibility = View.VISIBLE
// Debug logging // Debug logging
Log.d("CheckoutActivity", "Setting up payment methods: ${paymentMethods.size} methods available") Log.d("CheckoutActivity", "Setting up payment methods: ${paymentMethods.size} methods available")
@ -244,6 +281,16 @@ class CheckoutActivity : AppCompatActivity() {
this.adapter = adapter this.adapter = adapter
isNestedScrollingEnabled = false isNestedScrollingEnabled = false
} }
// if (checkoutData.cartItems.isEmpty()) {
// // Show empty products state
// binding.containerEmptyProducts.visibility = View.VISIBLE
// binding.rvProductItems.visibility = View.GONE
// return
// }
binding.containerEmptyProducts.visibility = View.GONE
binding.rvProductItems.visibility = View.VISIBLE
} }
private fun updateOrderSummary() { private fun updateOrderSummary() {
@ -268,7 +315,8 @@ class CheckoutActivity : AppCompatActivity() {
private fun updateShippingUI(shipName: String, shipService: String, shipEtd: String, shipPrice: Int) { private fun updateShippingUI(shipName: String, shipService: String, shipEtd: String, shipPrice: Int) {
if (shipName.isNotEmpty() && shipService.isNotEmpty()) { if (shipName.isNotEmpty() && shipService.isNotEmpty()) {
// Display shipping name and service in one line // Hide empty state and show selected shipping
binding.containerEmptyShipping.visibility = View.GONE
binding.cardShipment.visibility = View.VISIBLE binding.cardShipment.visibility = View.VISIBLE
binding.tvCourierName.text = "$shipName $shipService" binding.tvCourierName.text = "$shipName $shipService"
@ -276,6 +324,8 @@ class CheckoutActivity : AppCompatActivity() {
binding.tvShippingPrice.text = formatCurrency(shipPrice.toDouble()) binding.tvShippingPrice.text = formatCurrency(shipPrice.toDouble())
binding.rbJne.isChecked = true binding.rbJne.isChecked = true
} else { } else {
// Show empty shipping state
binding.containerEmptyShipping.visibility = View.VISIBLE
binding.cardShipment.visibility = View.GONE binding.cardShipment.visibility = View.GONE
} }
} }
@ -288,10 +338,10 @@ class CheckoutActivity : AppCompatActivity() {
} }
// Shipping method selection // Shipping method selection
binding.layoutShippingMethod.setOnClickListener { binding.tvShippingOption.setOnClickListener {
val addressId = viewModel.addressDetails.value?.id ?: 0 val addressId = viewModel.addressDetails.value?.id ?: 0
if (addressId <= 0) { if (addressId <= 0) {
Toast.makeText(this, "Please select delivery address first", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Silahkan pilih alamat dahulu", Toast.LENGTH_SHORT).show()
return@setOnClickListener return@setOnClickListener
} }
@ -341,7 +391,7 @@ class CheckoutActivity : AppCompatActivity() {
viewModel.setSelectedAddress(addressId) viewModel.setSelectedAddress(addressId)
// You might want to show a toast or some UI feedback // You might want to show a toast or some UI feedback
Toast.makeText(this, "Address selected successfully", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Berhasil memilih alamat", Toast.LENGTH_SHORT).show()
} }
} }
} }
@ -416,6 +466,7 @@ class CheckoutActivity : AppCompatActivity() {
const val EXTRA_PRICE = "PRICE" const val EXTRA_PRICE = "PRICE"
const val EXTRA_ISWHOLESALE = "ISWHOLESALE" const val EXTRA_ISWHOLESALE = "ISWHOLESALE"
const val EXTRA_CART_ITEM_WHOLESALE = "EXTRA_CART_ITEM_WHOLESALE" const val EXTRA_CART_ITEM_WHOLESALE = "EXTRA_CART_ITEM_WHOLESALE"
const val EXTRA_CART_ITEM_WHOLESALE_PRICES = "EXTRA_CART_ITEM_WHOLESALE_PRICES"
// Helper methods for starting activity // Helper methods for starting activity
@ -449,13 +500,17 @@ class CheckoutActivity : AppCompatActivity() {
fun startForCart( fun startForCart(
context: Context, context: Context,
cartItemIds: List<Int>, cartItemIds: List<Int>,
isWholesaleArray: BooleanArray? = null isWholesaleArray: BooleanArray? = null,
wholesalePrices: IntArray? = null
) { ) {
val intent = Intent(context, CheckoutActivity::class.java).apply { val intent = Intent(context, CheckoutActivity::class.java).apply {
putExtra(EXTRA_CART_ITEM_IDS, cartItemIds.toIntArray()) putExtra(EXTRA_CART_ITEM_IDS, cartItemIds.toIntArray())
if (isWholesaleArray != null) { if (isWholesaleArray != null) {
putExtra(EXTRA_CART_ITEM_WHOLESALE, isWholesaleArray) putExtra(EXTRA_CART_ITEM_WHOLESALE, isWholesaleArray)
} }
if (wholesalePrices != null) {
putExtra(EXTRA_CART_ITEM_WHOLESALE_PRICES, wholesalePrices)
}
} }
context.startActivity(intent) context.startActivity(intent)
} }

View File

@ -93,30 +93,46 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
} }
// Initialize checkout from cart // Initialize checkout from cart
fun initializeFromCart(cartItemIds: List<Int>, isWholesaleMap: Map<Int, Boolean> = emptyMap()) { fun initializeFromCart(
cartItemIds: List<Int>,
isWholesaleMap: Map<Int, Boolean> = emptyMap(),
wholesalePriceMap: Map<Int, Int> = emptyMap()
) {
viewModelScope.launch { viewModelScope.launch {
_isLoading.value = true _isLoading.value = true
try { try {
// Get cart data
val cartResult = repository.getCart() val cartResult = repository.getCart()
if (cartResult is Result.Success) { if (cartResult is Result.Success) {
// Find matching cart items
val matchingItems = mutableListOf<CartItemsItem>() val matchingItems = mutableListOf<CartItemsItem>()
var storeData: DataItemCart? = null var storeData: DataItemCart? = null
for (store in cartResult.data) { for (store in cartResult.data) {
val storeItems = store.cartItems.filter { it.cartItemId in cartItemIds } val storeItems = store.cartItems.filter { it.cartItemId in cartItemIds }
if (storeItems.isNotEmpty()) { if (storeItems.isNotEmpty()) {
matchingItems.addAll(storeItems) // ✅ Apply wholesale prices - Replace item prices with wholesale prices
val updatedItems = storeItems.map { item ->
val wholesalePrice = wholesalePriceMap[item.cartItemId]
val isWholesale = isWholesaleMap[item.cartItemId] ?: false
// Use wholesale price if item is wholesale and price exists
if (isWholesale && wholesalePrice != null) {
Log.d(TAG, "Applying wholesale price for item ${item.cartItemId}: ${item.price} -> $wholesalePrice")
item.copy(price = wholesalePrice)
} else {
Log.d(TAG, "Using regular price for item ${item.cartItemId}: ${item.price}")
item
}
}
matchingItems.addAll(updatedItems)
storeData = store storeData = store
break break
} }
} }
if (matchingItems.isNotEmpty() && storeData != null) { if (matchingItems.isNotEmpty() && storeData != null) {
// Create initial OrderRequest object
val orderRequest = OrderRequest( val orderRequest = OrderRequest(
addressId = 0, addressId = 0,
paymentMethodId = 0, paymentMethodId = 0,
@ -126,21 +142,26 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
isNego = false, isNego = false,
cartItemId = cartItemIds, cartItemId = cartItemIds,
shipEtd = "", shipEtd = "",
// Add a list tracking which items are wholesale isReseller = isWholesaleMap.any { it.value }
isReseller = isWholesaleMap.any { it.value } // Set true if any item is wholesale
) )
// Create checkout data
_checkoutData.value = CheckoutData( _checkoutData.value = CheckoutData(
orderRequest = orderRequest, orderRequest = orderRequest,
productName = matchingItems.first().productName, productName = matchingItems.first().productName,
sellerName = storeData.storeName, sellerName = storeData.storeName,
sellerId = storeData.storeId, sellerId = storeData.storeId,
isBuyNow = false, isBuyNow = false,
cartItems = matchingItems, cartItems = matchingItems, // These now have updated wholesale prices
cartItemWholesaleMap = isWholesaleMap // Store the wholesale map cartItemWholesaleMap = isWholesaleMap
) )
Log.d(TAG, "CheckoutData initialized with ${matchingItems.size} items")
matchingItems.forEachIndexed { index, item ->
val isWholesale = isWholesaleMap[item.cartItemId] ?: false
Log.d(TAG, "Item $index: ${item.productName}, Price: ${item.price}, IsWholesale: $isWholesale")
}
// Calculate totals with updated prices
calculateSubtotal() calculateSubtotal()
calculateTotal() calculateTotal()
} else { } else {
@ -151,6 +172,7 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
} }
} catch (e: Exception) { } catch (e: Exception) {
_errorMessage.value = "Error: ${e.message}" _errorMessage.value = "Error: ${e.message}"
Log.e(TAG, "Error in initializeFromCart", e)
} finally { } finally {
_isLoading.value = false _isLoading.value = false
} }
@ -405,8 +427,6 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
} }
} }
companion object { companion object {
private const val TAG = "CheckoutViewModel" private const val TAG = "CheckoutViewModel"
} }

View File

@ -67,7 +67,7 @@ class ShippingActivity : AppCompatActivity() {
// Validate required information // Validate required information
if (addressId <= 0 || productId <= 0) { if (addressId <= 0 || productId <= 0) {
Log.e(TAG, "Missing required shipping information: addressId=$addressId, productId=$productId") Log.e(TAG, "Missing required shipping information: addressId=$addressId, productId=$productId")
Toast.makeText(this, "Missing required shipping information", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Gagal memuat pengiriman", Toast.LENGTH_SHORT).show()
finish() finish()
return return
} }

View File

@ -34,6 +34,8 @@ class SingleCartItemAdapter(private val cartItem: CartItemsItem) :
// Load placeholder image // Load placeholder image
Glide.with(ivProduct.context) Glide.with(ivProduct.context)
.load(R.drawable.placeholder_image) .load(R.drawable.placeholder_image)
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
.into(ivProduct) .into(ivProduct)
} }
} }

View File

@ -22,12 +22,14 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
import com.alya.ecommerce_serang.data.api.dto.UserProfile
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictsItem
import com.alya.ecommerce_serang.data.api.response.customer.order.VillagesItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.OrderRepository import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityAddAddressBinding import com.alya.ecommerce_serang.databinding.ActivityAddAddressBinding
import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory
@ -37,8 +39,8 @@ class AddAddressActivity : AppCompatActivity() {
private lateinit var binding: ActivityAddAddressBinding private lateinit var binding: ActivityAddAddressBinding
private lateinit var apiService: ApiService private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager private lateinit var sessionManager: SessionManager
private var profileUser: Int = 1
private lateinit var locationManager: LocationManager private lateinit var locationManager: LocationManager
private var profileUserId: Int? = null
private var isRequestingLocation = false private var isRequestingLocation = false
@ -46,6 +48,8 @@ class AddAddressActivity : AppCompatActivity() {
private var longitude: Double? = null private var longitude: Double? = null
private val provinceAdapter by lazy { ProvinceAdapter(this) } private val provinceAdapter by lazy { ProvinceAdapter(this) }
private val cityAdapter by lazy { CityAdapter(this) } private val cityAdapter by lazy { CityAdapter(this) }
private val subdistrictAdapter by lazy { SubdsitrictAdapter(this)}
private val villageAdapter by lazy { VillagesAdapter(this)}
private val viewModel: AddAddressViewModel by viewModels { private val viewModel: AddAddressViewModel by viewModels {
SavedStateViewModelFactory(this) { savedStateHandle -> SavedStateViewModelFactory(this) { savedStateHandle ->
@ -80,11 +84,15 @@ class AddAddressActivity : AppCompatActivity() {
) )
windowInsets windowInsets
} }
viewModel.loadUserProfile()
// Get user profile from session manager
// profileUser =UserProfile.
viewModel.userProfile.observe(this) { user -> viewModel.userProfile.observe(this) { user ->
user?.let { updateProfile(it) } if (user != null) {
profileUserId = user.userId
Log.d(TAG, "Fetched userId = $profileUserId") // ✅ debug log
} else {
Log.e(TAG, "Error get profile")
}
} }
setupToolbar() setupToolbar()
@ -94,16 +102,10 @@ class AddAddressActivity : AppCompatActivity() {
setupButtonListeners() setupButtonListeners()
setupObservers() setupObservers()
// Force trigger province loading to ensure it happens // Force trigger province loading to ensure it happens
viewModel.getProvinces() viewModel.getProvinces()
} }
private fun updateProfile(userProfile: UserProfile){
profileUser = userProfile.userId
}
// UI setup methods // UI setup methods
private fun setupToolbar() { private fun setupToolbar() {
binding.toolbar.setNavigationOnClickListener { binding.toolbar.setNavigationOnClickListener {
@ -116,6 +118,8 @@ class AddAddressActivity : AppCompatActivity() {
// Set adapters // Set adapters
binding.autoCompleteProvinsi.setAdapter(provinceAdapter) binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
binding.autoCompleteKabupaten.setAdapter(cityAdapter) binding.autoCompleteKabupaten.setAdapter(cityAdapter)
binding.autoCompleteKecamatan.setAdapter(subdistrictAdapter)
binding.autoCompleteDesa.setAdapter(villageAdapter)
// Make dropdown appear on click (not just when typing) // Make dropdown appear on click (not just when typing)
binding.autoCompleteProvinsi.setOnClickListener { binding.autoCompleteProvinsi.setOnClickListener {
@ -134,6 +138,26 @@ class AddAddressActivity : AppCompatActivity() {
} }
} }
binding.autoCompleteKecamatan.setOnClickListener{
if (subdistrictAdapter.count > 0){
Log.d(TAG, "Subdistrict clicked, dropdown with ${subdistrictAdapter.count} items")
binding.autoCompleteKecamatan.showDropDown()
} else {
Log.d(TAG, "No kecamatan available")
Toast.makeText(this, "Pilih Kabupaten / Kota terlebih dahulu", Toast.LENGTH_SHORT).show()
}
}
binding.autoCompleteDesa.setOnClickListener{
if (villageAdapter.count > 0){
Log.d(TAG, "Village clicked, dropdown with ${villageAdapter.count} items")
binding.autoCompleteDesa.showDropDown()
} else {
Log.d(TAG, "No desa available")
Toast.makeText(this, "Pilih Kecamatan terlebih dahulu", Toast.LENGTH_SHORT).show()
}
}
// Set listeners for selection // Set listeners for selection
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ -> binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
val provinceId = provinceAdapter.getProvinceId(position) val provinceId = provinceAdapter.getProvinceId(position)
@ -152,9 +176,41 @@ class AddAddressActivity : AppCompatActivity() {
cityId?.let { id -> cityId?.let { id ->
Log.d(TAG, "Setting selectedCityId=$id") Log.d(TAG, "Setting selectedCityId=$id")
viewModel.getSubdistrict(cityId)
viewModel.selectedCityId = id viewModel.selectedCityId = id
binding.autoCompleteKecamatan.text.clear()
} ?: Log.e(TAG, "Could not get cityId for position $position") } ?: Log.e(TAG, "Could not get cityId for position $position")
} }
binding.autoCompleteKecamatan.setOnItemClickListener { _, _, position, _ ->
val subdistrictId = subdistrictAdapter.getSubdistrictId(position)
val subdistrictName = subdistrictAdapter.getSubdistrictName(position)
Log.d(TAG, "Subdistrict selected at position $position, subsId=$subdistrictId")
subdistrictId?.let { id ->
Log.d(TAG, "Setting subdistrict id=$id")
viewModel.getVillages(subdistrictId)
viewModel.selectedSubdistrictId = id
binding.autoCompleteDesa.text.clear()
} ?: Log.e(TAG, "Could not get subsId for position $position")
subdistrictName?.let { name ->
Log.d(TAG, "Setting subdistrict=$name")
viewModel.selectedSubdistrict = name
} ?: Log.e(TAG, "COuldnt get subs name for position ${position}")
}
binding.autoCompleteDesa.setOnItemClickListener { _, _, position, _ ->
val villageId = villageAdapter.getVillageId(position)
Log.d(TAG, "Village selected at position $position, villageId=$villageId")
villageId?.let { id ->
Log.d(TAG, "Setting village=$id")
viewModel.selectedVillages = id
} ?: Log.e(TAG, "Could not get villageId for position $position")
}
} }
private fun setupButtonListeners() { private fun setupButtonListeners() {
@ -178,6 +234,16 @@ class AddAddressActivity : AppCompatActivity() {
handleCityState(state) handleCityState(state)
} }
viewModel.subdistrictState.observe(this) {state ->
Log.d(TAG, "Received subdistrictId update: $state")
handleSubdistrictState(state)
}
viewModel.villagesState.observe(this) {state ->
Log.d(TAG, "Received subdistrictId update: $state")
handleVillageState(state)
}
// Observe address submission // Observe address submission
viewModel.addressSubmissionState.observe(this) { state -> viewModel.addressSubmissionState.observe(this) { state ->
Log.d(TAG, "Received addressSubmissionState update: $state") Log.d(TAG, "Received addressSubmissionState update: $state")
@ -202,7 +268,7 @@ class AddAddressActivity : AppCompatActivity() {
} }
is ViewState.Error -> { is ViewState.Error -> {
// Hide loading indicator // Hide loading indicator
showError("Failed to load provinces: ${state.message}") // showError("Failed to load provinces: ${state.message}")
Log.e("AddAddressActivity", "Province error: ${state.message}") Log.e("AddAddressActivity", "Province error: ${state.message}")
} }
} }
@ -221,12 +287,50 @@ class AddAddressActivity : AppCompatActivity() {
} }
is ViewState.Error -> { is ViewState.Error -> {
binding.cityProgressBar.visibility = View.GONE binding.cityProgressBar.visibility = View.GONE
showError("Failed to load cities: ${state.message}") // showError("Failed to load cities: ${state.message}")
Log.e("AddAddressActivity", "City error: ${state.message}") Log.e("AddAddressActivity", "City error: ${state.message}")
} }
} }
} }
private fun handleSubdistrictState(state: com.alya.ecommerce_serang.data.repository.Result<List<SubdistrictsItem>>) {
when (state) {
is Result.Loading -> {
Log.d(TAG, "Loading subdistrict...")
binding.subdistrictProgressBar.visibility = View.VISIBLE
}
is Result.Success -> {
Log.d(TAG, "Subdistrict loaded: ${state.data.size}")
binding.subdistrictProgressBar.visibility = View.GONE
subdistrictAdapter.updateData(state.data)
}
is Result.Error -> {
binding.subdistrictProgressBar.visibility = View.GONE
// showError("Failed to load subs: ${state.message}")
Log.e(TAG, "Subdistrict error: ${state}")
}
}
}
private fun handleVillageState(state: Result<List<VillagesItem>>) {
when (state) {
is Result.Loading -> {
Log.d(TAG, "Loading villages...")
binding.villageProgressBar.visibility = View.VISIBLE
}
is Result.Success -> {
Log.d(TAG, "Villages loaded: ${state.data.size}")
binding.villageProgressBar.visibility = View.GONE
villageAdapter.updateData(state.data)
}
is Result.Error -> {
binding.villageProgressBar.visibility = View.GONE
// showError("Failed to load subs: ${state.message}")
Log.e(TAG, "Village error: ${state}")
}
}
}
private fun handleAddressSubmissionState(state: ViewState<String>) { private fun handleAddressSubmissionState(state: ViewState<String>) {
when (state) { when (state) {
is ViewState.Loading -> { is ViewState.Loading -> {
@ -276,23 +380,20 @@ class AddAddressActivity : AppCompatActivity() {
} }
val street = binding.etDetailAlamat.text.toString().trim() val street = binding.etDetailAlamat.text.toString().trim()
val subDistrict = binding.etKecamatan.text.toString().trim() // val subDistrict = binding.etKecamatan.text.toString().trim()
val postalCode = binding.etKodePos.text.toString().trim() val postalCode = binding.etKodePos.text.toString().trim()
val recipient = binding.etNamaPenerima.text.toString().trim() val recipient = binding.etNamaPenerima.text.toString().trim()
val phone = binding.etNomorHp.text.toString().trim() val phone = binding.etNomorHp.text.toString().trim()
val userId = try { val userId = profileUserId
profileUser
} catch (e: Exception) {
Log.w(TAG, "Error getting userId, using default", e)
1 // Default userId for testing
}
val isStoreLocation = false val isStoreLocation = false
val provinceId = viewModel.selectedProvinceId val provinceId = viewModel.selectedProvinceId
val cityId = viewModel.selectedCityId.toString() val cityId = viewModel.selectedCityId.toString()
val subDistrict = viewModel.selectedSubdistrict.toString()
val villageId = viewModel.selectedVillages.toString()
Log.d(TAG, "Form data: street=$street, subDistrict=$subDistrict, postalCode=$postalCode, " + Log.d(TAG, "Form data: street=$street, subDistrict=$subDistrict, postalCode=$postalCode, " +
"recipient=$recipient, phone=$phone, userId=$userId, provinceId=$provinceId, cityId=$cityId, " + "recipient=$recipient, phone=$phone, userId=$userId, provinceId=$provinceId, cityId=$cityId, subdistrict=$subDistrict, villageId=$villageId " +
"lat=$latitude, long=$longitude") "lat=$latitude, long=$longitude")
// Validate required fields // Validate required fields
@ -333,7 +434,7 @@ class AddAddressActivity : AppCompatActivity() {
// Create request with all fields // Create request with all fields
val request = CreateAddressRequest( val request = CreateAddressRequest(
userId = userId, userId = userId!!,
lat = latitude!!, // Safe to use !! as we've checked above lat = latitude!!, // Safe to use !! as we've checked above
long = longitude!!, long = longitude!!,
street = street, street = street,
@ -341,7 +442,7 @@ class AddAddressActivity : AppCompatActivity() {
cityId = cityId, // ⚠️ Make sure this is Int cityId = cityId, // ⚠️ Make sure this is Int
provId = provinceId, provId = provinceId,
postCode = postalCode, postCode = postalCode,
idVillage = "", // Or provide a real ID if needed idVillage = villageId, // Or provide a real ID if needed
detailAddress = street, detailAddress = street,
isStoreLocation = false, isStoreLocation = false,
recipient = recipient, recipient = recipient,
@ -389,8 +490,8 @@ class AddAddressActivity : AppCompatActivity() {
binding.locationProgressBar.visibility = View.GONE binding.locationProgressBar.visibility = View.GONE
binding.tvLocationStatus.text = "Provider lokasi tidak tersedia" binding.tvLocationStatus.text = "Provider lokasi tidak tersedia"
isRequestingLocation = false isRequestingLocation = false
Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show()
showEnableLocationDialog() // showEnableLocationDialog()
return return
} }
@ -417,7 +518,7 @@ class AddAddressActivity : AppCompatActivity() {
latitude = -6.200000 latitude = -6.200000
longitude = 106.816666 longitude = 106.816666
isRequestingLocation = false isRequestingLocation = false
Toast.makeText(this, "Timeout lokasi, menggunakan lokasi default", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Timeout lokasi, menggunakan lokasi default", Toast.LENGTH_SHORT).show()
} }
}, 60000) // 15 seconds timeout }, 60000) // 15 seconds timeout
@ -431,7 +532,7 @@ class AddAddressActivity : AppCompatActivity() {
binding.locationProgressBar.visibility = View.GONE binding.locationProgressBar.visibility = View.GONE
binding.tvLocationStatus.text = "Lokasi terdeteksi: ${lastLocation.latitude}, ${lastLocation.longitude}" binding.tvLocationStatus.text = "Lokasi terdeteksi: ${lastLocation.latitude}, ${lastLocation.longitude}"
isRequestingLocation = false isRequestingLocation = false
Toast.makeText(this, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show()
return return
} else { } else {
Log.d(TAG, "No last known location, requesting updates") Log.d(TAG, "No last known location, requesting updates")
@ -449,7 +550,7 @@ class AddAddressActivity : AppCompatActivity() {
binding.locationProgressBar.visibility = View.GONE binding.locationProgressBar.visibility = View.GONE
binding.tvLocationStatus.text = "Lokasi terdeteksi: ${location.latitude}, ${location.longitude}" binding.tvLocationStatus.text = "Lokasi terdeteksi: ${location.latitude}, ${location.longitude}"
isRequestingLocation = false isRequestingLocation = false
Toast.makeText(this@AddAddressActivity, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show() // Toast.makeText(this@AddAddressActivity, "Lokasi terdeteksi", Toast.LENGTH_SHORT).show()
// Remove location updates after receiving a location // Remove location updates after receiving a location
try { try {
@ -472,7 +573,7 @@ class AddAddressActivity : AppCompatActivity() {
binding.locationProgressBar.visibility = View.GONE binding.locationProgressBar.visibility = View.GONE
binding.tvLocationStatus.text = "Provider lokasi dimatikan" binding.tvLocationStatus.text = "Provider lokasi dimatikan"
isRequestingLocation = false isRequestingLocation = false
Toast.makeText(this@AddAddressActivity, "Provider $provider dimatikan", Toast.LENGTH_SHORT).show() // Toast.makeText(this@AddAddressActivity, "Provider $provider dimatikan", Toast.LENGTH_SHORT).show()
} }
} }
@ -491,7 +592,7 @@ class AddAddressActivity : AppCompatActivity() {
binding.locationProgressBar.visibility = View.GONE binding.locationProgressBar.visibility = View.GONE
binding.tvLocationStatus.text = "Error: ${e.message}" binding.tvLocationStatus.text = "Error: ${e.message}"
isRequestingLocation = false isRequestingLocation = false
Toast.makeText(this, "Error mendapatkan lokasi: ${e.message}", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Error mendapatkan lokasi: ${e.message}", Toast.LENGTH_SHORT).show()
// Set default location // Set default location
latitude = -6.200000 latitude = -6.200000
@ -519,12 +620,12 @@ class AddAddressActivity : AppCompatActivity() {
// Add button to reload location (add this button to your layout) // Add button to reload location (add this button to your layout)
binding.btnReloadLocation.setOnClickListener { binding.btnReloadLocation.setOnClickListener {
Log.d(TAG, "Reload location button clicked") Log.d(TAG, "Reload location button clicked")
Toast.makeText(this, "Memuat ulang lokasi...", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Memuat ulang lokasi...", Toast.LENGTH_SHORT).show()
requestLocation() requestLocation()
} }
} }
companion object { companion object {
private const val TAG = "AddAddressViewModel" private const val TAG = "AddAddressActivity"
} }
} }

View File

@ -10,6 +10,9 @@ import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
import com.alya.ecommerce_serang.data.api.dto.UserProfile import com.alya.ecommerce_serang.data.api.dto.UserProfile
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictsItem
import com.alya.ecommerce_serang.data.api.response.customer.order.VillagesItem
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressDetail
import com.alya.ecommerce_serang.data.repository.OrderRepository import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository import com.alya.ecommerce_serang.data.repository.UserRepository
@ -31,6 +34,18 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
private val _citiesState = MutableLiveData<ViewState<List<CitiesItem>>>() private val _citiesState = MutableLiveData<ViewState<List<CitiesItem>>>()
val citiesState: LiveData<ViewState<List<CitiesItem>>> = _citiesState val citiesState: LiveData<ViewState<List<CitiesItem>>> = _citiesState
private val _subdistrictState = MutableLiveData<Result<List<SubdistrictsItem>>>()
val subdistrictState: LiveData<Result<List<SubdistrictsItem>>> = _subdistrictState
private val _villagesState = MutableLiveData<Result<List<VillagesItem>>>()
val villagesState: LiveData<Result<List<VillagesItem>>> = _villagesState
private val _userAddress = MutableLiveData<AddressDetail>()
val userAddress: LiveData<AddressDetail> = _userAddress
private val _editAddress = MutableLiveData<Boolean>()
val editAddress: LiveData<Boolean> get() = _editAddress
// Stored in SavedStateHandle for configuration changes // Stored in SavedStateHandle for configuration changes
var selectedProvinceId: Int? var selectedProvinceId: Int?
get() = savedStateHandle.get<Int>("selectedProvinceId") get() = savedStateHandle.get<Int>("selectedProvinceId")
@ -40,6 +55,10 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
get() = savedStateHandle.get<String>("selectedCityId") get() = savedStateHandle.get<String>("selectedCityId")
set(value) { savedStateHandle["selectedCityId"] = value } set(value) { savedStateHandle["selectedCityId"] = value }
var selectedSubdistrict: String? = null
var selectedSubdistrictId: String? = null
var selectedVillages: String? = null
init { init {
// Load provinces on initialization // Load provinces on initialization
getProvinces() getProvinces()
@ -125,20 +144,133 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
} }
} }
fun getSubdistrict(cityId: String) {
_subdistrictState.value = Result.Loading
viewModelScope.launch {
try {
selectedSubdistrictId = cityId
val result = repository.getListSubdistrict(cityId)
result?.let {
_subdistrictState.postValue(Result.Success(it.subdistricts))
Log.d(TAG, "Subdistrict loaded for city $cityId: ${it.subdistricts.size}")
} ?: run {
_subdistrictState.postValue(Result.Error(Exception("Failed to load cities")))
Log.e(TAG, "Subdistrict result was null for city $cityId")
}
} catch (e: Exception) {
_subdistrictState.postValue(Result.Error(Exception(e.message ?: "Error loading cities")))
Log.e(TAG, "Error fetching subdistrict for city $cityId", e)
}
}
}
fun getVillages(subdistrictId: String) {
_villagesState.value = Result.Loading
viewModelScope.launch {
try {
selectedVillages = subdistrictId
val result = repository.getListVillages(subdistrictId)
result?.let {
_villagesState.postValue(Result.Success(it.villages))
Log.d(TAG, "Villages loaded for subdistrict $subdistrictId: ${it.villages.size}")
} ?: run {
_villagesState.postValue(Result.Error(Exception("Failed to load cities")))
Log.e(TAG, "Village result was null for subdistrict $subdistrictId")
}
} catch (e: Exception) {
_villagesState.postValue(Result.Error(Exception(e.message ?: "Error loading cities")))
Log.e(TAG, "Error fetching villages for subdistrict $subdistrictId", e)
}
}
}
fun setSelectedProvinceId(id: Int) { fun setSelectedProvinceId(id: Int) {
selectedProvinceId = id selectedProvinceId = id
} }
fun updateSelectedCityId(id: String) { fun detailAddress(addressId: Int){
selectedCityId = id viewModelScope.launch {
try {
val response = repository.getAddressDetail(addressId)
if (response != null){
_userAddress.value = response.address
} else {
Log.e(TAG, "Failed load address detail")
}
} catch (e:Exception){
Log.e(TAG, "Error fetching address detail: $e", e)
}
}
}
fun updateAddress(oldAddress: AddressDetail, newAddress: AddressDetail) {
val params = buildUpdateBody(oldAddress, newAddress)
if (params.isEmpty()) {
Log.d(TAG, "No changes detected")
_editAddress.value = false
return
}
viewModelScope.launch {
try {
val response = repository.updateAddress(oldAddress.id, params)
_editAddress.value = response.isSuccessful
} catch (e: Exception) {
Log.e(TAG, "Error: ${e.message}")
_editAddress.value = false
}
}
}
private fun buildUpdateBody(oldAddress: AddressDetail, newAddress: AddressDetail): Map<String, Any> {
val params = mutableMapOf<String, Any>()
fun addIfChanged(key: String, oldValue: Any?, newValue: Any?) {
if (newValue != null && newValue != oldValue) {
params[key] = newValue
}
}
addIfChanged("street", oldAddress.street, newAddress.street)
addIfChanged("province_id", oldAddress.provinceId, newAddress.provinceId)
addIfChanged("detail", oldAddress.detail, newAddress.detail)
addIfChanged("subdistrict", oldAddress.subdistrict, newAddress.subdistrict)
addIfChanged("city_id", oldAddress.cityId, newAddress.cityId)
addIfChanged("village_id", oldAddress.villageId, newAddress.villageId)
addIfChanged("postal_code", oldAddress.postalCode, newAddress.postalCode)
addIfChanged("phone", oldAddress.phone, newAddress.phone)
addIfChanged("recipient", oldAddress.recipient, newAddress.recipient)
addIfChanged("latitude", oldAddress.latitude, newAddress.latitude)
addIfChanged("longitude", oldAddress.longitude, newAddress.longitude)
addIfChanged("is_store_location", oldAddress.isStoreLocation, newAddress.isStoreLocation)
addIfChanged("village_name", oldAddress.villageName, newAddress.villageName)
addIfChanged("subdsitrict_id", oldAddress.subdistrictId, newAddress.subdistrictId)
addIfChanged("id", oldAddress.id, newAddress.id)
addIfChanged("user_id", oldAddress.userId, newAddress.userId)
addIfChanged("city_name", oldAddress.cityName, newAddress.cityName)
addIfChanged("province_name", oldAddress.provinceName, newAddress.provinceName)
return params
} }
fun loadUserProfile() { fun loadUserProfile() {
viewModelScope.launch { viewModelScope.launch {
when (val result = userRepo.fetchUserProfile()){ when (val result = repository.fetchUserProfile()) {
is Result.Success -> _userProfile.postValue(result.data) is Result.Success -> {
is Result.Error -> _errorMessageUser.postValue(result.exception.message ?: "Unknown Error") result.data?.let {
is Result.Loading -> null _userProfile.postValue(it) // send UserProfile to LiveData
} ?: _errorMessageUser.postValue("User data not found")
}
is Result.Error -> {
_errorMessageUser.postValue(result.exception.message ?: "Unknown error")
}
is Result.Loading -> {
null
}
} }
} }
} }

View File

@ -52,12 +52,12 @@ class AddressActivity : AppCompatActivity() {
windowInsets windowInsets
} }
viewModel.fetchAddresses()
setupToolbar() setupToolbar()
setupRecyclerView() setupRecyclerView()
setupObservers() setupObservers()
viewModel.fetchAddresses()
} }
@ -74,13 +74,17 @@ class AddressActivity : AppCompatActivity() {
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {
adapter = AddressAdapter { address -> adapter = AddressAdapter(
// Select the address in the ViewModel onAddressClick = { address ->
viewModel.selectAddress(address.id) viewModel.selectAddress(address.id)
// Return immediately with the selected address
returnResultAndFinish(address.id) returnResultAndFinish(address.id)
},
onEditClick = { address ->
val intent = Intent(this, EditAddressActivity::class.java)
intent.putExtra(EditAddressActivity.EXTRA_ADDRESS_ID, address.id)
startActivity(intent)
} }
)
binding.rvSellerOrder.apply { binding.rvSellerOrder.apply {
layoutManager = LinearLayoutManager(this@AddressActivity) layoutManager = LinearLayoutManager(this@AddressActivity)
@ -119,6 +123,12 @@ class AddressActivity : AppCompatActivity() {
val intent = Intent() val intent = Intent()
intent.putExtra(EXTRA_ADDRESS_ID, addressId) intent.putExtra(EXTRA_ADDRESS_ID, addressId)
setResult(RESULT_OK, intent) setResult(RESULT_OK, intent)
finish()
}
override fun onResume() {
super.onResume()
viewModel.fetchAddresses()
} }
companion object { companion object {

View File

@ -3,6 +3,7 @@ package com.alya.ecommerce_serang.ui.order.address
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -13,7 +14,8 @@ import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressesIte
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
class AddressAdapter( class AddressAdapter(
private val onAddressClick: (AddressesItem) -> Unit private val onAddressClick: (AddressesItem) -> Unit,
private val onEditClick: (AddressesItem) -> Unit
) : ListAdapter<AddressesItem, AddressAdapter.AddressViewHolder>(DIFF_CALLBACK) { ) : ListAdapter<AddressesItem, AddressAdapter.AddressViewHolder>(DIFF_CALLBACK) {
private var selectedAddressId: Int? = null private var selectedAddressId: Int? = null
@ -47,18 +49,21 @@ class AddressAdapter(
// Pass the whole address object to provide more context // Pass the whole address object to provide more context
onAddressClick(address) onAddressClick(address)
} }
holder.editButton.setOnClickListener {
onEditClick(address)
}
} }
class AddressViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class AddressViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvName: TextView = itemView.findViewById(R.id.tv_name_address) private val tvName: TextView = itemView.findViewById(R.id.tv_name_address)
private val tvDetail: TextView = itemView.findViewById(R.id.tv_detail_address) private val tvDetail: TextView = itemView.findViewById(R.id.tv_detail_address)
val editButton: ImageView = itemView.findViewById(R.id.iv_edit)
private val card: MaterialCardView = itemView as MaterialCardView private val card: MaterialCardView = itemView as MaterialCardView
fun bind(address: AddressesItem, isSelected: Boolean) { fun bind(address: AddressesItem, isSelected: Boolean) {
tvName.text = address.recipient tvName.text = address.recipient
tvDetail.text = "${address.street}, ${address.subdistrict}, ${address.phone}" tvDetail.text = "${address.street}, ${address.subdistrict}, ${address.phone}"
// Make selection more visible
card.strokeWidth = if (isSelected) 3 else 0 card.strokeWidth = if (isSelected) 3 else 0
card.strokeColor = if (isSelected) card.strokeColor = if (isSelected)
ContextCompat.getColor(itemView.context, R.color.blue_400) ContextCompat.getColor(itemView.context, R.color.blue_400)

View File

@ -21,6 +21,8 @@ class AddressViewModel(private val repository: OrderRepository): ViewModel() {
val response = repository.getAddress() val response = repository.getAddress()
response?.let { response?.let {
_addresses.value = it.addresses _addresses.value = it.addresses
?.filter { address -> address.isStoreLocation == false }
?: emptyList()
} }
} }
} }

View File

@ -1,21 +1,458 @@
package com.alya.ecommerce_serang.ui.order.address package com.alya.ecommerce_serang.ui.order.address
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictsItem
import com.alya.ecommerce_serang.data.api.response.customer.order.VillagesItem
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressDetail
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityEditAddressBinding
import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
class EditAddressActivity : AppCompatActivity() { class EditAddressActivity : AppCompatActivity() {
private lateinit var binding: ActivityEditAddressBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
private var latitude: Double? = null
private var longitude: Double? = null
private var addressId: Int = -1
private var currentAddress: AddressDetail? = null
private val provinceAdapter by lazy { ProvinceAdapter(this) }
private val cityAdapter by lazy { CityAdapter(this) }
private val subdistrictAdapter by lazy { SubdsitrictAdapter(this)}
private val villageAdapter by lazy { VillagesAdapter(this)}
private var provincesList = mutableListOf<ProvincesItem>()
private var citiesList = mutableListOf<CitiesItem>()
private var subdistrictsList = mutableListOf<SubdistrictsItem>()
private var villagesList = mutableListOf<VillagesItem>()
private val viewModel: AddAddressViewModel by viewModels {
SavedStateViewModelFactory(this) { savedStateHandle ->
val apiService = ApiConfig.getApiService(sessionManager)
val orderRepository = OrderRepository(apiService)
val userRepository = UserRepository(apiService)
AddAddressViewModel(orderRepository, userRepository, savedStateHandle)
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityEditAddressBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager)
WindowCompat.setDecorFitsSystemWindows(window, false)
enableEdgeToEdge() enableEdgeToEdge()
setContentView(R.layout.activity_edit_address)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> // Apply insets to your root layout
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
insets view.setPadding(
systemBars.left,
systemBars.top,
systemBars.right,
systemBars.bottom
)
windowInsets
}
addressId = intent.getIntExtra(EXTRA_ADDRESS_ID, -1)
if (addressId == -1) {
Toast.makeText(this, "Gagal mendapatkan alamat pengguna", Toast.LENGTH_SHORT).show()
finish()
return
}
setupToolbar()
setupAdapters()
setupObservers()
setupListeners()
// Load address detail first
Log.d(TAG, "Loading address with ID: $addressId")
viewModel.detailAddress(addressId)
}
private fun setupToolbar() {
binding.toolbar.setNavigationOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
}
private fun setupAdapters() {
// Set custom adapters to AutoCompleteTextViews
binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
binding.autoCompleteKabupaten.setAdapter(cityAdapter)
binding.autoCompleteKecamatan.setAdapter(subdistrictAdapter)
binding.autoCompleteDesa.setAdapter(villageAdapter)
}
private fun setupObservers() {
// Observe address detail
viewModel.userAddress.observe(this) { address ->
currentAddress = address
Log.d(TAG, "Address loaded: $address")
populateAddressData(address)
}
// Observe provinces
viewModel.provincesState.observe(this) { viewState ->
when (viewState) {
is ViewState.Loading -> {
binding.progressBar.visibility = View.VISIBLE
}
is ViewState.Success -> {
binding.progressBar.visibility = View.GONE
provincesList.clear()
provincesList.addAll(viewState.data)
provinceAdapter.updateData(viewState.data)
// Set selected province if address is loaded
currentAddress?.let { address ->
setSelectedProvince(address.provinceId.toInt())
}
}
is ViewState.Error -> {
binding.progressBar.visibility = View.GONE
Log.e(TAG, "Failed to load province ${viewState.message}")
} }
} }
} }
// Observe cities
viewModel.citiesState.observe(this) { viewState ->
when (viewState) {
is ViewState.Loading -> {
binding.cityProgressBar.visibility = View.VISIBLE
}
is ViewState.Success -> {
binding.cityProgressBar.visibility = View.GONE
citiesList.clear()
citiesList.addAll(viewState.data)
cityAdapter.updateData(viewState.data)
// Set selected city if address is loaded
currentAddress?.let { address ->
setSelectedCity(address.cityId)
}
}
is ViewState.Error -> {
binding.cityProgressBar.visibility = View.GONE
Log.e(TAG, "Failed to load cities ${viewState.message}")
}
}
}
// Observe subdistricts
viewModel.subdistrictState.observe(this) { result ->
when (result) {
is com.alya.ecommerce_serang.data.repository.Result.Loading -> {
// You can add loading indicator for subdistrict if needed
}
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
subdistrictsList.clear()
subdistrictsList.addAll(result.data)
subdistrictAdapter.updateData(result.data)
// Set selected subdistrict if address is loaded
currentAddress?.let { address ->
setSelectedSubdistrict(address.subdistrict)
}
}
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
Log.e(TAG, "Failed to load subdistricy")
}
}
}
// Observe villages
viewModel.villagesState.observe(this) { result ->
when (result) {
is com.alya.ecommerce_serang.data.repository.Result.Loading -> {
// You can add loading indicator for village if needed
}
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
villagesList.clear()
villagesList.addAll(result.data)
villageAdapter.updateData(result.data)
// Set selected village if address is loaded
currentAddress?.let { address ->
setSelectedVillage(address.villageId)
}
}
is Result.Error -> {
Log.e(TAG, "Failed to load villages")
}
}
}
// Observe update result
viewModel.editAddress.observe(this) { isSuccess ->
binding.submitProgressBar.visibility = View.GONE
binding.buttonSimpan.isEnabled = true
if (isSuccess) {
Log.d(TAG, "Address updated successfully")
finish()
} else {
Log.d(TAG, "Failed to update address")
}
}
}
private fun setupListeners() {
// Province selection
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
val provinceId = provinceAdapter.getProvinceId(position)
provinceId?.let { id ->
viewModel.getCities(id)
// Clear dependent dropdowns
clearCitySelection()
clearSubdistrictSelection()
clearVillageSelection()
}
}
// City selection
binding.autoCompleteKabupaten.setOnItemClickListener { _, _, position, _ ->
val cityId = cityAdapter.getCityId(position)
cityId?.let { id ->
viewModel.getSubdistrict(id)
// Clear dependent dropdowns
clearSubdistrictSelection()
clearVillageSelection()
}
}
// Subdistrict selection
binding.autoCompleteKecamatan.setOnItemClickListener { _, _, position, _ ->
val subdistrictId = subdistrictAdapter.getSubdistrictId(position)
subdistrictId?.let { id ->
viewModel.getVillages(id)
// Clear dependent dropdowns
clearVillageSelection()
}
}
// Village selection - auto-populate postal code
binding.autoCompleteDesa.setOnItemClickListener { _, _, position, _ ->
// val postalCode = villageAdapter.getPostalCode(position)
// postalCode?.let {
// binding.etKodePos.setText(it)
// }
}
// Save button
binding.buttonSimpan.setOnClickListener {
saveAddress()
}
}
private fun populateAddressData(address: AddressDetail) {
binding.etNamaPenerima.setText(address.recipient ?: "")
binding.etNomorHp.setText(address.phone ?: "")
binding.etDetailAlamat.setText(address.detail ?: "")
binding.etKodePos.setText(address.postalCode ?: "")
// Province will be set when provinces are loaded
// City, subdistrict, village will be set in their respective observers
}
private fun setSelectedProvince(provinceId: Int?) {
provinceId?.let { id ->
val position = provincesList.indexOfFirst { it.provinceId?.toIntOrNull() == id }
if (position >= 0) {
val provinceName = provincesList[position].province
binding.autoCompleteProvinsi.setText(provinceName, false)
viewModel.getCities(id)
}
}
}
private fun setSelectedCity(cityId: String?) {
cityId?.let { id ->
val position = citiesList.indexOfFirst { it.cityId?.toString() == id }
if (position >= 0) {
val cityName = citiesList[position].cityName
binding.autoCompleteKabupaten.setText(cityName, false)
viewModel.getSubdistrict(id)
}
}
}
private fun setSelectedSubdistrict(subdistrictId: String?) {
subdistrictId?.let { id ->
val position = subdistrictsList.indexOfFirst { it.subdistrictId?.toString() == id }
if (position >= 0) {
val subdistrictName = subdistrictsList[position].subdistrictName
binding.autoCompleteKecamatan.setText(subdistrictName, false)
viewModel.getVillages(id)
}
}
}
private fun setSelectedVillage(villageId: String?) {
villageId?.let { id ->
val position = villagesList.indexOfFirst { it.villageId?.toString() == id }
if (position >= 0) {
val villageName = villagesList[position].villageName
binding.autoCompleteDesa.setText(villageName, false)
}
}
}
//
// private fun populatePostalCodeFromVillage(villageId: String?) {
// villageId?.let { id ->
// val village = villagesList.find { it.villageId?.toString() == id }
// village?.postalCode?.let { postalCode ->
// if (binding.etKodePos.text.isNullOrEmpty()) {
// binding.etKodePos.setText(postalCode)
// }
// }
// }
// }
private fun clearCitySelection() {
binding.autoCompleteKabupaten.setText("", false)
citiesList.clear()
cityAdapter.updateData(emptyList())
}
private fun clearSubdistrictSelection() {
binding.autoCompleteKecamatan.setText("", false)
subdistrictsList.clear()
subdistrictAdapter.updateData(emptyList())
}
private fun clearVillageSelection() {
binding.autoCompleteDesa.setText("", false)
binding.etKodePos.setText("") // Clear postal code when village is cleared
villagesList.clear()
villageAdapter.updateData(emptyList())
}
private fun saveAddress() {
currentAddress?.let { oldAddress ->
val newAddress = createNewAddressFromInputs(oldAddress)
binding.submitProgressBar.visibility = View.VISIBLE
binding.buttonSimpan.isEnabled = false
viewModel.updateAddress(oldAddress, newAddress)
} ?: run {
Log.d(TAG, "Address not loaded")
Toast.makeText(this, "Gagal mendapatkan alamat", Toast.LENGTH_SHORT).show()
}
}
private fun createNewAddressFromInputs(oldAddress: AddressDetail): AddressDetail {
val selectedProvinceId = getSelectedProvinceId()
val selectedCityId = getSelectedCityId()
val selectedSubdistrictName = getSelectedSubdistrictName()
val selectedVillageId = getSelectedVillageId()
val selectedSubdistrictId = getSelectedSubdistrictId()
val selectedVillageName = getSelectedVillageName()
val selectedProvinceName = getSelectedProvinceName()
Log.d(TAG, "Old subdistrict: ${oldAddress.subdistrict}")
Log.d(TAG, "Selected subdistrictId: $selectedSubdistrictName")
val newAddress = oldAddress.copy(
recipient = binding.etNamaPenerima.text.toString().trim(),
phone = binding.etNomorHp.text.toString().trim(),
detail = binding.etDetailAlamat.text.toString().trim(),
postalCode = binding.etKodePos.text.toString().trim(),
provinceId = selectedProvinceId?.toString() ?: oldAddress.provinceId,
cityId = selectedCityId ?: oldAddress.cityId,
subdistrict = selectedSubdistrictName ?: oldAddress.subdistrict,
villageId = selectedVillageId,
subdistrictId = selectedSubdistrictId ?: oldAddress.subdistrictId,
villageName = selectedVillageName ?: oldAddress.villageName,
provinceName = selectedProvinceName ?: oldAddress.provinceName
)
// 🔎 Debug logs
Log.d(TAG, "New subdistrict: ${newAddress.subdistrict}")
Log.d(TAG, "New village name: ${newAddress.villageName}")
return newAddress
}
private fun getSelectedProvinceId(): Int? {
val selectedText = binding.autoCompleteProvinsi.text.toString()
val position = provincesList.indexOfFirst { it.province == selectedText }
return if (position >= 0) provinceAdapter.getProvinceId(position) else null
}
private fun getSelectedProvinceName(): String? {
val selectedText = binding.autoCompleteProvinsi.text.toString()
val position = provincesList.indexOfFirst { it.province == selectedText }
return if (position >= 0) provinceAdapter.getProvinceName(position) else null
}
private fun getSelectedCityId(): String? {
val selectedText = binding.autoCompleteKabupaten.text.toString()
val position = citiesList.indexOfFirst { it.cityName == selectedText }
return if (position >= 0) cityAdapter.getCityId(position) else null
}
private fun getSelectedCityName(): String? {
val selectedText = binding.autoCompleteKabupaten.text.toString()
val position = citiesList.indexOfFirst { it.cityName == selectedText }
return if (position >= 0) cityAdapter.getCityName(position) else null
}
private fun getSelectedSubdistrictName(): String? {
val selectedText = binding.autoCompleteKecamatan.text.toString()
val position = subdistrictsList.indexOfFirst { it.subdistrictName == selectedText }
return if (position >= 0) subdistrictAdapter.getSubdistrictName(position) else null
}
private fun getSelectedSubdistrictId(): String? {
val selectedText = binding.autoCompleteKecamatan.text.toString()
val position = subdistrictsList.indexOfFirst { it.subdistrictName == selectedText }
return if (position >= 0) subdistrictAdapter.getSubdistrictId(position) else null
}
private fun getSelectedVillageId(): String? {
val selectedText = binding.autoCompleteDesa.text.toString()
val position = villagesList.indexOfFirst { it.villageName == selectedText }
return if (position >= 0) villageAdapter.getVillageId(position) else null
}
private fun getSelectedVillageName(): String? {
val selectedText = binding.autoCompleteDesa.text.toString()
val position = villagesList.indexOfFirst { it.villageName == selectedText }
return if (position >= 0) villageAdapter.getVillageName(position) else null
}
companion object {
const val EXTRA_ADDRESS_ID = "extra_address_id"
private const val TAG = "EditAddressActivity"
}
}

View File

@ -3,6 +3,7 @@ package com.alya.ecommerce_serang.ui.order.address
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Spinner
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictsItem import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictsItem
@ -31,6 +32,10 @@ class ProvinceAdapter(
fun getProvinceId(position: Int): Int? { fun getProvinceId(position: Int): Int? {
return provinces.getOrNull(position)?.provinceId?.toIntOrNull() return provinces.getOrNull(position)?.provinceId?.toIntOrNull()
} }
fun getProvinceName(position: Int): String? {
return provinces.getOrNull(position)?.province?.toString()
}
} }
class CityAdapter( class CityAdapter(
@ -52,6 +57,10 @@ class CityAdapter(
fun getCityId(position: Int): String? { fun getCityId(position: Int): String? {
return cities.getOrNull(position)?.cityId?.toString() return cities.getOrNull(position)?.cityId?.toString()
} }
fun getCityName(position: Int): String? {
return cities.getOrNull(position)?.cityName?.toString()
}
} }
class SubdsitrictAdapter( class SubdsitrictAdapter(
@ -73,6 +82,10 @@ class SubdsitrictAdapter(
fun getSubdistrictId(position: Int): String? { fun getSubdistrictId(position: Int): String? {
return cities.getOrNull(position)?.subdistrictId?.toString() return cities.getOrNull(position)?.subdistrictId?.toString()
} }
fun getSubdistrictName(position: Int): String? {
return cities.getOrNull(position)?.subdistrictName?.toString()
}
} }
class VillagesAdapter( class VillagesAdapter(
@ -94,7 +107,82 @@ class VillagesAdapter(
fun getVillageId(position: Int): String? { fun getVillageId(position: Int): String? {
return villages.getOrNull(position)?.villageId?.toString() return villages.getOrNull(position)?.villageId?.toString()
} }
fun getVillageName(position: Int): String? {
return villages.getOrNull(position)?.villageName.toString()
}
fun getPostalCode(position: Int): String?{ fun getPostalCode(position: Int): String?{
return villages.getOrNull(position)?.postalCode return villages.getOrNull(position)?.postalCode
} }
} }
class BankAdapter(
context: Context,
resource: Int = android.R.layout.simple_dropdown_item_1line
) : ArrayAdapter<String>(context, resource, ArrayList()) {
data class BankItem(
val bankName: String,
val bankCode: String? = null,
val description: String? = null
)
private val banks = ArrayList<BankItem>()
init {
loadHardcodedData()
}
private fun loadHardcodedData() {
val defaultBanks = listOf(
BankItem("Bank Mandiri", "008", "PT Bank Mandiri (Persero) Tbk"),
BankItem("Bank BRI", "002", "PT Bank Rakyat Indonesia (Persero) Tbk"),
BankItem("Bank BCA", "014", "PT Bank Central Asia Tbk"),
BankItem("Bank BNI", "009", "PT Bank Negara Indonesia (Persero) Tbk"),
BankItem("Bank BTN", "200", "PT Bank Tabungan Negara (Persero) Tbk"),
BankItem("Bank CIMB Niaga", "022", "PT Bank CIMB Niaga Tbk"),
BankItem("Bank Danamon", "011", "PT Bank Danamon Indonesia Tbk"),
BankItem("Bank Permata", "013", "PT Bank Permata Tbk"),
BankItem("Bank OCBC NISP", "028", "PT Bank OCBC NISP Tbk"),
BankItem("Bank Maybank", "016", "PT Bank Maybank Indonesia Tbk"),
BankItem("Bank Panin", "019", "PT Bank Panin Dubai Syariah Tbk"),
BankItem("Bank UOB", "023", "PT Bank UOB Indonesia"),
BankItem("Bank Mega", "426", "PT Bank Mega Tbk"),
BankItem("Bank Bukopin", "441", "PT Bank Bukopin Tbk"),
BankItem("Bank BJB", "110", "PT Bank Pembangunan Daerah Jawa Barat dan Banten Tbk")
)
updateData(defaultBanks)
}
fun updateData(newBanks: List<BankItem>) {
banks.clear()
banks.addAll(newBanks)
clear()
addAll(banks.map { it.bankName })
notifyDataSetChanged()
}
fun getBankName(position: Int): String? {
return banks.getOrNull(position)?.bankName
}
fun getBankItem(position: Int): BankItem? {
return banks.getOrNull(position)
}
fun getBankCode(position: Int): String? {
return banks.getOrNull(position)?.bankCode
}
fun findPositionByName(bankName: String): Int {
return banks.indexOfFirst { it.bankName == bankName }
}
fun setDefaultSelection(spinner: Spinner, defaultBankName: String) {
val position = findPositionByName(defaultBankName)
if (position >= 0) {
spinner.setSelection(position)
}
}
}

View File

@ -61,8 +61,8 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
} }
private val paymentMethods = arrayOf( private val paymentMethods = arrayOf(
"Pilih Metode Pembayaran",
"Transfer Bank", "Transfer Bank",
"QRIS",
) )
// private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> // private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
@ -122,7 +122,6 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "ERROR in AddEvidencePaymentActivity onCreate: ${e.message}", e) Log.e(TAG, "ERROR in AddEvidencePaymentActivity onCreate: ${e.message}", e)
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_LONG).show()
} }
} }
@ -288,7 +287,7 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error handling selected image", e) Log.e(TAG, "Error handling selected image", e)
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Terjadi kendala", Toast.LENGTH_SHORT).show()
} }
} }
@ -314,10 +313,10 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
return return
} }
if (binding.spinnerPaymentMethod.selectedItemPosition == 0) { // if (binding.spinnerPaymentMethod.selectedItemPosition == 0) {
Toast.makeText(this, "Silahkan pilih metode pembayaran", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Silahkan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
return // return
} // }
binding.etAccountNumber.visibility = View.GONE binding.etAccountNumber.visibility = View.GONE
// if (binding.etAccountNumber.text.toString().trim().isEmpty()) { // if (binding.etAccountNumber.text.toString().trim().isEmpty()) {
@ -325,10 +324,10 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
// return // return
// } // }
if (binding.tvPaymentDate.text.toString() == "Pilih tanggal") { // if (binding.tvPaymentDate.text.toString() == "Pilih tanggal") {
Toast.makeText(this, "Silahkan pilih tanggal pembayaran", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Silahkan pilih tanggal pembayaran", Toast.LENGTH_SHORT).show()
return // return
} // }
// All validations passed, proceed with upload // All validations passed, proceed with upload
uploadPaymentProof() uploadPaymentProof()
@ -367,7 +366,7 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
viewModel.uploadPaymentProof(request) viewModel.uploadPaymentProof(request)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error creating upload request: ${e.message}", e) Log.e(TAG, "Error creating upload request: ${e.message}", e)
Toast.makeText(this, "Error preparing upload: ${e.message}", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Gagal mengunggah foto", Toast.LENGTH_SHORT).show()
} }
} }
} }

View File

@ -160,7 +160,8 @@ class PaymentActivity : AppCompatActivity() {
viewModel.error.observe(this) { error -> viewModel.error.observe(this) { error ->
if (error.isNotEmpty()) { if (error.isNotEmpty()) {
Toast.makeText(this, error, Toast.LENGTH_SHORT).show() Toast.makeText(this, "Gagal melakukan pembayaran", Toast.LENGTH_SHORT).show()
Log.e(TAG, "Failed payment: $error")
} }
} }
} }

View File

@ -32,6 +32,7 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson import com.google.gson.Gson
import java.io.File import java.io.File
import java.text.NumberFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
@ -88,7 +89,8 @@ class OrderHistoryAdapter(
tvStoreName.text = storeName tvStoreName.text = storeName
// Set total amount // Set total amount
tvTotalAmount.text = order.totalAmount tvTotalAmount.text = formatCurrency(order.totalAmount.toDouble())
// Set item count // Set item count
val itemCount = order.orderItems.size val itemCount = order.orderItems.size
@ -515,14 +517,14 @@ class OrderHistoryAdapter(
} else { } else {
// Log error and show a Toast instead if we can't get a FragmentManager // Log error and show a Toast instead if we can't get a FragmentManager
Log.e("OrderHistoryAdapter", "Cannot show bottom sheet: Context is not a FragmentActivity") Log.e("OrderHistoryAdapter", "Cannot show bottom sheet: Context is not a FragmentActivity")
Toast.makeText(context, "Cannot show cancel order dialog", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Terjadi kendala", Toast.LENGTH_SHORT).show()
return return
} }
} }
else -> { else -> {
// Log error and show a Toast instead if we can't get a FragmentManager // Log error and show a Toast instead if we can't get a FragmentManager
Log.e("OrderHistoryAdapter", "Cannot show bottom sheet: Context is not a FragmentActivity") Log.e("OrderHistoryAdapter", "Cannot show bottom sheet: Context is not a FragmentActivity")
Toast.makeText(context, "Cannot show cancel order dialog", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Terjadi kendala", Toast.LENGTH_SHORT).show()
return return
} }
} }
@ -533,7 +535,7 @@ class OrderHistoryAdapter(
onOrderCancelled = { onOrderCancelled = {
callbacks.onOrderCancelled(orderId.toString(), true, "Order cancelled successfully") callbacks.onOrderCancelled(orderId.toString(), true, "Order cancelled successfully")
// Show a success message // Show a success message
Toast.makeText(context, "Order cancelled successfully", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Pesanan berhasil dibatalkan", Toast.LENGTH_SHORT).show()
} }
) )
@ -545,15 +547,6 @@ class OrderHistoryAdapter(
// Use ViewModel to fetch order details // Use ViewModel to fetch order details
viewModel.getOrderDetails(order.orderId) viewModel.getOrderDetails(order.orderId)
// Create loading dialog
// val loadingDialog = Dialog(itemView.context).apply {
// requestWindowFeature(Window.FEATURE_NO_TITLE)
// setContentView(R.layout.dialog_loading)
// window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
// setCancelable(false)
// }
// loadingDialog.show()
viewModel.error.observe(itemView.findViewTreeLifecycleOwner()!!) { errorMsg -> viewModel.error.observe(itemView.findViewTreeLifecycleOwner()!!) { errorMsg ->
if (!errorMsg.isNullOrEmpty()) { if (!errorMsg.isNullOrEmpty()) {
Toast.makeText(itemView.context, errorMsg, Toast.LENGTH_SHORT).show() Toast.makeText(itemView.context, errorMsg, Toast.LENGTH_SHORT).show()
@ -599,6 +592,11 @@ class OrderHistoryAdapter(
} }
} }
private fun formatCurrency(amount: Double): String {
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
return formatter.format(amount).replace(",00", "")
}
} }
companion object { companion object {

View File

@ -10,6 +10,8 @@ import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.customer.order.OrderItemsItem import com.alya.ecommerce_serang.data.api.response.customer.order.OrderItemsItem
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import java.text.NumberFormat
import java.util.Locale
class OrderProductAdapter : RecyclerView.Adapter<OrderProductAdapter.ProductViewHolder>() { class OrderProductAdapter : RecyclerView.Adapter<OrderProductAdapter.ProductViewHolder>() {
@ -46,7 +48,7 @@ class OrderProductAdapter : RecyclerView.Adapter<OrderProductAdapter.ProductView
tvQuantity.text = "${product.quantity} buah" tvQuantity.text = "${product.quantity} buah"
// Set price with currency format // Set price with currency format
tvProductPrice.text = formatCurrency(product.price) tvProductPrice.text = formatCurrency(product.price.toDouble())
val fullImageUrl = when (val img = product.productImage) { val fullImageUrl = when (val img = product.productImage) {
is String -> { is String -> {
@ -65,10 +67,9 @@ class OrderProductAdapter : RecyclerView.Adapter<OrderProductAdapter.ProductView
private fun formatCurrency(amount: Int): String { private fun formatCurrency(amount: Double): String {
// In a real app, you would use NumberFormat for proper currency formatting val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
// For simplicity, just return a basic formatted string return formatter.format(amount).replace(",00", "")
return "Rp${amount}"
} }
} }
} }

View File

@ -67,7 +67,7 @@ class CancelOrderBottomSheet(
btnConfirm.setOnClickListener { btnConfirm.setOnClickListener {
if (selectedReason == null) { if (selectedReason == null) {
Toast.makeText(context, "Please select a reason", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Pilih alasan pembatalan", Toast.LENGTH_SHORT).show()
return@setOnClickListener return@setOnClickListener
} }

View File

@ -90,7 +90,7 @@ class CreateReviewActivity : AppCompatActivity() {
) )
}) })
} catch (e: Exception) { } catch (e: Exception) {
Toast.makeText(this, "Error loading review items", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Gagal memuat ulasan", Toast.LENGTH_SHORT).show()
finish() finish()
} }
} else { } else {
@ -110,7 +110,7 @@ class CreateReviewActivity : AppCompatActivity() {
) )
) )
} else { } else {
Toast.makeText(this, "No items to review", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Tidak ada produk untuk direview", Toast.LENGTH_SHORT).show()
finish() finish()
} }
} }

View File

@ -112,13 +112,17 @@ class DetailProductActivity : AppCompatActivity() {
when (result) { when (result) {
is Result.Success -> { is Result.Success -> {
updateStoreInfo(result.data) updateStoreInfo(result.data)
binding.progressBarDetailStore.visibility = View.GONE
} }
is Result.Error -> { is Result.Error -> {
// Show error message, maybe a Toast or Snackbar // Show error message, maybe a Toast or Snackbar
Toast.makeText(this, "Failed to load store: ${result.exception.message}", Toast.LENGTH_SHORT).show() binding.progressBarDetailStore.visibility = View.GONE
Log.e("DetailProfileActivity", "Failed to load store: ${result.exception.message}")
Toast.makeText(this, "Kendala memuat toko", Toast.LENGTH_SHORT).show()
} }
is Result.Loading -> { is Result.Loading -> {
// Show loading indicator if needed // Show loading indicator if needed
binding.progressBarDetailStore.visibility = View.VISIBLE
} }
} }
} }
@ -160,6 +164,10 @@ class DetailProductActivity : AppCompatActivity() {
val products = viewModel.otherProducts.value.orEmpty() val products = viewModel.otherProducts.value.orEmpty()
if (products.isNotEmpty()) { if (products.isNotEmpty()) {
updateOtherProducts(products, storeMap) updateOtherProducts(products, storeMap)
} else {
binding.emptyOtherProducts.visibility = View.VISIBLE
binding.recyclerViewOtherProducts.visibility = View.GONE
binding.tvViewAllProducts.visibility = View.GONE
} }
} }
} }
@ -190,12 +198,14 @@ class DetailProductActivity : AppCompatActivity() {
private fun updateOtherProducts(products: List<ProductsItem>, storeMap: Map<Int, StoreItem>) { private fun updateOtherProducts(products: List<ProductsItem>, storeMap: Map<Int, StoreItem>) {
if (products.isEmpty()) { if (products.isEmpty()) {
Log.d("DetailProductActivity", "Product list is empty, hiding RecyclerView") Log.d("DetailProductActivity", "Product list is empty, hiding RecyclerView")
binding.recyclerViewOtherProducts.visibility = View.VISIBLE binding.recyclerViewOtherProducts.visibility = View.GONE
binding.emptyOtherProducts.visibility = View.VISIBLE
binding.tvViewAllProducts.visibility = View.GONE binding.tvViewAllProducts.visibility = View.GONE
} else { } else {
Log.d("DetailProductActivity", "Displaying product list in RecyclerView") Log.d("DetailProductActivity", "Displaying product list in RecyclerView")
binding.recyclerViewOtherProducts.visibility = View.VISIBLE binding.recyclerViewOtherProducts.visibility = View.VISIBLE
binding.tvViewAllProducts.visibility = View.VISIBLE binding.tvViewAllProducts.visibility = View.VISIBLE
binding.emptyOtherProducts.visibility = View.GONE
productAdapter = OtherProductAdapter(products, onClick = { product -> productAdapter = OtherProductAdapter(products, onClick = { product ->
handleProductClick(product) handleProductClick(product)
@ -316,11 +326,13 @@ class DetailProductActivity : AppCompatActivity() {
val limitedReviewList = if (reviewList.isNotEmpty()) listOf(reviewList.first()) else emptyList() val limitedReviewList = if (reviewList.isNotEmpty()) listOf(reviewList.first()) else emptyList()
if (reviewList.isEmpty()) { if (reviewList.isEmpty()) {
binding.recyclerViewReviews.visibility = View.GONE binding.recyclerViewReviews.visibility = View.GONE
binding.emptyReview.visibility = View.VISIBLE
binding.tvViewAllReviews.visibility = View.GONE binding.tvViewAllReviews.visibility = View.GONE
// binding.tvNoReviews.visibility = View.VISIBLE // binding.tvNoReviews.visibility = View.VISIBLE
} else { } else {
binding.recyclerViewReviews.visibility = View.VISIBLE binding.recyclerViewReviews.visibility = View.VISIBLE
binding.tvViewAllReviews.visibility = View.VISIBLE binding.tvViewAllReviews.visibility = View.VISIBLE
binding.emptyReview.visibility = View.GONE
} }
// binding.tvNoReviews.visibility = View.GONE // binding.tvNoReviews.visibility = View.GONE
reviewsAdapter = ReviewsAdapter( reviewsAdapter = ReviewsAdapter(
@ -519,7 +531,11 @@ class DetailProductActivity : AppCompatActivity() {
attachProduct = true // This will auto-attach the product! attachProduct = true // This will auto-attach the product!
) )
}
override fun onResume() {
super.onResume()
loadData()
} }
companion object { companion object {

View File

@ -64,10 +64,9 @@ class StoreDetailActivity : AppCompatActivity() {
) )
windowInsets windowInsets
} }
loadData()
setupUI() setupUI()
setupObservers() setupObservers()
loadData()
} }
private fun setupUI() { private fun setupUI() {
@ -88,15 +87,18 @@ class StoreDetailActivity : AppCompatActivity() {
viewModel.storeDetail.observe(this) { result -> viewModel.storeDetail.observe(this) { result ->
when (result) { when (result) {
is Result.Success -> { is Result.Success -> {
binding.progressBarDetailProdItem.visibility = View.GONE
updateStoreInfo(result.data) updateStoreInfo(result.data)
viewModel.loadOtherProducts(result.data.storeId) viewModel.loadOtherProducts(result.data.storeId)
} }
is Result.Error -> { is Result.Error -> {
// Show error message, maybe a Toast or Snackbar // Show error message, maybe a Toast or Snackbar
binding.progressBarDetailProdItem.visibility = View.GONE
Toast.makeText(this, "Failed to load store: ${result.exception.message}", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Failed to load store: ${result.exception.message}", Toast.LENGTH_SHORT).show()
} }
is Result.Loading -> { is Result.Loading -> {
// Show loading indicator if needed // Show loading indicator if needed
binding.progressBarDetailProdItem.visibility = View.VISIBLE
} }
} }
} }
@ -109,6 +111,9 @@ class StoreDetailActivity : AppCompatActivity() {
val products = viewModel.otherProducts.value.orEmpty() val products = viewModel.otherProducts.value.orEmpty()
if (products.isNotEmpty()) { if (products.isNotEmpty()) {
updateProducts(products, storeMap) updateProducts(products, storeMap)
} else {
binding.progressBarDetailProdItem.visibility = View.VISIBLE
binding.rvProducts.visibility = View.GONE
} }
} }
} }
@ -146,7 +151,7 @@ class StoreDetailActivity : AppCompatActivity() {
.into(binding.ivStoreImage) .into(binding.ivStoreImage)
val ratingStr = it.storeRating val ratingStr = it.storeRating
val ratingValue = ratingStr?.toFloatOrNull() val ratingValue = ratingStr.toFloatOrNull()
if (ratingValue != null && ratingValue > 0f) { if (ratingValue != null && ratingValue > 0f) {
binding.tvStoreRating.text = String.format("%.1f", ratingValue) binding.tvStoreRating.text = String.format("%.1f", ratingValue)
@ -161,10 +166,12 @@ class StoreDetailActivity : AppCompatActivity() {
private fun updateProducts(products: List<ProductsItem>, storeMap: Map<Int, StoreItem>) { private fun updateProducts(products: List<ProductsItem>, storeMap: Map<Int, StoreItem>) {
if (products.isEmpty()) { if (products.isEmpty()) {
binding.rvProducts.visibility = View.GONE binding.rvProducts.visibility = View.GONE
binding.progressBarDetailProdItem.visibility = View.VISIBLE
Log.d("StoreDetailActivity", "Product list is empty, hiding RecyclerView") Log.d("StoreDetailActivity", "Product list is empty, hiding RecyclerView")
} else { } else {
Log.d("StoreDetailActivity", "Displaying product list in RecyclerView") Log.d("StoreDetailActivity", "Displaying product list in RecyclerView")
binding.progressBarDetailProdItem.visibility = View.GONE
binding.rvProducts.visibility = View.VISIBLE binding.rvProducts.visibility = View.VISIBLE
productAdapter = HorizontalProductAdapter(products, onClick = { product -> productAdapter = HorizontalProductAdapter(products, onClick = { product ->
handleProductClick(product) handleProductClick(product)

View File

@ -0,0 +1,21 @@
package com.alya.ecommerce_serang.ui.profile
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 ChangePasswordActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_change_password)
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

@ -90,6 +90,8 @@ class DetailProfileActivity : AppCompatActivity() {
Log.e("DetailProfileActivity", "Error from ViewModel: $error") Log.e("DetailProfileActivity", "Error from ViewModel: $error")
Toast.makeText(this, error, Toast.LENGTH_SHORT).show() Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
} }
} }
private fun setupClickListeners() { private fun setupClickListeners() {
@ -106,7 +108,8 @@ class DetailProfileActivity : AppCompatActivity() {
} }
editProfileLauncher.launch(intent) editProfileLauncher.launch(intent)
} ?: run { } ?: run {
Toast.makeText(this, "Profile data is not available", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Akun tidak ditemukan", Toast.LENGTH_SHORT).show()
Log.e("DetailProfileActivity", "Profile data is not available")
} }
} }
} }

View File

@ -20,10 +20,10 @@ import com.alya.ecommerce_serang.data.repository.MyStoreRepository
import com.alya.ecommerce_serang.data.repository.UserRepository import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.FragmentProfileBinding import com.alya.ecommerce_serang.databinding.FragmentProfileBinding
import com.alya.ecommerce_serang.ui.auth.LoginActivity import com.alya.ecommerce_serang.ui.auth.LoginActivity
import com.alya.ecommerce_serang.ui.profile.mystore.RegisterStoreActivity
import com.alya.ecommerce_serang.ui.order.address.AddressActivity import com.alya.ecommerce_serang.ui.order.address.AddressActivity
import com.alya.ecommerce_serang.ui.order.history.HistoryActivity import com.alya.ecommerce_serang.ui.order.history.HistoryActivity
import com.alya.ecommerce_serang.ui.profile.mystore.MyStoreActivity import com.alya.ecommerce_serang.ui.profile.mystore.MyStoreActivity
import com.alya.ecommerce_serang.ui.profile.mystore.RegisterStoreActivity
import com.alya.ecommerce_serang.ui.profile.mystore.StoreOnReviewActivity import com.alya.ecommerce_serang.ui.profile.mystore.StoreOnReviewActivity
import com.alya.ecommerce_serang.ui.profile.mystore.StoreSuspendedActivity import com.alya.ecommerce_serang.ui.profile.mystore.StoreSuspendedActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.BaseViewModelFactory
@ -58,7 +58,6 @@ class ProfileFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
sessionManager = SessionManager(requireContext())
} }
override fun onCreateView( override fun onCreateView(
@ -72,26 +71,57 @@ class ProfileFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
sessionManager = SessionManager(requireContext())
if (!sessionManager.isLoggedIn()) {
// Redirect to LoginActivity
binding.tvName.text = "Selamat Datang"
binding.tvUsername.text = "Silahkan masuk"
binding.btnDetailProfile.text = "Masuk"
binding.btnDetailProfile.setOnClickListener {
val intent = Intent(requireContext(), LoginActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
// ✅ Finish the host activity so user cant go back
requireActivity().finish()
}
binding.containerBukaToko.visibility = View.GONE
binding.cardPesanan.visibility = View.GONE
binding.tvPengaturanAkun.visibility = View.GONE
binding.containerSettings.visibility = View.GONE
binding.cardAbout.visibility = View.GONE
binding.cardLogout.visibility = View.GONE
}
viewModel.loadUserProfile()
viewModel.checkStoreUser()
observeUserProfile() observeUserProfile()
observeStoreStatus() observeStoreStatus()
viewModel.loadUserProfile()
viewModel.checkStoreUser()
binding.cardBukaToko.setOnClickListener{ binding.cardBukaToko.setOnClickListener{
// if (hasStore == true) startActivity(Intent(requireContext(), MyStoreActivity::class.java)) // if (hasStore == true) startActivity(Intent(requireContext(), MyStoreActivity::class.java))
// else startActivity(Intent(requireContext(), RegisterStoreActivity::class.java)) // else startActivity(Intent(requireContext(), RegisterStoreActivity::class.java))
if (viewModel.checkStore.value == true) { if (viewModel.checkStore.value == true) {
myStoreViewModel.loadMyStore() myStoreViewModel.loadMyStore()
myStoreViewModel.myStoreProfile.observe(viewLifecycleOwner) { store -> myStoreViewModel.myStoreProfile.observe(viewLifecycleOwner) { storeDataResponse ->
store?.let { storeDataResponse?.let { storeResponse ->
when (store.storeStatus) { val store = storeResponse.store
when (store.approvalStatus) {
"process" -> startActivity(Intent(requireContext(), StoreOnReviewActivity::class.java)) "process" -> startActivity(Intent(requireContext(), StoreOnReviewActivity::class.java))
"active" -> startActivity(Intent(requireContext(), MyStoreActivity::class.java)) "rejected" -> startActivity(
"inactive" -> startActivity(Intent(requireContext(), MyStoreActivity::class.java)) Intent(requireContext(), RegisterStoreActivity::class.java).putExtra("REAPPLY", true))
else -> {
when(store.storeStatus){
"suspended" -> startActivity(Intent(requireContext(), StoreSuspendedActivity::class.java)) "suspended" -> startActivity(Intent(requireContext(), StoreSuspendedActivity::class.java))
else -> startActivity(Intent(requireContext(), RegisterStoreActivity::class.java)) else -> startActivity(Intent(requireContext(), MyStoreActivity::class.java))
}
}
} }
} ?: run { } ?: run {
Toast.makeText(requireContext(), "Gagal memuat data toko", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Gagal memuat data toko", Toast.LENGTH_SHORT).show()
@ -115,6 +145,11 @@ class ProfileFragment : Fragment() {
startActivity(intent) startActivity(intent)
} }
binding.cardChangePass.setOnClickListener{
val intent = Intent(requireContext(), ChangePasswordActivity::class.java)
startActivity(intent)
}
binding.cardLogout.setOnClickListener{ binding.cardLogout.setOnClickListener{
logout() logout()
} }
@ -130,7 +165,8 @@ class ProfileFragment : Fragment() {
user?.let { updateUI(it) } user?.let { updateUI(it) }
} }
viewModel.errorMessage.observe(viewLifecycleOwner) { errorMessage -> viewModel.errorMessage.observe(viewLifecycleOwner) { errorMessage ->
Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show() // Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show()
Log.e("Profile Fragment", "Failed to load profile: $errorMessage")
} }
} }
@ -186,6 +222,8 @@ class ProfileFragment : Fragment() {
sessionManager.clearAll() sessionManager.clearAll()
val intent = Intent(requireContext(), LoginActivity::class.java) val intent = Intent(requireContext(), LoginActivity::class.java)
startActivity(intent) startActivity(intent)
requireActivity().finish()
} catch (e: Exception) { } catch (e: Exception) {
Toast.makeText( Toast.makeText(
requireContext(), requireContext(),
@ -196,4 +234,11 @@ class ProfileFragment : Fragment() {
} }
} }
override fun onResume() {
super.onResume()
viewModel.loadUserProfile()
viewModel.checkStoreUser()
}
} }

View File

@ -1,10 +1,13 @@
package com.alya.ecommerce_serang.ui.profile.editprofile package com.alya.ecommerce_serang.ui.profile.editprofile
import android.Manifest
import android.app.Activity import android.app.Activity
import android.app.AlertDialog
import android.app.DatePickerDialog import android.app.DatePickerDialog
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Log import android.util.Log
@ -22,7 +25,6 @@ import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.UserProfile import com.alya.ecommerce_serang.data.api.dto.UserProfile
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityEditProfileCustBinding import com.alya.ecommerce_serang.databinding.ActivityEditProfileCustBinding
@ -31,7 +33,6 @@ import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.ProfileViewModel import com.alya.ecommerce_serang.utils.viewmodel.ProfileViewModel
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.google.gson.Gson import com.google.gson.Gson
import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
@ -39,9 +40,9 @@ import java.util.TimeZone
class EditProfileCustActivity : AppCompatActivity() { class EditProfileCustActivity : AppCompatActivity() {
private lateinit var binding: ActivityEditProfileCustBinding private lateinit var binding: ActivityEditProfileCustBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager private lateinit var sessionManager: SessionManager
private var selectedImageUri: Uri? = null private var selectedImageUri: Uri? = null
private var currentUser: UserProfile? = null
private val viewModel: ProfileViewModel by viewModels { private val viewModel: ProfileViewModel by viewModels {
BaseViewModelFactory { BaseViewModelFactory {
@ -52,7 +53,7 @@ class EditProfileCustActivity : AppCompatActivity() {
} }
private val getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> private val getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == RESULT_OK) {
val data: Intent? = result.data val data: Intent? = result.data
data?.data?.let { data?.data?.let {
selectedImageUri = it selectedImageUri = it
@ -103,8 +104,8 @@ class EditProfileCustActivity : AppCompatActivity() {
} }
userProfile?.let { userProfile?.let {
currentUser = it
populateFields(it) populateFields(it)
setupClickListeners() setupClickListeners()
observeViewModel() observeViewModel()
} }
@ -116,7 +117,7 @@ class EditProfileCustActivity : AppCompatActivity() {
binding.etNumberPhoneUser.setText(profile.phone) binding.etNumberPhoneUser.setText(profile.phone)
// Format birth date for display // Format birth date for display
profile.birthDate?.let { profile.birthDate.let {
binding.etDateBirth.setText(formatDate(it)) binding.etDateBirth.setText(formatDate(it))
} }
@ -154,22 +155,19 @@ class EditProfileCustActivity : AppCompatActivity() {
} }
binding.btnSave.setOnClickListener { binding.btnSave.setOnClickListener {
saveProfile() if (hasChanged()) confirmUpdate() else finish()
} }
} }
private fun openImagePicker() { private fun openImagePicker() {
// Check for permission first val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission( Manifest.permission.READ_MEDIA_IMAGES
this, } else {
android.Manifest.permission.READ_EXTERNAL_STORAGE Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED }
) {
ActivityCompat.requestPermissions( if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
this, ActivityCompat.requestPermissions(this, arrayOf(permission), REQUEST_STORAGE_PERMISSION)
arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE),
REQUEST_STORAGE_PERMISSION
)
} else { } else {
launchImagePicker() launchImagePicker()
} }
@ -214,25 +212,38 @@ class EditProfileCustActivity : AppCompatActivity() {
datePickerDialog.show() datePickerDialog.show()
} }
private fun confirmUpdate() {
AlertDialog.Builder(this)
.setTitle("Konfirmasi Perubahan")
.setMessage("Apakah Anda yakin ingin menyimpan perubahan profil toko Anda?")
.setPositiveButton("Ya") { _, _ -> saveProfile() }
.setNegativeButton("Batal", null)
.show()
}
private fun hasChanged(): Boolean {
val name = binding.etNameUser.text.toString() != currentUser?.name
val username = binding.etUsername.text.toString() != currentUser?.username
val email = binding.etEmailUser.text.toString() != currentUser?.email
val phone = binding.etNumberPhoneUser.text.toString() != currentUser?.phone
val displayDate = binding.etDateBirth.text.toString() != currentUser?.birthDate.toString()
val imgProfile = selectedImageUri != null
return name || username || email || phone || displayDate || imgProfile
}
private fun saveProfile() { private fun saveProfile() {
val name = binding.etNameUser.text.toString() val name = binding.etNameUser.text.toString()
val username = binding.etUsername.text.toString() val username = binding.etUsername.text.toString()
val email = binding.etEmailUser.text.toString() val email = binding.etEmailUser.text.toString()
val phone = binding.etNumberPhoneUser.text.toString() val phone = binding.etNumberPhoneUser.text.toString()
val displayDate = binding.etDateBirth.text.toString() val displayDate = binding.etDateBirth.text.toString()
val imgProfile = selectedImageUri
if (name.isEmpty() || username.isEmpty() || email.isEmpty() || phone.isEmpty() || displayDate.isEmpty()) {
Toast.makeText(this, "Semua field harus diisi", Toast.LENGTH_SHORT).show()
return
}
// Convert date to server format
val serverBirthDate = convertToServerDateFormat(displayDate) val serverBirthDate = convertToServerDateFormat(displayDate)
Log.d(TAG, "Starting profile save with direct method") Log.d(TAG, "Starting profile save with direct method")
Log.d(TAG, "Selected image URI: $selectedImageUri") Log.d(TAG, "Selected image URI: $selectedImageUri")
// Disable the button to prevent multiple clicks
binding.btnSave.isEnabled = false binding.btnSave.isEnabled = false
// Call the repository method via ViewModel // Call the repository method via ViewModel
@ -243,82 +254,10 @@ class EditProfileCustActivity : AppCompatActivity() {
phone = phone, phone = phone,
birthDate = serverBirthDate, birthDate = serverBirthDate,
email = email, email = email,
imageUri = selectedImageUri imageUri = imgProfile
) )
} }
private fun getRealPathFromURI(uri: Uri): String? {
Log.d(TAG, "Getting real path from URI: $uri")
// Handle different URI schemes
when {
// File URI
uri.scheme == "file" -> {
val path = uri.path
Log.d(TAG, "URI is file scheme, path: $path")
return path
}
// Content URI
uri.scheme == "content" -> {
try {
val projection = arrayOf(MediaStore.Images.Media.DATA)
contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
val path = cursor.getString(columnIndex)
Log.d(TAG, "Found path from content URI: $path")
return path
} else {
Log.e(TAG, "Cursor is empty")
}
} ?: Log.e(TAG, "Cursor is null")
// If the above fails, try the documented API way
contentResolver.openInputStream(uri)?.use { inputStream ->
// Create a temp file
val fileName = getFileName(uri) ?: "temp_img_${System.currentTimeMillis()}.jpg"
val tempFile = File(cacheDir, fileName)
tempFile.outputStream().use { outputStream ->
inputStream.copyTo(outputStream)
}
Log.d(TAG, "Created temporary file: ${tempFile.absolutePath}")
return tempFile.absolutePath
}
} catch (e: Exception) {
Log.e(TAG, "Error getting real path: ${e.message}", e)
}
}
}
Log.e(TAG, "Could not get real path for URI: $uri")
return null
}
private fun getFileName(uri: Uri): String? {
var result: String? = null
if (uri.scheme == "content") {
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
if (columnIndex >= 0) {
result = cursor.getString(columnIndex)
Log.d(TAG, "Found filename from content URI: $result")
}
}
}
}
if (result == null) {
result = uri.path
val cut = result?.lastIndexOf('/') ?: -1
if (cut != -1) {
result = result?.substring(cut + 1)
}
Log.d(TAG, "Extracted filename from path: $result")
}
return result
}
private fun formatDate(dateString: String?): String { private fun formatDate(dateString: String?): String {
if (dateString.isNullOrEmpty()) return "N/A" if (dateString.isNullOrEmpty()) return "N/A"

View File

@ -59,12 +59,13 @@ class MyStoreActivity : AppCompatActivity() {
finish() finish()
} }
viewModel.myStoreProfile.observe(this){ user ->
user?.let { myStoreProfileOverview(it.store) }
}
viewModel.loadMyStore() viewModel.loadMyStore()
viewModel.loadMyStoreProducts() viewModel.loadMyStoreProducts()
viewModel.fetchBalance()
viewModel.myStoreProfile.observe(this){ user ->
user?.let { myStoreProfileOverview(it) }
}
viewModel.errorMessage.observe(this) { error -> viewModel.errorMessage.observe(this) { error ->
Toast.makeText(this, error, Toast.LENGTH_SHORT).show() Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
@ -72,7 +73,6 @@ class MyStoreActivity : AppCompatActivity() {
setUpClickListeners() setUpClickListeners()
getCountOrder() getCountOrder()
observeViewModel() observeViewModel()
viewModel.fetchBalance()
fetchBalance() fetchBalance()
} }
@ -206,6 +206,13 @@ class MyStoreActivity : AppCompatActivity() {
} }
} }
override fun onResume() {
super.onResume()
viewModel.loadMyStore()
viewModel.loadMyStoreProducts()
viewModel.fetchBalance()
}
companion object { companion object {
private const val PROFILE_REQUEST_CODE = 100 private const val PROFILE_REQUEST_CODE = 100
} }

View File

@ -1,6 +1,5 @@
package com.alya.ecommerce_serang.ui.profile.mystore package com.alya.ecommerce_serang.ui.profile.mystore
import android.Manifest
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
@ -20,7 +19,6 @@ import android.widget.Toast
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toDrawable import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
@ -29,14 +27,26 @@ import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.auth.StoreTypesItem import com.alya.ecommerce_serang.data.api.response.auth.StoreTypesItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.MyStoreRepository
import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityRegisterStoreBinding import com.alya.ecommerce_serang.databinding.ActivityRegisterStoreBinding
import com.alya.ecommerce_serang.ui.order.address.BankAdapter
import com.alya.ecommerce_serang.ui.order.address.CityAdapter import com.alya.ecommerce_serang.ui.order.address.CityAdapter
import com.alya.ecommerce_serang.ui.order.address.ProvinceAdapter import com.alya.ecommerce_serang.ui.order.address.ProvinceAdapter
import com.alya.ecommerce_serang.ui.order.address.SubdsitrictAdapter
import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.FileUtils
import com.alya.ecommerce_serang.utils.ImageUtils
import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel
import com.alya.ecommerce_serang.utils.viewmodel.RegisterStoreViewModel import com.alya.ecommerce_serang.utils.viewmodel.RegisterStoreViewModel
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import androidx.core.net.toUri
class RegisterStoreActivity : AppCompatActivity() { class RegisterStoreActivity : AppCompatActivity() {
@ -45,11 +55,15 @@ class RegisterStoreActivity : AppCompatActivity() {
private lateinit var provinceAdapter: ProvinceAdapter private lateinit var provinceAdapter: ProvinceAdapter
private lateinit var cityAdapter: CityAdapter private lateinit var cityAdapter: CityAdapter
private lateinit var subdistrictAdapter: SubdsitrictAdapter
private lateinit var bankAdapter: BankAdapter
// Request codes for file picking // Request codes for file picking
private val PICK_STORE_IMAGE_REQUEST = 1001 private val PICK_STORE_IMAGE_REQUEST = 1001
private val PICK_KTP_REQUEST = 1002 private val PICK_KTP_REQUEST = 1002
private val PICK_NPWP_REQUEST = 1003 private val PICK_NPWP_REQUEST = 1003
private val PICK_NIB_REQUEST = 1004 private val PICK_NIB_REQUEST = 1004
private var isReapply: Boolean = false
// Location request code // Location request code
private val LOCATION_PERMISSION_REQUEST = 2001 private val LOCATION_PERMISSION_REQUEST = 2001
@ -61,6 +75,15 @@ class RegisterStoreActivity : AppCompatActivity() {
RegisterStoreViewModel(orderRepository) RegisterStoreViewModel(orderRepository)
} }
} }
private val myStoreViewModel: MyStoreViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val myStoreRepository = MyStoreRepository(apiService)
MyStoreViewModel(myStoreRepository)
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityRegisterStoreBinding.inflate(layoutInflater) binding = ActivityRegisterStoreBinding.inflate(layoutInflater)
@ -86,8 +109,12 @@ class RegisterStoreActivity : AppCompatActivity() {
setupHeader() setupHeader()
isReapply = intent.getBooleanExtra("REAPPLY", false)
provinceAdapter = ProvinceAdapter(this) provinceAdapter = ProvinceAdapter(this)
cityAdapter = CityAdapter(this) cityAdapter = CityAdapter(this)
subdistrictAdapter = SubdsitrictAdapter(this)
bankAdapter = BankAdapter(this)
Log.d(TAG, "onCreate: Adapters initialized") Log.d(TAG, "onCreate: Adapters initialized")
setupDataBinding() setupDataBinding()
@ -101,8 +128,12 @@ class RegisterStoreActivity : AppCompatActivity() {
setupObservers() setupObservers()
Log.d(TAG, "onCreate: Observers setup completed") Log.d(TAG, "onCreate: Observers setup completed")
setupMap() viewModel.latitude.value = "-6.2088"
Log.d(TAG, "onCreate: Map setup completed") viewModel.longitude.value = "106.8456"
Log.d(TAG, "Location permission granted, setting default location")
// Toast.makeText(this, "Lokasi dipilih", Toast.LENGTH_SHORT).show()
Log.d(TAG, "Default location set - Lat: ${viewModel.latitude.value}, Long: ${viewModel.longitude.value}")
// Toast.makeText(this, "Lokasi dipilih", Toast.LENGTH_SHORT).show()
setupDocumentUploads() setupDocumentUploads()
Log.d(TAG, "onCreate: Document uploads setup completed") Log.d(TAG, "onCreate: Document uploads setup completed")
@ -120,19 +151,140 @@ class RegisterStoreActivity : AppCompatActivity() {
viewModel.cityId.observe(this) { validateRequiredFields() } viewModel.cityId.observe(this) { validateRequiredFields() }
viewModel.storeTypeId.observe(this) { validateRequiredFields() } viewModel.storeTypeId.observe(this) { validateRequiredFields() }
// Setup register button if (isReapply) {
binding.btnRegister.setOnClickListener { binding.btnRegister.text = "Ajukan Kembali"
Log.d(TAG, "Register button clicked") binding.layoutRejected.visibility = View.VISIBLE
if (viewModel.validateForm()) {
Log.d(TAG, "Form validation successful, proceeding with registration") myStoreViewModel.loadMyStore()
viewModel.registerStore(this)
} else { myStoreViewModel.myStoreProfile.observe(this) { storeDataResponse ->
Log.e(TAG, "Form validation failed") storeDataResponse?.let { storeResponse ->
Toast.makeText(this, "Harap lengkapi semua field yang wajib diisi", Toast.LENGTH_SHORT).show() val store = storeResponse.store
binding.tvRejectedReason.text = store.approvalReason
// Prefill basic fields
binding.etStoreName.setText(store.storeName)
binding.etStoreDescription.setText(store.storeDescription)
binding.etStreet.setText(store.street)
binding.etPostalCode.setText(store.postalCode)
binding.etAddressDetail.setText(store.detail)
viewModel.storeName.value = store.storeName
viewModel.storeDescription.value = store.storeDescription
viewModel.street.value = store.street
viewModel.postalCode.value = store.postalCode.toIntOrNull() ?: 0
viewModel.addressDetail.value = store.detail
// Prefill bank info
storeResponse.payment.firstOrNull()?.let { payment ->
viewModel.bankName.value = payment.bankName
viewModel.bankNumber.value = payment.bankNum.toIntOrNull() ?: 0
val bankPosition = bankAdapter.findPositionByName(payment.bankName)
binding.spinnerBankName.setSelection(bankPosition, false)
}
// Prefill couriers
storeResponse.shipping.forEach { courier ->
when (courier.courier) {
"jne" -> binding.checkboxJne.isChecked = true
"pos" -> binding.checkboxPos.isChecked = true
"tiki" -> binding.checkboxTiki.isChecked = true
} }
} }
Log.d(TAG, "onCreate: RegisterStoreActivity setup completed") // Prefill document URIs
store.ktp.let { ktpUri ->
viewModel.ktpUri = ktpUri.toUri()
updateImagePreview(viewModel.ktpUri, binding.imgKtp, binding.layoutUploadKtp)
}
store.npwp.let { npwpUri ->
viewModel.npwpUri = npwpUri.toUri()
updateDocumentPreview(binding.layoutUploadNpwp)
}
store.nib.let { nibUri ->
viewModel.nibUri = nibUri.toUri()
updateDocumentPreview(binding.layoutUploadNib)
}
// Prefill spinner for store types
preselectStoreType(store.storeTypeId)
// Prefill province, city, and subdistrict
preselectProvinceCitySubdistrict(
provinceId = store.provinceId,
cityId = store.cityId,
subdistrictId = store.subdistrict
)
validateRequiredFields()
}
}
binding.btnRegister.setOnClickListener {
doUpdateStoreProfile()
}
} else {
binding.btnRegister.setOnClickListener {
if (viewModel.validateForm()) viewModel.registerStore(this)
else Toast.makeText(this, "Harap lengkapi semua field yang wajib diisi", Toast.LENGTH_SHORT).show()
}
}
}
private fun preselectStoreType(storeTypeId: Int) {
// The adapter is created in setupStoreTypeSpinner(...)
val adapter = binding.spinnerStoreType.adapter
if (adapter != null) {
val count = adapter.count
for (i in 0 until count) {
val item = adapter.getItem(i) as? StoreTypesItem
if (item?.id == storeTypeId) {
binding.spinnerStoreType.setSelection(i, false)
break
}
}
}
}
private fun preselectProvinceCitySubdistrict(
provinceId: Int,
cityId: String,
subdistrictId: String
) {
// Province first (this will trigger cities fetch)
val provCount = provinceAdapter.count
for (i in 0 until provCount) {
if (provinceAdapter.getProvinceId(i) == provinceId) {
binding.spinnerProvince.setSelection(i, false)
break
}
}
// When cities arrive, select the city, then load subdistricts
viewModel.citiesState.observe(this) { state ->
if (state is Result.Success) {
val cityCount = cityAdapter.count
for (i in 0 until cityCount) {
if (cityAdapter.getCityId(i) == cityId) {
binding.spinnerCity.setSelection(i, false)
break
}
}
}
}
// When subdistricts arrive, select the subdistrict
viewModel.subdistrictState.observe(this) { state ->
if (state is Result.Success) {
val subCount = subdistrictAdapter.count
for (i in 0 until subCount) {
if (subdistrictAdapter.getSubdistrictId(i) == subdistrictId) {
binding.spinnerSubdistrict.setSelection(i, false)
break
}
}
}
}
} }
private fun setupHeader() { private fun setupHeader() {
@ -161,16 +313,18 @@ class RegisterStoreActivity : AppCompatActivity() {
viewModel.ktpUri != null && viewModel.ktpUri != null &&
viewModel.nibUri != null && viewModel.nibUri != null &&
viewModel.npwpUri != null && viewModel.npwpUri != null &&
viewModel.selectedCouriers.isNotEmpty() viewModel.selectedCouriers.isNotEmpty() &&
!viewModel.accountName.value.isNullOrBlank()
binding.btnRegister.isEnabled = isFormValid
binding.btnRegister.isEnabled = true
if (isFormValid) { if (isFormValid) {
binding.btnRegister.setBackgroundResource(R.drawable.bg_button_active) binding.btnRegister.setBackgroundResource(R.drawable.bg_button_active)
binding.btnRegister.setTextColor(ContextCompat.getColor(this, R.color.white)) binding.btnRegister.setTextColor(ContextCompat.getColor(this, R.color.white))
binding.btnRegister.isEnabled = true
} else { } else {
binding.btnRegister.setBackgroundResource(R.drawable.bg_button_disabled) binding.btnRegister.setBackgroundResource(R.drawable.bg_button_disabled)
binding.btnRegister.setTextColor(ContextCompat.getColor(this, R.color.black_300)) binding.btnRegister.setTextColor(ContextCompat.getColor(this, R.color.black_300))
binding.btnRegister.isEnabled = false
} }
} }
@ -225,6 +379,28 @@ class RegisterStoreActivity : AppCompatActivity() {
} }
} }
viewModel.subdistrictState.observe(this) { state ->
when (state) {
is Result.Loading -> {
Log.d(TAG, "setupobservers: Loading Subdistrict...")
binding.subdistrictProgressBar.visibility = View.VISIBLE
binding.spinnerSubdistrict.isEnabled = false
}
is Result.Success -> {
Log.d(TAG, "setupobservers: Subdistrict loaded successfullti: ${state.data.size} subdistrict")
binding.subdistrictProgressBar.visibility = View.GONE
binding.spinnerSubdistrict.isEnabled = true
subdistrictAdapter.updateData(state.data)
}
is Result.Error -> {
Log.e(TAG, "setupObservers: Error loading subdistrict: ${state.exception.message}")
binding.subdistrictProgressBar.visibility = View.GONE
binding.spinnerCity.isEnabled = true
}
}
}
// Observe registration state // Observe registration state
viewModel.registerState.observe(this) { result -> viewModel.registerState.observe(this) { result ->
when (result) { when (result) {
@ -270,7 +446,7 @@ class RegisterStoreActivity : AppCompatActivity() {
viewModel.errorMessage.observe(this) { errorMsg -> viewModel.errorMessage.observe(this) { errorMsg ->
if (errorMsg.isNotEmpty()) { if (errorMsg.isNotEmpty()) {
Log.e(TAG, "setupStoreTypesObserver: Error loading store types: $errorMsg") Log.e(TAG, "setupStoreTypesObserver: Error loading store types: $errorMsg")
Toast.makeText(this, "Error loading store types: $errorMsg", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Error loading store types: $errorMsg", Toast.LENGTH_SHORT).show()
} }
} }
@ -395,6 +571,11 @@ class RegisterStoreActivity : AppCompatActivity() {
if (cityId != null) { if (cityId != null) {
Log.d(TAG, "Setting city ID: $cityId") Log.d(TAG, "Setting city ID: $cityId")
viewModel.cityId.value = cityId viewModel.cityId.value = cityId
Log.d(TAG, "Fetching subdistrict for city ID: $cityId")
viewModel.getSubdistrict(cityId)
subdistrictAdapter.clear()
binding.spinnerSubdistrict.setSelection(0)
viewModel.selectedCityId = cityId viewModel.selectedCityId = cityId
} else { } else {
Log.e(TAG, "Invalid city ID for position: $position") Log.e(TAG, "Invalid city ID for position: $position")
@ -406,6 +587,61 @@ class RegisterStoreActivity : AppCompatActivity() {
} }
} }
//Setup Subdistrict spinner
binding.spinnerSubdistrict.adapter = subdistrictAdapter
binding.spinnerSubdistrict.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
Log.d(TAG, "Subdistrict selected at position: $position")
val subdistrictId = subdistrictAdapter.getSubdistrictId(position)
if (subdistrictId != null) {
Log.d(TAG, "Setting subdistrict ID: $subdistrictId")
viewModel.subdistrict.value = subdistrictId
viewModel.selectedSubdistrict = subdistrictId
} else {
Log.e(TAG, "Invalid subdistrict ID for position: $position")
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
Log.d(TAG, "No city selected")
}
}
binding.spinnerBankName.adapter = bankAdapter
binding.spinnerBankName.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
Log.d(TAG, "Bank selected at position: $position")
val bankName = bankAdapter.getBankName(position)
if (bankName != null) {
Log.d(TAG, "Setting bank name: $bankName")
viewModel.bankName.value = bankName
viewModel.selectedBankName = bankName
// Optional: Log the selected bank details
val selectedBank = bankAdapter.getBankItem(position)
selectedBank?.let {
Log.d(TAG, "Selected bank: ${it.bankName} (Code: ${it.bankCode})")
}
// Hide progress bar if it was showing
binding.bankNameProgressBar.visibility = View.GONE
} else {
Log.e(TAG, "Invalid bank name for position: $position")
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
Log.d(TAG, "No bank selected")
viewModel.selectedBankName = null
}
}
// Add initial hints to the spinners // Add initial hints to the spinners
if (provinceAdapter.isEmpty) { if (provinceAdapter.isEmpty) {
Log.d(TAG, "Adding default province hint") Log.d(TAG, "Adding default province hint")
@ -417,6 +653,16 @@ class RegisterStoreActivity : AppCompatActivity() {
cityAdapter.add("Pilih Kabupaten/Kota") cityAdapter.add("Pilih Kabupaten/Kota")
} }
if (subdistrictAdapter.isEmpty) {
Log.d(TAG, "Adding default kecamatan hint")
subdistrictAdapter.add("Pilih Kecamatan")
}
if (bankAdapter.isEmpty) {
Log.d(TAG, "Adding default bank hint")
bankAdapter.add("Pilih Bank")
}
Log.d(TAG, "setupSpinners: Province and city spinners setup completed") Log.d(TAG, "setupSpinners: Province and city spinners setup completed")
} }
@ -500,44 +746,44 @@ class RegisterStoreActivity : AppCompatActivity() {
validateRequiredFields() validateRequiredFields()
} }
private fun setupMap() { // private fun setupMap() {
Log.d(TAG, "setupMap: Setting up map container") // Log.d(TAG, "setupMap: Setting up map container")
// This would typically integrate with Google Maps SDK // // This would typically integrate with Google Maps SDK
// For simplicity, we're just using a placeholder // // For simplicity, we're just using a placeholder
binding.mapContainer.setOnClickListener { // binding.mapContainer.setOnClickListener {
Log.d(TAG, "Map container clicked, checking location permission") // Log.d(TAG, "Map container clicked, checking location permission")
// Request location permission if not granted // // Request location permission if not granted
if (ContextCompat.checkSelfPermission( // if (ContextCompat.checkSelfPermission(
this, // this,
Manifest.permission.ACCESS_FINE_LOCATION // Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED // ) != PackageManager.PERMISSION_GRANTED
) { // ) {
Log.d(TAG, "Location permission not granted, requesting permission") // Log.d(TAG, "Location permission not granted, requesting permission")
ActivityCompat.requestPermissions( // ActivityCompat.requestPermissions(
this, // this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), // arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
LOCATION_PERMISSION_REQUEST // LOCATION_PERMISSION_REQUEST
) // )
viewModel.latitude.value = "-6.2088" // viewModel.latitude.value = "-6.2088"
viewModel.longitude.value = "106.8456" // viewModel.longitude.value = "106.8456"
Log.d(TAG, "Location permission granted, setting default location") // Log.d(TAG, "Location permission granted, setting default location")
Toast.makeText(this, "Lokasi dipilih", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Lokasi dipilih", Toast.LENGTH_SHORT).show()
Log.d(TAG, "Default location set - Lat: ${viewModel.latitude.value}, Long: ${viewModel.longitude.value}") // Log.d(TAG, "Default location set - Lat: ${viewModel.latitude.value}, Long: ${viewModel.longitude.value}")
Toast.makeText(this, "Lokasi dipilih", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Lokasi dipilih", Toast.LENGTH_SHORT).show()
} else { // } else {
Log.d(TAG, "Location permission already granted, setting location") // Log.d(TAG, "Location permission already granted, setting location")
// Show map selection UI // // Show map selection UI
// This would typically launch Maps UI for location selection // // This would typically launch Maps UI for location selection
// For now, we'll just set some dummy coordinates // // For now, we'll just set some dummy coordinates
viewModel.latitude.value = "-6.2088" // viewModel.latitude.value = "-6.2088"
viewModel.longitude.value = "106.8456" // viewModel.longitude.value = "106.8456"
Log.d(TAG, "Location set - Lat: ${viewModel.latitude.value}, Long: ${viewModel.longitude.value}") // Log.d(TAG, "Location set - Lat: ${viewModel.latitude.value}, Long: ${viewModel.longitude.value}")
Toast.makeText(this, "Lokasi dipilih", Toast.LENGTH_SHORT).show() // Toast.makeText(this, "Lokasi dipilih", Toast.LENGTH_SHORT).show()
} // }
} // }
//
Log.d(TAG, "setupMap: Map container setup completed") // Log.d(TAG, "setupMap: Map container setup completed")
} // }
private fun setupDataBinding() { private fun setupDataBinding() {
Log.d(TAG, "setupDataBinding: Setting up two-way data binding for text fields") Log.d(TAG, "setupDataBinding: Setting up two-way data binding for text fields")
@ -617,25 +863,36 @@ class RegisterStoreActivity : AppCompatActivity() {
validateRequiredFields() validateRequiredFields()
} }
}) })
//
// binding.etSubdistrict.addTextChangedListener(object : TextWatcher {
// override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
// override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
// override fun afterTextChanged(s: Editable?) {
// viewModel.subdistrict.value = s.toString()
// Log.d(TAG, "Subdistrict updated: ${s.toString()}")
// validateRequiredFields()
// }
// })
binding.etSubdistrict.addTextChangedListener(object : TextWatcher { // binding.etBankName.addTextChangedListener(object: TextWatcher {
// override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
// override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
// override fun afterTextChanged(s: Editable?) {
// viewModel.bankName.value = s.toString()
// Log.d(TAG, "Bank name updated: ${s.toString()}")
// validateRequiredFields()
// }
// })
binding.etAccountName.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) { override fun afterTextChanged(s: Editable?) {
viewModel.subdistrict.value = s.toString() viewModel.accountName.value = s.toString()
Log.d(TAG, "Subdistrict updated: ${s.toString()}") Log.d(TAG, "Account Name updated: ${s.toString()}")
validateRequiredFields() validateRequiredFields()
} }
})
binding.etBankName.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
viewModel.bankName.value = s.toString()
Log.d(TAG, "Bank name updated: ${s.toString()}")
validateRequiredFields()
}
}) })
Log.d(TAG, "setupDataBinding: Text field data binding setup completed") Log.d(TAG, "setupDataBinding: Text field data binding setup completed")
@ -735,6 +992,63 @@ class RegisterStoreActivity : AppCompatActivity() {
} }
} }
private fun doUpdateStoreProfile() {
val nameBody: RequestBody = (viewModel.storeName.value ?: "")
.toRequestBody("text/plain".toMediaTypeOrNull())
val typeBody: RequestBody = ((viewModel.storeTypeId.value ?: 0).toString())
.toRequestBody("text/plain".toMediaTypeOrNull())
val descBody: RequestBody = (viewModel.storeDescription.value ?: "")
.toRequestBody("text/plain".toMediaTypeOrNull())
val onLeaveBody: RequestBody = "false"
.toRequestBody("text/plain".toMediaTypeOrNull())
// --- Build Multipart for store image (optional) ---
// Prefer compressing images to keep payload small; fall back to raw copy if needed.
val storeImgPart: MultipartBody.Part? = viewModel.storeImageUri?.let { uri ->
try {
// (A) Optional safety check: only allow jpg/png/webp
val allowed = Regex("^(jpg|jpeg|png|webp)$", RegexOption.IGNORE_CASE)
if (!ImageUtils.isAllowedFileType(this, uri, allowed)) {
Toast.makeText(this, "Format gambar tidak didukung", Toast.LENGTH_SHORT).show()
null
} else {
// (B) Compress for upload (ke cacheDir), then build multipart
val compressed: File = ImageUtils.compressImage(
context = this,
uri = uri,
filename = "storeimg", // prefix
maxWidth = 1024,
maxHeight = 1024,
quality = 80
)
FileUtils.createMultipartFromFile("storeimg", compressed)
}
} catch (e: Exception) {
// If compression fails, try raw copy as fallback
val rawFile = FileUtils.createTempFileFromUri(this, uri)
rawFile?.let { FileUtils.createMultipartFromFile("storeimg", it) }
}
}
myStoreViewModel.updateStoreProfile(
storeName = nameBody,
storeType = typeBody,
description = descBody,
isOnLeave = onLeaveBody,
storeImage = storeImgPart
)
myStoreViewModel.updateStoreProfileResult.observe(this) {
Toast.makeText(this, "Pengajuan ulang berhasil dikirim", Toast.LENGTH_SHORT).show()
finish()
}
myStoreViewModel.errorMessage.observe(this) {
if (!it.isNullOrEmpty()) {
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
}
}
}
companion object { companion object {
private const val TAG = "RegisterStoreActivity" private const val TAG = "RegisterStoreActivity"
} }

View File

@ -15,11 +15,15 @@ import android.widget.Spinner
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.store.profile.Payment 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.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.utils.ImageUtils.compressImage
import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.SessionManager
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
@ -38,6 +42,8 @@ class BalanceTopUpActivity : AppCompatActivity() {
private lateinit var spinnerPaymentMethod: Spinner private lateinit var spinnerPaymentMethod: Spinner
private lateinit var edtTransactionDate: EditText private lateinit var edtTransactionDate: EditText
private lateinit var datePickerIcon: ImageView private lateinit var datePickerIcon: ImageView
private lateinit var layoutMBankingInstructions: View
private lateinit var layoutATMInstructions: View
private lateinit var btnSend: Button private lateinit var btnSend: Button
private lateinit var sessionManager: SessionManager private lateinit var sessionManager: SessionManager
@ -52,7 +58,22 @@ class BalanceTopUpActivity : AppCompatActivity() {
val imageUri = result.data?.data val imageUri = result.data?.data
imageUri?.let { imageUri?.let {
selectedImageUri = it selectedImageUri = it
imgPreview.setImageURI(it)
// Compress the image before displaying it
val compressedFile = compressImage(
context = this,
uri = it,
filename = "topup_img",
maxWidth = 1024,
maxHeight = 1024,
quality = 80
)
// Display the compressed image
selectedImageUri = Uri.fromFile(compressedFile)
imgPreview.setImageURI(Uri.fromFile(compressedFile))
validateForm()
} }
} }
} }
@ -71,6 +92,8 @@ class BalanceTopUpActivity : AppCompatActivity() {
spinnerPaymentMethod = findViewById(R.id.spinner_metode_bayar) spinnerPaymentMethod = findViewById(R.id.spinner_metode_bayar)
edtTransactionDate = findViewById(R.id.edt_tgl_transaksi) edtTransactionDate = findViewById(R.id.edt_tgl_transaksi)
datePickerIcon = findViewById(R.id.img_date_picker) datePickerIcon = findViewById(R.id.img_date_picker)
layoutMBankingInstructions = findViewById(R.id.layout_mbanking_instructions)
layoutATMInstructions = findViewById(R.id.layout_atm_instructions)
btnSend = findViewById(R.id.btn_send) btnSend = findViewById(R.id.btn_send)
// Setup header title // Setup header title
@ -98,10 +121,27 @@ class BalanceTopUpActivity : AppCompatActivity() {
// Fetch payment methods // Fetch payment methods
fetchPaymentMethods() fetchPaymentMethods()
setupClickListeners("1234567890")
// Setup submit button // Setup submit button
btnSend.setOnClickListener { btnSend.setOnClickListener {
submitForm() submitForm()
} }
// Validate form when any input changes
edtNominal.doAfterTextChanged { validateForm() }
edtTransactionDate.doAfterTextChanged { validateForm() }
spinnerPaymentMethod.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
selectedPaymentId = paymentMethods[position].id
validateForm()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
selectedPaymentId = -1
validateForm()
}
}
} }
private fun openGallery() { private fun openGallery() {
@ -200,6 +240,24 @@ class BalanceTopUpActivity : AppCompatActivity() {
} }
} }
private fun validateForm() {
val isNominalFilled = edtNominal.text.toString().trim().isNotEmpty()
val isPaymentMethodSelected = selectedPaymentId != -1
val isTransactionDateFilled = edtTransactionDate.text.toString().trim().isNotEmpty()
val isImageSelected = selectedImageUri != null
val valid = isNominalFilled && isPaymentMethodSelected && isTransactionDateFilled && isImageSelected
btnSend.isEnabled = valid
btnSend.setTextColor(
if (valid) ContextCompat.getColor(this, R.color.white)
else ContextCompat.getColor(this, R.color.black_300)
)
btnSend.setBackgroundResource(
if (valid) R.drawable.bg_button_active
else R.drawable.bg_button_disabled
)
}
private fun submitForm() { private fun submitForm() {
// Prevent multiple clicks // Prevent multiple clicks
if (!btnSend.isEnabled) { if (!btnSend.isEnabled) {
@ -316,7 +374,7 @@ class BalanceTopUpActivity : AppCompatActivity() {
// Show a dialog with the success message // Show a dialog with the success message
runOnUiThread { runOnUiThread {
androidx.appcompat.app.AlertDialog.Builder(this@BalanceTopUpActivity) AlertDialog.Builder(this@BalanceTopUpActivity)
.setTitle("Berhasil") .setTitle("Berhasil")
.setMessage(successMessage) .setMessage(successMessage)
.setPositiveButton("OK") { dialog, _ -> .setPositiveButton("OK") { dialog, _ ->
@ -350,7 +408,7 @@ class BalanceTopUpActivity : AppCompatActivity() {
// Show a dialog with the error message // Show a dialog with the error message
runOnUiThread { runOnUiThread {
androidx.appcompat.app.AlertDialog.Builder(this@BalanceTopUpActivity) AlertDialog.Builder(this@BalanceTopUpActivity)
.setTitle("Error Response") .setTitle("Error Response")
.setMessage(errorMessage) .setMessage(errorMessage)
.setPositiveButton("OK") { dialog, _ -> .setPositiveButton("OK") { dialog, _ ->
@ -392,4 +450,46 @@ class BalanceTopUpActivity : AppCompatActivity() {
return tempFile return tempFile
} }
private fun setupClickListeners(bankAccountNumber: String) {
// Instructions clicks
layoutMBankingInstructions.setOnClickListener {
showInstructions("mBanking", bankAccountNumber)
}
layoutATMInstructions.setOnClickListener {
showInstructions("ATM", bankAccountNumber)
}
}
private fun showInstructions(type: String, bankAccountNumber: String) {
// Implementasi tampilkan instruksi
val instructions = when (type) {
"mBanking" -> listOf(
"1. Login ke aplikasi mobile banking",
"2. Pilih menu Transfer",
"3. Pilih menu Antar Rekening",
"4. Masukkan nomor rekening tujuan: $bankAccountNumber",
"5. Masukkan nominal saldo yang ingin diisi",
"6. Konfirmasi dan selesaikan transfer"
)
"ATM" -> listOf(
"1. Masukkan kartu ATM dan PIN",
"2. Pilih menu Transfer",
"3. Pilih menu Antar Rekening",
"4. Masukkan kode bank dan nomor rekening tujuan: $bankAccountNumber",
"5. Masukkan nominal saldo yang ingin diisi",
"6. Konfirmasi dan selesaikan transfer"
)
else -> emptyList()
}
// Tampilkan instruksi dalam dialog
val dialog = AlertDialog.Builder(this)
.setTitle("Petunjuk Transfer $type")
.setItems(instructions.toTypedArray(), null)
.setPositiveButton("Tutup", null)
.create()
dialog.show()
}
} }

View File

@ -91,10 +91,10 @@ class DetailStoreProfileActivity : AppCompatActivity() {
viewModel.fetchStoreTypes() viewModel.fetchStoreTypes()
viewModel.myStoreProfile.observe(this) { viewModel.myStoreProfile.observe(this) {
currentStore = it currentStore = it?.store
currentStoreLoaded = true currentStoreLoaded = true
if (storeTypesLoaded) setupStoreTypeSpinner(storeTypesList) if (storeTypesLoaded) setupStoreTypeSpinner(storeTypesList)
updateUI(it) updateUI(it?.store)
} }
viewModel.storeTypes.observe(this) { viewModel.storeTypes.observe(this) {

View File

@ -1,6 +1,5 @@
package com.alya.ecommerce_serang.ui.profile.mystore.profile.address package com.alya.ecommerce_serang.ui.profile.mystore.profile.address
import android.app.Activity
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View import android.view.View
@ -14,13 +13,16 @@ import com.alya.ecommerce_serang.BuildConfig
import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.City 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.Province
import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictsItem
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressesItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.AddressRepository import com.alya.ecommerce_serang.data.repository.AddressRepository
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.databinding.ActivityDetailStoreAddressBinding 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.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
class DetailStoreAddressActivity : AppCompatActivity() { class DetailStoreAddressActivity : AppCompatActivity() {
@ -30,10 +32,15 @@ class DetailStoreAddressActivity : AppCompatActivity() {
private var selectedProvinceId: String? = null private var selectedProvinceId: String? = null
private var selectedCityId: String? = null private var selectedCityId: String? = null
private var selectedSubdistrict: String? = null
private var provinces: List<Province> = emptyList() private var provinces: List<Province> = emptyList()
private var cities: List<City> = emptyList() private var cities: List<City> = emptyList()
private var subdistrict: List<SubdistrictsItem> = emptyList()
private var currentAddress: AddressesItem? = null
private val TAG = "StoreAddressActivity" // private lateinit var subdistrictAdapter: SubdsitrictAdapter
private val TAG = "DetailStoreAddressActivity"
private val viewModel: AddressViewModel by viewModels { private val viewModel: AddressViewModel by viewModels {
BaseViewModelFactory { BaseViewModelFactory {
@ -58,13 +65,15 @@ class DetailStoreAddressActivity : AppCompatActivity() {
binding.tvError.visibility = View.GONE binding.tvError.visibility = View.GONE
// Set up header title // Set up header title
binding.header.headerTitle.text = "Atur Alamat Toko" binding.headerAddressStore.headerTitle.text = "Atur Alamat Toko"
// Set up back button // Set up back button
binding.header.headerLeftIcon.setOnClickListener { binding.headerAddressStore.headerLeftIcon.setOnClickListener {
onBackPressedDispatcher.onBackPressed() onBackPressedDispatcher.onBackPressed()
} }
// subdistrictAdapter = SubdsitrictAdapter(this)
setupSpinners() setupSpinners()
setupObservers() setupObservers()
setupSaveButton() setupSaveButton()
@ -113,11 +122,26 @@ class DetailStoreAddressActivity : AppCompatActivity() {
binding.spinnerCity.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { binding.spinnerCity.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
selectedCityId = if (position > 0) cities[position - 1].cityId else null selectedCityId = if (position > 0) cities[position - 1].cityId else null
viewModel.getSubdistrict(selectedCityId.toString())
checkAllFieldsFilled() checkAllFieldsFilled()
} }
override fun onNothingSelected(p0: AdapterView<*>?) {} override fun onNothingSelected(p0: AdapterView<*>?) {}
} }
binding.spinnerSubdistrict.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
selectedSubdistrict = if (position > 0) subdistrict[position - 1].subdistrictName else null
checkAllFieldsFilled()
}
override fun onNothingSelected(p0: AdapterView<*>?) {}
}
} }
private fun setupObservers() { private fun setupObservers() {
@ -176,19 +200,58 @@ class DetailStoreAddressActivity : AppCompatActivity() {
} }
} }
viewModel.subdistrictState.observe(this) { result ->
when (result) {
is com.alya.ecommerce_serang.data.repository.Result.Loading -> {
showSubLoading(true)
}
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
showSubLoading(false)
subdistrict = result.data
val subdistrictNames = mutableListOf("Pilih Kecamatan")
subdistrictNames.addAll(result.data.map { it.subdistrictName })
val adapter = ArrayAdapter(
this,
android.R.layout.simple_spinner_item,
subdistrictNames
)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.spinnerSubdistrict.adapter = adapter
// Compare by name, since stored value is the subdistrict name
viewModel.storeAddress.value?.let { address ->
val index = subdistrict.indexOfFirst { it.subdistrictName == address.subdistrict }
if (index != -1) {
binding.spinnerSubdistrict.setSelection(index + 1)
}
}
}
is Result.Error -> {
showSubLoading(false)
Log.e(TAG, "Error: ${result.exception.message}", result.exception)
}
}
}
// Observe store address data // Observe store address data
viewModel.storeAddress.observe(this) { address -> viewModel.storeAddress.observe(this) { address ->
currentAddress = address
Log.d(TAG, "Received store address: $address") Log.d(TAG, "Received store address: $address")
address?.let { address?.let {
// Set the fields // Set the fields
binding.edtStreet.setText(it.street) binding.edtStreet.setText(it.street)
binding.edtSubdistrict.setText(it.subdistrict) // binding.edtSubdistrict.setText(it.subdistrict)
binding.edtDetailAddress.setText(it.detail ?: "") binding.edtDetailAddress.setText(it.detail ?: "")
binding.edtPostalCode.setText(it.postalCode) binding.edtPostalCode.setText(it.postalCode)
binding.edtLatitude.setText(it.latitude.toString()) binding.edtLatitude.setText(it.latitude.toString())
binding.edtLongitude.setText(it.longitude.toString()) binding.edtLongitude.setText(it.longitude.toString())
selectedProvinceId = it.provinceId selectedProvinceId = it.provinceId
selectedCityId = it.cityId selectedCityId = it.cityId
selectedSubdistrict = it.subdistrict
// Find province index and select it after provinces are loaded // Find province index and select it after provinces are loaded
if (provinces.isNotEmpty()) { if (provinces.isNotEmpty()) {
@ -214,11 +277,12 @@ class DetailStoreAddressActivity : AppCompatActivity() {
} }
// Observe save success // Observe save success
viewModel.saveSuccess.observe(this) { viewModel.saveSuccess.observe(this) { success ->
if (it) { if (success) {
Toast.makeText(this, "Alamat berhasil disimpan", Toast.LENGTH_SHORT).show() Log.d(TAG, "Address updated successfully")
setResult(Activity.RESULT_OK)
finish() finish()
} else {
Log.e(TAG, "Failed to update address")
} }
} }
} }
@ -226,14 +290,15 @@ class DetailStoreAddressActivity : AppCompatActivity() {
private fun setupSaveButton() { private fun setupSaveButton() {
binding.btnSaveAddress.setOnClickListener { binding.btnSaveAddress.setOnClickListener {
val street = binding.edtStreet.text.toString() val street = binding.edtStreet.text.toString()
val subdistrict = binding.edtSubdistrict.text.toString()
val detail = binding.edtDetailAddress.text.toString() val detail = binding.edtDetailAddress.text.toString()
val postalCode = binding.edtPostalCode.text.toString() val postalCode = binding.edtPostalCode.text.toString()
val latitude = binding.edtLatitude.text.toString().toDoubleOrNull() ?: 0.0 val latitude = binding.edtLatitude.text.toString()
val longitude = binding.edtLongitude.text.toString().toDoubleOrNull() ?: 0.0 val longitude = binding.edtLongitude.text.toString()
val city = cities.find { it.cityId == selectedCityId } val city = cities.find { it.cityId == selectedCityId }
val province = provinces.find { it.provinceId == selectedProvinceId } val province = provinces.find { it.provinceId == selectedProvinceId }
val subdistrictName = subdistrict.find { it.subdistrictName == selectedSubdistrict }?.subdistrictName.toString()
Log.d(TAG, "Subdistrict name: $subdistrictName")
// Validate required fields // Validate required fields
if (selectedProvinceId.isNullOrEmpty() || city == null || street.isEmpty() || subdistrict.isEmpty() || postalCode.isEmpty()) { if (selectedProvinceId.isNullOrEmpty() || city == null || street.isEmpty() || subdistrict.isEmpty() || postalCode.isEmpty()) {
@ -241,19 +306,35 @@ class DetailStoreAddressActivity : AppCompatActivity() {
return@setOnClickListener return@setOnClickListener
} }
// Save address val oldAddress = currentAddress ?: return@setOnClickListener
viewModel.saveStoreAddress( val newAddress = oldAddress.copy(
provinceId = selectedProvinceId!!, provinceId = selectedProvinceId!!,
provinceName = province?.provinceName ?: "",
cityId = city.cityId, cityId = city.cityId,
cityName = city.cityName,
street = street, street = street,
subdistrict = subdistrict, subdistrict = subdistrictName,
detail = detail, detail = detail,
postalCode = postalCode, postalCode = postalCode,
latitude = latitude, latitude = latitude,
longitude = longitude longitude = longitude,
phone = oldAddress.phone,
recipient = oldAddress.recipient ?: "",
isStoreLocation = oldAddress.isStoreLocation,
villageId = oldAddress.villageId
) )
viewModel.saveStoreAddress(oldAddress, newAddress)
// Save address
// viewModel.saveStoreAddress(
// provinceId = selectedProvinceId!!,
// provinceName = province?.provinceName ?: "",
// cityId = city.cityId,
// cityName = city.cityName,
// street = street,
// subdistrict = subdistrict,
// detail = detail,
// postalCode = postalCode,
// latitude = latitude,
// longitude = longitude
// )
} }
} }
@ -264,15 +345,14 @@ class DetailStoreAddressActivity : AppCompatActivity() {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
} }
binding.edtStreet.addTextChangedListener(watcher) binding.edtStreet.addTextChangedListener(watcher)
binding.edtSubdistrict.addTextChangedListener(watcher)
binding.edtPostalCode.addTextChangedListener(watcher) binding.edtPostalCode.addTextChangedListener(watcher)
} }
private fun checkAllFieldsFilled() { private fun checkAllFieldsFilled() {
val allValid = !selectedProvinceId.isNullOrEmpty() val allValid = !selectedProvinceId.isNullOrEmpty()
&& !selectedCityId.isNullOrEmpty() && !selectedCityId.isNullOrEmpty()
&& !selectedSubdistrict.isNullOrEmpty()
&& binding.edtStreet.text.isNotBlank() && binding.edtStreet.text.isNotBlank()
&& binding.edtSubdistrict.text.isNotBlank()
&& binding.edtPostalCode.text.isNotBlank() && binding.edtPostalCode.text.isNotBlank()
binding.btnSaveAddress.let { binding.btnSaveAddress.let {
@ -284,6 +364,7 @@ class DetailStoreAddressActivity : AppCompatActivity() {
it.isEnabled = false it.isEnabled = false
it.setBackgroundResource(R.drawable.bg_button_disabled) it.setBackgroundResource(R.drawable.bg_button_disabled)
it.setTextColor(getColor(R.color.black_300)) it.setTextColor(getColor(R.color.black_300))
Toast.makeText(this, "Periksa dan lenkapi alamat anda", Toast.LENGTH_SHORT).show()
} }
} }
} }
@ -298,6 +379,12 @@ class DetailStoreAddressActivity : AppCompatActivity() {
binding.spinnerCity.visibility = if (isLoading) View.GONE else View.VISIBLE binding.spinnerCity.visibility = if (isLoading) View.GONE else View.VISIBLE
} }
private fun showSubLoading(isLoading: Boolean) {
binding.subdistrictProgressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
binding.spinnerSubdistrict.visibility = if (isLoading) View.GONE else View.VISIBLE
}
private fun showError(message: String) { private fun showError(message: String) {
binding.progressBar.visibility = View.GONE binding.progressBar.visibility = View.GONE
binding.tvError.visibility = View.VISIBLE binding.tvError.visibility = View.VISIBLE

View File

@ -6,9 +6,12 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.widget.AdapterView
import android.widget.Button import android.widget.Button
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.Spinner
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -18,6 +21,7 @@ import com.alya.ecommerce_serang.data.api.dto.PaymentInfo
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.PaymentInfoRepository import com.alya.ecommerce_serang.data.repository.PaymentInfoRepository
import com.alya.ecommerce_serang.databinding.ActivityPaymentInfoBinding import com.alya.ecommerce_serang.databinding.ActivityPaymentInfoBinding
import com.alya.ecommerce_serang.ui.order.address.BankAdapter
import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.UriToFileConverter import com.alya.ecommerce_serang.utils.UriToFileConverter
@ -32,6 +36,7 @@ class PaymentInfoActivity : AppCompatActivity() {
private lateinit var sessionManager: SessionManager private lateinit var sessionManager: SessionManager
private var selectedQrisImageUri: Uri? = null private var selectedQrisImageUri: Uri? = null
private var selectedQrisImageFile: File? = null private var selectedQrisImageFile: File? = null
private lateinit var bankAdapter: BankAdapter
// Store form data between dialog reopenings // Store form data between dialog reopenings
private var savedBankName: String = "" private var savedBankName: String = ""
@ -95,6 +100,7 @@ class PaymentInfoActivity : AppCompatActivity() {
onBackPressedDispatcher.onBackPressed() onBackPressedDispatcher.onBackPressed()
} }
bankAdapter = BankAdapter(this)
setupRecyclerView() setupRecyclerView()
setupObservers() setupObservers()
@ -173,10 +179,47 @@ class PaymentInfoActivity : AppCompatActivity() {
builder.setView(dialogView) builder.setView(dialogView)
val dialog = builder.create() val dialog = builder.create()
val spinnerBankName = dialogView.findViewById<Spinner>(R.id.spinner_bank_name)
val progressBarBank = dialogView.findViewById<ProgressBar>(R.id.bank_name_progress_bar)
spinnerBankName.adapter = bankAdapter
spinnerBankName.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
Log.d(TAG, "Bank selected at position: $position")
val bankName = bankAdapter.getBankName(position)
if (bankName != null) {
Log.d(TAG, "Setting bank name: $bankName")
viewModel.bankName.value = bankName
viewModel.selectedBankName = bankName
// Optional: Log the selected bank details
val selectedBank = bankAdapter.getBankItem(position)
selectedBank?.let {
Log.d(TAG, "Selected bank: ${it.bankName} (Code: ${it.bankCode})")
}
// Hide progress bar if it was showing
progressBarBank.visibility = View.GONE
} else {
Log.e(TAG, "Invalid bank name for position: $position")
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
Log.d(TAG, "No bank selected")
viewModel.selectedBankName = null
}
}
// Get references to views in the dialog // Get references to views in the dialog
val btnAddQris = dialogView.findViewById<Button>(R.id.btn_add_qris) val btnAddQris = dialogView.findViewById<Button>(R.id.btn_add_qris)
val bankNameEditText = dialogView.findViewById<EditText>(R.id.edt_bank_name) // val spinnerBankName = dialogView.findViewById<Spinner>(R.id.spinner_bank_name)
val bankNumberEditText = dialogView.findViewById<EditText>(R.id.edt_bank_number) val bankNumberEditText = dialogView.findViewById<EditText>(R.id.edt_bank_number)
val accountNameEditText = dialogView.findViewById<EditText>(R.id.edt_account_name) val accountNameEditText = dialogView.findViewById<EditText>(R.id.edt_account_name)
val qrisPreview = dialogView.findViewById<ImageView>(R.id.iv_qris_preview) val qrisPreview = dialogView.findViewById<ImageView>(R.id.iv_qris_preview)
@ -185,7 +228,10 @@ class PaymentInfoActivity : AppCompatActivity() {
// When reopening, restore the previously entered values // When reopening, restore the previously entered values
if (isReopened) { if (isReopened) {
bankNameEditText.setText(savedBankName) val savedPosition = bankAdapter.findPositionByName(savedBankName)
if (savedPosition >= 0) {
spinnerBankName.setSelection(savedPosition)
}
bankNumberEditText.setText(savedBankNumber) bankNumberEditText.setText(savedBankNumber)
accountNameEditText.setText(savedAccountName) accountNameEditText.setText(savedAccountName)
@ -199,7 +245,7 @@ class PaymentInfoActivity : AppCompatActivity() {
btnAddQris.setOnClickListener { btnAddQris.setOnClickListener {
// Save the current values before dismissing // Save the current values before dismissing
savedBankName = bankNameEditText.text.toString().trim() savedBankName = viewModel.selectedBankName ?: ""
savedBankNumber = bankNumberEditText.text.toString().trim() savedBankNumber = bankNumberEditText.text.toString().trim()
savedAccountName = accountNameEditText.text.toString().trim() savedAccountName = accountNameEditText.text.toString().trim()
@ -212,13 +258,13 @@ class PaymentInfoActivity : AppCompatActivity() {
} }
btnSave.setOnClickListener { btnSave.setOnClickListener {
val bankName = bankNameEditText.text.toString().trim() val bankName = viewModel.selectedBankName ?: ""
val bankNumber = bankNumberEditText.text.toString().trim() val bankNumber = bankNumberEditText.text.toString().trim()
val accountName = accountNameEditText.text.toString().trim() val accountName = accountNameEditText.text.toString().trim()
// Validation // Validation
if (bankName.isEmpty()) { if (bankName.isEmpty()) {
showSnackbar("Nama bank tidak boleh kosong") showSnackbar("Pilih nama bank terlebih dahulu")
return@setOnClickListener return@setOnClickListener
} }

View File

@ -17,6 +17,7 @@ import com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment.DetailShipmen
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.google.gson.Gson import com.google.gson.Gson
import java.text.NumberFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
@ -96,7 +97,7 @@ class SellsAdapter(
val product = order.orderItems?.firstOrNull() val product = order.orderItems?.firstOrNull()
tvSellsProductName.text = product?.productName tvSellsProductName.text = product?.productName
tvSellsProductQty.text = "x${product?.quantity}" tvSellsProductQty.text = "x${product?.quantity}"
tvSellsProductPrice.text = formatPrice(product?.price.toString()) tvSellsProductPrice.text = product?.price?.let { formatPrice(it.toDouble().toInt()) }
val fullImageUrl = when (val img = product?.productImage) { val fullImageUrl = when (val img = product?.productImage) {
is String -> { is String -> {
@ -169,7 +170,7 @@ class SellsAdapter(
val product = order.orderItems?.firstOrNull() val product = order.orderItems?.firstOrNull()
tvSellsProductName.text = product?.productName tvSellsProductName.text = product?.productName
tvSellsProductQty.text = "x${product?.quantity}" tvSellsProductQty.text = "x${product?.quantity}"
tvSellsProductPrice.text = formatPrice(product?.price.toString()) tvSellsProductPrice.text = product?.price?.let { formatPrice(it.toDouble().toInt()) }
val fullImageUrl = when (val img = product?.productImage) { val fullImageUrl = when (val img = product?.productImage) {
is String -> { is String -> {
@ -185,7 +186,7 @@ class SellsAdapter(
.into(ivSellsProduct) .into(ivSellsProduct)
tvSellsQty.text = "${order.orderItems?.size} produk" tvSellsQty.text = "${order.orderItems?.size} produk"
tvSellsPrice.text = formatPrice(order.totalAmount.toString()) tvSellsPrice.text = order.totalAmount?.let { formatPrice(it.toDouble().toInt()) }
} }
"paid" -> { "paid" -> {
layoutOrders.visibility = View.GONE layoutOrders.visibility = View.GONE
@ -308,10 +309,9 @@ class SellsAdapter(
} }
} }
private fun formatPrice(price: String): String { private fun formatPrice(amount: Int): String {
val priceDouble = price.toDoubleOrNull() ?: 0.0 val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
val formattedPrice = String.format(Locale("id", "ID"), "Rp%,.0f", priceDouble) return formatter.format(amount.toLong()).replace(",00", "")
return formattedPrice
} }
} }
} }

View File

@ -83,10 +83,10 @@ class SellsListFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
loadSells()
setupRecyclerView() setupRecyclerView()
observeSellsList() observeSellsList()
observePaymentConfirmation() observePaymentConfirmation()
loadSells()
// getAllOrderCountsAndNavigate() // getAllOrderCountsAndNavigate()
} }
@ -211,6 +211,12 @@ class SellsListFragment : Fragment() {
} }
} }
override fun onResume() {
super.onResume()
viewModel.getSellList(status)
observeSellsList()
}
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null

View File

@ -7,8 +7,10 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.City 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.Province
import com.alya.ecommerce_serang.data.api.dto.StoreAddress import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictsItem
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressesItem
import com.alya.ecommerce_serang.data.repository.AddressRepository import com.alya.ecommerce_serang.data.repository.AddressRepository
import com.alya.ecommerce_serang.data.repository.Result
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class AddressViewModel(private val addressRepository: AddressRepository) : ViewModel() { class AddressViewModel(private val addressRepository: AddressRepository) : ViewModel() {
@ -21,8 +23,8 @@ class AddressViewModel(private val addressRepository: AddressRepository) : ViewM
private val _cities = MutableLiveData<List<City>>() private val _cities = MutableLiveData<List<City>>()
val cities: LiveData<List<City>> = _cities val cities: LiveData<List<City>> = _cities
private val _storeAddress = MutableLiveData<StoreAddress?>() private val _storeAddress = MutableLiveData<AddressesItem?>()
val storeAddress: LiveData<StoreAddress?> = _storeAddress val storeAddress: LiveData<AddressesItem?> get() = _storeAddress
private val _isLoading = MutableLiveData<Boolean>() private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading val isLoading: LiveData<Boolean> = _isLoading
@ -31,99 +33,249 @@ class AddressViewModel(private val addressRepository: AddressRepository) : ViewM
val errorMessage: LiveData<String> = _errorMessage val errorMessage: LiveData<String> = _errorMessage
private val _saveSuccess = MutableLiveData<Boolean>() private val _saveSuccess = MutableLiveData<Boolean>()
val saveSuccess: LiveData<Boolean> = _saveSuccess val saveSuccess: LiveData<Boolean> get() = _saveSuccess
private val _subdistrictState = MutableLiveData<Result<List<SubdistrictsItem>>>()
val subdistrictState: LiveData<Result<List<SubdistrictsItem>>> = _subdistrictState
var selectedSubdistrict: String? = null
val subdistrict = MutableLiveData<String>()
fun fetchProvinces() { fun fetchProvinces() {
Log.d(TAG, "fetchProvinces() called")
_isLoading.value = true
viewModelScope.launch { viewModelScope.launch {
try { try {
Log.d(TAG, "Calling addressRepository.getProvinces()")
val response = addressRepository.getProvinces() val response = addressRepository.getProvinces()
Log.d(TAG, "Received provinces response: ${response.size} provinces") if (response.isSuccessful) {
_provinces.value = response _provinces.value = response.body()?.data ?: emptyList()
_isLoading.value = false } else {
Log.e("EditAddressVM", "Failed to get provinces: ${response.message()}")
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error fetching provinces", e) Log.e("EditAddressVM", "Error getting provinces: ${e.message}")
_errorMessage.value = "Failed to load provinces: ${e.message}"
_isLoading.value = false
} }
} }
} }
fun fetchCities(provinceId: String) { fun fetchCities(provinceId: String) {
Log.d(TAG, "fetchCities() called with provinceId: $provinceId")
_isLoading.value = true
viewModelScope.launch { viewModelScope.launch {
try { try {
Log.d(TAG, "Calling addressRepository.getCities()")
val response = addressRepository.getCities(provinceId) val response = addressRepository.getCities(provinceId)
Log.d(TAG, "Received cities response: ${response.size} cities") if (response.isSuccessful) {
_cities.value = response _cities.value = response.body()?.cities ?: emptyList()
_isLoading.value = false } else {
Log.e("EditAddressVM", "Failed to get cities: ${response.message()}")
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error fetching cities", e) Log.e("EditAddressVM", "Error getting cities: ${e.message}")
_errorMessage.value = "Failed to load cities: ${e.message}"
_isLoading.value = false
} }
} }
} }
fun getSubdistrict(cityId: String) {
_subdistrictState.value = Result.Loading
viewModelScope.launch {
try {
selectedSubdistrict = cityId
val result = addressRepository.getListSubdistrict(cityId)
result?.let {
_subdistrictState.postValue(Result.Success(it.subdistricts))
Log.d(TAG, "Cities loaded for province $cityId: ${it.subdistricts.size}")
} ?: run {
_subdistrictState.postValue(Result.Error(Exception("Failed to load cities")))
Log.e(TAG, "City result was null for province $cityId")
}
} catch (e: Exception) {
_subdistrictState.postValue(Result.Error(Exception(e.message ?: "Error loading cities")))
Log.e(TAG, "Error fetching cities for province $cityId", e)
}
}
}
// 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 {
// selecte
// 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 fetchStoreAddress() {
// viewModelScope.launch {
// try {
// val response = addressRepository.getStoreAddress()
// if (response.isSuccessful) {
// val storeAddress = response.body()?.addresses
// ?.firstOrNull { it.isStoreLocation == true }
//
// if (storeAddress != null) {
// _storeAddress.value = storeAddress
// } else {
// Log.d("EditAddressVM", "No store address found")
// }
// } else {
// Log.e("EditAddressVM", "Failed to get addresses: ${response.message()}")
// }
// } catch (e: Exception) {
// Log.e("EditAddressVM", "Error: ${e.message}")
// }
// }
// }
fun fetchStoreAddress() { fun fetchStoreAddress() {
Log.d(TAG, "fetchStoreAddress() called")
_isLoading.value = true
viewModelScope.launch { viewModelScope.launch {
try { try {
Log.d(TAG, "Calling addressRepository.getStoreAddress()")
val response = addressRepository.getStoreAddress() val response = addressRepository.getStoreAddress()
Log.d(TAG, "Received store address response: $response") if (response.isSuccessful) {
_storeAddress.value = response val storeAddress = response.body()?.addresses
_isLoading.value = false ?.firstOrNull { it.isStoreLocation == true }
if (storeAddress != null) {
_storeAddress.value = storeAddress
} else {
Log.d(TAG, "No store address found")
}
} else {
Log.e(TAG, "Failed to get addresses: ${response.message()}")
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error fetching store address", e) Log.e(TAG, "Error: ${e.message}")
_errorMessage.value = "Failed to load store address: ${e.message}"
_isLoading.value = false
} }
} }
} }
fun saveStoreAddress( // fun saveStoreAddress(
provinceId: String, // provinceId: String,
provinceName: String, // provinceName: String,
cityId: String, // cityId: String,
cityName: String, // cityName: String,
street: String, // street: String,
subdistrict: String, // subdistrict: String,
detail: String, // detail: String,
postalCode: String, // postalCode: String,
latitude: Double, // latitude: Double,
longitude: Double // longitude: Double
) { // ) {
Log.d(TAG, "saveStoreAddress() called with provinceId: $provinceId, cityId: $cityId") // Log.d(TAG, "saveStoreAddress() called with provinceId: $provinceId, cityId: $cityId")
_isLoading.value = true // _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
// }
// }
// }
fun saveStoreAddress(oldAddress: AddressesItem, newAddress: AddressesItem) {
val params = buildUpdateBody(oldAddress, newAddress)
if (params.isEmpty()) {
Log.d(TAG, "No changes detected")
_saveSuccess.value = false
return
}
viewModelScope.launch { viewModelScope.launch {
try { try {
Log.d(TAG, "Calling addressRepository.saveStoreAddress()") val response = addressRepository.updateAddress(oldAddress.id, params)
val success = addressRepository.saveStoreAddress( _saveSuccess.value = response.isSuccessful
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) { } catch (e: Exception) {
Log.e(TAG, "Error saving store address", e) Log.e(TAG, "Error: ${e.message}")
_errorMessage.value = "Failed to save address: ${e.message}" _saveSuccess.value = false
_isLoading.value = false
} }
} }
} }
private fun buildUpdateBody(oldAddress: AddressesItem, newAddress: AddressesItem): Map<String, Any> {
val params = mutableMapOf<String, Any>()
fun addIfChanged(key: String, oldValue: Any?, newValue: Any?) {
if (newValue != null && newValue != oldValue) {
params[key] = newValue
}
}
addIfChanged("street", oldAddress.street, newAddress.street)
addIfChanged("province_id", oldAddress.provinceId, newAddress.provinceId)
addIfChanged("detail", oldAddress.detail, newAddress.detail)
addIfChanged("subdistrict", oldAddress.subdistrict, newAddress.subdistrict)
addIfChanged("city_id", oldAddress.cityId, newAddress.cityId)
addIfChanged("village_id", oldAddress.villageId, newAddress.villageId)
addIfChanged("postal_code", oldAddress.postalCode, newAddress.postalCode)
addIfChanged("phone", oldAddress.phone, newAddress.phone)
addIfChanged("recipient", oldAddress.recipient, newAddress.recipient)
addIfChanged("latitude", oldAddress.latitude, newAddress.latitude)
addIfChanged("longitude", oldAddress.longitude, newAddress.longitude)
addIfChanged("is_store_location", oldAddress.isStoreLocation, newAddress.isStoreLocation)
return params
}
} }

View File

@ -7,8 +7,10 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.FcmReq import com.alya.ecommerce_serang.data.api.dto.FcmReq
import com.alya.ecommerce_serang.data.api.dto.ResetPassReq
import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
import com.alya.ecommerce_serang.data.api.response.auth.ResetPassResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.data.repository.Result
@ -27,6 +29,10 @@ class LoginViewModel(private val repository: UserRepository, private val context
private val _message = MutableLiveData<String>() private val _message = MutableLiveData<String>()
val message: LiveData<String> = _message val message: LiveData<String> = _message
private val _resetPasswordState = MutableLiveData<Result<ResetPassResponse>?>()
val resetPasswordState: LiveData<Result<ResetPassResponse>?> = _resetPasswordState
private val sessionManager by lazy { SessionManager(context) } private val sessionManager by lazy { SessionManager(context) }
private fun getAuthenticatedApiService(): ApiService { private fun getAuthenticatedApiService(): ApiService {
@ -69,4 +75,19 @@ class LoginViewModel(private val repository: UserRepository, private val context
} }
} }
fun resetPassword(email: String) {
viewModelScope.launch {
_resetPasswordState.value = Result.Loading
val request = ResetPassReq(emailOrPhone = email)
val result = repository.resetPassword(request)
_resetPasswordState.value = result
}
}
fun clearState() {
_resetPasswordState.value = null
}
} }

View File

@ -10,6 +10,8 @@ import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.dto.Store import com.alya.ecommerce_serang.data.api.dto.Store
import com.alya.ecommerce_serang.data.api.response.auth.StoreTypesItem import com.alya.ecommerce_serang.data.api.response.auth.StoreTypesItem
import com.alya.ecommerce_serang.data.api.response.store.StoreResponse import com.alya.ecommerce_serang.data.api.response.store.StoreResponse
import com.alya.ecommerce_serang.data.api.response.store.profile.Payment
import com.alya.ecommerce_serang.data.api.response.store.profile.Shipping
import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse
import com.alya.ecommerce_serang.data.repository.MyStoreRepository import com.alya.ecommerce_serang.data.repository.MyStoreRepository
import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.data.repository.Result
@ -21,12 +23,20 @@ import java.text.NumberFormat
import java.util.Locale import java.util.Locale
class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() { class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
private val _myStoreProfile = MutableLiveData<Store?>() private var TAG = "MyStoreViewModel"
val myStoreProfile: LiveData<Store?> = _myStoreProfile
private val _myStoreProfile = MutableLiveData<StoreResponse?>()
val myStoreProfile: LiveData<StoreResponse?> = _myStoreProfile
private val _storeTypes = MutableLiveData<List<StoreTypesItem>>() private val _storeTypes = MutableLiveData<List<StoreTypesItem>>()
val storeTypes: LiveData<List<StoreTypesItem>> = _storeTypes val storeTypes: LiveData<List<StoreTypesItem>> = _storeTypes
private val _shipping = MutableLiveData<List<Shipping>>()
val shipping: LiveData<List<Shipping>> = _shipping
private val _payment = MutableLiveData<List<Payment>>()
val payment: LiveData<List<Payment>> = _payment
private val _isLoadingType = MutableLiveData<Boolean>() private val _isLoadingType = MutableLiveData<Boolean>()
val isLoadingType: LiveData<Boolean> = _isLoadingType val isLoadingType: LiveData<Boolean> = _isLoadingType
@ -45,7 +55,12 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
fun loadMyStore(){ fun loadMyStore(){
viewModelScope.launch { viewModelScope.launch {
when (val result = repository.fetchMyStoreProfile()){ when (val result = repository.fetchMyStoreProfile()){
is Result.Success -> _myStoreProfile.postValue(result.data) is Result.Success -> {
val storeData = result.data
_myStoreProfile.postValue(storeData)
_shipping.postValue(storeData?.shipping)
_payment.postValue(storeData?.payment)
}
is Result.Error -> _errorMessage.postValue(result.exception.message ?: "Unknown Error") is Result.Error -> _errorMessage.postValue(result.exception.message ?: "Unknown Error")
is Result.Loading -> null is Result.Loading -> null
} }
@ -84,30 +99,40 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
if (store == null) { if (store == null) {
_errorMessage.postValue("Data toko tidak tersedia") _errorMessage.postValue("Data toko tidak tersedia")
Log.e(TAG, "Store data is null")
return@launch return@launch
} }
Log.d("UpdateStoreProfileVM", "Calling repository with params:")
Log.d("UpdateStoreProfileVM", "storeName: $storeName")
Log.d("UpdateStoreProfileVM", "description: $description")
Log.d("UpdateStoreProfileVM", "isOnLeave: $isOnLeave")
Log.d("UpdateStoreProfileVM", "storeType: $storeType")
Log.d("UpdateStoreProfileVM", "storeImage: ${storeImage?.headers}")
val response = repository.updateStoreProfile( val response = repository.updateStoreProfile(
storeName = storeName, storeName = storeName,
storeStatus = "active".toRequestBody(),
storeDescription = description, storeDescription = description,
isOnLeave = isOnLeave, isOnLeave = isOnLeave,
cityId = store.cityId.toString().toRequestBody(),
provinceId = store.provinceId.toString().toRequestBody(),
street = store.street.toRequestBody(),
subdistrict = store.subdistrict.toRequestBody(),
detail = store.detail.toRequestBody(),
postalCode = store.postalCode.toRequestBody(),
latitude = store.latitude.toRequestBody(),
longitude = store.longitude.toRequestBody(),
userPhone = store.phone.toRequestBody(),
storeType = storeType, storeType = storeType,
storeimg = storeImage storeimg = storeImage
) )
if (response.isSuccessful) _updateStoreProfileResult.postValue(response.body())
else _errorMessage.postValue("Gagal memperbarui profil") if (response != null) {
if (response.isSuccessful) {
_updateStoreProfileResult.postValue(response.body())
Log.d(TAG, "Update successful: ${response.body()}")
} else {
_errorMessage.postValue("Gagal memperbarui profil")
Log.e(TAG, "Update failed: ${response.errorBody()?.string()}")
}
} else {
_errorMessage.postValue("Terjadi kesalahan jaringan atau server")
Log.e(TAG, "Repository returned null response")
}
} catch (e: Exception) { } catch (e: Exception) {
_errorMessage.postValue(e.message ?: "Unexpected error") _errorMessage.postValue(e.message ?: "Unexpected error")
Log.e(TAG, "Exception updating store profile", e)
} }
} }
} }
@ -120,13 +145,13 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
result.data.orders.size ?: 0 result.data.orders.size ?: 0
} }
is Result.Error -> { is Result.Error -> {
Log.e("SellsViewModel", "Error getting orders count: ${result.exception.message}") Log.e(TAG, "Error getting orders count: ${result.exception.message}")
0 0
} }
is Result.Loading -> 0 is Result.Loading -> 0
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("SellsViewModel", "Exception getting orders count", e) Log.e(TAG, "Exception getting orders count", e)
0 0
} }
} }
@ -138,7 +163,7 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
statuses.forEach { status -> statuses.forEach { status ->
counts[status] = getTotalOrdersByStatus(status) counts[status] = getTotalOrdersByStatus(status)
Log.d("SellsViewModel", "Status: $status, countOrder=${counts[status]}") Log.d(TAG, "Status: $status, countOrder=${counts[status]}")
} }
return counts return counts

View File

@ -30,6 +30,9 @@ class PaymentInfoViewModel(private val repository: PaymentInfoRepository) : View
private val _deletePaymentSuccess = MutableLiveData<Boolean>() private val _deletePaymentSuccess = MutableLiveData<Boolean>()
val deletePaymentSuccess: LiveData<Boolean> = _deletePaymentSuccess val deletePaymentSuccess: LiveData<Boolean> = _deletePaymentSuccess
var selectedBankName: String? = null
val bankName = MutableLiveData<String>()
fun getPaymentInfo() { fun getPaymentInfo() {
_isLoading.value = true _isLoading.value = true
viewModelScope.launch { viewModelScope.launch {

View File

@ -11,6 +11,8 @@ import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse
import com.alya.ecommerce_serang.data.api.response.auth.StoreTypesItem import com.alya.ecommerce_serang.data.api.response.auth.StoreTypesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictsItem
import com.alya.ecommerce_serang.data.api.response.customer.order.VillagesItem
import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.utils.ImageUtils import com.alya.ecommerce_serang.utils.ImageUtils
@ -41,8 +43,17 @@ class RegisterStoreViewModel(
private val _citiesState = MutableLiveData<Result<List<CitiesItem>>>() private val _citiesState = MutableLiveData<Result<List<CitiesItem>>>()
val citiesState: LiveData<Result<List<CitiesItem>>> = _citiesState val citiesState: LiveData<Result<List<CitiesItem>>> = _citiesState
private val _subdistrictState = MutableLiveData<Result<List<SubdistrictsItem>>>()
val subdistrictState: LiveData<Result<List<SubdistrictsItem>>> = _subdistrictState
private val _villagesState = MutableLiveData<Result<List<VillagesItem>>>()
val villagesState: LiveData<Result<List<VillagesItem>>> = _villagesState
var selectedProvinceId: Int? = null var selectedProvinceId: Int? = null
var selectedCityId: String? = null var selectedCityId: String? = null
var selectedSubdistrict: String? = null
var selectedVillages: String? = null
var selectedBankName: String? = null
// Form fields // Form fields
val storeName = MutableLiveData<String>() val storeName = MutableLiveData<String>()
@ -72,47 +83,89 @@ class RegisterStoreViewModel(
val selectedCouriers = mutableListOf<String>() val selectedCouriers = mutableListOf<String>()
fun registerStore(context: Context) { fun registerStore(context: Context) {
Log.d(TAG, "Starting registerStore()")
val allowedFileTypes = Regex("^(jpeg|jpg|png|pdf)$", RegexOption.IGNORE_CASE) val allowedFileTypes = Regex("^(jpeg|jpg|png|pdf)$", RegexOption.IGNORE_CASE)
// Check each file if present fun logFileInfo(label: String, uri: Uri?) {
if (uri == null) {
Log.d(TAG, "$label URI: null")
return
}
Log.d(TAG, "$label URI: $uri")
try {
val fileSizeBytes = context.contentResolver.openFileDescriptor(uri, "r")?.use {
it.statSize
} ?: -1
Log.d(TAG, "$label original size: ${fileSizeBytes / 1024} KB")
} catch (e: Exception) {
Log.e(TAG, "Error getting size for $label", e)
}
}
// Log all file info before validation
logFileInfo("Store Image", storeImageUri)
logFileInfo("KTP", ktpUri)
logFileInfo("NPWP", npwpUri)
logFileInfo("NIB", nibUri)
logFileInfo("Persetujuan", persetujuanUri)
logFileInfo("QRIS", qrisUri)
// Check file types
if (storeImageUri != null && !ImageUtils.isAllowedFileType(context, storeImageUri, allowedFileTypes)) { if (storeImageUri != null && !ImageUtils.isAllowedFileType(context, storeImageUri, allowedFileTypes)) {
_errorMessage.value = "Foto toko harus berupa file JPEG, JPG, atau PNG" _errorMessage.value = "Foto toko harus berupa file JPEG, JPG, atau PNG"
Log.e(TAG, _errorMessage.value ?: "Invalid file type for store image")
_registerState.value = Result.Error(Exception(_errorMessage.value ?: "Invalid file type")) _registerState.value = Result.Error(Exception(_errorMessage.value ?: "Invalid file type"))
return return
} }
if (ktpUri != null && !ImageUtils.isAllowedFileType(context, ktpUri, allowedFileTypes)) { if (ktpUri != null && !ImageUtils.isAllowedFileType(context, ktpUri, allowedFileTypes)) {
_errorMessage.value = "KTP harus berupa file JPEG, JPG, atau PNG" _errorMessage.value = "KTP harus berupa file JPEG, JPG, atau PNG"
Log.e(TAG, _errorMessage.value ?: "Invalid file type for KTP")
_registerState.value = Result.Error(Exception(_errorMessage.value ?: "Invalid file type")) _registerState.value = Result.Error(Exception(_errorMessage.value ?: "Invalid file type"))
return return
} }
if (npwpUri != null && !ImageUtils.isAllowedFileType(context, npwpUri, allowedFileTypes)) { if (npwpUri != null && !ImageUtils.isAllowedFileType(context, npwpUri, allowedFileTypes)) {
_errorMessage.value = "NPWP harus berupa file JPEG, JPG, PNG, atau PDF" _errorMessage.value = "NPWP harus berupa file JPEG, JPG, PNG, atau PDF"
Log.e(TAG, _errorMessage.value ?: "Invalid file type for NPWP")
_registerState.value = Result.Error(Exception(_errorMessage.value ?: "Invalid file type")) _registerState.value = Result.Error(Exception(_errorMessage.value ?: "Invalid file type"))
return return
} }
if (nibUri != null && !ImageUtils.isAllowedFileType(context, nibUri, allowedFileTypes)) { if (nibUri != null && !ImageUtils.isAllowedFileType(context, nibUri, allowedFileTypes)) {
_errorMessage.value = "NIB harus berupa file JPEG, JPG, PNG, atau PDF" _errorMessage.value = "NIB harus berupa file JPEG, JPG, PNG, atau PDF"
Log.e(TAG, _errorMessage.value ?: "Invalid file type for NIB")
_registerState.value = Result.Error(Exception(_errorMessage.value ?: "Invalid file type")) _registerState.value = Result.Error(Exception(_errorMessage.value ?: "Invalid file type"))
return return
} }
if (persetujuanUri != null && !ImageUtils.isAllowedFileType(context, persetujuanUri, allowedFileTypes)) { if (persetujuanUri != null && !ImageUtils.isAllowedFileType(context, persetujuanUri, allowedFileTypes)) {
_errorMessage.value = "Persetujuan harus berupa file JPEG, JPG, PNG, atau PDF" _errorMessage.value = "Persetujuan harus berupa file JPEG, JPG, PNG, atau PDF"
Log.e(TAG, _errorMessage.value ?: "Invalid file type for Persetujuan")
_registerState.value = Result.Error(Exception(_errorMessage.value ?: "Invalid file type")) _registerState.value = Result.Error(Exception(_errorMessage.value ?: "Invalid file type"))
return return
} }
if (qrisUri != null && !ImageUtils.isAllowedFileType(context, qrisUri, allowedFileTypes)) { if (qrisUri != null && !ImageUtils.isAllowedFileType(context, qrisUri, allowedFileTypes)) {
_errorMessage.value = "QRIS harus berupa file JPEG, JPG, PNG, atau PDF" _errorMessage.value = "QRIS harus berupa file JPEG, JPG, PNG, atau PDF"
Log.e(TAG, _errorMessage.value ?: "Invalid file type for QRIS")
_registerState.value = Result.Error(Exception(_errorMessage.value ?: "Invalid file type")) _registerState.value = Result.Error(Exception(_errorMessage.value ?: "Invalid file type"))
return return
} }
Log.d(TAG, "File type checks passed. Starting repository.registerStoreUser() call.")
viewModelScope.launch { viewModelScope.launch {
try { try {
_registerState.value = Result.Loading _registerState.value = Result.Loading
Log.d(TAG, "Register store request payload: " +
"storeName=${storeName.value}, storeTypeId=${storeTypeId.value}, " +
"lat=${latitude.value}, long=${longitude.value}, " +
"street=${street.value}, subdistrict=${subdistrict.value}, " +
"cityId=${cityId.value}, provinceId=${provinceId.value}, postalCode=${postalCode.value}, " +
"bankName=${bankName.value}, bankNum=${bankNumber.value}, accountName=${accountName.value}, " +
"selectedCouriers=$selectedCouriers")
val result = repository.registerStoreUser( val result = repository.registerStoreUser(
context = context, context = context,
@ -139,25 +192,15 @@ class RegisterStoreViewModel(
accountName = accountName.value ?: "" accountName = accountName.value ?: ""
) )
Log.d(TAG, "Repository returned result: $result")
_registerState.value = result _registerState.value = result
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Exception during registerStore", e)
_registerState.value = Result.Error(e) _registerState.value = Result.Error(e)
} }
} }
} }
// // Helper function to convert Uri to File
// private fun getFileFromUri(context: Context, uri: Uri): File {
// val inputStream = context.contentResolver.openInputStream(uri)
// val tempFile = File(context.cacheDir, "temp_file_${System.currentTimeMillis()}")
// inputStream?.use { input ->
// tempFile.outputStream().use { output ->
// input.copyTo(output)
// }
// }
// return tempFile
// }
fun validateForm(): Boolean { fun validateForm(): Boolean {
// Implement form validation logic // Implement form validation logic
return !(storeName.value.isNullOrEmpty() || return !(storeName.value.isNullOrEmpty() ||
@ -174,8 +217,6 @@ class RegisterStoreViewModel(
nibUri == null) nibUri == null)
} }
// Function to fetch store types // Function to fetch store types
fun fetchStoreTypes() { fun fetchStoreTypes() {
_isLoadingType.value = true _isLoadingType.value = true
@ -235,6 +276,52 @@ class RegisterStoreViewModel(
} }
} }
fun getSubdistrict(cityId: String) {
_subdistrictState.value = Result.Loading
viewModelScope.launch {
try {
selectedSubdistrict = cityId
val result = repository.getListSubdistrict(cityId)
result?.let {
_subdistrictState.postValue(Result.Success(it.subdistricts))
Log.d(TAG, "Cities loaded for province $cityId: ${it.subdistricts.size}")
} ?: run {
_subdistrictState.postValue(Result.Error(Exception("Failed to load cities")))
Log.e(TAG, "City result was null for province $cityId")
}
} catch (e: Exception) {
_subdistrictState.postValue(Result.Error(Exception(e.message ?: "Error loading cities")))
Log.e(TAG, "Error fetching cities for province $cityId", e)
}
}
}
fun getVillages(subdistrictId: String) {
_villagesState.value = Result.Loading
viewModelScope.launch {
try {
selectedVillages = subdistrictId
val result = repository.getListVillages(subdistrictId)
result?.let {
_villagesState.postValue(Result.Success(it.villages))
Log.d(TAG, "Cities loaded for province $subdistrictId: ${it.villages.size}")
} ?: run {
_villagesState.postValue(Result.Error(Exception("Failed to load cities")))
Log.e(TAG, "City result was null for province $subdistrictId")
}
} catch (e: Exception) {
_villagesState.postValue(Result.Error(Exception(e.message ?: "Error loading cities")))
Log.e(TAG, "Error fetching cities for province $subdistrictId", e)
}
}
}
fun isBankSelected(): Boolean {
return !selectedBankName.isNullOrEmpty()
}
companion object { companion object {
private const val TAG = "RegisterStoreUserViewModel" private const val TAG = "RegisterStoreUserViewModel"
} }

View File

@ -7,8 +7,11 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
import com.alya.ecommerce_serang.data.api.dto.FcmReq
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.dto.ResetPassReq
import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
@ -71,6 +74,7 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
var selectedProvinceId: Int? = null var selectedProvinceId: Int? = null
var selectedCityId: String? = null var selectedCityId: String? = null
var selectedSubdistrict: String? = null var selectedSubdistrict: String? = null
var subdistrictName: String? = null
var selectedVillages: String? = null var selectedVillages: String? = null
var selectedPostalCode: String? = null var selectedPostalCode: String? = null
@ -382,6 +386,38 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
} }
} }
fun resetPass(request: ResetPassReq){
}
fun sendFcm(token: FcmReq) {
viewModelScope.launch {
_otpState.value = Result.Loading // Indicating API call in progress
try {
// Call the repository function to request OTP
val authenticatedApiService = getAuthenticatedApiService()
val authenticatedOrderRepo = UserRepository(authenticatedApiService)
val response: FcmTokenResponse = authenticatedOrderRepo.sendFcm(token)
// Log and store success message
Log.d("LoginViewModel", "OTP Response: ${response.message}")
_message.value = response.message ?: "berhasil" // Store the message for UI feedback
// Update state to indicate success
_otpState.value = Result.Success(Unit)
} catch (exception: Exception) {
// Handle any errors and update state
_otpState.value = Result.Error(exception)
_message.value = exception.localizedMessage ?: "Failed to request OTP"
// Log the error for debugging
Log.e("LoginViewModel", "OTP request failed for: $token", exception)
}
}
}
companion object { companion object {
private const val TAG = "RegisterViewModel" private const val TAG = "RegisterViewModel"
} }

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="100dp" android:tint="#489EC6" android:viewportHeight="24" android:viewportWidth="24" android:width="100dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM12,6c1.93,0 3.5,1.57 3.5,3.5S13.93,13 12,13s-3.5,-1.57 -3.5,-3.5S10.07,6 12,6zM12,20c-2.03,0 -4.43,-0.82 -6.14,-2.88C7.55,15.8 9.68,15 12,15s4.45,0.8 6.14,2.12C16.43,19.18 14.03,20 12,20z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#489EC6" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M7,18c-1.1,0 -1.99,0.9 -1.99,2S5.9,22 7,22s2,-0.9 2,-2 -0.9,-2 -2,-2zM1,2v2h2l3.6,7.59 -1.35,2.45c-0.16,0.28 -0.25,0.61 -0.25,0.96 0,1.1 0.9,2 2,2h12v-2L7.42,15c-0.14,0 -0.25,-0.11 -0.25,-0.25l0.03,-0.12 0.9,-1.63h7.45c0.75,0 1.41,-0.41 1.75,-1.03l3.58,-6.49c0.08,-0.14 0.12,-0.31 0.12,-0.48 0,-0.55 -0.45,-1 -1,-1L5.21,4l-0.94,-2L1,2zM17,18c-1.1,0 -1.99,0.9 -1.99,2s0.89,2 1.99,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18v-6h16v6zM20,8L4,8L4,6h16v2z"/>
</vector>

View File

@ -3,6 +3,6 @@
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="@color/blue_500" /> <solid android:color="@color/blue_500" />
<corners android:radius="5dp" /> <corners android:radius="24dp" />
</shape> </shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -168,25 +168,66 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="Kecamatan / Desa" android:text="Kecamatan"
android:textColor="@android:color/black" android:textColor="@android:color/black"
android:textSize="14sp" /> android:textSize="14sp" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:hint="Isi Kecamatan / Desa" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText <AutoCompleteTextView
android:id="@+id/etKecamatan" android:id="@+id/autoCompleteKecamatan"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="none"
android:focusable="false"
android:clickable="true"
android:padding="12dp" android:padding="12dp"
android:textSize="14sp" android:textSize="14sp" />
android:inputType="textCapWords" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<ProgressBar
android:id="@+id/subdistrictProgressBar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
android:visibility="gone" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Desa"
android:textColor="@android:color/black"
android:textSize="14sp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<AutoCompleteTextView
android:id="@+id/autoCompleteDesa"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
android:focusable="false"
android:clickable="true"
android:padding="12dp"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<ProgressBar
android:id="@+id/villageProgressBar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
android:visibility="gone" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -113,6 +113,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:visibility="gone"
android:text="Metode Pembayaran *" android:text="Metode Pembayaran *"
android:fontFamily="@font/dmsans_semibold" android:fontFamily="@font/dmsans_semibold"
android:textSize="16sp" /> android:textSize="16sp" />
@ -122,6 +123,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:visibility="gone"
android:background="@drawable/edit_text_background" android:background="@drawable/edit_text_background"
android:minHeight="50dp" android:minHeight="50dp"
android:padding="12dp" /> android:padding="12dp" />

View File

@ -26,14 +26,14 @@
android:paddingHorizontal="@dimen/horizontal_safe_area" android:paddingHorizontal="@dimen/horizontal_safe_area"
android:layout_marginTop="19dp"> android:layout_marginTop="19dp">
<!-- Foto Produk --> <!-- Bukti Bayar -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:layout_marginBottom="24dp"> android:layout_marginBottom="24dp">
<!-- Label Foto Produk --> <!-- Label Bukti Bayar -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -42,7 +42,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Foto Produk" android:text="Bukti Pembayaran"
style="@style/body_medium" style="@style/body_medium"
android:layout_marginEnd="4dp"/> android:layout_marginEnd="4dp"/>
@ -275,12 +275,73 @@
</LinearLayout> </LinearLayout>
<!-- Petunjuk -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="16dp"
android:background="#E0E0E0" />
<LinearLayout
android:id="@+id/layout_mbanking_instructions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:padding="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Petunjuk Transfer mBanking"
style="@style/body_medium" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_arrow_right" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E0E0E0" />
<LinearLayout
android:id="@+id/layout_atm_instructions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:padding="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Petunjuk Transfer ATM"
style="@style/body_medium" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_arrow_right" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E0E0E0" />
</LinearLayout> </LinearLayout>
<Button <Button
android:id="@+id/btn_send" android:id="@+id/btn_send"
android:text="Kirim" android:text="Kirim"
style="@style/button.large.disabled.long" style="@style/button.large.disabled.long"
android:enabled="false"
android:layout_marginBottom="16dp"/> android:layout_marginBottom="16dp"/>
</LinearLayout> </LinearLayout>

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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.profile.ChangePasswordActivity">
<include
android:id="@+id/headerStoreProduct"
layout="@layout/header" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="32dp"
android:paddingVertical="16dp">
<!-- Password label-->
<TextView
android:id="@+id/tv_password_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="@font/dmsans_medium"
android:text="Kata Sandi Lama"
android:textSize="18sp"
android:layout_marginVertical="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Password input -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_login_password"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:passwordToggleEnabled="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_password_label"
app:layout_constraintEnd_toEndOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Masukkan kata sandi akun"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Password label-->
<TextView
android:id="@+id/tv_new_password_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="@font/dmsans_medium"
android:text="Kata Sandi Baru"
android:textSize="18sp"
android:layout_marginVertical="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/til_login_password"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Password input -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_login_new_password"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:passwordToggleEnabled="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_new_password_label"
app:layout_constraintEnd_toEndOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_login_new_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Masukkan kata sandi baru"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Change Pass button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_change_pass"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Ubah Kata Sandi"
app:cornerRadius="8dp"
android:layout_marginVertical="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/til_login_new_password" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -34,69 +34,50 @@
android:orientation="vertical"> android:orientation="vertical">
<!-- Delivery Address Section --> <!-- Delivery Address Section -->
<androidx.cardview.widget.CardView <com.google.android.material.card.MaterialCardView
android:id="@+id/card_delivery_address" android:id="@+id/card_delivery_address"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="0dp" android:layout_marginHorizontal="16dp"
android:layout_marginTop="0dp" android:layout_marginTop="16dp"
app:cardElevation="0dp"> app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/white"
app:strokeColor="#E0E0E0"
app:strokeWidth="1dp">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:padding="20dp">
android:padding="16dp"
android:background="@color/white">
<!-- Header Row -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:id="@+id/address_header"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="horizontal"
android:gravity="center_vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView <ImageView
android:id="@+id/iv_location_icon" android:id="@+id/iv_location_icon"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:src="@drawable/baseline_location_pin_24" android:src="@drawable/baseline_location_pin_24"
android:layout_gravity="center_vertical" app:tint="@color/blue_300" />
app:tint="#3D84FF" />
<TextView <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Alamat Pengiriman"
android:textSize="16sp"
android:fontFamily="@font/dmsans_medium"
android:layout_marginStart="8dp" />
</LinearLayout>
<TextView
android:id="@+id/tv_places_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
android:textColor="#5A5A5A"
android:paddingHorizontal="8dp"
android:paddingVertical="2dp"
android:textSize="12sp"
android:layout_marginTop="8dp"
android:layout_marginStart="32dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<TextView
android:id="@+id/tv_address"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:text="-" android:text="Alamat Pengiriman"
android:textSize="14sp" android:textSize="16sp"
android:layout_marginStart="32dp" /> android:textColor="@android:color/black"
android:fontFamily="@font/dmsans_medium"
android:layout_marginStart="12dp" />
<TextView <TextView
android:id="@+id/tv_change_address" android:id="@+id/tv_change_address"
@ -104,89 +85,279 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Pilih Alamat" android:text="Pilih Alamat"
android:textColor="#3D84FF" android:textColor="#3D84FF"
android:textSize="14sp" /> android:textSize="14sp"
android:fontFamily="@font/dmsans_medium"
android:background="?attr/selectableItemBackgroundBorderless"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp" />
</LinearLayout> </LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="@color/black_50" />
<!-- Product Items Section -->
<androidx.cardview.widget.CardView
android:id="@+id/card_product"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="0dp">
<!-- Empty Address State -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:id="@+id/container_empty_address"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:background="@color/white" android:layout_marginTop="4dp"
android:padding="16dp"> android:gravity="center"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@id/address_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.recyclerview.widget.RecyclerView <TextView
android:id="@+id/rv_product_items" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Belum ada alamat dipilih"
android:fontFamily="@font/dmsans_medium"
android:textColor="#757575"
android:textSize="14sp"
android:gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Pilih alamat pengiriman untuk melanjutkan"
android:textColor="#BDBDBD"
android:textSize="12sp"
android:gravity="center" />
</LinearLayout>
<!-- Selected Address Content -->
<LinearLayout
android:id="@+id/container_address"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/address_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<!-- Address Label -->
<TextView
android:id="@+id/tv_places_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rumah"
android:textColor="#3D84FF"
android:textSize="12sp"
android:fontFamily="@font/dmsans_medium"
android:background="@drawable/bg_edit_text_background"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp"
tools:text="Rumah" />
<!-- Full Address -->
<TextView
android:id="@+id/tv_address"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
tools:listitem="@layout/item_order_seller" /> android:text="Jl. Raya Serang No. 123, Kecamatan Serang, Kabupaten Serang, Banten 42111"
android:textSize="14sp"
android:textColor="@android:color/black"
android:lineSpacingExtra="2dp"
tools:text="Jl. Raya Serang No. 123, Kecamatan Serang, Kabupaten Serang, Banten 42111" />
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView>
<View </androidx.constraintlayout.widget.ConstraintLayout>
android:layout_width="match_parent" </com.google.android.material.card.MaterialCardView>
android:layout_height="8dp"
android:background="#F5F5F5" />
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="@color/black_50" />
<!-- Shipping Method Section --> <!-- Product Items Section -->
<LinearLayout <com.google.android.material.card.MaterialCardView
android:id="@+id/layout_shipping_method" android:id="@+id/card_product"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:layout_marginHorizontal="16dp"
android:background="@color/white" android:layout_marginTop="8dp"
android:padding="16dp"> app:cardBackgroundColor="@color/white"
app:strokeColor="#E0E0E0"
app:strokeWidth="1dp">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="20dp">
<!-- Header Row -->
<LinearLayout
android:id="@+id/product_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical"> android:gravity="center_vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/baseline_local_grocery_store_24"
app:tint="@color/blue_300" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Produk Pesanan"
android:textSize="16sp"
android:textColor="@android:color/black"
android:fontFamily="@font/dmsans_medium"
android:layout_marginStart="12dp" />
</LinearLayout>
<!-- Empty Product State -->
<LinearLayout
android:id="@+id/container_empty_products"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="4dp"
android:gravity="center"
android:padding="8dp"
android:visibility="visible"
app:layout_constraintTop_toBottomOf="@id/product_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Tidak ada produk"
android:fontFamily="@font/dmsans_medium"
android:textColor="#757575"
android:textSize="14sp"
android:gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="Keranjang belanja kosong"
android:textColor="#BDBDBD"
android:textSize="12sp"
android:gravity="center" />
</LinearLayout>
<!-- Products RecyclerView -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_product_items"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/product_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:listitem="@layout/item_order_seller" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_shipping_method"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
app:cardBackgroundColor="@color/white"
app:strokeColor="#E0E0E0"
app:strokeWidth="1dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp">
<!-- Header Row -->
<LinearLayout
android:id="@+id/shipping_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView <TextView
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:text="Metode Pengiriman" android:text="Metode Pengiriman"
android:textSize="14sp" /> android:textSize="16sp"
android:textColor="@android:color/black"
android:fontFamily="@font/dmsans_medium"
android:layout_marginStart="12dp" />
<TextView <TextView
android:id="@+id/tv_shipping_option" android:id="@+id/tv_shipping_option"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Opsi Pengiriman" android:text="Pilih"
android:textColor="#3D84FF" android:textColor="#3D84FF"
android:textSize="14sp" /> android:textSize="14sp"
android:fontFamily="@font/dmsans_medium"
android:background="?attr/selectableItemBackgroundBorderless"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp" />
</LinearLayout> </LinearLayout>
<!-- Empty Shipping State -->
<LinearLayout
android:id="@+id/container_empty_shipping"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="4dp"
android:gravity="center"
android:padding="12dp"
app:layout_constraintTop_toBottomOf="@id/shipping_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Belum ada metode pengiriman"
android:fontFamily="@font/dmsans_medium"
android:textColor="#757575"
android:textSize="14sp"
android:gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Pilih alamat terlebih dahulu"
android:textColor="#BDBDBD"
android:textSize="12sp"
android:gravity="center" />
</LinearLayout>
<!-- Selected Shipping Content -->
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/card_shipment" android:id="@+id/card_shipment"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="4dp"
android:visibility="gone" android:visibility="gone"
app:cardCornerRadius="8dp" app:cardCornerRadius="8dp"
app:cardElevation="0dp" app:cardElevation="0dp"
app:cardBackgroundColor="#F5F5F5"> app:cardBackgroundColor="#F5F5F5"
app:layout_constraintTop_toBottomOf="@id/shipping_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -234,39 +405,121 @@
android:layout_gravity="center_vertical" /> android:layout_gravity="center_vertical" />
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
</LinearLayout>
<View </androidx.constraintlayout.widget.ConstraintLayout>
android:layout_width="match_parent" </com.google.android.material.card.MaterialCardView>
android:layout_height="8dp"
android:background="@color/black_50" />
<!-- Payment Method Section --> <!-- Payment Method Section -->
<LinearLayout <com.google.android.material.card.MaterialCardView
android:id="@+id/layout_payment_method" android:id="@+id/card_payment_method"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:layout_marginHorizontal="16dp"
android:background="@color/white" android:layout_marginTop="8dp"
android:padding="16dp"> app:cardBackgroundColor="@color/white"
app:strokeColor="#E0E0E0"
app:strokeWidth="1dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp">
<!-- Header Row -->
<LinearLayout
android:id="@+id/payment_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/baseline_payment_24"
app:tint="@color/blue_300" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Metode Pembayaran" android:text="Metode Pembayaran"
android:textSize="14sp" android:textSize="16sp"
android:layout_marginBottom="8dp" /> android:textColor="@android:color/black"
android:fontFamily="@font/dmsans_medium"
android:layout_marginStart="12dp" />
<TextView
android:id="@+id/tv_payment_option"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pilih"
android:textColor="#3D84FF"
android:textSize="14sp"
android:fontFamily="@font/dmsans_medium"
android:background="?attr/selectableItemBackgroundBorderless"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp"
android:visibility="gone" />
</LinearLayout>
<!-- Empty Payment State -->
<LinearLayout
android:id="@+id/container_empty_payment"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="4dp"
android:gravity="center"
android:padding="12dp"
app:layout_constraintTop_toBottomOf="@id/payment_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Belum ada metode pembayaran"
android:fontFamily="@font/dmsans_medium"
android:textColor="#757575"
android:textSize="14sp"
android:gravity="center" />
<TextView
android:id="@+id/tvEmptyPayment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Pilih alamat terlebih dahulu"
android:textColor="#BDBDBD"
android:textSize="12sp"
android:gravity="center" />
</LinearLayout>
<!-- Payment Methods RecyclerView -->
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_payment_info" android:id="@+id/rv_payment_info"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/payment_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:listitem="@layout/item_payment_method" /> tools:listitem="@layout/item_payment_method" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="8dp" android:layout_height="8dp"
android:layout_marginVertical="8dp"
android:background="@color/black_50" /> android:background="@color/black_50" />
<!-- Price Summary Section --> <!-- Price Summary Section -->
@ -294,7 +547,7 @@
android:id="@+id/tv_item_total" android:id="@+id/tv_item_total"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Rp65.000" android:text="Rp0"
android:textSize="14sp" /> android:textSize="14sp" />
</LinearLayout> </LinearLayout>
@ -315,7 +568,7 @@
android:id="@+id/tv_shipping_fee" android:id="@+id/tv_shipping_fee"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Rp15.000" android:text="Rp0"
android:textSize="14sp" /> android:textSize="14sp" />
</LinearLayout> </LinearLayout>
@ -342,8 +595,8 @@
android:id="@+id/tv_total" android:id="@+id/tv_total"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Rp75.000" android:text="Rp0"
android:textColor="#3D84FF" android:textColor="@color/blue_400"
android:textSize="16sp" android:textSize="16sp"
android:fontFamily="@font/dmsans_bold" /> android:fontFamily="@font/dmsans_bold" />
</LinearLayout> </LinearLayout>
@ -379,7 +632,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Rp75.000" android:text="Rp75.000"
android:textColor="#3D84FF" android:textColor="@color/blue_400"
android:textSize="18sp" android:textSize="18sp"
android:fontFamily="@font/dmsans_bold" /> android:fontFamily="@font/dmsans_bold" />
</LinearLayout> </LinearLayout>
@ -392,7 +645,7 @@
android:textAllCaps="false" android:textAllCaps="false"
android:paddingHorizontal="32dp" android:paddingHorizontal="32dp"
app:cornerRadius="8dp" app:cornerRadius="8dp"
android:backgroundTint="#3D84FF" /> android:backgroundTint="@color/blue_500" />
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -231,6 +231,18 @@
android:textSize="14sp" /> android:textSize="14sp" />
</LinearLayout> </LinearLayout>
<TextView
android:id="@+id/empty_review"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Belum ada ulasan"
android:textSize="16sp"
android:textColor="@color/black_200"
android:gravity="center"
android:visibility="gone"
android:fontFamily="@font/dmsans_mediumitalic"
android:layout_marginTop="8dp"/>
<!-- RecyclerView for Reviews --> <!-- RecyclerView for Reviews -->
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewReviews" android:id="@+id/recyclerViewReviews"
@ -385,6 +397,13 @@
android:orientation="horizontal" android:orientation="horizontal"
android:padding="16dp"> android:padding="16dp">
<ProgressBar
android:id="@+id/progress_bar_detail_store"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="center"/>
<de.hdodenhof.circleimageview.CircleImageView <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/ivSellerImage" android:id="@+id/ivSellerImage"
android:layout_width="48dp" android:layout_width="48dp"
@ -477,6 +496,18 @@
android:textSize="14sp" /> android:textSize="14sp" />
</LinearLayout> </LinearLayout>
<TextView
android:id="@+id/empty_other_products"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Belum ada produk lainnya"
android:textSize="16sp"
android:textColor="@color/black_200"
android:gravity="center"
android:visibility="gone"
android:fontFamily="@font/dmsans_mediumitalic"
android:layout_marginTop="8dp"/>
<!-- RecyclerView for Other Products --> <!-- RecyclerView for Other Products -->
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewOtherProducts" android:id="@+id/recyclerViewOtherProducts"

View File

@ -62,7 +62,7 @@
android:id="@+id/profile_image" android:id="@+id/profile_image"
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="100dp" android:layout_height="100dp"
android:src="@drawable/baseline_account_circle_24" android:src="@drawable/baseline_account_circle_100"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent"/>
@ -87,6 +87,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Ubah Profil" android:text="Ubah Profil"
style="@style/button.large.active.medium"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/profile_image" app:layout_constraintTop_toBottomOf="@id/profile_image"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@ -10,7 +10,7 @@
tools:context=".ui.profile.mystore.profile.address.DetailStoreAddressActivity"> tools:context=".ui.profile.mystore.profile.address.DetailStoreAddressActivity">
<include <include
android:id="@+id/header" android:id="@+id/header_address_store"
layout="@layout/header" /> layout="@layout/header" />
<ScrollView <ScrollView
@ -154,6 +154,58 @@
</LinearLayout> </LinearLayout>
<!-- Kecamatan -->
<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="Kecamatan"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
<!-- 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:gravity="center_vertical"
android:layout_marginTop="10dp">
<Spinner
android:id="@+id/spinner_subdistrict"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="8dp"
style="@style/body_small"
android:background="@null"/>
<ProgressBar
android:id="@+id/subdistrict_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"
android:layout_height="16dp"
android:src="@drawable/ic_down"
android:layout_marginEnd="8dp"
android:contentDescription="Chevron Down" />
</LinearLayout>
</LinearLayout>
<!-- Jalan --> <!-- Jalan -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -180,32 +232,6 @@
</LinearLayout> </LinearLayout>
<!-- Kecamatan -->
<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="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>
<!-- Kode Pos --> <!-- Kode Pos -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -301,7 +327,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:orientation="vertical" android:orientation="vertical"
android:layout_marginEnd="8dp"> android:layout_marginEnd="8dp"
android:visibility="gone">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -327,7 +354,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:orientation="vertical" android:orientation="vertical"
android:layout_marginStart="8dp"> android:layout_marginStart="8dp"
android:visibility="gone">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -5,6 +5,257 @@
android:id="@+id/main" android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.order.address.EditAddressActivity"> android:background="@android:color/white"
tools:context=".ui.order.address.AddAddressActivity">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="@drawable/ic_back_24"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:title="Ubah Alamat" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E0E0E0"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/buttonSimpan"
app:layout_constraintTop_toBottomOf="@id/divider">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nama Penerima"
android:textColor="@android:color/black"
android:textSize="14sp" />
<EditText
android:id="@+id/etNamaPenerima"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:hint="Isi nama penerima"
android:inputType="textPersonName"
android:padding="12dp"
android:textSize="14sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Nomor Hp"
android:textColor="@android:color/black"
android:textSize="14sp" />
<EditText
android:id="@+id/etNomorHp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:hint="Isi nomor handphone aktif"
android:inputType="phone"
android:padding="12dp"
android:textSize="14sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Detail Alamat"
android:textColor="@android:color/black"
android:textSize="14sp" />
<EditText
android:id="@+id/etDetailAlamat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:gravity="top"
android:hint="Isi detail alamat (nomor rumah, lantai, dll)"
android:inputType="textMultiLine"
android:lines="3"
android:padding="12dp"
android:textSize="14sp" />
<!-- Provinsi -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Provinsi"
android:textColor="@android:color/black"
android:textSize="14sp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="Pilih Provinsi"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<AutoCompleteTextView
android:id="@+id/autoCompleteProvinsi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
android:focusable="false"
android:clickable="true"
android:padding="12dp"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
android:visibility="gone" />
<!-- Kabupaten / Kota -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Kabupaten / Kota"
android:textColor="@android:color/black"
android:textSize="14sp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="Pilih Kota / Kabupaten"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<AutoCompleteTextView
android:id="@+id/autoCompleteKabupaten"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
android:focusable="false"
android:clickable="true"
android:padding="12dp"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<ProgressBar
android:id="@+id/cityProgressBar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
android:visibility="gone" />
<!-- Kecamatan -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Kecamatan"
android:textColor="@android:color/black"
android:textSize="14sp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="Pilih Kecamatan"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<AutoCompleteTextView
android:id="@+id/autoCompleteKecamatan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
android:focusable="false"
android:clickable="true"
android:padding="12dp"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Desa"
android:textColor="@android:color/black"
android:textSize="14sp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="Pilih Desa"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<AutoCompleteTextView
android:id="@+id/autoCompleteDesa"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
android:focusable="false"
android:clickable="true"
android:padding="12dp"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Kode Pos"
android:textColor="@android:color/black"
android:textSize="14sp" />
<EditText
android:id="@+id/etKodePos"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:hint="Isi jawaban Anda di sini"
android:inputType="number"
android:padding="12dp"
android:textSize="14sp" />
</LinearLayout>
</ScrollView>
<Button
android:id="@+id/buttonSimpan"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_margin="16dp"
android:background="@drawable/button_address_background"
android:text="Simpan"
android:textAllCaps="false"
android:textColor="@android:color/white"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent" />
<ProgressBar
android:id="@+id/submitProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/buttonSimpan"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -61,7 +61,7 @@
android:id="@+id/profile_image" android:id="@+id/profile_image"
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="100dp" android:layout_height="100dp"
android:src="@drawable/baseline_account_circle_24" android:src="@drawable/baseline_account_circle_100"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent"/>
@ -188,6 +188,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Simpan" android:text="Simpan"
style="@style/button.large.active.medium"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"

View File

@ -98,7 +98,7 @@
android:textAlignment="textEnd" android:textAlignment="textEnd"
android:textColor="@android:color/holo_red_light" android:textColor="@android:color/holo_red_light"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:visibility="gone" android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/til_login_password" /> app:layout_constraintTop_toBottomOf="@id/til_login_password" />
@ -136,6 +136,7 @@
android:id="@+id/tv_registrasi" android:id="@+id/tv_registrasi"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/signup" android:text="@string/signup"
android:textColor="@color/blue1" android:textColor="@color/blue1"
android:textStyle="bold" /> android:textStyle="bold" />

View File

@ -69,6 +69,35 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/layout_rejected"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical"
android:gravity="center"
android:background="@drawable/bg_product_active"
android:padding="8dp"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:textAlignment="center"
android:text="Permintaan Buka Toko Anda sebelumnya ditolak! Silahkan lakukan penyesuaian berdasarkan alasan berikut:"/>
<TextView
android:id="@+id/tv_rejected_reason"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/body_medium"
android:fontFamily="@font/dmsans_bold"
android:layout_marginTop="8dp"
android:text="KTP tidak sesuai"/>
</LinearLayout>
<!-- Nama Toko --> <!-- Nama Toko -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -344,12 +373,6 @@
</LinearLayout> </LinearLayout>
<!-- Kecamatan --> <!-- Kecamatan -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -373,15 +396,37 @@
</LinearLayout> </LinearLayout>
<EditText <LinearLayout
android:id="@+id/et_subdistrict"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/bg_text_field" android:background="@drawable/bg_text_field"
android:hint="Isi kecamatan tempat toko Anda di sini" android:gravity="center_vertical"
android:layout_marginTop="10dp">
<Spinner
android:id="@+id/spinner_subdistrict"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="8dp" android:padding="8dp"
style="@style/body_small" style="@style/body_small"
android:layout_marginTop="10dp"/> android:background="@null"/>
<ProgressBar
android:id="@+id/subdistrict_progress_bar"
android:layout_width="24dp"
android:layout_height="24dp"
android:padding="4dp"
android:visibility="gone"/>
<!-- 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" />
</LinearLayout> </LinearLayout>
@ -503,7 +548,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:layout_marginBottom="24dp"> android:layout_marginBottom="24dp"
android:visibility="gone">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -540,6 +586,74 @@
</LinearLayout> </LinearLayout>
<!-- Nama Bank-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
<!-- Label Jenis UMKM -->
<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="10. Bank"
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>
<!-- 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:gravity="center_vertical"
android:layout_marginTop="10dp">
<Spinner
android:id="@+id/spinner_bank_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="8dp"
style="@style/body_small"
android:background="@null"/>
<ProgressBar
android:id="@+id/bank_name_progress_bar"
android:layout_width="24dp"
android:layout_height="24dp"
android:padding="4dp"
android:visibility="gone"/>
<!-- 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" />
</LinearLayout>
</LinearLayout>
<!-- Nomor Rekening --> <!-- Nomor Rekening -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -583,6 +697,48 @@
</LinearLayout> </LinearLayout>
<!-- Nama Pemilik 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="12. Nama Pemilik 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/et_account_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:hint="Isi nama pemilik rekening"
android:padding="8dp"
style="@style/body_small"
android:layout_marginTop="10dp"/>
</LinearLayout>
<!-- Kurir --> <!-- Kurir -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -598,7 +754,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="12. Pilih Kurir Pengiriman" android:text="13. Pilih Kurir Pengiriman"
style="@style/body_medium" style="@style/body_medium"
android:layout_marginEnd="4dp"/> android:layout_marginEnd="4dp"/>
@ -651,7 +807,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="13. Foto Profil Toko" android:text="14. Foto Profil Toko"
style="@style/body_medium" style="@style/body_medium"
android:layout_marginEnd="4dp"/> android:layout_marginEnd="4dp"/>
@ -707,7 +863,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="14. Dokumen KTP" android:text="15. Dokumen KTP"
style="@style/body_medium" style="@style/body_medium"
android:layout_marginEnd="4dp"/> android:layout_marginEnd="4dp"/>
@ -774,7 +930,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="15. Dokumen NIB" android:text="16. Dokumen NIB"
style="@style/body_medium" style="@style/body_medium"
android:layout_marginEnd="4dp"/> android:layout_marginEnd="4dp"/>
@ -841,7 +997,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="16. Dokumen NPWP" android:text="17. Dokumen NPWP"
style="@style/body_medium" style="@style/body_medium"
android:layout_marginEnd="4dp"/> android:layout_marginEnd="4dp"/>
@ -896,7 +1052,8 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="horizontal"
android:visibility="gone">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -916,12 +1073,26 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/checkbox_approve"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Saya telah membaca dan menyetujui Syarat dan Ketentuan aplikasi" />
</LinearLayout>
<FrameLayout <FrameLayout
android:id="@+id/map_container" android:id="@+id/map_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="200dp" android:layout_height="200dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:background="@android:color/darker_gray"> android:background="@android:color/darker_gray"
android:visibility="gone">
<ImageView <ImageView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -933,7 +1104,7 @@
<Button <Button
android:id="@+id/btn_register" android:id="@+id/btn_register"
style="@style/button.large.disabled.long" style="@style/button.large.disabled.long"
android:enabled="false" android:enabled="true"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:text="Daftar" /> android:text="Daftar" />

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:gravity="top">
<include
android:id="@+id/header_reset_pass"
layout="@layout/header" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Masukkan alamat email anda. Periksa email anda untuk mendapatkan password baru"
android:textAlignment="center"
android:layout_marginVertical="24dp"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Co: 123@gmail.com"
android:inputType="textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btn_reset"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Kirim Password Baru"
android:layout_marginBottom="16dp" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
</LinearLayout>

View File

@ -15,61 +15,93 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<!-- Store Information --> <!-- Store Information Card -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/storeInfoCard"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="16dp"
app:cardBackgroundColor="@android:color/white"
app:cardCornerRadius="16dp"
app:cardElevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/searchContainer">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/storeInfoContainer" android:id="@+id/storeInfoContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/blue_50" android:padding="24dp">
android:layout_marginTop="16dp"
android:padding="24dp" <!-- Store Image with Material Card wrapper -->
app:layout_constraintTop_toBottomOf="@id/searchContainer"> <com.google.android.material.card.MaterialCardView
android:id="@+id/storeImageCard"
android:layout_width="80dp"
android:layout_height="80dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView <ImageView
android:id="@+id/ivStoreImage" android:id="@+id/ivStoreImage"
android:layout_width="64dp" android:layout_width="match_parent"
android:layout_height="64dp" android:layout_height="match_parent"
android:background="@drawable/circle_background"
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/placeholder_image" /> tools:src="@drawable/placeholder_image" />
</com.google.android.material.card.MaterialCardView>
<!-- Store Name -->
<TextView <TextView
android:id="@+id/tvStoreName" android:id="@+id/tvStoreName"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="20dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@android:color/black" android:textColor="@android:color/black"
android:textSize="18sp" android:textSize="20sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/ivStoreImage" app:layout_constraintStart_toEndOf="@id/storeImageCard"
app:layout_constraintTop_toTopOf="@id/ivStoreImage" app:layout_constraintTop_toTopOf="@id/storeImageCard"
tools:text="SnackEnak" /> tools:text="SnackEnak Store" />
<!-- Store Type -->
<TextView <TextView
android:id="@+id/tvStoreType" android:id="@+id/tvStoreType"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="20dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:textSize="14sp" android:background="@drawable/search_background"
android:paddingStart="12dp"
android:paddingTop="4dp"
android:paddingEnd="12dp"
android:paddingBottom="4dp"
android:textColor="@color/blue_500"
android:textSize="12sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/ivStoreImage" app:layout_constraintStart_toEndOf="@id/storeImageCard"
app:layout_constraintTop_toBottomOf="@id/tvStoreName" app:layout_constraintTop_toBottomOf="@id/tvStoreName"
tools:text="Makanan Ringan" /> tools:text="Makanan Ringan" />
<!-- Rating Container -->
<LinearLayout <LinearLayout
android:id="@+id/storeRatingContainer" android:id="@+id/storeRatingContainer"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="20dp"
android:layout_marginTop="4dp" android:layout_marginTop="8dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintStart_toEndOf="@id/ivStoreImage" app:layout_constraintStart_toEndOf="@id/storeImageCard"
app:layout_constraintTop_toBottomOf="@id/tvStoreType"> app:layout_constraintTop_toBottomOf="@id/tvStoreType">
<ImageView <ImageView
@ -84,87 +116,106 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:textSize="12sp" android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold" android:textStyle="bold"
tools:text="5.0" /> tools:text="4.8" />
</LinearLayout> </LinearLayout>
<!-- Location and Status Row -->
<LinearLayout
android:id="@+id/locationStatusContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/storeImageCard"
app:layout_constraintTop_toBottomOf="@id/storeRatingContainer">
<!-- Location with Icon -->
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@drawable/ic_location"
app:tint="@color/black_300" />
<TextView <TextView
android:id="@+id/tvStoreLocation" android:id="@+id/tvStoreLocation"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="4dp"
android:layout_marginTop="4dp" android:layout_weight="1"
android:layout_marginEnd="16dp" android:ellipsize="end"
android:textSize="12sp" android:maxLines="1"
app:layout_constraintEnd_toStartOf="@id/tvActiveStatus" android:textColor="@color/blue_400"
app:layout_constraintStart_toEndOf="@id/ivStoreImage" android:textSize="13sp"
app:layout_constraintTop_toBottomOf="@id/storeRatingContainer"
tools:text="Kabupaten Serang" /> tools:text="Kabupaten Serang" />
<!-- Status Indicator -->
<View
android:id="@+id/statusDot"
android:layout_width="6dp"
android:layout_height="6dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="6dp"
android:background="@drawable/baseline_circle_24"
android:backgroundTint="@color/black_300"/>
<TextView <TextView
android:id="@+id/tvActiveStatus" android:id="@+id/tvActiveStatus"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="4dp" android:textColor="@color/blue_500"
android:paddingEnd="4dp"
android:text="Aktif"
android:textColor="@android:color/black"
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/tvStoreLocation" android:fontFamily="@font/dmsans_semibold"
app:layout_constraintEnd_toEndOf="parent" tools:text="Aktif" />
app:layout_constraintTop_toTopOf="@id/tvStoreLocation" /> </LinearLayout>
<!-- <ImageButton-->
<!-- android:id="@+id/btnChevron"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:background="?attr/selectableItemBackgroundBorderless"-->
<!-- android:contentDescription="More"-->
<!-- android:src="@drawable/ic_chevron_right"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent" />-->
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Divider -->
<com.google.android.material.divider.MaterialDivider <com.google.android.material.divider.MaterialDivider
android:id="@+id/divider_product" android:id="@+id/divider_product"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/storeInfoContainer"/> android:layout_marginTop="24dp"
app:dividerColor="@color/black_200"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/storeInfoCard" />
<ProgressBar
<!-- Tab Layout: TO DO implement after review --> android:id="@+id/progress_bar_detail_prod_item"
<!-- <com.google.android.material.tabs.TabLayout--> android:layout_width="wrap_content"
<!-- android:id="@+id/tabLayout"--> android:layout_height="wrap_content"
<!-- android:layout_width="match_parent"--> android:visibility="gone"
<!-- android:layout_height="wrap_content"--> android:layout_gravity="center"
<!-- app:layout_constraintTop_toBottomOf="@id/storeInfoContainer"--> app:layout_constraintBottom_toBottomOf="parent"
<!-- app:tabIndicatorColor="@color/colorPrimary"--> app:layout_constraintEnd_toEndOf="parent"
<!-- app:tabSelectedTextColor="@color/colorPrimary"--> app:layout_constraintStart_toStartOf="parent"
<!-- app:tabTextColor="@android:color/darker_gray">--> app:layout_constraintTop_toBottomOf="@id/divider_product"/>
<!-- <com.google.android.material.tabs.TabItem-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="Produk" />-->
<!-- <com.google.android.material.tabs.TabItem-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="Kategori" />-->
<!-- </com.google.android.material.tabs.TabLayout>-->
<!-- Products RecyclerView --> <!-- Products RecyclerView -->
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_products" android:id="@+id/rv_products"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:clipToPadding="false"
android:paddingBottom="16dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/divider_product" app:layout_constraintTop_toBottomOf="@id/divider_product"
app:spanCount="2" app:spanCount="2"
tools:listitem="@layout/item_product_grid" /> tools:listitem="@layout/item_product_grid" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -14,18 +14,39 @@
android:textStyle="bold" android:textStyle="bold"
android:layout_marginBottom="16dp" /> android:layout_marginBottom="16dp" />
<com.google.android.material.textfield.TextInputLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="8dp" android:orientation="horizontal"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"> android:background="@drawable/bg_text_field"
android:gravity="center_vertical"
android:layout_marginTop="10dp">
<com.google.android.material.textfield.TextInputEditText <Spinner
android:id="@+id/edt_bank_name" android:id="@+id/spinner_bank_name"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="Nama Bank" /> android:layout_weight="1"
</com.google.android.material.textfield.TextInputLayout> android:padding="8dp"
style="@style/body_small"
android:background="@null"/>
<ProgressBar
android:id="@+id/bank_name_progress_bar"
android:layout_width="24dp"
android:layout_height="24dp"
android:padding="4dp"
android:visibility="gone"/>
<!-- 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" />
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -168,8 +168,13 @@
app:layout_constraintTop_toBottomOf="@id/searchContainer" /> app:layout_constraintTop_toBottomOf="@id/searchContainer" />
<include <include
android:id="@+id/loading" android:id="@+id/loadingAll"
layout="@layout/view_loading"/> layout="@layout/view_loading"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<include <include
android:id="@+id/error" android:id="@+id/error"

View File

@ -304,6 +304,55 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<!-- Change Password Card -->
<androidx.cardview.widget.CardView
android:id="@+id/card_change_pass"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:foreground="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ImageView
android:id="@+id/ivChangePass"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_change_pass"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/tvChangePass"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Ubah Kata Sandi"
android:textSize="14sp"
app:layout_constraintStart_toEndOf="@id/ivChangePass"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/ivChangePassArrow" />
<ImageView
android:id="@+id/ivChangePassArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_arrow_right"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<!-- About Card --> <!-- About Card -->
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/card_about" android:id="@+id/card_about"

View File

@ -50,8 +50,9 @@
android:id="@+id/et_otp" android:id="@+id/et_otp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="Enter OTP" android:hint="Masukkan OTP"
android:inputType="number" android:inputType="number"
android:textSize="16dp"
android:textAlignment="center" android:textAlignment="center"
android:maxLength="6" /> android:maxLength="6" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
@ -61,7 +62,17 @@
android:id="@+id/btn_verify" android:id="@+id/btn_verify"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Verify" android:text="Kirim kode OTP"
style="@style/button.large.active.medium"
app:cornerRadius="8dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_back"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
android:text="Kembali"
app:cornerRadius="8dp"/> app:cornerRadius="8dp"/>
<!-- Resend OTP --> <!-- Resend OTP -->
@ -81,7 +92,7 @@
android:id="@+id/tv_resend_otp" android:id="@+id/tv_resend_otp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Resend" android:text="Kirim Ulang"
android:textColor="@color/blue1" android:textColor="@color/blue1"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>

View File

@ -233,6 +233,12 @@
android:padding="12dp" android:padding="12dp"
android:textSize="14sp" /> android:textSize="14sp" />
<CheckBox
android:id="@+id/checkbox_approve"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Saya telah membaca dan menyetujui Syarat dan Ketentuan aplikasi" />
<!-- Navigation Button (Previous) --> <!-- Navigation Button (Previous) -->
<Button <Button
android:id="@+id/btn_previous" android:id="@+id/btn_previous"
@ -240,11 +246,14 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:background="@drawable/bg_button_outline" android:background="@drawable/bg_button_outline"
android:visibility="gone"
android:text="Kembali" android:text="Kembali"
android:textAllCaps="false" android:textAllCaps="false"
android:textColor="@color/blue1" android:textColor="@color/blue1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/> style="@style/Widget.Material3.Button.OutlinedButton.Icon"/>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
<!-- Register Button --> <!-- Register Button -->

View File

@ -36,6 +36,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/baseline_edit_24" android:src="@drawable/baseline_edit_24"
android:focusable="true"
android:clickable="true"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/> app:layout_constraintTop_toTopOf="parent"/>

View File

@ -9,10 +9,9 @@
android:id="@+id/content" android:id="@+id/content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="start" android:gravity="center"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginVertical="8dp" android:layout_marginVertical="8dp"
android:layout_marginBottom="4dp"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:orientation="horizontal"> android:orientation="horizontal">
<RadioButton <RadioButton
@ -24,38 +23,53 @@
android:layout_height="wrap_content"/> android:layout_height="wrap_content"/>
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:weightSum="1">
<!-- Left Section -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.7"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/courier_name_cost" android:id="@+id/courier_name_cost"
android:fontFamily="@font/dmsans_semibold" android:fontFamily="@font/dmsans_semibold"
android:textSize="20sp" android:textSize="14sp"
android:padding="4dp" android:paddingHorizontal="2dp"
android:layout_width="wrap_content" android:paddingTop="4dp"
android:ellipsize="end"
android:scrollHorizontally="false"
android:singleLine="false"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="JNE"/> android:maxLines="2"
android:text="JNE Express"/>
<TextView <TextView
android:id="@+id/est_date" android:id="@+id/est_date"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="16sp" android:textSize="12sp"
android:paddingHorizontal="8dp" android:paddingHorizontal="4dp"
android:text="Estimasi 3-4 hari"/> android:text="Estimasi 3-4 hari"/>
</LinearLayout> </LinearLayout>
<!-- Right Section -->
<TextView <TextView
android:id="@+id/cost_price" android:id="@+id/cost_price"
android:textSize="16sp" android:layout_width="0dp"
android:gravity="center_vertical" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_weight="0.3"
android:textSize="14sp"
android:gravity="start"
android:fontFamily="@font/dmsans_semibold" android:fontFamily="@font/dmsans_semibold"
android:layout_width="match_parent" android:text="Rp15.000"/>
android:layout_height="match_parent" </LinearLayout>
android:text="Rp15.0000"/>
</LinearLayout> </LinearLayout>

Some files were not shown because too many files have changed in this diff Show More