approval status

This commit is contained in:
Gracia Hotmauli
2025-08-22 04:13:37 +07:00
parent 22122d631b
commit 421c20cc4b
16 changed files with 466 additions and 48 deletions

View File

@ -29,6 +29,9 @@
android:theme="@style/Theme.Ecommerce_serang"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".ui.profile.ChangePasswordActivity"
android:exported="false" />
<activity
android:name=".ui.auth.ResetPassActivity"
android:exported="false" />

View File

@ -10,9 +10,6 @@ data class Store(
@field:SerializedName("store_status")
val storeStatus: String,
@field:SerializedName("sppirt")
val sppirt: String,
@field:SerializedName("user_name")
val userName: String,
@ -37,9 +34,6 @@ data class Store(
@field:SerializedName("user_phone")
val userPhone: String,
@field:SerializedName("halal")
val halal: String,
@field:SerializedName("id")
val id: Int,

View File

@ -1,18 +1,13 @@
package com.alya.ecommerce_serang.data.api.response.store
import com.alya.ecommerce_serang.data.api.dto.Store
import com.alya.ecommerce_serang.data.api.response.store.profile.Payment
import com.alya.ecommerce_serang.data.api.response.store.profile.Shipping
import com.google.gson.annotations.SerializedName
data class StoreResponse(
val message: String,
val store: Store
)
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 store: Store,
val shipping: List<Shipping> = emptyList(),
val payment: List<Payment> = emptyList()
)

View File

@ -4,7 +4,7 @@ import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
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.StoreResponse
import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse
import com.alya.ecommerce_serang.data.api.response.store.sells.OrderListResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
@ -15,13 +15,13 @@ import retrofit2.Response
import java.io.IOException
class MyStoreRepository(private val apiService: ApiService) {
suspend fun fetchMyStoreProfile(): Result<Store?> {
suspend fun fetchMyStoreProfile(): Result<StoreResponse?> {
return try {
val response = apiService.getStore()
val response = apiService.getMyStoreData()
if (response.isSuccessful) {
val storeResponse: StoreResponse? = response.body()
Result.Success(storeResponse?.store)
val storeResponse = response.body()
Result.Success(storeResponse)
} else {
val errorMessage = response.errorBody()?.string() ?: "Unknown API error"
Log.e("MyStoreRepository", "Error: $errorMessage")

View File

@ -0,0 +1,21 @@
package com.alya.ecommerce_serang.ui.profile
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
class ChangePasswordActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_change_password)
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
}
}
}

View File

@ -84,14 +84,19 @@ class ProfileFragment : Fragment() {
// else startActivity(Intent(requireContext(), RegisterStoreActivity::class.java))
if (viewModel.checkStore.value == true) {
myStoreViewModel.loadMyStore()
myStoreViewModel.myStoreProfile.observe(viewLifecycleOwner) { store ->
store?.let {
when (store.storeStatus) {
myStoreViewModel.myStoreProfile.observe(viewLifecycleOwner) { storeDataResponse ->
storeDataResponse?.let { storeResponse ->
val store = storeResponse.store
when (store.approvalStatus) {
"process" -> startActivity(Intent(requireContext(), StoreOnReviewActivity::class.java))
"active" -> startActivity(Intent(requireContext(), MyStoreActivity::class.java))
"inactive" -> startActivity(Intent(requireContext(), MyStoreActivity::class.java))
"suspended" -> startActivity(Intent(requireContext(), StoreSuspendedActivity::class.java))
else -> startActivity(Intent(requireContext(), RegisterStoreActivity::class.java))
"rejected" -> startActivity(
Intent(requireContext(), RegisterStoreActivity::class.java).putExtra("REAPPLY", true))
else -> {
when(store.storeStatus){
"suspended" -> startActivity(Intent(requireContext(), StoreSuspendedActivity::class.java))
else -> startActivity(Intent(requireContext(), MyStoreActivity::class.java))
}
}
}
} ?: run {
Toast.makeText(requireContext(), "Gagal memuat data toko", Toast.LENGTH_SHORT).show()
@ -115,6 +120,11 @@ class ProfileFragment : Fragment() {
startActivity(intent)
}
binding.cardChangePass.setOnClickListener{
val intent = Intent(requireContext(), ChangePasswordActivity::class.java)
startActivity(intent)
}
binding.cardLogout.setOnClickListener{
logout()
}

View File

@ -63,7 +63,7 @@ class MyStoreActivity : AppCompatActivity() {
viewModel.loadMyStoreProducts()
viewModel.myStoreProfile.observe(this){ user ->
user?.let { myStoreProfileOverview(it) }
user?.let { myStoreProfileOverview(it.store) }
}
viewModel.errorMessage.observe(this) { error ->

View File

@ -27,6 +27,7 @@ import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.auth.StoreTypesItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.MyStoreRepository
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityRegisterStoreBinding
@ -35,8 +36,17 @@ import com.alya.ecommerce_serang.ui.order.address.CityAdapter
import com.alya.ecommerce_serang.ui.order.address.ProvinceAdapter
import com.alya.ecommerce_serang.ui.order.address.SubdsitrictAdapter
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.FileUtils
import com.alya.ecommerce_serang.utils.ImageUtils
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel
import com.alya.ecommerce_serang.utils.viewmodel.RegisterStoreViewModel
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import androidx.core.net.toUri
class RegisterStoreActivity : AppCompatActivity() {
@ -53,6 +63,7 @@ class RegisterStoreActivity : AppCompatActivity() {
private val PICK_KTP_REQUEST = 1002
private val PICK_NPWP_REQUEST = 1003
private val PICK_NIB_REQUEST = 1004
private var isReapply: Boolean = false
// Location request code
private val LOCATION_PERMISSION_REQUEST = 2001
@ -64,6 +75,15 @@ class RegisterStoreActivity : AppCompatActivity() {
RegisterStoreViewModel(orderRepository)
}
}
private val myStoreViewModel: MyStoreViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val myStoreRepository = MyStoreRepository(apiService)
MyStoreViewModel(myStoreRepository)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityRegisterStoreBinding.inflate(layoutInflater)
@ -89,6 +109,8 @@ class RegisterStoreActivity : AppCompatActivity() {
setupHeader()
isReapply = intent.getBooleanExtra("REAPPLY", false)
provinceAdapter = ProvinceAdapter(this)
cityAdapter = CityAdapter(this)
subdistrictAdapter = SubdsitrictAdapter(this)
@ -129,19 +151,140 @@ class RegisterStoreActivity : AppCompatActivity() {
viewModel.cityId.observe(this) { validateRequiredFields() }
viewModel.storeTypeId.observe(this) { validateRequiredFields() }
// Setup register button
binding.btnRegister.setOnClickListener {
Log.d(TAG, "Register button clicked")
if (viewModel.validateForm()) {
Log.d(TAG, "Form validation successful, proceeding with registration")
viewModel.registerStore(this)
} else {
Log.e(TAG, "Form validation failed")
Toast.makeText(this, "Harap lengkapi semua field yang wajib diisi", Toast.LENGTH_SHORT).show()
if (isReapply) {
binding.btnRegister.text = "Ajukan Kembali"
binding.layoutRejected.visibility = View.VISIBLE
myStoreViewModel.loadMyStore()
myStoreViewModel.myStoreProfile.observe(this) { storeDataResponse ->
storeDataResponse?.let { storeResponse ->
val store = storeResponse.store
binding.tvRejectedReason.text = store.approvalReason
// Prefill basic fields
binding.etStoreName.setText(store.storeName)
binding.etStoreDescription.setText(store.storeDescription)
binding.etStreet.setText(store.street)
binding.etPostalCode.setText(store.postalCode)
binding.etAddressDetail.setText(store.detail)
viewModel.storeName.value = store.storeName
viewModel.storeDescription.value = store.storeDescription
viewModel.street.value = store.street
viewModel.postalCode.value = store.postalCode.toIntOrNull() ?: 0
viewModel.addressDetail.value = store.detail
// Prefill bank info
storeResponse.payment.firstOrNull()?.let { payment ->
viewModel.bankName.value = payment.bankName
viewModel.bankNumber.value = payment.bankNum.toIntOrNull() ?: 0
val bankPosition = bankAdapter.findPositionByName(payment.bankName)
binding.spinnerBankName.setSelection(bankPosition, false)
}
// Prefill couriers
storeResponse.shipping.forEach { courier ->
when (courier.courier) {
"jne" -> binding.checkboxJne.isChecked = true
"pos" -> binding.checkboxPos.isChecked = true
"tiki" -> binding.checkboxTiki.isChecked = true
}
}
// Prefill document URIs
store.ktp.let { ktpUri ->
viewModel.ktpUri = ktpUri.toUri()
updateImagePreview(viewModel.ktpUri, binding.imgKtp, binding.layoutUploadKtp)
}
store.npwp.let { npwpUri ->
viewModel.npwpUri = npwpUri.toUri()
updateDocumentPreview(binding.layoutUploadNpwp)
}
store.nib.let { nibUri ->
viewModel.nibUri = nibUri.toUri()
updateDocumentPreview(binding.layoutUploadNib)
}
// Prefill spinner for store types
preselectStoreType(store.storeTypeId)
// Prefill province, city, and subdistrict
preselectProvinceCitySubdistrict(
provinceId = store.provinceId,
cityId = store.cityId,
subdistrictId = store.subdistrict
)
validateRequiredFields()
}
}
binding.btnRegister.setOnClickListener {
doUpdateStoreProfile()
}
} else {
binding.btnRegister.setOnClickListener {
if (viewModel.validateForm()) viewModel.registerStore(this)
else Toast.makeText(this, "Harap lengkapi semua field yang wajib diisi", Toast.LENGTH_SHORT).show()
}
}
}
private fun preselectStoreType(storeTypeId: Int) {
// The adapter is created in setupStoreTypeSpinner(...)
val adapter = binding.spinnerStoreType.adapter
if (adapter != null) {
val count = adapter.count
for (i in 0 until count) {
val item = adapter.getItem(i) as? StoreTypesItem
if (item?.id == storeTypeId) {
binding.spinnerStoreType.setSelection(i, false)
break
}
}
}
}
private fun preselectProvinceCitySubdistrict(
provinceId: Int,
cityId: String,
subdistrictId: String
) {
// Province first (this will trigger cities fetch)
val provCount = provinceAdapter.count
for (i in 0 until provCount) {
if (provinceAdapter.getProvinceId(i) == provinceId) {
binding.spinnerProvince.setSelection(i, false)
break
}
}
Log.d(TAG, "onCreate: RegisterStoreActivity setup completed")
// When cities arrive, select the city, then load subdistricts
viewModel.citiesState.observe(this) { state ->
if (state is Result.Success) {
val cityCount = cityAdapter.count
for (i in 0 until cityCount) {
if (cityAdapter.getCityId(i) == cityId) {
binding.spinnerCity.setSelection(i, false)
break
}
}
}
}
// When subdistricts arrive, select the subdistrict
viewModel.subdistrictState.observe(this) { state ->
if (state is Result.Success) {
val subCount = subdistrictAdapter.count
for (i in 0 until subCount) {
if (subdistrictAdapter.getSubdistrictId(i) == subdistrictId) {
binding.spinnerSubdistrict.setSelection(i, false)
break
}
}
}
}
}
private fun setupHeader() {
@ -177,10 +320,11 @@ class RegisterStoreActivity : AppCompatActivity() {
if (isFormValid) {
binding.btnRegister.setBackgroundResource(R.drawable.bg_button_active)
binding.btnRegister.setTextColor(ContextCompat.getColor(this, R.color.white))
binding.btnRegister.isEnabled = true
} else {
binding.btnRegister.setBackgroundResource(R.drawable.bg_button_disabled)
binding.btnRegister.setTextColor(ContextCompat.getColor(this, R.color.black_300))
binding.btnRegister.isEnabled = false
}
}
@ -848,6 +992,63 @@ class RegisterStoreActivity : AppCompatActivity() {
}
}
private fun doUpdateStoreProfile() {
val nameBody: RequestBody = (viewModel.storeName.value ?: "")
.toRequestBody("text/plain".toMediaTypeOrNull())
val typeBody: RequestBody = ((viewModel.storeTypeId.value ?: 0).toString())
.toRequestBody("text/plain".toMediaTypeOrNull())
val descBody: RequestBody = (viewModel.storeDescription.value ?: "")
.toRequestBody("text/plain".toMediaTypeOrNull())
val onLeaveBody: RequestBody = "false"
.toRequestBody("text/plain".toMediaTypeOrNull())
// --- Build Multipart for store image (optional) ---
// Prefer compressing images to keep payload small; fall back to raw copy if needed.
val storeImgPart: MultipartBody.Part? = viewModel.storeImageUri?.let { uri ->
try {
// (A) Optional safety check: only allow jpg/png/webp
val allowed = Regex("^(jpg|jpeg|png|webp)$", RegexOption.IGNORE_CASE)
if (!ImageUtils.isAllowedFileType(this, uri, allowed)) {
Toast.makeText(this, "Format gambar tidak didukung", Toast.LENGTH_SHORT).show()
null
} else {
// (B) Compress for upload (ke cacheDir), then build multipart
val compressed: File = ImageUtils.compressImage(
context = this,
uri = uri,
filename = "storeimg", // prefix
maxWidth = 1024,
maxHeight = 1024,
quality = 80
)
FileUtils.createMultipartFromFile("storeimg", compressed)
}
} catch (e: Exception) {
// If compression fails, try raw copy as fallback
val rawFile = FileUtils.createTempFileFromUri(this, uri)
rawFile?.let { FileUtils.createMultipartFromFile("storeimg", it) }
}
}
myStoreViewModel.updateStoreProfile(
storeName = nameBody,
storeType = typeBody,
description = descBody,
isOnLeave = onLeaveBody,
storeImage = storeImgPart
)
myStoreViewModel.updateStoreProfileResult.observe(this) {
Toast.makeText(this, "Pengajuan ulang berhasil dikirim", Toast.LENGTH_SHORT).show()
finish()
}
myStoreViewModel.errorMessage.observe(this) {
if (!it.isNullOrEmpty()) {
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
}
}
}
companion object {
private const val TAG = "RegisterStoreActivity"
}

View File

@ -91,10 +91,10 @@ class DetailStoreProfileActivity : AppCompatActivity() {
viewModel.fetchStoreTypes()
viewModel.myStoreProfile.observe(this) {
currentStore = it
currentStore = it?.store
currentStoreLoaded = true
if (storeTypesLoaded) setupStoreTypeSpinner(storeTypesList)
updateUI(it)
updateUI(it?.store)
}
viewModel.storeTypes.observe(this) {

View File

@ -10,6 +10,8 @@ import com.alya.ecommerce_serang.data.api.dto.ProductsItem
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.StoreResponse
import com.alya.ecommerce_serang.data.api.response.store.profile.Payment
import com.alya.ecommerce_serang.data.api.response.store.profile.Shipping
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
@ -23,12 +25,18 @@ import java.util.Locale
class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
private var TAG = "MyStoreViewModel"
private val _myStoreProfile = MutableLiveData<Store?>()
val myStoreProfile: LiveData<Store?> = _myStoreProfile
private val _myStoreProfile = MutableLiveData<StoreResponse?>()
val myStoreProfile: LiveData<StoreResponse?> = _myStoreProfile
private val _storeTypes = MutableLiveData<List<StoreTypesItem>>()
val storeTypes: LiveData<List<StoreTypesItem>> = _storeTypes
private val _shipping = MutableLiveData<List<Shipping>>()
val shipping: LiveData<List<Shipping>> = _shipping
private val _payment = MutableLiveData<List<Payment>>()
val payment: LiveData<List<Payment>> = _payment
private val _isLoadingType = MutableLiveData<Boolean>()
val isLoadingType: LiveData<Boolean> = _isLoadingType
@ -47,7 +55,12 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
fun loadMyStore(){
viewModelScope.launch {
when (val result = repository.fetchMyStoreProfile()){
is Result.Success -> _myStoreProfile.postValue(result.data)
is Result.Success -> {
val storeData = result.data
_myStoreProfile.postValue(storeData)
_shipping.postValue(storeData?.shipping)
_payment.postValue(storeData?.payment)
}
is Result.Error -> _errorMessage.postValue(result.exception.message ?: "Unknown Error")
is Result.Loading -> null
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/main"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ui.profile.ChangePasswordActivity">
<include
android:id="@+id/headerStoreProduct"
layout="@layout/header" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="32dp"
android:paddingVertical="16dp">
<!-- Password label-->
<TextView
android:id="@+id/tv_password_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="@font/dmsans_medium"
android:text="Kata Sandi Lama"
android:textSize="18sp"
android:layout_marginVertical="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Password input -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_login_password"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:passwordToggleEnabled="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_password_label"
app:layout_constraintEnd_toEndOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Masukkan kata sandi akun"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Password label-->
<TextView
android:id="@+id/tv_new_password_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="@font/dmsans_medium"
android:text="Kata Sandi Baru"
android:textSize="18sp"
android:layout_marginVertical="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/til_login_password"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Password input -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_login_new_password"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:passwordToggleEnabled="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_new_password_label"
app:layout_constraintEnd_toEndOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_login_new_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Masukkan kata sandi baru"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Change Pass button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_change_pass"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Ubah Kata Sandi"
app:cornerRadius="8dp"
android:layout_marginVertical="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/til_login_new_password" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -136,6 +136,7 @@
android:id="@+id/tv_registrasi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/signup"
android:textColor="@color/blue1"
android:textStyle="bold" />

View File

@ -69,6 +69,35 @@
</LinearLayout>
<LinearLayout
android:id="@+id/layout_rejected"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical"
android:gravity="center"
android:background="@drawable/bg_product_active"
android:padding="8dp"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/label_small"
android:textAlignment="center"
android:text="Permintaan Buka Toko Anda sebelumnya ditolak! Silahkan lakukan penyesuaian berdasarkan alasan berikut:"/>
<TextView
android:id="@+id/tv_rejected_reason"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/body_medium"
android:fontFamily="@font/dmsans_bold"
android:layout_marginTop="8dp"
android:text="KTP tidak sesuai"/>
</LinearLayout>
<!-- Nama Toko -->
<LinearLayout
android:layout_width="match_parent"

View File

@ -304,6 +304,55 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<!-- Change Password Card -->
<androidx.cardview.widget.CardView
android:id="@+id/card_change_pass"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:foreground="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ImageView
android:id="@+id/ivChangePass"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_change_pass"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/tvChangePass"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Ubah Kata Sandi"
android:textSize="14sp"
app:layout_constraintStart_toEndOf="@id/ivChangePass"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/ivChangePassArrow" />
<ImageView
android:id="@+id/ivChangePassArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_arrow_right"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<!-- About Card -->
<androidx.cardview.widget.CardView
android:id="@+id/card_about"