Compare commits

1 Commits

Author SHA1 Message Date
4d24673107 Add files via upload 2025-08-21 13:39:03 +07:00
84 changed files with 972 additions and 2161 deletions

View File

@ -4,10 +4,10 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-08-17T17:32:55.497700100Z">
<DropdownSelection timestamp="2025-05-08T14:50:55.425322500Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Gracia Hotmauli\.android\avd\Pixel_9_2.avd" />
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Gracia Hotmauli\.android\avd\Pixel_8_2_2.avd" />
</handle>
</Target>
</DropdownSelection>

View File

@ -124,7 +124,4 @@ dependencies {
implementation(platform("com.google.firebase:firebase-bom:33.13.0"))
implementation("com.google.firebase:firebase-analytics")
implementation("com.google.firebase:firebase-messaging-ktx")
//Splash screen
implementation("androidx.core:core-splashscreen:1.0.0")
}

View File

@ -29,9 +29,6 @@
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" />
@ -173,8 +170,7 @@
<activity
android:name=".ui.auth.RegisterActivity"
android:exported="true"
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.App.SplashScreen">
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -1,6 +0,0 @@
package com.alya.ecommerce_serang.data.api.dto
data class ChangePasswordRequest(
val currentPassword: String,
val newPassword: String
)

View File

@ -10,6 +10,9 @@ data class Store(
@field:SerializedName("store_status")
val storeStatus: String,
@field:SerializedName("sppirt")
val sppirt: String,
@field:SerializedName("user_name")
val userName: String,
@ -34,6 +37,9 @@ 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,9 +0,0 @@
package com.alya.ecommerce_serang.data.api.response.auth
import com.google.gson.annotations.SerializedName
data class ChangePassResponse(
@field:SerializedName("message")
val message: String? = null
)

View File

@ -1,13 +1,18 @@
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,
val shipping: List<Shipping> = emptyList(),
val payment: List<Payment> = emptyList()
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
)

View File

@ -5,7 +5,6 @@ import com.alya.ecommerce_serang.data.api.dto.AddEvidenceRequest
import com.alya.ecommerce_serang.data.api.dto.AddPaymentInfoResponse
import com.alya.ecommerce_serang.data.api.dto.CancelOrderReq
import com.alya.ecommerce_serang.data.api.dto.CartItem
import com.alya.ecommerce_serang.data.api.dto.ChangePasswordRequest
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.ConfirmPaymentRequest
@ -28,7 +27,6 @@ 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.dto.VerifRegisReq
import com.alya.ecommerce_serang.data.api.response.auth.ChangePassResponse
import com.alya.ecommerce_serang.data.api.response.auth.CheckStoreResponse
import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
@ -532,11 +530,6 @@ interface ApiService {
@Body request: ResetPassReq
): Response<ResetPassResponse>
@POST("changepass")
suspend fun changePassword(
@Body request: ChangePasswordRequest
): Response<ChangePassResponse>
@GET("profile/address/detail/{id}")
suspend fun getDetailAddress(
@Path("id") addressId: Int

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.store.StoreResponse
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.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<StoreResponse?> {
suspend fun fetchMyStoreProfile(): Result<Store?> {
return try {
val response = apiService.getMyStoreData()
val response = apiService.getStore()
if (response.isSuccessful) {
val storeResponse = response.body()
Result.Success(storeResponse)
val storeResponse: StoreResponse? = response.body()
Result.Success(storeResponse?.store)
} else {
val errorMessage = response.errorBody()?.string() ?: "Unknown API error"
Log.e("MyStoreRepository", "Error: $errorMessage")

View File

@ -3,7 +3,6 @@ package com.alya.ecommerce_serang.data.repository
import android.content.Context
import android.net.Uri
import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.ChangePasswordRequest
import com.alya.ecommerce_serang.data.api.dto.FcmReq
import com.alya.ecommerce_serang.data.api.dto.LoginRequest
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
@ -11,7 +10,6 @@ import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.dto.ResetPassReq
import com.alya.ecommerce_serang.data.api.dto.UserProfile
import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
import com.alya.ecommerce_serang.data.api.response.auth.ChangePassResponse
import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse
@ -518,29 +516,6 @@ class UserRepository(private val apiService: ApiService) {
Result.Error(e)
}
}
suspend fun changePassword(currentPassword: String, newPassword: String): Result<ChangePassResponse> {
return try {
val request = ChangePasswordRequest(currentPassword, newPassword)
val response = apiService.changePassword(request) // Make the API call
if (response.isSuccessful) {
val changePassResponse = response.body()
if (changePassResponse != null) {
Result.Success(changePassResponse) // Return success with the response message
} else {
Result.Error(Exception("Empty response from server"))
}
} else {
val errorBody = response.errorBody()?.string() ?: "Unknown error"
Log.e(TAG, "Error changing password: $errorBody")
Result.Error(Exception(errorBody))
}
} catch (e: Exception) {
Result.Error(e)
}
}
companion object{
private const val TAG = "UserRepository"
}

View File

@ -43,10 +43,27 @@ class LoginActivity : AppCompatActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)
enableEdgeToEdge()
// ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
// val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
// view.setPadding(
// systemBars.left,
// systemBars.top,
// systemBars.right,
// systemBars.bottom
// )
// windowInsets
// }
// onBackPressedDispatcher.addCallback(this) {
// // Handle the back button event
// }
setupListeners()
observeLoginState()
FirebaseApp.initializeApp(this)
// Request FCM token at app startup
}
private fun setupListeners() {
@ -55,7 +72,7 @@ class LoginActivity : AppCompatActivity() {
val password = binding.etLoginPassword.text.toString()
if (email.isEmpty() || password.isEmpty()) {
Toast.makeText(this, "Mohon masukkan email atau password dengan benar", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Please fill in all fields", Toast.LENGTH_SHORT).show()
} else {
loginViewModel.login(email, password)
}
@ -83,14 +100,14 @@ class LoginActivity : AppCompatActivity() {
retrieveFCMToken()
// sessionManager.saveUserId(response.userId)
Toast.makeText(this, "Berhasil masuk", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Login Successful", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, MainActivity::class.java))
finish()
}
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
Log.e("LoginActivity", "Login Failed: ${result.exception.message}")
Toast.makeText(this, "Gagal masuk", Toast.LENGTH_LONG).show()
Toast.makeText(this, "Login Failed: ${result.exception.message}", Toast.LENGTH_LONG).show()
}
is Result.Loading -> {
// Show loading state

View File

@ -29,7 +29,7 @@ class OtpBottomSheetDialog(
onRegister(updatedUserData) // Send full data to ViewModel
dismiss() // Close dialog
} else {
Toast.makeText(requireContext(), "Silahkan masukkan kode OTP", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "Please enter OTP", Toast.LENGTH_SHORT).show()
}
}
return view

View File

@ -6,7 +6,6 @@ import android.util.Log
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
@ -40,8 +39,6 @@ class RegisterActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread.sleep(3000)
installSplashScreen()
binding = ActivityRegisterBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
@ -89,72 +86,20 @@ class RegisterActivity : AppCompatActivity() {
}
}
// In RegisterActivity, add debug to navigateToStep:
// navigate step register in fragment
fun navigateToStep(step: Int, userData: RegisterRequest?) {
Log.d("RegisterActivity", "=== NAVIGATE TO STEP START ===")
Log.d("RegisterActivity", "Target step: $step")
Log.d("RegisterActivity", "Current fragment count: ${supportFragmentManager.fragments.size}")
Log.d("RegisterActivity", "UserData: ${userData?.email}")
Log.d("RegisterActivity", "Navigation called from:")
Thread.currentThread().stackTrace.take(10).forEach { element ->
Log.d("RegisterActivity", " at ${element.className}.${element.methodName}(${element.fileName}:${element.lineNumber})")
val fragment = when (step) {
1 -> RegisterStep1Fragment.newInstance()
2 -> RegisterStep2Fragment.newInstance(userData)
3 -> RegisterStep3Fragment.newInstance()
else -> null
}
try {
val fragment = when (step) {
1 -> {
Log.d("RegisterActivity", "Creating RegisterStep1Fragment")
RegisterStep1Fragment.newInstance()
}
2 -> {
Log.d("RegisterActivity", "Creating RegisterStep2Fragment")
RegisterStep2Fragment.newInstance(userData)
}
3 -> {
Log.d("RegisterActivity", "Creating RegisterStep3Fragment")
RegisterStep3Fragment.newInstance()
}
else -> {
Log.e("RegisterActivity", "Invalid step: $step")
return
}
}
Log.d("RegisterActivity", "Fragment created, starting transaction")
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, fragment)
Log.d("RegisterActivity", "About to commit transaction")
transaction.commit()
Log.d("RegisterActivity", "Transaction committed")
// Update ViewModel step
registerViewModel.setStep(step)
Log.d("RegisterActivity", "ViewModel step updated to: $step")
} catch (e: Exception) {
Log.e("RegisterActivity", "Exception in navigateToStep: ${e.message}", e)
e.printStackTrace()
}
Log.d("RegisterActivity", "=== NAVIGATE TO STEP END ===")
}
// Handle Android back button - close activity or go to step 1
override fun onBackPressed() {
val currentStep = registerViewModel.currentStep.value ?: 1
if (currentStep == 1) {
// On step 1, exit the activity
super.onBackPressed()
} else {
// On other steps, go back to step 1
navigateToStep(1, null)
fragment?.let {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, it)
.addToBackStack(null)
.commit()
}
}
}

View File

@ -111,7 +111,7 @@ class ResetPassActivity : AppCompatActivity() {
}
private fun handleError(errorMessage: String) {
Log.e(TAG, "Error: $errorMessage")
Toast.makeText(this, "Error: $errorMessage", Toast.LENGTH_LONG).show()
// Optionally show error dialog
AlertDialog.Builder(this)

View File

@ -155,20 +155,19 @@ class RegisterStep1Fragment : Fragment() {
"email" -> {
isEmailValid = isValid
if (!isValid) {
Toast.makeText(requireContext(), "Email sudah digunakan. Gunakan email lainnya.", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "Email is already registered", Toast.LENGTH_SHORT).show()
}
}
"phone" -> {
isPhoneValid = isValid
if (!isValid) {
Toast.makeText(requireContext(), "Nomor handphone sudah digunakan. Gunakan nomor lainnya. ", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "Phone number is already registered", Toast.LENGTH_SHORT).show()
}
}
}
}
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
Toast.makeText(requireContext(), "Gagal melakukan validasi", Toast.LENGTH_SHORT).show()
Log.e(TAG, "Validation failed: ${result.exception.message}")
Toast.makeText(requireContext(), "Validation failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
}
}
}
@ -201,8 +200,7 @@ class RegisterStep1Fragment : Fragment() {
is Result.Error -> {
binding.progressBar.visibility = View.GONE
binding.btnNext.isEnabled = true
Log.e(TAG, "OTP Request Failed: ${result.exception.message}")
Toast.makeText(requireContext(), "Gagal mendapatkan OTP. Kirim ulang OTP", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "OTP Request Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
}
}
}
@ -231,13 +229,13 @@ class RegisterStep1Fragment : Fragment() {
// Check if all fields are filled
if (email.isEmpty() || password.isEmpty() || confirmPassword.isEmpty() || phone.isEmpty() ||
username.isEmpty() || fullName.isEmpty() || birthDate.isEmpty()) {
Toast.makeText(requireContext(), "Silahkan lengkapi seluruh isian", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "Please fill all required fields", Toast.LENGTH_SHORT).show()
return
}
// Check if passwords match
if (password != confirmPassword) {
Toast.makeText(requireContext(), "Konfirmasi kata sandi tidak sesua. Periksa kembali", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "Passwords do not match", Toast.LENGTH_SHORT).show()
return
}
@ -255,7 +253,7 @@ class RegisterStep1Fragment : Fragment() {
if (isEmailValid && isPhoneValid) {
requestOtp(email)
} else {
Toast.makeText(requireContext(), "Silahkan perbaiki data yang dimasukkan", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), "Please fix validation errors before proceeding", Toast.LENGTH_SHORT).show()
}
}

View File

@ -1,7 +1,5 @@
package com.alya.ecommerce_serang.ui.auth.fragments
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.CountDownTimer
@ -15,7 +13,6 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.FcmReq
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.OrderRepository
@ -27,15 +24,11 @@ import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.RegisterViewModel
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.firebase.messaging.FirebaseMessaging
class RegisterStep2Fragment : Fragment() {
private var _binding: FragmentRegisterStep2Binding? = null
private val binding get() = _binding!!
private lateinit var sessionManager: SessionManager
private var countDownTimer: CountDownTimer? = null
private var timeRemaining = 30
private var isTimerRunning = false
// In RegisterStep2Fragment AND RegisterStep3Fragment:
private val registerViewModel: RegisterViewModel by activityViewModels {
@ -46,8 +39,8 @@ class RegisterStep2Fragment : Fragment() {
RegisterViewModel(userRepository, orderRepository, requireContext())
}
}
// private var countDownTimer: CountDownTimer? = null
// private var timeRemaining = 30 // 30 seconds cooldown for resend
private var countDownTimer: CountDownTimer? = null
private var timeRemaining = 30 // 30 seconds cooldown for resend
companion object {
@ -119,20 +112,6 @@ class RegisterStep2Fragment : Fragment() {
observeRegistrationState()
observeLoginState()
Log.d(TAG, "Registration and login state observers set up")
binding.btnBack.setOnClickListener {
Log.d(TAG, "Back button clicked - cleaning up timer and going to step 1")
// Stop the timer before navigating
stopTimer()
// Small delay to ensure timer is properly canceled
binding.root.postDelayed({
// (activity as? RegisterActivity)?.navigateToStep(1, null)
val intent = Intent(requireContext(), RegisterActivity::class.java)
startActivity(intent)
requireActivity().finish()
}, 100)
}
}
private fun verifyOtp(userData: RegisterRequest?) {
@ -150,6 +129,11 @@ class RegisterStep2Fragment : Fragment() {
Log.d(TAG, "Updating user data with OTP: $otp")
registerViewModel.updateUserData(updatedUserData)
// For demo purposes, we're just proceeding to Step 3
// In a real app, you would verify the OTP with the server first
// registerViewModel.setStep(3)
// (activity as? RegisterActivity)?.navigateToStep(3, updatedUserData)
registerViewModel.registerUser(updatedUserData)
} ?: Log.e(TAG, "userData is null, cannot proceed with verification")
}
@ -186,9 +170,37 @@ class RegisterStep2Fragment : Fragment() {
} ?: Log.e(TAG, "Cannot resend OTP: email is null")
}
private fun startResendCooldown() {
Log.d(TAG, "startResendCooldown called")
timeRemaining = 30
binding.tvResendOtp.isEnabled = false
binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.soft_gray))
countDownTimer?.cancel()
countDownTimer = object : CountDownTimer(30000, 1000) {
override fun onTick(millisUntilFinished: Long) {
timeRemaining = (millisUntilFinished / 1000).toInt()
binding.tvTimer.text = "Kirim ulang OTP dalam waktu 00:${String.format("%02d", timeRemaining)}"
if (timeRemaining % 5 == 0) {
Log.d(TAG, "Cooldown remaining: $timeRemaining seconds")
}
}
override fun onFinish() {
Log.d(TAG, "Cooldown finished, enabling resend button")
binding.tvTimer.text = "Dapat mengirim ulang kode OTP"
binding.tvResendOtp.isEnabled = true
binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.blue1))
timeRemaining = 0
}
}.start()
}
private fun observeRegistrationState() {
registerViewModel.message.observe(viewLifecycleOwner) { message ->
Log.d(TAG, "Message from server: $message")
// You can use the message here if needed, e.g., for showing in a specific UI element
// or for storing for later use
}
registerViewModel.registerState.observe(viewLifecycleOwner) { result ->
when (result) {
@ -238,8 +250,6 @@ class RegisterStep2Fragment : Fragment() {
// Save the token in fragment
val accessToken = result.data.accessToken
sessionManager.saveToken(accessToken)
retrieveFCMToken()
Log.d(TAG, "Token saved to SessionManager: $accessToken")
// Proceed to Step 3
@ -269,116 +279,9 @@ class RegisterStep2Fragment : Fragment() {
}
}
private fun retrieveFCMToken() {
FirebaseMessaging.getInstance().token
.addOnCompleteListener { task ->
if (!task.isSuccessful) {
Log.e(TAG, "Failed to get FCM token", task.exception)
return@addOnCompleteListener
}
val token = task.result
// tokenTes = token
Log.d(TAG, "FCM token retrieved: $token")
// Save token locally
val sharedPreferences = requireContext().getSharedPreferences("FCM_PREFS", Context.MODE_PRIVATE)
sharedPreferences.edit().putString("FCM_TOKEN", token).apply()
// Send to your server
sendTokenToServer(token)
}
}
private fun sendTokenToServer(token: String) {
Log.d(TAG, "Would send token to server: $token")
val tokenFcm=FcmReq(
fcmToken = token
)
registerViewModel.sendFcm(tokenFcm)
Log.d(TAG, "Sent token fcm: $token")
}
private fun startResendCooldown() {
Log.d(TAG, "startResendCooldown called")
// Cancel any existing timer first
stopTimer()
timeRemaining = 30
isTimerRunning = true
binding.tvResendOtp.isEnabled = false
binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.soft_gray))
countDownTimer = object : CountDownTimer(30000, 1000) {
override fun onTick(millisUntilFinished: Long) {
if (!isTimerRunning) {
cancel()
return
}
timeRemaining = (millisUntilFinished / 1000).toInt()
// Check if fragment is still attached before updating UI
if (isAdded && _binding != null) {
binding.tvTimer.text = "Kirim ulang OTP dalam waktu 00:${String.format("%02d", timeRemaining)}"
if (timeRemaining % 5 == 0) {
Log.d(TAG, "Cooldown remaining: $timeRemaining seconds")
}
}
}
override fun onFinish() {
if (!isTimerRunning) return
Log.d(TAG, "Cooldown finished, enabling resend button")
// Check if fragment is still attached before updating UI
if (isAdded && _binding != null) {
binding.tvTimer.text = "Dapat mengirim ulang kode OTP"
binding.tvResendOtp.isEnabled = true
binding.tvResendOtp.setTextColor(ContextCompat.getColor(requireContext(), R.color.blue1))
timeRemaining = 0
}
isTimerRunning = false
}
}.start()
}
private fun stopTimer() {
Log.d(TAG, "stopTimer called")
isTimerRunning = false
countDownTimer?.cancel()
countDownTimer = null
}
override fun onPause() {
super.onPause()
Log.d(TAG, "onPause - stopping timer")
stopTimer()
}
override fun onStop() {
super.onStop()
Log.d(TAG, "onStop - stopping timer")
stopTimer()
}
override fun onDestroyView() {
Log.d(TAG, "onDestroyView - cleaning up")
super.onDestroyView()
// Ensure timer is stopped
stopTimer()
countDownTimer?.cancel()
_binding = null
}
override fun onDetach() {
super.onDetach()
Log.d(TAG, "onDetach - final cleanup")
stopTimer()
}
}

