diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt index 84b4caa..21a2ee1 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutActivity.kt @@ -26,6 +26,7 @@ import com.alya.ecommerce_serang.data.repository.OrderRepository import com.alya.ecommerce_serang.databinding.ActivityCheckoutBinding import com.alya.ecommerce_serang.ui.order.address.AddressActivity import com.alya.ecommerce_serang.utils.BaseViewModelFactory +import com.alya.ecommerce_serang.utils.PopUpDialog import com.alya.ecommerce_serang.utils.SessionManager import java.text.NumberFormat import java.util.Locale @@ -372,7 +373,16 @@ class CheckoutActivity : AppCompatActivity() { // Create order button binding.btnPay.setOnClickListener { if (validateOrder()) { - viewModel.createOrder() + PopUpDialog.showConfirmDialog( + context = this, + title = "Apakah anda yakin inging membuat pesanan?", + message = "Pastikan data yang dimasukkan sudah benar", + positiveText = "Ya", + negativeText = "Tidak", + onYesClicked = { + viewModel.createOrder() + } + ) } } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt index abf1471..66c45f8 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/CheckoutViewModel.kt @@ -40,8 +40,6 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() { private val _orderCreated = MutableLiveData() val orderCreated: LiveData = _orderCreated - - // Initialize "Buy Now" checkout fun initializeBuyNow( storeId: Int, diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/RegisterStoreActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/RegisterStoreActivity.kt index 2a3368c..48d502a 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/RegisterStoreActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/RegisterStoreActivity.kt @@ -21,15 +21,16 @@ import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toDrawable +import androidx.core.net.toUri import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import com.alya.ecommerce_serang.R +import com.alya.ecommerce_serang.data.api.dto.PaymentUpdate 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.MyStoreRepository import com.alya.ecommerce_serang.data.repository.Result -import com.alya.ecommerce_serang.data.repository.UserRepository import com.alya.ecommerce_serang.databinding.ActivityRegisterStoreBinding import com.alya.ecommerce_serang.ui.order.address.BankAdapter import com.alya.ecommerce_serang.ui.order.address.CityAdapter @@ -38,6 +39,8 @@ import com.alya.ecommerce_serang.ui.order.address.SubdsitrictAdapter import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.FileUtils import com.alya.ecommerce_serang.utils.ImageUtils +import com.alya.ecommerce_serang.utils.PopUpDialog +import com.alya.ecommerce_serang.utils.RegisterStoreViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel import com.alya.ecommerce_serang.utils.viewmodel.RegisterStoreViewModel @@ -46,8 +49,6 @@ import okhttp3.MultipartBody import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody import java.io.File -import androidx.core.net.toUri -import com.alya.ecommerce_serang.data.api.dto.PaymentUpdate class RegisterStoreActivity : AppCompatActivity() { @@ -85,11 +86,7 @@ class RegisterStoreActivity : AppCompatActivity() { private val LOCATION_PERMISSION_REQUEST = 2001 private val viewModel: RegisterStoreViewModel by viewModels { - BaseViewModelFactory { - val apiService = ApiConfig.Companion.getApiService(sessionManager) - val orderRepository = UserRepository(apiService) - RegisterStoreViewModel(orderRepository) - } + RegisterStoreViewModelFactory(this, intent.extras) } private val myStoreViewModel: MyStoreViewModel by viewModels { @@ -260,8 +257,21 @@ class RegisterStoreActivity : AppCompatActivity() { } } else { binding.btnRegister.setOnClickListener { - if (viewModel.validateForm()) viewModel.registerStore(this) - else Toast.makeText(this, "Harap lengkapi semua field yang wajib diisi", Toast.LENGTH_SHORT).show() + if (viewModel.validateForm()){ + PopUpDialog.showConfirmDialog( + context = this, + title = "Apakah anda yakin ingin mendaftar toko?", + message = "Pastikan data yang dimasukkan sudah benar", + positiveText = "Ya", + negativeText = "Tidak", + onYesClicked = { + viewModel.registerStore(this) + } + ) + } + else { + Toast.makeText(this, "Harap lengkapi semua field yang wajib diisi", Toast.LENGTH_SHORT).show() + } } } validateRequiredFields() @@ -797,12 +807,21 @@ class RegisterStoreActivity : AppCompatActivity() { 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() + if (viewModel.storeName.value != s.toString()) { + viewModel.storeName.value = s.toString() + } Log.d(TAG, "Store name updated: ${s.toString()}") validateRequiredFields() } }) + viewModel.storeName.observe(this) { value -> + if (binding.etStoreName.text.toString() != value) { + binding.etStoreName.setText(value) + binding.etStoreName.setSelection(value.length) // Set cursor to end + } + } + 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) {} @@ -817,48 +836,80 @@ class RegisterStoreActivity : AppCompatActivity() { 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() + if (viewModel.street.value != s.toString()) { + viewModel.street.value = s.toString() + } Log.d(TAG, "Street address updated: ${s.toString()}") validateRequiredFields() } }) + viewModel.street.observe(this) { value -> + if (binding.etStreet.text.toString() != value) { + binding.etStreet.setText(value) + binding.etStreet.setSelection(value.length) + } + } + 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?) { - viewModel.postalCode.value = s.toString().toIntOrNull() ?: 0 + val newValue = s.toString().toIntOrNull() ?: 0 + if (viewModel.postalCode.value != newValue) { + viewModel.postalCode.value = newValue + } validateRequiredFields() } }) + viewModel.postalCode.observe(this) { value -> + val currentText = binding.etPostalCode.text.toString() + val valueString = if (value == 0) "" else value.toString() + if (currentText != valueString) { + binding.etPostalCode.setText(valueString) + binding.etPostalCode.setSelection(valueString.length) + } + } + 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() + if (viewModel.addressDetail.value != s.toString()) { + viewModel.addressDetail.value = s.toString() + } Log.d(TAG, "Address detail updated: ${s.toString()}") validateRequiredFields() } }) + viewModel.addressDetail.observe(this) { value -> + if (binding.etAddressDetail.text.toString() != value) { + binding.etAddressDetail.setText(value) + binding.etAddressDetail.setSelection(value.length) + } + } + 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?) { val input = s.toString() - if (input.isNotEmpty()) { + val newValue = if (input.isNotEmpty()) { try { - viewModel.bankNumber.value = input.toInt() - Log.d(TAG, "Bank number updated: $input") + input.toInt() } catch (e: NumberFormatException) { - // Handle invalid input if needed Log.e(TAG, "Failed to parse bank number. Input: $input, Error: $e") + 0 } } else { - // Handle empty input - perhaps set to 0 or null depending on your requirements - viewModel.bankNumber.value = 0 // or 0 - Log.d(TAG, "Bank number set to default: 0") + 0 + } + + if (viewModel.bankNumber.value != newValue) { + viewModel.bankNumber.value = newValue + Log.d(TAG, "Bank number updated: $newValue") } validateRequiredFields() } @@ -888,16 +939,27 @@ class RegisterStoreActivity : AppCompatActivity() { 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.accountName.value = s.toString() + if (viewModel.accountName.value != s.toString()) { + viewModel.accountName.value = s.toString() + } Log.d(TAG, "Account Name updated: ${s.toString()}") validateRequiredFields() } }) + viewModel.accountName.observe(this) { value -> + if (binding.etAccountName.text.toString() != value) { + binding.etAccountName.setText(value) + binding.etAccountName.setSelection(value.length) + } + } + Log.d(TAG, "setupDataBinding: Text field data binding setup completed") } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) Log.d(TAG, "onActivityResult: Request code: $requestCode, Result code: $resultCode") diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/sells/payment/DetailPaymentActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/sells/payment/DetailPaymentActivity.kt index f12bd42..94488bd 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/sells/payment/DetailPaymentActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/sells/payment/DetailPaymentActivity.kt @@ -27,6 +27,7 @@ import com.alya.ecommerce_serang.data.repository.SellsRepository import com.alya.ecommerce_serang.databinding.ActivityDetailPaymentBinding import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsProductAdapter import com.alya.ecommerce_serang.utils.BaseViewModelFactory +import com.alya.ecommerce_serang.utils.PopUpDialog import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel @@ -109,8 +110,18 @@ class DetailPaymentActivity : AppCompatActivity() { binding.btnConfirmPayment.setOnClickListener { sells?.orderId?.let { - viewModel.confirmPayment(it, "confirmed") - Toast.makeText(this, "Pembayaran dikonfirmasi", Toast.LENGTH_SHORT).show() + + PopUpDialog.showConfirmDialog( + context = this, + title = "Apakah anda yakin?", + message = "Pastikan data pembayaran sudah sesuai", + positiveText = "Ya", + negativeText = "Tidak", + onYesClicked = { + viewModel.confirmPayment(it, "confirmed") + Toast.makeText(this, "Pembayaran dikonfirmasi", Toast.LENGTH_SHORT).show() + } + ) } ?: run { Log.e("DetailPaymentActivity", "No order passed in intent") } diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/BaseViewModelFactory.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/BaseViewModelFactory.kt index e224d9e..539cb21 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/BaseViewModelFactory.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/BaseViewModelFactory.kt @@ -1,10 +1,16 @@ package com.alya.ecommerce_serang.utils +import android.content.Context +import android.os.Bundle +import androidx.fragment.app.Fragment import androidx.lifecycle.AbstractSavedStateViewModelFactory import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.savedstate.SavedStateRegistryOwner +import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig +import com.alya.ecommerce_serang.data.repository.UserRepository +import com.alya.ecommerce_serang.utils.viewmodel.RegisterStoreViewModel class BaseViewModelFactory( private val creator: () -> VM @@ -29,4 +35,30 @@ class SavedStateViewModelFactory( ): T { return creator(handle) as T } +} + +class RegisterStoreViewModelFactory( + private val owner: SavedStateRegistryOwner, + private val defaultArgs: Bundle? = null +) : AbstractSavedStateViewModelFactory(owner, defaultArgs) { + + @Suppress("UNCHECKED_CAST") + override fun create( + key: String, + modelClass: Class, + handle: SavedStateHandle + ): T { + return when { + modelClass.isAssignableFrom(RegisterStoreViewModel::class.java) -> { + // Create SessionManager and ApiService + val context = if (owner is Context) owner else (owner as Fragment).requireContext() + val sessionManager = SessionManager(context) + val apiService = ApiConfig.getApiService(sessionManager) + val repository = UserRepository(apiService) + + RegisterStoreViewModel(repository, handle) as T + } + else -> throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}") + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/PopUpDialog.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/PopUpDialog.kt new file mode 100644 index 0000000..bab0022 --- /dev/null +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/PopUpDialog.kt @@ -0,0 +1,84 @@ +package com.alya.ecommerce_serang.utils + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import com.alya.ecommerce_serang.R +import com.google.android.material.button.MaterialButton +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +object PopUpDialog { + fun showConfirmDialog( + context: Context, + title: String , + message: String? = null, + iconRes: Int? = null, + positiveText: String? = null, + negativeText: String? = null, + onYesClicked: (() -> Unit)? = null, + onNoClicked: (() -> Unit)? = null + ) { + val inflater = LayoutInflater.from(context) + val dialogView = inflater.inflate(R.layout.dialog_popup, null) + + val iconView = dialogView.findViewById(R.id.dialogIcon) + val titleView = dialogView.findViewById(R.id.dialogTitle) + val messageView = dialogView.findViewById(R.id.dialogMessage) + val yesButton = dialogView.findViewById(R.id.btnYes) + val noButton = dialogView.findViewById(R.id.btnNo) + + if (iconRes != null) { + iconView.setImageResource(iconRes) + iconView.visibility = View.VISIBLE + } else { + iconView.visibility = View.GONE + } + + // Title + titleView.text = title + + // Message + if (message.isNullOrEmpty()) { + messageView.visibility = View.GONE + } else { + messageView.text = message + messageView.visibility = View.VISIBLE + } + + // Yes button (always visible, but customizable text) + + if (positiveText.isNullOrEmpty()) { + yesButton.visibility = View.GONE + } else { + yesButton.text = positiveText + yesButton.visibility = View.VISIBLE + } + + // No button (optional) + if (negativeText.isNullOrEmpty()) { + noButton.visibility = View.GONE + } else { + noButton.text = negativeText + noButton.visibility = View.VISIBLE + } + + val dialog = MaterialAlertDialogBuilder(context, R.style.ThemeOverlay_MyApp_AlertDialog) + .setView(dialogView) + .create() + + yesButton.setOnClickListener { + onYesClicked?.invoke() + dialog.dismiss() + } + + noButton.setOnClickListener { + onNoClicked?.invoke() + dialog.dismiss() + } + + + dialog.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/UriToFileConverter.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/UriToFileConverter.kt index 269e342..560ec1e 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/UriToFileConverter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/UriToFileConverter.kt @@ -7,7 +7,6 @@ import android.provider.OpenableColumns import android.util.Log import java.io.File import java.io.FileOutputStream -import java.io.IOException import java.io.InputStream import kotlin.random.Random @@ -123,23 +122,4 @@ object UriToFileConverter { null } } - - fun getFilePathFromUri(uri: Uri, context: Context): String? { - // For Media Gallery - val projection = arrayOf(MediaStore.Images.Media.DATA) - try { - val cursor = context.contentResolver.query(uri, projection, null, null, null) - cursor?.use { - if (it.moveToFirst()) { - val columnIndex = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA) - return it.getString(columnIndex) - } - } - } catch (e: Exception) { - Log.e(TAG, "Error getting file path from URI", e) - } - - // If the above method fails, try direct conversion - return uri.path - } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/RegisterStoreViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/RegisterStoreViewModel.kt index 9962e75..084bd34 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/RegisterStoreViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/RegisterStoreViewModel.kt @@ -5,6 +5,7 @@ import android.net.Uri import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.alya.ecommerce_serang.data.api.response.auth.RegisterStoreResponse @@ -19,7 +20,8 @@ import com.alya.ecommerce_serang.utils.ImageUtils import kotlinx.coroutines.launch class RegisterStoreViewModel( - private val repository: UserRepository + private val repository: UserRepository, + private val savedStateHandle: SavedStateHandle ) : ViewModel() { // LiveData for UI state @@ -56,20 +58,20 @@ class RegisterStoreViewModel( var selectedBankName: String? = 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() + val storeName: MutableLiveData = savedStateHandle.getLiveData("storeName", "") + val storeDescription: MutableLiveData = savedStateHandle.getLiveData("storeDescription", "") + val storeTypeId: MutableLiveData = savedStateHandle.getLiveData("storeTypeId", 0) + val latitude: MutableLiveData = savedStateHandle.getLiveData("latitude", "") + val longitude: MutableLiveData = savedStateHandle.getLiveData("longitude", "") + val street: MutableLiveData = savedStateHandle.getLiveData("street", "") + val subdistrict: MutableLiveData = savedStateHandle.getLiveData("subdistrict", "") + val cityId: MutableLiveData = savedStateHandle.getLiveData("cityId", "") + val provinceId: MutableLiveData = savedStateHandle.getLiveData("provinceId", 0) + val postalCode: MutableLiveData = savedStateHandle.getLiveData("postalCode", 0) + val addressDetail: MutableLiveData = savedStateHandle.getLiveData("addressDetail", "") + val bankName: MutableLiveData = savedStateHandle.getLiveData("bankName", "") + val bankNumber: MutableLiveData = savedStateHandle.getLiveData("bankNumber", 0) + val accountName: MutableLiveData = savedStateHandle.getLiveData("accountName", "") // Files var storeImageUri: Uri? = null @@ -79,6 +81,15 @@ class RegisterStoreViewModel( var persetujuanUri: Uri? = null var qrisUri: Uri? = null + fun getFieldValue(key: String): String { + return savedStateHandle.get(key) ?: "" + } + + // Helper function to update any field + fun updateField(key: String, value: String) { + savedStateHandle[key] = value + } + // Selected couriers val selectedCouriers = mutableListOf() diff --git a/app/src/main/res/drawable/checkmark__1_.xml b/app/src/main/res/drawable/checkmark__1_.xml new file mode 100644 index 0000000..f0e11c0 --- /dev/null +++ b/app/src/main/res/drawable/checkmark__1_.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cancel.xml b/app/src/main/res/drawable/ic_cancel.xml new file mode 100644 index 0000000..16837bb --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/splash_drawable.xml b/app/src/main/res/drawable/splash_drawable.xml index ddc1aba..bd2465d 100644 --- a/app/src/main/res/drawable/splash_drawable.xml +++ b/app/src/main/res/drawable/splash_drawable.xml @@ -4,8 +4,8 @@ \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_popup.xml b/app/src/main/res/layout/dialog_popup.xml new file mode 100644 index 0000000..7bb9dc2 --- /dev/null +++ b/app/src/main/res/layout/dialog_popup.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index e6706ed..8456b85 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -339,7 +339,10 @@ @style/ShapeAppearance.MyApp.MediumComponent + @color/white @font/dmsans_regular + @android:color/transparent + true