mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-13 10:42:21 +00:00
update address
This commit is contained in:
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
@ -21,7 +21,7 @@ data class CreateAddressRequest (
|
|||||||
@SerializedName("province_id")
|
@SerializedName("province_id")
|
||||||
val provId: Int,
|
val provId: Int,
|
||||||
@SerializedName("postal_code")
|
@SerializedName("postal_code")
|
||||||
val postCode: String,
|
val postCode: String? = null,
|
||||||
|
|
||||||
@SerializedName("detail")
|
@SerializedName("detail")
|
||||||
val detailAddress: String? = null,
|
val detailAddress: String? = null,
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.dto
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class UpdateCart (
|
||||||
|
@SerializedName("cart_item_id")
|
||||||
|
val cartItemId: Int,
|
||||||
|
|
||||||
|
@SerializedName("quantity")
|
||||||
|
val quantity: Int
|
||||||
|
)
|
@ -26,7 +26,7 @@ data class AddressesItem(
|
|||||||
val provinceId: Int,
|
val provinceId: Int,
|
||||||
|
|
||||||
@field:SerializedName("phone")
|
@field:SerializedName("phone")
|
||||||
val phone: Any,
|
val phone: String,
|
||||||
|
|
||||||
@field:SerializedName("street")
|
@field:SerializedName("street")
|
||||||
val street: String,
|
val street: String,
|
||||||
@ -35,7 +35,7 @@ data class AddressesItem(
|
|||||||
val subdistrict: String,
|
val subdistrict: String,
|
||||||
|
|
||||||
@field:SerializedName("recipient")
|
@field:SerializedName("recipient")
|
||||||
val recipient: Any,
|
val recipient: String,
|
||||||
|
|
||||||
@field:SerializedName("id")
|
@field:SerializedName("id")
|
||||||
val id: Int,
|
val id: Int,
|
||||||
|
@ -7,6 +7,7 @@ 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.OrderRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
|
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
|
||||||
@ -14,6 +15,9 @@ import com.alya.ecommerce_serang.data.api.response.cart.AddCartResponse
|
|||||||
import com.alya.ecommerce_serang.data.api.response.cart.ListCartResponse
|
import com.alya.ecommerce_serang.data.api.response.cart.ListCartResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.order.CourierCostResponse
|
import com.alya.ecommerce_serang.data.api.response.order.CourierCostResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse
|
import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.ListCityResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.ListProvinceResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.UpdateCartResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.product.AllProductResponse
|
import com.alya.ecommerce_serang.data.api.response.product.AllProductResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.product.CategoryResponse
|
import com.alya.ecommerce_serang.data.api.response.product.CategoryResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.product.DetailStoreProductResponse
|
import com.alya.ecommerce_serang.data.api.response.product.DetailStoreProductResponse
|
||||||
@ -21,11 +25,13 @@ import com.alya.ecommerce_serang.data.api.response.product.ProductResponse
|
|||||||
import com.alya.ecommerce_serang.data.api.response.product.ReviewProductResponse
|
import com.alya.ecommerce_serang.data.api.response.product.ReviewProductResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.product.StoreResponse
|
import com.alya.ecommerce_serang.data.api.response.product.StoreResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.profile.AddressResponse
|
import com.alya.ecommerce_serang.data.api.response.profile.AddressResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.profile.CreateAddressResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.profile.ProfileResponse
|
import com.alya.ecommerce_serang.data.api.response.profile.ProfileResponse
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.PUT
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
|
||||||
interface ApiService {
|
interface ApiService {
|
||||||
@ -80,9 +86,8 @@ interface ApiService {
|
|||||||
|
|
||||||
@POST("profile/addaddress")
|
@POST("profile/addaddress")
|
||||||
suspend fun createAddress(
|
suspend fun createAddress(
|
||||||
@Body addressRequest: CreateAddressRequest
|
@Body createAddressRequest: CreateAddressRequest
|
||||||
): Response<AddressResponse>
|
): Response<CreateAddressResponse>
|
||||||
|
|
||||||
|
|
||||||
@GET("mystore")
|
@GET("mystore")
|
||||||
suspend fun getStore (): Response<StoreResponse>
|
suspend fun getStore (): Response<StoreResponse>
|
||||||
@ -95,8 +100,23 @@ interface ApiService {
|
|||||||
@Body cartRequest: CartItem
|
@Body cartRequest: CartItem
|
||||||
): Response<AddCartResponse>
|
): Response<AddCartResponse>
|
||||||
|
|
||||||
|
@PUT("cart/update")
|
||||||
|
suspend fun updateCart(
|
||||||
|
@Body updateCart: UpdateCart
|
||||||
|
): Response<UpdateCartResponse>
|
||||||
|
|
||||||
@POST("couriercost")
|
@POST("couriercost")
|
||||||
suspend fun countCourierCost(
|
suspend fun countCourierCost(
|
||||||
@Body courierCost : CourierCostRequest
|
@Body courierCost : CourierCostRequest
|
||||||
): CourierCostResponse
|
): Response<CourierCostResponse>
|
||||||
|
|
||||||
|
@GET("cities/{id}")
|
||||||
|
suspend fun getCityProvId(
|
||||||
|
@Path("id") provId : Int
|
||||||
|
): Response<ListCityResponse>
|
||||||
|
|
||||||
|
@GET("provinces")
|
||||||
|
suspend fun getListProv(
|
||||||
|
): Response<ListProvinceResponse>
|
||||||
|
|
||||||
}
|
}
|
@ -1,10 +1,14 @@
|
|||||||
package com.alya.ecommerce_serang.data.repository
|
package com.alya.ecommerce_serang.data.repository
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
|
||||||
import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse
|
import com.alya.ecommerce_serang.data.api.response.order.CreateOrderResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.ListCityResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.ListProvinceResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.product.ProductResponse
|
import com.alya.ecommerce_serang.data.api.response.product.ProductResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.product.StoreResponse
|
import com.alya.ecommerce_serang.data.api.response.product.StoreResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.profile.CreateAddressResponse
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
|
||||||
@ -33,17 +37,31 @@ class OrderRepository(private val apiService: ApiService) {
|
|||||||
return if (response.isSuccessful) response.body() else null
|
return if (response.isSuccessful) response.body() else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//post data with message/response
|
||||||
|
suspend fun addAddress(createAddressRequest: CreateAddressRequest): Result<CreateAddressResponse> {
|
||||||
|
return try {
|
||||||
|
val response = apiService.createAddress(createAddressRequest)
|
||||||
|
if (response.isSuccessful){
|
||||||
|
response.body()?.let {
|
||||||
|
Result.Success(it)
|
||||||
|
} ?: Result.Error(Exception("Add Address failed"))
|
||||||
|
} else {
|
||||||
|
Log.e("OrderRepository", "Error: ${response.errorBody()?.string()}")
|
||||||
|
Result.Error(Exception(response.errorBody()?.string() ?: "Unknown error"))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//not yet implement the api service address
|
suspend fun getListProvinces(): ListProvinceResponse? {
|
||||||
// suspend fun getAddressDetails(addressId: Int): AddressesItem {
|
val response = apiService.getListProv()
|
||||||
// // Simulate API call to get address details
|
return if (response.isSuccessful) response.body() else null
|
||||||
// kotlinx.coroutines.delay(300) // Simulate network request
|
}
|
||||||
// // Return mock data
|
|
||||||
// return AddressesItem(
|
suspend fun getListCities(provId : Int): ListCityResponse?{
|
||||||
// id = addressId,
|
val response = apiService.getCityProvId(provId)
|
||||||
// label = "Rumah",
|
return if (response.isSuccessful) response.body() else null
|
||||||
// fullAddress = "Jl. Pegangasan Timur No. 42, Jakarta"
|
}
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
@ -10,12 +10,9 @@ import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
|||||||
|
|
||||||
class UserRepository(private val apiService: ApiService) {
|
class UserRepository(private val apiService: ApiService) {
|
||||||
|
|
||||||
|
//post data without message/response
|
||||||
suspend fun requestOtpRep(email: String): OtpResponse {
|
suspend fun requestOtpRep(email: String): OtpResponse {
|
||||||
|
|
||||||
// fun requestOtpRep(email: String): Result<String> {
|
|
||||||
|
|
||||||
return apiService.getOTP(OtpRequest(email))
|
return apiService.getOTP(OtpRequest(email))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun registerUser(request: RegisterRequest): String {
|
suspend fun registerUser(request: RegisterRequest): String {
|
||||||
|
@ -1,21 +1,289 @@
|
|||||||
package com.alya.ecommerce_serang.ui.order
|
package com.alya.ecommerce_serang.ui.order
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.location.Location
|
||||||
|
import android.location.LocationListener
|
||||||
|
import android.location.LocationManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.enableEdgeToEdge
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.constraintlayout.motion.widget.Debug.getLocation
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.lifecycle.Lifecycle
|
||||||
import com.alya.ecommerce_serang.R
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.CitiesItem
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.ActivityAddAddressBinding
|
||||||
|
import com.alya.ecommerce_serang.utils.SavedStateViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class AddAddressActivity : AppCompatActivity() {
|
class AddAddressActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityAddAddressBinding
|
||||||
|
private lateinit var apiService: ApiService
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
private lateinit var profileUser: UserProfile
|
||||||
|
private lateinit var locationManager: LocationManager
|
||||||
|
|
||||||
|
private var latitude: Double? = null
|
||||||
|
private var longitude: Double? = null
|
||||||
|
private val provinceAdapter by lazy { ProvinceAdapter(this) }
|
||||||
|
private val cityAdapter by lazy { CityAdapter(this) }
|
||||||
|
|
||||||
|
private val viewModel: AddAddressViewModel by viewModels {
|
||||||
|
SavedStateViewModelFactory(this) { savedStateHandle ->
|
||||||
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
val orderRepository = OrderRepository(apiService)
|
||||||
|
AddAddressViewModel(orderRepository, savedStateHandle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
binding = ActivityAddAddressBinding.inflate(layoutInflater)
|
||||||
setContentView(R.layout.activity_add_address)
|
setContentView(binding.root)
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
|
||||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
sessionManager = SessionManager(this)
|
||||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
apiService = ApiConfig.getApiService(sessionManager)
|
||||||
insets
|
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
||||||
|
|
||||||
|
setupToolbar()
|
||||||
|
setupAutoComplete()
|
||||||
|
setupButtonListeners()
|
||||||
|
collectFlows()
|
||||||
|
requestLocationPermission()
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun viewModelAddAddress(request: CreateAddressRequest) {
|
||||||
|
// Call the private fun in your ViewModel using reflection or expose it in ViewModel
|
||||||
|
val method = AddAddressViewModel::class.java.getDeclaredMethod("addAddress", CreateAddressRequest::class.java)
|
||||||
|
method.isAccessible = true
|
||||||
|
method.invoke(viewModel, request)
|
||||||
|
}
|
||||||
|
// UI setup methods
|
||||||
|
private fun setupToolbar() {
|
||||||
|
binding.toolbar.setNavigationOnClickListener { finish() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupAutoComplete() {
|
||||||
|
// Set adapters
|
||||||
|
binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
|
||||||
|
binding.autoCompleteKabupaten.setAdapter(cityAdapter)
|
||||||
|
|
||||||
|
// Set listeners
|
||||||
|
binding.autoCompleteProvinsi.setOnItemClickListener { _, _, position, _ ->
|
||||||
|
provinceAdapter.getProvinceId(position)?.let { provinceId ->
|
||||||
|
viewModel.getCities(provinceId)
|
||||||
|
binding.autoCompleteKabupaten.text.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.autoCompleteKabupaten.setOnItemClickListener { _, _, position, _ ->
|
||||||
|
cityAdapter.getCityId(position)?.let { cityId ->
|
||||||
|
viewModel.selectedCityId = cityId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupButtonListeners() {
|
||||||
|
binding.buttonSimpan.setOnClickListener {
|
||||||
|
validateAndSubmitForm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectFlows() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
launch {
|
||||||
|
viewModel.provincesState.collect { state ->
|
||||||
|
handleProvinceState(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
viewModel.citiesState.collect { state ->
|
||||||
|
handleCityState(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
viewModel.addressSubmissionState.collect { state ->
|
||||||
|
handleAddressSubmissionState(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleProvinceState(state: ViewState<List<ProvincesItem>>) {
|
||||||
|
when (state) {
|
||||||
|
is ViewState.Loading -> null //showProvinceLoading(true)
|
||||||
|
is ViewState.Success -> {
|
||||||
|
provinceAdapter.updateData(state.data)
|
||||||
|
}
|
||||||
|
is ViewState.Error -> {
|
||||||
|
showError(state.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCityState(state: ViewState<List<CitiesItem>>) {
|
||||||
|
when (state) {
|
||||||
|
is ViewState.Loading -> null //showCityLoading(true)
|
||||||
|
is ViewState.Success -> {
|
||||||
|
// showCityLoading(false)
|
||||||
|
cityAdapter.updateData(state.data)
|
||||||
|
}
|
||||||
|
is ViewState.Error -> {
|
||||||
|
// showCityLoading(false)
|
||||||
|
showError(state.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleAddressSubmissionState(state: ViewState<String>) {
|
||||||
|
when (state) {
|
||||||
|
is ViewState.Loading -> showSubmitLoading(true)
|
||||||
|
is ViewState.Success -> {
|
||||||
|
showSubmitLoading(false)
|
||||||
|
showSuccessAndFinish(state.data)
|
||||||
|
}
|
||||||
|
is ViewState.Error -> {
|
||||||
|
showSubmitLoading(false)
|
||||||
|
showError(state.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// private fun showProvinceLoading(isLoading: Boolean) {
|
||||||
|
// // Implement province loading indicator
|
||||||
|
// binding.provinceProgressBar?.visibility = if (isLoading) View.VISIBLE else View.GONE
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun showCityLoading(isLoading: Boolean) {
|
||||||
|
// // Implement city loading indicator
|
||||||
|
// binding.cityProgressBar?.visibility = if (isLoading) View.VISIBLE else View.GONE
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
private fun showSubmitLoading(isLoading: Boolean) {
|
||||||
|
binding.buttonSimpan.isEnabled = !isLoading
|
||||||
|
binding.buttonSimpan.text = if (isLoading) "Menyimpan..." else "Simpan"
|
||||||
|
// You might want to show a progress bar as well
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showError(message: String) {
|
||||||
|
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSuccessAndFinish(message: String) {
|
||||||
|
Toast.makeText(this, "Sukses: $message", Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateAndSubmitForm() {
|
||||||
|
val lat = latitude
|
||||||
|
val long = longitude
|
||||||
|
|
||||||
|
if (lat == null || long == null) {
|
||||||
|
showError("Lokasi belum terdeteksi")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val street = binding.etDetailAlamat.text.toString()
|
||||||
|
val subDistrict = binding.etKecamatan.text.toString()
|
||||||
|
val postalCode = binding.etKodePos.text.toString()
|
||||||
|
val recipient = binding.etNamaPenerima.text.toString()
|
||||||
|
val phone = binding.etNomorHp.text.toString()
|
||||||
|
val userId = profileUser.userId
|
||||||
|
val isStoreLocation = false
|
||||||
|
|
||||||
|
val provinceId = viewModel.selectedProvinceId
|
||||||
|
val cityId = viewModel.selectedCityId
|
||||||
|
|
||||||
|
if (street.isBlank() || recipient.isBlank() || phone.isBlank()) {
|
||||||
|
showError("Lengkapi semua field wajib")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provinceId == null) {
|
||||||
|
showError("Pilih provinsi terlebih dahulu")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cityId == null) {
|
||||||
|
showError("Pilih kota/kabupaten terlebih dahulu")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val request = CreateAddressRequest(
|
||||||
|
lat = lat,
|
||||||
|
long = long,
|
||||||
|
street = street,
|
||||||
|
subDistrict = subDistrict,
|
||||||
|
cityId = cityId,
|
||||||
|
provId = provinceId,
|
||||||
|
postCode = postalCode,
|
||||||
|
detailAddress = street,
|
||||||
|
userId = userId,
|
||||||
|
recipient = recipient,
|
||||||
|
phone = phone,
|
||||||
|
isStoreLocation = isStoreLocation
|
||||||
|
)
|
||||||
|
|
||||||
|
viewModel.addAddress(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val locationPermissionLauncher =
|
||||||
|
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
||||||
|
if (granted) getLocation() else Toast.makeText(this, "Izin lokasi ditolak",Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestLocationPermission() {
|
||||||
|
locationPermissionLauncher.launch(android.Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
private fun requestLocation() {
|
||||||
|
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
||||||
|
val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||||
|
|
||||||
|
if (!isGpsEnabled && !isNetworkEnabled) {
|
||||||
|
Toast.makeText(this, "Provider lokasi tidak tersedia", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val provider = if (isGpsEnabled) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER
|
||||||
|
|
||||||
|
locationManager.requestSingleUpdate(provider, object : LocationListener {
|
||||||
|
override fun onLocationChanged(location: Location) {
|
||||||
|
latitude = location.latitude
|
||||||
|
longitude = location.longitude
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
|
||||||
|
override fun onProviderEnabled(provider: String) {}
|
||||||
|
override fun onProviderDisabled(provider: String) {
|
||||||
|
Toast.makeText(this@AddAddressActivity, "Provider dimatikan", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
if (requestCode == 100 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
requestLocation()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "Location permission denied", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.CitiesItem
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem
|
||||||
|
import com.alya.ecommerce_serang.data.repository.OrderRepository
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class AddAddressViewModel(private val repository: OrderRepository, private val savedStateHandle: SavedStateHandle): ViewModel() {
|
||||||
|
// Flow states for data
|
||||||
|
private val _addressSubmissionState = MutableStateFlow<com.alya.ecommerce_serang.ui.order.ViewState<String>>(com.alya.ecommerce_serang.ui.order.ViewState.Loading)
|
||||||
|
val addressSubmissionState = _addressSubmissionState.asStateFlow()
|
||||||
|
|
||||||
|
private val _provincesState = MutableStateFlow<com.alya.ecommerce_serang.ui.order.ViewState<List<ProvincesItem>>>(com.alya.ecommerce_serang.ui.order.ViewState.Loading)
|
||||||
|
val provincesState = _provincesState.asStateFlow()
|
||||||
|
|
||||||
|
private val _citiesState = MutableStateFlow<com.alya.ecommerce_serang.ui.order.ViewState<List<CitiesItem>>>(com.alya.ecommerce_serang.ui.order.ViewState.Loading)
|
||||||
|
val citiesState = _citiesState.asStateFlow()
|
||||||
|
|
||||||
|
// Stored in SavedStateHandle for configuration changes
|
||||||
|
var selectedProvinceId: Int?
|
||||||
|
get() = savedStateHandle.get<Int>("selectedProvinceId")
|
||||||
|
set(value) { savedStateHandle["selectedProvinceId"] = value }
|
||||||
|
|
||||||
|
var selectedCityId: Int?
|
||||||
|
get() = savedStateHandle.get<Int>("selectedCityId")
|
||||||
|
set(value) { savedStateHandle["selectedCityId"] = value }
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Load provinces on initialization
|
||||||
|
getProvinces()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addAddress(request: CreateAddressRequest){
|
||||||
|
viewModelScope.launch {
|
||||||
|
when (val result = repository.addAddress(request)) {
|
||||||
|
is Result.Success -> {
|
||||||
|
val message = result.data.message // Ambil `message` dari CreateAddressResponse
|
||||||
|
_addressSubmissionState.value = ViewState.Success(message)
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
_addressSubmissionState.value = ViewState.Error(result.exception.message ?: "Unknown error")
|
||||||
|
}
|
||||||
|
is Result.Loading -> {
|
||||||
|
// Optional, karena sudah set Loading di awal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getProvinces(){
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val result = repository.getListProvinces()
|
||||||
|
result?.let {
|
||||||
|
_provincesState.value = com.alya.ecommerce_serang.ui.order.ViewState.Success(it.provinces)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("AddAddressViewModel", "Error fetching provinces: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCities(provinceId: Int){
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
selectedProvinceId = provinceId
|
||||||
|
val result = repository.getListCities(provinceId)
|
||||||
|
result?.let {
|
||||||
|
_citiesState.value = com.alya.ecommerce_serang.ui.order.ViewState.Success(it.cities)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("AddAddressViewModel", "Error fetching cities: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSelectedProvinceId(id: Int) {
|
||||||
|
selectedProvinceId = id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSelectedCityId(id: Int) {
|
||||||
|
selectedCityId = id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSelectedProvinceId(): Int? = selectedProvinceId
|
||||||
|
fun getSelectedCityId(): Int? = selectedCityId
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "AddAddressViewModel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ViewState<out T> {
|
||||||
|
object Loading : com.alya.ecommerce_serang.ui.order.ViewState<Nothing>()
|
||||||
|
data class Success<T>(val data: T) : com.alya.ecommerce_serang.ui.order.ViewState<T>()
|
||||||
|
data class Error(val message: String) : com.alya.ecommerce_serang.ui.order.ViewState<Nothing>()
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.order
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.CitiesItem
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.ProvincesItem
|
||||||
|
|
||||||
|
// UI adapters and helpers
|
||||||
|
class ProvinceAdapter(
|
||||||
|
context: Context,
|
||||||
|
resource: Int = android.R.layout.simple_dropdown_item_1line
|
||||||
|
) : ArrayAdapter<String>(context, resource, ArrayList()) {
|
||||||
|
|
||||||
|
private val provinces = ArrayList<ProvincesItem>()
|
||||||
|
|
||||||
|
fun updateData(newProvinces: List<ProvincesItem>) {
|
||||||
|
provinces.clear()
|
||||||
|
provinces.addAll(newProvinces)
|
||||||
|
|
||||||
|
clear()
|
||||||
|
addAll(provinces.map { it.province })
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getProvinceId(position: Int): Int? {
|
||||||
|
return provinces.getOrNull(position)?.provinceId?.toIntOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CityAdapter(
|
||||||
|
context: Context,
|
||||||
|
resource: Int = android.R.layout.simple_dropdown_item_1line
|
||||||
|
) : ArrayAdapter<String>(context, resource, ArrayList()) {
|
||||||
|
|
||||||
|
private val cities = ArrayList<CitiesItem>()
|
||||||
|
|
||||||
|
fun updateData(newCities: List<CitiesItem>) {
|
||||||
|
cities.clear()
|
||||||
|
cities.addAll(newCities)
|
||||||
|
|
||||||
|
clear()
|
||||||
|
addAll(cities.map { it.cityName })
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCityId(position: Int): Int? {
|
||||||
|
return cities.getOrNull(position)?.cityId?.toIntOrNull()
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,10 @@
|
|||||||
package com.alya.ecommerce_serang.utils
|
package com.alya.ecommerce_serang.utils
|
||||||
|
|
||||||
|
import androidx.lifecycle.AbstractSavedStateViewModelFactory
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.savedstate.SavedStateRegistryOwner
|
||||||
|
|
||||||
class BaseViewModelFactory<VM : ViewModel>(
|
class BaseViewModelFactory<VM : ViewModel>(
|
||||||
private val creator: () -> VM
|
private val creator: () -> VM
|
||||||
@ -11,3 +14,19 @@ class BaseViewModelFactory<VM : ViewModel>(
|
|||||||
return creator() as T
|
return creator() as T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a new factory for SavedStateHandle ViewModels
|
||||||
|
class SavedStateViewModelFactory<VM : ViewModel>(
|
||||||
|
private val owner: SavedStateRegistryOwner,
|
||||||
|
private val creator: (SavedStateHandle) -> VM
|
||||||
|
) : AbstractSavedStateViewModelFactory(owner, null) {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(
|
||||||
|
key: String,
|
||||||
|
modelClass: Class<T>,
|
||||||
|
handle: SavedStateHandle
|
||||||
|
): T {
|
||||||
|
return creator(handle) as T
|
||||||
|
}
|
||||||
|
}
|
@ -131,7 +131,6 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:hint="Pilih Kabupaten / Kota"
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
|
||||||
|
|
||||||
<AutoCompleteTextView
|
<AutoCompleteTextView
|
||||||
@ -139,6 +138,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="none"
|
android:inputType="none"
|
||||||
|
android:hint="Masukkan Kabupaten"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
android:textSize="14sp" />
|
android:textSize="14sp" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
@ -29,9 +29,11 @@
|
|||||||
android:textAlignment="textEnd"
|
android:textAlignment="textEnd"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:paddingEnd="16dp"
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingVertical="16dp"
|
||||||
android:textColor="@color/blue_500"
|
android:textColor="@color/blue_500"
|
||||||
android:fontFamily="@font/dmsans_semibold"
|
android:fontFamily="@font/dmsans_semibold"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
|
android:clickable="true"
|
||||||
android:text="Tambah Alamat"
|
android:text="Tambah Alamat"
|
||||||
tools:ignore="RtlCompat" />
|
tools:ignore="RtlCompat" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -47,13 +47,23 @@
|
|||||||
android:layout_gravity="start"
|
android:layout_gravity="start"
|
||||||
android:padding="8dp">
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_location_checkout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:tint="@color/blue_500"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:src="@drawable/baseline_location_pin_24"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/linear_address"
|
android:id="@+id/linear_address"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toEndOf="@id/iv_location_checkout"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/tv_change_address">
|
app:layout_constraintEnd_toStartOf="@id/tv_change_address">
|
||||||
|
Reference in New Issue
Block a user