Compare commits

16 Commits

Author SHA1 Message Date
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
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
59 changed files with 2858 additions and 534 deletions

View File

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

View File

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

View File

@ -90,7 +90,7 @@ data class OrdersItem(
val orderId: Int,
@field:SerializedName("city_id")
val cityId: Int,
val cityId: String,
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

@ -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,
@field:SerializedName("city_id")
val cityId: Int
val cityId: String
)
data class OrderListItemsItem(

View File

@ -82,7 +82,8 @@ data class Product(
data class CartItemCheckoutInfo(
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,
@field:SerializedName("city_id")
val cityId: Int
val cityId: String
)
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(
@field:SerializedName("village_id")
val villageId: String,
val villageId: String?,
@field:SerializedName("is_store_location")
val isStoreLocation: Boolean,
@ -29,7 +29,7 @@ data class AddressesItem(
val provinceId: String,
@field:SerializedName("phone")
val phone: String,
val phone: String?,
@field:SerializedName("street")
val street: String,
@ -38,7 +38,7 @@ data class AddressesItem(
val subdistrict: String,
@field:SerializedName("recipient")
val recipient: String,
val recipient: String?,
@field:SerializedName("id")
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

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

View File

@ -132,5 +132,5 @@ data class Orders(
val username: String? = null,
@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,
@field:SerializedName("city_id")
val cityId: Int? = null,
val cityId: 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.ProvinceResponse
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.SearchRequest
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.RegisterResponse
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.chat.ChatHistoryResponse
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.ReviewProductResponse
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.CreateAddressResponse
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.UpdateAddressResponse
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.CompletedOrderResponse
@ -391,18 +395,8 @@ interface ApiService {
@PUT("mystore/edit")
suspend fun updateStoreProfileMultipart(
@Part("store_name") storeName: RequestBody,
@Part("store_status") storeStatus: RequestBody,
@Part("store_description") storeDescription: RequestBody,
@Part("is_on_leave") isOnLeave: RequestBody,
@Part("city_id") cityId: RequestBody,
@Part("province_id") provinceId: RequestBody,
@Part("street") street: RequestBody,
@Part("subdistrict") subdistrict: RequestBody,
@Part("detail") detail: RequestBody,
@Part("postal_code") postalCode: RequestBody,
@Part("latitude") latitude: RequestBody,
@Part("longitude") longitude: RequestBody,
@Part("user_phone") userPhone: RequestBody,
@Part("store_type_id") storeTypeId: RequestBody,
@Part storeimg: MultipartBody.Part?
): Response<StoreDataResponse>
@ -454,6 +448,12 @@ interface ApiService {
@Body addressData: HashMap<String, Any?>
): Response<StoreAddressResponse>
@PUT("profile/address/edit/{id}")
suspend fun updateAddress(
@Path("id") addressId: Int,
@Body params: Map<String, @JvmSuppressWildcards Any>
): Response<UpdateAddressResponse>
@POST("search")
suspend fun saveSearchQuery(
@Body searchRequest: SearchRequest
@ -524,4 +524,14 @@ interface ApiService {
suspend fun getVillages(
@Path("subdistrictId") subdistrictId: String
): Response<VillagesResponse>
@POST("resetpass")
suspend fun postResetPass(
@Body request: ResetPassReq
): Response<ResetPassResponse>
@GET("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
import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.City
import com.alya.ecommerce_serang.data.api.dto.Province
import com.alya.ecommerce_serang.data.api.dto.StoreAddress
import com.alya.ecommerce_serang.data.api.dto.CityResponse
import com.alya.ecommerce_serang.data.api.dto.ProvinceResponse
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.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
import retrofit2.Response
class AddressRepository(private val apiService: ApiService) {
private val TAG = "AddressRepository"
suspend fun getProvinces(): List<Province> = withContext(Dispatchers.IO) {
Log.d(TAG, "getProvinces() called")
try {
val response = apiService.getProvinces()
Log.d(TAG, "getProvinces() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
// suspend fun getProvinces(): List<Province> = withContext(Dispatchers.IO) {
// Log.d(TAG, "getProvinces() called")
// try {
// val response = apiService.getProvinces()
// Log.d(TAG, "getProvinces() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
//
// // Log the raw response body for debugging
// val rawBody = response.raw().toString()
// Log.d(TAG, "Raw response: $rawBody")
//
// if (response.isSuccessful) {
// val responseBody = response.body()
// Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}")
//
// val provinces = responseBody?.data ?: emptyList()
// Log.d(TAG, "getProvinces() success, got ${provinces.size} provinces")
// return@withContext provinces
// } else {
// val errorBody = response.errorBody()?.string() ?: "Unknown error"
// Log.e(TAG, "getProvinces() error: $errorBody")
// throw Exception("API Error (${response.code()}): $errorBody")
// }
// } catch (e: Exception) {
// Log.e(TAG, "Exception in getProvinces()", e)
// throw Exception("Network error: ${e.message}")
// }
// }
// Log the raw response body for debugging
val rawBody = response.raw().toString()
Log.d(TAG, "Raw response: $rawBody")
// suspend fun getCities(provinceId: String): List<City> = withContext(Dispatchers.IO) {
// Log.d(TAG, "getCities() called with provinceId: $provinceId")
// try {
// val response = apiService.getCities(provinceId)
// Log.d(TAG, "getCities() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
//
// if (response.isSuccessful) {
// val responseBody = response.body()
// Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}")
//
// val cities = responseBody?.data ?: emptyList()
// Log.d(TAG, "getCities() success, got ${cities.size} cities")
// return@withContext cities
// } else {
// val errorBody = response.errorBody()?.string() ?: "Unknown error"
// Log.e(TAG, "getCities() error: $errorBody")
// throw Exception("API Error (${response.code()}): $errorBody")
// }
// } catch (e: Exception) {
// Log.e(TAG, "Exception in getCities()", e)
// throw Exception("Network error: ${e.message}")
// }
// }
if (response.isSuccessful) {
val responseBody = response.body()
Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}")
val provinces = responseBody?.data ?: emptyList()
Log.d(TAG, "getProvinces() success, got ${provinces.size} provinces")
return@withContext provinces
} else {
val errorBody = response.errorBody()?.string() ?: "Unknown error"
Log.e(TAG, "getProvinces() error: $errorBody")
throw Exception("API Error (${response.code()}): $errorBody")
}
} catch (e: Exception) {
Log.e(TAG, "Exception in getProvinces()", e)
throw Exception("Network error: ${e.message}")
}
suspend fun getProvinces(): Response<ProvinceResponse> {
return apiService.getProvinces()
}
suspend fun getCities(provinceId: String): List<City> = withContext(Dispatchers.IO) {
Log.d(TAG, "getCities() called with provinceId: $provinceId")
try {
val response = apiService.getCities(provinceId)
Log.d(TAG, "getCities() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
if (response.isSuccessful) {
val responseBody = response.body()
Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}")
val cities = responseBody?.data ?: emptyList()
Log.d(TAG, "getCities() success, got ${cities.size} cities")
return@withContext cities
} else {
val errorBody = response.errorBody()?.string() ?: "Unknown error"
Log.e(TAG, "getCities() error: $errorBody")
throw Exception("API Error (${response.code()}): $errorBody")
}
} catch (e: Exception) {
Log.e(TAG, "Exception in getCities()", e)
throw Exception("Network error: ${e.message}")
}
suspend fun getCities(provinceId: String): Response<CityResponse> {
return apiService.getCities(provinceId)
}
suspend fun getStoreAddress(): StoreAddress? = withContext(Dispatchers.IO) {
Log.d(TAG, "getStoreAddress() called")
try {
val response = apiService.getStoreAddress()
Log.d(TAG, "getStoreAddress() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
if (response.isSuccessful) {
val responseBody = response.body()
val rawJson = Gson().toJson(responseBody)
Log.d(TAG, "Response body: $rawJson")
val address = responseBody?.data
Log.d(TAG, "getStoreAddress() success, address: $address")
// Convert numeric strings to proper types if needed
address?.let {
// Handle city_id if it's a number
if (it.cityId.isBlank() && rawJson.contains("city_id")) {
try {
val cityId = JSONObject(rawJson).getJSONObject("store").optInt("city_id", 0)
if (cityId > 0) {
it.javaClass.getDeclaredField("cityId").apply {
isAccessible = true
set(it, cityId.toString())
}
Log.d(TAG, "Updated cityId to: ${it.cityId}")
}
} catch (e: Exception) {
Log.e(TAG, "Error parsing city_id", e)
}
}
// Handle province_id if it's a number
if (it.provinceId.isBlank() && rawJson.contains("province_id")) {
try {
val provinceId = JSONObject(rawJson).getJSONObject("store").optInt("province_id", 0)
if (provinceId > 0) {
it.javaClass.getDeclaredField("provinceId").apply {
isAccessible = true
set(it, provinceId.toString())
}
Log.d(TAG, "Updated provinceId to: ${it.provinceId}")
}
} catch (e: Exception) {
Log.e(TAG, "Error parsing province_id", e)
}
}
}
return@withContext address
} else {
val errorBody = response.errorBody()?.string() ?: "Unknown error"
Log.e(TAG, "getStoreAddress() error: $errorBody")
throw Exception("API Error (${response.code()}): $errorBody")
}
} catch (e: Exception) {
Log.e(TAG, "Exception in getStoreAddress()", e)
throw Exception("Network error: ${e.message}")
}
}
// suspend fun getStoreAddress(): StoreAddress? = withContext(Dispatchers.IO) {
// Log.d(TAG, "getStoreAddress() called")
// try {
// val response = apiService.getStoreAddress()
// Log.d(TAG, "getStoreAddress() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
//
// if (response.isSuccessful) {
// val responseBody = response.body()
// val rawJson = Gson().toJson(responseBody)
// Log.d(TAG, "Response body: $rawJson")
//
// val address = responseBody?.data
// Log.d(TAG, "getStoreAddress() success, address: $address")
//
// // Convert numeric strings to proper types if needed
// address?.let {
// // Handle city_id if it's a number
// if (it.cityId.isBlank() && rawJson.contains("city_id")) {
// try {
// val cityId = JSONObject(rawJson).getJSONObject("store").optInt("city_id", 0)
// if (cityId > 0) {
// it.javaClass.getDeclaredField("cityId").apply {
// isAccessible = true
// set(it, cityId.toString())
// }
// Log.d(TAG, "Updated cityId to: ${it.cityId}")
// }
// } catch (e: Exception) {
// Log.e(TAG, "Error parsing city_id", e)
// }
// }
//
// // Handle province_id if it's a number
// if (it.provinceId.isBlank() && rawJson.contains("province_id")) {
// try {
// val provinceId = JSONObject(rawJson).getJSONObject("store").optInt("province_id", 0)
// if (provinceId > 0) {
// it.javaClass.getDeclaredField("provinceId").apply {
// isAccessible = true
// set(it, provinceId.toString())
// }
// Log.d(TAG, "Updated provinceId to: ${it.provinceId}")
// }
// } catch (e: Exception) {
// Log.e(TAG, "Error parsing province_id", e)
// }
// }
// }
//
// return@withContext address
// } else {
// val errorBody = response.errorBody()?.string() ?: "Unknown error"
// Log.e(TAG, "getStoreAddress() error: $errorBody")
// throw Exception("API Error (${response.code()}): $errorBody")
// }
// } catch (e: Exception) {
// Log.e(TAG, "Exception in getStoreAddress()", e)
// throw Exception("Network error: ${e.message}")
// }
// }
suspend fun saveStoreAddress(
provinceId: String,
@ -171,4 +180,17 @@ class AddressRepository(private val apiService: ApiService) {
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

@ -53,48 +53,49 @@ class MyStoreRepository(private val apiService: ApiService) {
suspend fun updateStoreProfile(
storeName: RequestBody,
storeStatus: RequestBody,
storeDescription: RequestBody,
isOnLeave: RequestBody,
cityId: RequestBody,
provinceId: RequestBody,
street: RequestBody,
subdistrict: RequestBody,
detail: RequestBody,
postalCode: RequestBody,
latitude: RequestBody,
longitude: RequestBody,
userPhone: RequestBody,
storeType: RequestBody,
storeimg: MultipartBody.Part?
): Response<StoreDataResponse> {
return apiService.updateStoreProfileMultipart(
storeName, storeStatus, storeDescription, isOnLeave, cityId, provinceId,
street, subdistrict, detail, postalCode, latitude, longitude, userPhone, storeType, storeimg
)
): Response<StoreDataResponse>? {
return try {
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> {
return try {
Log.d("SellsRepository", "Add Evidence : $status")
Log.d(TAG, "Add Evidence : $status")
val response = apiService.getSellList(status)
if (response.isSuccessful) {
val allListSell = response.body()
if (allListSell != null) {
Log.d("SellsRepository", "Add Evidence successfully: ${allListSell.message}")
Log.d(TAG, "Add Evidence successfully: ${allListSell.message}")
Result.Success(allListSell)
} else {
Log.e("SellsRepository", "Response body was null")
Log.e(TAG, "Response body was null")
Result.Error(Exception("Empty response from server"))
}
} else {
val errorBody = response.errorBody()?.string() ?: "Unknown error"
Log.e("SellsRepository", "Error Add Evidence : $errorBody")
Log.e(TAG, "Error Add Evidence : $errorBody")
Result.Error(Exception(errorBody))
}
} catch (e: Exception) {
Log.e("SellsRepository", "Exception Add Evidence ", e)
Log.e(TAG, "Exception Add Evidence ", e)
Result.Error(e)
}
}
@ -119,7 +120,7 @@ class MyStoreRepository(private val apiService: ApiService) {
)
}
} catch (e: Exception) {
Log.e("MyStoreRepository", "Error fetching balance", e)
Log.e(TAG, "Error fetching balance", e)
Result.Error(e)
}
}
@ -133,11 +134,15 @@ class MyStoreRepository(private val apiService: ApiService) {
throw Exception("Failed to fetch store products: ${response.message()}")
}
} catch (e: Exception) {
Log.e("ProductRepository", "Error fetching store products", e)
Log.e(TAG, "Error fetching store products", e)
throw e
}
}
companion object {
private var TAG = "MyStoreRepository"
}
// private fun fetchBalance() {
// showLoading(true)
// 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.OrderDetailResponse
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.ProductResponse
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.profile.AddressDetailResponse
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.UpdateAddressResponse
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.CompletedOrderResponse
@ -294,6 +298,43 @@ class OrderRepository(private val apiService: ApiService) {
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?> {
return try {
val response = apiService.getUserProfile()
@ -319,6 +360,7 @@ class OrderRepository(private val apiService: ApiService) {
}
}
suspend fun uploadPaymentProof(request: AddEvidenceMultipartRequest): Result<AddEvidenceResponse> {
return try {
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.OtpRequest
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.VerifRegisReq
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.RegisterResponse
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.customer.order.ListCityResponse
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())
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)
} else {
throw IllegalArgumentException("$formName harus berupa file gambar (JPEG, JPG, atau PNG)")
@ -485,6 +492,30 @@ class UserRepository(private val apiService: ApiService) {
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{
private const val TAG = "UserRepository"
}

View File

@ -82,6 +82,11 @@ class LoginActivity : AppCompatActivity() {
startActivity(Intent(this, RegisterActivity::class.java))
finish()
}
binding.tvForgetPassword.setOnClickListener {
startActivity(Intent(this, ResetPassActivity::class.java))
finish()
}
}
private fun observeLoginState() {

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) {
Toast.makeText(this, "Error: $errorMessage", Toast.LENGTH_LONG).show()
// Optionally show error dialog
AlertDialog.Builder(this)
.setTitle("Gagal Ubah Password")
.setMessage(errorMessage)
.setPositiveButton("OK", null)
.show()
}
}

View File

@ -238,7 +238,8 @@ class RegisterStep3Fragment : Fragment() {
binding.autoCompleteKecamatan.setOnItemClickListener{ _, _, 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 ->
Log.d(TAG, "Selected subdistrict ID set to: $id")
@ -246,6 +247,11 @@ class RegisterStep3Fragment : Fragment() {
registerViewModel.getVillages(id)
binding.autoCompleteDesa.text.clear()
}
subdistictName?.let { name ->
Log.d(TAG, "Selected name subdistrict set to: $name")
registerViewModel.subdistrictName = name
}
}
binding.autoCompleteDesa.setOnItemClickListener{ _, _, position, _ ->
@ -375,7 +381,7 @@ class RegisterStep3Fragment : Fragment() {
val provinceId = registerViewModel.selectedProvinceId?.toInt() ?: 0
val cityId = registerViewModel.selectedCityId.toString()
val subDistrict = registerViewModel.selectedSubdistrict.toString()
val subDistrict = registerViewModel.subdistrictName.toString()
// val postalCode = registerViewModel.selectedPostalCode.toString()
val villageId = registerViewModel.selectedVillages ?: ""
@ -423,7 +429,7 @@ class RegisterStep3Fragment : Fragment() {
val provinceId = registerViewModel.selectedProvinceId
val cityId = registerViewModel.selectedCityId
val subDistrict = registerViewModel.selectedSubdistrict.toString()
val subDistrict = registerViewModel.selectedSubdistrict
val postalCode = registerViewModel.selectedPostalCode
Log.d(TAG, "Validating - Street: $street, SubDistrict: $subDistrict, PostalCode: $postalCode")
@ -462,6 +468,12 @@ class RegisterStep3Fragment : Fragment() {
return false
}
if (subDistrict == null) {
showError("Pilih kota/kabupaten terlebih dahulu")
binding.autoCompleteKecamatan.requestFocus()
return false
}
return true
}

View File

@ -30,6 +30,8 @@ class CartActivity : AppCompatActivity() {
private lateinit var sessionManager: SessionManager
private lateinit var storeAdapter: StoreAdapter
private var TAG = "Cart Activity"
private val viewModel: CartViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
@ -135,12 +137,44 @@ class CartActivity : AppCompatActivity() {
}
private fun startCheckoutWithWholesaleInfo(checkoutItems: List<CartItemCheckoutInfo>) {
// Extract cart item IDs and wholesale status
val cartItemIds = checkoutItems.map { it.cartItem.cartItemId }
val wholesaleArray = checkoutItems.map { it.isWholesale }.toBooleanArray()
val wholesalePriceMap = viewModel.cartItemWholesalePrice.value ?: emptyMap()
// Start checkout activity with the cart items and wholesale info
CheckoutActivity.startForCart(this, cartItemIds, wholesaleArray)
val updatedItems = checkoutItems.map { info ->
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() {
@ -233,7 +267,5 @@ class CartActivity : AppCompatActivity() {
val format = NumberFormat.getCurrencyInstance(Locale("id", "ID"))
return format.format(amount).replace("Rp", "Rp ")
}
}

View File

@ -96,18 +96,40 @@ class CheckoutActivity : AppCompatActivity() {
// Process Cart checkout flow
val cartItemIds = intent.getIntArrayExtra(EXTRA_CART_ITEM_IDS)?.toList() ?: emptyList()
val isWholesaleArray = intent.getBooleanArrayExtra(EXTRA_CART_ITEM_WHOLESALE)
val wholesalePricesArray = intent.getIntArrayExtra(EXTRA_CART_ITEM_WHOLESALE_PRICES)
if (cartItemIds.isNotEmpty()) {
// Create a map of cart item IDs to wholesale status if available
val wholesaleMap = if (isWholesaleArray != null && isWholesaleArray.size == cartItemIds.size) {
cartItemIds.mapIndexed { index, id -> id to isWholesaleArray[index] }.toMap()
// Build map of cartItemId -> isWholesale
val isWholesaleMap = if (isWholesaleArray != null && isWholesaleArray.size == cartItemIds.size) {
cartItemIds.mapIndexed { index, id ->
id to isWholesaleArray[index]
}.toMap()
} else {
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 {
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, "Error: No cart items specified", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Tidak ada item keranjang", Toast.LENGTH_SHORT).show()
finish()
}
}
@ -416,6 +438,7 @@ class CheckoutActivity : AppCompatActivity() {
const val EXTRA_PRICE = "PRICE"
const val EXTRA_ISWHOLESALE = "ISWHOLESALE"
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
@ -449,13 +472,17 @@ class CheckoutActivity : AppCompatActivity() {
fun startForCart(
context: Context,
cartItemIds: List<Int>,
isWholesaleArray: BooleanArray? = null
isWholesaleArray: BooleanArray? = null,
wholesalePrices: IntArray? = null
) {
val intent = Intent(context, CheckoutActivity::class.java).apply {
putExtra(EXTRA_CART_ITEM_IDS, cartItemIds.toIntArray())
if (isWholesaleArray != null) {
putExtra(EXTRA_CART_ITEM_WHOLESALE, isWholesaleArray)
}
if (wholesalePrices != null) {
putExtra(EXTRA_CART_ITEM_WHOLESALE_PRICES, wholesalePrices)
}
}
context.startActivity(intent)
}

View File

@ -93,30 +93,46 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
}
// 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 {
_isLoading.value = true
try {
// Get cart data
val cartResult = repository.getCart()
if (cartResult is Result.Success) {
// Find matching cart items
val matchingItems = mutableListOf<CartItemsItem>()
var storeData: DataItemCart? = null
for (store in cartResult.data) {
val storeItems = store.cartItems.filter { it.cartItemId in cartItemIds }
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
break
}
}
if (matchingItems.isNotEmpty() && storeData != null) {
// Create initial OrderRequest object
val orderRequest = OrderRequest(
addressId = 0,
paymentMethodId = 0,
@ -126,21 +142,26 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
isNego = false,
cartItemId = cartItemIds,
shipEtd = "",
// Add a list tracking which items are wholesale
isReseller = isWholesaleMap.any { it.value } // Set true if any item is wholesale
isReseller = isWholesaleMap.any { it.value }
)
// Create checkout data
_checkoutData.value = CheckoutData(
orderRequest = orderRequest,
productName = matchingItems.first().productName,
sellerName = storeData.storeName,
sellerId = storeData.storeId,
isBuyNow = false,
cartItems = matchingItems,
cartItemWholesaleMap = isWholesaleMap // Store the wholesale map
cartItems = matchingItems, // These now have updated wholesale prices
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()
calculateTotal()
} else {
@ -151,6 +172,7 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
}
} catch (e: Exception) {
_errorMessage.value = "Error: ${e.message}"
Log.e(TAG, "Error in initializeFromCart", e)
} finally {
_isLoading.value = false
}
@ -405,8 +427,6 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
}
}
companion object {
private const val TAG = "CheckoutViewModel"
}

View File

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

View File

@ -22,7 +22,6 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
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.ProvincesItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
@ -37,8 +36,8 @@ class AddAddressActivity : AppCompatActivity() {
private lateinit var binding: ActivityAddAddressBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
private var profileUser: Int = 1
private lateinit var locationManager: LocationManager
private var profileUserId: Int? = null
private var isRequestingLocation = false
@ -80,11 +79,15 @@ class AddAddressActivity : AppCompatActivity() {
)
windowInsets
}
viewModel.loadUserProfile()
// Get user profile from session manager
// profileUser =UserProfile.
viewModel.userProfile.observe(this){ user ->
user?.let { updateProfile(it) }
viewModel.userProfile.observe(this) { user ->
if (user != null) {
profileUserId = user.userId
Log.d(TAG, "Fetched userId = $profileUserId") // ✅ debug log
} else {
Log.e(TAG, "Error get profile")
}
}
setupToolbar()
@ -94,16 +97,10 @@ class AddAddressActivity : AppCompatActivity() {
setupButtonListeners()
setupObservers()
// Force trigger province loading to ensure it happens
viewModel.getProvinces()
}
private fun updateProfile(userProfile: UserProfile){
profileUser = userProfile.userId
}
// UI setup methods
private fun setupToolbar() {
binding.toolbar.setNavigationOnClickListener {
@ -280,12 +277,7 @@ class AddAddressActivity : AppCompatActivity() {
val postalCode = binding.etKodePos.text.toString().trim()
val recipient = binding.etNamaPenerima.text.toString().trim()
val phone = binding.etNomorHp.text.toString().trim()
val userId = try {
profileUser
} catch (e: Exception) {
Log.w(TAG, "Error getting userId, using default", e)
1 // Default userId for testing
}
val userId = profileUserId
val isStoreLocation = false
val provinceId = viewModel.selectedProvinceId
@ -333,7 +325,7 @@ class AddAddressActivity : AppCompatActivity() {
// Create request with all fields
val request = CreateAddressRequest(
userId = userId,
userId = userId!!,
lat = latitude!!, // Safe to use !! as we've checked above
long = longitude!!,
street = street,
@ -525,6 +517,6 @@ class AddAddressActivity : AppCompatActivity() {
}
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.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.repository.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result
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>>>()
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
var selectedProvinceId: Int?
get() = savedStateHandle.get<Int>("selectedProvinceId")
@ -40,6 +55,9 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
get() = savedStateHandle.get<String>("selectedCityId")
set(value) { savedStateHandle["selectedCityId"] = value }
var selectedSubdistrict: String? = null
var selectedVillages: String? = null
init {
// Load provinces on initialization
getProvinces()
@ -86,6 +104,8 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
}
}
fun getProvinces() {
_provincesState.value = ViewState.Loading
viewModelScope.launch {
@ -125,20 +145,127 @@ class AddAddressViewModel(private val repository: OrderRepository, private val u
}
}
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 setSelectedProvinceId(id: Int) {
selectedProvinceId = id
}
fun updateSelectedCityId(id: String) {
selectedCityId = id
fun detailAddress(addressId: Int){
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 loadUserProfile(){
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 {
when (val result = userRepo.fetchUserProfile()){
is Result.Success -> _userProfile.postValue(result.data)
is Result.Error -> _errorMessageUser.postValue(result.exception.message ?: "Unknown Error")
is Result.Loading -> null
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)
return params
}
fun loadUserProfile() {
viewModelScope.launch {
when (val result = repository.fetchUserProfile()) {
is Result.Success -> {
result.data?.let {
_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

@ -74,13 +74,17 @@ class AddressActivity : AppCompatActivity() {
}
private fun setupRecyclerView() {
adapter = AddressAdapter { address ->
// Select the address in the ViewModel
viewModel.selectAddress(address.id)
// Return immediately with the selected address
returnResultAndFinish(address.id)
}
adapter = AddressAdapter(
onAddressClick = { address ->
viewModel.selectAddress(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 {
layoutManager = LinearLayoutManager(this@AddressActivity)
@ -119,6 +123,7 @@ class AddressActivity : AppCompatActivity() {
val intent = Intent()
intent.putExtra(EXTRA_ADDRESS_ID, addressId)
setResult(RESULT_OK, intent)
finish()
}
companion object {

View File

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

View File

@ -1,21 +1,418 @@
package com.alya.ecommerce_serang.ui.order.address
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
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() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_edit_address)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
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?) {
super.onCreate(savedInstanceState)
binding = ActivityEditAddressBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager)
WindowCompat.setDecorFitsSystemWindows(window, false)
enableEdgeToEdge()
// Apply insets to your root layout
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
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 selectedSubdistrictId = getSelectedSubdistrictId()
val selectedVillageId = getSelectedVillageId()
return 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(),
cityId = selectedCityId.toString(),
subdistrict = selectedSubdistrictId.toString(),
villageId = selectedVillageId
)
}
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 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 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
}
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.util.Log
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.ProvincesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictsItem
@ -73,6 +74,10 @@ class SubdsitrictAdapter(
fun getSubdistrictId(position: Int): String? {
return cities.getOrNull(position)?.subdistrictId?.toString()
}
fun getSubdistrictName(position: Int): String? {
return cities.getOrNull(position)?.subdistrictName?.toString()
}
}
class VillagesAdapter(
@ -97,4 +102,75 @@ class VillagesAdapter(
fun getPostalCode(position: Int): String?{
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

@ -32,6 +32,7 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson
import java.io.File
import java.text.NumberFormat
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
@ -88,7 +89,8 @@ class OrderHistoryAdapter(
tvStoreName.text = storeName
// Set total amount
tvTotalAmount.text = order.totalAmount
tvTotalAmount.text = formatCurrency(order.totalAmount.toDouble())
// Set item count
val itemCount = order.orderItems.size
@ -599,6 +601,11 @@ class OrderHistoryAdapter(
}
}
private fun formatCurrency(amount: Double): String {
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
return formatter.format(amount).replace(",00", "")
}
}
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.data.api.response.customer.order.OrderItemsItem
import com.bumptech.glide.Glide
import java.text.NumberFormat
import java.util.Locale
class OrderProductAdapter : RecyclerView.Adapter<OrderProductAdapter.ProductViewHolder>() {
@ -46,7 +48,7 @@ class OrderProductAdapter : RecyclerView.Adapter<OrderProductAdapter.ProductView
tvQuantity.text = "${product.quantity} buah"
// Set price with currency format
tvProductPrice.text = formatCurrency(product.price)
tvProductPrice.text = formatCurrency(product.price.toDouble())
val fullImageUrl = when (val img = product.productImage) {
is String -> {
@ -65,10 +67,9 @@ class OrderProductAdapter : RecyclerView.Adapter<OrderProductAdapter.ProductView
private fun formatCurrency(amount: Int): String {
// In a real app, you would use NumberFormat for proper currency formatting
// For simplicity, just return a basic formatted string
return "Rp${amount}"
private fun formatCurrency(amount: Double): String {
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
return formatter.format(amount).replace(",00", "")
}
}
}

View File

@ -1,10 +1,12 @@
package com.alya.ecommerce_serang.ui.profile.editprofile
import android.Manifest
import android.app.Activity
import android.app.DatePickerDialog
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
@ -159,17 +161,14 @@ class EditProfileCustActivity : AppCompatActivity() {
}
private fun openImagePicker() {
// Check for permission first
if (ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE),
REQUEST_STORAGE_PERMISSION
)
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(permission), REQUEST_STORAGE_PERMISSION)
} else {
launchImagePicker()
}

View File

@ -1,6 +1,5 @@
package com.alya.ecommerce_serang.ui.profile.mystore
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
@ -20,7 +19,6 @@ import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.ViewCompat
@ -32,8 +30,10 @@ 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.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.ProvinceAdapter
import com.alya.ecommerce_serang.ui.order.address.SubdsitrictAdapter
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.RegisterStoreViewModel
@ -45,6 +45,9 @@ class RegisterStoreActivity : AppCompatActivity() {
private lateinit var provinceAdapter: ProvinceAdapter
private lateinit var cityAdapter: CityAdapter
private lateinit var subdistrictAdapter: SubdsitrictAdapter
private lateinit var bankAdapter: BankAdapter
// Request codes for file picking
private val PICK_STORE_IMAGE_REQUEST = 1001
private val PICK_KTP_REQUEST = 1002
@ -88,6 +91,8 @@ class RegisterStoreActivity : AppCompatActivity() {
provinceAdapter = ProvinceAdapter(this)
cityAdapter = CityAdapter(this)
subdistrictAdapter = SubdsitrictAdapter(this)
bankAdapter = BankAdapter(this)
Log.d(TAG, "onCreate: Adapters initialized")
setupDataBinding()
@ -101,8 +106,12 @@ class RegisterStoreActivity : AppCompatActivity() {
setupObservers()
Log.d(TAG, "onCreate: Observers setup completed")
setupMap()
Log.d(TAG, "onCreate: Map setup completed")
viewModel.latitude.value = "-6.2088"
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()
Log.d(TAG, "onCreate: Document uploads setup completed")
@ -161,16 +170,17 @@ class RegisterStoreActivity : AppCompatActivity() {
viewModel.ktpUri != null &&
viewModel.nibUri != null &&
viewModel.npwpUri != null &&
viewModel.selectedCouriers.isNotEmpty()
binding.btnRegister.isEnabled = isFormValid
viewModel.selectedCouriers.isNotEmpty() &&
!viewModel.accountName.value.isNullOrBlank()
binding.btnRegister.isEnabled = true
if (isFormValid) {
binding.btnRegister.setBackgroundResource(R.drawable.bg_button_active)
binding.btnRegister.setTextColor(ContextCompat.getColor(this, R.color.white))
} else {
binding.btnRegister.setBackgroundResource(R.drawable.bg_button_disabled)
binding.btnRegister.setTextColor(ContextCompat.getColor(this, R.color.black_300))
}
}
@ -225,6 +235,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
viewModel.registerState.observe(this) { result ->
when (result) {
@ -395,6 +427,11 @@ class RegisterStoreActivity : AppCompatActivity() {
if (cityId != null) {
Log.d(TAG, "Setting city ID: $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
} else {
Log.e(TAG, "Invalid city ID for position: $position")
@ -406,6 +443,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
if (provinceAdapter.isEmpty) {
Log.d(TAG, "Adding default province hint")
@ -417,6 +509,16 @@ class RegisterStoreActivity : AppCompatActivity() {
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")
}
@ -500,44 +602,44 @@ class RegisterStoreActivity : AppCompatActivity() {
validateRequiredFields()
}
private fun setupMap() {
Log.d(TAG, "setupMap: Setting up map container")
// This would typically integrate with Google Maps SDK
// For simplicity, we're just using a placeholder
binding.mapContainer.setOnClickListener {
Log.d(TAG, "Map container clicked, checking location permission")
// Request location permission if not granted
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
Log.d(TAG, "Location permission not granted, requesting permission")
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
LOCATION_PERMISSION_REQUEST
)
viewModel.latitude.value = "-6.2088"
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()
} else {
Log.d(TAG, "Location permission already granted, setting location")
// Show map selection UI
// This would typically launch Maps UI for location selection
// For now, we'll just set some dummy coordinates
viewModel.latitude.value = "-6.2088"
viewModel.longitude.value = "106.8456"
Log.d(TAG, "Location set - Lat: ${viewModel.latitude.value}, Long: ${viewModel.longitude.value}")
Toast.makeText(this, "Lokasi dipilih", Toast.LENGTH_SHORT).show()
}
}
Log.d(TAG, "setupMap: Map container setup completed")
}
// private fun setupMap() {
// Log.d(TAG, "setupMap: Setting up map container")
// // This would typically integrate with Google Maps SDK
// // For simplicity, we're just using a placeholder
// binding.mapContainer.setOnClickListener {
// Log.d(TAG, "Map container clicked, checking location permission")
// // Request location permission if not granted
// if (ContextCompat.checkSelfPermission(
// this,
// Manifest.permission.ACCESS_FINE_LOCATION
// ) != PackageManager.PERMISSION_GRANTED
// ) {
// Log.d(TAG, "Location permission not granted, requesting permission")
// ActivityCompat.requestPermissions(
// this,
// arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
// LOCATION_PERMISSION_REQUEST
// )
// viewModel.latitude.value = "-6.2088"
// 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()
// } else {
// Log.d(TAG, "Location permission already granted, setting location")
// // Show map selection UI
// // This would typically launch Maps UI for location selection
// // For now, we'll just set some dummy coordinates
// viewModel.latitude.value = "-6.2088"
// viewModel.longitude.value = "106.8456"
// Log.d(TAG, "Location set - Lat: ${viewModel.latitude.value}, Long: ${viewModel.longitude.value}")
// Toast.makeText(this, "Lokasi dipilih", Toast.LENGTH_SHORT).show()
// }
// }
//
// Log.d(TAG, "setupMap: Map container setup completed")
// }
private fun setupDataBinding() {
Log.d(TAG, "setupDataBinding: Setting up two-way data binding for text fields")
@ -617,25 +719,36 @@ class RegisterStoreActivity : AppCompatActivity() {
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 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()}")
viewModel.accountName.value = s.toString()
Log.d(TAG, "Account Name updated: ${s.toString()}")
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")

View File

@ -15,11 +15,15 @@ import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.lifecycleScope
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.store.profile.Payment
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.utils.ImageUtils.compressImage
import com.alya.ecommerce_serang.utils.SessionManager
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaTypeOrNull
@ -38,6 +42,8 @@ class BalanceTopUpActivity : AppCompatActivity() {
private lateinit var spinnerPaymentMethod: Spinner
private lateinit var edtTransactionDate: EditText
private lateinit var datePickerIcon: ImageView
private lateinit var layoutMBankingInstructions: View
private lateinit var layoutATMInstructions: View
private lateinit var btnSend: Button
private lateinit var sessionManager: SessionManager
@ -52,7 +58,22 @@ class BalanceTopUpActivity : AppCompatActivity() {
val imageUri = result.data?.data
imageUri?.let {
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)
edtTransactionDate = findViewById(R.id.edt_tgl_transaksi)
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)
// Setup header title
@ -98,10 +121,27 @@ class BalanceTopUpActivity : AppCompatActivity() {
// Fetch payment methods
fetchPaymentMethods()
setupClickListeners("1234567890")
// Setup submit button
btnSend.setOnClickListener {
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() {
@ -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() {
// Prevent multiple clicks
if (!btnSend.isEnabled) {
@ -316,7 +374,7 @@ class BalanceTopUpActivity : AppCompatActivity() {
// Show a dialog with the success message
runOnUiThread {
androidx.appcompat.app.AlertDialog.Builder(this@BalanceTopUpActivity)
AlertDialog.Builder(this@BalanceTopUpActivity)
.setTitle("Berhasil")
.setMessage(successMessage)
.setPositiveButton("OK") { dialog, _ ->
@ -350,7 +408,7 @@ class BalanceTopUpActivity : AppCompatActivity() {
// Show a dialog with the error message
runOnUiThread {
androidx.appcompat.app.AlertDialog.Builder(this@BalanceTopUpActivity)
AlertDialog.Builder(this@BalanceTopUpActivity)
.setTitle("Error Response")
.setMessage(errorMessage)
.setPositiveButton("OK") { dialog, _ ->
@ -392,4 +450,46 @@ class BalanceTopUpActivity : AppCompatActivity() {
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

@ -1,6 +1,5 @@
package com.alya.ecommerce_serang.ui.profile.mystore.profile.address
import android.app.Activity
import android.os.Bundle
import android.util.Log
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.data.api.dto.City
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.ApiService
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.utils.viewmodel.AddressViewModel
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel
import com.google.android.material.snackbar.Snackbar
class DetailStoreAddressActivity : AppCompatActivity() {
@ -30,10 +32,15 @@ class DetailStoreAddressActivity : AppCompatActivity() {
private var selectedProvinceId: String? = null
private var selectedCityId: String? = null
private var selectedSubdistrict: String? = null
private var provinces: List<Province> = 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 {
BaseViewModelFactory {
@ -58,13 +65,15 @@ class DetailStoreAddressActivity : AppCompatActivity() {
binding.tvError.visibility = View.GONE
// Set up header title
binding.header.headerTitle.text = "Atur Alamat Toko"
binding.headerAddressStore.headerTitle.text = "Atur Alamat Toko"
// Set up back button
binding.header.headerLeftIcon.setOnClickListener {
binding.headerAddressStore.headerLeftIcon.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
// subdistrictAdapter = SubdsitrictAdapter(this)
setupSpinners()
setupObservers()
setupSaveButton()
@ -113,11 +122,26 @@ class DetailStoreAddressActivity : AppCompatActivity() {
binding.spinnerCity.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
selectedCityId = if (position > 0) cities[position - 1].cityId else null
viewModel.getSubdistrict(selectedCityId.toString())
checkAllFieldsFilled()
}
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() {
@ -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
viewModel.storeAddress.observe(this) { address ->
currentAddress = address
Log.d(TAG, "Received store address: $address")
address?.let {
// Set the fields
binding.edtStreet.setText(it.street)
binding.edtSubdistrict.setText(it.subdistrict)
// binding.edtSubdistrict.setText(it.subdistrict)
binding.edtDetailAddress.setText(it.detail ?: "")
binding.edtPostalCode.setText(it.postalCode)
binding.edtLatitude.setText(it.latitude.toString())
binding.edtLongitude.setText(it.longitude.toString())
selectedProvinceId = it.provinceId
selectedCityId = it.cityId
selectedSubdistrict = it.subdistrict
// Find province index and select it after provinces are loaded
if (provinces.isNotEmpty()) {
@ -214,11 +277,12 @@ class DetailStoreAddressActivity : AppCompatActivity() {
}
// Observe save success
viewModel.saveSuccess.observe(this) {
if (it) {
Toast.makeText(this, "Alamat berhasil disimpan", Toast.LENGTH_SHORT).show()
setResult(Activity.RESULT_OK)
viewModel.saveSuccess.observe(this) { success ->
if (success) {
Log.d(TAG, "Address updated successfully")
finish()
} else {
Log.e(TAG, "Failed to update address")
}
}
}
@ -226,14 +290,15 @@ class DetailStoreAddressActivity : AppCompatActivity() {
private fun setupSaveButton() {
binding.btnSaveAddress.setOnClickListener {
val street = binding.edtStreet.text.toString()
val subdistrict = binding.edtSubdistrict.text.toString()
val detail = binding.edtDetailAddress.text.toString()
val postalCode = binding.edtPostalCode.text.toString()
val latitude = binding.edtLatitude.text.toString().toDoubleOrNull() ?: 0.0
val longitude = binding.edtLongitude.text.toString().toDoubleOrNull() ?: 0.0
val latitude = binding.edtLatitude.text.toString()
val longitude = binding.edtLongitude.text.toString()
val city = cities.find { it.cityId == selectedCityId }
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
if (selectedProvinceId.isNullOrEmpty() || city == null || street.isEmpty() || subdistrict.isEmpty() || postalCode.isEmpty()) {
@ -241,19 +306,35 @@ class DetailStoreAddressActivity : AppCompatActivity() {
return@setOnClickListener
}
// Save address
viewModel.saveStoreAddress(
val oldAddress = currentAddress ?: return@setOnClickListener
val newAddress = oldAddress.copy(
provinceId = selectedProvinceId!!,
provinceName = province?.provinceName ?: "",
cityId = city.cityId,
cityName = city.cityName,
street = street,
subdistrict = subdistrict,
subdistrict = subdistrictName,
detail = detail,
postalCode = postalCode,
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) {}
}
binding.edtStreet.addTextChangedListener(watcher)
binding.edtSubdistrict.addTextChangedListener(watcher)
binding.edtPostalCode.addTextChangedListener(watcher)
}
private fun checkAllFieldsFilled() {
val allValid = !selectedProvinceId.isNullOrEmpty()
&& !selectedCityId.isNullOrEmpty()
&& !selectedSubdistrict.isNullOrEmpty()
&& binding.edtStreet.text.isNotBlank()
&& binding.edtSubdistrict.text.isNotBlank()
&& binding.edtPostalCode.text.isNotBlank()
binding.btnSaveAddress.let {
@ -284,6 +364,7 @@ class DetailStoreAddressActivity : AppCompatActivity() {
it.isEnabled = false
it.setBackgroundResource(R.drawable.bg_button_disabled)
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
}
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) {
binding.progressBar.visibility = View.GONE
binding.tvError.visibility = View.VISIBLE

View File

@ -6,9 +6,12 @@ import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.AdapterView
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.Spinner
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
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.repository.PaymentInfoRepository
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.SessionManager
import com.alya.ecommerce_serang.utils.UriToFileConverter
@ -32,6 +36,7 @@ class PaymentInfoActivity : AppCompatActivity() {
private lateinit var sessionManager: SessionManager
private var selectedQrisImageUri: Uri? = null
private var selectedQrisImageFile: File? = null
private lateinit var bankAdapter: BankAdapter
// Store form data between dialog reopenings
private var savedBankName: String = ""
@ -95,6 +100,7 @@ class PaymentInfoActivity : AppCompatActivity() {
onBackPressedDispatcher.onBackPressed()
}
bankAdapter = BankAdapter(this)
setupRecyclerView()
setupObservers()
@ -173,10 +179,47 @@ class PaymentInfoActivity : AppCompatActivity() {
builder.setView(dialogView)
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
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 accountNameEditText = dialogView.findViewById<EditText>(R.id.edt_account_name)
val qrisPreview = dialogView.findViewById<ImageView>(R.id.iv_qris_preview)
@ -185,7 +228,10 @@ class PaymentInfoActivity : AppCompatActivity() {
// When reopening, restore the previously entered values
if (isReopened) {
bankNameEditText.setText(savedBankName)
val savedPosition = bankAdapter.findPositionByName(savedBankName)
if (savedPosition >= 0) {
spinnerBankName.setSelection(savedPosition)
}
bankNumberEditText.setText(savedBankNumber)
accountNameEditText.setText(savedAccountName)
@ -199,7 +245,7 @@ class PaymentInfoActivity : AppCompatActivity() {
btnAddQris.setOnClickListener {
// Save the current values before dismissing
savedBankName = bankNameEditText.text.toString().trim()
savedBankName = viewModel.selectedBankName ?: ""
savedBankNumber = bankNumberEditText.text.toString().trim()
savedAccountName = accountNameEditText.text.toString().trim()
@ -212,13 +258,13 @@ class PaymentInfoActivity : AppCompatActivity() {
}
btnSave.setOnClickListener {
val bankName = bankNameEditText.text.toString().trim()
val bankName = viewModel.selectedBankName ?: ""
val bankNumber = bankNumberEditText.text.toString().trim()
val accountName = accountNameEditText.text.toString().trim()
// Validation
if (bankName.isEmpty()) {
showSnackbar("Nama bank tidak boleh kosong")
showSnackbar("Pilih nama bank terlebih dahulu")
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.bumptech.glide.Glide
import com.google.gson.Gson
import java.text.NumberFormat
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
@ -96,7 +97,7 @@ class SellsAdapter(
val product = order.orderItems?.firstOrNull()
tvSellsProductName.text = product?.productName
tvSellsProductQty.text = "x${product?.quantity}"
tvSellsProductPrice.text = formatPrice(product?.price.toString())
tvSellsProductPrice.text = product?.price?.let { formatPrice(it.toInt()) }
val fullImageUrl = when (val img = product?.productImage) {
is String -> {
@ -169,7 +170,7 @@ class SellsAdapter(
val product = order.orderItems?.firstOrNull()
tvSellsProductName.text = product?.productName
tvSellsProductQty.text = "x${product?.quantity}"
tvSellsProductPrice.text = formatPrice(product?.price.toString())
tvSellsProductPrice.text = product?.price?.let { formatPrice(it.toInt()) }
val fullImageUrl = when (val img = product?.productImage) {
is String -> {
@ -185,7 +186,7 @@ class SellsAdapter(
.into(ivSellsProduct)
tvSellsQty.text = "${order.orderItems?.size} produk"
tvSellsPrice.text = formatPrice(order.totalAmount.toString())
tvSellsPrice.text = order.totalAmount?.let { formatPrice(it.toInt()) }
}
"paid" -> {
layoutOrders.visibility = View.GONE
@ -308,10 +309,9 @@ class SellsAdapter(
}
}
private fun formatPrice(price: String): String {
val priceDouble = price.toDoubleOrNull() ?: 0.0
val formattedPrice = String.format(Locale("id", "ID"), "Rp%,.0f", priceDouble)
return formattedPrice
private fun formatPrice(amount: Int): String {
val formatter = NumberFormat.getCurrencyInstance(Locale("in", "ID"))
return formatter.format(amount.toLong()).replace(",00", "")
}
}
}

View File

@ -7,8 +7,10 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.City
import com.alya.ecommerce_serang.data.api.dto.Province
import com.alya.ecommerce_serang.data.api.dto.StoreAddress
import com.alya.ecommerce_serang.data.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.Result
import kotlinx.coroutines.launch
class AddressViewModel(private val addressRepository: AddressRepository) : ViewModel() {
@ -21,8 +23,8 @@ class AddressViewModel(private val addressRepository: AddressRepository) : ViewM
private val _cities = MutableLiveData<List<City>>()
val cities: LiveData<List<City>> = _cities
private val _storeAddress = MutableLiveData<StoreAddress?>()
val storeAddress: LiveData<StoreAddress?> = _storeAddress
private val _storeAddress = MutableLiveData<AddressesItem?>()
val storeAddress: LiveData<AddressesItem?> get() = _storeAddress
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
@ -31,99 +33,249 @@ class AddressViewModel(private val addressRepository: AddressRepository) : ViewM
val errorMessage: LiveData<String> = _errorMessage
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() {
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
if (response.isSuccessful) {
_provinces.value = response.body()?.data ?: emptyList()
} else {
Log.e("EditAddressVM", "Failed to get provinces: ${response.message()}")
}
} catch (e: Exception) {
Log.e(TAG, "Error fetching provinces", e)
_errorMessage.value = "Failed to load provinces: ${e.message}"
_isLoading.value = false
Log.e("EditAddressVM", "Error getting provinces: ${e.message}")
}
}
}
fun fetchCities(provinceId: String) {
Log.d(TAG, "fetchCities() called with provinceId: $provinceId")
_isLoading.value = true
viewModelScope.launch {
try {
Log.d(TAG, "Calling addressRepository.getCities()")
val response = addressRepository.getCities(provinceId)
Log.d(TAG, "Received cities response: ${response.size} cities")
_cities.value = response
_isLoading.value = false
if (response.isSuccessful) {
_cities.value = response.body()?.cities ?: emptyList()
} else {
Log.e("EditAddressVM", "Failed to get cities: ${response.message()}")
}
} catch (e: Exception) {
Log.e(TAG, "Error fetching cities", e)
_errorMessage.value = "Failed to load cities: ${e.message}"
_isLoading.value = false
Log.e("EditAddressVM", "Error getting cities: ${e.message}")
}
}
}
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() {
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
if (response.isSuccessful) {
val storeAddress = response.body()?.addresses
?.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) {
Log.e(TAG, "Error fetching store address", e)
_errorMessage.value = "Failed to load store address: ${e.message}"
_isLoading.value = false
Log.e(TAG, "Error: ${e.message}")
}
}
}
fun saveStoreAddress(
provinceId: String,
provinceName: String,
cityId: String,
cityName: String,
street: String,
subdistrict: String,
detail: String,
postalCode: String,
latitude: Double,
longitude: Double
) {
Log.d(TAG, "saveStoreAddress() called with provinceId: $provinceId, cityId: $cityId")
_isLoading.value = true
// fun saveStoreAddress(
// provinceId: String,
// provinceName: String,
// cityId: String,
// cityName: String,
// street: String,
// subdistrict: String,
// detail: String,
// postalCode: String,
// latitude: Double,
// longitude: Double
// ) {
// Log.d(TAG, "saveStoreAddress() called with provinceId: $provinceId, cityId: $cityId")
// _isLoading.value = true
// viewModelScope.launch {
// try {
// Log.d(TAG, "Calling addressRepository.saveStoreAddress()")
// val success = addressRepository.saveStoreAddress(
// provinceId = provinceId,
// provinceName = provinceName,
// cityId = cityId,
// cityName = cityName,
// street = street,
// subdistrict = subdistrict,
// detail = detail,
// postalCode = postalCode,
// latitude = latitude,
// longitude = longitude
// )
// Log.d(TAG, "Save store address result: $success")
// _saveSuccess.value = success
// _isLoading.value = false
// } catch (e: Exception) {
// Log.e(TAG, "Error saving store address", e)
// _errorMessage.value = "Failed to save address: ${e.message}"
// _isLoading.value = false
// }
// }
// }
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 {
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
val response = addressRepository.updateAddress(oldAddress.id, params)
_saveSuccess.value = response.isSuccessful
} catch (e: Exception) {
Log.e(TAG, "Error saving store address", e)
_errorMessage.value = "Failed to save address: ${e.message}"
_isLoading.value = false
Log.e(TAG, "Error: ${e.message}")
_saveSuccess.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.viewModelScope
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.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.ApiService
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>()
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 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

@ -21,6 +21,8 @@ import java.text.NumberFormat
import java.util.Locale
class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
private var TAG = "MyStoreViewModel"
private val _myStoreProfile = MutableLiveData<Store?>()
val myStoreProfile: LiveData<Store?> = _myStoreProfile
@ -84,30 +86,40 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
if (store == null) {
_errorMessage.postValue("Data toko tidak tersedia")
Log.e(TAG, "Store data is null")
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(
storeName = storeName,
storeStatus = "active".toRequestBody(),
storeDescription = description,
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,
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) {
_errorMessage.postValue(e.message ?: "Unexpected error")
Log.e(TAG, "Exception updating store profile", e)
}
}
}
@ -120,13 +132,13 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
result.data.orders.size ?: 0
}
is Result.Error -> {
Log.e("SellsViewModel", "Error getting orders count: ${result.exception.message}")
Log.e(TAG, "Error getting orders count: ${result.exception.message}")
0
}
is Result.Loading -> 0
}
} catch (e: Exception) {
Log.e("SellsViewModel", "Exception getting orders count", e)
Log.e(TAG, "Exception getting orders count", e)
0
}
}
@ -138,7 +150,7 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
statuses.forEach { status ->
counts[status] = getTotalOrdersByStatus(status)
Log.d("SellsViewModel", "Status: $status, countOrder=${counts[status]}")
Log.d(TAG, "Status: $status, countOrder=${counts[status]}")
}
return counts

View File

@ -30,6 +30,9 @@ class PaymentInfoViewModel(private val repository: PaymentInfoRepository) : View
private val _deletePaymentSuccess = MutableLiveData<Boolean>()
val deletePaymentSuccess: LiveData<Boolean> = _deletePaymentSuccess
var selectedBankName: String? = null
val bankName = MutableLiveData<String>()
fun getPaymentInfo() {
_isLoading.value = true
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.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.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.utils.ImageUtils
@ -41,8 +43,17 @@ class RegisterStoreViewModel(
private val _citiesState = MutableLiveData<Result<List<CitiesItem>>>()
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 selectedCityId: String? = null
var selectedSubdistrict: String? = null
var selectedVillages: String? = null
var selectedBankName: String? = null
// Form fields
val storeName = MutableLiveData<String>()
@ -72,47 +83,89 @@ class RegisterStoreViewModel(
val selectedCouriers = mutableListOf<String>()
fun registerStore(context: Context) {
Log.d(TAG, "Starting registerStore()")
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)) {
_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"))
return
}
if (ktpUri != null && !ImageUtils.isAllowedFileType(context, ktpUri, allowedFileTypes)) {
_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"))
return
}
if (npwpUri != null && !ImageUtils.isAllowedFileType(context, npwpUri, allowedFileTypes)) {
_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"))
return
}
if (nibUri != null && !ImageUtils.isAllowedFileType(context, nibUri, allowedFileTypes)) {
_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"))
return
}
if (persetujuanUri != null && !ImageUtils.isAllowedFileType(context, persetujuanUri, allowedFileTypes)) {
_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"))
return
}
if (qrisUri != null && !ImageUtils.isAllowedFileType(context, qrisUri, allowedFileTypes)) {
_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"))
return
}
Log.d(TAG, "File type checks passed. Starting repository.registerStoreUser() call.")
viewModelScope.launch {
try {
_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(
context = context,
@ -139,25 +192,15 @@ class RegisterStoreViewModel(
accountName = accountName.value ?: ""
)
Log.d(TAG, "Repository returned result: $result")
_registerState.value = result
} catch (e: Exception) {
Log.e(TAG, "Exception during registerStore", 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 {
// Implement form validation logic
return !(storeName.value.isNullOrEmpty() ||
@ -174,8 +217,6 @@ class RegisterStoreViewModel(
nibUri == null)
}
// Function to fetch store types
fun fetchStoreTypes() {
_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 {
private const val TAG = "RegisterStoreUserViewModel"
}

View File

@ -8,6 +8,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
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.response.auth.LoginResponse
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
@ -71,6 +72,7 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
var selectedProvinceId: Int? = null
var selectedCityId: String? = null
var selectedSubdistrict: String? = null
var subdistrictName: String? = null
var selectedVillages: String? = null
var selectedPostalCode: String? = null
@ -382,6 +384,10 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
}
}
fun resetPass(request: ResetPassReq){
}
companion object {
private const val TAG = "RegisterViewModel"
}

View File

@ -26,14 +26,14 @@
android:paddingHorizontal="@dimen/horizontal_safe_area"
android:layout_marginTop="19dp">
<!-- Foto Produk -->
<!-- Bukti Bayar -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
<!-- Label Foto Produk -->
<!-- Label Bukti Bayar -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -42,7 +42,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Foto Produk"
android:text="Bukti Pembayaran"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
@ -275,12 +275,73 @@
</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>
<Button
android:id="@+id/btn_send"
android:text="Kirim"
style="@style/button.large.disabled.long"
android:enabled="false"
android:layout_marginBottom="16dp"/>
</LinearLayout>

View File

@ -10,7 +10,7 @@
tools:context=".ui.profile.mystore.profile.address.DetailStoreAddressActivity">
<include
android:id="@+id/header"
android:id="@+id/header_address_store"
layout="@layout/header" />
<ScrollView
@ -154,6 +154,58 @@
</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 -->
<LinearLayout
android:layout_width="match_parent"
@ -180,32 +232,6 @@
</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 -->
<LinearLayout
android:layout_width="match_parent"
@ -301,7 +327,8 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginEnd="8dp">
android:layout_marginEnd="8dp"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
@ -327,7 +354,8 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="8dp">
android:layout_marginStart="8dp"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"

View File

@ -5,6 +5,257 @@
android:id="@+id/main"
android:layout_width="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>

View File

@ -98,7 +98,7 @@
android:textAlignment="textEnd"
android:textColor="@android:color/holo_red_light"
android:layout_marginBottom="16dp"
android:visibility="gone"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/til_login_password" />

View File

@ -347,41 +347,57 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:text="6. Kecamatan"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="6. Kecamatan"
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_subdistrict"
android:layout_width="match_parent"
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/bg_text_field"
android:hint="Isi kecamatan tempat toko Anda di sini"
android:layout_weight="1"
android:text="*"
style="@style/body_medium"
android:textColor="@color/red_required"
android:layout_gravity="end"/>
</LinearLayout>
<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: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>
@ -503,7 +519,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
android:layout_marginBottom="24dp"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
@ -540,6 +557,74 @@
</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 -->
<LinearLayout
android:layout_width="match_parent"
@ -583,6 +668,48 @@
</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 -->
<LinearLayout
android:layout_width="match_parent"
@ -598,7 +725,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="12. Pilih Kurir Pengiriman"
android:text="13. Pilih Kurir Pengiriman"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
@ -651,7 +778,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="13. Foto Profil Toko"
android:text="14. Foto Profil Toko"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
@ -707,7 +834,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="14. Dokumen KTP"
android:text="15. Dokumen KTP"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
@ -774,7 +901,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="15. Dokumen NIB"
android:text="16. Dokumen NIB"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
@ -841,7 +968,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="16. Dokumen NPWP"
android:text="17. Dokumen NPWP"
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
@ -896,7 +1023,8 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:orientation="horizontal"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
@ -916,12 +1044,26 @@
</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
android:id="@+id/map_container"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="8dp"
android:background="@android:color/darker_gray">
android:background="@android:color/darker_gray"
android:visibility="gone">
<ImageView
android:layout_width="match_parent"
@ -933,7 +1075,7 @@
<Button
android:id="@+id/btn_register"
style="@style/button.large.disabled.long"
android:enabled="false"
android:enabled="true"
android:layout_marginTop="24dp"
android:layout_marginBottom="16dp"
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

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

View File

@ -245,6 +245,19 @@
android:textColor="@color/blue1"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
</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>
</ScrollView>
<!-- Register Button -->

View File

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

View File

@ -1,5 +1,5 @@
<resources>
<string name="app_name">Bisa UMKM</string>
<string name="app_name">PasarKlik</string>
<!--Placeholder-->
@ -146,5 +146,30 @@
<string name="typing">User is typing...</string>
<string name="buka_toko_desc">Mohon untuk melengkapi formulir pendaftaran ini agar dapat mengakses fitur penjual pada aplikasi.</string>
<!-- List Bank -->
<string-array name="bank_name_array">
<item>Allo Bank Indonesia</item>
<item>Bank Digital BCA</item>
<item>Bank Central Asia (BCA)</item>
<item>Bank BCA Syariah</item>
<item>Bank Jago</item>
<item>Bank Mandiri </item>
<item>Bank Kb Bukopi</item>
<item>Bank Jabar Banten Syariah</item>
<item> Bank Mandiri Taspen</item>
<item>Bank Maybank Indonesia </item>
<item>Bank Negara Indonesia (BNI)</item>
<item>Bank Permata</item>
<item>Bank Rakyat Indonesia (BRI)</item>
<item>Bank Saqu Indonesia</item>
<item>Bank Seabank Indonesia</item>
<item>Bank Sinarmas </item>
<item>Bank Syariah Indonesia (BSI)</item>
<item>Bank Tabungan Negara (BTN)</item>
<item>Bank UOB Indonesia</item>
<item>BPD Jawa Barat dan Banten</item>
<item>Super Bank Indonesia</item>
</string-array>
</resources>

View File

@ -1,5 +1,5 @@
[versions]
agp = "8.12.0"
agp = "8.9.2"
glide = "4.16.0"
gson = "2.11.0"
hiltAndroid = "2.56.2" # Updated from 2.44 for better compatibility