From 6a887a22aa7f564e85b5e38fcdf943f7fa3f0bfd Mon Sep 17 00:00:00 2001 From: Gracia Hotmauli <95269134+hotmauligracia@users.noreply.github.com> Date: Mon, 26 May 2025 04:41:47 +0700 Subject: [PATCH] edit store profile --- app/src/main/AndroidManifest.xml | 6 - .../data/api/retrofit/ApiService.kt | 1 + .../data/repository/MyStoreRepository.kt | 51 ++- .../ui/profile/mystore/MyStoreActivity.kt | 4 +- .../profile/DetailStoreProfileActivity.kt | 290 +++++++++++++---- .../profile/EditStoreProfileActivity.kt | 304 ------------------ .../utils/viewmodel/MyStoreViewModel.kt | 68 +++- .../layout/activity_detail_store_profile.xml | 34 +- app/src/main/res/layout/activity_my_store.xml | 6 +- .../main/res/layout/dialog_store_image.xml | 17 + 10 files changed, 392 insertions(+), 389 deletions(-) delete mode 100644 app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/EditStoreProfileActivity.kt create mode 100644 app/src/main/res/layout/dialog_store_image.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c7690ea..31e1666 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -47,18 +47,12 @@ - - 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 c889c3c..57e4742 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 @@ -382,6 +382,7 @@ interface ApiService { @Part("latitude") latitude: RequestBody, @Part("longitude") longitude: RequestBody, @Part("user_phone") userPhone: RequestBody, + @Part("store_type_id") storeTypeId: RequestBody, @Part storeimg: MultipartBody.Part? ): Response diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt index c14a0fd..801c8ea 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt @@ -2,9 +2,14 @@ package com.alya.ecommerce_serang.data.repository import android.util.Log import com.alya.ecommerce_serang.data.api.dto.Store +import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse +import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse import com.alya.ecommerce_serang.data.api.retrofit.ApiService +import okhttp3.MultipartBody +import okhttp3.RequestBody import retrofit2.HttpException +import retrofit2.Response import java.io.IOException class MyStoreRepository(private val apiService: ApiService) { @@ -14,18 +19,56 @@ class MyStoreRepository(private val apiService: ApiService) { if (response.isSuccessful) { val storeResponse: StoreResponse? = response.body() - Result.Success(storeResponse?.store) // ✅ Return Success with Store data + Result.Success(storeResponse?.store) } else { val errorMessage = response.errorBody()?.string() ?: "Unknown API error" Log.e("MyStoreRepository", "Error: $errorMessage") - Result.Error(HttpException(response)) // ✅ Wrap API error in Result.Error + Result.Error(HttpException(response)) } } catch (e: IOException) { Log.e("MyStoreRepository", "Network error: ${e.message}") - Result.Error(e) // ✅ Handle network-related errors + Result.Error(e) } catch (e: Exception) { Log.e("MyStoreRepository", "Unexpected error: ${e.message}") - Result.Error(e) // ✅ Handle unexpected errors + Result.Error(e) } } + + suspend fun listStoreType(): Result{ + return try{ + val response = apiService.listTypeStore() + if (response.isSuccessful) { + response.body()?.let { + Result.Success(it) + } ?: Result.Error(Exception("No store type")) + } else { + throw Exception("No response ${response.errorBody()?.string()}") + } + } catch (e:Exception){ + Result.Error(e) + } + } + + suspend fun updateStoreProfile( + storeName: RequestBody, + storeStatus: RequestBody, + storeDescription: RequestBody, + isOnLeave: RequestBody, + cityId: RequestBody, + provinceId: RequestBody, + street: RequestBody, + subdistrict: RequestBody, + detail: RequestBody, + postalCode: RequestBody, + latitude: RequestBody, + longitude: RequestBody, + userPhone: RequestBody, + storeType: RequestBody, + storeimg: MultipartBody.Part? + ): Response { + return apiService.updateStoreProfileMultipart( + storeName, storeStatus, storeDescription, isOnLeave, cityId, provinceId, + street, subdistrict, detail, postalCode, latitude, longitude, userPhone, storeType, storeimg + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt index a40035b..5911667 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt @@ -66,7 +66,7 @@ class MyStoreActivity : AppCompatActivity() { binding.tvStoreName.text = store.storeName binding.tvStoreType.text = store.storeType - if (store.storeImage != null && store.storeImage.toString().isNotEmpty() && store.storeImage.toString() != "null") { + if (store.storeImage.toString().isNotEmpty() && store.storeImage.toString() != "null") { val imageUrl = "$BASE_URL${store.storeImage}" Log.d("MyStoreActivity", "Loading store image from: $imageUrl") @@ -78,6 +78,8 @@ class MyStoreActivity : AppCompatActivity() { } else { Log.d("MyStoreActivity", "No store image available") } + +// binding.tvBalance.text = String.format("Rp%,.0f", store.balance.toString()) } private fun setUpClickListeners() { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/DetailStoreProfileActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/DetailStoreProfileActivity.kt index 44a375a..2f06c74 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/DetailStoreProfileActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/DetailStoreProfileActivity.kt @@ -1,20 +1,30 @@ package com.alya.ecommerce_serang.ui.profile.mystore.profile import android.app.Activity +import android.app.AlertDialog import android.content.Intent +import android.net.Uri import android.os.Bundle -import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.TextView import android.widget.Toast import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import com.alya.ecommerce_serang.BuildConfig.BASE_URL import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.dto.Store +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.api.retrofit.ApiService import com.alya.ecommerce_serang.data.repository.MyStoreRepository import com.alya.ecommerce_serang.databinding.ActivityDetailStoreProfileBinding +import com.alya.ecommerce_serang.databinding.DialogStoreImageBinding import com.alya.ecommerce_serang.ui.profile.mystore.profile.address.DetailStoreAddressActivity import com.alya.ecommerce_serang.ui.profile.mystore.profile.payment_info.PaymentInfoActivity import com.alya.ecommerce_serang.ui.profile.mystore.profile.shipping_service.ShippingServiceActivity @@ -22,12 +32,26 @@ import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager import com.bumptech.glide.Glide +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File +import java.io.FileOutputStream import kotlin.getValue class DetailStoreProfileActivity : AppCompatActivity() { private lateinit var binding: ActivityDetailStoreProfileBinding private lateinit var apiService: ApiService private lateinit var sessionManager: SessionManager + private var currentStore: Store? = null + private var editMode = false + private var selectedImageUri: Uri? = null + private var selectedStoreTypeId: Int = -1 + + private var storeTypesLoaded: Boolean = false + private var currentStoreLoaded: Boolean = false + private var storeTypesList: List = listOf() private val viewModel: MyStoreViewModel by viewModels { BaseViewModelFactory { @@ -37,6 +61,13 @@ class DetailStoreProfileActivity : AppCompatActivity() { } } + private val imagePickerLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> + uri?.let { + selectedImageUri = it + Glide.with(this).load(it).into(binding.ivProfile) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityDetailStoreProfileBinding.inflate(layoutInflater) @@ -55,97 +86,232 @@ class DetailStoreProfileActivity : AppCompatActivity() { onBackPressedDispatcher.onBackPressed() } + viewModel.loadMyStore() + viewModel.fetchStoreTypes() + + viewModel.myStoreProfile.observe(this) { + currentStore = it + currentStoreLoaded = true + if (storeTypesLoaded) setupStoreTypeSpinner(storeTypesList) + updateUI(it) + } + + viewModel.storeTypes.observe(this) { + storeTypesList = it + storeTypesLoaded = true + if (currentStoreLoaded) setupStoreTypeSpinner(storeTypesList) + } + + binding.ivProfile.setOnClickListener { + if (editMode) showImageOptions() + } + binding.btnEditStoreProfile.setOnClickListener { - val intent = Intent(this, EditStoreProfileActivity::class.java) - startActivityForResult(intent, EDIT_PROFILE_REQUEST_CODE) + if (editMode) { + if (hasChanges()) confirmUpdate() else exitEditMode() + } else { + enterEditMode() + } +// val intent = Intent(this, EditStoreProfileActivity::class.java) +// startActivityForResult(intent, EDIT_PROFILE_REQUEST_CODE) } binding.layoutAddress.setOnClickListener { - val intent = Intent(this, DetailStoreAddressActivity::class.java) - startActivityForResult(intent, ADDRESS_REQUEST_CODE) + startActivityForResult(Intent(this, DetailStoreAddressActivity::class.java), 101) } - // Set up payment method layout click listener binding.layoutPaymentMethod.setOnClickListener { - val intent = Intent(this, PaymentInfoActivity::class.java) - startActivityForResult(intent, PAYMENT_INFO_REQUEST_CODE) + startActivityForResult(Intent(this, PaymentInfoActivity::class.java), 102) } - // Set up shipping services layout click listener binding.layoutShipServices.setOnClickListener { - val intent = Intent(this, ShippingServiceActivity::class.java) - startActivityForResult(intent, SHIPPING_SERVICES_REQUEST_CODE) + startActivityForResult(Intent(this, ShippingServiceActivity::class.java), 103) } - viewModel.loadMyStore() + observeViewModel() + } - viewModel.myStoreProfile.observe(this){ user -> - user?.let { updateStoreProfile(it) } + private fun setupStoreTypeSpinner(storeTypes: List) { + val adapter = object : ArrayAdapter( + this, + android.R.layout.simple_spinner_item, + storeTypes + ) { + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = super.getView(position, convertView, parent) + (view as TextView).text = getItem(position)?.name ?: "" + return view + } + + override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = super.getDropDownView(position, convertView, parent) + (view as TextView).text = getItem(position)?.name ?: "" + return view + } } - viewModel.errorMessage.observe(this) { error -> - Toast.makeText(this, error, Toast.LENGTH_SHORT).show() + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding.spinnerJenisToko.adapter = adapter + + currentStore?.storeTypeId?.let { typeId -> + val index = storeTypes.indexOfFirst { it.id == typeId } + if (index >= 0) binding.spinnerJenisToko.setSelection(index) + } + + binding.spinnerJenisToko.isEnabled = editMode + binding.spinnerJenisToko.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, id: Long) { + val selected = adapter.getItem(pos) + selected?.let { + selectedStoreTypeId = it.id + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == EDIT_PROFILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) { - // Refresh the profile data + private fun observeViewModel() { + viewModel.updateStoreProfileResult.observe(this) { Toast.makeText(this, "Profil toko berhasil diperbarui", Toast.LENGTH_SHORT).show() viewModel.loadMyStore() + exitEditMode() + } - // Pass the result back to parent activity - setResult(Activity.RESULT_OK) - } else if (requestCode == ADDRESS_REQUEST_CODE && resultCode == Activity.RESULT_OK) { - // Refresh the profile data after address update - Toast.makeText(this, "Alamat toko berhasil diperbarui", Toast.LENGTH_SHORT).show() - viewModel.loadMyStore() - - // Pass the result back to parent activity - setResult(Activity.RESULT_OK) - } else if (requestCode == PAYMENT_INFO_REQUEST_CODE && resultCode == Activity.RESULT_OK) { - // Refresh the profile data after payment method update - Toast.makeText(this, "Metode pembayaran berhasil diperbarui", Toast.LENGTH_SHORT).show() - viewModel.loadMyStore() - - // Pass the result back to parent activity - setResult(Activity.RESULT_OK) - } else if (requestCode == SHIPPING_SERVICES_REQUEST_CODE && resultCode == Activity.RESULT_OK) { - // Refresh the profile data after shipping services update - Toast.makeText(this, "Layanan pengiriman berhasil diperbarui", Toast.LENGTH_SHORT).show() - viewModel.loadMyStore() - - // Pass the result back to parent activity - setResult(Activity.RESULT_OK) + viewModel.errorMessage.observe(this) { + Toast.makeText(this, it, Toast.LENGTH_SHORT).show() } } - companion object { - private const val EDIT_PROFILE_REQUEST_CODE = 100 - private const val ADDRESS_REQUEST_CODE = 101 - private const val PAYMENT_INFO_REQUEST_CODE = 102 - private const val SHIPPING_SERVICES_REQUEST_CODE = 103 - } - - private fun updateStoreProfile(store: Store){ - // Update text fields - binding.edtNamaToko.setText(store.storeName.toString()) - binding.edtJenisToko.setText(store.storeType.toString()) - binding.edtDeskripsiToko.setText(store.storeDescription.toString()) - - // Update store image if available - if (store.storeImage != null && store.storeImage.toString().isNotEmpty() && store.storeImage.toString() != "null") { - val imageUrl = "$BASE_URL${store.storeImage}" - Log.d("DetailStoreProfile", "Loading image from: $imageUrl") + private fun updateUI(store: Store?) { + store.let { + binding.edtNamaToko.setText(it?.storeName) + binding.edtDeskripsiToko.setText(it?.storeDescription) + binding.switchIsActive.isChecked = it?.isOnLeave == true + val imageUrl = when { + it?.storeImage.toString().isBlank() -> null + it?.storeImage.toString().startsWith("http") -> it?.storeImage + it?.storeImage.toString().startsWith("/") -> BASE_URL + it?.storeImage.toString().removePrefix("/") + else -> BASE_URL + it?.storeImage + } Glide.with(this) .load(imageUrl) .placeholder(R.drawable.placeholder_image) .error(R.drawable.placeholder_image) .into(binding.ivProfile) - } else { - Log.d("DetailStoreProfile", "No store image available") + + setFieldsEnabled(false) } } + + private fun setFieldsEnabled(enabled: Boolean) { + binding.edtNamaToko.isEnabled = enabled + binding.edtNamaToko.setBackgroundResource(R.drawable.bg_text_field) + + binding.spinnerJenisToko.isEnabled = enabled + binding.layoutJenisToko.setBackgroundResource(R.drawable.bg_text_field) + + binding.edtDeskripsiToko.isEnabled = enabled + binding.edtDeskripsiToko.setBackgroundResource(R.drawable.bg_text_field) + + binding.switchIsActive.isEnabled = enabled + } + + private fun enterEditMode() { + editMode = true + setFieldsEnabled(true) + binding.btnEditStoreProfile.text = "Simpan Perubahan" + binding.btnEditStoreProfile.setBackgroundResource(R.drawable.bg_button_active) + binding.btnEditStoreProfile.setTextColor(getColor(R.color.white)) + } + + private fun exitEditMode() { + editMode = false + setFieldsEnabled(false) + binding.btnEditStoreProfile.text = "Ubah Profil" + binding.btnEditStoreProfile.setBackgroundResource(R.drawable.bg_button_secondary) + binding.btnEditStoreProfile.setTextColor(getColor(R.color.blue_500)) + } + + private fun hasChanges(): Boolean { + val nameChanged = binding.edtNamaToko.text.toString() != currentStore?.storeName + val descChanged = binding.edtDeskripsiToko.text.toString() != currentStore?.storeDescription + val isOnLeaveChanged = (binding.switchIsActive.isChecked && currentStore?.isOnLeave != false) + || (!binding.switchIsActive.isChecked && currentStore?.isOnLeave == false) + val imageChanged = selectedImageUri != null + val storeTypeChanged = selectedStoreTypeId != currentStore?.storeTypeId + return nameChanged || descChanged || isOnLeaveChanged || imageChanged || storeTypeChanged + } + + private fun confirmUpdate() { + AlertDialog.Builder(this) + .setTitle("Konfirmasi Perubahan") + .setMessage("Apakah Anda yakin ingin menyimpan perubahan profil toko Anda?") + .setPositiveButton("Ya") { _, _ -> updateStoreProfile() } + .setNegativeButton("Batal", null) + .show() + } + + private fun showImageOptions() { + val options = arrayOf("Lihat Foto", "Ganti Foto") + AlertDialog.Builder(this) + .setItems(options) { _, which -> + when (which) { + 0 -> showImagePreviewDialog() + 1 -> imagePickerLauncher.launch("image/*") + } + } + .show() + } + + private fun showImagePreviewDialog() { + val dialogBinding = DialogStoreImageBinding.inflate(LayoutInflater.from(this)) + val imageUrl = when { + selectedImageUri != null -> selectedImageUri.toString() + currentStore?.storeImage.toString().isBlank() -> null + currentStore?.storeImage.toString().startsWith("http") == true -> currentStore?.storeImage + currentStore?.storeImage.toString().startsWith("/") == true -> BASE_URL + currentStore?.storeImage!!.toString().removePrefix("/") + else -> BASE_URL + currentStore?.storeImage + } + Glide.with(this) + .load(imageUrl) + .placeholder(R.drawable.placeholder_image) + .error(R.drawable.placeholder_image) + .into(dialogBinding.ivPreviewStoreImg) + + AlertDialog.Builder(this) + .setView(dialogBinding.root) + .setPositiveButton("Tutup", null) + .show() + } + + private fun updateStoreProfile() { + val storeName = binding.edtNamaToko.text.toString().toRequestBody() + val storeType = selectedStoreTypeId.toString().toRequestBody() + val description = binding.edtDeskripsiToko.text.toString().toRequestBody() + val isOnLeave = binding.switchIsActive.isChecked.toString().toRequestBody() + + val imagePart = selectedImageUri?.let { + val file = uriToNamedFile(it, this, "storeimg") + MultipartBody.Part.createFormData( + "storeimg", file.name, file.asRequestBody( + contentResolver.getType(it)?.toMediaTypeOrNull() + ) + ) + } + + viewModel.updateStoreProfile(storeName, storeType, description, isOnLeave, imagePart) + } + + private fun uriToNamedFile(uri: Uri, context: Activity, prefix: String): File { + val extension = contentResolver.getType(uri)?.substringAfter("/") ?: "jpg" + val filename = "$prefix-${System.currentTimeMillis()}.$extension" + val file = File(context.cacheDir, filename) + contentResolver.openInputStream(uri)?.use { input -> FileOutputStream(file).use { output -> input.copyTo(output) } } + return file + } + + private fun String.toRequestBody(): RequestBody = + RequestBody.create("text/plain".toMediaTypeOrNull(), this) } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/EditStoreProfileActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/EditStoreProfileActivity.kt deleted file mode 100644 index 2d31f3b..0000000 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/EditStoreProfileActivity.kt +++ /dev/null @@ -1,304 +0,0 @@ -package com.alya.ecommerce_serang.ui.profile.mystore.profile - -import android.app.Activity -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.provider.MediaStore -import android.util.Log -import android.view.View -import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.lifecycleScope -import com.alya.ecommerce_serang.R -import com.alya.ecommerce_serang.data.api.dto.Store -import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig -import com.alya.ecommerce_serang.databinding.ActivityEditStoreProfileBinding -import com.alya.ecommerce_serang.utils.SessionManager -import com.bumptech.glide.Glide -import kotlinx.coroutines.launch -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.MultipartBody -import okhttp3.RequestBody.Companion.asRequestBody -import okhttp3.RequestBody.Companion.toRequestBody -import java.io.File -import java.io.FileOutputStream - -class EditStoreProfileActivity : AppCompatActivity() { - private lateinit var binding: ActivityEditStoreProfileBinding - private lateinit var sessionManager: SessionManager - private var storeImageUri: Uri? = null - private lateinit var currentStore: Store - - private val pickImage = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { - result.data?.data?.let { uri -> - storeImageUri = uri - Log.d("EditStoreProfile", "Image selected: $uri") - - // Set the image to the ImageView for immediate preview - try { - binding.ivStoreImage.setImageURI(null) // Clear any previous image - binding.ivStoreImage.setImageURI(uri) - - // Alternative way using Glide for more reliable preview - Glide.with(this) - .load(uri) - .placeholder(R.drawable.placeholder_image) - .error(R.drawable.placeholder_image) - .into(binding.ivStoreImage) - - Toast.makeText(this, "Gambar berhasil dipilih", Toast.LENGTH_SHORT).show() - } catch (e: Exception) { - Log.e("EditStoreProfile", "Error displaying image preview", e) - Toast.makeText(this, "Gagal menampilkan preview gambar", Toast.LENGTH_SHORT).show() - } - } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityEditStoreProfileBinding.inflate(layoutInflater) - setContentView(binding.root) - - sessionManager = SessionManager(this) - - // Set up header - binding.header.headerTitle.text = "Edit Profil Toko" - binding.header.headerLeftIcon.setOnClickListener { finish() } - - loadStoreData() - setupClickListeners() - } - - private fun loadStoreData() { - binding.progressBar.visibility = View.VISIBLE - lifecycleScope.launch { - try { - val response = ApiConfig.getApiService(sessionManager).getStore() - binding.progressBar.visibility = View.GONE - - if (response.isSuccessful && response.body() != null) { - currentStore = response.body()!!.store - populateFields(currentStore) - } else { - showError("Gagal memuat profil toko") - } - } catch (e: Exception) { - binding.progressBar.visibility = View.GONE - showError("Terjadi kesalahan: ${e.message}") - } - } - } - - private fun populateFields(store: Store) { - // Load store image - if (store.storeImage != null && store.storeImage.toString().isNotEmpty() && store.storeImage.toString() != "null") { - Glide.with(this) - .load(store.storeImage.toString()) - .placeholder(R.drawable.placeholder_image) - .error(R.drawable.placeholder_image) - .into(binding.ivStoreImage) - } - - // Set other fields - binding.edtStoreName.setText(store.storeName) - binding.edtDescription.setText(store.storeDescription) - binding.edtUserPhone.setText(store.userPhone) - - // Set is on leave - binding.switchIsOnLeave.isChecked = store.isOnLeave - } - - private fun setupClickListeners() { - binding.btnSelectStoreImage.setOnClickListener { - val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) - pickImage.launch(intent) - } - - binding.btnSave.setOnClickListener { - saveStoreProfile() - } - } - - private fun saveStoreProfile() { - val storeName = binding.edtStoreName.text.toString() - val storeDescription = binding.edtDescription.text.toString() - val userPhone = binding.edtUserPhone.text.toString() - val storeStatus = currentStore.storeStatus // Keep the current status - val isOnLeave = binding.switchIsOnLeave.isChecked - - if (storeName.isEmpty() || userPhone.isEmpty()) { - showError("Nama toko dan nomor telepon harus diisi") - return - } - - binding.progressBar.visibility = View.VISIBLE - binding.btnSave.isEnabled = false - - // Show progress indicator on the image if we're uploading one - if (storeImageUri != null) { - binding.progressImage.visibility = View.VISIBLE - } - - lifecycleScope.launch { - try { - Log.d("EditStoreProfile", "Starting profile update process") - - // Create multipart request for image if selected - var storeImagePart: MultipartBody.Part? = null - if (storeImageUri != null) { - try { - val storeImageFile = uriToFile(storeImageUri!!) - Log.d("EditStoreProfile", "Image file created: ${storeImageFile.name}, size: ${storeImageFile.length()}") - - // Get the MIME type - val mimeType = contentResolver.getType(storeImageUri!!) ?: "image/jpeg" - Log.d("EditStoreProfile", "MIME type: $mimeType") - - val storeImageRequestBody = storeImageFile.asRequestBody(mimeType.toMediaTypeOrNull()) - storeImagePart = MultipartBody.Part.createFormData("storeimg", storeImageFile.name, storeImageRequestBody) - Log.d("EditStoreProfile", "Image part created with name: storeimg, filename: ${storeImageFile.name}") - } catch (e: Exception) { - Log.e("EditStoreProfile", "Error creating image part", e) - runOnUiThread { - Toast.makeText(this@EditStoreProfileActivity, "Error preparing image: ${e.message}", Toast.LENGTH_SHORT).show() - } - } - } - - // Create text parts - val nameRequestBody = storeName.toRequestBody("text/plain".toMediaTypeOrNull()) - val descriptionRequestBody = storeDescription.toRequestBody("text/plain".toMediaTypeOrNull()) - val userPhoneRequestBody = userPhone.toRequestBody("text/plain".toMediaTypeOrNull()) - val statusRequestBody = storeStatus.toRequestBody("text/plain".toMediaTypeOrNull()) - val onLeaveRequestBody = isOnLeave.toString().toRequestBody("text/plain".toMediaTypeOrNull()) - - // Log request parameters - Log.d("EditStoreProfile", "Request parameters: " + - "\nstore_name: $storeName" + - "\nstore_status: $storeStatus" + - "\nstore_description: $storeDescription" + - "\nis_on_leave: $isOnLeave" + - "\nuser_phone: $userPhone" + - "\nimage: ${storeImageUri != null}") - - // Log all parts for debugging - Log.d("EditStoreProfile", "Request parts:" + - "\nstoreName: $nameRequestBody" + - "\nstoreStatus: $statusRequestBody" + - "\nstoreDescription: $descriptionRequestBody" + - "\nisOnLeave: $onLeaveRequestBody" + - "\nuserPhone: $userPhoneRequestBody" + - "\nstoreimg: ${storeImagePart != null}") - - val response = ApiConfig.getApiService(sessionManager).updateStoreProfileMultipart( - storeName = nameRequestBody, - storeStatus = statusRequestBody, - storeDescription = descriptionRequestBody, - isOnLeave = onLeaveRequestBody, - cityId = currentStore.cityId.toString().toRequestBody("text/plain".toMediaTypeOrNull()), - provinceId = currentStore.provinceId.toString().toRequestBody("text/plain".toMediaTypeOrNull()), - street = currentStore.street.toRequestBody("text/plain".toMediaTypeOrNull()), - subdistrict = currentStore.subdistrict.toRequestBody("text/plain".toMediaTypeOrNull()), - detail = currentStore.detail.toRequestBody("text/plain".toMediaTypeOrNull()), - postalCode = currentStore.postalCode.toRequestBody("text/plain".toMediaTypeOrNull()), - latitude = currentStore.latitude.toRequestBody("text/plain".toMediaTypeOrNull()), - longitude = currentStore.longitude.toRequestBody("text/plain".toMediaTypeOrNull()), - userPhone = userPhoneRequestBody, - storeimg = storeImagePart - ) - - Log.d("EditStoreProfile", "Response received: isSuccessful=${response.isSuccessful}, code=${response.code()}") - - runOnUiThread { - binding.progressBar.visibility = View.GONE - binding.progressImage.visibility = View.GONE - binding.btnSave.isEnabled = true - - if (response.isSuccessful) { - Log.d("EditStoreProfile", "Response body: ${response.body()?.toString()}") - // Try to log the updated store image URL - response.body()?.let { responseBody -> - val updatedStoreImage = responseBody.store?.storeImage - Log.d("EditStoreProfile", "Updated store image URL: $updatedStoreImage") - } - showSuccess("Profil toko berhasil diperbarui") - setResult(Activity.RESULT_OK) - finish() - } else { - val errorBodyString = response.errorBody()?.string() ?: "Error body is null" - Log.e("EditStoreProfile", "Full error response: $errorBodyString") - Log.e("EditStoreProfile", "Response headers: ${response.headers()}") - showError("Gagal memperbarui profil toko (${response.code()})") - } - } - } catch (e: Exception) { - Log.e("EditStoreProfile", "Exception during API call", e) - - runOnUiThread { - binding.progressBar.visibility = View.GONE - binding.progressImage.visibility = View.GONE - binding.btnSave.isEnabled = true - showError("Error: ${e.message}") - } - } - } - } - - private fun uriToFile(uri: Uri): File { - val contentResolver = applicationContext.contentResolver - val fileExtension = getFileExtension(contentResolver, uri) - val timeStamp = System.currentTimeMillis() - val fileName = "IMG_${timeStamp}.$fileExtension" - val tempFile = File(cacheDir, fileName) - - Log.d("EditStoreProfile", "Creating temp file: ${tempFile.absolutePath}") - - try { - contentResolver.openInputStream(uri)?.use { inputStream -> - FileOutputStream(tempFile).use { outputStream -> - val buffer = ByteArray(4 * 1024) // 4k buffer - var bytesRead: Int - while (inputStream.read(buffer).also { bytesRead = it } != -1) { - outputStream.write(buffer, 0, bytesRead) - } - outputStream.flush() - } - } - - Log.d("EditStoreProfile", "File created successfully: ${tempFile.name}, size: ${tempFile.length()} bytes") - return tempFile - } catch (e: Exception) { - Log.e("EditStoreProfile", "Error creating file from URI", e) - throw e - } - } - - private fun getFileExtension(contentResolver: android.content.ContentResolver, uri: Uri): String { - val mimeType = contentResolver.getType(uri) - return if (mimeType != null) { - val mime = android.webkit.MimeTypeMap.getSingleton() - mime.getExtensionFromMimeType(mimeType) ?: "jpg" - } else { - // If mime type is null, try to get from URI path - val path = uri.path - if (path != null) { - val extension = android.webkit.MimeTypeMap.getFileExtensionFromUrl(path) - if (!extension.isNullOrEmpty()) { - extension - } else "jpg" - } else "jpg" - } - } - - private fun showSuccess(message: String) { - Toast.makeText(this, message, Toast.LENGTH_LONG).show() - } - - private fun showError(message: String) { - Toast.makeText(this, message, Toast.LENGTH_LONG).show() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/MyStoreViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/MyStoreViewModel.kt index 9ca5b34..5dcb052 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/MyStoreViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/MyStoreViewModel.kt @@ -5,24 +5,88 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.alya.ecommerce_serang.data.api.dto.Store +import com.alya.ecommerce_serang.data.api.response.auth.StoreTypesItem +import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse import com.alya.ecommerce_serang.data.repository.MyStoreRepository import com.alya.ecommerce_serang.data.repository.Result import kotlinx.coroutines.launch +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody -class MyStoreViewModel(private val myStoreRepository: MyStoreRepository): ViewModel() { +class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() { private val _myStoreProfile = MutableLiveData() val myStoreProfile: LiveData = _myStoreProfile + private val _storeTypes = MutableLiveData>() + val storeTypes: LiveData> = _storeTypes + + private val _isLoadingType = MutableLiveData() + val isLoadingType: LiveData = _isLoadingType + + private val _updateStoreProfileResult = MutableLiveData() + val updateStoreProfileResult: LiveData = _updateStoreProfileResult + private val _errorMessage = MutableLiveData() val errorMessage : LiveData = _errorMessage fun loadMyStore(){ viewModelScope.launch { - when (val result = myStoreRepository.fetchMyStoreProfile()){ + when (val result = repository.fetchMyStoreProfile()){ is Result.Success -> _myStoreProfile.postValue(result.data) is Result.Error -> _errorMessage.postValue(result.exception.message ?: "Unknown Error") is Result.Loading -> null } } } + + fun fetchStoreTypes() { + _isLoadingType.value = true + viewModelScope.launch { + when (val result = repository.listStoreType()) { + is Result.Success -> { + _storeTypes.value = result.data.storeTypes + _isLoadingType.value = false + } + is Result.Error -> { + _errorMessage.value = result.exception.message ?: "Unknown error occurred" + _isLoadingType.value = false + } + is Result.Loading -> { + _isLoadingType.value = true + } + } + } + } + + fun updateStoreProfile( + storeName: RequestBody, + storeType: RequestBody, + description: RequestBody, + isOnLeave: RequestBody, + storeImage: MultipartBody.Part? + ) { + viewModelScope.launch { + try { + val response = repository.updateStoreProfile( + storeName, + "active".toRequestBody(), + description, + isOnLeave, + 0.toString().toRequestBody(), + 0.toString().toRequestBody(), + "".toRequestBody(), "".toRequestBody(), "".toRequestBody(), "".toRequestBody(), + "".toRequestBody(), "".toRequestBody(), "".toRequestBody(), + storeType, storeImage + ) + if (response.isSuccessful) _updateStoreProfileResult.postValue(response.body()) + else _errorMessage.postValue("Gagal memperbarui profil") + } catch (e: Exception) { + _errorMessage.postValue(e.message ?: "Unexpected error") + } + } + } + + private fun String.toRequestBody(): RequestBody = + RequestBody.create("text/plain".toMediaTypeOrNull(), this) } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_detail_store_profile.xml b/app/src/main/res/layout/activity_detail_store_profile.xml index a32534f..d88cebf 100644 --- a/app/src/main/res/layout/activity_detail_store_profile.xml +++ b/app/src/main/res/layout/activity_detail_store_profile.xml @@ -2,6 +2,7 @@ - + + android:gravity="center_vertical" + android:layout_marginTop="10dp"> + + + + + + + @@ -160,7 +180,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:text="Toko Aktif" + android:text="Toko Libur" style="@style/label_large"/> + android:text="Pesanan Masuk"/> diff --git a/app/src/main/res/layout/dialog_store_image.xml b/app/src/main/res/layout/dialog_store_image.xml new file mode 100644 index 0000000..ba9a4e7 --- /dev/null +++ b/app/src/main/res/layout/dialog_store_image.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file