This commit is contained in:
shaulascr
2025-07-10 11:58:14 +07:00
parent f7f198e46f
commit adf324e15d
22 changed files with 279 additions and 180 deletions

3
app/.gitignore vendored
View File

@ -1 +1,2 @@
/build /build
google-services.json

View File

@ -15,10 +15,14 @@ class ApiConfig {
val loggingInterceptor = HttpLoggingInterceptor().apply { val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY level = HttpLoggingInterceptor.Level.BODY
//httplogginginterceptor ntuk debug dan monitoring request/response
} }
val authInterceptor = AuthInterceptor(tokenManager) val authInterceptor = AuthInterceptor(tokenManager)
// utk tambak token auth otomatis pada header
// Konfigurasi OkHttpClient
//Low-level HTTP client yang melakukan actual network request
val client = OkHttpClient.Builder() val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor) .addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor) .addInterceptor(authInterceptor)
@ -27,13 +31,17 @@ class ApiConfig {
.writeTimeout(300, TimeUnit.SECONDS) // 5 minutes .writeTimeout(300, TimeUnit.SECONDS) // 5 minutes
.build() .build()
// Konfigurasi Retrofit
val retrofit = Retrofit.Builder() val retrofit = Retrofit.Builder()
//almat domain backend
.baseUrl(BuildConfig.BASE_URL) .baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
//gson convertes: mengkonversi JSON ke object Kotlin dan sebaliknya
.client(client) .client(client)
.build() .build()
return retrofit.create(ApiService::class.java) return retrofit.create(ApiService::class.java)
// retrofit : menyederhanakan HTTP Request dgn mengubah interface Kotlin di ApiService menjadi HTTP calls secara otomatis
} }
fun getUnauthenticatedApiService(): ApiService { fun getUnauthenticatedApiService(): ApiService {

View File

@ -8,9 +8,7 @@ import android.widget.Toast
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.data.api.dto.FcmReq import com.alya.ecommerce_serang.data.api.dto.FcmReq
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.data.repository.Result
@ -43,20 +41,18 @@ class LoginActivity : AppCompatActivity() {
setContentView(binding.root) setContentView(binding.root)
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
enableEdgeToEdge() enableEdgeToEdge()
// Apply insets to your root layout // ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets -> // val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) // view.setPadding(
view.setPadding( // systemBars.left,
systemBars.left, // systemBars.top,
systemBars.top, // systemBars.right,
systemBars.right, // systemBars.bottom
systemBars.bottom // )
) // windowInsets
windowInsets // }
}
// onBackPressedDispatcher.addCallback(this) { // onBackPressedDispatcher.addCallback(this) {
// // Handle the back button event // // Handle the back button event

View File

@ -43,24 +43,6 @@ class RegisterActivity : AppCompatActivity() {
setContentView(binding.root) setContentView(binding.root)
sessionManager = SessionManager(this) sessionManager = SessionManager(this)
WindowCompat.setDecorFitsSystemWindows(window, false)
enableEdgeToEdge()
// Apply insets to your root layout
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.setPadding(
systemBars.left,
systemBars.top,
systemBars.right,
systemBars.bottom
)
windowInsets
}
Log.d("RegisterActivity", "Token in storage: '${sessionManager.getToken()}'") Log.d("RegisterActivity", "Token in storage: '${sessionManager.getToken()}'")
Log.d("RegisterActivity", "User ID in storage: '${sessionManager.getUserId()}'") Log.d("RegisterActivity", "User ID in storage: '${sessionManager.getUserId()}'")
@ -104,7 +86,7 @@ class RegisterActivity : AppCompatActivity() {
} }
} }
// Function to navigate to the next fragment // navigate step register in fragment
fun navigateToStep(step: Int, userData: RegisterRequest?) { fun navigateToStep(step: Int, userData: RegisterRequest?) {
val fragment = when (step) { val fragment = when (step) {
1 -> RegisterStep1Fragment.newInstance() 1 -> RegisterStep1Fragment.newInstance()

View File

@ -204,6 +204,13 @@ class RegisterStep1Fragment : Fragment() {
} }
} }
} }
registerViewModel.toastMessage.observe(viewLifecycleOwner){ event ->
//memanggil toast check value email dan phone
event.getContentIfNotHandled()?.let { msg ->
Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show()
}
}
} }
private fun validateAndProceed() { private fun validateAndProceed() {

View File

@ -252,15 +252,10 @@ class RegisterStep2Fragment : Fragment() {
sessionManager.saveToken(accessToken) sessionManager.saveToken(accessToken)
Log.d(TAG, "Token saved to SessionManager: $accessToken") Log.d(TAG, "Token saved to SessionManager: $accessToken")
// Also save user ID if available in the login response
// result.data.?.let { userId ->
// sessionManager.saveUserId(userId)
// }
Log.d(TAG, "Login successful, token saved: $accessToken")
// Proceed to Step 3 // Proceed to Step 3
Log.d(TAG, "Proceeding to Step 3 after successful login") Log.d(TAG, "Proceeding to Step 3 after successful login")
// call navigate register step from activity
(activity as? RegisterActivity)?.navigateToStep(3, null ) (activity as? RegisterActivity)?.navigateToStep(3, null )
} }
is Result.Error -> { is Result.Error -> {
@ -270,7 +265,7 @@ class RegisterStep2Fragment : Fragment() {
// Show error message but continue to Step 3 anyway // Show error message but continue to Step 3 anyway
Log.e(TAG, "Login failed but proceeding to Step 3", result.exception) Log.e(TAG, "Login failed but proceeding to Step 3", result.exception)
Toast.makeText(requireContext(), "Gagal login, namun berhasil membuat akun", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Berhasil membuat akun, namun belum login", Toast.LENGTH_SHORT).show()
// Proceed to Step 3 // Proceed to Step 3
(activity as? RegisterActivity)?.navigateToStep(3, null) (activity as? RegisterActivity)?.navigateToStep(3, null)

View File

@ -1,6 +1,7 @@
package com.alya.ecommerce_serang.ui.cart package com.alya.ecommerce_serang.ui.cart
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
@ -153,7 +154,8 @@ class CartActivity : AppCompatActivity() {
} }
viewModel.isLoading.observe(this) { isLoading -> viewModel.isLoading.observe(this) { isLoading ->
// Show/hide loading indicator if needed binding.progressBarCart?.visibility = if (isLoading) View.VISIBLE else View.GONE
Log.d("CartActivity", "Loading state: $isLoading")
} }
viewModel.errorMessage.observe(this) { errorMessage -> viewModel.errorMessage.observe(this) { errorMessage ->

View File

@ -129,6 +129,7 @@ class ChatActivity : AppCompatActivity() {
return return
} }
// set up data toko
binding.tvStoreName.text = storeName binding.tvStoreName.text = storeName
val fullImageUrl = when (val img = storeImg) { val fullImageUrl = when (val img = storeImg) {
is String -> { is String -> {
@ -142,7 +143,7 @@ class ChatActivity : AppCompatActivity() {
.placeholder(R.drawable.placeholder_image) .placeholder(R.drawable.placeholder_image)
.into(binding.imgProfile) .into(binding.imgProfile)
// Set chat parameters to ViewModel // Set chat parameter to send to ViewModel with product
viewModel.setChatParameters( viewModel.setChatParameters(
storeId = storeId, storeId = storeId,
productId = productId, productId = productId,
@ -159,10 +160,12 @@ class ChatActivity : AppCompatActivity() {
} }
// Setup UI components // Setup UI components
// rv isi chat
setupRecyclerView() setupRecyclerView()
setupWindowInsets() setupWindowInsets()
setupListeners() setupListeners()
setupTypingIndicator() setupTypingIndicator()
// observe listener from viewmodel
observeViewModel() observeViewModel()
// If opened from ChatListFragment with a valid chatRoomId // If opened from ChatListFragment with a valid chatRoomId

View File

@ -1,6 +1,7 @@
package com.alya.ecommerce_serang.ui.chat package com.alya.ecommerce_serang.ui.chat
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -55,31 +56,44 @@ class ChatListFragment : Fragment() {
viewModel.chatList.observe(viewLifecycleOwner) { result -> viewModel.chatList.observe(viewLifecycleOwner) { result ->
when (result) { when (result) {
is Result.Success -> { is Result.Success -> {
val adapter = ChatListAdapter(result.data) { chatItem -> val data = result.data
// Use the ChatActivity.createIntent factory method for proper navigation
ChatActivity.createIntent( binding.tvEmptyChat.visibility = View.GONE
context = requireActivity(), if (data.isNotEmpty()) {
storeId = chatItem.storeId, val adapter = ChatListAdapter(data) { chatItem ->
productId = 0, // Default value since we don't have it in ChatListItem ChatActivity.createIntent(
productName = null, // Null is acceptable as per ChatActivity context = requireActivity(),
productPrice = "", storeId = chatItem.storeId,
productImage = null, productId = 0,
productRating = null, productName = null,
storeName = chatItem.storeName, productPrice = "",
chatRoomId = chatItem.chatRoomId, productImage = null,
storeImage = chatItem.storeImage productRating = null,
) storeName = chatItem.storeName,
chatRoomId = chatItem.chatRoomId,
storeImage = chatItem.storeImage
)
}
binding.chatListRecyclerView.adapter = adapter
} else {
binding.tvEmptyChat.visibility = View.VISIBLE
} }
binding.chatListRecyclerView.adapter = adapter
} }
is Result.Error -> { is Result.Error -> {
binding.tvEmptyChat.visibility = View.VISIBLE
Toast.makeText(requireContext(), "Failed to load chats", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Failed to load chats", Toast.LENGTH_SHORT).show()
} }
Result.Loading -> { Result.Loading -> {
binding.progressBarChat.visibility = View.VISIBLE
// Optional: show progress bar // Optional: show progress bar
} }
} }
} }
//loading chat list
viewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
binding.progressBarChat?.visibility = if (isLoading) View.VISIBLE else View.GONE
Log.d(TAG, "Loading state: $isLoading")
}
} }
@ -89,6 +103,6 @@ class ChatListFragment : Fragment() {
} }
companion object{ companion object{
private var TAG = "ChatListFragment"
} }
} }

View File

@ -60,6 +60,9 @@ class ChatViewModel @Inject constructor(
private val _state = MutableLiveData(ChatUiState()) private val _state = MutableLiveData(ChatUiState())
val state: LiveData<ChatUiState> = _state val state: LiveData<ChatUiState> = _state
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
val _chatRoomId = MutableLiveData<Int>(0) val _chatRoomId = MutableLiveData<Int>(0)
val chatRoomId: LiveData<Int> = _chatRoomId val chatRoomId: LiveData<Int> = _chatRoomId
@ -94,12 +97,15 @@ class ChatViewModel @Inject constructor(
} }
private fun initializeUser() { private fun initializeUser() {
_isLoading.value = true
viewModelScope.launch { viewModelScope.launch {
Log.d(TAG, "Initializing user session...") Log.d(TAG, "Initializing user session...")
when (val result = chatRepository.fetchUserProfile()) { when (val result = chatRepository.fetchUserProfile()) {
is Result.Success -> { is Result.Success -> {
currentUserId = result.data?.userId currentUserId = result.data?.userId
_isLoading.value = false
Log.d(TAG, "User session initialized - User ID: $currentUserId") Log.d(TAG, "User session initialized - User ID: $currentUserId")
if (currentUserId == null || currentUserId == 0) { if (currentUserId == null || currentUserId == 0) {
@ -111,10 +117,12 @@ class ChatViewModel @Inject constructor(
} }
} }
is Result.Error -> { is Result.Error -> {
_isLoading.value = false
Log.e(TAG, "Failed to fetch user profile: ${result.exception.message}") Log.e(TAG, "Failed to fetch user profile: ${result.exception.message}")
updateState { it.copy(error = "User authentication error. Please login again.") } updateState { it.copy(error = "User authentication error. Please login again.") }
} }
is Result.Loading -> { is Result.Loading -> {
_isLoading.value = true
Log.d(TAG, "Loading user profile...") Log.d(TAG, "Loading user profile...")
} }
} }
@ -335,10 +343,13 @@ class ChatViewModel @Inject constructor(
} }
fun getChatList() { fun getChatList() {
_isLoading.value = true
Log.d(TAG, "Getting chat list...") Log.d(TAG, "Getting chat list...")
viewModelScope.launch { viewModelScope.launch {
_chatList.value = Result.Loading // _chatList.value = Result.Loading
_chatList.value = chatRepository.getListChat() _chatList.value = chatRepository.getListChat()
_isLoading.value = false
} }
} }

View File

@ -208,25 +208,6 @@ class HomeFragment : Fragment() {
private fun initUi() { private fun initUi() {
// For LightStatusBar // For LightStatusBar
setLightStatusBar() setLightStatusBar()
// val banners = binding.banners
// banners.offscreenPageLimit = 1
//
// val nextItemVisiblePx = resources.getDimension(R.dimen.viewpager_next_item_visible)
// val currentItemHorizontalMarginPx =
// resources.getDimension(R.dimen.viewpager_current_item_horizontal_margin)
// val pageTranslationX = nextItemVisiblePx + currentItemHorizontalMarginPx
//
// banners.setPageTransformer { page, position ->
// page.translationX = -pageTranslationX * position
// page.scaleY = 1 - (0.25f * kotlin.math.abs(position))
// }
//
// banners.addItemDecoration(
// HorizontalMarginItemDecoration(
// requireContext(),
// R.dimen.viewpager_current_item_horizontal_margin
// )
// )
} }
private fun handleProductClick(product: ProductsItem) { private fun handleProductClick(product: ProductsItem) {
@ -248,8 +229,4 @@ class HomeFragment : Fragment() {
categoryAdapter = null categoryAdapter = null
_binding = null _binding = null
} }
// private fun showLoading(isLoading: Boolean) {
// binding.progressBar.isVisible = isLoading
// }
} }

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
@ -110,11 +111,6 @@ class CheckoutActivity : AppCompatActivity() {
finish() finish()
} }
} }
// viewModel.getPaymentMethods { paymentMethods ->
// // Logging is just for debugging
// Log.d("CheckoutActivity", "Loaded ${paymentMethods.size} payment methods")
// }
} }
private fun setupToolbar() { private fun setupToolbar() {
@ -165,7 +161,7 @@ class CheckoutActivity : AppCompatActivity() {
// Observe loading state // Observe loading state
viewModel.isLoading.observe(this) { isLoading -> viewModel.isLoading.observe(this) { isLoading ->
binding.btnPay.isEnabled = !isLoading binding.btnPay.isEnabled = !isLoading
// Show/hide loading indicator if you have one
} }
// Observe error messages // Observe error messages
@ -273,10 +269,14 @@ class CheckoutActivity : AppCompatActivity() {
private fun updateShippingUI(shipName: String, shipService: String, shipEtd: String, shipPrice: Int) { private fun updateShippingUI(shipName: String, shipService: String, shipEtd: String, shipPrice: Int) {
if (shipName.isNotEmpty() && shipService.isNotEmpty()) { if (shipName.isNotEmpty() && shipService.isNotEmpty()) {
// Display shipping name and service in one line // Display shipping name and service in one line
binding.cardShipment.visibility = View.VISIBLE
binding.tvCourierName.text = "$shipName $shipService" binding.tvCourierName.text = "$shipName $shipService"
binding.tvDeliveryEstimate.text = "$shipEtd hari kerja" binding.tvDeliveryEstimate.text = "$shipEtd hari kerja"
binding.tvShippingPrice.text = formatCurrency(shipPrice.toDouble()) binding.tvShippingPrice.text = formatCurrency(shipPrice.toDouble())
binding.rbJne.isChecked = true binding.rbJne.isChecked = true
} else {
binding.cardShipment.visibility = View.GONE
} }
} }

View File

@ -44,6 +44,9 @@ import com.google.gson.Gson
import java.io.File import java.io.File
import java.text.NumberFormat import java.text.NumberFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
@ -197,12 +200,12 @@ class DetailOrderStatusActivity : AppCompatActivity() {
Log.d(TAG, "populateOrderDetails: Payment method=${orders.payInfoName ?: "Tidak tersedia"}") Log.d(TAG, "populateOrderDetails: Payment method=${orders.payInfoName ?: "Tidak tersedia"}")
// Set subtotal, shipping cost, and total // Set subtotal, shipping cost, and total
val subtotal = orders.totalAmount?.minus(orders.shipmentPrice.toIntOrNull() ?: 0) ?: 0 // val subtotal = orders.totalAmount?.minus(orders.shipmentPrice.toDouble() ?: 0) ?: 0
binding.tvSubtotal.text = formatCurrency(subtotal.toDouble()) // binding.tvSubtotal.text = formatCurrency(subtotal.toDouble())
binding.tvShippingCost.text = formatCurrency(orders.shipmentPrice.toDouble()) binding.tvShippingCost.text = formatCurrency(orders.shipmentPrice.toDouble())
binding.tvTotal.text = formatCurrency(orders.totalAmount?.toDouble() ?: 0.00) binding.tvTotal.text = formatCurrency(orders.totalAmount?.toDouble() ?: 0.00)
Log.d(TAG, "populateOrderDetails: Subtotal=$subtotal, Shipping=${orders.shipmentPrice}, Total=${orders.totalAmount}") Log.d(TAG, "populateOrderDetails: Subtotal=, Shipping=${orders.shipmentPrice}, Total=${orders.totalAmount}")
// Adjust buttons based on order status // Adjust buttons based on order status
Log.d(TAG, "populateOrderDetails: Adjusting buttons for status=$orderStatus") Log.d(TAG, "populateOrderDetails: Adjusting buttons for status=$orderStatus")
@ -223,6 +226,11 @@ class DetailOrderStatusActivity : AppCompatActivity() {
this.adapter = adapter this.adapter = adapter
} }
adapter.submitList(orderItems) adapter.submitList(orderItems)
// get data from ordetlistitemsitem untuk ambil subtotal nya dan dijumlahkan
val subtotalSum = orderItems.sumOf { it.subtotal }
binding.tvSubtotal.text = formatCurrency(subtotalSum.toDouble())
} }
private fun adjustButtonsBasedOnStatus(orders: Orders, status: String) { private fun adjustButtonsBasedOnStatus(orders: Orders, status: String) {
@ -287,7 +295,7 @@ class DetailOrderStatusActivity : AppCompatActivity() {
// Show status note // Show status note
binding.tvStatusHeader.text = "Sudah Dibayar" binding.tvStatusHeader.text = "Sudah Dibayar"
binding.tvStatusNote.visibility = View.VISIBLE binding.tvStatusNote.visibility = View.VISIBLE
binding.tvStatusNote.text = "Menunggu pesanan dikonfirmasi penjual ${formatDatePay(orders.updatedAt)}" binding.tvStatusNote.text = "Menunggu pesanan dikonfirmasi penjual ${formatDatePaid(orders.updatedAt)}"
binding.tvPaymentDeadlineLabel.text = "Batas konfirmasi penjual:" binding.tvPaymentDeadlineLabel.text = "Batas konfirmasi penjual:"
binding.tvPaymentDeadline.text = formatDatePaid(orders.updatedAt) binding.tvPaymentDeadline.text = formatDatePaid(orders.updatedAt)
@ -606,32 +614,17 @@ class DetailOrderStatusActivity : AppCompatActivity() {
} }
private fun formatDate(dateString: String): String { private fun formatDate(dateString: String): String {
Log.d(TAG, "formatDate: Formatting date: $dateString")
return try { return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()) val jakarta = ZoneId.of("Asia/Jakarta")
inputFormat.timeZone = TimeZone.getTimeZone("UTC") val instant = Instant.parse(dateString) // parses ISO8601 with Z
val zoned = instant.atZone(jakarta)
val timeFormat = SimpleDateFormat("HH:mm", Locale("id", "ID")) val time = DateTimeFormatter.ofPattern("HH:mm", Locale("id", "ID")).format(zoned)
val dateFormat = SimpleDateFormat("dd MMMM yyyy", Locale("id", "ID")) val date = DateTimeFormatter.ofPattern("dd MMMM yyyy",Locale("id", "ID")).format(zoned)
val date = inputFormat.parse(dateString) "$time\n$date"
date?.let {
val calendar = Calendar.getInstance()
calendar.time = it
calendar.set(Calendar.HOUR_OF_DAY, 23)
calendar.set(Calendar.MINUTE, 59)
val timePart = timeFormat.format(calendar.time)
val datePart = dateFormat.format(calendar.time)
val formatted = "$timePart\n$datePart"
Log.d(TAG, "formatDate: Formatted date: $formatted")
formatted
} ?: dateString
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "formatDate: Error formatting date: ${e.message}", e) Log.e(TAG, "formatDate: $e")
dateString dateString
} }
} }

View File

@ -64,6 +64,9 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
private val _registeredUser = MutableLiveData<User>() private val _registeredUser = MutableLiveData<User>()
val registeredUser: LiveData<User> = _registeredUser val registeredUser: LiveData<User> = _registeredUser
private val _toastMessage = MutableLiveData<com.alya.ecommerce_serang.utils.viewmodel.Event<String>>()
val toastMessage: LiveData<com.alya.ecommerce_serang.utils.viewmodel.Event<String>> = _toastMessage
// For address data // For address data
var selectedProvinceId: Int? = null var selectedProvinceId: Int? = null
var selectedCityId: String? = null var selectedCityId: String? = null
@ -224,9 +227,16 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
Log.d("RegisterViewModel", "OTP Response: ${response.available}") Log.d("RegisterViewModel", "OTP Response: ${response.available}")
_checkValue.value = Result.Success(response.available)// Store the message for UI feedback _checkValue.value = Result.Success(response.available)// Store the message for UI feedback
val msg = if (response.available)
"${request.fieldRegis.capitalize()} dapat digunakan"
else
"${request.fieldRegis.capitalize()} sudah terdaftar"
_toastMessage.value = Event(msg)
} catch (exception: Exception) { } catch (exception: Exception) {
// Handle any errors and update state // Handle any errors and update state
_checkValue.value = Result.Error(exception) _checkValue.value = Result.Error(exception)
_toastMessage.value = Event("Gagal memeriksa ${request.fieldRegis}")
// Log the error for debugging // Log the error for debugging
Log.e("RegisterViewModel", "Error:", exception) Log.e("RegisterViewModel", "Error:", exception)
@ -375,4 +385,10 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
companion object { companion object {
private const val TAG = "RegisterViewModel" private const val TAG = "RegisterViewModel"
} }
}
class Event<out T>(private val data: T) {
private var handled = false
fun getContentIfNotHandled(): T? = if (handled) null else { handled = true; data }
} }

View File

@ -21,6 +21,18 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/header"/> app:layout_constraintTop_toBottomOf="@+id/header"/>
<ProgressBar
android:id="@+id/progressBarCart"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:layout_marginBottom="8dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/bottomCheckoutLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/header"/>
<TextView <TextView
android:id="@+id/tvWholesaleWarning" android:id="@+id/tvWholesaleWarning"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -6,7 +6,7 @@
android:id="@+id/main" android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/black_800" android:background="@color/white"
android:theme="@style/Theme.Ecommerce_serang" android:theme="@style/Theme.Ecommerce_serang"
tools:context=".ui.order.CheckoutActivity"> tools:context=".ui.order.CheckoutActivity">
@ -75,7 +75,7 @@
android:id="@+id/tv_places_address" android:id="@+id/tv_places_address"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Rumah" android:text="-"
android:textColor="#5A5A5A" android:textColor="#5A5A5A"
android:paddingHorizontal="8dp" android:paddingHorizontal="8dp"
android:paddingVertical="2dp" android:paddingVertical="2dp"
@ -94,7 +94,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:text="Jl. Pegangasan Timur" android:text="-"
android:textSize="14sp" android:textSize="14sp"
android:layout_marginStart="32dp" /> android:layout_marginStart="32dp" />
@ -179,9 +179,11 @@
</LinearLayout> </LinearLayout>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/card_shipment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:visibility="gone"
app:cardCornerRadius="8dp" app:cardCornerRadius="8dp"
app:cardElevation="0dp" app:cardElevation="0dp"
app:cardBackgroundColor="#F5F5F5"> app:cardBackgroundColor="#F5F5F5">

View File

@ -3,38 +3,48 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:theme="@style/Theme.Ecommerce_serang"
tools:context=".ui.product.listproduct.ListProductActivity"> tools:context=".ui.product.listproduct.ListProductActivity">
<include <LinearLayout
android:id="@+id/searchContainerList"
layout="@layout/view_search_back"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/rvProductsList"/> app:layout_constraintTop_toTopOf="parent">
<include
android:id="@+id/searchContainerList"
layout="@layout/view_search_back"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<!-- <com.google.android.material.divider.MaterialDivider-->
<!-- android:id="@+id/divider_product"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginTop="2dp"-->
<!-- app:layout_constraintTop_toBottomOf="@id/searchContainer"/>-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvProductsList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="23dp"
app:layout_constraintTop_toBottomOf="@id/searchContainerList"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="4dp"
app:spanCount="2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:listitem="@layout/item_product_grid"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"/>
</LinearLayout>
<!-- <com.google.android.material.divider.MaterialDivider-->
<!-- android:id="@+id/divider_product"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginTop="2dp"-->
<!-- app:layout_constraintTop_toBottomOf="@id/searchContainer"/>-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvProductsList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="23dp"
app:layout_constraintTop_toBottomOf="@id/searchContainerList"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="4dp"
app:spanCount="2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:listitem="@layout/item_product_grid"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,97 +1,136 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:paddingHorizontal="32dp"
android:layout_margin="16dp" android:paddingVertical="16dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
tools:context=".ui.auth.LoginActivity"> tools:context=".ui.auth.LoginActivity">
<!-- Title -->
<TextView <TextView
android:layout_width="match_parent" android:id="@+id/tv_login_title"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/login" android:text="@string/login"
android:textAlignment="center"
android:textSize="24sp" android:textSize="24sp"
android:textStyle="bold" android:textStyle="bold"
android:textAlignment="center" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginBottom="24dp"/> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/tv_email_label"
android:layout_marginBottom="48dp"
android:paddingBottom="24dp"/>
<!-- Email label -->
<TextView <TextView
android:layout_width="match_parent" android:id="@+id/tv_email_label"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium" android:fontFamily="@font/dmsans_medium"
android:text="@string/login_email"
android:textSize="18sp" android:textSize="18sp"
android:text="@string/login_email"/> android:layout_marginVertical="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_login_title"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Email input -->
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:id="@+id/til_login_email"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_email_label"
app:layout_constraintEnd_toEndOf="parent">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_login_email" android:id="@+id/et_login_email"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/hint_login_email" android:hint="@string/hint_login_email"
android:inputType="textEmailAddress"/> android:inputType="textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<!-- Password label-->
<TextView <TextView
android:layout_width="match_parent" android:id="@+id/tv_password_label"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium" android:fontFamily="@font/dmsans_medium"
android:text="@string/password"
android:textSize="18sp" android:textSize="18sp"
android:text="@string/password"/> android:layout_marginVertical="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/til_login_email"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Password input -->
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:id="@+id/til_login_password"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="12dp" android:layout_marginBottom="12dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" app:passwordToggleEnabled="true"
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 <com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_login_password" android:id="@+id/et_login_password"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/hint_login_password" android:hint="@string/hint_login_password"
android:inputType="textPassword"/> android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<!-- “Forgot password” link -->
<TextView <TextView
android:id="@+id/tv_forgetPassword" android:id="@+id/tv_forgetPassword"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/forget_password" android:text="@string/forget_password"
android:textColor="@android:color/holo_red_light"
android:textAlignment="textEnd" android:textAlignment="textEnd"
android:layout_marginBottom="16dp"/> android:textColor="@android:color/holo_red_light"
android:layout_marginBottom="16dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/til_login_password" />
<!-- Login button -->
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btn_login" android:id="@+id/btn_login"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/login" android:text="@string/login"
app:cornerRadius="8dp"/> app:cornerRadius="8dp"
android:layout_marginVertical="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_forgetPassword" />
<!-- “Dont have an account?” row (kept as LinearLayout) -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:id="@+id/ll_signup_row"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center" android:gravity="center"
android:layout_marginTop="16dp"> android:orientation="horizontal"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_login">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/no_account"/> android:text="@string/no_account" />
<TextView <TextView
android:id="@+id/tv_registrasi" android:id="@+id/tv_registrasi"
@ -99,7 +138,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/signup" android:text="@string/signup"
android:textColor="@color/blue1" android:textColor="@color/blue1"
android:textStyle="bold"/> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -38,5 +38,6 @@
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/linear_shipment" app:layout_constraintTop_toBottomOf="@id/linear_shipment"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -33,4 +33,23 @@
tools:listitem="@layout/item_chat" tools:listitem="@layout/item_chat"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
<ProgressBar
android:id="@+id/progressBarChat"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
android:visibility="gone"/>
<TextView
android:id="@+id/tv_empty_chat"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="16dp"
android:gravity="center"
android:visibility="gone"
android:text="Keranjang Anda kosong"
android:textColor="@android:color/black"
android:textSize="18sp" />
</LinearLayout> </LinearLayout>

View File

@ -75,7 +75,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Didn't receive the code? " /> android:text="Belum menerima kode? " />
<TextView <TextView
android:id="@+id/tv_resend_otp" android:id="@+id/tv_resend_otp"

View File

@ -1,6 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ProgressBar
android:id="@+id/progressBar"
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"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>