diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6a794d7..5ce0012 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,6 +29,9 @@
android:theme="@style/Theme.Ecommerce_serang"
android:usesCleartextTraffic="true"
tools:targetApi="31">
+
@@ -64,7 +67,7 @@
+ android:exported="false" />
@@ -157,6 +160,7 @@
+
@@ -168,6 +172,4 @@
android:value="fcm_default_channel" />
-
-
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/app/App.kt b/app/src/main/java/com/alya/ecommerce_serang/app/App.kt
index 55e0321..fa3dd4f 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/app/App.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/app/App.kt
@@ -1,50 +1,17 @@
package com.alya.ecommerce_serang.app
import android.app.Application
-import android.content.Context
-import android.util.Log
-import com.google.firebase.FirebaseApp
-import com.google.firebase.messaging.FirebaseMessaging
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class App : Application(){
- private val TAG = "AppSerang"
+// private val TAG = "AppSerang"
+//
+//// var tokenTes: String? = null
+//
+// override fun onCreate() {
+//
+// }
-// var tokenTes: String? = null
- override fun onCreate() {
- super.onCreate()
-
- // Initialize Firebase
- FirebaseApp.initializeApp(this)
-
- // Request FCM token at app startup
- retrieveFCMToken()
- }
-
- private fun retrieveFCMToken() {
- FirebaseMessaging.getInstance().token
- .addOnCompleteListener { task ->
- if (!task.isSuccessful) {
- Log.e(TAG, "Failed to get FCM token", task.exception)
- return@addOnCompleteListener
- }
-
- val token = task.result
-// tokenTes = token
- Log.d(TAG, "FCM token retrieved: $token")
-
- // Save token locally
- val sharedPreferences = getSharedPreferences("FCM_PREFS", Context.MODE_PRIVATE)
- sharedPreferences.edit().putString("FCM_TOKEN", token).apply()
-
- // Send to your server
- sendTokenToServer(token)
- }
- }
-
- private fun sendTokenToServer(token: String) {
- Log.d(TAG, "Would send token to server: $token")
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CancelOrderReq.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CancelOrderReq.kt
new file mode 100644
index 0000000..1009818
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/CancelOrderReq.kt
@@ -0,0 +1,11 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class CancelOrderReq (
+ @SerializedName("order_id")
+ val orderId: Int,
+
+ @SerializedName("reason")
+ val reason: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/FcmReq.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/FcmReq.kt
new file mode 100644
index 0000000..0f64faf
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/FcmReq.kt
@@ -0,0 +1,8 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class FcmReq (
+ @SerializedName("fcm_req")
+ val fcmToken: String?= null
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/VerifRegisReq.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/VerifRegisReq.kt
new file mode 100644
index 0000000..3b1054d
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/VerifRegisReq.kt
@@ -0,0 +1,11 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class VerifRegisReq (
+ @SerializedName("field")
+ val fieldRegis: String,
+
+ @SerializedName("value")
+ val valueRegis: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/FcmTokenResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/FcmTokenResponse.kt
new file mode 100644
index 0000000..0790e8c
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/FcmTokenResponse.kt
@@ -0,0 +1,9 @@
+package com.alya.ecommerce_serang.data.api.response.auth
+
+import com.google.gson.annotations.SerializedName
+
+data class FcmTokenResponse(
+
+ @field:SerializedName("message")
+ val message: String? = null
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/HasStoreResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/HasStoreResponse.kt
new file mode 100644
index 0000000..cd0a881
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/HasStoreResponse.kt
@@ -0,0 +1,9 @@
+package com.alya.ecommerce_serang.data.api.response.auth
+
+import com.google.gson.annotations.SerializedName
+
+data class HasStoreResponse(
+
+ @field:SerializedName("hasStore")
+ val hasStore: Boolean
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/ListStoreTypeResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/ListStoreTypeResponse.kt
new file mode 100644
index 0000000..c30af1f
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/ListStoreTypeResponse.kt
@@ -0,0 +1,21 @@
+package com.alya.ecommerce_serang.data.api.response.auth
+
+import com.google.gson.annotations.SerializedName
+
+data class ListStoreTypeResponse(
+
+ @field:SerializedName("storeTypes")
+ val storeTypes: List,
+
+ @field:SerializedName("message")
+ val message: String
+)
+
+data class StoreTypesItem(
+
+ @field:SerializedName("name")
+ val name: String,
+
+ @field:SerializedName("id")
+ val id: Int
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/RegisterStoreResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/RegisterStoreResponse.kt
new file mode 100644
index 0000000..973e594
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/auth/RegisterStoreResponse.kt
@@ -0,0 +1,57 @@
+package com.alya.ecommerce_serang.data.api.response.auth
+
+import com.google.gson.annotations.SerializedName
+
+data class RegisterStoreResponse(
+
+ @field:SerializedName("store")
+ val store: Store,
+
+ @field:SerializedName("message")
+ val message: String
+)
+
+data class Store(
+
+ @field:SerializedName("image")
+ val image: String,
+
+ @field:SerializedName("ktp")
+ val ktp: String,
+
+ @field:SerializedName("nib")
+ val nib: String,
+
+ @field:SerializedName("npwp")
+ val npwp: String,
+
+ @field:SerializedName("address_id")
+ val addressId: Int,
+
+ @field:SerializedName("description")
+ val description: String,
+
+ @field:SerializedName("store_type_id")
+ val storeTypeId: Int,
+
+ @field:SerializedName("is_on_leave")
+ val isOnLeave: Boolean,
+
+ @field:SerializedName("balance")
+ val balance: String,
+
+ @field:SerializedName("user_id")
+ val userId: Int,
+
+ @field:SerializedName("name")
+ val name: String,
+
+ @field:SerializedName("persetujuan")
+ val persetujuan: String,
+
+ @field:SerializedName("id")
+ val id: Int,
+
+ @field:SerializedName("status")
+ val status: String
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/order/CancelOrderResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/order/CancelOrderResponse.kt
new file mode 100644
index 0000000..24adeed
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/customer/order/CancelOrderResponse.kt
@@ -0,0 +1,14 @@
+package com.alya.ecommerce_serang.data.api.response.customer.order
+
+data class CancelOrderResponse(
+ val data: DataCancel,
+ val message: String
+)
+
+data class DataCancel(
+ val reason: String,
+ val createdAt: String,
+ val id: Int,
+ val orderId: Int
+)
+
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiConfig.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiConfig.kt
index 026c995..78e0394 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiConfig.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiConfig.kt
@@ -22,6 +22,9 @@ class ApiConfig {
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
+ .connectTimeout(60, TimeUnit.SECONDS) // Increase to 60 seconds
+ .readTimeout(60, TimeUnit.SECONDS) // Increase to 60 seconds
+ .writeTimeout(60, TimeUnit.SECONDS)
.build()
val retrofit = Retrofit.Builder()
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
index 05322c5..195ef18 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
@@ -3,11 +3,13 @@ package com.alya.ecommerce_serang.data.api.retrofit
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceRequest
import com.alya.ecommerce_serang.data.api.dto.AddPaymentInfoResponse
+import com.alya.ecommerce_serang.data.api.dto.CancelOrderReq
import com.alya.ecommerce_serang.data.api.dto.CartItem
import com.alya.ecommerce_serang.data.api.dto.CityResponse
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
+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.OrderRequest
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
@@ -20,10 +22,16 @@ import com.alya.ecommerce_serang.data.api.dto.ShippingServiceRequest
import com.alya.ecommerce_serang.data.api.dto.StoreAddressResponse
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest
+import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
import com.alya.ecommerce_serang.data.api.response.auth.CheckStoreResponse
+import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
+import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
+import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse
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.VerifRegisterResponse
import com.alya.ecommerce_serang.data.api.response.chat.ChatHistoryResponse
import com.alya.ecommerce_serang.data.api.response.chat.ChatListResponse
import com.alya.ecommerce_serang.data.api.response.chat.SendChatResponse
@@ -32,6 +40,7 @@ import com.alya.ecommerce_serang.data.api.response.customer.cart.AddCartResponse
import com.alya.ecommerce_serang.data.api.response.customer.cart.DeleteCartResponse
import com.alya.ecommerce_serang.data.api.response.customer.cart.ListCartResponse
import com.alya.ecommerce_serang.data.api.response.customer.cart.UpdateCartResponse
+import com.alya.ecommerce_serang.data.api.response.customer.order.CancelOrderResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateOrderResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateReviewResponse
@@ -73,6 +82,7 @@ import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Part
+import retrofit2.http.PartMap
import retrofit2.http.Path
import retrofit2.http.Query
@@ -82,20 +92,54 @@ interface ApiService {
@Body registerRequest: RegisterRequest
): Response
+ @POST("verif")
+ suspend fun verifValue (
+ @Body verifRegisReq: VerifRegisReq
+ ):VerifRegisterResponse
+
@GET("checkstore")
suspend fun checkStore (): Response
-// @Multipart
-// @POST("registerstore")
-// suspend fun registerStore(
-//
-// ): Response<>
+ @Multipart
+ @POST("registerstore")
+ suspend fun registerStore(
+ @Part("description") description: RequestBody,
+ @Part("store_type_id") storeTypeId: RequestBody,
+ @Part("latitude") latitude: RequestBody,
+ @Part("longitude") longitude: RequestBody,
+ @Part("street") street: RequestBody,
+ @Part("subdistrict") subdistrict: RequestBody,
+ @Part("city_id") cityId: RequestBody,
+ @Part("province_id") provinceId: RequestBody,
+ @Part("postal_code") postalCode: RequestBody,
+ @Part("detail") detail: RequestBody,
+ @Part("bank_name") bankName: RequestBody,
+ @Part("bank_num") bankNum: RequestBody,
+ @Part("store_name") storeName: RequestBody,
+ @Part storeimg: MultipartBody.Part?,
+ @Part ktp: MultipartBody.Part?,
+ @Part npwp: MultipartBody.Part?,
+ @Part nib: MultipartBody.Part?,
+ @Part persetujuan: MultipartBody.Part?,
+ @PartMap couriers: Map,
+ @Part qris: MultipartBody.Part?,
+ @Part("account_name") accountName: RequestBody,
+ ): Response
@POST("otp")
suspend fun getOTP(
@Body otpRequest: OtpRequest
):OtpResponse
+ @PUT("updatefcm")
+ suspend fun updateFcm(
+ @Body fcmReq: FcmReq
+ ): FcmTokenResponse
+
+ @GET("checkstore")
+ suspend fun checkStoreUser(
+ ): HasStoreResponse
+
@POST("login")
suspend fun login(
@Body loginRequest: LoginRequest
@@ -105,6 +149,10 @@ interface ApiService {
suspend fun allCategory(
): Response
+ @GET("storetype")
+ suspend fun listTypeStore(
+ ): Response
+
@GET("product")
suspend fun getAllProduct(): Response
@@ -131,6 +179,11 @@ interface ApiService {
@Body request: OrderRequest
): Response
+ @POST("order/cancel")
+ suspend fun cancelOrder(
+ @Body cancelReq: CancelOrderReq
+ ): Response
+
@GET("order/detail/{id}")
suspend fun getDetailOrder(
@Path("id") orderId: Int
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt
index 080c3cb..e162141 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/OrderRepository.kt
@@ -2,16 +2,17 @@ package com.alya.ecommerce_serang.data.repository
import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceMultipartRequest
+import com.alya.ecommerce_serang.data.api.dto.CancelOrderReq
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
-import com.alya.ecommerce_serang.data.api.dto.OrdersItem
import com.alya.ecommerce_serang.data.api.dto.ReviewProductItem
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
import com.alya.ecommerce_serang.data.api.dto.UserProfile
import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItemCart
+import com.alya.ecommerce_serang.data.api.response.customer.order.CancelOrderResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateOrderResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateReviewResponse
@@ -491,4 +492,23 @@ class OrderRepository(private val apiService: ApiService) {
}
+ suspend fun cancelOrder(cancelReq: CancelOrderReq): Result{
+ return try{
+ val response= apiService.cancelOrder(cancelReq)
+
+ if (response.isSuccessful){
+ response.body()?.let { cancelOrderResponse ->
+ Result.Success(cancelOrderResponse)
+ } ?: run {
+ Result.Error(Exception("Failed to cancel order"))
+ }
+ } else {
+ val errorMsg = response.errorBody()?.string() ?: "Unknown Error"
+ Result.Error(Exception(errorMsg))
+ }
+ }catch (e: Exception){
+ Result.Error(e)
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt
index ff202ee..cdffe08 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/UserRepository.kt
@@ -3,17 +3,30 @@ package com.alya.ecommerce_serang.data.repository
import android.content.Context
import android.net.Uri
import android.util.Log
+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.UserProfile
+import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
+import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
+import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
+import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse
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.RegisterStoreResponse
+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
import com.alya.ecommerce_serang.data.api.response.customer.profile.EditProfileResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.utils.FileUtils
import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.File
class UserRepository(private val apiService: ApiService) {
//post data without message/response
@@ -21,6 +34,31 @@ class UserRepository(private val apiService: ApiService) {
return apiService.getOTP(OtpRequest(email))
}
+ suspend fun listStoreType(): Result{
+ return try{
+ val response = apiService.listTypeStore()
+ if (response.isSuccessful) {
+ response.body()?.let {
+ Result.Success(it)
+ } ?: Result.Error(Exception("No store type"))
+ } else {
+ throw Exception("No response ${response.errorBody()?.string()}")
+ }
+ } catch (e:Exception){
+ Result.Error(e)
+ }
+ }
+
+ suspend fun getListProvinces(): ListProvinceResponse? {
+ val response = apiService.getListProv()
+ return if (response.isSuccessful) response.body() else null
+ }
+
+ suspend fun getListCities(provId : Int): ListCityResponse? {
+ val response = apiService.getCityProvId(provId)
+ return if (response.isSuccessful) response.body() else null
+ }
+
suspend fun registerUser(request: RegisterRequest): String {
val response = apiService.register(request) // API call
@@ -32,6 +70,169 @@ class UserRepository(private val apiService: ApiService) {
}
}
+ suspend fun registerStoreUser(
+ context: Context,
+ description: String,
+ storeTypeId: Int,
+ latitude: String,
+ longitude: String,
+ street: String,
+ subdistrict: String,
+ cityId: Int,
+ provinceId: Int,
+ postalCode: Int,
+ detail: String?,
+ bankName: String,
+ bankNum: Int,
+ storeName: String,
+ storeImg: Uri?,
+ ktp: Uri?,
+ npwp: Uri?,
+ nib: Uri?,
+ persetujuan: Uri?,
+ couriers: List,
+ qris: Uri?,
+ accountName: String
+ ): Result {
+ return try {
+ val descriptionPart = description.toRequestBody("text/plain".toMediaTypeOrNull())
+ val storeTypeIdPart = storeTypeId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
+ val latitudePart = latitude.toRequestBody("text/plain".toMediaTypeOrNull())
+ val longitudePart = longitude.toRequestBody("text/plain".toMediaTypeOrNull())
+ val streetPart = street.toRequestBody("text/plain".toMediaTypeOrNull())
+ val subdistrictPart = subdistrict.toRequestBody("text/plain".toMediaTypeOrNull())
+ val cityIdPart = cityId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
+ val provinceIdPart = provinceId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
+ val postalCodePart = postalCode.toString().toRequestBody("text/plain".toMediaTypeOrNull())
+ val detailPart = detail?.toRequestBody("text/plain".toMediaTypeOrNull())
+ val bankNamePart = bankName.toRequestBody("text/plain".toMediaTypeOrNull())
+ val bankNumPart = bankNum.toString().toRequestBody("text/plain".toMediaTypeOrNull())
+ val storeNamePart = storeName.toRequestBody("text/plain".toMediaTypeOrNull())
+ val accountNamePart = accountName.toRequestBody("text/plain".toMediaTypeOrNull())
+
+
+ // Create a Map for courier values
+ val courierMap = HashMap()
+ couriers.forEach { courier ->
+ courierMap["couriers[]"] = courier.toRequestBody("text/plain".toMediaTypeOrNull())
+ }
+
+ // Convert URIs to MultipartBody.Part
+ val storeImgPart = storeImg?.let {
+ val inputStream = context.contentResolver.openInputStream(it)
+ val file = File(context.cacheDir, "store_img_${System.currentTimeMillis()}")
+ inputStream?.use { input ->
+ file.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+ val mimeType = context.contentResolver.getType(it) ?: "application/octet-stream"
+ val requestFile = file.asRequestBody(mimeType.toMediaTypeOrNull())
+ MultipartBody.Part.createFormData("storeimg", file.name, requestFile)
+ }
+
+ val ktpPart = ktp?.let {
+ val inputStream = context.contentResolver.openInputStream(it)
+ val file = File(context.cacheDir, "ktp_${System.currentTimeMillis()}")
+ inputStream?.use { input ->
+ file.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+ val mimeType = context.contentResolver.getType(it) ?: "application/octet-stream"
+ val requestFile = file.asRequestBody(mimeType.toMediaTypeOrNull())
+ MultipartBody.Part.createFormData("ktp", file.name, requestFile)
+ }
+
+ val npwpPart = npwp?.let {
+ val inputStream = context.contentResolver.openInputStream(it)
+ val file = File(context.cacheDir, "npwp_${System.currentTimeMillis()}")
+ inputStream?.use { input ->
+ file.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+ val mimeType = context.contentResolver.getType(it) ?: "application/octet-stream"
+ val requestFile = file.asRequestBody(mimeType.toMediaTypeOrNull())
+ MultipartBody.Part.createFormData("npwp", file.name, requestFile)
+ }
+
+ val nibPart = nib?.let {
+ val inputStream = context.contentResolver.openInputStream(it)
+ val file = File(context.cacheDir, "nib_${System.currentTimeMillis()}")
+ inputStream?.use { input ->
+ file.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+ val mimeType = context.contentResolver.getType(it) ?: "application/octet-stream"
+ val requestFile = file.asRequestBody(mimeType.toMediaTypeOrNull())
+ MultipartBody.Part.createFormData("nib", file.name, requestFile)
+ }
+
+ val persetujuanPart = persetujuan?.let {
+ val inputStream = context.contentResolver.openInputStream(it)
+ val file = File(context.cacheDir, "persetujuan_${System.currentTimeMillis()}")
+ inputStream?.use { input ->
+ file.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+ val mimeType = context.contentResolver.getType(it) ?: "application/octet-stream"
+ val requestFile = file.asRequestBody(mimeType.toMediaTypeOrNull())
+ MultipartBody.Part.createFormData("persetujuan", file.name, requestFile)
+ }
+
+ val qrisPart = qris?.let {
+ val inputStream = context.contentResolver.openInputStream(it)
+ val file = File(context.cacheDir, "qris_${System.currentTimeMillis()}")
+ inputStream?.use { input ->
+ file.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+ val mimeType = context.contentResolver.getType(it) ?: "application/octet-stream"
+ val requestFile = file.asRequestBody(mimeType.toMediaTypeOrNull())
+ MultipartBody.Part.createFormData("qris", file.name, requestFile)
+ }
+
+ // Make the API call
+ val response = apiService.registerStore(
+ descriptionPart,
+ storeTypeIdPart,
+ latitudePart,
+ longitudePart,
+ streetPart,
+ subdistrictPart,
+ cityIdPart,
+ provinceIdPart,
+ postalCodePart,
+ detailPart ?: "".toRequestBody("text/plain".toMediaTypeOrNull()),
+ bankNamePart,
+ bankNumPart,
+ storeNamePart,
+ storeImgPart,
+ ktpPart,
+ npwpPart,
+ nibPart,
+ persetujuanPart,
+ courierMap,
+ qrisPart,
+ accountNamePart
+ )
+
+ // Check if response is successful
+ if (response.isSuccessful) {
+ Result.Success(response.body() ?: throw Exception("Response body is null"))
+ } else {
+ Result.Error(Exception("Registration failed with code: ${response.code()}"))
+ }
+
+ } catch (e: Exception) {
+ Result.Error(e)
+ }
+ }
+
suspend fun login(email: String, password: String): Result {
return try {
val response = apiService.login(LoginRequest(email, password))
@@ -130,6 +331,18 @@ class UserRepository(private val apiService: ApiService) {
Result.Error(e)
}
}
+
+ suspend fun checkStore(): HasStoreResponse{
+ return apiService.checkStoreUser()
+ }
+
+ suspend fun checkValue(request: VerifRegisReq): VerifRegisterResponse{
+ return apiService.verifValue(request)
+ }
+
+ suspend fun sendFcm(request: FcmReq): FcmTokenResponse{
+ return apiService.updateFcm(request)
+ }
companion object{
private const val TAG = "UserRepository"
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/MainActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/MainActivity.kt
index 35a85a4..12d3b44 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/MainActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/MainActivity.kt
@@ -1,8 +1,10 @@
package com.alya.ecommerce_serang.ui
+import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
+import android.util.Log
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
@@ -20,11 +22,15 @@ import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.databinding.ActivityMainBinding
import com.alya.ecommerce_serang.ui.notif.WebSocketManager
import com.alya.ecommerce_serang.utils.SessionManager
+import com.google.firebase.FirebaseApp
+import com.google.firebase.messaging.FirebaseMessaging
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
+ private val TAG = "MainActivity"
+
private lateinit var binding: ActivityMainBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
@@ -65,6 +71,11 @@ class MainActivity : AppCompatActivity() {
)
windowInsets
}
+ // Initialize Firebase
+ FirebaseApp.initializeApp(this)
+
+ // Request FCM token at app startup
+ retrieveFCMToken()
requestNotificationPermissionIfNeeded()
@@ -151,4 +162,31 @@ class MainActivity : AppCompatActivity() {
}
}
}
+
+ private fun retrieveFCMToken() {
+ FirebaseMessaging.getInstance().token
+ .addOnCompleteListener { task ->
+ if (!task.isSuccessful) {
+ Log.e(TAG, "Failed to get FCM token", task.exception)
+ return@addOnCompleteListener
+ }
+
+ val token = task.result
+// tokenTes = token
+ Log.d(TAG, "FCM token retrieved: $token")
+
+ // Save token locally
+ val sharedPreferences = getSharedPreferences("FCM_PREFS", Context.MODE_PRIVATE)
+ sharedPreferences.edit().putString("FCM_TOKEN", token).apply()
+
+ // Send to your server
+ sendTokenToServer(token)
+ }
+ }
+
+ private fun sendTokenToServer(token: String) {
+ Log.d(TAG, "Would send token to server: $token")
+ }
+
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/auth/LoginActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/LoginActivity.kt
index e2c4839..2b2a48f 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/auth/LoginActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/LoginActivity.kt
@@ -1,11 +1,14 @@
package com.alya.ecommerce_serang.ui.auth
+import android.content.Context
import android.content.Intent
import android.os.Bundle
+import android.util.Log
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
+import com.alya.ecommerce_serang.data.api.dto.FcmReq
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
@@ -14,9 +17,13 @@ import com.alya.ecommerce_serang.ui.MainActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.LoginViewModel
+import com.google.firebase.FirebaseApp
+import com.google.firebase.messaging.FirebaseMessaging
class LoginActivity : AppCompatActivity() {
+ private val TAG = "LoginActivity"
+
private lateinit var binding: ActivityLoginBinding
private val loginViewModel: LoginViewModel by viewModels{
BaseViewModelFactory {
@@ -35,6 +42,11 @@ class LoginActivity : AppCompatActivity() {
setupListeners()
observeLoginState()
+
+ FirebaseApp.initializeApp(this)
+
+ // Request FCM token at app startup
+ retrieveFCMToken()
}
private fun setupListeners() {
@@ -74,4 +86,35 @@ class LoginActivity : AppCompatActivity() {
}
}
}
+
+ private fun retrieveFCMToken() {
+ FirebaseMessaging.getInstance().token
+ .addOnCompleteListener { task ->
+ if (!task.isSuccessful) {
+ Log.e(TAG, "Failed to get FCM token", task.exception)
+ return@addOnCompleteListener
+ }
+
+ val token = task.result
+// tokenTes = token
+ Log.d(TAG, "FCM token retrieved: $token")
+
+ // Save token locally
+ val sharedPreferences = getSharedPreferences("FCM_PREFS", Context.MODE_PRIVATE)
+ sharedPreferences.edit().putString("FCM_TOKEN", token).apply()
+
+ // Send to your server
+ sendTokenToServer(token)
+ }
+ }
+
+ private fun sendTokenToServer(token: String) {
+ Log.d(TAG, "Would send token to server: $token")
+ val tokenFcm=FcmReq(
+ fcmToken = token
+ )
+ loginViewModel.sendFcm(tokenFcm)
+ Log.d(TAG, "Sent token fcm: $token")
+
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterActivity.kt
index 72d1e1a..f07dcd4 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterActivity.kt
@@ -12,6 +12,7 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
+import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
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
@@ -27,6 +28,17 @@ import java.util.Locale
class RegisterActivity : AppCompatActivity() {
private lateinit var binding: ActivityRegisterBinding
private lateinit var sessionManager: SessionManager
+
+ private var isEmailValid = false
+ private var isPhoneValid = false
+
+ // Track which validation was last performed
+ private var lastCheckField = ""
+
+ // Counter for signup validation
+ private var signupValidationsComplete = 0
+ private var signupInProgress = false
+
private val registerViewModel: RegisterViewModel by viewModels{
BaseViewModelFactory {
val apiService = ApiConfig.getUnauthenticatedApiService()
@@ -76,107 +88,248 @@ class RegisterActivity : AppCompatActivity() {
windowInsets
}
+ setupObservers()
- // Observe OTP state
- observeOtpState()
+ // Set up field validations
+ setupFieldValidations()
binding.btnSignup.setOnClickListener {
- // Retrieve values inside the click listener (so we get latest input)
- val birthDate = binding.etBirthDate.text.toString()
- val email = binding.etEmail.text.toString()
- val password = binding.etPassword.text.toString()
- val phone = binding.etNumberPhone.text.toString()
- val username = binding.etUsername.text.toString()
- val name = binding.etFullname.text.toString()
- val image = null
-
- val userData = RegisterRequest(name, email, password, username, phone, birthDate, image)
-
- Log.d("RegisterActivity", "Requesting OTP for email: $email")
-
- // Request OTP and wait for success before showing dialog
- registerViewModel.requestOtp(userData.email.toString())
-
- // Observe OTP state and show OTP dialog only when successful
- registerViewModel.otpState.observe(this) { result ->
- when (result) {
- is Result.Success -> {
- Log.d("RegisterActivity", "OTP sent successfully. Showing OTP dialog.")
- // Show OTP dialog after OTP is successfully sent
- val otpBottomSheet = OtpBottomSheetDialog(userData) { fullUserData ->
- Log.d("RegisterActivity", "OTP entered successfully. Proceeding with registration.")
- registerViewModel.registerUser(fullUserData) // Send complete data
- }
- otpBottomSheet.show(supportFragmentManager, "OtpBottomSheet")
- }
- is Result.Error -> {
- // Show error message if OTP request fails
- Log.e("RegisterActivity", "Failed to request OTP: ${result.exception.message}")
- Toast.makeText(this, "Failed to request OTP: ${result.exception.message}", Toast.LENGTH_LONG).show()
- }
- is Result.Loading -> {
- // Optional: Show loading indicator
- }
- }
- }
-
- // Observe Register state
- observeRegisterState()
+ handleSignUp()
}
- binding.tvLoginAlt.setOnClickListener{
+ binding.tvLoginAlt.setOnClickListener {
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
}
- binding.etBirthDate.setOnClickListener{
+ binding.etBirthDate.setOnClickListener {
showDatePicker()
}
}
- private fun observeOtpState() {
- registerViewModel.otpState.observe(this) { result ->
- when (result) {
- is Result.Loading -> {
- // Show loading indicator
- binding.progressBarOtp.visibility = android.view.View.VISIBLE
- }
- is Result.Success -> {
- // Hide loading indicator and show success message
- binding.progressBarOtp.visibility = android.view.View.GONE
-// Toast.makeText(this@RegisterActivity, result.data, Toast.LENGTH_SHORT).show()
- }
- is Result.Error -> {
- // Hide loading indicator and show error message
- binding.progressBarOtp.visibility = android.view.View.GONE
- Toast.makeText(this, "OTP Request Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
- }
+ private fun setupFieldValidations() {
+ // Validate email when focus changes
+ binding.etEmail.setOnFocusChangeListener { _, hasFocus ->
+ if (!hasFocus) {
+ val email = binding.etEmail.text.toString()
+ if (email.isNotEmpty()) {
+ validateEmail(email, false)
}
}
+ }
+
+ // Validate phone when focus changes
+ binding.etNumberPhone.setOnFocusChangeListener { _, hasFocus ->
+ if (!hasFocus) {
+ val phone = binding.etNumberPhone.text.toString()
+ if (phone.isNotEmpty()) {
+ validatePhone(phone, false)
+ }
+ }
+ }
}
- private fun observeRegisterState() {
- registerViewModel.registerState.observe(this) { result ->
- when (result) {
- is Result.Loading -> {
- // Show loading indicator for registration
- binding.progressBarRegister.visibility = android.view.View.VISIBLE
+ private fun validateEmail(email: String, isSignup: Boolean) {
+ lastCheckField = "email"
+ Log.d("RegisterActivity", "Validating email: $email (signup: $isSignup)")
+
+ val checkValueEmail = VerifRegisReq(
+ fieldRegis = "email",
+ valueRegis = email
+ )
+ registerViewModel.checkValueReg(checkValueEmail)
+ }
+
+ private fun validatePhone(phone: String, isSignup: Boolean) {
+ lastCheckField = "phone"
+ Log.d("RegisterActivity", "Validating phone: $phone (signup: $isSignup)")
+
+ val checkValuePhone = VerifRegisReq(
+ fieldRegis = "phone",
+ valueRegis = phone
+ )
+ registerViewModel.checkValueReg(checkValuePhone)
+ }
+
+ private fun setupObservers() {
+
+ registerViewModel.checkValue.observe(this) { result ->
+ when (result) {
+ is Result.Loading -> {
+ // Show loading if needed
+ }
+ is Result.Success -> {
+ val isValid = (result.data as? Boolean) ?: false
+
+ when (lastCheckField) {
+ "email" -> {
+ isEmailValid = isValid
+ if (!isValid) {
+ Toast.makeText(this, "Email is already registered", Toast.LENGTH_SHORT).show()
+ } else {
+ Log.d("RegisterActivity", "Email is valid")
+ }
+ }
+ "phone" -> {
+ isPhoneValid = isValid
+ if (!isValid) {
+ Toast.makeText(this, "Phone number is already registered", Toast.LENGTH_SHORT).show()
+ } else {
+ Log.d("RegisterActivity", "Phone is valid")
+ }
+ }
}
- is Result.Success -> {
- // Hide loading indicator and show success message
- binding.progressBarRegister.visibility = android.view.View.GONE
- Toast.makeText(this, result.data, Toast.LENGTH_SHORT).show()
- val intent = Intent(this, LoginActivity::class.java)
- startActivity(intent)
- // Navigate to another screen if needed
- }
- is com.alya.ecommerce_serang.data.repository.Result.Error -> {
- // Hide loading indicator and show error message
- binding.progressBarRegister.visibility = android.view.View.GONE
- Toast.makeText(this, "Registration Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
+
+ // Check if we're in signup process
+ if (signupInProgress) {
+ signupValidationsComplete++
+
+ // Check if both validations completed
+ if (signupValidationsComplete >= 2) {
+ signupInProgress = false
+ signupValidationsComplete = 0
+
+ // If both validations passed, request OTP
+ if (isEmailValid && isPhoneValid) {
+ requestOtp()
+ }
+ }
}
}
+ is Result.Error -> {
+ val fieldType = if (lastCheckField == "email") "Email" else "Phone"
+ Toast.makeText(this, "$fieldType validation failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
+
+ // Mark validation as invalid
+ if (lastCheckField == "email") {
+ isEmailValid = false
+ } else if (lastCheckField == "phone") {
+ isPhoneValid = false
+ }
+
+ // Update signup validation counter if in signup process
+ if (signupInProgress) {
+ signupValidationsComplete++
+
+ // Check if both validations completed
+ if (signupValidationsComplete >= 2) {
+ signupInProgress = false
+ signupValidationsComplete = 0
+ }
+ }
+ }
+ else -> {
+ Log.e("RegisterActivity", "Unexpected result type: $result")
+ }
}
+ }
+ registerViewModel.otpState.observe(this) { result ->
+ when (result) {
+ is Result.Loading -> {
+ binding.progressBarOtp.visibility = android.view.View.VISIBLE
+ }
+ is Result.Success -> {
+ binding.progressBarOtp.visibility = android.view.View.GONE
+ Log.d("RegisterActivity", "OTP sent successfully. Showing OTP dialog.")
+
+ // Create user data before showing OTP dialog
+ val userData = createUserData()
+
+ // Show OTP dialog
+ val otpBottomSheet = OtpBottomSheetDialog(userData) { fullUserData ->
+ Log.d("RegisterActivity", "OTP entered successfully. Proceeding with registration.")
+ registerViewModel.registerUser(fullUserData)
+ }
+ otpBottomSheet.show(supportFragmentManager, "OtpBottomSheet")
+ }
+ is Result.Error -> {
+ binding.progressBarOtp.visibility = android.view.View.GONE
+ Toast.makeText(this, "OTP Request Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
+ }
+ else -> {
+ Log.e("RegisterActivity", "Unexpected result type: $result")
+ }
+ }
+ }
+ registerViewModel.registerState.observe(this) { result ->
+ when (result) {
+ is Result.Loading -> {
+ // Show loading indicator for registration
+ binding.progressBarRegister.visibility = android.view.View.VISIBLE
+ }
+ is Result.Success -> {
+ // Hide loading indicator and show success message
+ binding.progressBarRegister.visibility = android.view.View.GONE
+ Toast.makeText(this, result.data, Toast.LENGTH_SHORT).show()
+ val intent = Intent(this, LoginActivity::class.java)
+ startActivity(intent)
+ // Navigate to another screen if needed
+ }
+ is com.alya.ecommerce_serang.data.repository.Result.Error -> {
+ // Hide loading indicator and show error message
+ binding.progressBarRegister.visibility = android.view.View.GONE
+ Toast.makeText(this, "Registration Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ }
+
+ private fun handleSignUp() {
+ // Basic validation first
+ val email = binding.etEmail.text.toString()
+ val password = binding.etPassword.text.toString()
+ val confirmPassword = binding.etConfirmPassword.text.toString()
+ val phone = binding.etNumberPhone.text.toString()
+ val username = binding.etUsername.text.toString()
+ val name = binding.etFullname.text.toString()
+ val birthDate = binding.etBirthDate.text.toString()
+
+ // Check if fields are filled
+ if (email.isEmpty() || password.isEmpty() || confirmPassword.isEmpty() ||
+ phone.isEmpty() || username.isEmpty() || name.isEmpty() || birthDate.isEmpty()) {
+ Toast.makeText(this, "Please fill all required fields", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ // Check if passwords match
+ if (password != confirmPassword) {
+ Toast.makeText(this, "Passwords do not match", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ // If both validations are already done and successful, just request OTP
+ if (isEmailValid && isPhoneValid) {
+ requestOtp()
+ return
+ }
+
+ // Reset validation counters
+ signupInProgress = true
+ signupValidationsComplete = 0
+
+ // Start validations in parallel
+ validateEmail(email, true)
+ validatePhone(phone, true)
+ }
+
+ private fun requestOtp() {
+ val email = binding.etEmail.text.toString()
+ Log.d("RegisterActivity", "Requesting OTP for email: $email")
+ registerViewModel.requestOtp(email)
+ }
+
+ private fun createUserData(): RegisterRequest {
+ // Get all form values
+ val birthDate = binding.etBirthDate.text.toString()
+ val email = binding.etEmail.text.toString()
+ val password = binding.etPassword.text.toString()
+ val phone = binding.etNumberPhone.text.toString()
+ val username = binding.etUsername.text.toString()
+ val name = binding.etFullname.text.toString()
+ val image = null
+
+ // Create and return user data object
+ return RegisterRequest(name, email, password, username, phone, birthDate, image)
}
private fun showDatePicker() {
@@ -189,7 +342,7 @@ class RegisterActivity : AppCompatActivity() {
this,
{ _, selectedYear, selectedMonth, selectedDay ->
calendar.set(selectedYear, selectedMonth, selectedDay)
- val sdf = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault())
+ val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
binding.etBirthDate.setText(sdf.format(calendar.time))
},
year, month, day
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterStoreActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterStoreActivity.kt
new file mode 100644
index 0000000..46a91b0
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterStoreActivity.kt
@@ -0,0 +1,609 @@
+package com.alya.ecommerce_serang.ui.auth
+
+import android.Manifest
+import android.app.Activity
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Bundle
+import android.provider.MediaStore
+import android.text.Editable
+import android.text.TextWatcher
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+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.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import com.alya.ecommerce_serang.data.api.response.auth.StoreTypesItem
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.data.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.CityAdapter
+import com.alya.ecommerce_serang.ui.order.address.ProvinceAdapter
+import com.alya.ecommerce_serang.utils.BaseViewModelFactory
+import com.alya.ecommerce_serang.utils.SessionManager
+
+class RegisterStoreActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityRegisterStoreBinding
+ private lateinit var sessionManager: SessionManager
+
+ private lateinit var provinceAdapter: ProvinceAdapter
+ private lateinit var cityAdapter: CityAdapter
+ // Request codes for file picking
+ private val PICK_STORE_IMAGE_REQUEST = 1001
+ private val PICK_KTP_REQUEST = 1002
+ private val PICK_NPWP_REQUEST = 1003
+ private val PICK_NIB_REQUEST = 1004
+ private val PICK_PERSETUJUAN_REQUEST = 1005
+ private val PICK_QRIS_REQUEST = 1006
+
+ // Location request code
+ private val LOCATION_PERMISSION_REQUEST = 2001
+
+ private val viewModel: RegisterStoreViewModel by viewModels {
+ BaseViewModelFactory {
+ val apiService = ApiConfig.getApiService(sessionManager)
+ val orderRepository = UserRepository(apiService)
+ RegisterStoreViewModel(orderRepository)
+ }
+ }
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityRegisterStoreBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ sessionManager = SessionManager(this)
+
+ 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
+ }
+
+ provinceAdapter = ProvinceAdapter(this)
+ cityAdapter = CityAdapter(this)
+
+ setupDataBinding()
+ setupSpinners() // Location spinners
+
+ // Setup observers
+ setupStoreTypesObserver() // Store type observer
+ setupObservers()
+
+ setupMap()
+ setupDocumentUploads()
+ setupCourierSelection()
+
+ viewModel.fetchStoreTypes()
+ viewModel.getProvinces()
+
+
+ // Setup register button
+ binding.btnRegister.setOnClickListener {
+ if (viewModel.validateForm()) {
+ viewModel.registerStore(this)
+ } else {
+ Toast.makeText(this, "Harap lengkapi semua field yang wajib diisi", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ private fun setupObservers() {
+ // Observe province state
+ viewModel.provincesState.observe(this) { state ->
+ when (state) {
+ is Result.Loading -> {
+ Log.d(TAG, "Loading provinces...")
+ binding.provinceProgressBar?.visibility = View.VISIBLE
+ binding.spinnerProvince.isEnabled = false
+ }
+ is Result.Success -> {
+ Log.d(TAG, "Provinces loaded: ${state.data.size}")
+ binding.provinceProgressBar?.visibility = View.GONE
+ binding.spinnerProvince.isEnabled = true
+
+ // Update adapter with data
+ provinceAdapter.updateData(state.data)
+ }
+ is Result.Error -> {
+// Log.e(TAG, "Error loading provinces: ${state.}")
+ binding.provinceProgressBar?.visibility = View.GONE
+ binding.spinnerProvince.isEnabled = true
+
+// Toast.makeText(this, "Gagal memuat provinsi: ${state.message}", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ // Observe city state
+ viewModel.citiesState.observe(this) { state ->
+ when (state) {
+ is Result.Loading -> {
+ Log.d(TAG, "Loading cities...")
+ binding.cityProgressBar?.visibility = View.VISIBLE
+ binding.spinnerCity.isEnabled = false
+ }
+ is Result.Success -> {
+ Log.d(TAG, "Cities loaded: ${state.data.size}")
+ binding.cityProgressBar?.visibility = View.GONE
+ binding.spinnerCity.isEnabled = true
+
+ // Update adapter with data
+ cityAdapter.updateData(state.data)
+ }
+ is Result.Error -> {
+// Log.e(TAG, "Error loading cities: ${state.message}")
+ binding.cityProgressBar?.visibility = View.GONE
+ binding.spinnerCity.isEnabled = true
+
+// Toast.makeText(this, "Gagal memuat kota: ${state.message}", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ // Observe registration state
+ viewModel.registerState.observe(this) { result ->
+ when (result) {
+ is Result.Loading -> {
+ showLoading(true)
+ }
+ is Result.Success -> {
+ showLoading(false)
+ Toast.makeText(this, "Toko berhasil didaftarkan", Toast.LENGTH_SHORT).show()
+ finish() // Return to previous screen
+ }
+ is Result.Error -> {
+ showLoading(false)
+ Toast.makeText(this, "Gagal mendaftarkan toko: ${result.exception.message}", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ }
+
+ private fun setupStoreTypesObserver() {
+ // Observe loading state
+ viewModel.isLoadingType.observe(this) { isLoading ->
+ if (isLoading) {
+ // Show loading indicator for store types spinner
+ binding.spinnerStoreType.isEnabled = false
+ binding.storeTypeProgressBar?.visibility = View.VISIBLE
+ } else {
+ binding.spinnerStoreType.isEnabled = true
+ binding.storeTypeProgressBar?.visibility = View.GONE
+ }
+ }
+
+ // Observe error messages
+ viewModel.errorMessage.observe(this) { errorMsg ->
+ if (errorMsg.isNotEmpty()) {
+ Toast.makeText(this, "Error loading store types: $errorMsg", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ // Observe store types data
+ viewModel.storeTypes.observe(this) { storeTypes ->
+ Log.d(TAG, "Store types loaded: ${storeTypes.size}")
+ if (storeTypes.isNotEmpty()) {
+ // Add "Pilih Jenis UMKM" as the first item if it's not already there
+ val displayList = if (storeTypes.any { it.name == "Pilih Jenis UMKM" || it.id == 0 }) {
+ storeTypes
+ } else {
+ val defaultItem = StoreTypesItem(name = "Pilih Jenis UMKM", id = 0)
+ listOf(defaultItem) + storeTypes
+ }
+
+ // Setup spinner with API data
+ setupStoreTypeSpinner(displayList)
+ }
+ }
+ }
+
+ private fun setupStoreTypeSpinner(storeTypes: List) {
+ Log.d(TAG, "Setting up store type spinner with ${storeTypes.size} items")
+
+ // Create a custom adapter to display just the name but hold the whole object
+ val adapter = object : ArrayAdapter(
+ this,
+ android.R.layout.simple_spinner_item,
+ storeTypes
+ ) {
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val view = super.getView(position, convertView, parent)
+ (view as TextView).text = getItem(position)?.name ?: ""
+ return view
+ }
+
+ override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val view = super.getDropDownView(position, convertView, parent)
+ (view as TextView).text = getItem(position)?.name ?: ""
+ return view
+ }
+
+ // Override toString to ensure proper display
+ override fun getItem(position: Int): StoreTypesItem? {
+ return super.getItem(position)
+ }
+ }
+
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+
+ // Set adapter to spinner
+ binding.spinnerStoreType.adapter = adapter
+
+ // Set item selection listener
+ binding.spinnerStoreType.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ val selectedItem = adapter.getItem(position)
+ Log.d(TAG, "Store type selected: position=$position, item=${selectedItem?.name}, id=${selectedItem?.id}")
+
+ if (selectedItem != null && selectedItem.id > 0) {
+ // Store the actual ID from the API, not just position
+ viewModel.storeTypeId.value = selectedItem.id
+ Log.d(TAG, "Set storeTypeId to ${selectedItem.id}")
+ }
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {
+ Log.d(TAG, "No store type selected")
+ }
+ }
+
+ // Hide progress bar after setup
+ binding.storeTypeProgressBar?.visibility = View.GONE
+ }
+
+ private fun setupSpinners() {
+ // Setup province spinner
+ binding.spinnerProvince.adapter = provinceAdapter
+ binding.spinnerProvince.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ Log.d(TAG, "Province selected at position: $position")
+ val provinceId = provinceAdapter.getProvinceId(position)
+ if (provinceId != null) {
+ Log.d(TAG, "Setting province ID: $provinceId")
+ viewModel.provinceId.value = provinceId
+ viewModel.getCities(provinceId)
+
+ // Reset city selection when province changes
+ cityAdapter.clear()
+ binding.spinnerCity.setSelection(0)
+ } else {
+ Log.e(TAG, "Invalid province ID for position: $position")
+ }
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {
+ // Do nothing
+ }
+ }
+
+ // Setup city spinner
+ binding.spinnerCity.adapter = cityAdapter
+ binding.spinnerCity.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ Log.d(TAG, "City selected at position: $position")
+ val cityId = cityAdapter.getCityId(position)
+ if (cityId != null) {
+ Log.d(TAG, "Setting city ID: $cityId")
+ viewModel.cityId.value = cityId
+ viewModel.selectedCityId = cityId
+ } else {
+ Log.e(TAG, "Invalid city ID for position: $position")
+ }
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {
+ // Do nothing
+ }
+ }
+
+ // Add initial hints to the spinners
+ if (provinceAdapter.isEmpty) {
+ provinceAdapter.add("Pilih Provinsi")
+ }
+
+ if (cityAdapter.isEmpty) {
+ cityAdapter.add("Pilih Kabupaten/Kota")
+ }
+ }
+
+// private fun setupSubdistrictSpinner(cityId: Int) {
+// // This would typically be populated from API based on cityId
+// val subdistricts = listOf("Pilih Kecamatan", "Kecamatan 1", "Kecamatan 2", "Kecamatan 3")
+// val subdistrictAdapter = ArrayAdapter(this, R.layout.simple_spinner_dropdown_item, subdistricts)
+// binding.spinnerSubdistrict.adapter = subdistrictAdapter
+// binding.spinnerSubdistrict.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+// override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+// if (position > 0) {
+// viewModel.subdistrict.value = subdistricts[position]
+// }
+// }
+// override fun onNothingSelected(parent: AdapterView<*>?) {}
+// }
+// }
+
+ private fun setupDocumentUploads() {
+ // Store Image
+ binding.containerStoreImg.setOnClickListener {
+ pickImage(PICK_STORE_IMAGE_REQUEST)
+ }
+
+ // KTP
+ binding.containerKtp.setOnClickListener {
+ pickImage(PICK_KTP_REQUEST)
+ }
+
+ // NIB
+ binding.containerNib.setOnClickListener {
+ pickDocument(PICK_NIB_REQUEST)
+ }
+
+ // NPWP
+ binding.containerNpwp?.setOnClickListener {
+ pickImage(PICK_NPWP_REQUEST)
+ }
+
+ // SPPIRT
+ binding.containerSppirt.setOnClickListener {
+ pickDocument(PICK_PERSETUJUAN_REQUEST)
+ }
+
+ // Halal
+ binding.containerHalal.setOnClickListener {
+ pickDocument(PICK_QRIS_REQUEST)
+ }
+ }
+
+ private fun pickImage(requestCode: Int) {
+ val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
+ startActivityForResult(intent, requestCode)
+ }
+
+ private fun pickDocument(requestCode: Int) {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ intent.type = "*/*"
+ val mimeTypes = arrayOf("application/pdf", "image/jpeg", "image/png")
+ intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
+ startActivityForResult(intent, requestCode)
+ }
+
+ private fun setupCourierSelection() {
+ binding.checkboxJne.setOnCheckedChangeListener { _, isChecked ->
+ handleCourierSelection("jne", isChecked)
+ }
+
+ binding.checkboxJnt.setOnCheckedChangeListener { _, isChecked ->
+ handleCourierSelection("tiki", isChecked)
+ }
+
+ binding.checkboxPos.setOnCheckedChangeListener { _, isChecked ->
+ handleCourierSelection("pos", isChecked)
+ }
+ }
+
+ private fun handleCourierSelection(courier: String, isSelected: Boolean) {
+ if (isSelected) {
+ if (!viewModel.selectedCouriers.contains(courier)) {
+ viewModel.selectedCouriers.add(courier)
+ }
+ } else {
+ viewModel.selectedCouriers.remove(courier)
+ }
+ }
+
+ private fun setupMap() {
+ // This would typically integrate with Google Maps SDK
+ // For simplicity, we're just using a placeholder
+ binding.mapContainer.setOnClickListener {
+ // Request location permission if not granted
+ if (ContextCompat.checkSelfPermission(
+ this,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ ActivityCompat.requestPermissions(
+ this,
+ arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
+ LOCATION_PERMISSION_REQUEST
+
+ )
+ viewModel.latitude.value = "-6.2088"
+ viewModel.longitude.value = "106.8456"
+ Toast.makeText(this, "Lokasi dipilih", Toast.LENGTH_SHORT).show()
+ } else {
+ // 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"
+ Toast.makeText(this, "Lokasi dipilih", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ private fun setupDataBinding() {
+ // Two-way data binding for text fields
+ binding.etStoreName.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.storeName.value = s.toString()
+ }
+ })
+
+ binding.etStoreDescription.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.storeDescription.value = s.toString()
+ }
+ })
+
+ binding.etStreet.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.street.value = s.toString()
+ }
+ })
+
+ binding.etPostalCode.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?) {
+ try {
+ viewModel.postalCode.value = s.toString().toInt()
+ } catch (e: NumberFormatException) {
+ // Handle invalid input
+ //show toast
+ }
+ }
+ })
+
+ binding.etAddressDetail.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.addressDetail.value = s.toString()
+ }
+ })
+
+ binding.etBankNumber.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.bankNumber.value = s.toString().toInt()
+ }
+ })
+
+ 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()
+ }
+ })
+
+ 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.subdistrict.value = s.toString()
+ }
+ })
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ val uri = data.data
+ when (requestCode) {
+ PICK_STORE_IMAGE_REQUEST -> {
+ viewModel.storeImageUri = uri
+ updateImagePreview(uri, binding.imgStore, binding.layoutUploadStoreImg)
+ }
+ PICK_KTP_REQUEST -> {
+ viewModel.ktpUri = uri
+ updateImagePreview(uri, binding.imgKtp, binding.layoutUploadKtp)
+ }
+ PICK_NPWP_REQUEST -> {
+ viewModel.npwpUri = uri
+ updateDocumentPreview(binding.layoutUploadNpwp)
+ }
+ PICK_NIB_REQUEST -> {
+ viewModel.nibUri = uri
+ updateDocumentPreview(binding.layoutUploadNib)
+ }
+ PICK_PERSETUJUAN_REQUEST -> {
+ viewModel.persetujuanUri = uri
+ updateDocumentPreview(binding.layoutUploadSppirt)
+ }
+ PICK_QRIS_REQUEST -> {
+ viewModel.qrisUri = uri
+ updateDocumentPreview(binding.layoutUploadHalal)
+ }
+ }
+ }
+ }
+
+ private fun updateImagePreview(uri: Uri?, imageView: ImageView, uploadLayout: LinearLayout) {
+ uri?.let {
+ imageView.setImageURI(it)
+ imageView.visibility = View.VISIBLE
+ uploadLayout.visibility = View.GONE
+ }
+ }
+
+ private fun updateDocumentPreview(uploadLayout: LinearLayout) {
+ // For documents, we just show a success indicator
+ val checkIcon = ImageView(this)
+ checkIcon.setImageResource(android.R.drawable.ic_menu_gallery)
+ val successText = TextView(this)
+ successText.text = "Dokumen berhasil diunggah"
+
+ uploadLayout.removeAllViews()
+ uploadLayout.addView(checkIcon)
+ uploadLayout.addView(successText)
+ }
+
+ //later implement get location form gps
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == LOCATION_PERMISSION_REQUEST) {
+ if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // Permission granted, proceed with location selection
+ viewModel.latitude.value = "-6.2088"
+ viewModel.longitude.value = "106.8456"
+ Toast.makeText(this, "Lokasi dipilih", Toast.LENGTH_SHORT).show()
+ } else {
+ viewModel.latitude.value = "-6.2088"
+ viewModel.longitude.value = "106.8456"
+ }
+ }
+ }
+
+ private fun showLoading(isLoading: Boolean) {
+ if (isLoading) {
+ // Show loading indicator
+ binding.btnRegister.isEnabled = false
+ binding.btnRegister.text = "Mendaftar..."
+ } else {
+ // Hide loading indicator
+ binding.btnRegister.isEnabled = true
+ binding.btnRegister.text = "Daftar"
+ }
+ }
+
+ companion object {
+ private const val TAG = "RegisterStoreActivity"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterStoreViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterStoreViewModel.kt
new file mode 100644
index 0000000..41bfbf1
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterStoreViewModel.kt
@@ -0,0 +1,202 @@
+package com.alya.ecommerce_serang.ui.auth
+
+import android.content.Context
+import android.net.Uri
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.alya.ecommerce_serang.data.api.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.repository.Result
+import com.alya.ecommerce_serang.data.repository.UserRepository
+import kotlinx.coroutines.launch
+
+class RegisterStoreViewModel(
+ private val repository: UserRepository
+) : ViewModel() {
+
+ // LiveData for UI state
+ private val _registerState = MutableLiveData>()
+ val registerState: LiveData> = _registerState
+
+ private val _storeTypes = MutableLiveData>()
+ val storeTypes: LiveData> = _storeTypes
+
+ // LiveData for error messages
+ private val _errorMessage = MutableLiveData()
+ val errorMessage: LiveData = _errorMessage
+
+ // LiveData for loading state
+ private val _isLoadingType = MutableLiveData()
+ val isLoadingType: LiveData = _isLoadingType
+
+ private val _provincesState = MutableLiveData>>()
+ val provincesState: LiveData>> = _provincesState
+
+ private val _citiesState = MutableLiveData>>()
+ val citiesState: LiveData>> = _citiesState
+
+ var selectedProvinceId: Int? = null
+ var selectedCityId: Int? = null
+
+ // Form fields
+ val storeName = MutableLiveData()
+ val storeDescription = MutableLiveData()
+ val storeTypeId = MutableLiveData()
+ val latitude = MutableLiveData()
+ val longitude = MutableLiveData()
+ val street = MutableLiveData()
+ val subdistrict = MutableLiveData()
+ val cityId = MutableLiveData()
+ val provinceId = MutableLiveData()
+ val postalCode = MutableLiveData()
+ val addressDetail = MutableLiveData()
+ val bankName = MutableLiveData()
+ val bankNumber = MutableLiveData()
+ val accountName = MutableLiveData()
+
+ // Files
+ var storeImageUri: Uri? = null
+ var ktpUri: Uri? = null
+ var npwpUri: Uri? = null
+ var nibUri: Uri? = null
+ var persetujuanUri: Uri? = null
+ var qrisUri: Uri? = null
+
+ // Selected couriers
+ val selectedCouriers = mutableListOf()
+
+ fun registerStore(context: Context) {
+ viewModelScope.launch {
+ try {
+ _registerState.value = Result.Loading
+
+ val result = repository.registerStoreUser(
+ context = context,
+ description = storeDescription.value ?: "",
+ storeTypeId = storeTypeId.value ?: 0,
+ latitude = latitude.value ?: "",
+ longitude = longitude.value ?: "",
+ street = street.value ?: "",
+ subdistrict = subdistrict.value ?: "",
+ cityId = cityId.value ?: 0,
+ provinceId = provinceId.value ?: 0,
+ postalCode = postalCode.value ?: 0,
+ detail = addressDetail.value,
+ bankName = bankName.value ?: "",
+ bankNum = bankNumber.value ?: 0,
+ storeName = storeName.value ?: "",
+ storeImg = storeImageUri,
+ ktp = ktpUri,
+ npwp = npwpUri,
+ nib = nibUri,
+ persetujuan = persetujuanUri,
+ couriers = selectedCouriers,
+ qris = qrisUri,
+ accountName = accountName.value ?: ""
+ )
+
+ _registerState.value = result
+ } catch (e: Exception) {
+ _registerState.value = com.alya.ecommerce_serang.data.repository.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() ||
+ storeTypeId.value == null ||
+ street.value.isNullOrEmpty() ||
+ subdistrict.value.isNullOrEmpty() ||
+ cityId.value == null ||
+ provinceId.value == null ||
+ postalCode.value == null ||
+ bankName.value.isNullOrEmpty() ||
+ bankNumber.value == null ||
+ selectedCouriers.isEmpty() ||
+ ktpUri == null ||
+ nibUri == null)
+ }
+
+
+
+ // Function to fetch store types
+ fun fetchStoreTypes() {
+ _isLoadingType.value = true
+ viewModelScope.launch {
+ when (val result = repository.listStoreType()) {
+ is Result.Success -> {
+ _storeTypes.value = result.data.storeTypes
+ _isLoadingType.value = false
+ }
+ is Result.Error -> {
+ _errorMessage.value = result.exception.message ?: "Unknown error occurred"
+ _isLoadingType.value = false
+ }
+ is Result.Loading -> {
+ _isLoadingType.value = true
+ }
+ }
+ }
+ }
+
+ fun getProvinces() {
+ _provincesState.value = Result.Loading
+ viewModelScope.launch {
+ try {
+ val result = repository.getListProvinces()
+ if (result?.provinces != null) {
+ _provincesState.postValue(Result.Success(result.provinces))
+ Log.d(TAG, "Provinces loaded: ${result.provinces.size}")
+ } else {
+ _provincesState.postValue(Result.Error(Exception("Failed to load provinces")))
+ Log.e(TAG, "Province result was null or empty")
+ }
+ } catch (e: Exception) {
+ _provincesState.postValue(Result.Error(Exception(e.message ?: "Error loading provinces")))
+ Log.e(TAG, "Error fetching provinces", e)
+ }
+ }
+ }
+
+ fun getCities(provinceId: Int){
+ _citiesState.value = Result.Loading
+ viewModelScope.launch {
+ try {
+ selectedProvinceId = provinceId
+ val result = repository.getListCities(provinceId)
+ result?.let {
+ _citiesState.postValue(Result.Success(it.cities))
+ Log.d(TAG, "Cities loaded for province $provinceId: ${it.cities.size}")
+ } ?: run {
+ _citiesState.postValue(Result.Error(Exception("Failed to load cities")))
+ Log.e(TAG, "City result was null for province $provinceId")
+ }
+ } catch (e: Exception) {
+ _citiesState.postValue(Result.Error(Exception(e.message ?: "Error loading cities")))
+ Log.e(TAG, "Error fetching cities for province $provinceId", e)
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "RegisterStoreUserViewModel"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt
index 907d4bb..0d61401 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt
@@ -125,6 +125,7 @@ class ChatActivity : AppCompatActivity() {
val productRating = intent.getFloatExtra(Constants.EXTRA_PRODUCT_RATING, 0f)
val storeName = intent.getStringExtra(Constants.EXTRA_STORE_NAME) ?: ""
val chatRoomId = intent.getIntExtra(Constants.EXTRA_CHAT_ROOM_ID, 0)
+ val storeImg = intent.getStringExtra(Constants.EXTRA_STORE_IMAGE) ?: ""
// Check if user is logged in
val token = sessionManager.getToken()
@@ -137,7 +138,20 @@ class ChatActivity : AppCompatActivity() {
return
}
- // Set chat parameters to ViewModel
+ binding.tvStoreName.text = storeName
+ val fullImageUrl = when (val img = storeImg) {
+ is String -> {
+ if (img.startsWith("/")) BASE_URL + img.substring(1) else img
+ }
+ else -> R.drawable.placeholder_image
+ }
+
+ Glide.with(this)
+ .load(fullImageUrl)
+ .placeholder(R.drawable.placeholder_image)
+ .into(binding.imgProfile)
+
+ // Set chat parameters to ViewModel
viewModel.setChatParameters(
storeId = storeId,
productId = productId,
@@ -227,6 +241,7 @@ class ChatActivity : AppCompatActivity() {
}
})
+
// Observe state changes using LiveData
viewModel.state.observe(this, Observer { state ->
// Update messages
@@ -244,6 +259,7 @@ class ChatActivity : AppCompatActivity() {
binding.ratingBar.rating = state.productRating
binding.tvRating.text = state.productRating.toString()
binding.tvSellerName.text = state.storeName
+ binding.tvStoreName.text=state.storeName
// Load product image
if (!state.productImageUrl.isNullOrEmpty()) {
@@ -270,6 +286,7 @@ class ChatActivity : AppCompatActivity() {
binding.editTextMessage.hint = getString(R.string.write_message)
}
+
// Show typing indicator
binding.tvTypingIndicator.visibility =
if (state.isOtherUserTyping) View.VISIBLE else View.GONE
@@ -467,7 +484,8 @@ class ChatActivity : AppCompatActivity() {
productImage: String? = null,
productRating: String? = null,
storeName: String? = null,
- chatRoomId: Int = 0
+ chatRoomId: Int = 0,
+ storeImage: String? = null
) {
val intent = Intent(context, ChatActivity::class.java).apply {
putExtra(Constants.EXTRA_STORE_ID, storeId)
@@ -475,6 +493,7 @@ class ChatActivity : AppCompatActivity() {
putExtra(Constants.EXTRA_PRODUCT_NAME, productName)
putExtra(Constants.EXTRA_PRODUCT_PRICE, productPrice)
putExtra(Constants.EXTRA_PRODUCT_IMAGE, productImage)
+ putExtra(Constants.EXTRA_STORE_IMAGE, storeImage)
// Convert productRating string to float if provided
if (productRating != null) {
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatFragment.kt
deleted file mode 100644
index 4bd12ae..0000000
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatFragment.kt
+++ /dev/null
@@ -1,337 +0,0 @@
-//package com.alya.ecommerce_serang.ui.chat
-//
-//import android.Manifest
-//import android.app.Activity
-//import android.content.Intent
-//import android.content.pm.PackageManager
-//import android.net.Uri
-//import android.os.Bundle
-//import android.provider.MediaStore
-//import android.text.Editable
-//import android.text.TextWatcher
-//import androidx.fragment.app.Fragment
-//import android.view.LayoutInflater
-//import android.view.View
-//import android.view.ViewGroup
-//import android.widget.Toast
-//import androidx.activity.result.contract.ActivityResultContracts
-//import androidx.core.app.ActivityCompat
-//import androidx.core.content.ContextCompat
-//import androidx.core.content.FileProvider
-//import androidx.fragment.app.viewModels
-//import androidx.lifecycle.lifecycleScope
-//import androidx.navigation.fragment.navArgs
-//import androidx.recyclerview.widget.LinearLayoutManager
-//import com.alya.ecommerce_serang.BuildConfig.BASE_URL
-//import com.alya.ecommerce_serang.R
-//import com.alya.ecommerce_serang.databinding.FragmentChatBinding
-//import com.alya.ecommerce_serang.utils.Constants
-//import com.bumptech.glide.Glide
-//import dagger.hilt.android.AndroidEntryPoint
-//import kotlinx.coroutines.launch
-//import java.io.File
-//import java.text.SimpleDateFormat
-//import java.util.Locale
-//
-//@AndroidEntryPoint
-//class ChatFragment : Fragment() {
-//
-// private var _binding: FragmentChatBinding? = null
-// private val binding get() = _binding!!
-//
-// private val viewModel: ChatViewModel by viewModels()
-//// private val args: ChatFragmentArgs by navArgs()
-//
-// private lateinit var chatAdapter: ChatAdapter
-//
-// // For image attachment
-// private var tempImageUri: Uri? = null
-//
-// // Typing indicator handler
-// private val typingHandler = android.os.Handler(android.os.Looper.getMainLooper())
-// private val stopTypingRunnable = Runnable {
-// viewModel.sendTypingStatus(false)
-// }
-//
-// // Activity Result Launchers
-// private val pickImageLauncher = registerForActivityResult(
-// ActivityResultContracts.StartActivityForResult()
-// ) { result ->
-// if (result.resultCode == Activity.RESULT_OK) {
-// result.data?.data?.let { uri ->
-// handleSelectedImage(uri)
-// }
-// }
-// }
-//
-// private val takePictureLauncher = registerForActivityResult(
-// ActivityResultContracts.StartActivityForResult()
-// ) { result ->
-// if (result.resultCode == Activity.RESULT_OK) {
-// tempImageUri?.let { uri ->
-// handleSelectedImage(uri)
-// }
-// }
-// }
-//
-// override fun onCreateView(
-// inflater: LayoutInflater,
-// container: ViewGroup?,
-// savedInstanceState: Bundle?
-// ): View {
-// _binding = FragmentChatBinding.inflate(inflater, container, false)
-// return binding.root
-// }
-//
-// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-// super.onViewCreated(view, savedInstanceState)
-//
-// setupRecyclerView()
-// setupListeners()
-// setupTypingIndicator()
-// observeViewModel()
-// }
-//
-// private fun setupRecyclerView() {
-// chatAdapter = ChatAdapter()
-// binding.recyclerChat.apply {
-// adapter = chatAdapter
-// layoutManager = LinearLayoutManager(requireContext()).apply {
-// stackFromEnd = true
-// }
-// }
-// }
-//
-// private fun setupListeners() {
-// // Back button
-// binding.btnBack.setOnClickListener {
-// requireActivity().onBackPressed()
-// }
-//
-// // Options button
-// binding.btnOptions.setOnClickListener {
-// showOptionsMenu()
-// }
-//
-// // Send button
-// binding.btnSend.setOnClickListener {
-// val message = binding.editTextMessage.text.toString().trim()
-// if (message.isNotEmpty() || viewModel.state.value.hasAttachment) {
-// viewModel.sendMessage(message)
-// binding.editTextMessage.text.clear()
-// }
-// }
-//
-// // Attachment button
-// binding.btnAttachment.setOnClickListener {
-// checkPermissionsAndShowImagePicker()
-// }
-// }
-//
-// private fun setupTypingIndicator() {
-// binding.editTextMessage.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) {
-// viewModel.sendTypingStatus(true)
-//
-// // Reset the timer
-// typingHandler.removeCallbacks(stopTypingRunnable)
-// typingHandler.postDelayed(stopTypingRunnable, 1000)
-// }
-//
-// override fun afterTextChanged(s: Editable?) {}
-// })
-// }
-//
-// private fun observeViewModel() {
-// viewLifecycleOwner.lifecycleScope.launch {
-// viewModel.state.collectLatest { state ->
-// // Update messages
-// chatAdapter.submitList(state.messages)
-//
-// // Scroll to bottom if new message
-// if (state.messages.isNotEmpty()) {
-// binding.recyclerChat.scrollToPosition(state.messages.size - 1)
-// }
-//
-// // Update product info
-// binding.tvProductName.text = state.productName
-// binding.tvProductPrice.text = state.productPrice
-// binding.ratingBar.rating = state.productRating
-// binding.tvRating.text = state.productRating.toString()
-// binding.tvSellerName.text = state.storeName
-//
-// // Load product image
-// if (state.productImageUrl.isNotEmpty()) {
-// Glide.with(requireContext())
-// .load(BASE_URL + state.productImageUrl)
-// .centerCrop()
-// .placeholder(R.drawable.placeholder_image)
-// .error(R.drawable.placeholder_image)
-// .into(binding.imgProduct)
-// }
-//
-// // Show/hide loading indicators
-// binding.progressBar.visibility = if (state.isLoading) View.VISIBLE else View.GONE
-// binding.btnSend.isEnabled = !state.isSending
-//
-// // Update attachment hint
-// if (state.hasAttachment) {
-// binding.editTextMessage.hint = getString(R.string.image_attached)
-// } else {
-// binding.editTextMessage.hint = getString(R.string.write_message)
-// }
-//
-// // Show typing indicator
-// binding.tvTypingIndicator.visibility =
-// if (state.isOtherUserTyping) View.VISIBLE else View.GONE
-//
-// // Handle connection state
-// handleConnectionState(state.connectionState)
-//
-// // Show error if any
-// state.error?.let { error ->
-// Toast.makeText(requireContext(), error, Toast.LENGTH_SHORT).show()
-// viewModel.clearError()
-// }
-// }
-// }
-// }
-//
-// private fun handleConnectionState(state: ConnectionState) {
-// when (state) {
-// is ConnectionState.Connected -> {
-// binding.tvConnectionStatus.visibility = View.GONE
-// }
-// is ConnectionState.Connecting -> {
-// binding.tvConnectionStatus.visibility = View.VISIBLE
-// binding.tvConnectionStatus.text = getString(R.string.connecting)
-// }
-// is ConnectionState.Disconnected -> {
-// binding.tvConnectionStatus.visibility = View.VISIBLE
-// binding.tvConnectionStatus.text = getString(R.string.disconnected_reconnecting)
-// }
-// is ConnectionState.Error -> {
-// binding.tvConnectionStatus.visibility = View.VISIBLE
-// binding.tvConnectionStatus.text = getString(R.string.connection_error, state.message)
-// }
-// }
-// }
-//
-// private fun showOptionsMenu() {
-// val options = arrayOf(
-// getString(R.string.block_user),
-// getString(R.string.report),
-// getString(R.string.clear_chat),
-// getString(R.string.cancel)
-// )
-//
-// androidx.appcompat.app.AlertDialog.Builder(requireContext())
-// .setTitle(getString(R.string.options))
-// .setItems(options) { dialog, which ->
-// when (which) {
-// 0 -> Toast.makeText(requireContext(), R.string.block_user_selected, Toast.LENGTH_SHORT).show()
-// 1 -> Toast.makeText(requireContext(), R.string.report_selected, Toast.LENGTH_SHORT).show()
-// 2 -> Toast.makeText(requireContext(), R.string.clear_chat_selected, Toast.LENGTH_SHORT).show()
-// }
-// dialog.dismiss()
-// }
-// .show()
-// }
-//
-// private fun checkPermissionsAndShowImagePicker() {
-// if (ContextCompat.checkSelfPermission(
-// requireContext(),
-// Manifest.permission.READ_EXTERNAL_STORAGE
-// ) != PackageManager.PERMISSION_GRANTED
-// ) {
-// ActivityCompat.requestPermissions(
-// requireActivity(),
-// arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA),
-// Constants.REQUEST_STORAGE_PERMISSION
-// )
-// } else {
-// showImagePickerOptions()
-// }
-// }
-//
-// private fun showImagePickerOptions() {
-// val options = arrayOf(
-// getString(R.string.take_photo),
-// getString(R.string.choose_from_gallery),
-// getString(R.string.cancel)
-// )
-//
-// androidx.appcompat.app.AlertDialog.Builder(requireContext())
-// .setTitle(getString(R.string.select_attachment))
-// .setItems(options) { dialog, which ->
-// when (which) {
-// 0 -> openCamera()
-// 1 -> openGallery()
-// }
-// dialog.dismiss()
-// }
-// .show()
-// }
-//
-// private fun openCamera() {
-// val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
-// val imageFileName = "IMG_${timeStamp}.jpg"
-// val storageDir = requireContext().getExternalFilesDir(null)
-// val imageFile = File(storageDir, imageFileName)
-//
-// tempImageUri = FileProvider.getUriForFile(
-// requireContext(),
-// "${requireContext().packageName}.fileprovider",
-// imageFile
-// )
-//
-// val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
-// putExtra(MediaStore.EXTRA_OUTPUT, tempImageUri)
-// }
-//
-// takePictureLauncher.launch(intent)
-// }
-//
-// private fun openGallery() {
-// val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
-// pickImageLauncher.launch(intent)
-// }
-//
-// private fun handleSelectedImage(uri: Uri) {
-// // Get the file from Uri
-// val filePathColumn = arrayOf(MediaStore.Images.Media.DATA)
-// val cursor = requireContext().contentResolver.query(uri, filePathColumn, null, null, null)
-// cursor?.moveToFirst()
-// val columnIndex = cursor?.getColumnIndex(filePathColumn[0])
-// val filePath = cursor?.getString(columnIndex ?: 0)
-// cursor?.close()
-//
-// if (filePath != null) {
-// viewModel.setSelectedImageFile(File(filePath))
-// Toast.makeText(requireContext(), R.string.image_selected, Toast.LENGTH_SHORT).show()
-// }
-// }
-//
-// override fun onRequestPermissionsResult(
-// requestCode: Int,
-// permissions: Array,
-// grantResults: IntArray
-// ) {
-// super.onRequestPermissionsResult(requestCode, permissions, grantResults)
-// if (requestCode == Constants.REQUEST_STORAGE_PERMISSION) {
-// if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-// showImagePickerOptions()
-// } else {
-// Toast.makeText(requireContext(), R.string.permission_denied, Toast.LENGTH_SHORT).show()
-// }
-// }
-// }
-//
-// override fun onDestroyView() {
-// super.onDestroyView()
-// typingHandler.removeCallbacks(stopTypingRunnable)
-// _binding = null
-// }
-//}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatListFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatListFragment.kt
index 4d395a2..ed2aa87 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatListFragment.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatListFragment.kt
@@ -21,6 +21,7 @@ class ChatListFragment : Fragment() {
private val binding get() = _binding!!
private lateinit var socketService: SocketIOService
private lateinit var sessionManager: SessionManager
+
private val viewModel: com.alya.ecommerce_serang.ui.chat.ChatViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
@@ -65,7 +66,8 @@ class ChatListFragment : Fragment() {
productImage = null,
productRating = null,
storeName = chatItem.storeName,
- chatRoomId = chatItem.chatRoomId
+ chatRoomId = chatItem.chatRoomId,
+ storeImage = chatItem.storeImage
)
}
binding.chatListRecyclerView.adapter = adapter
@@ -85,4 +87,8 @@ class ChatListFragment : Fragment() {
super.onDestroyView()
_binding = null
}
+
+ companion object{
+
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt
index b5db71a..989dedd 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt
@@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.response.chat.ChatItem
import com.alya.ecommerce_serang.data.api.response.chat.ChatItemList
import com.alya.ecommerce_serang.data.api.response.chat.ChatLine
+import com.alya.ecommerce_serang.data.api.response.customer.product.StoreProduct
import com.alya.ecommerce_serang.data.repository.ChatRepository
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.utils.Constants
@@ -23,6 +24,8 @@ import javax.inject.Inject
class ChatViewModel @Inject constructor(
private val chatRepository: ChatRepository,
private val socketService: SocketIOService,
+
+
private val sessionManager: SessionManager
) : ViewModel() {
@@ -38,6 +41,9 @@ class ChatViewModel @Inject constructor(
private val _chatList = MutableLiveData>>()
val chatList: LiveData>> = _chatList
+ private val _storeDetail = MutableLiveData>()
+ val storeDetail : LiveData> get() = _storeDetail
+
// Store and product parameters
private var storeId: Int = 0
private var productId: Int? = 0
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingViewModel.kt
index 1252f3d..9793977 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingViewModel.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/ShippingViewModel.kt
@@ -1,5 +1,6 @@
package com.alya.ecommerce_serang.ui.order
+import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@@ -9,6 +10,7 @@ import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostsItem
import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class ShippingViewModel(
@@ -30,12 +32,71 @@ class ShippingViewModel(
/**
* Load shipping options based on address, product, and quantity
*/
+// fun loadShippingOptions(addressId: Int, productId: Int, quantity: Int) {
+// _isLoading.value = true
+// _errorMessage.value = ""
+//
+// val costProduct = CostProduct(
+// productId = productId,
+// quantity = quantity
+// )
+//
+// viewModelScope.launch {
+// // Define the courier services to try
+// val courierServices = listOf("pos", "jne", "tiki")
+//
+// // Create a mutable list to collect successful courier options
+// val availableCourierOptions = mutableListOf()
+//
+// // Try each courier service
+// for (courier in courierServices) {
+// try {
+// // Create a request for this specific courier
+// val courierRequest = CourierCostRequest(
+// addressId = addressId,
+// itemCost = listOf(costProduct),
+// courier = courier // Add the courier to the request
+// )
+//
+// // Make a separate API call for each courier
+// val result = repository.getCountCourierCost(courierRequest)
+//
+// when (result) {
+// is Result.Success -> {
+// // Add this courier's options to our collection
+// result.data.courierCosts?.let { costs ->
+// availableCourierOptions.addAll(costs)
+// }
+// // Update UI with what we have so far
+// _shippingOptions.value = availableCourierOptions
+// }
+// is Result.Error -> {
+// // Log the error but continue with next courier
+// Log.e("ShippingViewModel", "Error fetching cost for courier $courier: ${result.exception.message}")
+// }
+// is Result.Loading -> {
+// // Handle loading state
+// }
+// }
+// } catch (e: Exception) {
+// // Log the exception but continue with next courier
+// Log.e("ShippingViewModel", "Exception for courier $courier: ${e.message}")
+// }
+// }
+//
+// // Show error only if we couldn't get any shipping options
+// if (availableCourierOptions.isEmpty()) {
+// _errorMessage.value = "No shipping options available. Please try again later."
+// }
+//
+// _isLoading.value = false
+// }
+// }
+
fun loadShippingOptions(addressId: Int, productId: Int, quantity: Int) {
- // Reset previous state
_isLoading.value = true
_errorMessage.value = ""
- // Prepare the request
val costProduct = CostProduct(
productId = productId,
quantity = quantity
@@ -43,34 +104,47 @@ class ShippingViewModel(
val request = CourierCostRequest(
addressId = addressId,
- itemCost = listOf(costProduct) // Wrap in a list
+ itemCost = listOf(costProduct)
)
viewModelScope.launch {
- try {
- // Fetch courier costs
- val result = repository.getCountCourierCost(request)
+ var success = false
+ var attempt = 0
+ val maxAttempts = 3
- when (result) {
- is Result.Success -> {
- // Update shipping options directly with courier costs
- _shippingOptions.value = result.data.courierCosts
- }
- is Result.Error -> {
- // Handle error case
- _errorMessage.value = result.exception.message ?: "Unknown error occurred"
- }
- is Result.Loading -> {
- // Typically handled by the loading state
+ while (!success && attempt < maxAttempts) {
+ attempt++
+
+ try {
+ val result = repository.getCountCourierCost(request)
+
+ when (result) {
+ is Result.Success -> {
+ _shippingOptions.value = result.data.courierCosts
+ success = true
+ }
+ is com.alya.ecommerce_serang.data.repository.Result.Error -> {
+ Log.e("ShippingViewModel", "Attempt $attempt failed: ${result.exception.message}")
+ // Wait before retrying
+ delay(120000)
+ }
+ is com.alya.ecommerce_serang.data.repository.Result.Loading -> {
+ // Handle loading state
+ }
}
+ } catch (e: Exception) {
+ Log.e("ShippingViewModel", "Attempt $attempt exception: ${e.message}")
+ // Wait before retrying
+ delay(1000)
}
- } catch (e: Exception) {
- // Catch any unexpected exceptions
- _errorMessage.value = e.localizedMessage ?: "An unexpected error occurred"
- } finally {
- // Always set loading to false
- _isLoading.value = false
}
+
+ // After all attempts, check if we have any shipping options
+ if (!success || _shippingOptions.value.isNullOrEmpty()) {
+ _errorMessage.value = "No shipping options available. Please try again later."
+ }
+
+ _isLoading.value = false
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt
index c955672..4de34df 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt
@@ -398,7 +398,7 @@ class AddAddressActivity : AppCompatActivity() {
isRequestingLocation = false
Toast.makeText(this, "Timeout lokasi, menggunakan lokasi default", Toast.LENGTH_SHORT).show()
}
- }, 15000) // 15 seconds timeout
+ }, 60000) // 15 seconds timeout
// Try getting last known location first
try {
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/HistoryViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/HistoryViewModel.kt
index 4f02163..9c36c28 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/HistoryViewModel.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/HistoryViewModel.kt
@@ -5,8 +5,10 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.alya.ecommerce_serang.data.api.dto.CancelOrderReq
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
+import com.alya.ecommerce_serang.data.api.response.customer.order.CancelOrderResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.OrderListItemsItem
import com.alya.ecommerce_serang.data.api.response.customer.order.Orders
import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse
@@ -31,6 +33,11 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() {
private val _orderDetails = MutableLiveData()
val orderDetails: LiveData get() = _orderDetails
+ private val _cancelOrderStatus = MutableLiveData>()
+ val cancelOrderStatus: LiveData> = _cancelOrderStatus
+ private val _isCancellingOrder = MutableLiveData()
+ val isCancellingOrder: LiveData = _isCancellingOrder
+
// LiveData untuk OrderItems
private val _orderItems = MutableLiveData>()
val orderItems: LiveData> get() = _orderItems
@@ -131,4 +138,26 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() {
}
}
}
+
+ fun cancelOrder(cancelReq: CancelOrderReq) {
+ viewModelScope.launch {
+ try {
+ _cancelOrderStatus.value = Result.Loading
+ val result = repository.cancelOrder(cancelReq)
+ _cancelOrderStatus.value = result
+ } catch (e: Exception) {
+ Log.e("HistoryViewModel", "Error cancelling order: ${e.message}")
+ _cancelOrderStatus.value = Result.Error(e)
+ }
+ }
+ }
+
+ fun refreshOrders(status: String = "all") {
+ Log.d(TAG, "Refreshing orders with status: $status")
+ // Clear current orders before fetching new ones
+ _orders.value = ViewState.Loading
+
+ // Re-fetch the orders with the current status
+ getOrderList(status)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt
index c420ae8..5432f78 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryAdapter.kt
@@ -2,6 +2,7 @@ package com.alya.ecommerce_serang.ui.order.history
import android.app.Activity
import android.app.Dialog
+import android.content.ContextWrapper
import android.content.Intent
import android.graphics.Color
import android.net.Uri
@@ -17,6 +18,7 @@ import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
+import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -24,6 +26,7 @@ import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
import com.alya.ecommerce_serang.data.api.dto.ReviewUIItem
import com.alya.ecommerce_serang.ui.order.detail.PaymentActivity
+import com.alya.ecommerce_serang.ui.order.history.cancelorder.CancelOrderBottomSheet
import com.alya.ecommerce_serang.ui.order.review.CreateReviewActivity
import com.alya.ecommerce_serang.ui.product.ReviewProductActivity
import com.google.android.material.button.MaterialButton
@@ -150,7 +153,8 @@ class OrderHistoryAdapter(
visibility = View.VISIBLE
text = itemView.context.getString(R.string.canceled_order_btn)
setOnClickListener {
- showCancelOrderDialog(order.orderId.toString())
+ showCancelOrderBottomSheet(order.orderId)
+ viewModel.refreshOrders()
}
}
deadlineDate.apply {
@@ -171,7 +175,8 @@ class OrderHistoryAdapter(
visibility = View.VISIBLE
text = itemView.context.getString(R.string.canceled_order_btn)
setOnClickListener {
- showCancelOrderDialog(order.orderId.toString())
+ showCancelOrderBottomSheet(order.orderId)
+ viewModel.refreshOrders()
}
}
@@ -208,6 +213,7 @@ class OrderHistoryAdapter(
text = itemView.context.getString(R.string.canceled_order_btn)
setOnClickListener {
showCancelOrderDialog(order.orderId.toString())
+ viewModel.refreshOrders()
}
}
}
@@ -226,7 +232,7 @@ class OrderHistoryAdapter(
text = itemView.context.getString(R.string.claim_complaint)
setOnClickListener {
showCancelOrderDialog(order.orderId.toString())
- // Handle click event
+ viewModel.refreshOrders()
}
}
btnRight.apply {
@@ -235,6 +241,7 @@ class OrderHistoryAdapter(
setOnClickListener {
// Handle click event
viewModel.confirmOrderCompleted(order.orderId, "completed")
+ viewModel.refreshOrders()
}
}
@@ -243,16 +250,6 @@ class OrderHistoryAdapter(
text = formatShipmentDate(order.updatedAt, order.etd ?: "0")
}
}
- "delivered" -> {
- // Untuk status delivered, tampilkan "Beri Ulasan"
- btnRight.apply {
- visibility = View.VISIBLE
- text = itemView.context.getString(R.string.add_review)
- setOnClickListener {
- // Handle click event
- }
- }
- }
"completed" -> {
statusOrder.apply {
visibility = View.VISIBLE
@@ -267,6 +264,7 @@ class OrderHistoryAdapter(
text = itemView.context.getString(R.string.add_review)
setOnClickListener {
addReviewProduct(order)
+ viewModel.refreshOrders()
// Handle click event
}
}
@@ -492,6 +490,48 @@ class OrderHistoryAdapter(
dialog.show()
}
+ private fun showCancelOrderBottomSheet(orderId : Int) {
+ val context = itemView.context
+
+ // We need a FragmentManager to show the bottom sheet
+ // Try to get it from the context
+ val fragmentActivity = when (context) {
+ is FragmentActivity -> context
+ is ContextWrapper -> {
+ val baseContext = context.baseContext
+ if (baseContext is FragmentActivity) {
+ baseContext
+ } else {
+ // Log error and show a Toast instead if we can't get a FragmentManager
+ Log.e("OrderHistoryAdapter", "Cannot show bottom sheet: Context is not a FragmentActivity")
+ Toast.makeText(context, "Cannot show cancel order dialog", Toast.LENGTH_SHORT).show()
+ return
+ }
+ }
+ else -> {
+ // Log error and show a Toast instead if we can't get a FragmentManager
+ Log.e("OrderHistoryAdapter", "Cannot show bottom sheet: Context is not a FragmentActivity")
+ Toast.makeText(context, "Cannot show cancel order dialog", Toast.LENGTH_SHORT).show()
+ return
+ }
+ }
+
+ // Create and show the bottom sheet using the obtained FragmentManager
+ val bottomSheet = CancelOrderBottomSheet(
+ orderId = orderId,
+ onOrderCancelled = {
+ // Handle the successful cancellation
+ // Refresh the data
+ viewModel.refreshOrders() // Assuming there's a method to refresh orders
+
+ // Show a success message
+ Toast.makeText(context, "Order cancelled successfully", Toast.LENGTH_SHORT).show()
+ }
+ )
+
+ bottomSheet.show(fragmentActivity.supportFragmentManager, CancelOrderBottomSheet.TAG)
+ }
+
private fun addReviewProduct(order: OrdersItem) {
// Use ViewModel to fetch order details
viewModel.getOrderDetails(order.orderId)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryFragment.kt
index b7d0f6e..267eb28 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryFragment.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderHistoryFragment.kt
@@ -47,11 +47,9 @@ class OrderHistoryFragment : Fragment() {
1 -> getString(R.string.pending_orders)
2 -> getString(R.string.unpaid_orders)
3 -> getString(R.string.processed_orders)
- 4 -> getString(R.string.paid_orders)
- 5 -> getString(R.string.shipped_orders)
- 6 -> getString(R.string.delivered_orders)
- 7 -> getString(R.string.completed_orders)
- 8 -> getString(R.string.canceled_orders)
+ 4 -> getString(R.string.shipped_orders)
+ 5 -> getString(R.string.completed_orders)
+ 6 -> getString(R.string.canceled_orders)
else -> "Tab $position"
}
}.attach()
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderViewPageAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderViewPageAdapter.kt
index a759240..615f7a1 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderViewPageAdapter.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/OrderViewPageAdapter.kt
@@ -14,9 +14,7 @@ class OrderViewPagerAdapter(
"pending", // Menunggu Tagihan
"unpaid", // Belum Dibayar
"processed", // Diproses
- "paid", // Dibayar
"shipped", // Dikirim
- "delivered", // Diterima
"completed", // Selesai
"canceled" // Dibatalkan
)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/cancelorder/CancelOrderBottomSheet.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/cancelorder/CancelOrderBottomSheet.kt
new file mode 100644
index 0000000..99f25de
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/history/cancelorder/CancelOrderBottomSheet.kt
@@ -0,0 +1,173 @@
+package com.alya.ecommerce_serang.ui.order.history.cancelorder
+
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.Button
+import android.widget.Spinner
+import android.widget.TextView
+import android.widget.Toast
+import androidx.fragment.app.viewModels
+import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.dto.CancelOrderReq
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.data.repository.OrderRepository
+import com.alya.ecommerce_serang.data.repository.Result
+import com.alya.ecommerce_serang.ui.order.history.HistoryViewModel
+import com.alya.ecommerce_serang.utils.BaseViewModelFactory
+import com.alya.ecommerce_serang.utils.SessionManager
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+
+class CancelOrderBottomSheet(
+ private val orderId: Int,
+ private val onOrderCancelled: () -> Unit
+) : BottomSheetDialogFragment() {
+ private lateinit var sessionManager: SessionManager
+
+ private val viewModel: HistoryViewModel by viewModels {
+ BaseViewModelFactory {
+ val apiService = ApiConfig.getApiService(sessionManager)
+ val orderRepository = OrderRepository(apiService)
+ HistoryViewModel(orderRepository)
+ }
+ }
+ private var selectedReason: CancelOrderReq? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.layout_cancel_order_bottom, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ sessionManager = SessionManager(requireContext())
+
+ val tvTitle = view.findViewById(R.id.tv_title)
+ val spinnerReason = view.findViewById(R.id.spinner_reason)
+ val btnCancel = view.findViewById