View File

@ -96,21 +96,26 @@ class RegisterStep3Fragment : Fragment() {
Log.d(TAG, "Auto-filled name: ${it.name}, phone: ${it.phone}")
}
// Set up province and city dropdowns
setupAutoComplete()
setupEdgeToEdge()
// Set up button listeners
binding.btnPrevious.setOnClickListener {
val step2Fragment = RegisterStep2Fragment()
parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container, step2Fragment)
.commit()
// Go back to the previous step
parentFragmentManager.popBackStack()
}
binding.btnRegister.setOnClickListener {
submitAddress()
}
// If user skips address entry
// binding.btnSkip.setOnClickListener {
// showRegistrationSuccess()
// }
// Observe address submission state
observeAddressSubmissionState()
@ -498,8 +503,7 @@ class RegisterStep3Fragment : Fragment() {
private fun showRegistrationSuccess() {
// Now we can show the success message for the overall registration process
Toast.makeText(requireContext(), "Berhasil mendaftarkan akun", Toast.LENGTH_LONG).show()
sessionManager.clearAll()
Toast.makeText(requireContext(), "Registration completed successfully!", Toast.LENGTH_LONG).show()
// Navigate to login screen
startActivity(Intent(requireContext(), LoginActivity::class.java))
@ -517,5 +521,4 @@ class RegisterStep3Fragment : Fragment() {
ViewCompat.setWindowInsetsAnimationCallback(binding.root, null)
_binding = null
}
}

View File

@ -41,16 +41,11 @@ class CartActivity : AppCompatActivity() {
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager)
binding = ActivityCartBinding.inflate(layoutInflater)
setContentView(binding.root)
if (!sessionManager.isLoggedIn()){
binding.emptyCart.text = "Silahkan masuk terlebih dahulu"
}
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager)
WindowCompat.setDecorFitsSystemWindows(window, false)
@ -123,7 +118,7 @@ class CartActivity : AppCompatActivity() {
// Start checkout with the prepared items
startCheckoutWithWholesaleInfo(selectedItems)
} else {
Toast.makeText(this, "Pilih produk yang sama dengan toko", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Please select items from a single store only", Toast.LENGTH_SHORT).show()
}
}
} else {

View File

@ -124,7 +124,7 @@ class ChatActivity : AppCompatActivity() {
if (token.isEmpty()) {
// User not logged in, redirect to login
Toast.makeText(this, "Silahkan masuk terlebih dahulu", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Please login first", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, LoginActivity::class.java))
finish()
return
@ -506,7 +506,7 @@ class ChatActivity : AppCompatActivity() {
}
startActivity(intent)
} catch (e: Exception) {
Toast.makeText(this, "Gagal memuat produk", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Cannot open product details", Toast.LENGTH_SHORT).show()
Log.e(TAG, "Error navigating to product detail", e)
}
}
@ -622,7 +622,7 @@ class ChatActivity : AppCompatActivity() {
if (outputFile.exists() && outputFile.length() > 0) {
if (outputFile.length() > 5 * 1024 * 1024) {
Log.e(TAG, "File too large: ${outputFile.length()} bytes")
Toast.makeText(this, "Gambar terlalu besar. Maksimal 1MB", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Image too large (max 5MB)", Toast.LENGTH_SHORT).show()
return
}

View File

@ -5,6 +5,7 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
@ -79,10 +80,8 @@ class ChatListFragment : Fragment() {
}
}
is Result.Error -> {
// binding.tvEmptyChat.visibility = View.VISIBLE
binding.progressBarChat.visibility = View.VISIBLE
// Toast.makeText(requireContext(), "Failed to load chats", Toast.LENGTH_SHORT).show()
Log.e(TAG, "Failed to load chats")
binding.tvEmptyChat.visibility = View.VISIBLE
Toast.makeText(requireContext(), "Failed to load chats", Toast.LENGTH_SHORT).show()
}
Result.Loading -> {
binding.progressBarChat.visibility = View.VISIBLE

View File

@ -6,7 +6,6 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@ -33,7 +32,6 @@ import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.setLightStatusBar
import com.alya.ecommerce_serang.utils.viewmodel.HomeUiState
import com.alya.ecommerce_serang.utils.viewmodel.HomeViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
//@AndroidEntryPoint
@ -69,10 +67,12 @@ class HomeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initUi()
setupRecyclerView()
observeData()
setupSearchView()
}
private fun setupRecyclerView() {
@ -140,26 +140,24 @@ class HomeFragment : Fragment() {
viewModel.uiState.collect { state ->
when (state) {
is HomeUiState.Loading -> {
binding.loadingAll.root.visibility = View.VISIBLE
binding.loading.root.isVisible = true
binding.error.root.isVisible = false
binding.home.isVisible = false
}
is HomeUiState.Success -> {
val products = state.products
viewModel.loadStoresForProducts(products)
delay(2000)
binding.loadingAll.root.visibility = View.GONE
binding.loading.root.isVisible = false
binding.error.root.isVisible = false
binding.home.isVisible = true
val products = state.products
viewModel.loadStoresForProducts(products) // << add this here
productAdapter?.updateLimitedProducts(products)
}
is HomeUiState.Error -> {
binding.loadingAll.root.visibility = View.GONE
binding.loading.root.isVisible = false
binding.error.root.isVisible = true
binding.home.isVisible = false
// binding.error.errorMessage.text = state.message
Log.e("HomeFragment", "Error load data: ${state.message}")
Toast.makeText(requireContext(), "Terjadi kendala. Muat ulang halaman", Toast.LENGTH_SHORT) .show()
binding.error.errorMessage.text = state.message
binding.error.retryButton.setOnClickListener {
viewModel.retry()
}
@ -168,6 +166,7 @@ class HomeFragment : Fragment() {
}
}
}
viewLifecycleOwner.lifecycleScope.launch {

View File

@ -7,15 +7,12 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.ProductRepository
@ -109,11 +106,6 @@ class SearchHomeFragment : Fragment() {
}
})
val searchText = findViewById<TextView>(androidx.appcompat.R.id.search_src_text)
searchText.textSize = 14f // in sp
searchText.setHintTextColor(ContextCompat.getColor(context, R.color.black_200))
searchText.setTextColor(ContextCompat.getColor(context, R.color.black))
if (args.query.isNullOrEmpty()) {
requestFocus()
post {

View File

@ -78,4 +78,4 @@ import com.google.firebase.messaging.RemoteMessage
val notificationId = System.currentTimeMillis().toInt()
notificationManager.notify(notificationId, notificationBuilder.build())
}
}
}

View File

@ -154,18 +154,8 @@ class CheckoutActivity : AppCompatActivity() {
// Observe address details
viewModel.addressDetails.observe(this) { address ->
if (address != null) {
// Show selected address
binding.containerEmptyAddress.visibility = View.GONE
binding.containerAddress.visibility = View.VISIBLE
binding.tvPlacesAddress.text = address.recipient
binding.tvAddress.text = "${address.street}, ${address.subdistrict}"
} else {
// Show empty address state
binding.containerEmptyAddress.visibility = View.VISIBLE
binding.containerAddress.visibility = View.GONE
}
binding.tvPlacesAddress.text = address?.recipient
binding.tvAddress.text = "${address?.street}, ${address?.subdistrict}"
}
viewModel.availablePaymentMethods.observe(this) { paymentMethods ->
@ -182,7 +172,9 @@ class CheckoutActivity : AppCompatActivity() {
// Update the adapter ONLY if it exists
paymentAdapter?.let { adapter ->
// This line was causing issues - using setSelectedPayment instead of setSelectedPaymentName
adapter.setSelectedPaymentId(selectedPayment.id)
Log.d("CheckoutActivity", "Updated adapter with selected payment: ${selectedPayment.id}")
}
}
@ -191,20 +183,20 @@ class CheckoutActivity : AppCompatActivity() {
// Observe loading state
viewModel.isLoading.observe(this) { isLoading ->
binding.btnPay.isEnabled = !isLoading
}
// Observe error messages
viewModel.errorMessage.observe(this) { message ->
if (message.isNotEmpty()) {
Toast.makeText(this, "Terdapat kendala di pemesanan", Toast.LENGTH_SHORT).show()
Log.e("CheckoutActivity", "Error from errorMessage: $message")
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
// Observe order creation
viewModel.orderCreated.observe(this) { created ->
if (created) {
Toast.makeText(this, "Berhasil membuat pesanan", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Order successfully created!", Toast.LENGTH_SHORT).show()
setResult(RESULT_OK)
finish()
}
@ -214,17 +206,10 @@ class CheckoutActivity : AppCompatActivity() {
private fun setupPaymentMethodsRecyclerView(paymentMethods: List<DetailPaymentItem>) {
if (paymentMethods.isEmpty()) {
Log.e("CheckoutActivity", "Payment methods list is empty")
Toast.makeText(this, "Tidak ditemukan metode pembayaran", Toast.LENGTH_SHORT).show()
// Show empty payment state
binding.containerEmptyPayment.visibility = View.VISIBLE
binding.rvPaymentInfo.visibility = View.GONE
Toast.makeText(this, "No payment methods available", Toast.LENGTH_SHORT).show()
return
}
binding.containerEmptyPayment.visibility = View.GONE
binding.rvPaymentInfo.visibility = View.VISIBLE
// Debug logging
Log.d("CheckoutActivity", "Setting up payment methods: ${paymentMethods.size} methods available")
@ -281,16 +266,6 @@ class CheckoutActivity : AppCompatActivity() {
this.adapter = adapter
isNestedScrollingEnabled = false
}
// if (checkoutData.cartItems.isEmpty()) {
// // Show empty products state
// binding.containerEmptyProducts.visibility = View.VISIBLE
// binding.rvProductItems.visibility = View.GONE
// return
// }
binding.containerEmptyProducts.visibility = View.GONE
binding.rvProductItems.visibility = View.VISIBLE
}
private fun updateOrderSummary() {
@ -315,8 +290,7 @@ class CheckoutActivity : AppCompatActivity() {
private fun updateShippingUI(shipName: String, shipService: String, shipEtd: String, shipPrice: Int) {
if (shipName.isNotEmpty() && shipService.isNotEmpty()) {
// Hide empty state and show selected shipping
binding.containerEmptyShipping.visibility = View.GONE
// Display shipping name and service in one line
binding.cardShipment.visibility = View.VISIBLE
binding.tvCourierName.text = "$shipName $shipService"
@ -324,8 +298,6 @@ class CheckoutActivity : AppCompatActivity() {
binding.tvShippingPrice.text = formatCurrency(shipPrice.toDouble())
binding.rbJne.isChecked = true
} else {
// Show empty shipping state
binding.containerEmptyShipping.visibility = View.VISIBLE
binding.cardShipment.visibility = View.GONE
}
}
@ -338,10 +310,10 @@ class CheckoutActivity : AppCompatActivity() {
}
// Shipping method selection
binding.tvShippingOption.setOnClickListener {
binding.layoutShippingMethod.setOnClickListener {
val addressId = viewModel.addressDetails.value?.id ?: 0
if (addressId <= 0) {
Toast.makeText(this, "Silahkan pilih alamat dahulu", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Please select delivery address first", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
@ -391,7 +363,7 @@ class CheckoutActivity : AppCompatActivity() {
viewModel.setSelectedAddress(addressId)
// You might want to show a toast or some UI feedback
Toast.makeText(this, "Berhasil memilih alamat", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Address selected successfully", Toast.LENGTH_SHORT).show()
}
}
}

View File

@ -145,8 +145,6 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
isReseller = isWholesaleMap.any { it.value }
)
Log.d(TAG, "Cek is reseller: ${orderRequest.isReseller}")
_checkoutData.value = CheckoutData(
orderRequest = orderRequest,
productName = matchingItems.first().productName,
@ -380,7 +378,6 @@ class CheckoutViewModel(private val repository: OrderRepository) : ViewModel() {
} else {
// For Cart checkout, use the standard order endpoint
val cartRequest = data.orderRequest as OrderRequest
Log.d(TAG, "data: ${cartRequest.cartItemId}")
repository.createOrder(cartRequest)
}

View File

@ -67,7 +67,7 @@ class ShippingActivity : AppCompatActivity() {
// Validate required information
if (addressId <= 0 || productId <= 0) {
Log.e(TAG, "Missing required shipping information: addressId=$addressId, productId=$productId")
Toast.makeText(this, "Gagal memuat pengiriman", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Missing required shipping information", Toast.LENGTH_SHORT).show()
finish()
return
}

View File

@ -52,12 +52,12 @@ class AddressActivity : AppCompatActivity() {
windowInsets
}
viewModel.fetchAddresses()
setupToolbar()
setupRecyclerView()
setupObservers()
viewModel.fetchAddresses()
}
@ -126,11 +126,6 @@ class AddressActivity : AppCompatActivity() {
finish()
}
override fun onResume() {
super.onResume()
viewModel.fetchAddresses()
}
companion object {
const val EXTRA_ADDRESS_ID = "extra_address_id"
}

View File

@ -4,7 +4,6 @@ import android.content.Context
import android.util.Log
import android.widget.ArrayAdapter
import android.widget.Spinner
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.customer.order.CitiesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.ProvincesItem
import com.alya.ecommerce_serang.data.api.response.customer.order.SubdistrictsItem
@ -135,8 +134,23 @@ class BankAdapter(
}
private fun loadHardcodedData() {
val bankNames = context.resources.getStringArray(R.array.bank_names)
val defaultBanks = bankNames.map { BankItem(bankName = it) }
val defaultBanks = listOf(
BankItem("Bank Mandiri", "008", "PT Bank Mandiri (Persero) Tbk"),
BankItem("Bank BRI", "002", "PT Bank Rakyat Indonesia (Persero) Tbk"),
BankItem("Bank BCA", "014", "PT Bank Central Asia Tbk"),
BankItem("Bank BNI", "009", "PT Bank Negara Indonesia (Persero) Tbk"),
BankItem("Bank BTN", "200", "PT Bank Tabungan Negara (Persero) Tbk"),
BankItem("Bank CIMB Niaga", "022", "PT Bank CIMB Niaga Tbk"),
BankItem("Bank Danamon", "011", "PT Bank Danamon Indonesia Tbk"),
BankItem("Bank Permata", "013", "PT Bank Permata Tbk"),
BankItem("Bank OCBC NISP", "028", "PT Bank OCBC NISP Tbk"),
BankItem("Bank Maybank", "016", "PT Bank Maybank Indonesia Tbk"),
BankItem("Bank Panin", "019", "PT Bank Panin Dubai Syariah Tbk"),
BankItem("Bank UOB", "023", "PT Bank UOB Indonesia"),
BankItem("Bank Mega", "426", "PT Bank Mega Tbk"),
BankItem("Bank Bukopin", "441", "PT Bank Bukopin Tbk"),
BankItem("Bank BJB", "110", "PT Bank Pembangunan Daerah Jawa Barat dan Banten Tbk")
)
updateData(defaultBanks)
}

View File

@ -61,8 +61,8 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
}
private val paymentMethods = arrayOf(
"Pilih Metode Pembayaran",
"Transfer Bank",
"QRIS",
)
// private val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
@ -122,6 +122,7 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
} catch (e: Exception) {
Log.e(TAG, "ERROR in AddEvidencePaymentActivity onCreate: ${e.message}", e)
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_LONG).show()
}
}
@ -287,7 +288,7 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
}
} catch (e: Exception) {
Log.e(TAG, "Error handling selected image", e)
Toast.makeText(this, "Terjadi kendala", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
@ -313,10 +314,10 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
return
}
// if (binding.spinnerPaymentMethod.selectedItemPosition == 0) {
// Toast.makeText(this, "Silahkan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
// return
// }
if (binding.spinnerPaymentMethod.selectedItemPosition == 0) {
Toast.makeText(this, "Silahkan pilih metode pembayaran", Toast.LENGTH_SHORT).show()
return
}
binding.etAccountNumber.visibility = View.GONE
// if (binding.etAccountNumber.text.toString().trim().isEmpty()) {
@ -324,10 +325,10 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
// return
// }
// if (binding.tvPaymentDate.text.toString() == "Pilih tanggal") {
// Toast.makeText(this, "Silahkan pilih tanggal pembayaran", Toast.LENGTH_SHORT).show()
// return
// }
if (binding.tvPaymentDate.text.toString() == "Pilih tanggal") {
Toast.makeText(this, "Silahkan pilih tanggal pembayaran", Toast.LENGTH_SHORT).show()
return
}
// All validations passed, proceed with upload
uploadPaymentProof()
@ -366,7 +367,7 @@ class AddEvidencePaymentActivity : AppCompatActivity() {
viewModel.uploadPaymentProof(request)
} catch (e: Exception) {
Log.e(TAG, "Error creating upload request: ${e.message}", e)
Toast.makeText(this, "Gagal mengunggah foto", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Error preparing upload: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}

View File

@ -160,8 +160,7 @@ class PaymentActivity : AppCompatActivity() {
viewModel.error.observe(this) { error ->
if (error.isNotEmpty()) {
Toast.makeText(this, "Gagal melakukan pembayaran", Toast.LENGTH_SHORT).show()
Log.e(TAG, "Failed payment: $error")
Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
}
}
}

View File

@ -27,6 +27,7 @@ class HistoryActivity : AppCompatActivity() {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHistoryBinding.inflate(layoutInflater)

View File

@ -37,6 +37,9 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() {
private const val TAG = "HistoryViewModel"
}
// private val _orders = MutableLiveData<ViewState<List<OrdersItem>>>()
// val orders: LiveData<ViewState<List<OrdersItem>>> = _orders
private val _orderCompletionStatus = MutableLiveData<Result<CompletedOrderResponse>>()
val orderCompletionStatus: LiveData<Result<CompletedOrderResponse>> = _orderCompletionStatus
@ -110,6 +113,83 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() {
ViewState.Loading // ② initial value, still fine
)
// fun getOrderList(status: String) {
// _orders.value = ViewState.Loading
// viewModelScope.launch {
// try {
// if (status == "all") {
// // Get all orders by combining all statuses
// getAllOrdersCombined()
// } else {
// // Get orders for specific status
// when (val result = repository.getOrderList(status)) {
// is Result.Success -> {
// _orders.value = ViewState.Success(result.data.orders)
// Log.d(TAG, "Orders loaded successfully: ${result.data.orders.size} items")
// }
// is Result.Error -> {
// _orders.value = ViewState.Error(result.exception.message ?: "Unknown error occurred")
// Log.e(TAG, "Error loading orders", result.exception)
// }
// is Result.Loading -> {
// // Keep loading state
// }
// }
// }
// } catch (e: Exception) {
// _orders.value = ViewState.Error("An unexpected error occurred: ${e.message}")
// Log.e(TAG, "Exception in getOrderList", e)
// }
// }
// }
// private suspend fun getAllOrdersCombined() {
// try {
// val allStatuses = listOf("unpaid", "paid", "processed", "shipped", "completed", "canceled")
// val allOrders = mutableListOf<OrdersItem>()
//
// // Use coroutineScope to allow launching async blocks
// coroutineScope {
// val deferreds = allStatuses.map { status ->
// async {
// when (val result = repository.getOrderList(status)) {
// is Result.Success -> {
// // Tag each order with the status it was fetched from
// result.data.orders.onEach { it.displayStatus = status }
// }
// is Result.Error -> {
// Log.e(TAG, "Error loading orders for status $status", result.exception)
// emptyList<OrdersItem>()
// }
// is Result.Loading -> emptyList<OrdersItem>()
// }
// }
// }
//
// // Await all results and combine
// deferreds.awaitAll().forEach { orders ->
// allOrders.addAll(orders)
// }
// }
//
// // Sort orders
// val sortedOrders = allOrders.sortedByDescending { order ->
// try {
// SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()).parse(order.createdAt)
// } catch (e: Exception) {
// null
// }
// }
//
// _orders.value = ViewState.Success(sortedOrders)
// Log.d(TAG, "All orders loaded successfully: ${sortedOrders.size} items")
//
// } catch (e: Exception) {
// _orders.value = ViewState.Error("An unexpected error occurred: ${e.message}")
// Log.e(TAG, "Exception in getAllOrdersCombined", e)
// }
// }
private suspend fun getAllOrdersCombined(): ViewState<List<OrdersItem>> = try {
val statuses = listOf("unpaid", "paid", "processed", "shipped", "completed", "canceled")
@ -212,6 +292,11 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() {
}
}
// fun refreshOrders(status: String = "all") {
// Log.d(TAG, "Refreshing orders with status: $status")
// // Don't set Loading here if you want to show current data while refreshing
// getOrderList(status)
// }
fun updateStatus(status: String, forceRefresh: Boolean = false) {
Log.d(TAG, "↪️ updateStatus(status = $status, forceRefresh = $forceRefresh)")

View File

@ -200,6 +200,10 @@ class OrderHistoryAdapter(
// viewModel.refreshOrders()
}
}
// deadlineDate.apply {
// visibility = View.VISIBLE
// text = formatDatePay(order.updatedAt)
// }
}
"processed" -> {
// Untuk status processed, tampilkan "Hubungi Penjual"
@ -211,6 +215,15 @@ class OrderHistoryAdapter(
visibility = View.VISIBLE
text = itemView.context.getString(R.string.dl_processed)
}
// gabisa complaint
// btnLeft.apply {
// visibility = View.VISIBLE
// text = itemView.context.getString(R.string.canceled_order_btn)
// setOnClickListener {
// showCancelOrderDialog(order.orderId.toString())
// viewModel.refreshOrders()
// }
// }
}
"shipped" -> {
// Untuk status shipped, tampilkan "Lacak Pengiriman" dan "Terima Barang"
@ -504,14 +517,14 @@ class OrderHistoryAdapter(
} else {
// Log error and show a Toast instead if we can't get a FragmentManager
Log.e("OrderHistoryAdapter", "Cannot show bottom sheet: Context is not a FragmentActivity")
Toast.makeText(context, "Terjadi kendala di batalkan pesanan", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Cannot show cancel order dialog", Toast.LENGTH_SHORT).show()
return
}
}
else -> {
// Log error and show a Toast instead if we can't get a FragmentManager
Log.e("OrderHistoryAdapter", "Cannot show bottom sheet: Context is not a FragmentActivity")
Toast.makeText(context, "Terjadi kendala di batalkan pesanan", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Cannot show cancel order dialog", Toast.LENGTH_SHORT).show()
return
}
}
@ -522,7 +535,7 @@ class OrderHistoryAdapter(
onOrderCancelled = {
callbacks.onOrderCancelled(orderId.toString(), true, "Order cancelled successfully")
// Show a success message
Toast.makeText(context, "Pesanan berhasil dibatalkan", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Order cancelled successfully", Toast.LENGTH_SHORT).show()
}
)
@ -534,10 +547,18 @@ class OrderHistoryAdapter(
// Use ViewModel to fetch order details
viewModel.getOrderDetails(order.orderId)
// Create loading dialog
// val loadingDialog = Dialog(itemView.context).apply {
// requestWindowFeature(Window.FEATURE_NO_TITLE)
// setContentView(R.layout.dialog_loading)
// window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
// setCancelable(false)
// }
// loadingDialog.show()
viewModel.error.observe(itemView.findViewTreeLifecycleOwner()!!) { errorMsg ->
if (!errorMsg.isNullOrEmpty()) {
Log.e("OrderHistoryAdapter", "Error $errorMsg")
Toast.makeText(itemView.context, "Terdapat kendala di tambah ulasan", Toast.LENGTH_SHORT).show()
Toast.makeText(itemView.context, errorMsg, Toast.LENGTH_SHORT).show()
}
}
@ -573,7 +594,7 @@ class OrderHistoryAdapter(
} else {
Toast.makeText(
itemView.context,
"Tidak ada produk untuk direview",
"No items to review",
Toast.LENGTH_SHORT
).show()
}

View File

@ -44,6 +44,8 @@ class OrderHistoryFragment : Fragment() {
sessionManager = SessionManager(requireContext())
setupViewPager()
}
private fun setupViewPager() {
@ -65,25 +67,17 @@ class OrderHistoryFragment : Fragment() {
}
}.attach()
statusPage()
}
private fun statusPage(){
binding.viewPager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
val status = viewPagerAdapter.orderStatuses[position]
/* setStatus() is the API we added earlier; TRUE → always requery */
historyVm.updateStatus(status, forceRefresh = true)
}
}
)
}
override fun onResume() {
super.onResume()
statusPage()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null

View File

@ -3,7 +3,6 @@ package com.alya.ecommerce_serang.ui.order.history
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -85,6 +84,8 @@ class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks {
setupRecyclerView()
observeOrderList()
observeViewModel()
// observeOrderCompletionStatus()
// loadOrders()
}
private fun setupRecyclerView() {
@ -105,6 +106,31 @@ class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks {
}
private fun observeOrderList() {
// Now we only need to observe one LiveData for all cases
// viewModel.orders.observe(viewLifecycleOwner) { result ->
// when (result) {
// is ViewState.Success -> {
// binding.progressBar.visibility = View.GONE
//
// if (result.data.isNullOrEmpty()) {
// binding.tvEmptyState.visibility = View.VISIBLE
// binding.rvOrders.visibility = View.GONE
// } else {
// binding.tvEmptyState.visibility = View.GONE
// binding.rvOrders.visibility = View.VISIBLE
// orderAdapter.submitList(result.data)
// }
// }
// is ViewState.Error -> {
// binding.progressBar.visibility = View.GONE
// binding.tvEmptyState.visibility = View.VISIBLE
// Toast.makeText(requireContext(), result.message, Toast.LENGTH_SHORT).show()
// }
// is ViewState.Loading -> {
// binding.progressBar.visibility = View.VISIBLE
// }
// }
// }
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.orders.collect { state ->
when (state) {
@ -115,7 +141,7 @@ class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks {
binding.progressBar.isVisible = false
binding.tvEmptyState.isVisible = true
binding.rvOrders.isVisible = false
Log.e("OrderListFragment", "Error in order list: ${state.message}")
Toast.makeText(requireContext(), state.message, Toast.LENGTH_SHORT).show()
}
is ViewState.Success -> {
binding.progressBar.isVisible = false
@ -131,19 +157,47 @@ class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks {
}
private fun observeViewModel() {
// Observe order completion
// viewModel.orderCompletionStatus.observe(viewLifecycleOwner) { result ->
// when (result) {
// is Result.Success -> {
// Toast.makeText(requireContext(), "Order completed successfully!", Toast.LENGTH_SHORT).show()
//// loadOrders() // Refresh here
// }
// is Result.Error -> {
// Toast.makeText(requireContext(), "Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
// }
// is Result.Loading -> {
// // Show loading if needed
// }
// }
// }
//
// // Observe cancel order status
// viewModel.cancelOrderStatus.observe(viewLifecycleOwner) { result ->
// when (result) {
// is Result.Success -> {
// Toast.makeText(requireContext(), "Order cancelled successfully!", Toast.LENGTH_SHORT).show()
// loadOrders() // Refresh here
// }
// is Result.Error -> {
// Toast.makeText(requireContext(), "Failed to cancel: ${result.exception.message}", Toast.LENGTH_SHORT).show()
// }
// is Result.Loading -> {
// // Show loading if needed
// }
// }
// }
viewModel.orderCompletionStatus.observe(viewLifecycleOwner) { result ->
when (result) {
is Result.Success -> {
Toast.makeText(requireContext(),
"Pesanan Selesai", Toast.LENGTH_SHORT).show()
Log.d("OrderListFragment", "Order selesai")
"Order completed!", Toast.LENGTH_SHORT).show()
viewModel.updateStatus(status, forceRefresh = true)
}
is Result.Error ->
// Toast.makeText(requireContext(),
// "Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
Log.e("OrderListFragment", "Failed: ${result.exception.message}")
Toast.makeText(requireContext(),
"Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
else -> { /* Loading → no UI change */ }
}
}
@ -152,19 +206,31 @@ class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks {
when (result) {
is Result.Success -> {
Toast.makeText(requireContext(),
"Pesanan Dibatalkan", Toast.LENGTH_SHORT).show()
Log.d("OrderListFragment", "Order dibatalkan")
"Order cancelled!", Toast.LENGTH_SHORT).show()
viewModel.updateStatus(status, forceRefresh = true)
}
is Result.Error ->
Log.e("OrderListFragment", "Failed: ${result.exception.message}")
// Toast.makeText(requireContext(),
// "Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(),
"Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
else -> { /* Loading */ }
}
}
}
// private fun loadOrders() {
// // Simple - just call getOrderList for any status including "all"
// viewModel.getOrderList(status)
// }
// private val detailOrderLauncher = registerForActivityResult(
// ActivityResultContracts.StartActivityForResult()
// ) { result ->
// if (result.resultCode == Activity.RESULT_OK) {
// // Refresh order list when returning with OK result
//// loadOrders()
// }
// }
private fun navigateToOrderDetail(order: OrdersItem) {
val intent = Intent(requireContext(), DetailOrderStatusActivity::class.java).apply {
putExtra("ORDER_ID", order.orderId)
@ -173,10 +239,11 @@ class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks {
detailOrderLauncher.launch(intent)
}
override fun onOrderCancelled(orderId: String, success: Boolean, message: String) {
if (success) {
Toast.makeText(requireContext(), "Berhasil batalkan pesanan", Toast.LENGTH_SHORT).show()
Log.d("OrderListFragment", "Order cancel success: $message")
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
// loadOrders() // Refresh the list
if (success) viewModel.updateStatus(status, forceRefresh = true)
@ -187,13 +254,11 @@ class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks {
override fun onOrderCompleted(orderId: Int, success: Boolean, message: String) {
if (success) {
Toast.makeText(requireContext(), "Pesanan selesai", Toast.LENGTH_SHORT).show()
Log.d("OrderListFragment", "Pesanan selesai: $message")
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
// loadOrders() // Refresh the list
if (success) viewModel.updateStatus(status, forceRefresh = true)
} else {
Log.e("OrderListFragment", "Error Order Complete: $message")
Toast.makeText(requireContext(), "Terdapat kendala di pesanan selesai", Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
}
}
@ -206,8 +271,20 @@ class OrderListFragment : Fragment(), OrderHistoryAdapter.OrderActionCallbacks {
_binding = null
}
override fun onResume() {
super.onResume()
observeOrderList()
}
// private fun observeOrderCompletionStatus() {
// viewModel.orderCompletionStatus.observe(viewLifecycleOwner) { result ->
// when (result) {
// is Result.Loading -> {
// // Handle loading state if needed
// }
// is Result.Success -> {
// Toast.makeText(requireContext(), "Order completed successfully!", Toast.LENGTH_SHORT).show()
//// loadOrders()
// }
// is Result.Error -> {
// Toast.makeText(requireContext(), "Failed to complete order: ${result.exception.message}", Toast.LENGTH_SHORT).show()
// }
// }
// }
// }
}

View File

@ -67,7 +67,7 @@ class CancelOrderBottomSheet(
btnConfirm.setOnClickListener {
if (selectedReason == null) {
Toast.makeText(context, "Pilih alasan pembatalan", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "Please select a reason", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}

View File

@ -90,7 +90,7 @@ class CreateReviewActivity : AppCompatActivity() {
)
})
} catch (e: Exception) {
Toast.makeText(this, "Gagal memuat ulasan", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Error loading review items", Toast.LENGTH_SHORT).show()
finish()
}
} else {
@ -110,7 +110,7 @@ class CreateReviewActivity : AppCompatActivity() {
)
)
} else {
Toast.makeText(this, "Tidak ada produk untuk direview", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "No items to review", Toast.LENGTH_SHORT).show()
finish()
}
}

View File

@ -112,17 +112,13 @@ class DetailProductActivity : AppCompatActivity() {
when (result) {
is Result.Success -> {
updateStoreInfo(result.data)
binding.progressBarDetailStore.visibility = View.GONE
}
is Result.Error -> {
// Show error message, maybe a Toast or Snackbar
binding.progressBarDetailStore.visibility = View.GONE
Log.e("DetailProfileActivity", "Failed to load store: ${result.exception.message}")
Toast.makeText(this, "Kendala memuat toko", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Failed to load store: ${result.exception.message}", Toast.LENGTH_SHORT).show()
}
is Result.Loading -> {
// Show loading indicator if needed
binding.progressBarDetailStore.visibility = View.VISIBLE
}
}
}
@ -164,10 +160,6 @@ class DetailProductActivity : AppCompatActivity() {
val products = viewModel.otherProducts.value.orEmpty()
if (products.isNotEmpty()) {
updateOtherProducts(products, storeMap)
} else {
binding.emptyOtherProducts.visibility = View.VISIBLE
binding.recyclerViewOtherProducts.visibility = View.GONE
binding.tvViewAllProducts.visibility = View.GONE
}
}
}
@ -198,14 +190,12 @@ class DetailProductActivity : AppCompatActivity() {
private fun updateOtherProducts(products: List<ProductsItem>, storeMap: Map<Int, StoreItem>) {
if (products.isEmpty()) {
Log.d("DetailProductActivity", "Product list is empty, hiding RecyclerView")
binding.recyclerViewOtherProducts.visibility = View.GONE
binding.emptyOtherProducts.visibility = View.VISIBLE
binding.recyclerViewOtherProducts.visibility = View.VISIBLE
binding.tvViewAllProducts.visibility = View.GONE
} else {
Log.d("DetailProductActivity", "Displaying product list in RecyclerView")
binding.recyclerViewOtherProducts.visibility = View.VISIBLE
binding.tvViewAllProducts.visibility = View.VISIBLE
binding.emptyOtherProducts.visibility = View.GONE
productAdapter = OtherProductAdapter(products, onClick = { product ->
handleProductClick(product)
@ -326,13 +316,11 @@ class DetailProductActivity : AppCompatActivity() {
val limitedReviewList = if (reviewList.isNotEmpty()) listOf(reviewList.first()) else emptyList()
if (reviewList.isEmpty()) {
binding.recyclerViewReviews.visibility = View.GONE
binding.emptyReview.visibility = View.VISIBLE
binding.tvViewAllReviews.visibility = View.GONE
// binding.tvNoReviews.visibility = View.VISIBLE
} else {
binding.recyclerViewReviews.visibility = View.VISIBLE
binding.tvViewAllReviews.visibility = View.VISIBLE
binding.emptyReview.visibility = View.GONE
}
// binding.tvNoReviews.visibility = View.GONE
reviewsAdapter = ReviewsAdapter(
@ -531,11 +519,7 @@ class DetailProductActivity : AppCompatActivity() {
attachProduct = true // This will auto-attach the product!
)
}
override fun onResume() {
super.onResume()
loadData()
}
companion object {

View File

@ -64,9 +64,10 @@ class StoreDetailActivity : AppCompatActivity() {
)
windowInsets
}
loadData()
setupUI()
setupObservers()
loadData()
}
private fun setupUI() {
@ -87,18 +88,15 @@ class StoreDetailActivity : AppCompatActivity() {
viewModel.storeDetail.observe(this) { result ->
when (result) {
is Result.Success -> {
binding.progressBarDetailProdItem.visibility = View.GONE
updateStoreInfo(result.data)
viewModel.loadOtherProducts(result.data.storeId)
}
is Result.Error -> {
// Show error message, maybe a Toast or Snackbar
binding.progressBarDetailProdItem.visibility = View.GONE
Toast.makeText(this, "Failed to load store: ${result.exception.message}", Toast.LENGTH_SHORT).show()
}
is Result.Loading -> {
// Show loading indicator if needed
binding.progressBarDetailProdItem.visibility = View.VISIBLE
}
}
}
@ -111,9 +109,6 @@ class StoreDetailActivity : AppCompatActivity() {
val products = viewModel.otherProducts.value.orEmpty()
if (products.isNotEmpty()) {
updateProducts(products, storeMap)
} else {
binding.progressBarDetailProdItem.visibility = View.VISIBLE
binding.rvProducts.visibility = View.GONE
}
}
}
@ -151,7 +146,7 @@ class StoreDetailActivity : AppCompatActivity() {
.into(binding.ivStoreImage)
val ratingStr = it.storeRating
val ratingValue = ratingStr?.toFloatOrNull() ?: 0f
val ratingValue = ratingStr?.toFloatOrNull()
if (ratingValue != null && ratingValue > 0f) {
binding.tvStoreRating.text = String.format("%.1f", ratingValue)
@ -166,12 +161,10 @@ class StoreDetailActivity : AppCompatActivity() {
private fun updateProducts(products: List<ProductsItem>, storeMap: Map<Int, StoreItem>) {
if (products.isEmpty()) {
binding.rvProducts.visibility = View.GONE
binding.progressBarDetailProdItem.visibility = View.VISIBLE
Log.d("StoreDetailActivity", "Product list is empty, hiding RecyclerView")
} else {
Log.d("StoreDetailActivity", "Displaying product list in RecyclerView")
binding.progressBarDetailProdItem.visibility = View.GONE
binding.rvProducts.visibility = View.VISIBLE
productAdapter = HorizontalProductAdapter(products, onClick = { product ->
handleProductClick(product)

View File

@ -45,11 +45,11 @@ class StoreDetailViewModel (private val repository: ProductRepository
} // Filter by storeId and exclude current product
_otherProducts.value = filteredProducts // Update LiveData
} else if (result is Result.Error) {
Log.e("StoreDetailViewModel", "Error loading other products: ${result.exception.message}")
Log.e("ProductViewModel", "Error loading other products: ${result.exception.message}")
_otherProducts.value = emptyList() // Set empty list on failure
}
} catch (e: Exception) {
Log.e("StoreDetailViewModel", "Exception loading other products: ${e.message}")
Log.e("ProductViewModel", "Exception loading other products: ${e.message}")
_otherProducts.value = emptyList()
}
}
@ -67,7 +67,7 @@ class StoreDetailViewModel (private val repository: ProductRepository
loadStoreDetail(storeId)
}
} catch (e: Exception) {
Log.e("StoreDetailViewModel", "Error loading product details: ${e.message}")
Log.e("ProductViewModel", "Error loading product details: ${e.message}")
_error.value = "Failed to load product details: ${e.message}"
} finally {
_isLoading.value = false
@ -82,7 +82,7 @@ class StoreDetailViewModel (private val repository: ProductRepository
val result = repository.fetchStoreDetail(storeId)
_storeDetail.value = result
} catch (e: Exception) {
Log.e("StoreDetailViewModel", "Error loading store details: ${e.message}")
Log.e("ProductViewModel", "Error loading store details: ${e.message}")
_storeDetail.value = Result.Error(e)
}
}
@ -99,10 +99,10 @@ class StoreDetailViewModel (private val repository: ProductRepository
if (result is Result.Success) {
map[storeId] = result.data
} else if (result is Result.Error) {
Log.e("StoreDetailViewModel", "Failed to load storeId $storeId", result.exception)
Log.e("ProductViewModel", "Failed to load storeId $storeId", result.exception)
}
} catch (e: Exception) {
Log.e("StoreDetailViewModel", "Exception fetching storeId $storeId", e)
Log.e("ProductViewModel", "Exception fetching storeId $storeId", e)
}
}

View File

@ -1,73 +0,0 @@
package com.alya.ecommerce_serang.ui.profile
import android.os.Bundle
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Observer
import com.alya.ecommerce_serang.R
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.Result
import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityChangePasswordBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.ProfileViewModel
import kotlin.getValue
class ChangePasswordActivity : AppCompatActivity() {
private lateinit var binding: ActivityChangePasswordBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
private val viewModel: ProfileViewModel by viewModels {
BaseViewModelFactory {
apiService = ApiConfig.getApiService(sessionManager)
val userRepository = UserRepository(apiService)
ProfileViewModel(userRepository)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityChangePasswordBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
// Listen for the result of the password change
viewModel.changePasswordResult.observe(this, Observer { result ->
when (result) {
is Result.Error -> {
Toast.makeText(
this,
"Gagal mengubah kata sandi: ${result.exception.message}",
Toast.LENGTH_SHORT
).show()
}
is Result.Success -> {
Toast.makeText(this, "Berhasil mengubah kata sandi", Toast.LENGTH_SHORT).show()
finish()
}
is Result.Loading -> {}
}
})
// Button to trigger password change
binding.btnChangePass.setOnClickListener {
val currentPassword = binding.etLoginPassword.text.toString()
val newPassword = binding.etLoginNewPassword.text.toString()
if (currentPassword.isNotEmpty() && newPassword.isNotEmpty()) {
// Call change password function from ViewModel
viewModel.changePassword(currentPassword, newPassword)
} else {
Toast.makeText(this, "Lengkapi data", Toast.LENGTH_SHORT).show()
}
}
}
}

View File

@ -90,8 +90,6 @@ class DetailProfileActivity : AppCompatActivity() {
Log.e("DetailProfileActivity", "Error from ViewModel: $error")
Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
}
}
private fun setupClickListeners() {
@ -108,8 +106,7 @@ class DetailProfileActivity : AppCompatActivity() {
}
editProfileLauncher.launch(intent)
} ?: run {
Toast.makeText(this, "Akun tidak ditemukan", Toast.LENGTH_SHORT).show()
Log.e("DetailProfileActivity", "Profile data is not available")
Toast.makeText(this, "Profile data is not available", Toast.LENGTH_SHORT).show()
}
}
}

View File

@ -20,10 +20,10 @@ import com.alya.ecommerce_serang.data.repository.MyStoreRepository
import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.FragmentProfileBinding
import com.alya.ecommerce_serang.ui.auth.LoginActivity
import com.alya.ecommerce_serang.ui.profile.mystore.RegisterStoreActivity
import com.alya.ecommerce_serang.ui.order.address.AddressActivity
import com.alya.ecommerce_serang.ui.order.history.HistoryActivity
import com.alya.ecommerce_serang.ui.profile.mystore.MyStoreActivity
import com.alya.ecommerce_serang.ui.profile.mystore.RegisterStoreActivity
import com.alya.ecommerce_serang.ui.profile.mystore.StoreOnReviewActivity
import com.alya.ecommerce_serang.ui.profile.mystore.StoreSuspendedActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
@ -58,6 +58,7 @@ class ProfileFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sessionManager = SessionManager(requireContext())
}
override fun onCreateView(
@ -71,57 +72,26 @@ class ProfileFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sessionManager = SessionManager(requireContext())
if (!sessionManager.isLoggedIn()) {
// Redirect to LoginActivity
binding.tvName.text = "Selamat Datang"
binding.tvUsername.text = "Silahkan masuk"
binding.btnDetailProfile.text = "Masuk"
binding.btnDetailProfile.setOnClickListener {
val intent = Intent(requireContext(), LoginActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
// ✅ Finish the host activity so user cant go back
requireActivity().finish()
}
binding.containerBukaToko.visibility = View.GONE
binding.cardPesanan.visibility = View.GONE
binding.tvPengaturanAkun.visibility = View.GONE
binding.containerSettings.visibility = View.GONE
binding.cardAbout.visibility = View.GONE
binding.cardLogout.visibility = View.GONE
}
viewModel.loadUserProfile()
viewModel.checkStoreUser()
observeUserProfile()
observeStoreStatus()
viewModel.loadUserProfile()
viewModel.checkStoreUser()
binding.cardBukaToko.setOnClickListener{
// if (hasStore == true) startActivity(Intent(requireContext(), MyStoreActivity::class.java))
// else startActivity(Intent(requireContext(), RegisterStoreActivity::class.java))
if (viewModel.checkStore.value == true) {
myStoreViewModel.loadMyStore()
myStoreViewModel.myStoreProfile.observe(viewLifecycleOwner) { storeDataResponse ->
storeDataResponse?.let { storeResponse ->
val store = storeResponse.store
when (store.approvalStatus) {
myStoreViewModel.myStoreProfile.observe(viewLifecycleOwner) { store ->
store?.let {
when (store.storeStatus) {
"process" -> startActivity(Intent(requireContext(), StoreOnReviewActivity::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))
}
}
"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))
}
} ?: run {
Toast.makeText(requireContext(), "Gagal memuat data toko", Toast.LENGTH_SHORT).show()
@ -145,11 +115,6 @@ class ProfileFragment : Fragment() {
startActivity(intent)
}
binding.cardChangePass.setOnClickListener{
val intent = Intent(requireContext(), ChangePasswordActivity::class.java)
startActivity(intent)
}
binding.cardLogout.setOnClickListener{
logout()
}
@ -165,8 +130,7 @@ class ProfileFragment : Fragment() {
user?.let { updateUI(it) }
}
viewModel.errorMessage.observe(viewLifecycleOwner) { errorMessage ->
// Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show()
Log.e("Profile Fragment", "Failed to load profile: $errorMessage")
Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show()
}
}
@ -222,8 +186,6 @@ class ProfileFragment : Fragment() {
sessionManager.clearAll()
val intent = Intent(requireContext(), LoginActivity::class.java)
startActivity(intent)
requireActivity().finish()
} catch (e: Exception) {
Toast.makeText(
requireContext(),
@ -234,11 +196,4 @@ class ProfileFragment : Fragment() {
}
}
override fun onResume() {
super.onResume()
viewModel.loadUserProfile()
viewModel.checkStoreUser()
}
}

View File

@ -2,7 +2,6 @@ package com.alya.ecommerce_serang.ui.profile.editprofile
import android.Manifest
import android.app.Activity
import android.app.AlertDialog
import android.app.DatePickerDialog
import android.content.Intent
import android.content.pm.PackageManager
@ -25,6 +24,7 @@ import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.UserProfile
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.Result
import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityEditProfileCustBinding
@ -33,6 +33,7 @@ import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.ProfileViewModel
import com.bumptech.glide.Glide
import com.google.gson.Gson
import java.io.File
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
@ -40,9 +41,9 @@ import java.util.TimeZone
class EditProfileCustActivity : AppCompatActivity() {
private lateinit var binding: ActivityEditProfileCustBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
private var selectedImageUri: Uri? = null
private var currentUser: UserProfile? = null
private val viewModel: ProfileViewModel by viewModels {
BaseViewModelFactory {
@ -53,7 +54,7 @@ class EditProfileCustActivity : AppCompatActivity() {
}
private val getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
data?.data?.let {
selectedImageUri = it
@ -104,8 +105,8 @@ class EditProfileCustActivity : AppCompatActivity() {
}
userProfile?.let {
currentUser = it
populateFields(it)
setupClickListeners()
observeViewModel()
}
@ -117,7 +118,7 @@ class EditProfileCustActivity : AppCompatActivity() {
binding.etNumberPhoneUser.setText(profile.phone)
// Format birth date for display
profile.birthDate.let {
profile.birthDate?.let {
binding.etDateBirth.setText(formatDate(it))
}
@ -155,7 +156,7 @@ class EditProfileCustActivity : AppCompatActivity() {
}
binding.btnSave.setOnClickListener {
if (hasChanged()) confirmUpdate() else finish()
saveProfile()
}
}
@ -212,38 +213,25 @@ class EditProfileCustActivity : AppCompatActivity() {
datePickerDialog.show()
}
private fun confirmUpdate() {
AlertDialog.Builder(this)
.setTitle("Konfirmasi Perubahan")
.setMessage("Apakah Anda yakin ingin menyimpan perubahan profil toko Anda?")
.setPositiveButton("Ya") { _, _ -> saveProfile() }
.setNegativeButton("Batal", null)
.show()
}
private fun hasChanged(): Boolean {
val name = binding.etNameUser.text.toString() != currentUser?.name
val username = binding.etUsername.text.toString() != currentUser?.username
val email = binding.etEmailUser.text.toString() != currentUser?.email
val phone = binding.etNumberPhoneUser.text.toString() != currentUser?.phone
val displayDate = binding.etDateBirth.text.toString() != currentUser?.birthDate.toString()
val imgProfile = selectedImageUri != null
return name || username || email || phone || displayDate || imgProfile
}
private fun saveProfile() {
val name = binding.etNameUser.text.toString()
val username = binding.etUsername.text.toString()
val email = binding.etEmailUser.text.toString()
val phone = binding.etNumberPhoneUser.text.toString()
val displayDate = binding.etDateBirth.text.toString()
val imgProfile = selectedImageUri
if (name.isEmpty() || username.isEmpty() || email.isEmpty() || phone.isEmpty() || displayDate.isEmpty()) {
Toast.makeText(this, "Semua field harus diisi", Toast.LENGTH_SHORT).show()
return
}
// Convert date to server format
val serverBirthDate = convertToServerDateFormat(displayDate)
Log.d(TAG, "Starting profile save with direct method")
Log.d(TAG, "Selected image URI: $selectedImageUri")
// Disable the button to prevent multiple clicks
binding.btnSave.isEnabled = false
// Call the repository method via ViewModel
@ -254,10 +242,82 @@ class EditProfileCustActivity : AppCompatActivity() {
phone = phone,
birthDate = serverBirthDate,
email = email,
imageUri = imgProfile
imageUri = selectedImageUri
)
}
private fun getRealPathFromURI(uri: Uri): String? {
Log.d(TAG, "Getting real path from URI: $uri")
// Handle different URI schemes
when {
// File URI
uri.scheme == "file" -> {
val path = uri.path
Log.d(TAG, "URI is file scheme, path: $path")
return path
}
// Content URI
uri.scheme == "content" -> {
try {
val projection = arrayOf(MediaStore.Images.Media.DATA)
contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
val path = cursor.getString(columnIndex)
Log.d(TAG, "Found path from content URI: $path")
return path
} else {
Log.e(TAG, "Cursor is empty")
}
} ?: Log.e(TAG, "Cursor is null")
// If the above fails, try the documented API way
contentResolver.openInputStream(uri)?.use { inputStream ->
// Create a temp file
val fileName = getFileName(uri) ?: "temp_img_${System.currentTimeMillis()}.jpg"
val tempFile = File(cacheDir, fileName)
tempFile.outputStream().use { outputStream ->
inputStream.copyTo(outputStream)
}
Log.d(TAG, "Created temporary file: ${tempFile.absolutePath}")
return tempFile.absolutePath
}
} catch (e: Exception) {
Log.e(TAG, "Error getting real path: ${e.message}", e)
}
}
}
Log.e(TAG, "Could not get real path for URI: $uri")
return null
}
private fun getFileName(uri: Uri): String? {
var result: String? = null
if (uri.scheme == "content") {
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
if (columnIndex >= 0) {
result = cursor.getString(columnIndex)
Log.d(TAG, "Found filename from content URI: $result")
}
}
}
}
if (result == null) {
result = uri.path
val cut = result?.lastIndexOf('/') ?: -1
if (cut != -1) {
result = result?.substring(cut + 1)
}
Log.d(TAG, "Extracted filename from path: $result")
}
return result
}
private fun formatDate(dateString: String?): String {
if (dateString.isNullOrEmpty()) return "N/A"

View File

@ -59,13 +59,12 @@ class MyStoreActivity : AppCompatActivity() {
finish()
}
viewModel.myStoreProfile.observe(this){ user ->
user?.let { myStoreProfileOverview(it.store) }
}
viewModel.loadMyStore()
viewModel.loadMyStoreProducts()
viewModel.fetchBalance()
viewModel.myStoreProfile.observe(this){ user ->
user?.let { myStoreProfileOverview(it) }
}
viewModel.errorMessage.observe(this) { error ->
Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
@ -73,6 +72,7 @@ class MyStoreActivity : AppCompatActivity() {
setUpClickListeners()
getCountOrder()
observeViewModel()
viewModel.fetchBalance()
fetchBalance()
}
@ -206,16 +206,6 @@ class MyStoreActivity : AppCompatActivity() {
}
}
override fun onResume() {
super.onResume()
lifecycleScope.launch {
viewModel.getAllStatusCounts()
}
viewModel.loadMyStore()
viewModel.loadMyStoreProducts()
viewModel.fetchBalance()
}
companion object {
private const val PROFILE_REQUEST_CODE = 100
}

View File

@ -27,7 +27,6 @@ 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
@ -36,17 +35,8 @@ 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() {
@ -63,7 +53,6 @@ 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
@ -75,15 +64,6 @@ 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)
@ -109,8 +89,6 @@ class RegisterStoreActivity : AppCompatActivity() {
setupHeader()
isReapply = intent.getBooleanExtra("REAPPLY", false)
provinceAdapter = ProvinceAdapter(this)
cityAdapter = CityAdapter(this)
subdistrictAdapter = SubdsitrictAdapter(this)
@ -151,140 +129,19 @@ class RegisterStoreActivity : AppCompatActivity() {
viewModel.cityId.observe(this) { validateRequiredFields() }
viewModel.storeTypeId.observe(this) { validateRequiredFields() }
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
// 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()
}
}
// 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
}
}
}
}
Log.d(TAG, "onCreate: RegisterStoreActivity setup completed")
}
private fun setupHeader() {
@ -320,11 +177,10 @@ 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
}
}
@ -446,7 +302,7 @@ class RegisterStoreActivity : AppCompatActivity() {
viewModel.errorMessage.observe(this) { errorMsg ->
if (errorMsg.isNotEmpty()) {
Log.e(TAG, "setupStoreTypesObserver: Error loading store types: $errorMsg")
// Toast.makeText(this, "Error loading store types: $errorMsg", Toast.LENGTH_SHORT).show()
Toast.makeText(this, "Error loading store types: $errorMsg", Toast.LENGTH_SHORT).show()
}
}
@ -992,63 +848,6 @@ 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?.store
currentStore = it
currentStoreLoaded = true
if (storeTypesLoaded) setupStoreTypeSpinner(storeTypesList)
updateUI(it?.store)
updateUI(it)
}
viewModel.storeTypes.observe(this) {

View File

@ -322,6 +322,19 @@ class DetailStoreAddressActivity : AppCompatActivity() {
villageId = oldAddress.villageId
)
viewModel.saveStoreAddress(oldAddress, newAddress)
// Save address
// viewModel.saveStoreAddress(
// provinceId = selectedProvinceId!!,
// provinceName = province?.provinceName ?: "",
// cityId = city.cityId,
// cityName = city.cityName,
// street = street,
// subdistrict = subdistrict,
// detail = detail,
// postalCode = postalCode,
// latitude = latitude,
// longitude = longitude
// )
}
}
@ -347,13 +360,11 @@ class DetailStoreAddressActivity : AppCompatActivity() {
it.isEnabled = true
it.setBackgroundResource(R.drawable.bg_button_active)
it.setTextColor(getColor(R.color.white))
binding.btnSaveAddress.text = "Simpan Perubahan"
} else {
it.isEnabled = false
it.setBackgroundResource(R.drawable.bg_button_disabled)
it.setTextColor(getColor(R.color.black_300))
binding.btnSaveAddress.text = "Lengkapi alamat anda"
Toast.makeText(this, "Periksa dan lenkapi alamat anda", Toast.LENGTH_SHORT).show()
}
}
}

View File

@ -97,7 +97,7 @@ class SellsAdapter(
val product = order.orderItems?.firstOrNull()
tvSellsProductName.text = product?.productName
tvSellsProductQty.text = "x${product?.quantity}"
tvSellsProductPrice.text = product?.price?.let { formatPrice(it.toDouble().toInt()) }
tvSellsProductPrice.text = product?.price?.let { formatPrice(it.toInt()) }
val fullImageUrl = when (val img = product?.productImage) {
is String -> {
@ -170,7 +170,7 @@ class SellsAdapter(
val product = order.orderItems?.firstOrNull()
tvSellsProductName.text = product?.productName
tvSellsProductQty.text = "x${product?.quantity}"
tvSellsProductPrice.text = product?.price?.let { formatPrice(it.toDouble().toInt()) }
tvSellsProductPrice.text = product?.price?.let { formatPrice(it.toInt()) }
val fullImageUrl = when (val img = product?.productImage) {
is String -> {
@ -186,7 +186,7 @@ class SellsAdapter(
.into(ivSellsProduct)
tvSellsQty.text = "${order.orderItems?.size} produk"
tvSellsPrice.text = order.totalAmount?.let { formatPrice(it.toDouble().toInt()) }
tvSellsPrice.text = order.totalAmount?.let { formatPrice(it.toInt()) }
}
"paid" -> {
layoutOrders.visibility = View.GONE

View File

@ -5,15 +5,9 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.viewpager2.widget.ViewPager2
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.SellsRepository
import com.alya.ecommerce_serang.databinding.FragmentSellsBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel
import com.google.android.material.tabs.TabLayoutMediator
class SellsFragment : Fragment() {
@ -24,13 +18,6 @@ class SellsFragment : Fragment() {
private lateinit var viewPagerAdapter: SellsViewPagerAdapter
private val sellsVm: SellsViewModel by activityViewModels {
BaseViewModelFactory {
val api = ApiConfig.getApiService(SessionManager(requireContext()))
SellsViewModel(SellsRepository(api))
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -66,25 +53,8 @@ class SellsFragment : Fragment() {
else -> "Tab $position"
}
}.attach()
statusPage()
}
private fun statusPage(){
binding.viewPagerSells.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
val status = viewPagerAdapter.sellsStatuses[position]
sellsVm.updateStatus(status, forceRefresh = true)
}
}
)
}
override fun onResume() {
super.onResume()
statusPage()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null

View File

@ -83,10 +83,11 @@ class SellsListFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
loadSells()
setupRecyclerView()
observeSellsList()
observePaymentConfirmation()
loadSells()
// getAllOrderCountsAndNavigate()
}
private fun setupRecyclerView() {
@ -120,8 +121,8 @@ class SellsListFragment : Fragment() {
Log.d(TAG, "Data received: ${result.data?.size ?: 0} items")
if (result.data.isNullOrEmpty()) {
binding.rvSells.visibility = View.GONE
binding.tvEmptyState.visibility = View.VISIBLE
binding.rvSells.visibility = View.GONE
Log.d(TAG, "Showing empty state")
} else {
@ -210,12 +211,6 @@ class SellsListFragment : Fragment() {
}
}
override fun onResume() {
super.onResume()
viewModel.getSellList(status)
observeSellsList()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null

View File

@ -9,7 +9,7 @@ class SellsViewPagerAdapter(
) : FragmentStateAdapter(fragmentActivity) {
// Define all possible sells statuses - keeping your original list
val sellsStatuses = listOf(
private val sellsStatuses = listOf(
"all",
"unpaid",
"paid",

View File

@ -2,6 +2,7 @@ package com.alya.ecommerce_serang.ui.profile.mystore.sells.payment
import android.app.Dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.Log
@ -15,8 +16,6 @@ import android.widget.TextView
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.drawable.toDrawable
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.store.sells.Orders
@ -39,6 +38,9 @@ import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
import androidx.core.graphics.drawable.toDrawable
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.ui.profile.mystore.sells.DetailSellsActivity
class DetailPaymentActivity : AppCompatActivity() {
@ -137,7 +139,6 @@ class DetailPaymentActivity : AppCompatActivity() {
tvOrderSubtotal.text = formatPrice(sell.totalAmount.toString())
tvOrderShipPrice.text = formatPrice(sell.shipmentPrice.toString())
tvOrderPrice.text = formatPrice(sell.totalAmount.toString())
tvOrderDue.text = formatDueDate(sell.updatedAt.toString(), 2)
tvOrderRecipient.text = sell.recipient ?: "-"
tvOrderRecipientNum.text = sell.receiptNum?.toString() ?: "-"
@ -200,28 +201,6 @@ class DetailPaymentActivity : AppCompatActivity() {
}
}
private fun formatDueDate(dateString: String, dueDay: Int): String {
return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
val outputFormat = SimpleDateFormat("dd MMM; HH.mm", Locale("id", "ID"))
val date = inputFormat.parse(dateString)
date?.let {
val calendar = Calendar.getInstance()
calendar.time = it
calendar.add(Calendar.DATE, dueDay)
outputFormat.format(calendar.time)
} ?: dateString
} catch (e: Exception) {
Log.e("DateFormatting", "Error formatting date: ${e.message}")
dateString
}
}
private fun formatPrice(price: String): String {
val priceDouble = price.toDoubleOrNull() ?: 0.0
return String.format(Locale("id", "ID"), "Rp%,.0f", priceDouble)

View File

@ -14,6 +14,7 @@ import com.alya.ecommerce_serang.data.repository.AddressRepository
import com.alya.ecommerce_serang.data.repository.SellsRepository
import com.alya.ecommerce_serang.databinding.ActivityDetailShipmentBinding
import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsProductAdapter
import com.alya.ecommerce_serang.ui.profile.mystore.sells.payment.DetailPaymentActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.AddressViewModel
@ -23,6 +24,7 @@ import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
import kotlin.getValue
class DetailShipmentActivity : AppCompatActivity() {
@ -104,7 +106,6 @@ class DetailShipmentActivity : AppCompatActivity() {
tvOrderSubtotal.text = formatPrice(sell.totalAmount.toString())
tvOrderShipPrice.text = formatPrice(sell.shipmentPrice.toString())
tvOrderPrice.text = formatPrice(sell.totalAmount.toString())
tvOrderDue.text = formatDueDate(sell.updatedAt.toString(), 2)
tvOrderRecipient.text = sell.recipient ?: "-"
tvOrderRecipientNum.text = sell.receiptNum?.toString() ?: "-"
@ -167,28 +168,6 @@ class DetailShipmentActivity : AppCompatActivity() {
}
}
private fun formatDueDate(dateString: String, dueDay: Int): String {
return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
val outputFormat = SimpleDateFormat("dd MMM; HH.mm", Locale("id", "ID"))
val date = inputFormat.parse(dateString)
date?.let {
val calendar = Calendar.getInstance()
calendar.time = it
calendar.add(Calendar.DATE, dueDay)
outputFormat.format(calendar.time)
} ?: dateString
} catch (e: Exception) {
Log.e("DateFormatting", "Error formatting date: ${e.message}")
dateString
}
}
private fun formatPrice(price: String): String {
val priceDouble = price.toDoubleOrNull() ?: 0.0
return String.format(Locale("id", "ID"), "Rp%,.0f", priceDouble)

View File

@ -10,8 +10,6 @@ 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
@ -25,18 +23,12 @@ import java.util.Locale
class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
private var TAG = "MyStoreViewModel"
private val _myStoreProfile = MutableLiveData<StoreResponse?>()
val myStoreProfile: LiveData<StoreResponse?> = _myStoreProfile
private val _myStoreProfile = MutableLiveData<Store?>()
val myStoreProfile: LiveData<Store?> = _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
@ -55,12 +47,7 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() {
fun loadMyStore(){
viewModelScope.launch {
when (val result = repository.fetchMyStoreProfile()){
is Result.Success -> {
val storeData = result.data
_myStoreProfile.postValue(storeData)
_shipping.postValue(storeData?.shipping)
_payment.postValue(storeData?.payment)
}
is Result.Success -> _myStoreProfile.postValue(result.data)
is Result.Error -> _errorMessage.postValue(result.exception.message ?: "Unknown Error")
is Result.Loading -> null
}

View File

@ -8,7 +8,6 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.UserProfile
import com.alya.ecommerce_serang.data.api.response.auth.ChangePassResponse
import com.alya.ecommerce_serang.data.api.response.auth.HasStoreResponse
import com.alya.ecommerce_serang.data.api.response.customer.profile.EditProfileResponse
import com.alya.ecommerce_serang.data.repository.Result
@ -27,7 +26,7 @@ class ProfileViewModel(private val userRepository: UserRepository) : ViewModel()
private val _checkStore = MutableLiveData<Boolean>()
val checkStore: LiveData<Boolean> = _checkStore
val changePasswordResult = MutableLiveData<Result<ChangePassResponse>>()
private val _logout = MutableLiveData<Boolean>()
val logout : LiveData<Boolean> = _logout
@ -111,20 +110,6 @@ class ProfileViewModel(private val userRepository: UserRepository) : ViewModel()
}
}
fun changePassword(currentPassword: String, newPassword: String) {
viewModelScope.launch {
try {
// Call the repository to change the password
val result = userRepository.changePassword(currentPassword, newPassword)
// Post the result (success or error) to LiveData
changePasswordResult.postValue(result)
} catch (e: Exception) {
// Handle any unexpected errors
changePasswordResult.postValue(Result.Error(e))
}
}
}
companion object {
private const val TAG = "ProfileViewModel"

View File

@ -7,11 +7,9 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
import com.alya.ecommerce_serang.data.api.dto.FcmReq
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.dto.ResetPassReq
import com.alya.ecommerce_serang.data.api.dto.VerifRegisReq
import com.alya.ecommerce_serang.data.api.response.auth.FcmTokenResponse
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
@ -390,34 +388,6 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
}
fun sendFcm(token: FcmReq) {
viewModelScope.launch {
_otpState.value = Result.Loading // Indicating API call in progress
try {
// Call the repository function to request OTP
val authenticatedApiService = getAuthenticatedApiService()
val authenticatedOrderRepo = UserRepository(authenticatedApiService)
val response: FcmTokenResponse = authenticatedOrderRepo.sendFcm(token)
// Log and store success message
Log.d("LoginViewModel", "OTP Response: ${response.message}")
_message.value = response.message ?: "berhasil" // Store the message for UI feedback
// Update state to indicate success
_otpState.value = Result.Success(Unit)
} catch (exception: Exception) {
// Handle any errors and update state
_otpState.value = Result.Error(exception)
_message.value = exception.localizedMessage ?: "Failed to request OTP"
// Log the error for debugging
Log.e("LoginViewModel", "OTP request failed for: $token", exception)
}
}
}
companion object {
private const val TAG = "RegisterViewModel"
}

View File

@ -17,9 +17,6 @@ import com.alya.ecommerce_serang.ui.order.address.ViewState
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Locale
@ -55,9 +52,6 @@ class SellsViewModel(private val repository: SellsRepository) : ViewModel() {
private val _error = MutableLiveData<String>()
val error: LiveData<String> get() = _error
private val _selectedStatus = MutableStateFlow("all")
val selectedStatus: StateFlow<String> = _selectedStatus.asStateFlow()
fun getSellList(status: String) {
Log.d(TAG, "========== Starting getSellList ==========")
Log.d(TAG, "Requested status: '$status'")
@ -326,40 +320,4 @@ class SellsViewModel(private val repository: SellsRepository) : ViewModel() {
Log.d(TAG, "========== refreshOrders method completed ==========")
}
fun updateStatus(status: String, forceRefresh: Boolean = false) {
Log.d(TAG, "↪️ updateStatus(status = $status, forceRefresh = $forceRefresh)")
// Noop guard (optional): skip if user reselects same tab and no refresh asked
if (_selectedStatus.value == status && !forceRefresh) {
Log.d(TAG, "🔸 Status unchanged & forceRefresh = false → skip update")
return
}
_selectedStatus.value = status
Log.d(TAG, "✅ _selectedStatus set to \"$status\"")
if (forceRefresh) {
Log.d(TAG, "🔄 forceRefresh = true → launching refresh()")
viewModelScope.launch { refresh(status) }
}
}
private suspend fun refresh(status: String) {
Log.d(TAG, "⏳ refresh(\"$status\") started")
try {
if (status == "all") {
Log.d(TAG, "🌐 Calling getAllOrdersCombined()")
getAllStatusCounts() // network → cache
} else {
Log.d(TAG, "🌐 repository.getOrderList(\"$status\")")
repository.getSellList(status) // network → cache
}
Log.d(TAG, "✅ refresh(\"$status\") completed (repository updated)")
// Flow that watches DB/cache will emit automatically
} catch (e: Exception) {
Log.e(TAG, "❌ refresh(\"$status\") failed: ${e.message}", e)
}
}
}

View File

@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="100dp" android:tint="#489EC6" android:viewportHeight="24" android:viewportWidth="24" android:width="100dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM12,6c1.93,0 3.5,1.57 3.5,3.5S13.93,13 12,13s-3.5,-1.57 -3.5,-3.5S10.07,6 12,6zM12,20c-2.03,0 -4.43,-0.82 -6.14,-2.88C7.55,15.8 9.68,15 12,15s4.45,0.8 6.14,2.12C16.43,19.18 14.03,20 12,20z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#489EC6" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M7,18c-1.1,0 -1.99,0.9 -1.99,2S5.9,22 7,22s2,-0.9 2,-2 -0.9,-2 -2,-2zM1,2v2h2l3.6,7.59 -1.35,2.45c-0.16,0.28 -0.25,0.61 -0.25,0.96 0,1.1 0.9,2 2,2h12v-2L7.42,15c-0.14,0 -0.25,-0.11 -0.25,-0.25l0.03,-0.12 0.9,-1.63h7.45c0.75,0 1.41,-0.41 1.75,-1.03l3.58,-6.49c0.08,-0.14 0.12,-0.31 0.12,-0.48 0,-0.55 -0.45,-1 -1,-1L5.21,4l-0.94,-2L1,2zM17,18c-1.1,0 -1.99,0.9 -1.99,2s0.89,2 1.99,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18v-6h16v6zM20,8L4,8L4,6h16v2z"/>
</vector>

View File

@ -3,6 +3,6 @@
android:shape="rectangle">
<solid android:color="@color/blue_500" />
<corners android:radius="24dp" />
<corners android:radius="5dp" />
</shape>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -1,29 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="108"
android:viewportHeight="108">
<!-- Background circle for better splash screen appearance -->
<path
android:pathData="M54,54m-50,0a50,50 0,1 1,100 0a50,50 0,1 1,-100 0"
android:fillColor="#ffff"/>
<!-- Main logo content scaled and centered -->
<group android:translateX="32" android:translateY="32" android:scaleX="0.7" android:scaleY="0.7">
<!-- Background rectangle -->
<path
android:pathData="M0,0h64v64h-64z"
android:fillColor="#489EC6"/>
<!-- Main logo shape -->
<path
android:pathData="M11.868,58C10.523,58 9.399,57.538 8.497,56.614C7.594,55.69 7.144,54.537 7.146,53.155V29.074C5.912,28.14 5.009,26.915 4.438,25.399C3.867,23.883 3.854,22.266 4.4,20.548L7.245,10.924C7.635,9.7 8.264,8.74 9.131,8.044C10.001,7.348 11.075,7 12.354,7H51.536C52.813,7 53.883,7.326 54.747,7.978C55.608,8.63 56.24,9.573 56.641,10.807L59.598,20.545C60.146,22.265 60.134,23.896 59.563,25.438C58.992,26.98 58.089,28.23 56.855,29.188V53.152C56.855,54.534 56.404,55.687 55.501,56.611C54.599,57.535 53.476,57.998 52.133,58H11.868ZM38.433,28C40.311,28 41.712,27.46 42.638,26.38C43.564,25.302 43.954,24.188 43.808,23.038L41.866,10H33.465V22.6C33.465,24.074 33.957,25.342 34.939,26.404C35.922,27.468 37.084,28 38.433,28ZM25.278,28C26.849,28 28.119,27.468 29.088,26.404C30.057,25.34 30.541,24.072 30.541,22.6V10H22.138L20.19,23.269C20.071,24.201 20.468,25.222 21.38,26.332C22.292,27.442 23.594,27.998 25.278,28ZM12.263,28C13.551,28 14.647,27.55 15.55,26.65C16.452,25.75 17.014,24.627 17.234,23.281L19.067,10H12.354C11.716,10 11.209,10.144 10.833,10.432C10.457,10.72 10.176,11.153 9.991,11.731L7.292,21.205C6.812,22.781 7.017,24.308 7.906,25.786C8.795,27.264 10.247,28.002 12.263,28ZM51.738,28C53.488,28 54.886,27.3 55.931,25.9C56.978,24.5 57.237,22.935 56.709,21.205L53.864,11.617C53.676,11.039 53.395,10.625 53.019,10.375C52.642,10.125 52.137,10 51.501,10H44.933L46.767,23.281C46.987,24.627 47.549,25.75 48.451,26.65C49.354,27.55 50.449,28 51.738,28Z"
android:fillColor="#ffffff"/>
<!-- Text elements -->
<path
android:pathData="M20.382,39V30.6H23.97C24.554,30.6 25.046,30.692 25.446,30.876C25.846,31.052 26.15,31.304 26.358,31.632C26.574,31.952 26.682,32.332 26.682,32.772C26.682,33.196 26.59,33.552 26.406,33.84C26.222,34.128 25.978,34.348 25.674,34.5C25.378,34.652 25.05,34.744 24.69,34.776L24.882,34.632C25.274,34.648 25.618,34.752 25.914,34.944C26.21,35.136 26.442,35.388 26.61,35.7C26.786,36.004 26.874,36.34 26.874,36.708C26.874,37.164 26.766,37.564 26.55,37.908C26.334,38.252 26.018,38.52 25.602,38.712C25.186,38.904 24.682,39 24.09,39H20.382ZM22.182,37.536H23.79C24.19,37.536 24.498,37.448 24.714,37.272C24.938,37.088 25.05,36.824 25.05,36.48C25.05,36.136 24.934,35.868 24.702,35.676C24.478,35.484 24.166,35.388 23.766,35.388H22.182V37.536ZM22.182,34.056H23.658C24.042,34.056 24.334,33.968 24.534,33.792C24.742,33.616 24.846,33.368 24.846,33.048C24.846,32.728 24.742,32.48 24.534,32.304C24.334,32.12 24.038,32.028 23.646,32.028H22.182V34.056ZM28.163,39V32.952H29.963V39H28.163ZM29.063,32.256C28.743,32.256 28.483,32.164 28.283,31.98C28.083,31.796 27.983,31.564 27.983,31.284C27.983,30.996 28.083,30.76 28.283,30.576C28.483,30.392 28.743,30.3 29.063,30.3C29.391,30.3 29.655,30.392 29.855,30.576C30.063,30.76 30.167,30.996 30.167,31.284C30.167,31.564 30.063,31.796 29.855,31.98C29.655,32.164 29.391,32.256 29.063,32.256ZM34.069,39.144C33.501,39.144 33.009,39.056 32.593,38.88C32.186,38.696 31.861,38.448 31.622,38.136C31.389,37.824 31.257,37.472 31.226,37.08H33.014C33.046,37.216 33.102,37.34 33.181,37.452C33.27,37.556 33.386,37.64 33.529,37.704C33.681,37.76 33.849,37.788 34.034,37.788C34.234,37.788 34.394,37.764 34.514,37.716C34.641,37.66 34.737,37.588 34.801,37.5C34.866,37.412 34.897,37.32 34.897,37.224C34.897,37.072 34.849,36.956 34.754,36.876C34.666,36.796 34.534,36.732 34.357,36.684C34.181,36.628 33.97,36.576 33.722,36.528C33.433,36.464 33.146,36.392 32.857,36.312C32.577,36.224 32.326,36.116 32.102,35.988C31.885,35.86 31.709,35.696 31.573,35.496C31.445,35.288 31.382,35.036 31.382,34.74C31.382,34.38 31.482,34.056 31.681,33.768C31.882,33.472 32.169,33.24 32.546,33.072C32.922,32.896 33.377,32.808 33.914,32.808C34.674,32.808 35.27,32.976 35.701,33.312C36.133,33.648 36.389,34.1 36.47,34.668H34.79C34.742,34.508 34.641,34.388 34.489,34.308C34.338,34.22 34.146,34.176 33.914,34.176C33.65,34.176 33.45,34.22 33.313,34.308C33.178,34.396 33.11,34.512 33.11,34.656C33.11,34.752 33.153,34.84 33.242,34.92C33.338,34.992 33.473,35.056 33.65,35.112C33.826,35.168 34.042,35.224 34.298,35.28C34.785,35.384 35.206,35.496 35.557,35.616C35.917,35.736 36.197,35.912 36.397,36.144C36.597,36.368 36.694,36.696 36.686,37.128C36.694,37.52 36.59,37.868 36.374,38.172C36.166,38.476 35.866,38.716 35.473,38.892C35.082,39.06 34.613,39.144 34.069,39.144ZM40.094,39.144C39.59,39.144 39.17,39.064 38.834,38.904C38.506,38.744 38.262,38.528 38.102,38.256C37.95,37.976 37.874,37.668 37.874,37.332C37.874,36.972 37.962,36.656 38.138,36.384C38.322,36.104 38.606,35.884 38.99,35.724C39.374,35.556 39.858,35.472 40.442,35.472H41.906C41.906,35.2 41.87,34.976 41.798,34.8C41.734,34.624 41.626,34.492 41.474,34.404C41.322,34.316 41.114,34.272 40.85,34.272C40.57,34.272 40.334,34.328 40.142,34.44C39.95,34.552 39.83,34.728 39.782,34.968H38.054C38.094,34.536 38.234,34.16 38.474,33.84C38.722,33.52 39.05,33.268 39.458,33.084C39.866,32.9 40.334,32.808 40.862,32.808C41.438,32.808 41.938,32.904 42.362,33.096C42.786,33.28 43.114,33.552 43.346,33.912C43.586,34.272 43.706,34.72 43.706,35.256V39H42.206L41.99,38.124C41.902,38.276 41.798,38.416 41.678,38.544C41.558,38.664 41.418,38.772 41.258,38.868C41.098,38.956 40.922,39.024 40.73,39.072C40.538,39.12 40.326,39.144 40.094,39.144ZM40.538,37.776C40.73,37.776 40.898,37.744 41.042,37.68C41.186,37.616 41.31,37.528 41.414,37.416C41.518,37.304 41.602,37.176 41.666,37.032C41.738,36.88 41.79,36.716 41.822,36.54V36.528H40.658C40.458,36.528 40.29,36.556 40.154,36.612C40.026,36.66 39.93,36.732 39.866,36.828C39.802,36.924 39.77,37.036 39.77,37.164C39.77,37.3 39.802,37.416 39.866,37.512C39.938,37.6 40.03,37.668 40.142,37.716C40.262,37.756 40.394,37.776 40.538,37.776ZM17.132,53.144C16.5,53.144 15.924,53.02 15.404,52.772C14.892,52.516 14.484,52.136 14.18,51.632C13.884,51.128 13.736,50.492 13.736,49.724V44.6H15.536V49.736C15.536,50.112 15.596,50.432 15.716,50.696C15.844,50.96 16.028,51.16 16.268,51.296C16.516,51.424 16.812,51.488 17.156,51.488C17.508,51.488 17.804,51.424 18.044,51.296C18.292,51.16 18.48,50.96 18.608,50.696C18.736,50.432 18.8,50.112 18.8,49.736V44.6H20.6V49.724C20.6,50.492 20.44,51.128 20.12,51.632C19.808,52.136 19.388,52.516 18.86,52.772C18.34,53.02 17.764,53.144 17.132,53.144ZM22.128,53V44.6H24.288L26.736,49.652L29.16,44.6H31.308V53H29.508V47.636L27.444,51.824H26.004L23.928,47.636V53H22.128ZM32.921,53V44.6H34.721V47.78L37.625,44.6H39.833L36.749,47.924L39.941,53H37.733L35.465,49.316L34.721,50.12V53H32.921ZM41.007,53V44.6H43.167L45.615,49.652L48.039,44.6H50.187V53H48.387V47.636L46.323,51.824H44.883L42.807,47.636V53H41.007Z"
android:fillColor="#489EC6"/>
</group>
</vector>

View File

@ -113,7 +113,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="gone"
android:text="Metode Pembayaran *"
android:fontFamily="@font/dmsans_semibold"
android:textSize="16sp" />
@ -123,7 +122,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:visibility="gone"
android:background="@drawable/edit_text_background"
android:minHeight="50dp"
android:padding="12dp" />

View File

@ -1,102 +0,0 @@
<?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

@ -34,50 +34,69 @@
android:orientation="vertical">
<!-- Delivery Address Section -->
<com.google.android.material.card.MaterialCardView
<androidx.cardview.widget.CardView
android:id="@+id/card_delivery_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/white"
app:strokeColor="#E0E0E0"
app:strokeWidth="1dp">
android:layout_marginHorizontal="0dp"
android:layout_marginTop="0dp"
app:cardElevation="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp">
android:orientation="vertical"
android:padding="16dp"
android:background="@color/white">
<!-- Header Row -->
<LinearLayout
android:id="@+id/address_header"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_location_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/baseline_location_pin_24"
app:tint="@color/blue_300" />
android:layout_gravity="center_vertical"
app:tint="#3D84FF" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Alamat Pengiriman"
android:textSize="16sp"
android:fontFamily="@font/dmsans_medium"
android:layout_marginStart="8dp" />
</LinearLayout>
<TextView
android:id="@+id/tv_places_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
android:textColor="#5A5A5A"
android:paddingHorizontal="8dp"
android:paddingVertical="2dp"
android:textSize="12sp"
android:layout_marginTop="8dp"
android:layout_marginStart="32dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<TextView
android:id="@+id/tv_address"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Alamat Pengiriman"
android:textSize="16sp"
android:textColor="@android:color/black"
android:fontFamily="@font/dmsans_medium"
android:layout_marginStart="12dp" />
android:text="-"
android:textSize="14sp"
android:layout_marginStart="32dp" />
<TextView
android:id="@+id/tv_change_address"
@ -85,441 +104,169 @@
android:layout_height="wrap_content"
android:text="Pilih Alamat"
android:textColor="#3D84FF"
android:textSize="14sp"
android:fontFamily="@font/dmsans_medium"
android:background="?attr/selectableItemBackgroundBorderless"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp" />
android:textSize="14sp" />
</LinearLayout>
<!-- Empty Address State -->
<LinearLayout
android:id="@+id/container_empty_address"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="4dp"
android:gravity="center"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@id/address_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Belum ada alamat dipilih"
android:fontFamily="@font/dmsans_medium"
android:textColor="#757575"
android:textSize="14sp"
android:gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Pilih alamat pengiriman untuk melanjutkan"
android:textColor="#BDBDBD"
android:textSize="12sp"
android:gravity="center" />
</LinearLayout>
<!-- Selected Address Content -->
<LinearLayout
android:id="@+id/container_address"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/address_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<!-- Address Label -->
<TextView
android:id="@+id/tv_places_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rumah"
android:textColor="#3D84FF"
android:textSize="12sp"
android:fontFamily="@font/dmsans_medium"
android:background="@drawable/bg_edit_text_background"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp"
tools:text="Rumah" />
<!-- Full Address -->
<TextView
android:id="@+id/tv_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Jl. Raya Serang No. 123, Kecamatan Serang, Kabupaten Serang, Banten 42111"
android:textSize="14sp"
android:textColor="@android:color/black"
android:lineSpacingExtra="2dp"
tools:text="Jl. Raya Serang No. 123, Kecamatan Serang, Kabupaten Serang, Banten 42111" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Product Items Section -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_product"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
app:cardBackgroundColor="@color/white"
app:strokeColor="#E0E0E0"
app:strokeWidth="1dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp">
<!-- Header Row -->
<LinearLayout
android:id="@+id/product_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/baseline_local_grocery_store_24"
app:tint="@color/blue_300" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Produk Pesanan"
android:textSize="16sp"
android:textColor="@android:color/black"
android:fontFamily="@font/dmsans_medium"
android:layout_marginStart="12dp" />
</LinearLayout>
<!-- Empty Product State -->
<LinearLayout
android:id="@+id/container_empty_products"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="4dp"
android:gravity="center"
android:padding="8dp"
android:visibility="visible"
app:layout_constraintTop_toBottomOf="@id/product_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Tidak ada produk"
android:fontFamily="@font/dmsans_medium"
android:textColor="#757575"
android:textSize="14sp"
android:gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="Keranjang belanja kosong"
android:textColor="#BDBDBD"
android:textSize="12sp"
android:gravity="center" />
</LinearLayout>
<!-- Products RecyclerView -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_product_items"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/product_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:listitem="@layout/item_order_seller" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_shipping_method"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
app:cardBackgroundColor="@color/white"
app:strokeColor="#E0E0E0"
app:strokeWidth="1dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp">
<!-- Header Row -->
<LinearLayout
android:id="@+id/shipping_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Metode Pengiriman"
android:textSize="16sp"
android:textColor="@android:color/black"
android:fontFamily="@font/dmsans_medium"
android:layout_marginStart="12dp" />
<TextView
android:id="@+id/tv_shipping_option"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pilih"
android:textColor="#3D84FF"
android:textSize="14sp"
android:fontFamily="@font/dmsans_medium"
android:background="?attr/selectableItemBackgroundBorderless"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp" />
</LinearLayout>
<!-- Empty Shipping State -->
<LinearLayout
android:id="@+id/container_empty_shipping"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="4dp"
android:gravity="center"
android:padding="12dp"
app:layout_constraintTop_toBottomOf="@id/shipping_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Belum ada metode pengiriman"
android:fontFamily="@font/dmsans_medium"
android:textColor="#757575"
android:textSize="14sp"
android:gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Pilih alamat terlebih dahulu"
android:textColor="#BDBDBD"
android:textSize="12sp"
android:gravity="center" />
</LinearLayout>
<!-- Selected Shipping Content -->
<androidx.cardview.widget.CardView
android:id="@+id/card_shipment"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:visibility="gone"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#F5F5F5"
app:layout_constraintTop_toBottomOf="@id/shipping_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp">
<RadioButton
android:id="@+id/rb_jne"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="8dp">
<TextView
android:id="@+id/tv_courier_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="JNE"
android:textSize="16sp"
android:fontFamily="@font/dmsans_medium" />
<TextView
android:id="@+id/tv_delivery_estimate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="3 - 4 hari kerja"
android:textSize="14sp"
android:textColor="#757575" />
</LinearLayout>
<TextView
android:id="@+id/tv_shipping_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp15.000"
android:textSize="16sp"
android:fontFamily="@font/dmsans_medium"
android:layout_gravity="center_vertical" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Payment Method Section -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_payment_method"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
app:cardBackgroundColor="@color/white"
app:strokeColor="#E0E0E0"
app:strokeWidth="1dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp">
<!-- Header Row -->
<LinearLayout
android:id="@+id/payment_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/baseline_payment_24"
app:tint="@color/blue_300" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Metode Pembayaran"
android:textSize="16sp"
android:textColor="@android:color/black"
android:fontFamily="@font/dmsans_medium"
android:layout_marginStart="12dp" />
<TextView
android:id="@+id/tv_payment_option"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pilih"
android:textColor="#3D84FF"
android:textSize="14sp"
android:fontFamily="@font/dmsans_medium"
android:background="?attr/selectableItemBackgroundBorderless"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp"
android:visibility="gone" />
</LinearLayout>
<!-- Empty Payment State -->
<LinearLayout
android:id="@+id/container_empty_payment"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="4dp"
android:gravity="center"
android:padding="12dp"
app:layout_constraintTop_toBottomOf="@id/payment_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Belum ada metode pembayaran"
android:fontFamily="@font/dmsans_medium"
android:textColor="#757575"
android:textSize="14sp"
android:gravity="center" />
<TextView
android:id="@+id/tvEmptyPayment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Pilih alamat terlebih dahulu"
android:textColor="#BDBDBD"
android:textSize="12sp"
android:gravity="center" />
</LinearLayout>
<!-- Payment Methods RecyclerView -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_payment_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/payment_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:listitem="@layout/item_payment_method" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.cardview.widget.CardView>
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="@color/black_50" />
<!-- Product Items Section -->
<androidx.cardview.widget.CardView
android:id="@+id/card_product"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/white"
android:padding="16dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_product_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
tools:listitem="@layout/item_order_seller" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="#F5F5F5" />
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="@color/black_50" />
<!-- Shipping Method Section -->
<LinearLayout
android:id="@+id/layout_shipping_method"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/white"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Metode Pengiriman"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_shipping_option"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Opsi Pengiriman"
android:textColor="#3D84FF"
android:textSize="14sp" />
</LinearLayout>
<androidx.cardview.widget.CardView
android:id="@+id/card_shipment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:visibility="gone"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#F5F5F5">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp">
<RadioButton
android:id="@+id/rb_jne"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="8dp">
<TextView
android:id="@+id/tv_courier_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="JNE"
android:textSize="16sp"
android:fontFamily="@font/dmsans_medium" />
<TextView
android:id="@+id/tv_delivery_estimate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="3 - 4 hari kerja"
android:textSize="14sp"
android:textColor="#757575" />
</LinearLayout>
<TextView
android:id="@+id/tv_shipping_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp15.000"
android:textSize="16sp"
android:fontFamily="@font/dmsans_medium"
android:layout_gravity="center_vertical" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="@color/black_50" />
<!-- Payment Method Section -->
<LinearLayout
android:id="@+id/layout_payment_method"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/white"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Metode Pembayaran"
android:textSize="14sp"
android:layout_marginBottom="8dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_payment_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/item_payment_method" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="8dp"
android:layout_marginVertical="8dp"
android:background="@color/black_50" />
<!-- Price Summary Section -->
@ -547,7 +294,7 @@
android:id="@+id/tv_item_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp0"
android:text="Rp65.000"
android:textSize="14sp" />
</LinearLayout>
@ -568,7 +315,7 @@
android:id="@+id/tv_shipping_fee"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp0"
android:text="Rp15.000"
android:textSize="14sp" />
</LinearLayout>
@ -595,8 +342,8 @@
android:id="@+id/tv_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp0"
android:textColor="@color/blue_400"
android:text="Rp75.000"
android:textColor="#3D84FF"
android:textSize="16sp"
android:fontFamily="@font/dmsans_bold" />
</LinearLayout>
@ -632,7 +379,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp75.000"
android:textColor="@color/blue_400"
android:textColor="#3D84FF"
android:textSize="18sp"
android:fontFamily="@font/dmsans_bold" />
</LinearLayout>
@ -645,7 +392,7 @@
android:textAllCaps="false"
android:paddingHorizontal="32dp"
app:cornerRadius="8dp"
android:backgroundTint="@color/blue_500" />
android:backgroundTint="#3D84FF" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -14,6 +14,7 @@
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -166,7 +167,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/soft_gray"
android:textSize="12sp"
android:textSize="14sp"
tools:text="@string/item_sold" />
<View
@ -185,7 +186,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:textSize="12sp"
android:textSize="14sp"
tools:text="4.5" />
</LinearLayout>
</LinearLayout>
@ -218,7 +219,7 @@
android:layout_weight="1"
android:text="@string/ulasan_pembeli"
android:textColor="@color/black"
android:textSize="14sp"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
@ -230,18 +231,6 @@
android:textSize="14sp" />
</LinearLayout>
<TextView
android:id="@+id/empty_review"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Belum ada ulasan"
android:textSize="12sp"
android:textColor="@color/black_200"
android:gravity="center"
android:visibility="gone"
android:fontFamily="@font/dmsans_mediumitalic"
android:layout_marginTop="8dp"/>
<!-- RecyclerView for Reviews -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewReviews"
@ -276,7 +265,7 @@
android:layout_height="wrap_content"
android:text="@string/detail_produk"
android:textColor="@color/black"
android:textSize="14sp"
android:textSize="16sp"
android:textStyle="bold" />
<TableLayout
@ -295,7 +284,7 @@
android:layout_weight="1"
android:text="@string/berat_produk"
android:fontFamily="@font/dmsans_semibold"
android:textSize="12sp" />
android:textSize="14sp" />
<TextView
android:id="@+id/tvWeight"
@ -303,7 +292,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/blue_500"
android:textSize="12sp"
android:textSize="14sp"
tools:text="200 gram" />
</TableRow>
@ -318,7 +307,7 @@
android:layout_weight="1"
android:text="@string/stock_product"
android:fontFamily="@font/dmsans_semibold"
android:textSize="12sp" />
android:textSize="14sp" />
<TextView
android:id="@+id/tvStock"
@ -326,7 +315,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/blue_500"
android:textSize="12sp"
android:textSize="14sp"
tools:text="100 buah" />
</TableRow>
@ -357,8 +346,8 @@
android:layout_height="wrap_content"
android:text="@string/deskripsi_produk"
android:textColor="@color/black"
android:textSize="14sp"
android:layout_marginTop="16dp"
android:textSize="16sp"
android:layout_marginTop="8dp"
android:textStyle="bold" />
<TextView
@ -367,7 +356,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="@color/black"
android:textSize="12sp"
android:textSize="14sp"
tools:text="Terbuat dari tepung dan ikan tenggiri asli Serang Banten. Tahan selama 25 hari." />
</LinearLayout>
</androidx.cardview.widget.CardView>
@ -396,13 +385,6 @@
android:orientation="horizontal"
android:padding="16dp">
<ProgressBar
android:id="@+id/progress_bar_detail_store"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="center"/>
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/ivSellerImage"
android:layout_width="48dp"
@ -421,7 +403,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="14sp"
android:textSize="16sp"
android:textStyle="bold"
tools:text="SnackEnak" />
@ -429,9 +411,8 @@
android:id="@+id/tvSellerLocation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/dmsans_regular"
android:textColor="@color/black_300"
android:textSize="12sp"
android:fontFamily="@font/dmsans_semibold"
android:textSize="14sp"
tools:text="Jakarta Selatan" />
<RatingBar
@ -450,7 +431,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textSize="12sp"
android:textSize="14sp"
android:textStyle="bold"
tools:text="5.0" />
</LinearLayout>
@ -484,7 +465,7 @@
android:layout_weight="1"
android:text="@string/produk_lainnya"
android:textColor="@color/black"
android:textSize="14sp"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
@ -496,18 +477,6 @@
android:textSize="14sp" />
</LinearLayout>
<TextView
android:id="@+id/empty_other_products"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Belum ada produk lainnya"
android:textSize="12sp"
android:textColor="@color/black_200"
android:gravity="center"
android:visibility="gone"
android:fontFamily="@font/dmsans_mediumitalic"
android:layout_marginTop="8dp"/>
<!-- RecyclerView for Other Products -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewOtherProducts"
@ -574,7 +543,6 @@
android:insetBottom="0dp"
android:text="@string/add_to_cart"
android:textColor="@color/blue_500"
android:textSize="14sp"
app:cornerRadius="4dp"
app:icon="@drawable/baseline_add_24"
app:iconGravity="textStart"
@ -592,7 +560,6 @@
android:insetTop="0dp"
android:insetBottom="0dp"
android:text="@string/beli_sekarang"
android:textSize="14sp"
android:textColor="@color/white"
app:cornerRadius="4dp" />
</LinearLayout>

View File

@ -62,7 +62,7 @@
android:id="@+id/profile_image"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/baseline_account_circle_100"
android:src="@drawable/baseline_account_circle_24"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
@ -87,7 +87,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ubah Profil"
style="@style/button.large.active.medium"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/profile_image"
app:layout_constraintStart_toStartOf="parent"

View File

@ -61,7 +61,7 @@
android:id="@+id/profile_image"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/baseline_account_circle_100"
android:src="@drawable/baseline_account_circle_24"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
@ -188,7 +188,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Simpan"
style="@style/button.large.active.medium"
android:layout_marginTop="32dp"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="16dp"

View File

@ -6,8 +6,6 @@
android:layout_height="match_parent"
android:paddingHorizontal="32dp"
android:paddingVertical="16dp"
android:layout_marginTop="32dp"
android:theme="@style/Theme.Ecommerce_serang"
tools:context=".ui.auth.LoginActivity">
<!-- Title -->
@ -138,7 +136,6 @@
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,35 +69,6 @@
</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

@ -15,207 +15,156 @@
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent" />
<!-- Store Information Card -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/storeInfoCard"
android:layout_width="0dp"
<!-- Store Information -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/storeInfoContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="16dp"
app:cardBackgroundColor="@android:color/white"
app:cardCornerRadius="16dp"
app:cardElevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:background="@color/blue_50"
android:layout_marginTop="16dp"
android:padding="24dp"
app:layout_constraintTop_toBottomOf="@id/searchContainer">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/storeInfoContainer"
android:layout_width="match_parent"
<ImageView
android:id="@+id/ivStoreImage"
android:layout_width="64dp"
android:layout_height="64dp"
android:background="@drawable/circle_background"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/placeholder_image" />
<TextView
android:id="@+id/tvStoreName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="24dp">
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/ivStoreImage"
app:layout_constraintTop_toTopOf="@id/ivStoreImage"
tools:text="SnackEnak" />
<!-- Store Image with Material Card wrapper -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/storeImageCard"
android:layout_width="80dp"
android:layout_height="80dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tvStoreType"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/ivStoreImage"
app:layout_constraintTop_toBottomOf="@id/tvStoreName"
tools:text="Makanan Ringan" />
<ImageView
android:id="@+id/ivStoreImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@drawable/placeholder_image" />
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:id="@+id/storeRatingContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_constraintStart_toEndOf="@id/ivStoreImage"
app:layout_constraintTop_toBottomOf="@id/tvStoreType">
<ImageView
android:id="@+id/ivStoreRatingStar"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_star"
app:tint="@color/yellow" />
<!-- Store Name -->
<TextView
android:id="@+id/tvStoreName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@android:color/black"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/storeImageCard"
app:layout_constraintTop_toTopOf="@id/storeImageCard"
tools:text="SnackEnak Store" />
<!-- Store Type -->
<TextView
android:id="@+id/tvStoreType"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:background="@drawable/search_background"
android:paddingStart="12dp"
android:paddingTop="4dp"
android:paddingEnd="12dp"
android:paddingBottom="4dp"
android:textColor="@color/blue_500"
android:textSize="12sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/storeImageCard"
app:layout_constraintTop_toBottomOf="@id/tvStoreName"
tools:text="Makanan Ringan" />
<!-- Rating Container -->
<LinearLayout
android:id="@+id/storeRatingContainer"
android:id="@+id/tvStoreRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_constraintStart_toEndOf="@id/storeImageCard"
app:layout_constraintTop_toBottomOf="@id/tvStoreType">
android:layout_marginStart="4dp"
android:textSize="12sp"
android:textStyle="bold"
tools:text="5.0" />
</LinearLayout>
<ImageView
android:id="@+id/ivStoreRatingStar"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_star"
app:tint="@color/yellow" />
<TextView
android:id="@+id/tvStoreLocation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@id/tvActiveStatus"
app:layout_constraintStart_toEndOf="@id/ivStoreImage"
app:layout_constraintTop_toBottomOf="@id/storeRatingContainer"
tools:text="Kabupaten Serang" />
<TextView
android:id="@+id/tvStoreRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold"
tools:text="4.8" />
</LinearLayout>
<TextView
android:id="@+id/tvActiveStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:text="Aktif"
android:textColor="@android:color/black"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/tvStoreLocation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvStoreLocation" />
<!-- Location and Status Row -->
<LinearLayout
android:id="@+id/locationStatusContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="16dp"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/storeImageCard"
app:layout_constraintTop_toBottomOf="@id/storeRatingContainer">
<!-- Location with Icon -->
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@drawable/ic_location"
app:tint="@color/black_300" />
<TextView
android:id="@+id/tvStoreLocation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/blue_400"
android:textSize="13sp"
tools:text="Kabupaten Serang" />
<!-- Status Indicator -->
<View
android:id="@+id/statusDot"
android:layout_width="6dp"
android:layout_height="6dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="6dp"
android:background="@drawable/baseline_circle_24"
android:backgroundTint="@color/black_300"/>
<TextView
android:id="@+id/tvActiveStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/blue_500"
android:textSize="12sp"
android:fontFamily="@font/dmsans_semibold"
tools:text="Aktif" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Divider -->
<!-- <ImageButton-->
<!-- android:id="@+id/btnChevron"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:background="?attr/selectableItemBackgroundBorderless"-->
<!-- android:contentDescription="More"-->
<!-- android:src="@drawable/ic_chevron_right"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent" />-->
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.divider.MaterialDivider
android:id="@+id/divider_product"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
app:dividerColor="@color/black_200"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/storeInfoCard" />
app:layout_constraintTop_toBottomOf="@id/storeInfoContainer"/>
<ProgressBar
android:id="@+id/progress_bar_detail_prod_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/divider_product"/>
<!-- Tab Layout: TO DO implement after review -->
<!-- <com.google.android.material.tabs.TabLayout-->
<!-- android:id="@+id/tabLayout"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- app:layout_constraintTop_toBottomOf="@id/storeInfoContainer"-->
<!-- app:tabIndicatorColor="@color/colorPrimary"-->
<!-- app:tabSelectedTextColor="@color/colorPrimary"-->
<!-- app:tabTextColor="@android:color/darker_gray">-->
<!-- <com.google.android.material.tabs.TabItem-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="Produk" />-->
<!-- <com.google.android.material.tabs.TabItem-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="Kategori" />-->
<!-- </com.google.android.material.tabs.TabLayout>-->
<!-- Products RecyclerView -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_products"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:clipToPadding="false"
android:paddingBottom="16dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/divider_product"
app:spanCount="2"
tools:listitem="@layout/item_product_grid" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -168,13 +168,8 @@
app:layout_constraintTop_toBottomOf="@id/searchContainer" />
<include
android:id="@+id/loadingAll"
layout="@layout/view_loading"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
android:id="@+id/loading"
layout="@layout/view_loading"/>
<include
android:id="@+id/error"

View File

@ -304,55 +304,6 @@
</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"

View File

@ -50,9 +50,8 @@
android:id="@+id/et_otp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Masukkan OTP"
android:hint="Enter OTP"
android:inputType="number"
android:textSize="16dp"
android:textAlignment="center"
android:maxLength="6" />
</com.google.android.material.textfield.TextInputLayout>
@ -62,19 +61,9 @@
android:id="@+id/btn_verify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Kirim kode OTP"
style="@style/button.large.active.medium"
android:text="Verify"
app:cornerRadius="8dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_back"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
android:text="Kembali"
app:cornerRadius="8dp"/>
<!-- Resend OTP -->
<LinearLayout
android:layout_width="match_parent"
@ -92,7 +81,7 @@
android:id="@+id/tv_resend_otp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kirim Ulang"
android:text="Resend"
android:textColor="@color/blue1"
android:textStyle="bold" />
</LinearLayout>

View File

@ -233,12 +233,6 @@
android:padding="12dp"
android:textSize="14sp" />
<CheckBox
android:id="@+id/checkbox_approve"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Saya telah membaca dan menyetujui Syarat dan Ketentuan aplikasi" />
<!-- Navigation Button (Previous) -->
<Button
android:id="@+id/btn_previous"
@ -246,14 +240,24 @@
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:background="@drawable/bg_button_outline"
android:visibility="gone"
android:text="Kembali"
android:textAllCaps="false"
android:textColor="@color/blue1"
style="@style/Widget.Material3.Button.OutlinedButton.Icon"/>
style="@style/Widget.MaterialComponents.Button.OutlinedButton"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/checkbox_approve"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Saya telah membaca dan menyetujui Syarat dan Ketentuan aplikasi" />
</LinearLayout>
</ScrollView>
<!-- Register Button -->
@ -261,7 +265,7 @@
android:id="@+id/btn_register"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginHorizontal="16dp"
android:layout_margin="16dp"
android:background="@drawable/button_address_background"
android:text="@string/signup"
android:textAllCaps="false"

View File

@ -9,9 +9,10 @@
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:gravity="start"
android:layout_marginStart="16dp"
android:layout_marginVertical="8dp"
android:layout_marginBottom="4dp"
android:layout_gravity="center_horizontal"
android:orientation="horizontal">
<RadioButton
@ -23,54 +24,39 @@
android:layout_height="wrap_content"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:weightSum="1">
<!-- Left Section -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.7"
android:orientation="vertical">
<TextView
android:id="@+id/courier_name_cost"
android:fontFamily="@font/dmsans_semibold"
android:textSize="14sp"
android:paddingHorizontal="2dp"
android:paddingTop="4dp"
android:ellipsize="end"
android:scrollHorizontally="false"
android:singleLine="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="2"
android:text="JNE Express"/>
<TextView
android:id="@+id/est_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:paddingHorizontal="4dp"
android:text="Estimasi 3-4 hari"/>
</LinearLayout>
<!-- Right Section -->
android:orientation="vertical">
<TextView
android:id="@+id/cost_price"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.3"
android:textSize="14sp"
android:gravity="start"
android:id="@+id/courier_name_cost"
android:fontFamily="@font/dmsans_semibold"
android:text="Rp15.000"/>
android:textSize="20sp"
android:padding="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="JNE"/>
<TextView
android:id="@+id/est_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:paddingHorizontal="8dp"
android:text="Estimasi 3-4 hari"/>
</LinearLayout>
<TextView
android:id="@+id/cost_price"
android:textSize="16sp"
android:gravity="center_vertical"
android:layout_margin="16dp"
android:fontFamily="@font/dmsans_semibold"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Rp15.0000"/>
</LinearLayout>
<View

View File

@ -22,8 +22,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Muat Ulang"
style="@style/Widget.Material3.FloatingActionButton.Large.Surface"
android:text="Retry"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@ -6,13 +6,12 @@
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -147,112 +147,29 @@
<string name="buka_toko_desc">Mohon untuk melengkapi formulir pendaftaran ini agar dapat mengakses fitur penjual pada aplikasi.</string>
<!-- List Bank -->
<string-array name="bank_names">
<item>Bank of America</item>
<item>Bank of China (Hong Kong)</item>
<item>Citibank</item>
<item>Deutsche Bank Ag</item>
<item>JP Morgan Chase Bank</item>
<item>MUFG Bank</item>
<string-array name="bank_name_array">
<item>Allo Bank Indonesia</item>
<item>Bank Aceh Syariah</item>
<item>Bank Aladin Syariah</item>
<item>Bank Amar Indonesia</item>
<item>Bank ANZ Indonesia</item>
<item>Bank Artha Graha Internasional</item>
<item>Bank BCA Syariah</item>
<item>Bank BNP Paribas Indonesia</item>
<item>Bank BTPN Syariah</item>
<item>Bank Bumi Arta</item>
<item>Bank Capital Indonesia</item>
<item>Bank Central Asia (BCA)</item>
<item>Bank China Construction Bank Indonesia</item>
<item>Bank CIMB Niaga</item>
<item>Bank CTBC Indonesia</item>
<item>Bank Danamon Indonesia</item>
<item>Bank DBS Indonesia</item>
<item>Bank Digital BCA</item>
<item>Bank Ganesha</item>
<item>Bank Hibank Indonesia</item>
<item>Bank HSBC Indonesia</item>
<item>Bank IBK Indonesia</item>
<item>Bank ICBC Indonesia</item>
<item>Bank Ina Perdana</item>
<item>Bank Index Selindo</item>
<item>Bank Jabar Banten Syariah</item>
<item>Bank Central Asia (BCA)</item>
<item>Bank BCA Syariah</item>
<item>Bank Jago</item>
<item>Bank JTrust Indonesia</item>
<item>Bank Kb Bukopin Syariah</item>
<item>Bank Kb Bukopin</item>
<item>Bank Keb Hana Indonesia</item>
<item>Bank Mandiri</item>
<item>Bank Mandiri Taspen</item>
<item>Bank Maspion Indonesia</item>
<item>Bank Mayapada International</item>
<item>Bank Maybank Indonesia</item>
<item>Bank Mega Syariah</item>
<item>Bank Mega</item>
<item>Bank Mestika Dharma</item>
<item>Bank Mizuho Indonesia</item>
<item>Bank MNC Internasional</item>
<item>Bank Muamalat Indonesia</item>
<item>Bank Multiarta Sentosa</item>
<item>Bank Nagari</item>
<item>Bank Nano Syariah</item>
<item>Bank Nationalnobu</item>
<item>Bank Mandiri </item>
<item>Bank Kb Bukopi</item>
<item>Bank Jabar Banten Syariah</item>
<item> Bank Mandiri Taspen</item>
<item>Bank Maybank Indonesia </item>
<item>Bank Negara Indonesia (BNI)</item>
<item>Bank Neo Commerce</item>
<item>Bank NTB Syariah</item>
<item>Bank OCBC NISP</item>
<item>Bank of India Indonesia</item>
<item>Bank Oke Indonesia</item>
<item>Bank PAN Indonesia</item>
<item>Bank Panin Dubai Syariah</item>
<item>Bank Pembangunan Daerah (BPD) Bali</item>
<item>Bank Permata</item>
<item>Bank QNB Indonesia</item>
<item>Bank Rakyat Indonesia (BRI)</item>
<item>Bank Raya Indonesia</item>
<item>Bank Resona Perdania</item>
<item>Bank Sahabat Sampoerna</item>
<item>Bank Saqu Indonesia</item>
<item>Bank SBI Indonesia</item>
<item>Bank Seabank Indonesia</item>
<item>Bank Shinhan Indonesia</item>
<item>Bank Sinarmas</item>
<item>Bank SMBC Indonesia</item>
<item>Bank Sinarmas </item>
<item>Bank Syariah Indonesia (BSI)</item>
<item>Bank Tabungan Negara (BTN)</item>
<item>Bank UOB Indonesia</item>
<item>Bank Victoria International</item>
<item>Bank Victoria Syariah</item>
<item>Bank Woori Saudara Indonesia 1906</item>
<item>BPD Banten</item>
<item>BPD Bengkulu</item>
<item>BPD Daerah Istimewa Yogyakarta</item>
<item>BPD DKI</item>
<item>BPD Jambi</item>
<item>BPD Jawa Barat dan Banten</item>
<item>BPD Jawa Tengah</item>
<item>BPD Jawa Timur</item>
<item>BPD Kalimantan Barat</item>
<item>BPD Kalimantan Selatan</item>
<item>BPD Kalimantan Tengah</item>
<item>BPD Kalimantan Timur dan Kalimantan Utara</item>
<item>BPD Lampung</item>
<item>BPD Maluku dan Maluku Utara</item>
<item>BPD Nusa Tenggara Timur</item>
<item>BPD Papua</item>
<item>BPD Riau Kepri Syariah</item>
<item>BPD Sulawesi Selatan dan Sulawesi Barat</item>
<item>BPD Sulawesi Tengah</item>
<item>BPD Sulawesi Tenggara</item>
<item>BPD Sulawesi Utara dan Gorontalo</item>
<item>BPD Sumatera Selatan dan Bangka Belitung</item>
<item>BPD Sumatera Utara</item>
<item>Krom Bank Indonesia</item>
<item>Super Bank Indonesia</item>
<item>Standard Chartered Bank</item>
</string-array>
</resources>

View File

@ -1,4 +1,4 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.Ecommerce_serang" parent="Theme.Material3.Light.NoActionBar">
<!-- Primary Color Customization -->
@ -269,7 +269,7 @@
</style>
<style name="button.small.active.short">
<item name="android:layout_width">100dp</item>
<item name="android:layout_width">144dp</item>
</style>
<style name="button.small.active.short.only_icon">
@ -330,12 +330,4 @@
<item name="cornerSize">5dp</item>
</style>
<style name="Theme.App.SplashScreen" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/white</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/logo_bisa_umkm</item>
<item name="windowSplashScreenAnimationDuration">1000</item>
<item name="android:windowSplashScreenBehavior" tools:targetApi="33">icon_preferred</item>
<item name="postSplashScreenTheme">@style/Theme.Ecommerce_serang</item>
</style>
</resources>
</resources>