diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 610dd43..01dfd70 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"> + diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/City.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/City.kt index f8160a2..cb07730 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/City.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/City.kt @@ -2,30 +2,20 @@ package com.alya.ecommerce_serang.data.api.dto import com.google.gson.annotations.SerializedName -data class City( - @SerializedName("city_id") - val cityId: String, +data class CityResponse( - @SerializedName("city_name") - val cityName: String, + @field:SerializedName("cities") + val cities: List, - @SerializedName("province_id") - val provinceId: String, - - @SerializedName("province") - val provinceName: String, - - @SerializedName("type") - val type: String, - - @SerializedName("postal_code") - val postalCode: String + @field:SerializedName("message") + val message: String ) -data class CityResponse( - @SerializedName("message") - val message: String, +data class City( - @SerializedName("cities") - val data: List -) \ No newline at end of file + @field:SerializedName("city_name") + val cityName: String, + + @field:SerializedName("city_id") + val cityId: String +) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/ResetPassReq.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/ResetPassReq.kt new file mode 100644 index 0000000..6d281a6 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/ResetPassReq.kt @@ -0,0 +1,8 @@ +package com.alya.ecommerce_serang.data.api.dto + +import com.google.gson.annotations.SerializedName + +data class ResetPassReq ( + @SerializedName("emailOrPhone") + val emailOrPhone: String +) \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/UpdateAddressReq.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/UpdateAddressReq.kt new file mode 100644 index 0000000..68c815f --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/UpdateAddressReq.kt @@ -0,0 +1,28 @@ +package com.alya.ecommerce_serang.data.api.dto + +import com.google.gson.annotations.SerializedName + +data class UpdateAddressReq( + @SerializedName("street") + val street: String? = "", + + @SerializedName("subdistrict") + val subdistrict: String? = "", + + @SerializedName("postal_code") + val postalCCode: String? = "", + + @SerializedName("detail") + val detail: String? = "", + + @SerializedName("city_id") + val cityId: String? = "", + + @SerializedName("province_id") + val provId: String? = "", + + @SerializedName("phone") + val phone: String? = "" + + +) \ No newline at end of file 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 6a85bca..724db3d 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 @@ -19,6 +19,7 @@ import com.alya.ecommerce_serang.data.api.dto.OtpRequest import com.alya.ecommerce_serang.data.api.dto.PaymentConfirmRequest import com.alya.ecommerce_serang.data.api.dto.ProvinceResponse import com.alya.ecommerce_serang.data.api.dto.RegisterRequest +import com.alya.ecommerce_serang.data.api.dto.ResetPassReq import com.alya.ecommerce_serang.data.api.dto.ReviewProductItem import com.alya.ecommerce_serang.data.api.dto.SearchRequest import com.alya.ecommerce_serang.data.api.dto.ShippingServiceRequest @@ -36,6 +37,7 @@ import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse +import com.alya.ecommerce_serang.data.api.response.auth.ResetPassResponse import com.alya.ecommerce_serang.data.api.response.auth.VerifRegisterResponse import com.alya.ecommerce_serang.data.api.response.chat.ChatHistoryResponse import com.alya.ecommerce_serang.data.api.response.chat.ChatListResponse @@ -65,6 +67,7 @@ import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressRespo import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.EditProfileResponse import com.alya.ecommerce_serang.data.api.response.customer.profile.ProfileResponse +import com.alya.ecommerce_serang.data.api.response.customer.profile.UpdateAddressResponse import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse @@ -454,6 +457,12 @@ interface ApiService { @Body addressData: HashMap ): Response + @PUT("profile/address/edit/{idAddress}") + suspend fun updateAddress( + @Path("id") addressId: Int, + @Body params: Map + ): Response + @POST("search") suspend fun saveSearchQuery( @Body searchRequest: SearchRequest @@ -524,4 +533,9 @@ interface ApiService { suspend fun getVillages( @Path("subdistrictId") subdistrictId: String ): Response + + @POST("resetpass") + suspend fun postResetPass( + @Body request: ResetPassReq + ): Response } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/AddressRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/AddressRepository.kt index 0095e93..3ea4f63 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/AddressRepository.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/AddressRepository.kt @@ -1,131 +1,139 @@ package com.alya.ecommerce_serang.data.repository import android.util.Log -import com.alya.ecommerce_serang.data.api.dto.City -import com.alya.ecommerce_serang.data.api.dto.Province -import com.alya.ecommerce_serang.data.api.dto.StoreAddress +import com.alya.ecommerce_serang.data.api.dto.CityResponse +import com.alya.ecommerce_serang.data.api.dto.ProvinceResponse +import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse +import com.alya.ecommerce_serang.data.api.response.customer.profile.UpdateAddressResponse import com.alya.ecommerce_serang.data.api.retrofit.ApiService -import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.json.JSONObject +import retrofit2.Response class AddressRepository(private val apiService: ApiService) { private val TAG = "AddressRepository" - suspend fun getProvinces(): List = withContext(Dispatchers.IO) { - Log.d(TAG, "getProvinces() called") - try { - val response = apiService.getProvinces() - Log.d(TAG, "getProvinces() response: isSuccessful=${response.isSuccessful}, code=${response.code()}") +// suspend fun getProvinces(): List = withContext(Dispatchers.IO) { +// Log.d(TAG, "getProvinces() called") +// try { +// val response = apiService.getProvinces() +// Log.d(TAG, "getProvinces() response: isSuccessful=${response.isSuccessful}, code=${response.code()}") +// +// // Log the raw response body for debugging +// val rawBody = response.raw().toString() +// Log.d(TAG, "Raw response: $rawBody") +// +// if (response.isSuccessful) { +// val responseBody = response.body() +// Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}") +// +// val provinces = responseBody?.data ?: emptyList() +// Log.d(TAG, "getProvinces() success, got ${provinces.size} provinces") +// return@withContext provinces +// } else { +// val errorBody = response.errorBody()?.string() ?: "Unknown error" +// Log.e(TAG, "getProvinces() error: $errorBody") +// throw Exception("API Error (${response.code()}): $errorBody") +// } +// } catch (e: Exception) { +// Log.e(TAG, "Exception in getProvinces()", e) +// throw Exception("Network error: ${e.message}") +// } +// } - // Log the raw response body for debugging - val rawBody = response.raw().toString() - Log.d(TAG, "Raw response: $rawBody") +// suspend fun getCities(provinceId: String): List = withContext(Dispatchers.IO) { +// Log.d(TAG, "getCities() called with provinceId: $provinceId") +// try { +// val response = apiService.getCities(provinceId) +// Log.d(TAG, "getCities() response: isSuccessful=${response.isSuccessful}, code=${response.code()}") +// +// if (response.isSuccessful) { +// val responseBody = response.body() +// Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}") +// +// val cities = responseBody?.data ?: emptyList() +// Log.d(TAG, "getCities() success, got ${cities.size} cities") +// return@withContext cities +// } else { +// val errorBody = response.errorBody()?.string() ?: "Unknown error" +// Log.e(TAG, "getCities() error: $errorBody") +// throw Exception("API Error (${response.code()}): $errorBody") +// } +// } catch (e: Exception) { +// Log.e(TAG, "Exception in getCities()", e) +// throw Exception("Network error: ${e.message}") +// } +// } - if (response.isSuccessful) { - val responseBody = response.body() - Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}") - - val provinces = responseBody?.data ?: emptyList() - Log.d(TAG, "getProvinces() success, got ${provinces.size} provinces") - return@withContext provinces - } else { - val errorBody = response.errorBody()?.string() ?: "Unknown error" - Log.e(TAG, "getProvinces() error: $errorBody") - throw Exception("API Error (${response.code()}): $errorBody") - } - } catch (e: Exception) { - Log.e(TAG, "Exception in getProvinces()", e) - throw Exception("Network error: ${e.message}") - } + suspend fun getProvinces(): Response { + return apiService.getProvinces() } - suspend fun getCities(provinceId: String): List = withContext(Dispatchers.IO) { - Log.d(TAG, "getCities() called with provinceId: $provinceId") - try { - val response = apiService.getCities(provinceId) - Log.d(TAG, "getCities() response: isSuccessful=${response.isSuccessful}, code=${response.code()}") - - if (response.isSuccessful) { - val responseBody = response.body() - Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}") - - val cities = responseBody?.data ?: emptyList() - Log.d(TAG, "getCities() success, got ${cities.size} cities") - return@withContext cities - } else { - val errorBody = response.errorBody()?.string() ?: "Unknown error" - Log.e(TAG, "getCities() error: $errorBody") - throw Exception("API Error (${response.code()}): $errorBody") - } - } catch (e: Exception) { - Log.e(TAG, "Exception in getCities()", e) - throw Exception("Network error: ${e.message}") - } + suspend fun getCities(provinceId: String): Response { + return apiService.getCities(provinceId) } - suspend fun getStoreAddress(): StoreAddress? = withContext(Dispatchers.IO) { - Log.d(TAG, "getStoreAddress() called") - try { - val response = apiService.getStoreAddress() - Log.d(TAG, "getStoreAddress() response: isSuccessful=${response.isSuccessful}, code=${response.code()}") - - if (response.isSuccessful) { - val responseBody = response.body() - val rawJson = Gson().toJson(responseBody) - Log.d(TAG, "Response body: $rawJson") - - val address = responseBody?.data - Log.d(TAG, "getStoreAddress() success, address: $address") - - // Convert numeric strings to proper types if needed - address?.let { - // Handle city_id if it's a number - if (it.cityId.isBlank() && rawJson.contains("city_id")) { - try { - val cityId = JSONObject(rawJson).getJSONObject("store").optInt("city_id", 0) - if (cityId > 0) { - it.javaClass.getDeclaredField("cityId").apply { - isAccessible = true - set(it, cityId.toString()) - } - Log.d(TAG, "Updated cityId to: ${it.cityId}") - } - } catch (e: Exception) { - Log.e(TAG, "Error parsing city_id", e) - } - } - - // Handle province_id if it's a number - if (it.provinceId.isBlank() && rawJson.contains("province_id")) { - try { - val provinceId = JSONObject(rawJson).getJSONObject("store").optInt("province_id", 0) - if (provinceId > 0) { - it.javaClass.getDeclaredField("provinceId").apply { - isAccessible = true - set(it, provinceId.toString()) - } - Log.d(TAG, "Updated provinceId to: ${it.provinceId}") - } - } catch (e: Exception) { - Log.e(TAG, "Error parsing province_id", e) - } - } - } - - return@withContext address - } else { - val errorBody = response.errorBody()?.string() ?: "Unknown error" - Log.e(TAG, "getStoreAddress() error: $errorBody") - throw Exception("API Error (${response.code()}): $errorBody") - } - } catch (e: Exception) { - Log.e(TAG, "Exception in getStoreAddress()", e) - throw Exception("Network error: ${e.message}") - } - } +// suspend fun getStoreAddress(): StoreAddress? = withContext(Dispatchers.IO) { +// Log.d(TAG, "getStoreAddress() called") +// try { +// val response = apiService.getStoreAddress() +// Log.d(TAG, "getStoreAddress() response: isSuccessful=${response.isSuccessful}, code=${response.code()}") +// +// if (response.isSuccessful) { +// val responseBody = response.body() +// val rawJson = Gson().toJson(responseBody) +// Log.d(TAG, "Response body: $rawJson") +// +// val address = responseBody?.data +// Log.d(TAG, "getStoreAddress() success, address: $address") +// +// // Convert numeric strings to proper types if needed +// address?.let { +// // Handle city_id if it's a number +// if (it.cityId.isBlank() && rawJson.contains("city_id")) { +// try { +// val cityId = JSONObject(rawJson).getJSONObject("store").optInt("city_id", 0) +// if (cityId > 0) { +// it.javaClass.getDeclaredField("cityId").apply { +// isAccessible = true +// set(it, cityId.toString()) +// } +// Log.d(TAG, "Updated cityId to: ${it.cityId}") +// } +// } catch (e: Exception) { +// Log.e(TAG, "Error parsing city_id", e) +// } +// } +// +// // Handle province_id if it's a number +// if (it.provinceId.isBlank() && rawJson.contains("province_id")) { +// try { +// val provinceId = JSONObject(rawJson).getJSONObject("store").optInt("province_id", 0) +// if (provinceId > 0) { +// it.javaClass.getDeclaredField("provinceId").apply { +// isAccessible = true +// set(it, provinceId.toString()) +// } +// Log.d(TAG, "Updated provinceId to: ${it.provinceId}") +// } +// } catch (e: Exception) { +// Log.e(TAG, "Error parsing province_id", e) +// } +// } +// } +// +// return@withContext address +// } else { +// val errorBody = response.errorBody()?.string() ?: "Unknown error" +// Log.e(TAG, "getStoreAddress() error: $errorBody") +// throw Exception("API Error (${response.code()}): $errorBody") +// } +// } catch (e: Exception) { +// Log.e(TAG, "Exception in getStoreAddress()", e) +// throw Exception("Network error: ${e.message}") +// } +// } suspend fun saveStoreAddress( provinceId: String, @@ -171,4 +179,12 @@ class AddressRepository(private val apiService: ApiService) { throw Exception("Network error: ${e.message}") } } + + suspend fun getStoreAddress(): Response { + return apiService.getAddress() + } + + suspend fun updateAddress(addressId: Int, params: Map): Response { + return apiService.updateAddress(addressId, params) + } } \ 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 c6acff8..bf7f317 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 @@ -7,6 +7,7 @@ import com.alya.ecommerce_serang.data.api.dto.FcmReq import com.alya.ecommerce_serang.data.api.dto.LoginRequest import com.alya.ecommerce_serang.data.api.dto.OtpRequest import com.alya.ecommerce_serang.data.api.dto.RegisterRequest +import com.alya.ecommerce_serang.data.api.dto.ResetPassReq import com.alya.ecommerce_serang.data.api.dto.UserProfile import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse @@ -18,6 +19,7 @@ import com.alya.ecommerce_serang.data.api.response.auth.NotifstoreItem import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse +import com.alya.ecommerce_serang.data.api.response.auth.ResetPassResponse import com.alya.ecommerce_serang.data.api.response.auth.VerifRegisterResponse import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse import com.alya.ecommerce_serang.data.api.response.customer.order.ListProvinceResponse @@ -490,6 +492,30 @@ class UserRepository(private val apiService: ApiService) { Result.Error(e) } } + + suspend fun resetPassword(request: ResetPassReq): Result{ + return try { + val response = apiService.postResetPass(request) + + if (response.isSuccessful){ + val resetPassResponse = response.body() + if (resetPassResponse != null) { + Result.Success(resetPassResponse) + } + else { + Result.Error(Exception("Empty response from server")) + } + } + else { + val errorBody = response.errorBody()?.string() ?: "Unknown error" + Log.e(TAG, "Error RESET PASS address: $errorBody") + Result.Error(Exception(errorBody)) + } + } + catch (e: Exception){ + Result.Error(e) + } + } companion object{ private const val TAG = "UserRepository" } 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 01e7307..9d8184c 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 @@ -82,6 +82,11 @@ class LoginActivity : AppCompatActivity() { startActivity(Intent(this, RegisterActivity::class.java)) finish() } + + binding.tvForgetPassword.setOnClickListener { + startActivity(Intent(this, ResetPassActivity::class.java)) + finish() + } } private fun observeLoginState() { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/auth/ResetPassActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/ResetPassActivity.kt new file mode 100644 index 0000000..70cc88d --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/ResetPassActivity.kt @@ -0,0 +1,123 @@ +package com.alya.ecommerce_serang.ui.auth + +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.Toast +import androidx.activity.viewModels +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig +import com.alya.ecommerce_serang.data.repository.Result +import com.alya.ecommerce_serang.data.repository.UserRepository +import com.alya.ecommerce_serang.databinding.ActivityResetPassBinding +import com.alya.ecommerce_serang.utils.BaseViewModelFactory +import com.alya.ecommerce_serang.utils.viewmodel.LoginViewModel + +class ResetPassActivity : AppCompatActivity() { + + private val TAG = "ResetPassActivity" + private lateinit var binding: ActivityResetPassBinding + + private val loginViewModel: LoginViewModel by viewModels{ + BaseViewModelFactory { + val apiService = ApiConfig.getUnauthenticatedApiService() + val userRepository = UserRepository(apiService) + LoginViewModel(userRepository, this) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityResetPassBinding.inflate(layoutInflater) + setContentView(binding.root) +// enableEdgeToEdge() + + setupToolbar() + setupUI() + + } + + private fun setupToolbar(){ + binding.headerResetPass.headerLeftIcon.setOnClickListener{ + finish() + } + binding.headerResetPass.headerTitle.text = "Lupa Password" + } + + private fun setupUI(){ + binding.btnReset.setOnClickListener { + val email = binding.etEmail.text.toString().trim() + if (email.isNotEmpty()) { + loginViewModel.resetPassword(email) + } else { + binding.etEmail.error = "Masukkan Email Anda" + } + } + } + + private fun observeResetPassword() { + loginViewModel.resetPasswordState.observe(this) { result -> + when (result) { + is com.alya.ecommerce_serang.data.repository.Result.Loading -> { + showLoading(true) + } + + is com.alya.ecommerce_serang.data.repository.Result.Success -> { + showLoading(false) + handleSuccess("Silahkan cek email anda untuk melihat password anda.") + Log.d(TAG, "Success rest password: ${result.data.message}") + } + + is Result.Error -> { + showLoading(false) + handleError("Email anda salah atau tidak ditemukan.") + Log.e(TAG, "Error reset password ${result.exception.message}") + } + + null -> { + // Initial state + } + } + } + } + + private fun showLoading(isLoading: Boolean) { + if (isLoading) { + binding.progressBar.visibility = View.VISIBLE + binding.btnReset.isEnabled = false + binding.etEmail.isEnabled = false + } else { + binding.progressBar.visibility = View.GONE + binding.btnReset.isEnabled = true + binding.etEmail.isEnabled = true + } + } + + private fun handleSuccess(message: String) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show() + + // Show success dialog and navigate back to login + AlertDialog.Builder(this) + .setTitle("Berhasil Ubah Password") + .setMessage(message) + .setPositiveButton("OK") { _, _ -> + // Navigate back to login activity + finish() + } + .setCancelable(false) + .show() + } + + private fun handleError(errorMessage: String) { + Toast.makeText(this, "Error: $errorMessage", Toast.LENGTH_LONG).show() + + // Optionally show error dialog + AlertDialog.Builder(this) + .setTitle("Gagal Ubah Password") + .setMessage(errorMessage) + .setPositiveButton("OK", null) + .show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressActivity.kt index edd0a5a..3c7dcaa 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddressActivity.kt @@ -99,6 +99,7 @@ class AddressActivity : AppCompatActivity() { viewModel.selectedAddressId.observe(this) { selectedId -> adapter.setSelectedAddressId(selectedId) + finish() } } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/address/DetailStoreAddressActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/address/DetailStoreAddressActivity.kt index c90fe8b..26bfc87 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/address/DetailStoreAddressActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/address/DetailStoreAddressActivity.kt @@ -1,6 +1,5 @@ package com.alya.ecommerce_serang.ui.profile.mystore.profile.address -import android.app.Activity import android.os.Bundle import android.util.Log import android.view.View @@ -14,13 +13,14 @@ import com.alya.ecommerce_serang.BuildConfig import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.dto.City import com.alya.ecommerce_serang.data.api.dto.Province +import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressesItem import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.repository.AddressRepository import com.alya.ecommerce_serang.databinding.ActivityDetailStoreAddressBinding -import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager +import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel import com.google.android.material.snackbar.Snackbar class DetailStoreAddressActivity : AppCompatActivity() { @@ -32,6 +32,7 @@ class DetailStoreAddressActivity : AppCompatActivity() { private var selectedCityId: String? = null private var provinces: List = emptyList() private var cities: List = emptyList() + private var currentAddress: AddressesItem? = null private val TAG = "StoreAddressActivity" @@ -178,6 +179,7 @@ class DetailStoreAddressActivity : AppCompatActivity() { // Observe store address data viewModel.storeAddress.observe(this) { address -> + currentAddress = address Log.d(TAG, "Received store address: $address") address?.let { // Set the fields @@ -214,11 +216,12 @@ class DetailStoreAddressActivity : AppCompatActivity() { } // Observe save success - viewModel.saveSuccess.observe(this) { - if (it) { - Toast.makeText(this, "Alamat berhasil disimpan", Toast.LENGTH_SHORT).show() - setResult(Activity.RESULT_OK) + viewModel.saveSuccess.observe(this) { success -> + if (success) { + Log.d(TAG, "Address updated successfully") finish() + } else { + Log.e(TAG, "Failed to update address") } } } @@ -229,8 +232,8 @@ class DetailStoreAddressActivity : AppCompatActivity() { val subdistrict = binding.edtSubdistrict.text.toString() val detail = binding.edtDetailAddress.text.toString() val postalCode = binding.edtPostalCode.text.toString() - val latitude = binding.edtLatitude.text.toString().toDoubleOrNull() ?: 0.0 - val longitude = binding.edtLongitude.text.toString().toDoubleOrNull() ?: 0.0 + val latitude = binding.edtLatitude.text.toString() + val longitude = binding.edtLongitude.text.toString() val city = cities.find { it.cityId == selectedCityId } val province = provinces.find { it.provinceId == selectedProvinceId } @@ -241,12 +244,10 @@ class DetailStoreAddressActivity : AppCompatActivity() { return@setOnClickListener } - // Save address - viewModel.saveStoreAddress( + val oldAddress = currentAddress ?: return@setOnClickListener + val newAddress = oldAddress.copy( provinceId = selectedProvinceId!!, - provinceName = province?.provinceName ?: "", cityId = city.cityId, - cityName = city.cityName, street = street, subdistrict = subdistrict, detail = detail, @@ -254,6 +255,20 @@ class DetailStoreAddressActivity : AppCompatActivity() { latitude = latitude, longitude = longitude ) + viewModel.saveStoreAddress(oldAddress, newAddress) + // Save address +// viewModel.saveStoreAddress( +// provinceId = selectedProvinceId!!, +// provinceName = province?.provinceName ?: "", +// cityId = city.cityId, +// cityName = city.cityName, +// street = street, +// subdistrict = subdistrict, +// detail = detail, +// postalCode = postalCode, +// latitude = latitude, +// longitude = longitude +// ) } } diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/AddressViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/AddressViewModel.kt index 9f3d94a..3f3bb5c 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/AddressViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/AddressViewModel.kt @@ -7,7 +7,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.alya.ecommerce_serang.data.api.dto.City import com.alya.ecommerce_serang.data.api.dto.Province -import com.alya.ecommerce_serang.data.api.dto.StoreAddress +import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressesItem import com.alya.ecommerce_serang.data.repository.AddressRepository import kotlinx.coroutines.launch @@ -21,8 +21,8 @@ class AddressViewModel(private val addressRepository: AddressRepository) : ViewM private val _cities = MutableLiveData>() val cities: LiveData> = _cities - private val _storeAddress = MutableLiveData() - val storeAddress: LiveData = _storeAddress + private val _storeAddress = MutableLiveData() + val storeAddress: LiveData get() = _storeAddress private val _isLoading = MutableLiveData() val isLoading: LiveData = _isLoading @@ -31,99 +31,219 @@ class AddressViewModel(private val addressRepository: AddressRepository) : ViewM val errorMessage: LiveData = _errorMessage private val _saveSuccess = MutableLiveData() - val saveSuccess: LiveData = _saveSuccess + val saveSuccess: LiveData get() = _saveSuccess + fun fetchProvinces() { - Log.d(TAG, "fetchProvinces() called") - _isLoading.value = true viewModelScope.launch { try { - Log.d(TAG, "Calling addressRepository.getProvinces()") val response = addressRepository.getProvinces() - Log.d(TAG, "Received provinces response: ${response.size} provinces") - _provinces.value = response - _isLoading.value = false + if (response.isSuccessful) { + _provinces.value = response.body()?.data ?: emptyList() + } else { + Log.e("EditAddressVM", "Failed to get provinces: ${response.message()}") + } } catch (e: Exception) { - Log.e(TAG, "Error fetching provinces", e) - _errorMessage.value = "Failed to load provinces: ${e.message}" - _isLoading.value = false + Log.e("EditAddressVM", "Error getting provinces: ${e.message}") } } } fun fetchCities(provinceId: String) { - Log.d(TAG, "fetchCities() called with provinceId: $provinceId") - _isLoading.value = true viewModelScope.launch { try { - Log.d(TAG, "Calling addressRepository.getCities()") val response = addressRepository.getCities(provinceId) - Log.d(TAG, "Received cities response: ${response.size} cities") - _cities.value = response - _isLoading.value = false + if (response.isSuccessful) { + _cities.value = response.body()?.cities ?: emptyList() + } else { + Log.e("EditAddressVM", "Failed to get cities: ${response.message()}") + } } catch (e: Exception) { - Log.e(TAG, "Error fetching cities", e) - _errorMessage.value = "Failed to load cities: ${e.message}" - _isLoading.value = false + Log.e("EditAddressVM", "Error getting cities: ${e.message}") } } } +// fun fetchProvinces() { +// Log.d(TAG, "fetchProvinces() called") +// _isLoading.value = true +// viewModelScope.launch { +// try { +// Log.d(TAG, "Calling addressRepository.getProvinces()") +// val response = addressRepository.getProvinces() +// Log.d(TAG, "Received provinces response: ${response.size} provinces") +// _provinces.value = response +// _isLoading.value = false +// } catch (e: Exception) { +// Log.e(TAG, "Error fetching provinces", e) +// _errorMessage.value = "Failed to load provinces: ${e.message}" +// _isLoading.value = false +// } +// } +// } + +// fun fetchCities(provinceId: String) { +// Log.d(TAG, "fetchCities() called with provinceId: $provinceId") +// _isLoading.value = true +// viewModelScope.launch { +// try { +// selecte +// Log.d(TAG, "Calling addressRepository.getCities()") +// val response = addressRepository.getCities(provinceId) +// Log.d(TAG, "Received cities response: ${response.size} cities") +// _cities.value = response +// _isLoading.value = false +// } catch (e: Exception) { +// Log.e(TAG, "Error fetching cities", e) +// _errorMessage.value = "Failed to load cities: ${e.message}" +// _isLoading.value = false +// } +// } +// } + +// fun fetchStoreAddress() { +// Log.d(TAG, "fetchStoreAddress() called") +// _isLoading.value = true +// viewModelScope.launch { +// try { +// Log.d(TAG, "Calling addressRepository.getStoreAddress()") +// val response = addressRepository.getStoreAddress() +// Log.d(TAG, "Received store address response: $response") +// _storeAddress.value = response +// _isLoading.value = false +// } catch (e: Exception) { +// Log.e(TAG, "Error fetching store address", e) +// _errorMessage.value = "Failed to load store address: ${e.message}" +// _isLoading.value = false +// } +// } +// } + +// fun fetchStoreAddress() { +// viewModelScope.launch { +// try { +// val response = addressRepository.getStoreAddress() +// if (response.isSuccessful) { +// val storeAddress = response.body()?.addresses +// ?.firstOrNull { it.isStoreLocation == true } +// +// if (storeAddress != null) { +// _storeAddress.value = storeAddress +// } else { +// Log.d("EditAddressVM", "No store address found") +// } +// } else { +// Log.e("EditAddressVM", "Failed to get addresses: ${response.message()}") +// } +// } catch (e: Exception) { +// Log.e("EditAddressVM", "Error: ${e.message}") +// } +// } +// } + fun fetchStoreAddress() { - Log.d(TAG, "fetchStoreAddress() called") - _isLoading.value = true viewModelScope.launch { try { - Log.d(TAG, "Calling addressRepository.getStoreAddress()") val response = addressRepository.getStoreAddress() - Log.d(TAG, "Received store address response: $response") - _storeAddress.value = response - _isLoading.value = false + if (response.isSuccessful) { + val storeAddress = response.body()?.addresses + ?.firstOrNull { it.isStoreLocation == true } + + if (storeAddress != null) { + _storeAddress.value = storeAddress + } else { + Log.d(TAG, "No store address found") + } + } else { + Log.e(TAG, "Failed to get addresses: ${response.message()}") + } } catch (e: Exception) { - Log.e(TAG, "Error fetching store address", e) - _errorMessage.value = "Failed to load store address: ${e.message}" - _isLoading.value = false + Log.e(TAG, "Error: ${e.message}") } } } - fun saveStoreAddress( - provinceId: String, - provinceName: String, - cityId: String, - cityName: String, - street: String, - subdistrict: String, - detail: String, - postalCode: String, - latitude: Double, - longitude: Double - ) { - Log.d(TAG, "saveStoreAddress() called with provinceId: $provinceId, cityId: $cityId") - _isLoading.value = true +// fun saveStoreAddress( +// provinceId: String, +// provinceName: String, +// cityId: String, +// cityName: String, +// street: String, +// subdistrict: String, +// detail: String, +// postalCode: String, +// latitude: Double, +// longitude: Double +// ) { +// Log.d(TAG, "saveStoreAddress() called with provinceId: $provinceId, cityId: $cityId") +// _isLoading.value = true +// viewModelScope.launch { +// try { +// Log.d(TAG, "Calling addressRepository.saveStoreAddress()") +// val success = addressRepository.saveStoreAddress( +// provinceId = provinceId, +// provinceName = provinceName, +// cityId = cityId, +// cityName = cityName, +// street = street, +// subdistrict = subdistrict, +// detail = detail, +// postalCode = postalCode, +// latitude = latitude, +// longitude = longitude +// ) +// Log.d(TAG, "Save store address result: $success") +// _saveSuccess.value = success +// _isLoading.value = false +// } catch (e: Exception) { +// Log.e(TAG, "Error saving store address", e) +// _errorMessage.value = "Failed to save address: ${e.message}" +// _isLoading.value = false +// } +// } +// } + + fun saveStoreAddress(oldAddress: AddressesItem, newAddress: AddressesItem) { + val params = buildUpdateBody(oldAddress, newAddress) + if (params.isEmpty()) { + Log.d(TAG, "No changes detected") + _saveSuccess.value = false + return + } + viewModelScope.launch { try { - Log.d(TAG, "Calling addressRepository.saveStoreAddress()") - val success = addressRepository.saveStoreAddress( - provinceId = provinceId, - provinceName = provinceName, - cityId = cityId, - cityName = cityName, - street = street, - subdistrict = subdistrict, - detail = detail, - postalCode = postalCode, - latitude = latitude, - longitude = longitude - ) - Log.d(TAG, "Save store address result: $success") - _saveSuccess.value = success - _isLoading.value = false + val response = addressRepository.updateAddress(oldAddress.id, params) + _saveSuccess.value = response.isSuccessful } catch (e: Exception) { - Log.e(TAG, "Error saving store address", e) - _errorMessage.value = "Failed to save address: ${e.message}" - _isLoading.value = false + Log.e(TAG, "Error: ${e.message}") + _saveSuccess.value = false } } } + + private fun buildUpdateBody(oldAddress: AddressesItem, newAddress: AddressesItem): Map { + val params = mutableMapOf() + + fun addIfChanged(key: String, oldValue: Any?, newValue: Any?) { + if (newValue != null && newValue != oldValue) { + params[key] = newValue + } + } + + addIfChanged("street", oldAddress.street, newAddress.street) + addIfChanged("province_id", oldAddress.provinceId, newAddress.provinceId) + addIfChanged("detail", oldAddress.detail, newAddress.detail) + addIfChanged("subdistrict", oldAddress.subdistrict, newAddress.subdistrict) + addIfChanged("city_id", oldAddress.cityId, newAddress.cityId) + addIfChanged("village_id", oldAddress.villageId, newAddress.villageId) + addIfChanged("postal_code", oldAddress.postalCode, newAddress.postalCode) + addIfChanged("phone", oldAddress.phone, newAddress.phone) + addIfChanged("recipient", oldAddress.recipient, newAddress.recipient) + addIfChanged("latitude", oldAddress.latitude, newAddress.latitude) + addIfChanged("longitude", oldAddress.longitude, newAddress.longitude) + addIfChanged("is_store_location", oldAddress.isStoreLocation, newAddress.isStoreLocation) + + return params + } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/LoginViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/LoginViewModel.kt index e4a6592..934cd71 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/LoginViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/LoginViewModel.kt @@ -7,8 +7,10 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.alya.ecommerce_serang.data.api.dto.FcmReq +import com.alya.ecommerce_serang.data.api.dto.ResetPassReq import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse +import com.alya.ecommerce_serang.data.api.response.auth.ResetPassResponse import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.repository.Result @@ -27,6 +29,10 @@ class LoginViewModel(private val repository: UserRepository, private val context private val _message = MutableLiveData() val message: LiveData = _message + private val _resetPasswordState = MutableLiveData?>() + val resetPasswordState: LiveData?> = _resetPasswordState + + private val sessionManager by lazy { SessionManager(context) } private fun getAuthenticatedApiService(): ApiService { @@ -69,4 +75,19 @@ class LoginViewModel(private val repository: UserRepository, private val context } } + fun resetPassword(email: String) { + viewModelScope.launch { + _resetPasswordState.value = Result.Loading + + val request = ResetPassReq(emailOrPhone = email) + val result = repository.resetPassword(request) + + _resetPasswordState.value = result + } + } + + fun clearState() { + _resetPasswordState.value = null + } + } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/RegisterViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/RegisterViewModel.kt index 7cddac2..07a062b 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/RegisterViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/RegisterViewModel.kt @@ -8,6 +8,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest import com.alya.ecommerce_serang.data.api.dto.RegisterRequest +import com.alya.ecommerce_serang.data.api.dto.ResetPassReq import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse @@ -382,6 +383,10 @@ class RegisterViewModel(private val repository: UserRepository, private val orde } } + fun resetPass(request: ResetPassReq){ + + } + companion object { private const val TAG = "RegisterViewModel" } diff --git a/app/src/main/res/layout/activity_detail_store_address.xml b/app/src/main/res/layout/activity_detail_store_address.xml index 230a417..caf9113 100644 --- a/app/src/main/res/layout/activity_detail_store_address.xml +++ b/app/src/main/res/layout/activity_detail_store_address.xml @@ -301,7 +301,8 @@ android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" - android:layout_marginEnd="8dp"> + android:layout_marginEnd="8dp" + android:visibility="gone"> + android:layout_marginStart="8dp" + android:visibility="gone"> diff --git a/app/src/main/res/layout/activity_reset_pass.xml b/app/src/main/res/layout/activity_reset_pass.xml new file mode 100644 index 0000000..1f34826 --- /dev/null +++ b/app/src/main/res/layout/activity_reset_pass.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + +