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

1
app/.gitignore vendored
View File

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

View File

@ -15,10 +15,14 @@ class ApiConfig {
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
//httplogginginterceptor ntuk debug dan monitoring request/response
}
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()
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
@ -27,13 +31,17 @@ class ApiConfig {
.writeTimeout(300, TimeUnit.SECONDS) // 5 minutes
.build()
// Konfigurasi Retrofit
val retrofit = Retrofit.Builder()
//almat domain backend
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
//gson convertes: mengkonversi JSON ke object Kotlin dan sebaliknya
.client(client)
.build()
return retrofit.create(ApiService::class.java)
// retrofit : menyederhanakan HTTP Request dgn mengubah interface Kotlin di ApiService menjadi HTTP calls secara otomatis
}
fun getUnauthenticatedApiService(): ApiService {

View File

@ -8,9 +8,7 @@ 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.WindowCompat
import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.data.api.dto.FcmReq
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.Result
@ -43,20 +41,18 @@ class LoginActivity : AppCompatActivity() {
setContentView(binding.root)
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
}
// 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

View File

@ -43,24 +43,6 @@ class RegisterActivity : AppCompatActivity() {
setContentView(binding.root)
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", "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?) {
val fragment = when (step) {
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() {

View File

@ -252,15 +252,10 @@ class RegisterStep2Fragment : Fragment() {
sessionManager.saveToken(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
Log.d(TAG, "Proceeding to Step 3 after successful login")
// call navigate register step from activity
(activity as? RegisterActivity)?.navigateToStep(3, null )
}
is Result.Error -> {
@ -270,7 +265,7 @@ class RegisterStep2Fragment : Fragment() {
// Show error message but continue to Step 3 anyway
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
(activity as? RegisterActivity)?.navigateToStep(3, null)

View File

@ -1,6 +1,7 @@
package com.alya.ecommerce_serang.ui.cart
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
@ -153,7 +154,8 @@ class CartActivity : AppCompatActivity() {
}
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 ->

View File

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

View File

@ -1,6 +1,7 @@
package com.alya.ecommerce_serang.ui.chat
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -55,13 +56,16 @@ class ChatListFragment : Fragment() {
viewModel.chatList.observe(viewLifecycleOwner) { result ->
when (result) {
is Result.Success -> {
val adapter = ChatListAdapter(result.data) { chatItem ->
// Use the ChatActivity.createIntent factory method for proper navigation
val data = result.data
binding.tvEmptyChat.visibility = View.GONE
if (data.isNotEmpty()) {
val adapter = ChatListAdapter(data) { chatItem ->
ChatActivity.createIntent(
context = requireActivity(),
storeId = chatItem.storeId,
productId = 0, // Default value since we don't have it in ChatListItem
productName = null, // Null is acceptable as per ChatActivity
productId = 0,
productName = null,
productPrice = "",
productImage = null,
productRating = null,
@ -71,15 +75,25 @@ class ChatListFragment : Fragment() {
)
}
binding.chatListRecyclerView.adapter = adapter
} else {
binding.tvEmptyChat.visibility = View.VISIBLE
}
}
is Result.Error -> {
binding.tvEmptyChat.visibility = View.VISIBLE
Toast.makeText(requireContext(), "Failed to load chats", Toast.LENGTH_SHORT).show()
}
Result.Loading -> {
binding.progressBarChat.visibility = View.VISIBLE
// 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{
private var TAG = "ChatListFragment"
}
}

View File

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

View File

@ -208,25 +208,6 @@ class HomeFragment : Fragment() {
private fun initUi() {
// For LightStatusBar
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) {
@ -248,8 +229,4 @@ class HomeFragment : Fragment() {
categoryAdapter = 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.os.Bundle
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
@ -110,11 +111,6 @@ class CheckoutActivity : AppCompatActivity() {
finish()
}
}
// viewModel.getPaymentMethods { paymentMethods ->
// // Logging is just for debugging
// Log.d("CheckoutActivity", "Loaded ${paymentMethods.size} payment methods")
// }
}
private fun setupToolbar() {
@ -165,7 +161,7 @@ class CheckoutActivity : AppCompatActivity() {
// Observe loading state
viewModel.isLoading.observe(this) { isLoading ->
binding.btnPay.isEnabled = !isLoading
// Show/hide loading indicator if you have one
}
// Observe error messages
@ -273,10 +269,14 @@ class CheckoutActivity : AppCompatActivity() {
private fun updateShippingUI(shipName: String, shipService: String, shipEtd: String, shipPrice: Int) {
if (shipName.isNotEmpty() && shipService.isNotEmpty()) {
// Display shipping name and service in one line
binding.cardShipment.visibility = View.VISIBLE
binding.tvCourierName.text = "$shipName $shipService"
binding.tvDeliveryEstimate.text = "$shipEtd hari kerja"
binding.tvShippingPrice.text = formatCurrency(shipPrice.toDouble())
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.text.NumberFormat
import java.text.SimpleDateFormat
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
@ -197,12 +200,12 @@ class DetailOrderStatusActivity : AppCompatActivity() {
Log.d(TAG, "populateOrderDetails: Payment method=${orders.payInfoName ?: "Tidak tersedia"}")
// Set subtotal, shipping cost, and total
val subtotal = orders.totalAmount?.minus(orders.shipmentPrice.toIntOrNull() ?: 0) ?: 0
binding.tvSubtotal.text = formatCurrency(subtotal.toDouble())
// val subtotal = orders.totalAmount?.minus(orders.shipmentPrice.toDouble() ?: 0) ?: 0
// binding.tvSubtotal.text = formatCurrency(subtotal.toDouble())
binding.tvShippingCost.text = formatCurrency(orders.shipmentPrice.toDouble())
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
Log.d(TAG, "populateOrderDetails: Adjusting buttons for status=$orderStatus")
@ -223,6 +226,11 @@ class DetailOrderStatusActivity : AppCompatActivity() {
this.adapter = adapter
}
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) {
@ -287,7 +295,7 @@ class DetailOrderStatusActivity : AppCompatActivity() {
// Show status note
binding.tvStatusHeader.text = "Sudah Dibayar"
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.tvPaymentDeadline.text = formatDatePaid(orders.updatedAt)
@ -606,32 +614,17 @@ class DetailOrderStatusActivity : AppCompatActivity() {
}
private fun formatDate(dateString: String): String {
Log.d(TAG, "formatDate: Formatting date: $dateString")
return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
val jakarta = ZoneId.of("Asia/Jakarta")
val instant = Instant.parse(dateString) // parses ISO8601 with Z
val zoned = instant.atZone(jakarta)
val timeFormat = SimpleDateFormat("HH:mm", Locale("id", "ID"))
val dateFormat = SimpleDateFormat("dd MMMM yyyy", Locale("id", "ID"))
val time = DateTimeFormatter.ofPattern("HH:mm", Locale("id", "ID")).format(zoned)
val date = DateTimeFormatter.ofPattern("dd MMMM yyyy",Locale("id", "ID")).format(zoned)
val date = inputFormat.parse(dateString)
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
"$time\n$date"
} catch (e: Exception) {
Log.e(TAG, "formatDate: Error formatting date: ${e.message}", e)
Log.e(TAG, "formatDate: $e")
dateString
}
}

View File

@ -64,6 +64,9 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
private val _registeredUser = MutableLiveData<User>()
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
var selectedProvinceId: Int? = 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}")
_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) {
// Handle any errors and update state
_checkValue.value = Result.Error(exception)
_toastMessage.value = Event("Gagal memeriksa ${request.fieldRegis}")
// Log the error for debugging
Log.e("RegisterViewModel", "Error:", exception)
@ -375,4 +385,10 @@ class RegisterViewModel(private val repository: UserRepository, private val orde
companion object {
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_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
android:id="@+id/tvWholesaleWarning"
android:layout_width="match_parent"

View File

@ -6,7 +6,7 @@
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black_800"
android:background="@color/white"
android:theme="@style/Theme.Ecommerce_serang"
tools:context=".ui.order.CheckoutActivity">
@ -75,7 +75,7 @@
android:id="@+id/tv_places_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rumah"
android:text="-"
android:textColor="#5A5A5A"
android:paddingHorizontal="8dp"
android:paddingVertical="2dp"
@ -94,7 +94,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Jl. Pegangasan Timur"
android:text="-"
android:textSize="14sp"
android:layout_marginStart="32dp" />
@ -179,9 +179,11 @@
</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">

View File

@ -3,9 +3,16 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<include
android:id="@+id/searchContainerList"
layout="@layout/view_search_back"
@ -13,15 +20,14 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/rvProductsList"/>
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"/>-->
<!-- <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"
@ -37,4 +43,8 @@
tools:listitem="@layout/item_product_grid"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,97 +1,136 @@
<?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:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="16dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
android:paddingHorizontal="32dp"
android:paddingVertical="16dp"
tools:context=".ui.auth.LoginActivity">
<!-- Title -->
<TextView
android:layout_width="match_parent"
android:id="@+id/tv_login_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/login"
android:textAlignment="center"
android:textSize="24sp"
android:textStyle="bold"
android:textAlignment="center"
android:layout_marginBottom="24dp"/>
app:layout_constraintEnd_toEndOf="parent"
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
android:layout_width="match_parent"
android:id="@+id/tv_email_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium"
android:text="@string/login_email"
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
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_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
android:id="@+id/et_login_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_login_email"
android:inputType="textEmailAddress"/>
android:inputType="textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Password label-->
<TextView
android:layout_width="match_parent"
android:id="@+id/tv_password_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium"
android:text="@string/password"
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
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_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
android:id="@+id/et_login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_login_password"
android:inputType="textPassword"/>
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<!-- “Forgot password” link -->
<TextView
android:id="@+id/tv_forgetPassword"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/forget_password"
android:textColor="@android:color/holo_red_light"
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
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
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
android:layout_width="match_parent"
android:id="@+id/ll_signup_row"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
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
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_account"/>
android:text="@string/no_account" />
<TextView
android:id="@+id/tv_registrasi"
@ -99,7 +138,7 @@
android:layout_height="wrap_content"
android:text="@string/signup"
android:textColor="@color/blue1"
android:textStyle="bold"/>
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

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

View File

@ -33,4 +33,23 @@
tools:listitem="@layout/item_chat"
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>

View File

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

View File

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