Merge pull request #26

gracia
This commit is contained in:
Gracia Hotmauli
2025-05-26 04:42:08 +07:00
committed by GitHub
10 changed files with 392 additions and 389 deletions

View File

@ -47,18 +47,12 @@
<activity
android:name=".ui.profile.mystore.sells.shipment.ShipmentConfirmationActivity"
android:exported="false" />
<activity
android:name=".ui.profile.mystore.profile.EditStoreProfileActivity"
android:exported="false" />
<activity
android:name=".ui.profile.mystore.sells.shipment.DetailShipmentActivity"
android:exported="false" />
<activity
android:name=".ui.profile.mystore.sells.payment.DetailPaymentActivity"
android:exported="false" />
<activity
android:name=".ui.profile.mystore.sells.order.DetailOrderActivity"
android:exported="false" />
<activity
android:name=".ui.chat.ChatActivity"
android:exported="false" /> <!-- <provider -->

View File

@ -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<StoreDataResponse>

View File

@ -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<ListStoreTypeResponse>{
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<StoreDataResponse> {
return apiService.updateStoreProfileMultipart(
storeName, storeStatus, storeDescription, isOnLeave, cityId, provinceId,
street, subdistrict, detail, postalCode, latitude, longitude, userPhone, storeType, storeimg
)
}
}

View File

@ -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() {

View File

@ -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<StoreTypesItem> = 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<StoreTypesItem>) {
val adapter = object : ArrayAdapter<StoreTypesItem>(
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)
}

View File

@ -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()
}
}

View File

@ -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<Store?>()
val myStoreProfile: LiveData<Store?> = _myStoreProfile
private val _storeTypes = MutableLiveData<List<StoreTypesItem>>()
val storeTypes: LiveData<List<StoreTypesItem>> = _storeTypes
private val _isLoadingType = MutableLiveData<Boolean>()
val isLoadingType: LiveData<Boolean> = _isLoadingType
private val _updateStoreProfileResult = MutableLiveData<StoreDataResponse>()
val updateStoreProfileResult: LiveData<StoreDataResponse> = _updateStoreProfileResult
private val _errorMessage = MutableLiveData<String>()
val errorMessage : LiveData<String> = _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)
}

View File

@ -2,6 +2,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:fitsSystemWindows="true"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -104,15 +105,34 @@
style="@style/body_medium"
android:layout_marginEnd="4dp"/>
<EditText
android:id="@+id/edt_jenis_toko"
<!-- Spinner Dropdown dengan Chevron -->
<LinearLayout
android:id="@+id/layout_jenis_toko"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/bg_text_field_disabled"
android:padding="8dp"
style="@style/body_small"
android:layout_marginTop="4dp"
android:enabled="false"/>
android:gravity="center_vertical"
android:layout_marginTop="10dp">
<Spinner
android:id="@+id/spinner_jenis_toko"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="8dp"
style="@style/body_small"
android:background="@null" />
<!-- Chevron Down Icon -->
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_down"
android:layout_marginEnd="8dp"
android:contentDescription="Chevron Down" />
</LinearLayout>
</LinearLayout>
@ -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"/>
<Switch

View File

@ -198,7 +198,7 @@
<TextView
style="@style/headline_small"
android:id="@+id/tv_num_perlu_tagihan"
android:id="@+id/tv_num_pesanan_masuk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
@ -206,10 +206,10 @@
<TextView
style="@style/label_small"
android:id="@+id/tv_perlu_tagihan"
android:id="@+id/tv_pesanan_masuk"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Perlu Tagihan"/>
android:text="Pesanan Masuk"/>
</LinearLayout>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/iv_preview_store_img"
android:layout_width="300dp"
android:layout_height="300dp"
android:scaleType="fitCenter"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>