merge screen-features: category and product

This commit is contained in:
shaulascr
2025-03-16 15:32:35 +07:00
59 changed files with 2497 additions and 235 deletions

View File

@ -23,7 +23,7 @@ android {
buildTypes {
release {
buildConfigField("String", "BASE_URL", "\"http://192.168.1.13:3000/\"")
buildConfigField("String", "BASE_URL", "\"http://192.168.1.4:3000/\"")
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
@ -31,7 +31,7 @@ android {
)
}
debug {
buildConfigField("String", "BASE_URL", "\"http://192.168.1.13:3000/\"")
buildConfigField("String", "BASE_URL", "\"http://192.168.1.4:3000/\"")
}
}
compileOptions {
@ -72,6 +72,8 @@ dependencies {
implementation("com.github.bumptech.glide:glide:4.16.0")
implementation("androidx.paging:paging-runtime:3.2.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("de.hdodenhof:circleimageview:3.1.0")
// implementation(libs.hilt.android)

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
@ -38,14 +39,27 @@
android:name=".ui.profile.mystore.TokoSayaActivity"
android:exported="false" />
<activity
android:name=".ui.MainActivity"
android:exported="true" >
android:name=".ui.product.DetailProductActivity"
android:exported="false" />
<activity
android:name=".ui.auth.RegisterActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.auth.LoginActivity"
android:exported="false" />
<activity
android:name=".ui.profile.DetailProfileActivity"
android:exported="false" />
<activity
android:name=".ui.MainActivity"
android:exported="true">
</activity>
</application>
</manifest>

View File

@ -1,11 +0,0 @@
package com.alya.ecommerce_serang.data.api.dto
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class Category(
val id: String,
val image: String,
val title: String
): Parcelable

View File

@ -0,0 +1,22 @@
package com.alya.ecommerce_serang.data.api.dto
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class CategoryItem(
@field:SerializedName("image")
val image: String,
@field:SerializedName("name")
val name: String,
@field:SerializedName("id")
val id: Int,
@field:SerializedName("store_type_id")
val storeTypeId: Int
) : Parcelable

View File

@ -14,7 +14,7 @@ data class DetailProduct(
val inStock: Int,
val price: Double,
val rating: Double,
val related: List<Product>,
val related: List<ProductsItem>,
val reviews: Int,
val title: String,
@SerializedName("free_delivery")

View File

@ -0,0 +1,8 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class LoginRequest (
@SerializedName("emailOrPhone") val email: String,
@SerializedName("password") val password: String,
)

View File

@ -0,0 +1,7 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class OtpRequest(
@SerializedName("email") val email: String
)

View File

@ -1,17 +0,0 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class Product (
val id: String,
val discount: Double?,
@SerializedName("favorite")
var wishlist: Boolean,
val image: String,
val price: Double,
val rating: Double,
@SerializedName("rating_count")
val ratingCount: Int,
val title: String,
)

View File

@ -0,0 +1,49 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class ProductsItem(
@field:SerializedName("store_id")
val storeId: Int,
@field:SerializedName("image")
val image: String,
@field:SerializedName("rating")
val rating: String,
@field:SerializedName("description")
val description: String,
@field:SerializedName("weight")
val weight: Int,
@field:SerializedName("is_pre_order")
val isPreOrder: Boolean,
@field:SerializedName("category_id")
val categoryId: Int,
@field:SerializedName("price")
val price: String,
@field:SerializedName("name")
val name: String,
@field:SerializedName("id")
val id: Int,
@field:SerializedName("min_order")
val minOrder: Int,
@field:SerializedName("total_sold")
val totalSold: Int,
@field:SerializedName("stock")
val stock: Int,
@field:SerializedName("status")
val status: String
)

View File

@ -0,0 +1,14 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class RegisterRequest (
val name: String?,
val email: String?,
val password: String?,
val username: String?,
val phone: String?,
@SerializedName("birth_date") val birthDate: String?,
@SerializedName("userimg") val image: String?,
val otp: String? = null
)

View File

@ -1,5 +1,6 @@
package com.alya.ecommerce_serang.data.api.response
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.google.gson.annotations.SerializedName
data class AllProductResponse(
@ -11,47 +12,4 @@ data class AllProductResponse(
val products: List<ProductsItem>
)
data class ProductsItem(
@field:SerializedName("store_id")
val storeId: Int,
@field:SerializedName("image")
val image: String,
@field:SerializedName("rating")
val rating: String,
@field:SerializedName("description")
val description: String,
@field:SerializedName("weight")
val weight: Int,
@field:SerializedName("is_pre_order")
val isPreOrder: Boolean,
@field:SerializedName("category_id")
val categoryId: Int,
@field:SerializedName("price")
val price: String,
@field:SerializedName("name")
val name: String,
@field:SerializedName("id")
val id: Int,
@field:SerializedName("min_order")
val minOrder: Int,
@field:SerializedName("total_sold")
val totalSold: Int,
@field:SerializedName("stock")
val stock: Int,
@field:SerializedName("status")
val status: String
)

View File

@ -0,0 +1,15 @@
package com.alya.ecommerce_serang.data.api.response
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
import com.google.gson.annotations.SerializedName
data class CategoryResponse(
@field:SerializedName("Category")
val category: List<CategoryItem>,
@field:SerializedName("message")
val message: String
)

View File

@ -0,0 +1,15 @@
package com.alya.ecommerce_serang.data.api.response
import com.google.gson.annotations.SerializedName
data class LoginResponse(
@field:SerializedName("role")
val role: String,
@field:SerializedName("message")
val message: String,
@field:SerializedName("accessToken")
val accessToken: String
)

View File

@ -0,0 +1,9 @@
package com.alya.ecommerce_serang.data.api.response
import com.google.gson.annotations.SerializedName
data class OtpResponse(
@field:SerializedName("message")
val message: String
)

View File

@ -0,0 +1,42 @@
package com.alya.ecommerce_serang.data.api.response
import com.google.gson.annotations.SerializedName
data class RegisterResponse(
@field:SerializedName("message")
val message: String,
@field:SerializedName("user")
val user: User
)
data class User(
@field:SerializedName("image")
val image: String,
@field:SerializedName("password")
val password: String,
@field:SerializedName("role")
val role: String,
@field:SerializedName("phone")
val phone: String,
@field:SerializedName("birth_date")
val birthDate: String,
@field:SerializedName("name")
val name: String,
@field:SerializedName("id")
val id: Int,
@field:SerializedName("email")
val email: String,
@field:SerializedName("username")
val username: String
)

View File

@ -0,0 +1,138 @@
package com.alya.ecommerce_serang.data.api.response
import com.google.gson.annotations.SerializedName
data class StoreResponse(
@field:SerializedName("shipping")
val shipping: List<ShippingItem>,
@field:SerializedName("payment")
val payment: List<PaymentItem>,
@field:SerializedName("store")
val store: Store,
@field:SerializedName("message")
val message: String
)
data class Store(
@field:SerializedName("approval_reason")
val approvalReason: String,
@field:SerializedName("store_status")
val storeStatus: String,
@field:SerializedName("sppirt")
val sppirt: String,
@field:SerializedName("user_name")
val userName: String,
@field:SerializedName("nib")
val nib: String,
@field:SerializedName("latitude")
val latitude: String,
@field:SerializedName("store_type_id")
val storeTypeId: Int,
@field:SerializedName("balance")
val balance: String,
@field:SerializedName("street")
val street: String,
@field:SerializedName("store_name")
val storeName: String,
@field:SerializedName("user_phone")
val userPhone: String,
@field:SerializedName("halal")
val halal: String,
@field:SerializedName("id")
val id: Int,
@field:SerializedName("email")
val email: String,
@field:SerializedName("store_image")
val storeImage: Any,
@field:SerializedName("longitude")
val longitude: String,
@field:SerializedName("store_id")
val storeId: Int,
@field:SerializedName("is_store_location")
val isStoreLocation: Boolean,
@field:SerializedName("ktp")
val ktp: String,
@field:SerializedName("approval_status")
val approvalStatus: String,
@field:SerializedName("npwp")
val npwp: String,
@field:SerializedName("store_type")
val storeType: String,
@field:SerializedName("is_on_leave")
val isOnLeave: Boolean,
@field:SerializedName("user_id")
val userId: Int,
@field:SerializedName("province_id")
val provinceId: Int,
@field:SerializedName("phone")
val phone: String,
@field:SerializedName("subdistrict")
val subdistrict: String,
@field:SerializedName("recipient")
val recipient: String,
@field:SerializedName("detail")
val detail: String,
@field:SerializedName("postal_code")
val postalCode: String,
@field:SerializedName("store_description")
val storeDescription: String,
@field:SerializedName("city_id")
val cityId: Int
)
data class ShippingItem(
@field:SerializedName("courier")
val courier: String
)
data class PaymentItem(
@field:SerializedName("qris_image")
val qrisImage: String,
@field:SerializedName("bank_num")
val bankNum: String,
@field:SerializedName("bank_name")
val bankName: String,
@field:SerializedName("id")
val id: Int
)

View File

@ -1,24 +1,52 @@
package com.alya.ecommerce_serang.data.api.retrofit
import com.alya.ecommerce_serang.BuildConfig
import com.alya.ecommerce_serang.utils.AuthInterceptor
import com.alya.ecommerce_serang.utils.SessionManager
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class ApiConfig {
companion object{
fun getApiService(): ApiService {
val loggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
companion object {
fun getApiService(tokenManager: SessionManager): ApiService {
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val authInterceptor = AuthInterceptor(tokenManager)
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
return retrofit.create(ApiService::class.java)
}
fun getUnauthenticatedApiService(): ApiService {
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
return retrofit.create(ApiService::class.java)
}
}

View File

@ -1,22 +1,50 @@
package com.alya.ecommerce_serang.data.api.retrofit
import com.alya.ecommerce_serang.data.api.dto.LoginRequest
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.response.AllProductResponse
import com.alya.ecommerce_serang.data.api.response.CategoryResponse
import com.alya.ecommerce_serang.data.api.response.LoginResponse
import com.alya.ecommerce_serang.data.api.response.OtpResponse
import com.alya.ecommerce_serang.data.api.response.ProductResponse
import com.alya.ecommerce_serang.data.api.response.RegisterResponse
import com.alya.ecommerce_serang.data.api.response.StoreResponse
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Path
interface ApiService {
@GET("product")
fun getAllProduct(
@Header("Authorization") token: String = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzEsIm5hbWUiOiJhbHlhIiwiZW1haWwiOiJha3VuYmVsYWphci5hbHlhQGdtYWlsLmNvbSIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNzM4NDg0OTc0LCJleHAiOjE3NDEwNzY5NzR9.0JyXJQ_6CKiZEi0gvk1gcn-0ILu3a9lOr3HqjhJXbBE"
): Call<AllProductResponse>
@POST("registeruser")
suspend fun register (
@Body registerRequest: RegisterRequest
): Response<RegisterResponse>
@POST("otp")
suspend fun getOTP(
@Body otpRequest: OtpRequest
):OtpResponse
@POST("login")
suspend fun login(
@Body loginRequest: LoginRequest
): Response<LoginResponse>
@GET("category")
suspend fun allCategory(
): Response<CategoryResponse>
@GET("product")
suspend fun getAllProduct(): Response<AllProductResponse>
@GET("product/detail/{id}")
fun getDetailProduct (
@Header("Authorization") token: String,
@Path("id") productId: Int
): Call<ProductResponse>
@GET("mystore")
fun getStore (): Call<StoreResponse>
}

View File

@ -1,7 +1,8 @@
package com.alya.ecommerce_serang.data.repository
import android.util.Log
import com.alya.ecommerce_serang.data.api.response.ProductsItem
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -11,25 +12,41 @@ class ProductRepository(private val apiService: ApiService) {
withContext(Dispatchers.IO) {
try {
Log.d("ProductRepository", "Attempting to fetch products")
val response = apiService.getAllProduct().execute()
Log.d("ProductRepository", "Response received. Success: ${response.isSuccessful}")
Log.d("ProductRepository", "Response code: ${response.code()}")
Log.d("ProductRepository", "Response message: ${response.message()}")
val response = apiService.getAllProduct()
if (response.isSuccessful) {
Result.success(response.body()?.products ?: emptyList())
// Return a Result.Success with the list of products
Result.Success(response.body()?.products ?: emptyList())
} else {
Result.failure(Exception("Failed to fetch products"))
// Return a Result.Error with a custom Exception
Result.Error(Exception("Failed to fetch products. Code: ${response.code()}"))
}
} catch (e: Exception) {
Result.failure(e)
// Return a Result.Error with the exception caught
Result.Error(e)
}
}
// suspend fun getCategories():List<Category>
//
// fun getProducts(query: ProductQuery) : Flow<PagingData<Product>>
// fun getRecentSearchs(): Flow<List<String>>
// suspend fun clearRecents()
// suspend fun addRecents(search:String)
// suspend fun getProduct(id:String):DetailProduct
suspend fun getAllCategories(): Result<List<CategoryItem>> =
withContext(Dispatchers.IO) {
try {
Log.d("Categories", "Attempting to fetch categories")
val response = apiService.allCategory()
if (response.isSuccessful) {
val categories = response.body()?.category ?: emptyList()
Log.d("Categories", "Fetched categories: $categories")
categories.forEach { Log.d("Category Image", "Category: ${it.name}, Image: ${it.image}") }
Result.Success(categories)
} else {
Result.Error(Exception("Failed to fetch categories. Code: ${response.code()}"))
}
} catch (e: Exception) {
Log.e("Categories", "Error fetching categories", e)
Result.Error(e)
}
}
}

View File

@ -0,0 +1,51 @@
package com.alya.ecommerce_serang.data.repository
import com.alya.ecommerce_serang.data.api.dto.LoginRequest
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.response.LoginResponse
import com.alya.ecommerce_serang.data.api.response.OtpResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
class UserRepository(private val apiService: ApiService) {
suspend fun requestOtpRep(email: String): OtpResponse {
// fun requestOtpRep(email: String): Result<String> {
return apiService.getOTP(OtpRequest(email))
}
suspend fun registerUser(request: RegisterRequest): String {
val response = apiService.register(request) // API call
if (response.isSuccessful) {
val responseBody = response.body() ?: throw Exception("Empty response body")
return responseBody.message // Get the message from RegisterResponse
} else {
throw Exception("Registration failed: ${response.errorBody()?.string()}")
}
}
suspend fun login(email: String, password: String): Result<LoginResponse> {
return try {
val response = apiService.login(LoginRequest(email, password))
if (response.isSuccessful) {
response.body()?.let {
Result.Success(it)
} ?: Result.Error(Exception("Login response is empty"))
} else {
Result.Error(Exception(response.errorBody()?.string() ?: "Unknown error"))
}
} catch (e: Exception) {
Result.Error(e)
}
}
}
sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
object Loading : Result<Nothing>()
}

View File

@ -6,11 +6,16 @@ import androidx.core.view.isVisible
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
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.databinding.ActivityMainBinding
import com.alya.ecommerce_serang.utils.SessionManager
//@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
private val navController by lazy {
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
}
@ -19,6 +24,9 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager) // Inject SessionManager
setupBottomNavigation()
observeDestinationChanges()

View File

@ -0,0 +1,75 @@
package com.alya.ecommerce_serang.ui.auth
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityLoginBinding
import com.alya.ecommerce_serang.ui.MainActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding
private val loginViewModel: LoginViewModel by viewModels{
BaseViewModelFactory {
val apiService = ApiConfig.getUnauthenticatedApiService()
val userRepository = UserRepository(apiService)
LoginViewModel(userRepository)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
setupListeners()
observeLoginState()
}
private fun setupListeners() {
binding.btnLogin.setOnClickListener {
val email = binding.etLoginEmail.text.toString()
val password = binding.etLoginPassword.text.toString()
if (email.isEmpty() || password.isEmpty()) {
Toast.makeText(this, "Please fill in all fields", Toast.LENGTH_SHORT).show()
} else {
loginViewModel.login(email, password)
}
}
}
private fun observeLoginState() {
loginViewModel.loginState.observe(this) { result ->
when (result) {
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
val accessToken = result.data.accessToken
val sessionManager = SessionManager(this)
sessionManager.saveToken(accessToken)
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 -> {
Toast.makeText(this, "Login Failed: ${result.exception.message}", Toast.LENGTH_LONG).show()
}
is Result.Loading -> {
// Show loading state
}
}
}
}
}

View File

@ -0,0 +1,23 @@
package com.alya.ecommerce_serang.ui.auth
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.response.LoginResponse
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository
import kotlinx.coroutines.launch
class LoginViewModel(private val repository: UserRepository) : ViewModel() {
private val _loginState = MutableLiveData<com.alya.ecommerce_serang.data.repository.Result<LoginResponse>>()
val loginState: LiveData<Result<LoginResponse>> get() = _loginState
fun login(email: String, password: String) {
viewModelScope.launch {
_loginState.value = com.alya.ecommerce_serang.data.repository.Result.Loading
val result = repository.login(email, password)
_loginState.value = result
}
}
}

View File

@ -0,0 +1,41 @@
package com.alya.ecommerce_serang.ui.auth
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
class OtpBottomSheetDialog(
private val userData: RegisterRequest, // Store user data
private val onRegister: (RegisterRequest) -> Unit)
: BottomSheetDialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.dialog_otp, container, false)
val etOtp = view.findViewById<EditText>(R.id.etOtp)
val btnSubmit = view.findViewById<Button>(R.id.btnSubmit)
btnSubmit.setOnClickListener {
val otp = etOtp.text.toString()
if (otp.isNotEmpty()) {
val updatedUserData = userData.copy(otp = otp) // Add OTP to userData
onRegister(updatedUserData) // Send full data to ViewModel
dismiss() // Close dialog
} else {
Toast.makeText(requireContext(), "Please enter OTP", Toast.LENGTH_SHORT).show()
}
}
return view
}
// override fun getTheme(): Int {
// return R.style.BottomSheetDialogTheme // Optional: Customize style
// }
}

View File

@ -0,0 +1,142 @@
package com.alya.ecommerce_serang.ui.auth
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
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.Result
import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.ActivityRegisterBinding
import com.alya.ecommerce_serang.ui.MainActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
class RegisterActivity : AppCompatActivity() {
private lateinit var binding: ActivityRegisterBinding
private lateinit var sessionManager: SessionManager
private val registerViewModel: RegisterViewModel by viewModels{
BaseViewModelFactory {
val apiService = ApiConfig.getUnauthenticatedApiService()
val userRepository = UserRepository(apiService)
RegisterViewModel(userRepository)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sessionManager = SessionManager(this)
if (!sessionManager.getToken().isNullOrEmpty()) {
// User already logged in, redirect to MainActivity
startActivity(Intent(this, MainActivity::class.java))
finish()
}
enableEdgeToEdge()
binding = ActivityRegisterBinding.inflate(layoutInflater)
setContentView(binding.root)
// Observe OTP state
observeOtpState()
binding.btnSignup.setOnClickListener {
// Retrieve values inside the click listener (so we get latest input)
val birthDate = binding.etBirthDate.text.toString()
val email = binding.etEmail.text.toString()
val password = binding.etPassword.text.toString()
val phone = binding.etNumberPhone.text.toString()
val username = binding.etUsername.text.toString()
val name = binding.etFullname.text.toString()
val image = "not yet"
val userData = RegisterRequest(name, email, password, username, phone, birthDate, image)
Log.d("RegisterActivity", "Requesting OTP for email: $email")
// Request OTP and wait for success before showing dialog
registerViewModel.requestOtp(userData.email.toString())
// Observe OTP state and show OTP dialog only when successful
registerViewModel.otpState.observe(this) { result ->
when (result) {
is Result.Success -> {
Log.d("RegisterActivity", "OTP sent successfully. Showing OTP dialog.")
// Show OTP dialog after OTP is successfully sent
val otpBottomSheet = OtpBottomSheetDialog(userData) { fullUserData ->
Log.d("RegisterActivity", "OTP entered successfully. Proceeding with registration.")
registerViewModel.registerUser(fullUserData) // Send complete data
}
otpBottomSheet.show(supportFragmentManager, "OtpBottomSheet")
}
is Result.Error -> {
// Show error message if OTP request fails
Log.e("RegisterActivity", "Failed to request OTP: ${result.exception.message}")
Toast.makeText(this, "Failed to request OTP: ${result.exception.message}", Toast.LENGTH_LONG).show()
}
is Result.Loading -> {
// Optional: Show loading indicator
}
}
}
// Observe Register state
observeRegisterState()
}
binding.tvLoginAlt.setOnClickListener{
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
}
}
private fun observeOtpState() {
registerViewModel.otpState.observe(this) { result ->
when (result) {
is Result.Loading -> {
// Show loading indicator
binding.progressBarOtp.visibility = android.view.View.VISIBLE
}
is Result.Success -> {
// Hide loading indicator and show success message
binding.progressBarOtp.visibility = android.view.View.GONE
// Toast.makeText(this@RegisterActivity, result.data, Toast.LENGTH_SHORT).show()
}
is Result.Error -> {
// Hide loading indicator and show error message
binding.progressBarOtp.visibility = android.view.View.GONE
Toast.makeText(this, "OTP Request Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun observeRegisterState() {
registerViewModel.registerState.observe(this) { result ->
when (result) {
is Result.Loading -> {
// Show loading indicator for registration
binding.progressBarRegister.visibility = android.view.View.VISIBLE
}
is Result.Success -> {
// Hide loading indicator and show success message
binding.progressBarRegister.visibility = android.view.View.GONE
Toast.makeText(this, result.data, Toast.LENGTH_SHORT).show()
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
// Navigate to another screen if needed
}
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
// Hide loading indicator and show error message
binding.progressBarRegister.visibility = android.view.View.GONE
Toast.makeText(this, "Registration Failed: ${result.exception.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
}

View File

@ -0,0 +1,93 @@
package com.alya.ecommerce_serang.ui.auth
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
import com.alya.ecommerce_serang.data.api.response.OtpResponse
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.data.repository.UserRepository
import kotlinx.coroutines.launch
class RegisterViewModel(private val repository: UserRepository) : ViewModel() {
// MutableLiveData for handling register state (Loading, Success, or Error)
private val _registerState = MutableLiveData<Result<String>>()
val registerState: LiveData<Result<String>> = _registerState
// MutableLiveData for handling OTP request state
private val _otpState = MutableLiveData<Result<Unit>>()
val otpState: LiveData<Result<Unit>> = _otpState
// MutableLiveData to store messages from API responses
private val _message = MutableLiveData<String>()
val message: LiveData<String> = _message
/**
* Function to request OTP by sending an email to the API.
* - It sets the OTP state to `Loading` before calling the repository.
* - If successful, it updates `_message` with the response message and signals success.
* - If an error occurs, it updates `_otpState` with `Result.Error` and logs the failure.
*/
fun requestOtp(email: String) {
viewModelScope.launch {
_otpState.value = Result.Loading // Indicating API call in progress
try {
// Call the repository function to request OTP
val response: OtpResponse = repository.requestOtpRep(email)
// Log and store success message
Log.d("RegisterViewModel", "OTP Response: ${response.message}")
_message.value = response.message // 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("RegisterViewModel", "OTP request failed for: $email", exception)
}
}
}
/**
* Function to register a new user.
* - It first sets `_registerState` to `Loading` to indicate the process is starting.
* - Calls the repository function to handle user registration.
* - If successful, it updates `_message` and signals success with the response message.
* - If an error occurs, it updates `_registerState` with `Result.Error` and logs the failure.
*/
fun registerUser(request: RegisterRequest) {
viewModelScope.launch {
_registerState.value = Result.Loading // Indicating API call in progress
try {
// Call repository function to register the user
val message = repository.registerUser(request)
// Store and display success message
_message.value = message
_registerState.value = Result.Success(message) // Store success result
} catch (exception: Exception) {
// Handle any errors and update state
_registerState.value = Result.Error(exception)
_message.value = exception.localizedMessage ?: "Registration failed"
// Log the error for debugging
Log.e("RegisterViewModel", "User registration failed", exception)
}
}
}
}

View File

@ -1,16 +1,19 @@
package com.alya.ecommerce_serang.ui.home
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.data.api.dto.Category
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
import com.alya.ecommerce_serang.databinding.ItemCategoryHomeBinding
import com.bumptech.glide.Glide
class HomeCategoryAdapter(
private val categories:List<Category>,
private var categories:List<CategoryItem>,
//A lambda function that will be invoked when a category item is clicked.
private val onClick:(category:Category) -> Unit
private val onClick:(category:CategoryItem) -> Unit
): RecyclerView.Adapter<HomeCategoryAdapter.ViewHolder>() {
/*
@ -18,12 +21,25 @@ class HomeCategoryAdapter(
the RecyclerView.It binds the Category data to the corresponding views within the item layout.
*/
inner class ViewHolder(private val binding: ItemCategoryHomeBinding): RecyclerView.ViewHolder(binding.root){
fun bind(category: Category) = with(binding){
Glide.with(root).load(category.image).into(image)
name.text = category.title
root.setOnClickListener{
onClick(category)
fun bind(category: CategoryItem) = with(binding) {
Log.d("CategoriesAdapter", "Binding category: ${category.name}, Image: ${category.image}")
val fullImageUrl = if (category.image.startsWith("/")) {
BASE_URL + category.image.removePrefix("/") // Append base URL if the path starts with "/"
} else {
category.image // Use as is if it's already a full URL
}
Log.d("CategoriesAdapter", "Loading image: $fullImageUrl")
Glide.with(itemView.context)
.load(fullImageUrl) // Ensure full URL
.placeholder(R.drawable.placeholder_image)
.into(imageCategory)
name.text = category.name
root.setOnClickListener { onClick(category) }
}
}
@ -36,4 +52,14 @@ class HomeCategoryAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(categories[position])
}
fun updateData(newCategories: List<CategoryItem>) {
categories = newCategories.toList()
notifyDataSetChanged()
}
fun updateLimitedCategory(newCategories: List<CategoryItem>){
val limitedCategories = newCategories.take(10)
updateData(limitedCategories)
}
}

View File

@ -1,21 +1,26 @@
package com.alya.ecommerce_serang.ui.home
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.response.ProductsItem
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
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
import com.alya.ecommerce_serang.databinding.FragmentHomeBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.HorizontalMarginItemDecoration
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.setLightStatusBar
import kotlinx.coroutines.launch
@ -24,30 +29,33 @@ class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private lateinit var viewModel: HomeViewModel
private var productAdapter: HorizontalProductAdapter? = null
private var categoryAdapter: HomeCategoryAdapter? = null
private lateinit var sessionManager: SessionManager
private val viewModel: HomeViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val productRepository = ProductRepository(apiService)
HomeViewModel(productRepository)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sessionManager = SessionManager(requireContext())
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
return _binding!!.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val repository = ProductRepository(ApiConfig.getApiService())
viewModel = ViewModelProvider(
this,
// Pass a lambda that creates the ViewModel
BaseViewModelFactory {
HomeViewModel(repository)
}
)[HomeViewModel::class.java]
initUi()
setupRecyclerView()
observeData()
@ -59,6 +67,11 @@ class HomeFragment : Fragment() {
onClick = { product -> handleProductClick(product) }
)
categoryAdapter = HomeCategoryAdapter(
categories = emptyList(),
onClick = { category -> handleCategoryProduct(category)}
)
binding.newProducts.apply {
adapter = productAdapter
layoutManager = LinearLayoutManager(
@ -67,37 +80,59 @@ class HomeFragment : Fragment() {
false
)
}
binding.categories.apply {
adapter = categoryAdapter
layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.HORIZONTAL,
false
)
}
}
private fun observeData() {
viewLifecycleOwner.lifecycleScope.launch {
viewModel.uiState.collect { state ->
when (state) {
is HomeUiState.Loading -> {
binding.loading.root.isVisible = true
binding.error.root.isVisible = false
binding.home.isVisible = false
}
is HomeUiState.Success -> {
binding.loading.root.isVisible = false
binding.error.root.isVisible = false
binding.home.isVisible = true
productAdapter?.updateProducts(state.products)
}
is HomeUiState.Error -> {
binding.loading.root.isVisible = false
binding.error.root.isVisible = true
binding.home.isVisible = false
binding.error.errorMessage.text = state.message
binding.error.retryButton.setOnClickListener {
viewModel.retry()
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is HomeUiState.Loading -> {
binding.loading.root.isVisible = true
binding.error.root.isVisible = false
binding.home.isVisible = false
}
is HomeUiState.Success -> {
binding.loading.root.isVisible = false
binding.error.root.isVisible = false
binding.home.isVisible = true
productAdapter?.updateLimitedProducts(state.products)
}
is HomeUiState.Error -> {
binding.loading.root.isVisible = false
binding.error.root.isVisible = true
binding.home.isVisible = false
binding.error.errorMessage.text = state.message
binding.error.retryButton.setOnClickListener {
viewModel.retry()
}
}
}
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.categories.collect { categories ->
Log.d("Categories", "Updated Categories: $categories")
categories.forEach { Log.d("Category Image", "Category: ${it.name}, Image: ${it.image}") }
categoryAdapter?.updateLimitedCategory(categories)
}
}
}
}
private fun initUi() {
// For LightStatusBar
setLightStatusBar()
@ -124,22 +159,21 @@ class HomeFragment : Fragment() {
private fun handleProductClick(product: ProductsItem) {
// Navigate to product detail
// findNavController().navigate(
// HomeFragmentDirections.actionHomeToDetail(product.id)
// )
}
private fun handleCategoryProduct(category: CategoryItem) {
}
override fun onDestroyView() {
super.onDestroyView()
productAdapter = null
categoryAdapter = null
_binding = null
}
private fun showLoading(isLoading: Boolean) {
if (isLoading) {
binding.progressBar.visibility = View.VISIBLE
} else {
binding.progressBar.visibility = View.GONE
}
binding.progressBar.isVisible = isLoading
}
}

View File

@ -1,13 +1,12 @@
package com.alya.ecommerce_serang.ui.home
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.response.AllProductResponse
import com.alya.ecommerce_serang.data.api.response.ProductsItem
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.data.repository.ProductRepository
import com.alya.ecommerce_serang.data.repository.Result
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@ -18,37 +17,56 @@ class HomeViewModel (
): ViewModel() {
private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
val home = MutableLiveData<AllProductResponse?>(null)
constructor() : this(ProductRepository(ApiConfig.getApiService()))
private val _categories = MutableStateFlow<List<CategoryItem>>(emptyList())
val categories: StateFlow<List<CategoryItem>> = _categories.asStateFlow()
init {
loadProducts()
loadCategories()
}
private fun loadProducts() {
viewModelScope.launch {
_uiState.value = HomeUiState.Loading
productRepository.getAllProducts()
.onSuccess { products ->
_uiState.value = HomeUiState.Success(products)
}
.onFailure { error ->
_uiState.value = HomeUiState.Error(error.message ?: "Unknown error")
Log.e("ProductViewModel", "Products fetch failed", error)
}
when (val result = productRepository.getAllProducts()) {
is Result.Success -> _uiState.value = HomeUiState.Success(result.data)
is Result.Error -> _uiState.value = HomeUiState.Error(result.exception.message ?: "Unknown error")
is Result.Loading -> {}
}
}
}
fun retry() {
loadProducts()
private fun loadCategories() {
viewModelScope.launch {
when (val result = productRepository.getAllCategories()) {
is Result.Success -> _categories.value = result.data
is Result.Error -> Log.e("HomeViewModel", "Failed to fetch categories", result.exception)
is Result.Loading -> {}
}
}
}
// fun toggleWishlist(product: Product) = viewModelScope.launch {
// try {
// productRepository.toggleWishlist(product.id,product.wishlist)
// }catch (e:Exception){
//
fun retry() {
loadProducts()
loadCategories()
}
// private fun fetchUserData() {
// viewModelScope.launch {
// try {
// val response = apiService.getProtectedData() // Example API request
// if (response.isSuccessful) {
// val data = response.body()
// Log.d("HomeFragment", "User Data: $data")
// // Update UI with data
// } else {
// Log.e("HomeFragment", "Error: ${response.message()}")
// }
// } catch (e: Exception) {
// Log.e("HomeFragment", "Exception: ${e.message}")
// }
// }
// }
}
@ -57,4 +75,4 @@ sealed class HomeUiState {
object Loading : HomeUiState()
data class Success(val products: List<ProductsItem>) : HomeUiState()
data class Error(val message: String) : HomeUiState()
}
}

View File

@ -1,9 +1,13 @@
package com.alya.ecommerce_serang.ui.home
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.data.api.response.ProductsItem
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.alya.ecommerce_serang.databinding.ItemProductHorizontalBinding
import com.bumptech.glide.Glide
@ -16,17 +20,24 @@ class HorizontalProductAdapter(
RecyclerView.ViewHolder(binding.root) {
fun bind(product: ProductsItem) = with(binding) {
val fullImageUrl = if (product.image.startsWith("/")) {
BASE_URL + product.image.removePrefix("/") // Append base URL if the path starts with "/"
} else {
product.image // Use as is if it's already a full URL
}
Log.d("ProductAdapter", "Loading image: $fullImageUrl")
itemName.text = product.name
itemPrice.text = product.price
rating.text = product.rating
// productSold.text = "${product.totalSold} sold"
// Load image using Glide
Glide.with(itemView)
// .load("${BuildConfig.BASE_URL}/product/${product.image}")
// .load("${BuildConfig.BASE_URL}/${product.image}")
.load(product.image)
.into(image)
.load(fullImageUrl)
.placeholder(R.drawable.placeholder_image)
.into(imageProduct)
root.setOnClickListener { onClick(product) }
}
@ -46,7 +57,32 @@ class HorizontalProductAdapter(
}
fun updateProducts(newProducts: List<ProductsItem>) {
val diffCallback = ProductDiffCallback(products, newProducts)
val diffResult = DiffUtil.calculateDiff(diffCallback)
products = newProducts
notifyDataSetChanged()
diffResult.dispatchUpdatesTo(this)
}
fun updateLimitedProducts(newProducts: List<ProductsItem>) {
val diffCallback = ProductDiffCallback(products, newProducts)
val limitedProducts = newProducts.take(10) // Limit to 10 items
val diffResult = DiffUtil.calculateDiff(diffCallback)
diffResult.dispatchUpdatesTo(this)
updateProducts(limitedProducts)
}
class ProductDiffCallback(
private val oldList: List<ProductsItem>,
private val newList: List<ProductsItem>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition].id == newList[newItemPosition].id // Compare unique IDs
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition] == newList[newItemPosition] // Compare entire object
}
}

View File

@ -0,0 +1,21 @@
package com.alya.ecommerce_serang.ui.product
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R
class DetailProductActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_detail_product)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
}
}

View File

@ -1,35 +0,0 @@
package com.alya.ecommerce_serang.ui.product
import androidx.recyclerview.widget.RecyclerView
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.Product
import com.alya.ecommerce_serang.databinding.ItemProductHorizontalBinding
import com.bumptech.glide.Glide
class ProductViewHolder(private val binding: ItemProductHorizontalBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(
product: Product,
onClick: (product: Product) -> Unit,
) = with(binding) {
Glide.with(root).load(product.image).into(image)
// discount.isVisible = product.discount != null
// product.discount?.let {
// val discount = (product.discount / product.price * 100).roundToInt()
// binding.discount.text =
// root.context.getString(R.string.fragment_item_product_discount, discount)
// }
itemName.text = product.title
rating.text = String.format("%.1f", product.rating)
// val current = product.price - (product.discount ?: 0.0)
val current = product.price
itemPrice.text = root.context.getString(R.string.item_price_txt, current)
root.setOnClickListener {
onClick(product)
}
}
}

View File

@ -0,0 +1,21 @@
package com.alya.ecommerce_serang.ui.profile
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.alya.ecommerce_serang.R
class DetailProfileActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_detail_profile)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
}
}

View File

@ -5,16 +5,13 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.databinding.FragmentProfileBinding
class ProfileFragment : Fragment() {
companion object {
fun newInstance() = ProfileFragment()
}
private val viewModel: ProfileViewModel by viewModels()
private var _binding: FragmentProfileBinding? = null
private val binding get() = _binding!!
private lateinit var viewModel: ProfileViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -26,6 +23,7 @@ class ProfileFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_profile, container, false)
_binding = FragmentProfileBinding.inflate(inflater, container, false)
return binding.root
}
}

View File

@ -0,0 +1,22 @@
package com.alya.ecommerce_serang.utils
import android.util.Log
import okhttp3.Interceptor
import okhttp3.Response
class AuthInterceptor(private val sessionManager: SessionManager) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = sessionManager.getToken()
Log.d("AuthInterceptor", "Token: $token")
val request = if (!token.isNullOrEmpty()) {
chain.request().newBuilder()
.addHeader("Authorization", "Bearer $token")
.build()
} else {
chain.request()
}
return chain.proceed(request)
}
}

View File

@ -1,12 +1,12 @@
package com.alya.ecommerce_serang.utils
import android.os.Parcelable
import com.alya.ecommerce_serang.data.api.dto.Category
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
import kotlinx.parcelize.Parcelize
@Parcelize
data class ProductQuery (
val category: Category? = null,
val category: CategoryItem,
val search:String? = null,
val range:Pair<Float,Float> = 0f to 10000f,
val rating:Int? = null,

View File

@ -0,0 +1,32 @@
package com.alya.ecommerce_serang.utils
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
class SessionManager(context: Context) {
private var sharedPreferences: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
companion object {
private const val PREFS_NAME = "app_prefs"
private const val USER_TOKEN = "user_token"
}
fun saveToken(token: String) {
val editor = sharedPreferences.edit()
editor.putString(USER_TOKEN, token)
editor.apply()
}
fun getToken(): String? {
val token = sharedPreferences.getString(USER_TOKEN, null)
Log.d("SessionManager", "Retrieved token: $token")
return token
}
fun clearToken() {
val editor = sharedPreferences.edit()
editor.remove(USER_TOKEN)
editor.apply()
}
}

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="20dp" android:viewportHeight="15" android:viewportWidth="18" android:width="24dp">
<path android:fillColor="@color/black" android:pathData="M9,0.5L0.667,8H3.167V14.667H14.833V8H17.333L9,0.5ZM9,5.292C9.497,5.292 9.974,5.489 10.326,5.841C10.677,6.192 10.875,6.669 10.875,7.167C10.875,7.664 10.677,8.141 10.326,8.492C9.974,8.844 9.497,9.042 9,9.042C8.503,9.042 8.026,8.844 7.674,8.492C7.323,8.141 7.125,7.664 7.125,7.167C7.125,6.669 7.323,6.192 7.674,5.841C8.026,5.489 8.503,5.292 9,5.292ZM9,10.5C10.25,10.5 12.75,11.125 12.75,12.375V13H5.25V12.375C5.25,11.125 7.75,10.5 9,10.5Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/black" android:pathData="M8.59,16.59L13.17,12 8.59,7.41 10,6l6,6 -6,6 -1.41,-1.41z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="17dp"
android:viewportWidth="24"
android:viewportHeight="17">
<path
android:pathData="M0,0.5V2H14.25V13.25H9.633C9.299,11.961 8.139,11 6.75,11C5.361,11 4.201,11.961 3.867,13.25H3V9.5H1.5V14.75H3.867C4.201,16.039 5.361,17 6.75,17C8.139,17 9.299,16.039 9.633,14.75H15.867C16.201,16.039 17.361,17 18.75,17C20.139,17 21.299,16.039 21.633,14.75H24V8.633L23.953,8.515L22.453,4.015L22.29,3.5H15.75V0.5H0ZM0.75,3.5V5H7.5V3.5H0.75ZM15.75,5H21.211L22.5,8.844V13.25H21.633C21.299,11.961 20.139,11 18.75,11C17.361,11 16.201,11.961 15.867,13.25H15.75V5ZM1.5,6.5V8H6V6.5H1.5ZM6.75,12.5C7.588,12.5 8.25,13.162 8.25,14C8.25,14.838 7.588,15.5 6.75,15.5C5.912,15.5 5.25,14.838 5.25,14C5.25,13.162 5.912,12.5 6.75,12.5ZM18.75,12.5C19.588,12.5 20.25,13.162 20.25,14C20.25,14.838 19.588,15.5 18.75,15.5C17.912,15.5 17.25,14.838 17.25,14C17.25,13.162 17.912,12.5 18.75,12.5Z"
android:fillColor="@color/black"/>
</vector>

View File

@ -0,0 +1,9 @@
<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="M19,7c0,-1.1 -0.9,-2 -2,-2h-3v2h3v2.65L13.52,14H10V9H6c-2.21,0 -4,1.79 -4,4v3h2c0,1.66 1.34,3 3,3s3,-1.34 3,-3h4.48L19,10.35V7zM4,14v-1c0,-1.1 0.9,-2 2,-2h2v3H4zM7,17c-0.55,0 -1,-0.45 -1,-1h2C8,16.55 7.55,17 7,17z"/>
<path android:fillColor="@android:color/white" android:pathData="M5,6h5v2h-5z"/>
<path android:fillColor="@android:color/white" android:pathData="M19,13c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3S20.66,13 19,13zM19,17c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1s1,0.45 1,1C20,16.55 19.55,17 19,17z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="20" android:viewportWidth="18" android:width="21.6dp">
<path android:fillColor="@color/black" android:pathData="M18,14.5C18,14.88 17.79,15.21 17.47,15.38L9.57,19.82C9.41,19.94 9.21,20 9,20C8.79,20 8.59,19.94 8.43,19.82L0.53,15.38C0.37,15.296 0.235,15.169 0.142,15.014C0.048,14.859 -0.001,14.681 0,14.5V5.5C0,5.12 0.21,4.79 0.53,4.62L8.43,0.18C8.59,0.06 8.79,0 9,0C9.21,0 9.41,0.06 9.57,0.18L17.47,4.62C17.79,4.79 18,5.12 18,5.5V14.5ZM9,2.15L7.11,3.22L13,6.61L14.96,5.5L9,2.15ZM3.04,5.5L9,8.85L10.96,7.75L5.08,4.35L3.04,5.5ZM2,13.91L8,17.29V10.58L2,7.21V13.91ZM16,13.91V7.21L10,10.58V17.29L16,13.91Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<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="M18,4H6C3.79,4 2,5.79 2,8v8c0,2.21 1.79,4 4,4h12c2.21,0 4,-1.79 4,-4V8C22,5.79 20.21,4 18,4zM16.14,13.77c-0.24,0.2 -0.57,0.28 -0.88,0.2L4.15,11.25C4.45,10.52 5.16,10 6,10h12c0.67,0 1.26,0.34 1.63,0.84L16.14,13.77zM6,6h12c1.1,0 2,0.9 2,2v0.55C19.41,8.21 18.73,8 18,8H6C5.27,8 4.59,8.21 4,8.55V8C4,6.9 4.9,6 6,6z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/black" android:pathData="M18.36,9l0.6,3L5.04,12l0.6,-3h12.72M20,4L4,4v2h16L20,4zM20,7L4,7l-1,5v2h1v6h10v-6h4v6h2v-6h1v-2l-1,-5zM6,18v-4h6v4L6,18z"/>
</vector>

View File

@ -0,0 +1,208 @@
<?xml version="1.0" encoding="utf-8"?>
<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:background="@color/white"
tools:context=".ui.product.DetailProductActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/white"
app:titleTextColor="@android:color/black"
app:navigationIcon="@drawable/ic_back_24"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:title="Detail Produk" />
<!-- Main Content with Scroll -->
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="60dp"
android:fillViewport="true"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:layout_constraintBottom_toTopOf="@id/bottom_buttons">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Product Image -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<ImageView
android:id="@+id/imgProduct"
android:layout_width="match_parent"
android:layout_height="220dp"
android:scaleType="centerCrop"
android:src="@drawable/placeholder_image" />
</androidx.cardview.widget.CardView>
<!-- Product Info -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
android:padding="12dp">
<!-- Sold & Rating -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="4dp">
<TextView
android:id="@+id/tvProductPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rp65.000"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/black" />
<TextView
android:id="@+id/tvProductName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Keripik Ikan Tenggiri"
android:textSize="16sp"
android:textColor="@color/black" />
<TextView
android:id="@+id/tvSold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Terjual 10 buah"
android:textColor="@color/gray_1" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="8dp"
android:src="@drawable/baseline_star_24" />
<TextView
android:id="@+id/tvRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="4.5"
android:textColor="@color/black" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Buyer Reviews -->
<TextView
android:text="Ulasan Pembeli"
android:textSize="16sp"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvReviews"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false" />
<!-- Product Details -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
android:padding="12dp">
<TextView
android:text="Detail Produk"
android:textSize="16sp"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:text="Berat: 200 gram"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:text="Stok: 100 buah"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:text="Kategori: Makanan Ringan"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</androidx.cardview.widget.CardView>
<!-- Related Products -->
<TextView
android:text="Produk lainnya"
android:textSize="16sp"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvRelatedProducts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:nestedScrollingEnabled="false" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<!-- Fixed Bottom Buttons -->
<FrameLayout
android:id="@+id/bottom_buttons"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:background="@color/white"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="12dp">
<Button
android:id="@+id/btnAddToCart"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Keranjang"
android:backgroundTint="@color/soft_gray"
android:textColor="@color/black" />
<Button
android:id="@+id/btnBuyNow"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Beli Sekarang"
android:backgroundTint="@color/blue_500"
android:textColor="@color/white" />
</LinearLayout>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="utf-8"?>
<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="wrap_content"
android:background="@color/white"
tools:context=".ui.profile.DetailProfileActivity">
<LinearLayout
android:id="@+id/top_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageButton
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_back_24"
android:background="?attr/selectableItemBackgroundBorderless"/>
<TextView
android:id="@+id/tv_profile_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Profil"
android:textSize="20sp"
android:fontFamily="@font/dmsans_bold"
android:textStyle="bold"
android:gravity="center"/>
</LinearLayout>
<androidx.cardview.widget.CardView
android:id="@+id/card_profile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
app:layout_constraintTop_toBottomOf="@id/top_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/profile_image"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/baseline_account_circle_24"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<Button
android:id="@+id/btn_ubah_profil"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ubah Profil"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/profile_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_nama"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginHorizontal="16dp"
app:layout_constraintTop_toBottomOf="@id/card_profile">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nama"
android:text="@string/users_name"
android:enabled="false"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="16dp"
app:layout_constraintTop_toBottomOf="@id/til_nama">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Username"
android:text="@string/username_profile"
android:enabled="false"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/til_username">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email"
android:text="@string/users_email"
android:enabled="false"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_nomor_handphone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="16dp"
app:layout_constraintTop_toBottomOf="@id/til_email">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nomor Handphone"
android:text="@string/phone_number"
android:enabled="false"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/til_tanggal_lahir"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="16dp"
app:layout_constraintTop_toBottomOf="@id/til_nomor_handphone">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Tanggal Lahir"
android:text="@string/date_birth"
android:enabled="false"/>
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,104 @@
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="16dp"
tools:context=".ui.auth.LoginActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/login"
android:textSize="24sp"
android:textStyle="bold"
android:textAlignment="center"
android:layout_marginBottom="24dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium"
android:textSize="18sp"
android:text="@string/login_email"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<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"/>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium"
android:textSize="18sp"
android:text="@string/password"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:passwordToggleEnabled="true">
<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"/>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/tv_forgetPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/forget_password"
android:textColor="@android:color/holo_red_light"
android:textAlignment="textEnd"
android:layout_marginBottom="16dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/login"
app:cornerRadius="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginTop="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_account"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/signup"
android:textColor="@color/blue1"
android:textStyle="bold"/>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,250 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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:background="@color/white"
tools:context=".ui.auth.RegisterActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginVertical="16dp"
android:layout_marginHorizontal="16dp"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Buat Akun"
android:textSize="24sp"
android:textStyle="bold"
android:textAlignment="center"
android:layout_marginBottom="24dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium"
android:textSize="18sp"
android:text="@string/email"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_email"
android:inputType="textEmailAddress"/>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium"
android:textSize="18sp"
android:text="@string/username"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_username"
android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium"
android:textSize="18sp"
android:text="@string/full_name"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_fullname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_fullname"
android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium"
android:textSize="18sp"
android:text="@string/password"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_password"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium"
android:textSize="18sp"
android:text="@string/confirm_password"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_confirm_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_confirmation_password"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium"
android:textSize="18sp"
android:text="@string/birth_date"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<AutoCompleteTextView
android:id="@+id/et_birth_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_birth_date"
android:inputType="date"/>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium"
android:textSize="18sp"
android:text="@string/gender"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<AutoCompleteTextView
android:id="@+id/et_gender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_gender"
android:inputType="textFilter"/>
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:fontFamily="@font/dmsans_medium"
android:textSize="18sp"
android:text="@string/number_phone"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_number_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_number_phone"
android:inputType="phone"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_signup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/signup"
android:layout_marginTop="16dp"
app:cornerRadius="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginTop="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_account"/>
<TextView
android:id="@+id/tv_login_alt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/login"
android:textColor="@color/blue1"
android:textStyle="bold"/>
</LinearLayout>
<ProgressBar
android:id="@+id/progressBarOtp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="center"/>
<!-- ProgressBar for Registration -->
<ProgressBar
android:id="@+id/progressBarRegister"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="center"/>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="@android:color/white">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enter_otp"
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center"
android:layout_gravity="center"/>
<EditText
android:id="@+id/etOtp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter OTP"
android:inputType="number"
android:maxLength="6"
android:gravity="center"
android:layout_marginTop="8dp"/>
<Button
android:id="@+id/btnSubmit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Submit"
android:layout_marginTop="12dp"/>
</LinearLayout>

View File

@ -92,7 +92,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="24dp"
android:text="@string/new_products_text"
android:text="@string/sold_product_text"
android:textColor="@color/black"
android:textSize="22sp"
app:layout_constraintStart_toStartOf="parent"

View File

@ -1,13 +1,327 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.profile.ProfileFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello" />
<!-- Profile Header -->
<ImageView
android:id="@+id/profileImage"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:src="@drawable/outline_account_circle_24"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>
<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Gracia Hotmauli"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@id/profileImage"
app:layout_constraintTop_toTopOf="@id/profileImage" />
<TextView
android:id="@+id/tvUsername"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="\@gracia34"
android:textColor="#757575"
app:layout_constraintStart_toStartOf="@id/tvName"
app:layout_constraintTop_toBottomOf="@id/tvName" />
<Button
android:id="@+id/btnDetailProfile"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="Detail Profil"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/profileImage" />
<!-- Store Button -->
<androidx.cardview.widget.CardView
android:id="@+id/cardBukaToko"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:layout_marginTop="16dp"
app:cardElevation="4dp"
app:layout_constraintTop_toBottomOf="@id/profileImage">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_marginHorizontal="14dp"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tvBukaToko"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawableStart="@drawable/outline_store_24"
android:drawablePadding="16dp"
android:padding="16dp"
android:fontFamily="@font/dmsans_semibold"
android:textSize="14sp"
android:text="@string/open_store"
app:layout_constraintEnd_toStartOf="@id/ivStoreArrow"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/ivStoreArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_arrow_right"
app:layout_constraintBottom_toBottomOf="@id/tvBukaToko"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvBukaToko" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/cardPesanan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/cardBukaToko">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/tvPesananSaya"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:paddingVertical="8dp"
android:fontFamily="@font/dmsans_medium"
android:text="Pesanan Saya"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/tvLihatRiwayat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_marginEnd="16dp"
android:textSize="12sp"
android:padding="0dp"
android:fontFamily="@font/dmsans_light"
android:text="Lihat Riwayat Pesanan"
android:textColor="#2196F3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvPesananSaya" />
<LinearLayout
android:id="@+id/layoutOrderStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal"
android:weightSum="3"
app:layout_constraintTop_toBottomOf="@id/tvPesananSaya">
<!-- Status items (keeping LinearLayout for simplicity of equal width distribution) -->
<LinearLayout
android:id="@+id/ly_waiting"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_waiting"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_wallet" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/waiting_payment" />
</LinearLayout>
<LinearLayout
android:id="@+id/ly_packages"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_packages"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_package" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/packages" />
</LinearLayout>
<LinearLayout
android:id="@+id/ly_delivery"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_delivery"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_delivery" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/delivery" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<!-- Orders Section Header -->
<!-- Account Settings Header -->
<TextView
android:id="@+id/tvPengaturanAkun"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:padding="16dp"
android:text="Pengaturan Akun"
android:fontFamily="@font/dmsans_medium"
app:layout_constraintTop_toBottomOf="@id/cardPesanan" />
<!-- Address -->
<ImageView
android:id="@+id/ivAddress"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:src="@drawable/ic_address"
app:layout_constraintBottom_toBottomOf="@id/tvAddress"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tvAddress" />
<TextView
android:id="@+id/tvAddress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:padding="16dp"
android:text="Alamat"
app:layout_constraintEnd_toStartOf="@id/ivAddressArrow"
app:layout_constraintStart_toEndOf="@id/ivAddress"
app:layout_constraintTop_toBottomOf="@id/tvPengaturanAkun" />
<ImageView
android:id="@+id/ivAddressArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_arrow_right"
app:layout_constraintBottom_toBottomOf="@id/tvAddress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvAddress" />
<!-- About -->
<ImageView
android:id="@+id/ivAbout"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@id/tvAbout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tvAbout" />
<TextView
android:id="@+id/tvAbout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:padding="16dp"
android:text="Tentang"
app:layout_constraintEnd_toStartOf="@id/ivAboutArrow"
app:layout_constraintStart_toEndOf="@id/ivAbout"
app:layout_constraintTop_toBottomOf="@id/tvAddress" />
<ImageView
android:id="@+id/ivAboutArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_arrow_right"
app:layout_constraintBottom_toBottomOf="@id/tvAbout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvAbout" />
<!-- Logout -->
<ImageView
android:id="@+id/ivLogout"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@id/tvLogout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tvLogout" />
<TextView
android:id="@+id/tvLogout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:padding="16dp"
android:text="Keluar"
app:layout_constraintEnd_toStartOf="@id/ivLogoutArrow"
app:layout_constraintStart_toEndOf="@id/ivLogout"
app:layout_constraintTop_toBottomOf="@id/tvAbout" />
<ImageView
android:id="@+id/ivLogoutArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_arrow_right"
app:layout_constraintBottom_toBottomOf="@id/tvLogout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvLogout" />
<!-- Bottom Navigation -->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -17,7 +17,7 @@
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/image"
android:id="@+id/image_category"
android:src="@drawable/makanan_ringan"
android:scaleType="centerCrop" />
</com.google.android.material.card.MaterialCardView>

View File

@ -17,7 +17,7 @@
app:strokeWidth="1dp">
<ImageView
android:id="@+id/image"
android:id="@+id/image_product"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp"
android:gravity="center_horizontal">
<!-- Product Image -->
<ImageView
android:id="@+id/imgRelatedProduct"
android:layout_width="140dp"
android:layout_height="120dp"
android:scaleType="centerCrop"
android:src="@drawable/placeholder_image" />
<!-- Product Name -->
<TextView
android:id="@+id/tvRelatedProductName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Keripik Kulit Sapi"
android:textSize="14sp"
android:textStyle="bold"
android:gravity="fill"
android:layout_marginTop="8dp"
android:textColor="@color/black" />
<!-- Price -->
<TextView
android:id="@+id/tvRelatedProductPrice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Rp45.000"
android:textSize="14sp"
android:textStyle="bold"
android:gravity="fill"
android:layout_marginTop="4dp"
android:textColor="@color/black" />
<!-- Rating -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_horizontal"
android:layout_gravity="start"
android:layout_marginTop="4dp">
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/baseline_star_24" />
<TextView
android:id="@+id/tvRelatedProductRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="5.0"
android:textColor="@color/black" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp">
<!-- User Profile Image -->
<ImageView
android:id="@+id/imgUser"
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="centerCrop"
android:src="@drawable/placeholder_image"
android:background="@drawable/placeholder_image" />
<!-- Review Content -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="12dp">
<!-- Username & Rating -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvUsername"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="budi21"
android:textStyle="bold"
android:textColor="@color/black" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="8dp"
android:src="@drawable/baseline_star_24" />
<TextView
android:id="@+id/tvReviewRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="5.0"
android:textColor="@color/black" />
</LinearLayout>
<!-- Review Text -->
<TextView
android:id="@+id/tvReviewText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enak sekali dan renyah. Sudah dua kali pesan. Terima kasih."
android:textColor="@color/black"
android:layout_marginTop="4dp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -20,4 +20,14 @@
android:name="com.alya.ecommerce_serang.ui.chat.ChatFragment"
android:label="fragment_chat"
tools:layout="@layout/fragment_chat" />
<activity
android:id="@+id/registerActivity"
android:name="com.alya.ecommerce_serang.ui.auth.RegisterActivity"
android:label="activity_register"
tools:layout="@layout/activity_register" />
<activity
android:id="@+id/loginActivity"
android:name="com.alya.ecommerce_serang.ui.auth.LoginActivity"
android:label="activity_login"
tools:layout="@layout/activity_login" />
</navigation>

View File

@ -13,4 +13,38 @@
<string name="error_loading">Terdapat error...</string>
<string name="new_products_text">Produk Terbaru</string>
<string name="rating">4.5</string>
<string name="open_store">Buka Toko</string>
<string name="delivery">Dikirim</string>
<string name="packages">Dikemas</string>
<string name="waiting_payment">Belum Dibayar</string>
<string name="users_email">youremail@gmail.com</string>
<string name="phone_number">123456789123</string>
<string name="date_birth">24 Oktober 2024</string>
<string name="username_profile">User123</string>
<string name="users_name">User</string>
<string name="hint_confirmation_password">Konfirmasi Kata Sandi</string>
<string name="email">Email</string>
<string name="hint_email">Masukkan Email</string>
<string name="username">Nama Pengguna</string>
<string name="hint_username">Masukkan Nama Pengguna</string>
<string name="full_name">Nama Lengkap</string>
<string name="hint_fullname">Masukkan Nama Lengkap</string>
<string name="password">Kata Sandi</string>
<string name="hint_password">Masukkan Kata Sandi Baru</string>
<string name="confirm_password">Konfirmasi Kata Sandi</string>
<string name="birth_date">Tanggal Lahir</string>
<string name="hint_birth_date">Pilih Tanggal Lahir</string>
<string name="gender">Jenis Kelamin</string>
<string name="hint_gender">Pilih Jenis Kelamin</string>
<string name="number_phone">Nomor Telepon</string>
<string name="hint_number_phone">Masukkan Nomor Telepon</string>
<string name="no_account">Belum punya akun?</string>
<string name="login">Masuk</string>
<string name="signup">Daftar</string>
<string name="login_email">Email atau Nomor Telepon</string>
<string name="hint_login_email">Email atau No. Handphone</string>
<string name="hint_login_password">Kata Sandi</string>
<string name="forget_password">Lupa Kata Sandi?</string>
<string name="enter_otp">Masukkan Kode OTP</string>
<string name="sold_product_text">Produk Terlaris</string>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="ButtonStatusDelivery" parent="@android:style/Widget.Button">
<!-- <item name="android:background">@drawable/custom_button_background</item>-->
<!-- <item name="android:textColor">@color/custom_button_text_color</item>-->
<item name="android:textSize">16sp</item>
<item name="android:padding">12dp</item>
<!-- Add more style attributes as needed -->
</style>
</resources>