fix progress bar loading

This commit is contained in:
shaulascr
2025-08-22 11:21:07 +07:00
parent 792e247eaa
commit 9cd0675d82
15 changed files with 261 additions and 150 deletions

View File

@ -86,19 +86,72 @@ class RegisterActivity : AppCompatActivity() {
}
}
// In RegisterActivity, add debug to navigateToStep:
fun navigateToStep(step: Int, userData: RegisterRequest?) {
val fragment = when (step) {
1 -> RegisterStep1Fragment.newInstance()
2 -> RegisterStep2Fragment.newInstance(userData)
3 -> RegisterStep3Fragment.newInstance()
else -> null
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})")
}
fragment?.let {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, it)
.addToBackStack(null)
.commit()
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)
}
}
}

View File

@ -1,6 +1,7 @@
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
@ -32,6 +33,9 @@ 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 {
@ -42,8 +46,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 {
@ -112,13 +116,23 @@ class RegisterStep2Fragment : Fragment() {
}
}
binding.btnBack.setOnClickListener {
parentFragmentManager.popBackStack()
}
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?) {
@ -172,37 +186,9 @@ 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) {
@ -314,9 +300,117 @@ class RegisterStep2Fragment : Fragment() {
}
override fun onDestroyView() {
super.onDestroyView()
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()
_binding = null
}
}
override fun onDetach() {
super.onDetach()
Log.d(TAG, "onDetach - final cleanup")
stopTimer()
}
}
// override fun onDestroyView() {
// super.onDestroyView()
// countDownTimer?.cancel()
// _binding = 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()
// }

View File

@ -6,6 +6,7 @@ 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
@ -140,12 +141,12 @@ class HomeFragment : Fragment() {
viewModel.uiState.collect { state ->
when (state) {
is HomeUiState.Loading -> {
binding.loading.root.isVisible = true
binding.loadingAll.visibility = View.VISIBLE
binding.error.root.isVisible = false
binding.home.isVisible = false
}
is HomeUiState.Success -> {
binding.loading.root.isVisible = false
binding.loadingAll.visibility = View.GONE
binding.error.root.isVisible = false
binding.home.isVisible = true
val products = state.products
@ -154,10 +155,12 @@ class HomeFragment : Fragment() {
productAdapter?.updateLimitedProducts(products)
}
is HomeUiState.Error -> {
binding.loading.root.isVisible = false
binding.loadingAll.visibility = View.GONE
binding.error.root.isVisible = true
binding.home.isVisible = false
binding.error.errorMessage.text = state.message
// 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.retryButton.setOnClickListener {
viewModel.retry()
}

View File

@ -90,6 +90,8 @@ class DetailProfileActivity : AppCompatActivity() {
Log.e("DetailProfileActivity", "Error from ViewModel: $error")
Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
}
}
private fun setupClickListeners() {

View File

@ -222,4 +222,11 @@ class ProfileFragment : Fragment() {
}
}
override fun onResume() {
super.onResume()
viewModel.loadUserProfile()
viewModel.checkStoreUser()
}
}

View File

@ -2,6 +2,7 @@ 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
@ -24,7 +25,6 @@ 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,7 +33,6 @@ 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
@ -41,9 +40,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 {
@ -54,7 +53,7 @@ class EditProfileCustActivity : AppCompatActivity() {
}
private val getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
if (result.resultCode == RESULT_OK) {
val data: Intent? = result.data
data?.data?.let {
selectedImageUri = it
@ -105,8 +104,8 @@ class EditProfileCustActivity : AppCompatActivity() {
}
userProfile?.let {
currentUser = it
populateFields(it)
setupClickListeners()
observeViewModel()
}
@ -118,7 +117,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))
}
@ -156,7 +155,7 @@ class EditProfileCustActivity : AppCompatActivity() {
}
binding.btnSave.setOnClickListener {
saveProfile()
if (hasChanged()) confirmUpdate() else finish()
}
}
@ -213,25 +212,38 @@ 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
@ -242,82 +254,10 @@ class EditProfileCustActivity : AppCompatActivity() {
phone = phone,
birthDate = serverBirthDate,
email = email,
imageUri = selectedImageUri
imageUri = imgProfile
)
}
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

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

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

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_24"
android:src="@drawable/baseline_account_circle_100"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
@ -87,6 +87,7 @@
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_24"
android:src="@drawable/baseline_account_circle_100"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
@ -188,6 +188,7 @@
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

@ -167,9 +167,15 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/searchContainer" />
<include
android:id="@+id/loading"
layout="@layout/view_loading"/>
<ProgressBar
android:id="@+id/loadingAll"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<include
android:id="@+id/error"

View File

@ -71,7 +71,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:visibility="gone"
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
android:text="Kembali"
app:cornerRadius="8dp"/>

View File

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

View File

@ -6,11 +6,10 @@
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

View File

@ -269,7 +269,7 @@
</style>
<style name="button.small.active.short">
<item name="android:layout_width">144dp</item>
<item name="android:layout_width">100dp</item>
</style>
<style name="button.small.active.short.only_icon">