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

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <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.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application <application
android:allowBackup="true" android:allowBackup="true"
@ -38,14 +39,27 @@
android:name=".ui.profile.mystore.TokoSayaActivity" android:name=".ui.profile.mystore.TokoSayaActivity"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.product.DetailProductActivity"
android:exported="true" > android:exported="false" />
<activity
android:name=".ui.auth.RegisterActivity"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </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> </application>
</manifest> </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 inStock: Int,
val price: Double, val price: Double,
val rating: Double, val rating: Double,
val related: List<Product>, val related: List<ProductsItem>,
val reviews: Int, val reviews: Int,
val title: String, val title: String,
@SerializedName("free_delivery") @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 package com.alya.ecommerce_serang.data.api.response
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
data class AllProductResponse( data class AllProductResponse(
@ -11,47 +12,4 @@ data class AllProductResponse(
val products: List<ProductsItem> 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 package com.alya.ecommerce_serang.data.api.retrofit
import com.alya.ecommerce_serang.BuildConfig 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.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
class ApiConfig { class ApiConfig {
companion object{ companion object {
fun getApiService(): ApiService { fun getApiService(tokenManager: SessionManager): ApiService {
val loggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val authInterceptor = AuthInterceptor(tokenManager)
val client = OkHttpClient.Builder() val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor) .addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
.build() .build()
val retrofit = Retrofit.Builder() val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL) .baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.client(client) .client(client)
.build() .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) return retrofit.create(ApiService::class.java)
} }
} }

View File

@ -1,22 +1,50 @@
package com.alya.ecommerce_serang.data.api.retrofit 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.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.ProductResponse
import com.alya.ecommerce_serang.data.api.response.RegisterResponse
import com.alya.ecommerce_serang.data.api.response.StoreResponse
import retrofit2.Call import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.POST
import retrofit2.http.Path import retrofit2.http.Path
interface ApiService { interface ApiService {
@GET("product") @POST("registeruser")
fun getAllProduct( suspend fun register (
@Header("Authorization") token: String = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzEsIm5hbWUiOiJhbHlhIiwiZW1haWwiOiJha3VuYmVsYWphci5hbHlhQGdtYWlsLmNvbSIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNzM4NDg0OTc0LCJleHAiOjE3NDEwNzY5NzR9.0JyXJQ_6CKiZEi0gvk1gcn-0ILu3a9lOr3HqjhJXbBE" @Body registerRequest: RegisterRequest
): Call<AllProductResponse> ): 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}") @GET("product/detail/{id}")
fun getDetailProduct ( fun getDetailProduct (
@Header("Authorization") token: String,
@Path("id") productId: Int @Path("id") productId: Int
): Call<ProductResponse> ): Call<ProductResponse>
@GET("mystore")
fun getStore (): Call<StoreResponse>
} }

View File

@ -1,7 +1,8 @@
package com.alya.ecommerce_serang.data.repository package com.alya.ecommerce_serang.data.repository
import android.util.Log 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 com.alya.ecommerce_serang.data.api.retrofit.ApiService
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -11,25 +12,41 @@ class ProductRepository(private val apiService: ApiService) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
Log.d("ProductRepository", "Attempting to fetch products") Log.d("ProductRepository", "Attempting to fetch products")
val response = apiService.getAllProduct().execute() val response = apiService.getAllProduct()
Log.d("ProductRepository", "Response received. Success: ${response.isSuccessful}")
Log.d("ProductRepository", "Response code: ${response.code()}")
Log.d("ProductRepository", "Response message: ${response.message()}")
if (response.isSuccessful) { 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 { } 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) { } catch (e: Exception) {
Result.failure(e) // Return a Result.Error with the exception caught
Result.Error(e)
} }
} }
// suspend fun getCategories():List<Category>
// suspend fun getAllCategories(): Result<List<CategoryItem>> =
// fun getProducts(query: ProductQuery) : Flow<PagingData<Product>> withContext(Dispatchers.IO) {
// fun getRecentSearchs(): Flow<List<String>> try {
// suspend fun clearRecents() Log.d("Categories", "Attempting to fetch categories")
// suspend fun addRecents(search:String) val response = apiService.allCategory()
// suspend fun getProduct(id:String):DetailProduct
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.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import com.alya.ecommerce_serang.R 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.databinding.ActivityMainBinding
import com.alya.ecommerce_serang.utils.SessionManager
//@AndroidEntryPoint //@AndroidEntryPoint
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
private val navController by lazy { private val navController by lazy {
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController (supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
} }
@ -19,6 +24,9 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
sessionManager = SessionManager(this)
apiService = ApiConfig.getApiService(sessionManager) // Inject SessionManager
setupBottomNavigation() setupBottomNavigation()
observeDestinationChanges() 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 package com.alya.ecommerce_serang.ui.home
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView 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.alya.ecommerce_serang.databinding.ItemCategoryHomeBinding
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
class HomeCategoryAdapter( 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. //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>() { ): 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. 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){ inner class ViewHolder(private val binding: ItemCategoryHomeBinding): RecyclerView.ViewHolder(binding.root){
fun bind(category: Category) = with(binding){ fun bind(category: CategoryItem) = with(binding) {
Glide.with(root).load(category.image).into(image) Log.d("CategoriesAdapter", "Binding category: ${category.name}, Image: ${category.image}")
name.text = category.title
root.setOnClickListener{ val fullImageUrl = if (category.image.startsWith("/")) {
onClick(category) 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) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(categories[position]) 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 package com.alya.ecommerce_serang.ui.home
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment 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.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.alya.ecommerce_serang.R 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.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.data.repository.ProductRepository
import com.alya.ecommerce_serang.databinding.FragmentHomeBinding import com.alya.ecommerce_serang.databinding.FragmentHomeBinding
import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.HorizontalMarginItemDecoration import com.alya.ecommerce_serang.utils.HorizontalMarginItemDecoration
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.setLightStatusBar import com.alya.ecommerce_serang.utils.setLightStatusBar
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -24,30 +29,33 @@ class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private lateinit var viewModel: HomeViewModel
private var productAdapter: HorizontalProductAdapter? = null 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( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false) _binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root return _binding!!.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) 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() initUi()
setupRecyclerView() setupRecyclerView()
observeData() observeData()
@ -59,6 +67,11 @@ class HomeFragment : Fragment() {
onClick = { product -> handleProductClick(product) } onClick = { product -> handleProductClick(product) }
) )
categoryAdapter = HomeCategoryAdapter(
categories = emptyList(),
onClick = { category -> handleCategoryProduct(category)}
)
binding.newProducts.apply { binding.newProducts.apply {
adapter = productAdapter adapter = productAdapter
layoutManager = LinearLayoutManager( layoutManager = LinearLayoutManager(
@ -67,37 +80,59 @@ class HomeFragment : Fragment() {
false false
) )
} }
binding.categories.apply {
adapter = categoryAdapter
layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.HORIZONTAL,
false
)
}
} }
private fun observeData() { private fun observeData() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewModel.uiState.collect { state -> viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
when (state) { viewModel.uiState.collect { state ->
is HomeUiState.Loading -> { when (state) {
binding.loading.root.isVisible = true is HomeUiState.Loading -> {
binding.error.root.isVisible = false binding.loading.root.isVisible = true
binding.home.isVisible = false binding.error.root.isVisible = false
} binding.home.isVisible = false
is HomeUiState.Success -> { }
binding.loading.root.isVisible = false is HomeUiState.Success -> {
binding.error.root.isVisible = false binding.loading.root.isVisible = false
binding.home.isVisible = true binding.error.root.isVisible = false
productAdapter?.updateProducts(state.products) binding.home.isVisible = true
} productAdapter?.updateLimitedProducts(state.products)
is HomeUiState.Error -> { }
binding.loading.root.isVisible = false is HomeUiState.Error -> {
binding.error.root.isVisible = true binding.loading.root.isVisible = false
binding.home.isVisible = false binding.error.root.isVisible = true
binding.error.errorMessage.text = state.message binding.home.isVisible = false
binding.error.retryButton.setOnClickListener { binding.error.errorMessage.text = state.message
viewModel.retry() 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() { private fun initUi() {
// For LightStatusBar // For LightStatusBar
setLightStatusBar() setLightStatusBar()
@ -124,22 +159,21 @@ class HomeFragment : Fragment() {
private fun handleProductClick(product: ProductsItem) { private fun handleProductClick(product: ProductsItem) {
// Navigate to product detail
// findNavController().navigate( }
// HomeFragmentDirections.actionHomeToDetail(product.id)
// ) private fun handleCategoryProduct(category: CategoryItem) {
} }
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
productAdapter = null
categoryAdapter = null
_binding = null _binding = null
} }
private fun showLoading(isLoading: Boolean) { private fun showLoading(isLoading: Boolean) {
if (isLoading) { binding.progressBar.isVisible = isLoading
binding.progressBar.visibility = View.VISIBLE
} else {
binding.progressBar.visibility = View.GONE
}
} }
} }

View File

@ -1,13 +1,12 @@
package com.alya.ecommerce_serang.ui.home package com.alya.ecommerce_serang.ui.home
import android.util.Log import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.response.AllProductResponse import com.alya.ecommerce_serang.data.api.dto.CategoryItem
import com.alya.ecommerce_serang.data.api.response.ProductsItem 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.data.repository.ProductRepository
import com.alya.ecommerce_serang.data.repository.Result
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -18,37 +17,56 @@ class HomeViewModel (
): ViewModel() { ): ViewModel() {
private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading) private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow() 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 { init {
loadProducts() loadProducts()
loadCategories()
} }
private fun loadProducts() { private fun loadProducts() {
viewModelScope.launch { viewModelScope.launch {
_uiState.value = HomeUiState.Loading _uiState.value = HomeUiState.Loading
productRepository.getAllProducts() when (val result = productRepository.getAllProducts()) {
.onSuccess { products -> is Result.Success -> _uiState.value = HomeUiState.Success(result.data)
_uiState.value = HomeUiState.Success(products) is Result.Error -> _uiState.value = HomeUiState.Error(result.exception.message ?: "Unknown error")
} is Result.Loading -> {}
.onFailure { error -> }
_uiState.value = HomeUiState.Error(error.message ?: "Unknown error")
Log.e("ProductViewModel", "Products fetch failed", error)
}
} }
} }
fun retry() { private fun loadCategories() {
loadProducts() 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 { fun retry() {
// productRepository.toggleWishlist(product.id,product.wishlist) loadProducts()
// }catch (e:Exception){ 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}")
// }
// } // }
// } // }
} }

View File

@ -1,9 +1,13 @@
package com.alya.ecommerce_serang.ui.home package com.alya.ecommerce_serang.ui.home
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView 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.alya.ecommerce_serang.databinding.ItemProductHorizontalBinding
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -16,17 +20,24 @@ class HorizontalProductAdapter(
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
fun bind(product: ProductsItem) = with(binding) { 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 itemName.text = product.name
itemPrice.text = product.price itemPrice.text = product.price
rating.text = product.rating rating.text = product.rating
// productSold.text = "${product.totalSold} sold"
// Load image using Glide // Load image using Glide
Glide.with(itemView) Glide.with(itemView)
// .load("${BuildConfig.BASE_URL}/product/${product.image}") .load(fullImageUrl)
// .load("${BuildConfig.BASE_URL}/${product.image}") .placeholder(R.drawable.placeholder_image)
.load(product.image) .into(imageProduct)
.into(image)
root.setOnClickListener { onClick(product) } root.setOnClickListener { onClick(product) }
} }
@ -46,7 +57,32 @@ class HorizontalProductAdapter(
} }
fun updateProducts(newProducts: List<ProductsItem>) { fun updateProducts(newProducts: List<ProductsItem>) {
val diffCallback = ProductDiffCallback(products, newProducts)
val diffResult = DiffUtil.calculateDiff(diffCallback)
products = newProducts 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.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import com.alya.ecommerce_serang.databinding.FragmentProfileBinding
import com.alya.ecommerce_serang.R
class ProfileFragment : Fragment() { class ProfileFragment : Fragment() {
companion object { private var _binding: FragmentProfileBinding? = null
fun newInstance() = ProfileFragment() private val binding get() = _binding!!
} private lateinit var viewModel: ProfileViewModel
private val viewModel: ProfileViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -26,6 +23,7 @@ class ProfileFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): 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 package com.alya.ecommerce_serang.utils
import android.os.Parcelable 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 import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class ProductQuery ( data class ProductQuery (
val category: Category? = null, val category: CategoryItem,
val search:String? = null, val search:String? = null,
val range:Pair<Float,Float> = 0f to 10000f, val range:Pair<Float,Float> = 0f to 10000f,
val rating:Int? = null, 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_height="wrap_content"
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:text="@string/new_products_text" android:text="@string/sold_product_text"
android:textColor="@color/black" android:textColor="@color/black"
android:textSize="22sp" android:textSize="22sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@ -1,13 +1,327 @@
<?xml version="1.0" encoding="utf-8"?> <?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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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"> tools:context=".ui.profile.ProfileFragment">
<TextView <!-- Profile Header -->
android:layout_width="match_parent" <ImageView
android:layout_height="match_parent" android:id="@+id/profileImage"
android:text="Hello" /> 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 <ImageView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/image" android:id="@+id/image_category"
android:src="@drawable/makanan_ringan" android:src="@drawable/makanan_ringan"
android:scaleType="centerCrop" /> android:scaleType="centerCrop" />
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>

View File

@ -17,7 +17,7 @@
app:strokeWidth="1dp"> app:strokeWidth="1dp">
<ImageView <ImageView
android:id="@+id/image" android:id="@+id/image_product"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scaleType="centerCrop" 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:name="com.alya.ecommerce_serang.ui.chat.ChatFragment"
android:label="fragment_chat" android:label="fragment_chat"
tools:layout="@layout/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> </navigation>

View File

@ -13,4 +13,38 @@
<string name="error_loading">Terdapat error...</string> <string name="error_loading">Terdapat error...</string>
<string name="new_products_text">Produk Terbaru</string> <string name="new_products_text">Produk Terbaru</string>
<string name="rating">4.5</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> </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>