diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 20052e6..fa26332 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,6 +29,9 @@
android:theme="@style/Theme.Ecommerce_serang"
android:usesCleartextTraffic="true"
tools:targetApi="31">
+
@@ -40,8 +43,7 @@
android:exported="false" />
-
+ android:exported="false" />
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/City.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/City.kt
new file mode 100644
index 0000000..f8160a2
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/City.kt
@@ -0,0 +1,31 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class City(
+ @SerializedName("city_id")
+ val cityId: String,
+
+ @SerializedName("city_name")
+ val cityName: String,
+
+ @SerializedName("province_id")
+ val provinceId: String,
+
+ @SerializedName("province")
+ val provinceName: String,
+
+ @SerializedName("type")
+ val type: String,
+
+ @SerializedName("postal_code")
+ val postalCode: String
+)
+
+data class CityResponse(
+ @SerializedName("message")
+ val message: String,
+
+ @SerializedName("cities")
+ val data: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Province.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Province.kt
new file mode 100644
index 0000000..056d517
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/Province.kt
@@ -0,0 +1,19 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class Province(
+ @SerializedName("province_id")
+ val provinceId: String,
+
+ @SerializedName("province")
+ val provinceName: String
+)
+
+data class ProvinceResponse(
+ @SerializedName("message")
+ val message: String,
+
+ @SerializedName("provinces")
+ val data: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/StoreAddress.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/StoreAddress.kt
new file mode 100644
index 0000000..f403fc2
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/dto/StoreAddress.kt
@@ -0,0 +1,55 @@
+package com.alya.ecommerce_serang.data.api.dto
+
+import com.google.gson.annotations.SerializedName
+
+data class StoreAddress(
+ @SerializedName("id")
+ val id: String? = null,
+
+ @SerializedName("store_id")
+ val storeId: String? = null,
+
+ @SerializedName("province_id")
+ val provinceId: String = "",
+
+ @SerializedName("province_name")
+ val provinceName: String = "",
+
+ @SerializedName("city_id")
+ val cityId: String = "",
+
+ @SerializedName("city_name")
+ val cityName: String = "",
+
+ @SerializedName("street")
+ val street: String = "",
+
+ @SerializedName("subdistrict")
+ val subdistrict: String = "",
+
+ @SerializedName("detail")
+ val detail: String? = null,
+
+ @SerializedName("postal_code")
+ val postalCode: String = "",
+
+ @SerializedName("latitude")
+ val latitude: Double? = 0.0,
+
+ @SerializedName("longitude")
+ val longitude: Double? = 0.0,
+
+ @SerializedName("created_at")
+ val createdAt: String? = null,
+
+ @SerializedName("updated_at")
+ val updatedAt: String? = null
+)
+
+data class StoreAddressResponse(
+ @SerializedName("message")
+ val message: String,
+
+ @SerializedName("store")
+ val data: StoreAddress? = null
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/profile/StoreDataResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/profile/StoreDataResponse.kt
new file mode 100644
index 0000000..711e8be
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/profile/StoreDataResponse.kt
@@ -0,0 +1,55 @@
+package com.alya.ecommerce_serang.data.api.response.store.profile
+
+import com.google.gson.annotations.SerializedName
+
+data class StoreDataResponse(
+ val message: String,
+ val store: Store,
+ val shipping: List,
+ val payment: List
+)
+
+data class Store(
+ @SerializedName("store_id") val storeId: Int,
+ @SerializedName("store_status") val storeStatus: String,
+ @SerializedName("store_name") val storeName: String,
+ @SerializedName("user_name") val userName: String,
+ val email: String,
+ @SerializedName("user_phone") val userPhone: String,
+ val balance: String,
+ val ktp: String,
+ val npwp: String,
+ val nib: String,
+ val persetujuan: String?,
+ @SerializedName("store_image") val storeImage: String,
+ @SerializedName("store_description") val storeDescription: String,
+ @SerializedName("is_on_leave") val isOnLeave: Boolean,
+ @SerializedName("store_type_id") val storeTypeId: Int,
+ @SerializedName("store_type") val storeType: String,
+ val id: Int,
+ val latitude: String,
+ val longitude: String,
+ val street: String,
+ val subdistrict: String,
+ @SerializedName("postal_code") val postalCode: String,
+ val detail: String,
+ @SerializedName("is_store_location") val isStoreLocation: Boolean,
+ @SerializedName("user_id") val userId: Int,
+ @SerializedName("city_id") val cityId: Int,
+ @SerializedName("province_id") val provinceId: Int,
+ val phone: String?,
+ val recipient: String?,
+ @SerializedName("approval_status") val approvalStatus: String,
+ @SerializedName("approval_reason") val approvalReason: String?
+)
+
+data class Shipping(
+ val courier: String
+)
+
+data class Payment(
+ val id: Int,
+ @SerializedName("bank_num") val bankNum: String,
+ @SerializedName("bank_name") val bankName: String,
+ @SerializedName("qris_image") val qrisImage: String
+)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/topup/BalanceTopUpResponse.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/topup/BalanceTopUpResponse.kt
new file mode 100644
index 0000000..a6965ce
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/response/store/topup/BalanceTopUpResponse.kt
@@ -0,0 +1,6 @@
+package com.alya.ecommerce_serang.data.api.response.store.topup
+
+data class BalanceTopUpResponse(
+ val success: Boolean,
+ val message: String
+)
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 62a4ab0..b86b350 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
@@ -3,6 +3,7 @@ package com.alya.ecommerce_serang.data.api.retrofit
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceRequest
import com.alya.ecommerce_serang.data.api.dto.CartItem
+import com.alya.ecommerce_serang.data.api.dto.CityResponse
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
@@ -10,8 +11,10 @@ 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.OrderRequestBuy
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
+import com.alya.ecommerce_serang.data.api.dto.ProvinceResponse
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.dto.SearchRequest
+import com.alya.ecommerce_serang.data.api.dto.StoreAddressResponse
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
@@ -48,6 +51,8 @@ import com.alya.ecommerce_serang.data.api.response.store.product.CreateProductRe
import com.alya.ecommerce_serang.data.api.response.store.product.DeleteProductResponse
import com.alya.ecommerce_serang.data.api.response.store.product.UpdateProductResponse
import com.alya.ecommerce_serang.data.api.response.store.product.ViewStoreProductsResponse
+import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse
+import com.alya.ecommerce_serang.data.api.response.store.topup.BalanceTopUpResponse
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.Call
@@ -147,6 +152,8 @@ interface ApiService {
@GET("mystore")
suspend fun getStore (): Response
+ suspend fun getStoreData(): Response
+ suspend fun getStoreAddress(): Response
@GET("mystore/product") // Replace with actual endpoint
suspend fun getStoreProduct(): Response
@@ -235,6 +242,54 @@ interface ApiService {
@Part complaintimg: MultipartBody.Part
): Response
+ @Multipart
+ @POST("store/createtopup")
+ suspend fun addBalanceTopUp(
+ @Part topupimg: MultipartBody.Part,
+ @Part("amount") amount: RequestBody,
+ @Part("payment_info_id") paymentInfoId: RequestBody,
+ @Part("transaction_date") transactionDate: RequestBody,
+ @Part("bank_name") bankName: RequestBody,
+ @Part("bank_num") bankNum: RequestBody
+ ): Response
+
+ @PUT("mystore/edit")
+ suspend fun updateStoreProfile(
+ @Body requestBody: okhttp3.RequestBody
+ ): Response
+
+ @Multipart
+ @PUT("mystore/edit")
+ suspend fun updateStoreProfileMultipart(
+ @Part("store_name") storeName: RequestBody,
+ @Part("store_status") storeStatus: RequestBody,
+ @Part("store_description") storeDescription: RequestBody,
+ @Part("is_on_leave") isOnLeave: RequestBody,
+ @Part("city_id") cityId: RequestBody,
+ @Part("province_id") provinceId: RequestBody,
+ @Part("street") street: RequestBody,
+ @Part("subdistrict") subdistrict: RequestBody,
+ @Part("detail") detail: RequestBody,
+ @Part("postal_code") postalCode: RequestBody,
+ @Part("latitude") latitude: RequestBody,
+ @Part("longitude") longitude: RequestBody,
+ @Part("user_phone") userPhone: RequestBody,
+ @Part storeimg: MultipartBody.Part?
+ ): Response
+
+ @GET("provinces")
+ suspend fun getProvinces(): Response
+
+ @GET("cities/{provinceId}")
+ suspend fun getCities(
+ @Path("provinceId") provinceId: String
+ ): Response
+
+ @PUT("mystore/edit")
+ suspend fun updateStoreAddress(
+ @Body addressData: HashMap
+ ): Response
+
@POST("search")
suspend fun saveSearchQuery(
@Body searchRequest: SearchRequest
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/AddressRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/AddressRepository.kt
new file mode 100644
index 0000000..8659ef7
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/AddressRepository.kt
@@ -0,0 +1,174 @@
+package com.alya.ecommerce_serang.data.repository
+
+import android.util.Log
+import com.alya.ecommerce_serang.data.api.dto.City
+import com.alya.ecommerce_serang.data.api.dto.Province
+import com.alya.ecommerce_serang.data.api.dto.StoreAddress
+import com.alya.ecommerce_serang.data.api.retrofit.ApiService
+import com.google.gson.Gson
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.json.JSONObject
+
+class AddressRepository(private val apiService: ApiService) {
+
+ private val TAG = "AddressRepository"
+
+ suspend fun getProvinces(): List = withContext(Dispatchers.IO) {
+ Log.d(TAG, "getProvinces() called")
+ try {
+ val response = apiService.getProvinces()
+ Log.d(TAG, "getProvinces() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
+
+ // Log the raw response body for debugging
+ val rawBody = response.raw().toString()
+ Log.d(TAG, "Raw response: $rawBody")
+
+ if (response.isSuccessful) {
+ val responseBody = response.body()
+ Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}")
+
+ val provinces = responseBody?.data ?: emptyList()
+ Log.d(TAG, "getProvinces() success, got ${provinces.size} provinces")
+ return@withContext provinces
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "getProvinces() error: $errorBody")
+ throw Exception("API Error (${response.code()}): $errorBody")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception in getProvinces()", e)
+ throw Exception("Network error: ${e.message}")
+ }
+ }
+
+ suspend fun getCities(provinceId: String): List = withContext(Dispatchers.IO) {
+ Log.d(TAG, "getCities() called with provinceId: $provinceId")
+ try {
+ val response = apiService.getCities(provinceId)
+ Log.d(TAG, "getCities() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
+
+ if (response.isSuccessful) {
+ val responseBody = response.body()
+ Log.d(TAG, "Response body: ${Gson().toJson(responseBody)}")
+
+ val cities = responseBody?.data ?: emptyList()
+ Log.d(TAG, "getCities() success, got ${cities.size} cities")
+ return@withContext cities
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "getCities() error: $errorBody")
+ throw Exception("API Error (${response.code()}): $errorBody")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception in getCities()", e)
+ throw Exception("Network error: ${e.message}")
+ }
+ }
+
+ suspend fun getStoreAddress(): StoreAddress? = withContext(Dispatchers.IO) {
+ Log.d(TAG, "getStoreAddress() called")
+ try {
+ val response = apiService.getStoreAddress()
+ Log.d(TAG, "getStoreAddress() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
+
+ if (response.isSuccessful) {
+ val responseBody = response.body()
+ val rawJson = Gson().toJson(responseBody)
+ Log.d(TAG, "Response body: $rawJson")
+
+ val address = responseBody?.data
+ Log.d(TAG, "getStoreAddress() success, address: $address")
+
+ // Convert numeric strings to proper types if needed
+ address?.let {
+ // Handle city_id if it's a number
+ if (it.cityId.isBlank() && rawJson.contains("city_id")) {
+ try {
+ val cityId = JSONObject(rawJson).getJSONObject("store").optInt("city_id", 0)
+ if (cityId > 0) {
+ it.javaClass.getDeclaredField("cityId").apply {
+ isAccessible = true
+ set(it, cityId.toString())
+ }
+ Log.d(TAG, "Updated cityId to: ${it.cityId}")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Error parsing city_id", e)
+ }
+ }
+
+ // Handle province_id if it's a number
+ if (it.provinceId.isBlank() && rawJson.contains("province_id")) {
+ try {
+ val provinceId = JSONObject(rawJson).getJSONObject("store").optInt("province_id", 0)
+ if (provinceId > 0) {
+ it.javaClass.getDeclaredField("provinceId").apply {
+ isAccessible = true
+ set(it, provinceId.toString())
+ }
+ Log.d(TAG, "Updated provinceId to: ${it.provinceId}")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Error parsing province_id", e)
+ }
+ }
+ }
+
+ return@withContext address
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "getStoreAddress() error: $errorBody")
+ throw Exception("API Error (${response.code()}): $errorBody")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception in getStoreAddress()", e)
+ throw Exception("Network error: ${e.message}")
+ }
+ }
+
+ suspend fun saveStoreAddress(
+ provinceId: String,
+ provinceName: String,
+ cityId: String,
+ cityName: String,
+ street: String,
+ subdistrict: String,
+ detail: String,
+ postalCode: String,
+ latitude: Double,
+ longitude: Double
+ ): Boolean = withContext(Dispatchers.IO) {
+ Log.d(TAG, "saveStoreAddress() called with provinceId: $provinceId, cityId: $cityId")
+
+ try {
+ val addressMap = HashMap()
+ addressMap["provinceId"] = provinceId
+ addressMap["provinceName"] = provinceName
+ addressMap["cityId"] = cityId
+ addressMap["cityName"] = cityName
+ addressMap["street"] = street
+ addressMap["subdistrict"] = subdistrict
+ addressMap["detail"] = detail
+ addressMap["postalCode"] = postalCode
+ addressMap["latitude"] = latitude
+ addressMap["longitude"] = longitude
+
+ Log.d(TAG, "saveStoreAddress() request data: $addressMap")
+ val response = apiService.updateStoreAddress(addressMap)
+ Log.d(TAG, "saveStoreAddress() response: isSuccessful=${response.isSuccessful}, code=${response.code()}")
+
+ if (response.isSuccessful) {
+ Log.d(TAG, "saveStoreAddress() success")
+ return@withContext true
+ } else {
+ val errorBody = response.errorBody()?.string() ?: "Unknown error"
+ Log.e(TAG, "saveStoreAddress() error: $errorBody")
+ throw Exception("API Error (${response.code()}): $errorBody")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception in saveStoreAddress()", e)
+ throw Exception("Network error: ${e.message}")
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceActivity.kt
index 4192943..24e9618 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceActivity.kt
@@ -1,21 +1,36 @@
package com.alya.ecommerce_serang.ui.profile.mystore.balance
+import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.databinding.ActivityBalanceBinding
class BalanceActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityBalanceBinding
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
- setContentView(R.layout.activity_balance)
+ binding = ActivityBalanceBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
+
+ setupListeners()
+ }
+
+ private fun setupListeners() {
+ binding.btnTopUp.setOnClickListener {
+ val intent = Intent(this, BalanceTopUpActivity::class.java)
+ startActivity(intent)
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTopUpActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTopUpActivity.kt
index 12c437a..5ef8b31 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTopUpActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceTopUpActivity.kt
@@ -1,21 +1,395 @@
package com.alya.ecommerce_serang.ui.profile.mystore.balance
+import android.app.DatePickerDialog
+import android.content.Intent
+import android.net.Uri
import android.os.Bundle
-import androidx.activity.enableEdgeToEdge
+import android.provider.MediaStore
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Button
+import android.widget.EditText
+import android.widget.ImageView
+import android.widget.Spinner
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.lifecycleScope
import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.data.api.response.store.profile.Payment
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.utils.SessionManager
+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.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Locale
class BalanceTopUpActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContentView(R.layout.activity_balance_top_up)
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
- val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
- v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
- insets
+ private lateinit var imgPreview: ImageView
+ private lateinit var addPhotoTextView: TextView
+ private lateinit var edtNominal: EditText
+ private lateinit var spinnerPaymentMethod: Spinner
+ private lateinit var edtTransactionDate: EditText
+ private lateinit var datePickerIcon: ImageView
+ private lateinit var btnSend: Button
+ private lateinit var sessionManager: SessionManager
+
+ private var selectedImageUri: Uri? = null
+ private var paymentMethods: List = emptyList()
+ private var selectedPaymentId: Int = -1
+
+ private val calendar = Calendar.getInstance()
+
+ private val getImageContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == RESULT_OK) {
+ val imageUri = result.data?.data
+ imageUri?.let {
+ selectedImageUri = it
+ imgPreview.setImageURI(it)
+ }
}
}
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_balance_top_up)
+
+ // Initialize session manager
+ sessionManager = SessionManager(this)
+
+ // Initialize views
+ imgPreview = findViewById(R.id.img_preview)
+ addPhotoTextView = findViewById(R.id.tv_tambah_foto)
+ edtNominal = findViewById(R.id.edt_nominal_topup)
+ spinnerPaymentMethod = findViewById(R.id.spinner_metode_bayar)
+ edtTransactionDate = findViewById(R.id.edt_tgl_transaksi)
+ datePickerIcon = findViewById(R.id.img_date_picker)
+ btnSend = findViewById(R.id.btn_send)
+
+ // Setup header title
+ val headerTitle = findViewById(R.id.header_title)
+ headerTitle.text = "Isi Ulang Saldo"
+
+ // Setup back button
+ val backButton = findViewById(R.id.header_left_icon)
+ backButton.setOnClickListener {
+ finish()
+ }
+
+ // Setup photo selection
+ addPhotoTextView.setOnClickListener {
+ openGallery()
+ }
+
+ imgPreview.setOnClickListener {
+ openGallery()
+ }
+
+ // Setup date picker
+ setupDatePicker()
+
+ // Fetch payment methods
+ fetchPaymentMethods()
+
+ // Setup submit button
+ btnSend.setOnClickListener {
+ submitForm()
+ }
+ }
+
+ private fun openGallery() {
+ val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
+ getImageContent.launch(intent)
+ }
+
+ private fun setupDatePicker() {
+ val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth ->
+ calendar.set(Calendar.YEAR, year)
+ calendar.set(Calendar.MONTH, month)
+ calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
+ updateDateInView()
+ }
+
+ edtTransactionDate.setOnClickListener {
+ showDatePicker(dateSetListener)
+ }
+
+ datePickerIcon.setOnClickListener {
+ showDatePicker(dateSetListener)
+ }
+ }
+
+ private fun showDatePicker(dateSetListener: DatePickerDialog.OnDateSetListener) {
+ DatePickerDialog(
+ this,
+ dateSetListener,
+ calendar.get(Calendar.YEAR),
+ calendar.get(Calendar.MONTH),
+ calendar.get(Calendar.DAY_OF_MONTH)
+ ).show()
+ }
+
+ private fun updateDateInView() {
+ val format = "yyyy-MM-dd"
+ val sdf = SimpleDateFormat(format, Locale.US)
+ edtTransactionDate.setText(sdf.format(calendar.time))
+ }
+
+ private fun fetchPaymentMethods() {
+ lifecycleScope.launch {
+ try {
+ val response = ApiConfig.getApiService(sessionManager).getStoreData()
+ if (response.isSuccessful && response.body() != null) {
+ val storeData = response.body()!!
+ paymentMethods = storeData.payment
+
+ setupPaymentMethodSpinner()
+ } else {
+ Toast.makeText(
+ this@BalanceTopUpActivity,
+ "Gagal memuat metode pembayaran",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ } catch (e: Exception) {
+ Toast.makeText(
+ this@BalanceTopUpActivity,
+ "Error: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+
+ private fun setupPaymentMethodSpinner() {
+ if (paymentMethods.isEmpty()) {
+ Toast.makeText(
+ this,
+ "Tidak ada metode pembayaran tersedia",
+ Toast.LENGTH_SHORT
+ ).show()
+ return
+ }
+
+ // Debug payment methods
+ for (payment in paymentMethods) {
+ android.util.Log.d("BalanceTopUp", "Payment Option - ID: ${payment.id}, Bank: ${payment.bankName}, Number: ${payment.bankNum}")
+ }
+
+ val paymentOptions = paymentMethods.map { "${it.bankName} - ${it.bankNum}" }.toTypedArray()
+ val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, paymentOptions)
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+
+ spinnerPaymentMethod.adapter = adapter
+ spinnerPaymentMethod.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ selectedPaymentId = paymentMethods[position].id
+ android.util.Log.d("BalanceTopUp", "Selected payment ID: $selectedPaymentId")
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {
+ selectedPaymentId = -1
+ }
+ }
+ }
+
+ private fun submitForm() {
+ // Prevent multiple clicks
+ if (!btnSend.isEnabled) {
+ return
+ }
+
+ // Validate inputs
+ if (selectedImageUri == null) {
+ Toast.makeText(this, "Mohon pilih foto bukti pembayaran", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ val nominal = edtNominal.text.toString().trim()
+ if (nominal.isEmpty()) {
+ Toast.makeText(this, "Mohon isi nominal top up", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ try {
+ // Validate the amount is a valid number
+ val amountValue = nominal.replace("[^0-9]".toRegex(), "").toLong()
+ if (amountValue <= 0) {
+ Toast.makeText(this, "Nominal harus lebih dari 0", Toast.LENGTH_SHORT).show()
+ return
+ }
+ } catch (e: NumberFormatException) {
+ Toast.makeText(this, "Format nominal tidak valid", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ if (selectedPaymentId == -1) {
+ Toast.makeText(this, "Mohon pilih metode pembayaran", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ val transactionDate = edtTransactionDate.text.toString().trim()
+ if (transactionDate.isEmpty()) {
+ Toast.makeText(this, "Mohon pilih tanggal transaksi", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ // Show progress indicator
+ btnSend.text = "Mengirim..."
+ btnSend.isEnabled = false
+
+ // Proceed with the API call
+ uploadTopUpData(nominal, selectedPaymentId.toString(), transactionDate)
+ }
+
+ private fun uploadTopUpData(amount: String, paymentInfoId: String, transactionDate: String) {
+ lifecycleScope.launch {
+ try {
+ // Log the values being sent
+ android.util.Log.d("BalanceTopUp", "Amount: $amount")
+ android.util.Log.d("BalanceTopUp", "Payment ID: $paymentInfoId")
+ android.util.Log.d("BalanceTopUp", "Transaction Date: $transactionDate")
+
+ // Find the selected payment method to get bank name
+ val selectedPayment = paymentMethods.find { it.id.toString() == paymentInfoId }
+ if (selectedPayment == null) {
+ Toast.makeText(
+ this@BalanceTopUpActivity,
+ "Metode pembayaran tidak valid",
+ Toast.LENGTH_SHORT
+ ).show()
+ return@launch
+ }
+
+ val bankName = selectedPayment.bankName
+ val bankNum = selectedPayment.bankNum
+ android.util.Log.d("BalanceTopUp", "Bank Name: $bankName")
+ android.util.Log.d("BalanceTopUp", "Bank Number: $bankNum")
+
+ // Get the actual file from URI
+ val file = uriToFile(selectedImageUri!!)
+ android.util.Log.d("BalanceTopUp", "File size: ${file.length()} bytes")
+ android.util.Log.d("BalanceTopUp", "File name: ${file.name}")
+
+ // Create multipart file with specific JPEG content type
+ val requestFile = file.asRequestBody("image/jpeg".toMediaTypeOrNull())
+ val imagePart = MultipartBody.Part.createFormData("topupimg", file.name, requestFile)
+
+ // Create other request bodies - ensure proper formatting
+ // Make sure amount has no commas, spaces or currency symbols
+ val cleanedAmount = amount.replace("[^0-9]".toRegex(), "")
+ val amountBody = cleanedAmount.toRequestBody("text/plain".toMediaTypeOrNull())
+ val paymentInfoIdBody = paymentInfoId.toRequestBody("text/plain".toMediaTypeOrNull())
+ val transactionDateBody = transactionDate.toRequestBody("text/plain".toMediaTypeOrNull())
+ val bankNameBody = bankName.toRequestBody("text/plain".toMediaTypeOrNull())
+ val bankNumBody = bankNum.toRequestBody("text/plain".toMediaTypeOrNull())
+
+ // Make the API call
+ val response = ApiConfig.getApiService(sessionManager).addBalanceTopUp(
+ imagePart,
+ amountBody,
+ paymentInfoIdBody,
+ transactionDateBody,
+ bankNameBody,
+ bankNumBody
+ )
+
+ if (response.isSuccessful) {
+ // Log the complete response
+ val responseBody = response.body()
+ android.util.Log.d("BalanceTopUp", "Success response: ${responseBody?.message}")
+
+ // Show the actual message from backend
+ val successMessage = "Top Up Berhasil"
+ Toast.makeText(
+ this@BalanceTopUpActivity,
+ successMessage,
+ Toast.LENGTH_LONG
+ ).show()
+
+ // Show a dialog with the success message
+ runOnUiThread {
+ androidx.appcompat.app.AlertDialog.Builder(this@BalanceTopUpActivity)
+ .setTitle("Berhasil")
+ .setMessage(successMessage)
+ .setPositiveButton("OK") { dialog, _ ->
+ dialog.dismiss()
+ finish()
+ }
+ .show()
+ }
+ } else {
+ // Get more detailed error information
+ val errorBody = response.errorBody()?.string()
+ android.util.Log.e("BalanceTopUp", "Error body: $errorBody")
+ android.util.Log.e("BalanceTopUp", "Error code: ${response.code()}")
+
+ // Try to parse the error body to extract the message
+ var errorMessage = "Gagal mengirim permintaan: ${response.message() ?: "Error ${response.code()}"}"
+ try {
+ val jsonObject = org.json.JSONObject(errorBody ?: "{}")
+ if (jsonObject.has("message")) {
+ errorMessage = jsonObject.getString("message")
+ }
+ } catch (e: Exception) {
+ android.util.Log.e("BalanceTopUp", "Error parsing error body", e)
+ }
+
+ Toast.makeText(
+ this@BalanceTopUpActivity,
+ errorMessage,
+ Toast.LENGTH_LONG
+ ).show()
+
+ // Show a dialog with the error message
+ runOnUiThread {
+ androidx.appcompat.app.AlertDialog.Builder(this@BalanceTopUpActivity)
+ .setTitle("Error Response")
+ .setMessage(errorMessage)
+ .setPositiveButton("OK") { dialog, _ ->
+ dialog.dismiss()
+ }
+ .show()
+ }
+ }
+ } catch (e: Exception) {
+ android.util.Log.e("BalanceTopUp", "Exception: ${e.message}", e)
+ Toast.makeText(
+ this@BalanceTopUpActivity,
+ "Error: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ } finally {
+ // Reset button state
+ btnSend.text = "Kirim"
+ btnSend.isEnabled = true
+ }
+ }
+ }
+
+ private fun uriToFile(uri: Uri): File {
+ val inputStream = contentResolver.openInputStream(uri)
+ val tempFile = File.createTempFile("upload", ".jpg", cacheDir)
+ tempFile.deleteOnExit()
+
+ inputStream?.use { input ->
+ tempFile.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+
+ // Validate file isn't empty
+ if (tempFile.length() == 0L) {
+ throw IllegalStateException("File is empty")
+ }
+
+ return tempFile
+ }
}
\ No newline at end of file
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 8e098d3..3cfb95f 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,15 +1,20 @@
package com.alya.ecommerce_serang.ui.profile.mystore.profile
+import android.app.Activity
+import android.content.Intent
import android.os.Bundle
+import android.util.Log
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
+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.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.data.repository.MyStoreRepository
import com.alya.ecommerce_serang.databinding.ActivityDetailStoreProfileBinding
+import com.alya.ecommerce_serang.ui.profile.mystore.profile.address.DetailStoreAddressActivity
import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
@@ -39,6 +44,24 @@ class DetailStoreProfileActivity : AppCompatActivity() {
enableEdgeToEdge()
+ // Set up header title
+ binding.header.headerTitle.text = "Profil Toko"
+
+ // Set up back button
+ binding.header.headerLeftIcon.setOnClickListener {
+ onBackPressedDispatcher.onBackPressed()
+ }
+
+ binding.btnEditStoreProfile.setOnClickListener {
+ val intent = Intent(this, EditStoreProfileActivity::class.java)
+ startActivityForResult(intent, EDIT_PROFILE_REQUEST_CODE)
+ }
+
+ binding.layoutAddress.setOnClickListener {
+ val intent = Intent(this, DetailStoreAddressActivity::class.java)
+ startActivity(intent)
+ }
+
viewModel.loadMyStore()
viewModel.myStoreProfile.observe(this){ user ->
@@ -50,11 +73,48 @@ class DetailStoreProfileActivity : AppCompatActivity() {
}
}
- private fun updateStoreProfile(store: Store){
+ 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
+ Toast.makeText(this, "Profil toko berhasil diperbarui", Toast.LENGTH_SHORT).show()
+ viewModel.loadMyStore()
+ // 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)
+ }
+ }
+
+ companion object {
+ private const val EDIT_PROFILE_REQUEST_CODE = 100
+ private const val ADDRESS_REQUEST_CODE = 101
+ }
+
+ 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 = "http:/192.168.100.156:3000${store.storeImage}"
+ Log.d("DetailStoreProfile", "Loading image from: $imageUrl")
+
+ 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")
+ }
}
}
\ 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
new file mode 100644
index 0000000..2d31f3b
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/EditStoreProfileActivity.kt
@@ -0,0 +1,304 @@
+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/ui/profile/mystore/profile/address/DetailStoreAddressActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/address/DetailStoreAddressActivity.kt
index 94f258c..897e6c8 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/address/DetailStoreAddressActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/profile/address/DetailStoreAddressActivity.kt
@@ -1,21 +1,312 @@
package com.alya.ecommerce_serang.ui.profile.mystore.profile.address
+import android.app.Activity
+import android.app.AlertDialog
import android.os.Bundle
-import androidx.activity.enableEdgeToEdge
+import android.util.Log
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Toast
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowInsetsCompat
-import com.alya.ecommerce_serang.R
+import com.alya.ecommerce_serang.BuildConfig
+import com.alya.ecommerce_serang.data.api.dto.City
+import com.alya.ecommerce_serang.data.api.dto.Province
+import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
+import com.alya.ecommerce_serang.data.api.retrofit.ApiService
+import com.alya.ecommerce_serang.data.repository.AddressRepository
+import com.alya.ecommerce_serang.databinding.ActivityDetailStoreAddressBinding
+import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel
+import com.alya.ecommerce_serang.utils.BaseViewModelFactory
+import com.alya.ecommerce_serang.utils.SessionManager
+import com.google.android.material.snackbar.Snackbar
class DetailStoreAddressActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityDetailStoreAddressBinding
+ private lateinit var apiService: ApiService
+ private lateinit var sessionManager: SessionManager
+
+ private var selectedProvinceId: String? = null
+ private var provinces: List = emptyList()
+ private var cities: List = emptyList()
+
+ private val TAG = "StoreAddressActivity"
+
+ private val viewModel: AddressViewModel by viewModels {
+ BaseViewModelFactory {
+ val apiService = ApiConfig.getApiService(sessionManager)
+ val addressRepository = AddressRepository(apiService)
+ AddressViewModel(addressRepository)
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContentView(R.layout.activity_detail_store_address)
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
- val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
- v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
- insets
+ binding = ActivityDetailStoreAddressBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ sessionManager = SessionManager(this)
+ apiService = ApiConfig.getApiService(sessionManager)
+
+ // Log the base URL
+ Log.d(TAG, "BASE_URL: ${BuildConfig.BASE_URL}")
+
+ // Add error text view
+ binding.tvError.visibility = View.GONE
+
+ // Set up header title
+ binding.header.headerTitle.text = "Alamat Toko"
+
+ // Set up back button
+ binding.header.headerLeftIcon.setOnClickListener {
+ onBackPressedDispatcher.onBackPressed()
+ }
+
+ setupSpinners()
+ setupObservers()
+ setupSaveButton()
+
+ // Add retry button
+ binding.btnRetry.setOnClickListener {
+ binding.tvError.visibility = View.GONE
+ binding.progressBar.visibility = View.VISIBLE
+ Log.d(TAG, "Retrying to fetch provinces...")
+ viewModel.fetchProvinces()
+ }
+
+ // Show loading spinners initially
+ showProvinceLoading(true)
+
+ // Load existing address data first
+ Log.d(TAG, "Fetching store address...")
+ viewModel.fetchStoreAddress()
+
+ // Load provinces data
+ Log.d(TAG, "Fetching provinces...")
+ viewModel.fetchProvinces()
+ }
+
+ private fun setupSpinners() {
+ // Province spinner listener
+ binding.spinnerProvince.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ Log.d(TAG, "Province selected at position: $position")
+ if (position > 0 && provinces.isNotEmpty()) {
+ selectedProvinceId = provinces[position - 1].provinceId
+ Log.d(TAG, "Selected province ID: $selectedProvinceId")
+ selectedProvinceId?.let {
+ Log.d(TAG, "Fetching cities for province ID: $it")
+ showCityLoading(true)
+ viewModel.fetchCities(it)
+ }
+ }
+ }
+
+ override fun onNothingSelected(p0: AdapterView<*>?) {
+ // Do nothing
+ }
+ }
+ }
+
+ private fun setupObservers() {
+ // Observe provinces data
+ viewModel.provinces.observe(this) { provinceList ->
+ Log.d(TAG, "Received provinces: ${provinceList.size}")
+ showProvinceLoading(false)
+
+ if (provinceList.isEmpty()) {
+ showError("No provinces available")
+ return@observe
+ }
+
+ provinces = provinceList
+ val provinceNames = mutableListOf("Pilih Provinsi")
+ provinceNames.addAll(provinceList.map { it.provinceName })
+
+ Log.d(TAG, "Province names: $provinceNames")
+
+ val adapter = ArrayAdapter(
+ this,
+ android.R.layout.simple_spinner_item,
+ provinceNames
+ )
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ binding.spinnerProvince.adapter = adapter
+ }
+
+ // Observe cities data
+ viewModel.cities.observe(this) { cityList ->
+ Log.d(TAG, "Received cities: ${cityList.size}")
+ showCityLoading(false)
+
+ cities = cityList
+ val cityNames = mutableListOf("Pilih Kota/Kabupaten")
+ cityNames.addAll(cityList.map { it.cityName })
+
+ Log.d(TAG, "City names: $cityNames")
+
+ val adapter = ArrayAdapter(
+ this,
+ android.R.layout.simple_spinner_item,
+ cityNames
+ )
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ binding.spinnerCity.adapter = adapter
+
+ // If we have a stored city_id, select it
+ viewModel.storeAddress.value?.let { address ->
+ if (address.cityId.isNotEmpty()) {
+ val cityIndex = cities.indexOfFirst { city ->
+ city.cityId == address.cityId
+ }
+ Log.d(TAG, "City index for ID ${address.cityId}: $cityIndex")
+ if (cityIndex != -1) {
+ binding.spinnerCity.setSelection(cityIndex + 1) // +1 because of "Pilih Kota/Kabupaten"
+ }
+ }
+ }
+ }
+
+ // Observe store address data
+ viewModel.storeAddress.observe(this) { address ->
+ Log.d(TAG, "Received store address: $address")
+ address?.let {
+ // Set the fields
+ binding.edtStreet.setText(address.street)
+ binding.edtSubdistrict.setText(address.subdistrict)
+ binding.edtDetailAddress.setText(address.detail ?: "")
+ binding.edtPostalCode.setText(address.postalCode)
+
+ // Handle latitude and longitude
+ val lat = if (address.latitude == null || address.latitude.toString() == "NaN") 0.0 else address.latitude
+ val lng = if (address.longitude == null || address.longitude.toString() == "NaN") 0.0 else address.longitude
+
+ // Set selected province ID to trigger city loading
+ if (address.provinceId.isNotEmpty()) {
+ selectedProvinceId = address.provinceId
+
+ // Find province index and select it after provinces are loaded
+ if (provinces.isNotEmpty()) {
+ val provinceIndex = provinces.indexOfFirst { province ->
+ province.provinceId == address.provinceId
+ }
+ Log.d(TAG, "Province index for ID ${address.provinceId}: $provinceIndex")
+ if (provinceIndex != -1) {
+ binding.spinnerProvince.setSelection(provinceIndex + 1) // +1 because of "Pilih Provinsi"
+
+ // Now fetch cities for this province
+ showCityLoading(true)
+ viewModel.fetchCities(address.provinceId)
+ }
+ }
+ }
+ }
+ }
+
+ // Observe loading state
+ viewModel.isLoading.observe(this) { isLoading ->
+ binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
+ }
+
+ // Observe error messages
+ viewModel.errorMessage.observe(this) { errorMsg ->
+ Log.e(TAG, "Error: $errorMsg")
+ showError(errorMsg)
+ }
+
+ // Observe save success
+ viewModel.saveSuccess.observe(this) { success ->
+ if (success) {
+ Toast.makeText(this, "Alamat berhasil disimpan", Toast.LENGTH_SHORT).show()
+ setResult(Activity.RESULT_OK)
+ finish()
+ }
+ }
+ }
+
+ private fun showProvinceLoading(isLoading: Boolean) {
+ binding.provinceProgressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
+ binding.spinnerProvince.visibility = if (isLoading) View.GONE else View.VISIBLE
+ }
+
+ private fun showCityLoading(isLoading: Boolean) {
+ binding.cityProgressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
+ binding.spinnerCity.visibility = if (isLoading) View.GONE else View.VISIBLE
+ }
+
+ private fun showError(message: String) {
+ binding.progressBar.visibility = View.GONE
+ binding.tvError.visibility = View.VISIBLE
+ binding.tvError.text = "Error: $message\nURL: ${BuildConfig.BASE_URL}/provinces"
+ binding.btnRetry.visibility = View.VISIBLE
+
+ // Also show in a dialog for immediate attention
+ AlertDialog.Builder(this)
+ .setTitle("Error")
+ .setMessage("$message\n\nAPI URL: ${BuildConfig.BASE_URL}/provinces")
+ .setPositiveButton("Retry") { _, _ ->
+ binding.tvError.visibility = View.GONE
+ binding.progressBar.visibility = View.VISIBLE
+ viewModel.fetchProvinces()
+ }
+ .setNegativeButton("Cancel") { dialog, _ ->
+ dialog.dismiss()
+ }
+ .show()
+
+ // Also show a snackbar
+ Snackbar.make(binding.root, "Error: $message", Snackbar.LENGTH_LONG)
+ .setAction("Retry") {
+ binding.tvError.visibility = View.GONE
+ binding.progressBar.visibility = View.VISIBLE
+ viewModel.fetchProvinces()
+ }
+ .show()
+ }
+
+ private fun setupSaveButton() {
+ binding.btnSaveAddress.setOnClickListener {
+ val street = binding.edtStreet.text.toString()
+ val subdistrict = binding.edtSubdistrict.text.toString()
+ val detail = binding.edtDetailAddress.text.toString()
+ val postalCode = binding.edtPostalCode.text.toString()
+ val latitudeStr = TODO()
+ val longitudeStr = TODO()
+
+ // Validate required fields
+ if (selectedProvinceId == null || binding.spinnerCity.selectedItemPosition <= 0 ||
+ street.isEmpty() || subdistrict.isEmpty() || postalCode.isEmpty()) {
+ Toast.makeText(this, "Mohon lengkapi data yang wajib diisi", Toast.LENGTH_SHORT).show()
+ return@setOnClickListener
+ }
+
+ // Get selected city
+ val cityPosition = binding.spinnerCity.selectedItemPosition
+ if (cityPosition <= 0 || cities.isEmpty() || cityPosition > cities.size) {
+ Toast.makeText(this, "Mohon pilih kota/kabupaten", Toast.LENGTH_SHORT).show()
+ return@setOnClickListener
+ }
+
+ val selectedCity = cities[cityPosition - 1]
+
+ // Parse coordinates
+ val latitude = latitudeStr.toDoubleOrNull() ?: 0.0
+ val longitude = longitudeStr.toDoubleOrNull() ?: 0.0
+
+ // Save address
+ viewModel.saveStoreAddress(
+ provinceId = selectedProvinceId!!,
+ provinceName = provinces.find { it.provinceId == selectedProvinceId }?.provinceName ?: "",
+ cityId = selectedCity.cityId,
+ cityName = selectedCity.cityName,
+ street = street,
+ subdistrict = subdistrict,
+ detail = detail,
+ postalCode = postalCode,
+ latitude = latitude,
+ longitude = longitude
+ )
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/AddressViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/AddressViewModel.kt
new file mode 100644
index 0000000..9f3d94a
--- /dev/null
+++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/AddressViewModel.kt
@@ -0,0 +1,129 @@
+package com.alya.ecommerce_serang.utils.viewmodel
+
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.alya.ecommerce_serang.data.api.dto.City
+import com.alya.ecommerce_serang.data.api.dto.Province
+import com.alya.ecommerce_serang.data.api.dto.StoreAddress
+import com.alya.ecommerce_serang.data.repository.AddressRepository
+import kotlinx.coroutines.launch
+
+class AddressViewModel(private val addressRepository: AddressRepository) : ViewModel() {
+
+ private val TAG = "AddressViewModel"
+
+ private val _provinces = MutableLiveData>()
+ val provinces: LiveData> = _provinces
+
+ private val _cities = MutableLiveData>()
+ val cities: LiveData> = _cities
+
+ private val _storeAddress = MutableLiveData()
+ val storeAddress: LiveData = _storeAddress
+
+ private val _isLoading = MutableLiveData()
+ val isLoading: LiveData = _isLoading
+
+ private val _errorMessage = MutableLiveData()
+ val errorMessage: LiveData = _errorMessage
+
+ private val _saveSuccess = MutableLiveData()
+ val saveSuccess: LiveData = _saveSuccess
+
+ fun fetchProvinces() {
+ Log.d(TAG, "fetchProvinces() called")
+ _isLoading.value = true
+ viewModelScope.launch {
+ try {
+ Log.d(TAG, "Calling addressRepository.getProvinces()")
+ val response = addressRepository.getProvinces()
+ Log.d(TAG, "Received provinces response: ${response.size} provinces")
+ _provinces.value = response
+ _isLoading.value = false
+ } catch (e: Exception) {
+ Log.e(TAG, "Error fetching provinces", e)
+ _errorMessage.value = "Failed to load provinces: ${e.message}"
+ _isLoading.value = false
+ }
+ }
+ }
+
+ fun fetchCities(provinceId: String) {
+ Log.d(TAG, "fetchCities() called with provinceId: $provinceId")
+ _isLoading.value = true
+ viewModelScope.launch {
+ try {
+ Log.d(TAG, "Calling addressRepository.getCities()")
+ val response = addressRepository.getCities(provinceId)
+ Log.d(TAG, "Received cities response: ${response.size} cities")
+ _cities.value = response
+ _isLoading.value = false
+ } catch (e: Exception) {
+ Log.e(TAG, "Error fetching cities", e)
+ _errorMessage.value = "Failed to load cities: ${e.message}"
+ _isLoading.value = false
+ }
+ }
+ }
+
+ fun fetchStoreAddress() {
+ Log.d(TAG, "fetchStoreAddress() called")
+ _isLoading.value = true
+ viewModelScope.launch {
+ try {
+ Log.d(TAG, "Calling addressRepository.getStoreAddress()")
+ val response = addressRepository.getStoreAddress()
+ Log.d(TAG, "Received store address response: $response")
+ _storeAddress.value = response
+ _isLoading.value = false
+ } catch (e: Exception) {
+ Log.e(TAG, "Error fetching store address", e)
+ _errorMessage.value = "Failed to load store address: ${e.message}"
+ _isLoading.value = false
+ }
+ }
+ }
+
+ fun saveStoreAddress(
+ provinceId: String,
+ provinceName: String,
+ cityId: String,
+ cityName: String,
+ street: String,
+ subdistrict: String,
+ detail: String,
+ postalCode: String,
+ latitude: Double,
+ longitude: Double
+ ) {
+ Log.d(TAG, "saveStoreAddress() called with provinceId: $provinceId, cityId: $cityId")
+ _isLoading.value = true
+ viewModelScope.launch {
+ try {
+ Log.d(TAG, "Calling addressRepository.saveStoreAddress()")
+ val success = addressRepository.saveStoreAddress(
+ provinceId = provinceId,
+ provinceName = provinceName,
+ cityId = cityId,
+ cityName = cityName,
+ street = street,
+ subdistrict = subdistrict,
+ detail = detail,
+ postalCode = postalCode,
+ latitude = latitude,
+ longitude = longitude
+ )
+ Log.d(TAG, "Save store address result: $success")
+ _saveSuccess.value = success
+ _isLoading.value = false
+ } catch (e: Exception) {
+ Log.e(TAG, "Error saving store address", e)
+ _errorMessage.value = "Failed to save address: ${e.message}"
+ _isLoading.value = false
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_balance_top_up.xml b/app/src/main/res/layout/activity_balance_top_up.xml
index 913dc1a..133e8f4 100644
--- a/app/src/main/res/layout/activity_balance_top_up.xml
+++ b/app/src/main/res/layout/activity_balance_top_up.xml
@@ -322,7 +322,8 @@
+ style="@style/button.large.disabled.long"
+ android:layout_marginBottom="16dp"/>
diff --git a/app/src/main/res/layout/activity_detail_store_address.xml b/app/src/main/res/layout/activity_detail_store_address.xml
index 3efe392..837e0e5 100644
--- a/app/src/main/res/layout/activity_detail_store_address.xml
+++ b/app/src/main/res/layout/activity_detail_store_address.xml
@@ -26,7 +26,33 @@
android:paddingHorizontal="@dimen/horizontal_safe_area"
android:layout_marginTop="19dp">
-
+
+
+
+
+
+
+
+
+
-
+
+ android:orientation="horizontal"
+ android:background="@drawable/bg_text_field"
+ android:gravity="center_vertical"
+ android:layout_marginTop="10dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -103,94 +204,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:enabled="false"
+ android:layout_marginBottom="16dp"/>
+
+
diff --git a/app/src/main/res/layout/activity_detail_store_product.xml b/app/src/main/res/layout/activity_detail_store_product.xml
index 3922988..7be0362 100644
--- a/app/src/main/res/layout/activity_detail_store_product.xml
+++ b/app/src/main/res/layout/activity_detail_store_product.xml
@@ -847,7 +847,8 @@
android:id="@+id/btn_save_product"
android:text="Simpan Produk"
style="@style/button.large.disabled.long"
- android:enabled="false"/>
+ android:enabled="false"
+ android:layout_marginBottom="16dp"/>
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 bc29ee3..a32534f 100644
--- a/app/src/main/res/layout/activity_detail_store_profile.xml
+++ b/app/src/main/res/layout/activity_detail_store_profile.xml
@@ -8,7 +8,9 @@
android:orientation="vertical"
tools:context=".ui.profile.mystore.profile.DetailStoreProfileActivity">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index df89d71..5609b8a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
[versions]
-agp = "8.5.2"
+agp = "8.10.0"
glide = "4.16.0"
hiltAndroid = "2.48" # Updated from 2.44 for better compatibility
hiltLifecycleViewmodel = "1.0.0-alpha03"