mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-14 11:02:21 +00:00
Merge branch 'master' into gracia
This commit is contained in:
@ -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
|
@ -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
|
@ -14,7 +14,7 @@ data class DetailProduct(
|
||||
val inStock: Int,
|
||||
val price: Double,
|
||||
val rating: Double,
|
||||
val related: List<Product>,
|
||||
val related: List<ProductsItem>,
|
||||
val reviews: Int,
|
||||
val title: String,
|
||||
@SerializedName("free_delivery")
|
||||
|
@ -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,
|
||||
)
|
@ -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
|
||||
)
|
@ -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,
|
||||
)
|
@ -1,8 +1,9 @@
|
||||
package com.alya.ecommerce_serang.data.model
|
||||
package com.alya.ecommerce_serang.data.api.dto
|
||||
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Product(
|
||||
data class ProductsItem(
|
||||
|
||||
@field:SerializedName("store_id")
|
||||
val storeId: Int,
|
||||
@ -19,23 +20,20 @@ data class Product(
|
||||
@field:SerializedName("weight")
|
||||
val weight: Int,
|
||||
|
||||
@field:SerializedName("product_name")
|
||||
val productName: String,
|
||||
|
||||
@field:SerializedName("is_pre_order")
|
||||
val isPreOrder: Boolean,
|
||||
|
||||
@field:SerializedName("duration")
|
||||
val duration: Any,
|
||||
|
||||
@field:SerializedName("category_id")
|
||||
val categoryId: Int,
|
||||
|
||||
@field:SerializedName("price")
|
||||
val price: String,
|
||||
|
||||
@field:SerializedName("product_id")
|
||||
val productId: Int,
|
||||
@field:SerializedName("name")
|
||||
val name: String,
|
||||
|
||||
@field:SerializedName("id")
|
||||
val id: Int,
|
||||
|
||||
@field:SerializedName("min_order")
|
||||
val minOrder: Int,
|
||||
@ -46,6 +44,6 @@ data class Product(
|
||||
@field:SerializedName("stock")
|
||||
val stock: Int,
|
||||
|
||||
@field:SerializedName("product_category")
|
||||
val productCategory: String
|
||||
@field:SerializedName("status")
|
||||
val status: String
|
||||
)
|
@ -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
|
||||
)
|
@ -0,0 +1,30 @@
|
||||
package com.alya.ecommerce_serang.data.api.dto
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class UserProfile(
|
||||
|
||||
@field:SerializedName("image")
|
||||
val image: String? = null,
|
||||
|
||||
@field:SerializedName("role")
|
||||
val role: String,
|
||||
|
||||
@field:SerializedName("user_id")
|
||||
val userId: Int,
|
||||
|
||||
@field:SerializedName("phone")
|
||||
val phone: String,
|
||||
|
||||
@field:SerializedName("birth_date")
|
||||
val birthDate: String,
|
||||
|
||||
@field:SerializedName("name")
|
||||
val name: String,
|
||||
|
||||
@field:SerializedName("email")
|
||||
val email: String,
|
||||
|
||||
@field:SerializedName("username")
|
||||
val username: String
|
||||
)
|
@ -1,5 +1,6 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class AllProductResponse(
|
||||
@ -11,47 +12,4 @@ data class AllProductResponse(
|
||||
val products: List<ProductsItem>
|
||||
)
|
||||
|
||||
data class ProductsItem(
|
||||
|
||||
@field:SerializedName("store_id")
|
||||
val storeId: Int,
|
||||
|
||||
@field:SerializedName("image")
|
||||
val image: String,
|
||||
|
||||
@field:SerializedName("rating")
|
||||
val rating: String,
|
||||
|
||||
@field:SerializedName("description")
|
||||
val description: String,
|
||||
|
||||
@field:SerializedName("weight")
|
||||
val weight: Int,
|
||||
|
||||
@field:SerializedName("is_pre_order")
|
||||
val isPreOrder: Boolean,
|
||||
|
||||
@field:SerializedName("category_id")
|
||||
val categoryId: Int,
|
||||
|
||||
@field:SerializedName("price")
|
||||
val price: String,
|
||||
|
||||
@field:SerializedName("name")
|
||||
val name: String,
|
||||
|
||||
@field:SerializedName("id")
|
||||
val id: Int,
|
||||
|
||||
@field:SerializedName("min_order")
|
||||
val minOrder: Int,
|
||||
|
||||
@field:SerializedName("total_sold")
|
||||
val totalSold: Int,
|
||||
|
||||
@field:SerializedName("stock")
|
||||
val stock: Int,
|
||||
|
||||
@field:SerializedName("status")
|
||||
val status: String
|
||||
)
|
||||
|
@ -0,0 +1,33 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class AllStoreResponse(
|
||||
|
||||
@field:SerializedName("store")
|
||||
val store: AllStore,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class AllStore(
|
||||
|
||||
@field:SerializedName("store_name")
|
||||
val storeName: String,
|
||||
|
||||
@field:SerializedName("description")
|
||||
val description: String,
|
||||
|
||||
@field:SerializedName("store_type")
|
||||
val storeType: String,
|
||||
|
||||
@field:SerializedName("store_location")
|
||||
val storeLocation: String,
|
||||
|
||||
@field:SerializedName("store_image")
|
||||
val storeImage: Any,
|
||||
|
||||
@field:SerializedName("status")
|
||||
val status: String
|
||||
)
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -0,0 +1,33 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class DetailStoreProductResponse(
|
||||
|
||||
@field:SerializedName("store")
|
||||
val store: StoreProduct,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class StoreProduct(
|
||||
|
||||
@field:SerializedName("store_name")
|
||||
val storeName: String,
|
||||
|
||||
@field:SerializedName("description")
|
||||
val description: String,
|
||||
|
||||
@field:SerializedName("store_type")
|
||||
val storeType: String,
|
||||
|
||||
@field:SerializedName("store_location")
|
||||
val storeLocation: String,
|
||||
|
||||
@field:SerializedName("store_image")
|
||||
val storeImage: Any,
|
||||
|
||||
@field:SerializedName("status")
|
||||
val status: String
|
||||
)
|
@ -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
|
||||
)
|
@ -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
|
||||
)
|
@ -17,7 +17,7 @@ data class Product(
|
||||
val storeId: Int,
|
||||
|
||||
@field:SerializedName("image")
|
||||
val image: String,
|
||||
val image: String? = null,
|
||||
|
||||
@field:SerializedName("rating")
|
||||
val rating: String,
|
||||
@ -35,7 +35,7 @@ data class Product(
|
||||
val isPreOrder: Boolean,
|
||||
|
||||
@field:SerializedName("duration")
|
||||
val duration: Any,
|
||||
val duration: Any?,
|
||||
|
||||
@field:SerializedName("category_id")
|
||||
val categoryId: Int,
|
||||
|
@ -0,0 +1,15 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
|
||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ProfileResponse(
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String,
|
||||
|
||||
@field:SerializedName("user")
|
||||
val user: UserProfile
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
)
|
@ -0,0 +1,39 @@
|
||||
package com.alya.ecommerce_serang.data.api.response
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class ReviewProductResponse(
|
||||
|
||||
@field:SerializedName("reviews")
|
||||
val reviews: List<ReviewsItem>,
|
||||
|
||||
@field:SerializedName("message")
|
||||
val message: String
|
||||
)
|
||||
|
||||
data class ReviewsItem(
|
||||
|
||||
@field:SerializedName("order_item_id")
|
||||
val orderItemId: Int,
|
||||
|
||||
@field:SerializedName("review_date")
|
||||
val reviewDate: String,
|
||||
|
||||
@field:SerializedName("user_image")
|
||||
val userImage: String? = null,
|
||||
|
||||
@field:SerializedName("product_id")
|
||||
val productId: Int,
|
||||
|
||||
@field:SerializedName("rating")
|
||||
val rating: Int,
|
||||
|
||||
@field:SerializedName("review_text")
|
||||
val reviewText: String,
|
||||
|
||||
@field:SerializedName("product_name")
|
||||
val productName: String,
|
||||
|
||||
@field:SerializedName("username")
|
||||
val username: String
|
||||
)
|
@ -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
|
||||
)
|
@ -1,24 +1,52 @@
|
||||
package com.alya.ecommerce_serang.data.api.retrofit
|
||||
|
||||
import com.alya.ecommerce_serang.BuildConfig
|
||||
import com.alya.ecommerce_serang.utils.AuthInterceptor
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
||||
class ApiConfig {
|
||||
companion object{
|
||||
fun getApiService(): ApiService {
|
||||
val loggingInterceptor =
|
||||
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
companion object {
|
||||
fun getApiService(tokenManager: SessionManager): ApiService {
|
||||
|
||||
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
|
||||
val authInterceptor = AuthInterceptor(tokenManager)
|
||||
|
||||
val client = OkHttpClient.Builder()
|
||||
.addInterceptor(loggingInterceptor)
|
||||
.addInterceptor(authInterceptor)
|
||||
.build()
|
||||
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl(BuildConfig.BASE_URL)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(client)
|
||||
.build()
|
||||
|
||||
return retrofit.create(ApiService::class.java)
|
||||
}
|
||||
|
||||
fun getUnauthenticatedApiService(): ApiService {
|
||||
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
|
||||
val client = OkHttpClient.Builder()
|
||||
.addInterceptor(loggingInterceptor)
|
||||
.build()
|
||||
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl(BuildConfig.BASE_URL)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(client)
|
||||
.build()
|
||||
|
||||
return retrofit.create(ApiService::class.java)
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,67 @@
|
||||
package com.alya.ecommerce_serang.data.api.retrofit
|
||||
|
||||
import com.alya.ecommerce_serang.data.api.dto.LoginRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
|
||||
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||
import com.alya.ecommerce_serang.data.api.response.AllProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.CategoryResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.DetailStoreProductResponse
|
||||
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.ProfileResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.RegisterResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.ReviewProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.StoreResponse
|
||||
import retrofit2.Call
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface ApiService {
|
||||
@GET("product")
|
||||
fun getAllProduct(
|
||||
@Header("Authorization") token: String = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NzEsIm5hbWUiOiJhbHlhIiwiZW1haWwiOiJha3VuYmVsYWphci5hbHlhQGdtYWlsLmNvbSIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNzM4NDg0OTc0LCJleHAiOjE3NDEwNzY5NzR9.0JyXJQ_6CKiZEi0gvk1gcn-0ILu3a9lOr3HqjhJXbBE"
|
||||
): Call<AllProductResponse>
|
||||
@POST("registeruser")
|
||||
suspend fun register (
|
||||
@Body registerRequest: RegisterRequest
|
||||
): Response<RegisterResponse>
|
||||
|
||||
@POST("otp")
|
||||
suspend fun getOTP(
|
||||
@Body otpRequest: OtpRequest
|
||||
):OtpResponse
|
||||
|
||||
@POST("login")
|
||||
suspend fun login(
|
||||
@Body loginRequest: LoginRequest
|
||||
): Response<LoginResponse>
|
||||
|
||||
@GET("category")
|
||||
suspend fun allCategory(
|
||||
): Response<CategoryResponse>
|
||||
|
||||
@GET("product")
|
||||
suspend fun getAllProduct(): Response<AllProductResponse>
|
||||
|
||||
@GET("product/review/{id}")
|
||||
suspend fun getProductReview(
|
||||
@Path("id") productId: Int
|
||||
): Response<ReviewProductResponse>
|
||||
|
||||
@GET("product/detail/{id}")
|
||||
fun getDetailProduct (
|
||||
@Header("Authorization") token: String,
|
||||
suspend fun getDetailProduct (
|
||||
@Path("id") productId: Int
|
||||
): Call<ProductResponse>
|
||||
): Response<ProductResponse>
|
||||
|
||||
@GET("profile")
|
||||
suspend fun getUserProfile(): Response<ProfileResponse>
|
||||
|
||||
@GET("store/detail/{id}")
|
||||
suspend fun getDetailStore (
|
||||
@Path("id") storeId: Int
|
||||
): Response<DetailStoreProductResponse>
|
||||
|
||||
|
||||
@GET("mystore")
|
||||
fun getStore (): Call<StoreResponse>
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
package com.alya.ecommerce_serang.data.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.alya.ecommerce_serang.data.api.response.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.ProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.ReviewsItem
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -11,25 +14,84 @@ class ProductRepository(private val apiService: ApiService) {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
Log.d("ProductRepository", "Attempting to fetch products")
|
||||
val response = apiService.getAllProduct().execute()
|
||||
Log.d("ProductRepository", "Response received. Success: ${response.isSuccessful}")
|
||||
Log.d("ProductRepository", "Response code: ${response.code()}")
|
||||
Log.d("ProductRepository", "Response message: ${response.message()}")
|
||||
val response = apiService.getAllProduct()
|
||||
|
||||
if (response.isSuccessful) {
|
||||
Result.success(response.body()?.products ?: emptyList())
|
||||
// Return a Result.Success with the list of products
|
||||
|
||||
Result.Success(response.body()?.products ?: emptyList())
|
||||
|
||||
} else {
|
||||
Result.failure(Exception("Failed to fetch products"))
|
||||
// Return a Result.Error with a custom Exception
|
||||
Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||
Result.Error(Exception("Failed to fetch products. Code: ${response.code()}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
// Return a Result.Error with the exception caught
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
// suspend fun getCategories():List<Category>
|
||||
|
||||
suspend fun fetchProductDetail(productId: Int): ProductResponse? {
|
||||
return try {
|
||||
val response = apiService.getDetailProduct(productId)
|
||||
if (response.isSuccessful) {
|
||||
response.body()
|
||||
} else {
|
||||
Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getAllCategories(): Result<List<CategoryItem>> =
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
Log.d("Categories", "Attempting to fetch categories")
|
||||
val response = apiService.allCategory()
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val categories = response.body()?.category ?: emptyList()
|
||||
Log.d("Categories", "Fetched categories: $categories")
|
||||
categories.forEach { Log.d("Category Image", "Category: ${it.name}, Image: ${it.image}") }
|
||||
Result.Success(categories)
|
||||
} else {
|
||||
Result.Error(Exception("Failed to fetch categories. Code: ${response.code()}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Categories", "Error fetching categories", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchProductReview(productId: Int): List<ReviewsItem>? {
|
||||
return try {
|
||||
val response = apiService.getProductReview(productId)
|
||||
if (response.isSuccessful) {
|
||||
response.body()?.reviews // Ambil daftar review dari response
|
||||
} else {
|
||||
Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// suspend fun fetchStoreDetail(storeId: Int): Store? {
|
||||
// return try {
|
||||
// val response = apiService.getStore(storeId)
|
||||
// if (response.isSucessful) {
|
||||
// response.body()?.store
|
||||
// } else {
|
||||
// Log.e("ProductRepository", "Error: ${response.errorBody()?.string()}")
|
||||
//
|
||||
// fun getProducts(query: ProductQuery) : Flow<PagingData<Product>>
|
||||
// fun getRecentSearchs(): Flow<List<String>>
|
||||
// suspend fun clearRecents()
|
||||
// suspend fun addRecents(search:String)
|
||||
// suspend fun getProduct(id:String):DetailProduct
|
||||
}
|
||||
// null
|
||||
// }
|
||||
// } catch (e: Exception) {
|
||||
// null
|
||||
// }
|
||||
// }
|
@ -0,0 +1,69 @@
|
||||
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.dto.UserProfile
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchUserProfile(): Result<UserProfile?> {
|
||||
return try {
|
||||
val response = apiService.getUserProfile()
|
||||
if (response.isSuccessful) {
|
||||
response.body()?.user?.let {
|
||||
Result.Success(it) // ✅ Returning only UserProfile
|
||||
} ?: Result.Error(Exception("User data not found"))
|
||||
} else {
|
||||
Result.Error(Exception("Error fetching profile: ${response.code()}"))
|
||||
}
|
||||
} 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>()
|
||||
}
|
@ -6,11 +6,16 @@ import androidx.core.view.isVisible
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import com.alya.ecommerce_serang.databinding.ActivityMainBinding
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
|
||||
//@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var apiService: ApiService
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private val navController by lazy {
|
||||
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
|
||||
}
|
||||
@ -19,6 +24,9 @@ class MainActivity : AppCompatActivity() {
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
sessionManager = SessionManager(this)
|
||||
apiService = ApiConfig.getApiService(sessionManager) // Inject SessionManager
|
||||
|
||||
setupBottomNavigation()
|
||||
observeDestinationChanges()
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
// }
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1,16 +1,19 @@
|
||||
package com.alya.ecommerce_serang.ui.home
|
||||
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.data.api.dto.Category
|
||||
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
|
||||
import com.alya.ecommerce_serang.databinding.ItemCategoryHomeBinding
|
||||
import com.bumptech.glide.Glide
|
||||
|
||||
class HomeCategoryAdapter(
|
||||
private val categories:List<Category>,
|
||||
private var categories:List<CategoryItem>,
|
||||
//A lambda function that will be invoked when a category item is clicked.
|
||||
private val onClick:(category:Category) -> Unit
|
||||
private val onClick:(category:CategoryItem) -> Unit
|
||||
): RecyclerView.Adapter<HomeCategoryAdapter.ViewHolder>() {
|
||||
|
||||
/*
|
||||
@ -18,12 +21,25 @@ class HomeCategoryAdapter(
|
||||
the RecyclerView.It binds the Category data to the corresponding views within the item layout.
|
||||
*/
|
||||
inner class ViewHolder(private val binding: ItemCategoryHomeBinding): RecyclerView.ViewHolder(binding.root){
|
||||
fun bind(category: Category) = with(binding){
|
||||
Glide.with(root).load(category.image).into(image)
|
||||
name.text = category.title
|
||||
root.setOnClickListener{
|
||||
onClick(category)
|
||||
fun bind(category: CategoryItem) = with(binding) {
|
||||
Log.d("CategoriesAdapter", "Binding category: ${category.name}, Image: ${category.image}")
|
||||
|
||||
val fullImageUrl = if (category.image.startsWith("/")) {
|
||||
BASE_URL + category.image.removePrefix("/") // Append base URL if the path starts with "/"
|
||||
} else {
|
||||
category.image // Use as is if it's already a full URL
|
||||
}
|
||||
|
||||
Log.d("CategoriesAdapter", "Loading image: $fullImageUrl")
|
||||
|
||||
Glide.with(itemView.context)
|
||||
.load(fullImageUrl) // Ensure full URL
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.into(imageCategory)
|
||||
|
||||
name.text = category.name
|
||||
|
||||
root.setOnClickListener { onClick(category) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,4 +52,14 @@ class HomeCategoryAdapter(
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(categories[position])
|
||||
}
|
||||
|
||||
fun updateData(newCategories: List<CategoryItem>) {
|
||||
categories = newCategories.toList()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun updateLimitedCategory(newCategories: List<CategoryItem>){
|
||||
val limitedCategories = newCategories.take(10)
|
||||
updateData(limitedCategories)
|
||||
}
|
||||
}
|
@ -1,21 +1,28 @@
|
||||
package com.alya.ecommerce_serang.ui.home
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.response.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||
import com.alya.ecommerce_serang.databinding.FragmentHomeBinding
|
||||
import com.alya.ecommerce_serang.ui.product.DetailProductActivity
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.HorizontalMarginItemDecoration
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import com.alya.ecommerce_serang.utils.setLightStatusBar
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ -24,30 +31,33 @@ class HomeFragment : Fragment() {
|
||||
|
||||
private var _binding: FragmentHomeBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private lateinit var viewModel: HomeViewModel
|
||||
private var productAdapter: HorizontalProductAdapter? = null
|
||||
private var categoryAdapter: HomeCategoryAdapter? = null
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private val viewModel: HomeViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val productRepository = ProductRepository(apiService)
|
||||
HomeViewModel(productRepository)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
sessionManager = SessionManager(requireContext())
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
return _binding!!.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val repository = ProductRepository(ApiConfig.getApiService())
|
||||
viewModel = ViewModelProvider(
|
||||
this,
|
||||
// Pass a lambda that creates the ViewModel
|
||||
BaseViewModelFactory {
|
||||
HomeViewModel(repository)
|
||||
}
|
||||
)[HomeViewModel::class.java]
|
||||
|
||||
initUi()
|
||||
setupRecyclerView()
|
||||
observeData()
|
||||
@ -59,6 +69,11 @@ class HomeFragment : Fragment() {
|
||||
onClick = { product -> handleProductClick(product) }
|
||||
)
|
||||
|
||||
categoryAdapter = HomeCategoryAdapter(
|
||||
categories = emptyList(),
|
||||
onClick = { category -> handleCategoryProduct(category)}
|
||||
)
|
||||
|
||||
binding.newProducts.apply {
|
||||
adapter = productAdapter
|
||||
layoutManager = LinearLayoutManager(
|
||||
@ -67,37 +82,59 @@ class HomeFragment : Fragment() {
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
binding.categories.apply {
|
||||
adapter = categoryAdapter
|
||||
layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeData() {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.uiState.collect { state ->
|
||||
when (state) {
|
||||
is HomeUiState.Loading -> {
|
||||
binding.loading.root.isVisible = true
|
||||
binding.error.root.isVisible = false
|
||||
binding.home.isVisible = false
|
||||
}
|
||||
is HomeUiState.Success -> {
|
||||
binding.loading.root.isVisible = false
|
||||
binding.error.root.isVisible = false
|
||||
binding.home.isVisible = true
|
||||
productAdapter?.updateProducts(state.products)
|
||||
}
|
||||
is HomeUiState.Error -> {
|
||||
binding.loading.root.isVisible = false
|
||||
binding.error.root.isVisible = true
|
||||
binding.home.isVisible = false
|
||||
binding.error.errorMessage.text = state.message
|
||||
binding.error.retryButton.setOnClickListener {
|
||||
viewModel.retry()
|
||||
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.uiState.collect { state ->
|
||||
when (state) {
|
||||
is HomeUiState.Loading -> {
|
||||
binding.loading.root.isVisible = true
|
||||
binding.error.root.isVisible = false
|
||||
binding.home.isVisible = false
|
||||
}
|
||||
is HomeUiState.Success -> {
|
||||
binding.loading.root.isVisible = false
|
||||
binding.error.root.isVisible = false
|
||||
binding.home.isVisible = true
|
||||
productAdapter?.updateLimitedProducts(state.products) // Ensure productAdapter is initialized
|
||||
}
|
||||
is HomeUiState.Error -> {
|
||||
binding.loading.root.isVisible = false
|
||||
binding.error.root.isVisible = true
|
||||
binding.home.isVisible = false
|
||||
binding.error.errorMessage.text = state.message
|
||||
binding.error.retryButton.setOnClickListener {
|
||||
viewModel.retry()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.categories.collect { categories ->
|
||||
Log.d("Categories", "Updated Categories: $categories")
|
||||
categories.forEach { Log.d("Category Image", "Category: ${it.name}, Image: ${it.image}") }
|
||||
categoryAdapter?.updateLimitedCategory(categories)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun initUi() {
|
||||
// For LightStatusBar
|
||||
setLightStatusBar()
|
||||
@ -124,22 +161,23 @@ class HomeFragment : Fragment() {
|
||||
|
||||
|
||||
private fun handleProductClick(product: ProductsItem) {
|
||||
// Navigate to product detail
|
||||
// findNavController().navigate(
|
||||
// HomeFragmentDirections.actionHomeToDetail(product.id)
|
||||
// )
|
||||
val intent = Intent(requireContext(), DetailProductActivity::class.java)
|
||||
intent.putExtra("PRODUCT_ID", product.id) // Pass product ID
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun handleCategoryProduct(category: CategoryItem) {
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
productAdapter = null
|
||||
categoryAdapter = null
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun showLoading(isLoading: Boolean) {
|
||||
if (isLoading) {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
}
|
||||
binding.progressBar.isVisible = isLoading
|
||||
}
|
||||
}
|
@ -1,13 +1,12 @@
|
||||
package com.alya.ecommerce_serang.ui.home
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.response.AllProductResponse
|
||||
import com.alya.ecommerce_serang.data.api.response.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@ -18,37 +17,56 @@ class HomeViewModel (
|
||||
): ViewModel() {
|
||||
private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
|
||||
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
|
||||
val home = MutableLiveData<AllProductResponse?>(null)
|
||||
constructor() : this(ProductRepository(ApiConfig.getApiService()))
|
||||
|
||||
private val _categories = MutableStateFlow<List<CategoryItem>>(emptyList())
|
||||
val categories: StateFlow<List<CategoryItem>> = _categories.asStateFlow()
|
||||
|
||||
init {
|
||||
loadProducts()
|
||||
loadCategories()
|
||||
}
|
||||
|
||||
private fun loadProducts() {
|
||||
viewModelScope.launch {
|
||||
_uiState.value = HomeUiState.Loading
|
||||
productRepository.getAllProducts()
|
||||
.onSuccess { products ->
|
||||
_uiState.value = HomeUiState.Success(products)
|
||||
}
|
||||
.onFailure { error ->
|
||||
_uiState.value = HomeUiState.Error(error.message ?: "Unknown error")
|
||||
Log.e("ProductViewModel", "Products fetch failed", error)
|
||||
}
|
||||
when (val result = productRepository.getAllProducts()) {
|
||||
is Result.Success -> _uiState.value = HomeUiState.Success(result.data)
|
||||
is Result.Error -> _uiState.value = HomeUiState.Error(result.exception.message ?: "Unknown error")
|
||||
is Result.Loading -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun retry() {
|
||||
loadProducts()
|
||||
private fun loadCategories() {
|
||||
viewModelScope.launch {
|
||||
when (val result = productRepository.getAllCategories()) {
|
||||
is Result.Success -> _categories.value = result.data
|
||||
is Result.Error -> Log.e("HomeViewModel", "Failed to fetch categories", result.exception)
|
||||
is Result.Loading -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fun toggleWishlist(product: Product) = viewModelScope.launch {
|
||||
// try {
|
||||
// productRepository.toggleWishlist(product.id,product.wishlist)
|
||||
// }catch (e:Exception){
|
||||
//
|
||||
|
||||
fun retry() {
|
||||
loadProducts()
|
||||
loadCategories()
|
||||
}
|
||||
|
||||
// private fun fetchUserData() {
|
||||
// viewModelScope.launch {
|
||||
// try {
|
||||
// val response = apiService.getProtectedData() // Example API request
|
||||
// if (response.isSuccessful) {
|
||||
// val data = response.body()
|
||||
// Log.d("HomeFragment", "User Data: $data")
|
||||
// // Update UI with data
|
||||
// } else {
|
||||
// Log.e("HomeFragment", "Error: ${response.message()}")
|
||||
// }
|
||||
// } catch (e: Exception) {
|
||||
// Log.e("HomeFragment", "Exception: ${e.message}")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@ -57,4 +75,4 @@ sealed class HomeUiState {
|
||||
object Loading : HomeUiState()
|
||||
data class Success(val products: List<ProductsItem>) : HomeUiState()
|
||||
data class Error(val message: String) : HomeUiState()
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
package com.alya.ecommerce_serang.ui.home
|
||||
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.data.api.response.ProductsItem
|
||||
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.dto.ProductsItem
|
||||
import com.alya.ecommerce_serang.databinding.ItemProductHorizontalBinding
|
||||
import com.bumptech.glide.Glide
|
||||
|
||||
@ -16,17 +20,24 @@ class HorizontalProductAdapter(
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(product: ProductsItem) = with(binding) {
|
||||
|
||||
val fullImageUrl = if (product.image.startsWith("/")) {
|
||||
BASE_URL + product.image.removePrefix("/") // Append base URL if the path starts with "/"
|
||||
} else {
|
||||
product.image // Use as is if it's already a full URL
|
||||
}
|
||||
|
||||
Log.d("ProductAdapter", "Loading image: $fullImageUrl")
|
||||
|
||||
itemName.text = product.name
|
||||
itemPrice.text = product.price
|
||||
rating.text = product.rating
|
||||
// productSold.text = "${product.totalSold} sold"
|
||||
|
||||
// Load image using Glide
|
||||
Glide.with(itemView)
|
||||
// .load("${BuildConfig.BASE_URL}/product/${product.image}")
|
||||
// .load("${BuildConfig.BASE_URL}/${product.image}")
|
||||
.load(product.image)
|
||||
.into(image)
|
||||
.load(fullImageUrl)
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.into(imageProduct)
|
||||
|
||||
root.setOnClickListener { onClick(product) }
|
||||
}
|
||||
@ -46,7 +57,32 @@ class HorizontalProductAdapter(
|
||||
}
|
||||
|
||||
fun updateProducts(newProducts: List<ProductsItem>) {
|
||||
val diffCallback = ProductDiffCallback(products, newProducts)
|
||||
val diffResult = DiffUtil.calculateDiff(diffCallback)
|
||||
products = newProducts
|
||||
notifyDataSetChanged()
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
fun updateLimitedProducts(newProducts: List<ProductsItem>) {
|
||||
val diffCallback = ProductDiffCallback(products, newProducts)
|
||||
val limitedProducts = newProducts.take(10) // Limit to 10 items
|
||||
val diffResult = DiffUtil.calculateDiff(diffCallback)
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
updateProducts(limitedProducts)
|
||||
}
|
||||
|
||||
class ProductDiffCallback(
|
||||
private val oldList: List<ProductsItem>,
|
||||
private val newList: List<ProductsItem>
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun getOldListSize() = oldList.size
|
||||
override fun getNewListSize() = newList.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||
oldList[oldItemPosition].id == newList[newItemPosition].id // Compare unique IDs
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||
oldList[oldItemPosition] == newList[newItemPosition] // Compare entire object
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
package com.alya.ecommerce_serang.ui.product
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
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.data.api.response.Product
|
||||
import com.alya.ecommerce_serang.data.api.response.ReviewsItem
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||
import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding
|
||||
import com.alya.ecommerce_serang.ui.home.HorizontalProductAdapter
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import com.bumptech.glide.Glide
|
||||
|
||||
class DetailProductActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityDetailProductBinding
|
||||
private lateinit var apiService: ApiService
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private var productAdapter: HorizontalProductAdapter? = null
|
||||
private var reviewsAdapter: ReviewsAdapter? = null
|
||||
|
||||
private val viewModel: ProductViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val productRepository = ProductRepository(apiService)
|
||||
ProductViewModel(productRepository)
|
||||
}
|
||||
}
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityDetailProductBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
sessionManager = SessionManager(this)
|
||||
apiService = ApiConfig.getApiService(sessionManager)
|
||||
|
||||
val productId = intent.getIntExtra("PRODUCT_ID", -1)
|
||||
//nanti tambah get store id dari HomeFragment Product.storeId
|
||||
if (productId == -1) {
|
||||
Log.e("DetailProductActivity", "Invalid Product ID")
|
||||
finish() // Close activity if no valid ID
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.loadProductDetail(productId)
|
||||
viewModel.loadReviews(productId)
|
||||
|
||||
viewModel.productDetail.observe(this) { product ->
|
||||
if (product != null) {
|
||||
Log.d("ProductDetail", "Name: ${product.productName}, Price: ${product.price}")
|
||||
// Update UI here, e.g., show in a TextView or ImageView
|
||||
viewModel.loadProductDetail(productId)
|
||||
|
||||
} else {
|
||||
Log.e("ProductDetail", "Failed to fetch product details")
|
||||
}
|
||||
}
|
||||
observeProductDetail()
|
||||
observeProductReviews()
|
||||
}
|
||||
private fun observeProductDetail() {
|
||||
viewModel.productDetail.observe(this) { product ->
|
||||
product?.let { updateUI(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeProductReviews() {
|
||||
viewModel.reviewProduct.observe(this) { reviews ->
|
||||
setupRecyclerViewReviewsProduct(reviews)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateUI(product: Product){
|
||||
binding.tvProductName.text = product.productName
|
||||
binding.tvPrice.text = product.price
|
||||
binding.tvSold.text = product.totalSold.toString()
|
||||
binding.tvRating.text = product.rating
|
||||
binding.tvWeight.text = product.weight.toString()
|
||||
binding.tvStock.text = product.stock.toString()
|
||||
binding.tvCategory.text = product.productCategory
|
||||
binding.tvDescription.text = product.description
|
||||
binding.tvSellerName.text = product.storeId.toString()
|
||||
|
||||
binding.tvViewAllReviews.setOnClickListener{
|
||||
handleAllReviewsClick(product.productId)
|
||||
}
|
||||
|
||||
val fullImageUrl = when (val img = product.image) {
|
||||
is String -> {
|
||||
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
|
||||
}
|
||||
else -> R.drawable.placeholder_image // Default image for null
|
||||
}
|
||||
Log.d("ProductAdapter", "Loading image: $fullImageUrl")
|
||||
|
||||
Glide.with(this)
|
||||
.load(fullImageUrl)
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.into(binding.ivProductImage)
|
||||
|
||||
setupRecyclerViewOtherProducts()
|
||||
}
|
||||
|
||||
private fun handleAllReviewsClick(productId: Int) {
|
||||
val intent = Intent(this, ReviewProductActivity::class.java)
|
||||
intent.putExtra("PRODUCT_ID", productId) // Pass product ID
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun setupRecyclerViewOtherProducts(){
|
||||
productAdapter = HorizontalProductAdapter(
|
||||
products = emptyList(),
|
||||
onClick = { productsItem -> handleProductClick(productsItem) }
|
||||
)
|
||||
|
||||
binding.recyclerViewOtherProducts.apply {
|
||||
adapter = productAdapter
|
||||
layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupRecyclerViewReviewsProduct(reviewList: List<ReviewsItem>){
|
||||
val limitedReviewList = if (reviewList.isNotEmpty()) listOf(reviewList.first()) else emptyList()
|
||||
|
||||
reviewsAdapter = ReviewsAdapter(
|
||||
reviewList = limitedReviewList
|
||||
)
|
||||
|
||||
binding.recyclerViewReviews.apply {
|
||||
adapter = reviewsAdapter
|
||||
layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleProductClick(product: ProductsItem) {
|
||||
val intent = Intent(this, DetailProductActivity::class.java)
|
||||
intent.putExtra("PRODUCT_ID", product.id) // Pass product ID
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.alya.ecommerce_serang.ui.product
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.response.Product
|
||||
import com.alya.ecommerce_serang.data.api.response.ReviewsItem
|
||||
import com.alya.ecommerce_serang.data.api.response.Store
|
||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ProductViewModel(private val repository: ProductRepository) : ViewModel() {
|
||||
|
||||
private val _productDetail = MutableLiveData<Product?>()
|
||||
val productDetail: LiveData<Product?> get() = _productDetail
|
||||
|
||||
private val _storeDetail = MutableLiveData<Store?>()
|
||||
val storeDetail : LiveData<Store?> get() = _storeDetail
|
||||
|
||||
private val _reviewProduct = MutableLiveData<List<ReviewsItem>>()
|
||||
val reviewProduct: LiveData<List<ReviewsItem>> get() = _reviewProduct
|
||||
|
||||
fun loadProductDetail(productId: Int) {
|
||||
viewModelScope.launch {
|
||||
val result = repository.fetchProductDetail(productId)
|
||||
_productDetail.value = result?.product
|
||||
}
|
||||
}
|
||||
|
||||
fun loadReviews(productId: Int) {
|
||||
viewModelScope.launch {
|
||||
val reviews = repository.fetchProductReview(productId)
|
||||
_reviewProduct.value = reviews ?: emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fun loadStoreDetail(storeId: Int){
|
||||
// viewModelScope.launch {
|
||||
// val storeResult = repository.fetchStoreDetail(storeId)
|
||||
// _storeDetail.value = storeResult
|
||||
// }
|
||||
// }
|
@ -0,0 +1,79 @@
|
||||
package com.alya.ecommerce_serang.ui.product
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||
import com.alya.ecommerce_serang.databinding.ActivityReviewProductBinding
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
|
||||
class ReviewProductActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityReviewProductBinding
|
||||
private lateinit var apiService: ApiService
|
||||
private var reviewsAdapter: ReviewsAdapter? = null
|
||||
private lateinit var sessionManager: SessionManager
|
||||
private val viewModel: ProductViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val productRepository = ProductRepository(apiService)
|
||||
ProductViewModel(productRepository)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityReviewProductBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
enableEdgeToEdge()
|
||||
|
||||
sessionManager = SessionManager(this)
|
||||
apiService = ApiConfig.getApiService(sessionManager)
|
||||
|
||||
val productId = intent.getIntExtra("PRODUCT_ID", -1) // Get the product ID
|
||||
if (productId == -1) {
|
||||
Log.e("ReviewProductActivity", "Invalid Product ID")
|
||||
finish() // Close the activity if the product ID is invalid
|
||||
return
|
||||
}
|
||||
|
||||
setupRecyclerView()
|
||||
viewModel.loadReviews(productId) // Fetch reviews using productId
|
||||
|
||||
observeReviews() // Observe review data
|
||||
}
|
||||
|
||||
private fun observeReviews() {
|
||||
viewModel.reviewProduct.observe(this) { reviews ->
|
||||
if (reviews.isNotEmpty()) {
|
||||
reviewsAdapter?.setReviews(reviews)
|
||||
binding.tvNoReviews.visibility = View.GONE
|
||||
} else {
|
||||
binding.tvNoReviews.visibility = View.VISIBLE // Show "No Reviews" message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
reviewsAdapter = ReviewsAdapter(
|
||||
reviewList = emptyList()
|
||||
)
|
||||
|
||||
binding.rvReviewsProduct.apply {
|
||||
adapter = reviewsAdapter
|
||||
layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.VERTICAL,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package com.alya.ecommerce_serang.ui.product
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.response.ReviewsItem
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
class ReviewsAdapter(
|
||||
private var reviewList: List<ReviewsItem>
|
||||
) : RecyclerView.Adapter<ReviewsAdapter.ReviewViewHolder>() {
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReviewViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_review, parent, false)
|
||||
return ReviewViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ReviewViewHolder, position: Int) {
|
||||
val review = reviewList[position]
|
||||
|
||||
with(holder) {
|
||||
tvReviewerName.text = review.username
|
||||
tvReviewRating.text = review.rating.toString()
|
||||
tvReviewText.text = review.reviewText
|
||||
tvDateReview.text = formatDate(review.reviewDate)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = reviewList.size
|
||||
|
||||
fun setReviews(reviews: List<ReviewsItem>) {
|
||||
reviewList = reviews
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun formatDate(dateString: String): String {
|
||||
return try {
|
||||
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()) //from json
|
||||
inputFormat.timeZone = TimeZone.getTimeZone("UTC") //get timezone
|
||||
val outputFormat = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()) // new format
|
||||
val date = inputFormat.parse(dateString) // Parse from json format
|
||||
outputFormat.format(date!!) // convert to new format
|
||||
} catch (e: Exception) {
|
||||
dateString // Return original if error occurs
|
||||
}
|
||||
}
|
||||
|
||||
class ReviewViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val tvReviewRating: TextView = itemView.findViewById(R.id.tvReviewRating)
|
||||
val tvReviewerName: TextView = itemView.findViewById(R.id.tvUsername)
|
||||
val tvReviewText: TextView = itemView.findViewById(R.id.tvReviewText)
|
||||
val tvDateReview: TextView = itemView.findViewById(R.id.date_review)
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package com.alya.ecommerce_serang.ui.profile
|
||||
|
||||
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.dto.UserProfile
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||
import com.alya.ecommerce_serang.databinding.ActivityDetailProfileBinding
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import com.bumptech.glide.Glide
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
class DetailProfileActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityDetailProfileBinding
|
||||
private lateinit var apiService: ApiService
|
||||
private lateinit var sessionManager: SessionManager
|
||||
|
||||
private val viewModel: ProfileViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val userRepository = UserRepository(apiService)
|
||||
ProfileViewModel(userRepository)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityDetailProfileBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
sessionManager = SessionManager(this)
|
||||
apiService = ApiConfig.getApiService(sessionManager)
|
||||
|
||||
enableEdgeToEdge()
|
||||
// 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
|
||||
// }
|
||||
|
||||
viewModel.loadUserProfile()
|
||||
|
||||
viewModel.userProfile.observe(this){ user ->
|
||||
user?.let { updateProfile(it) }
|
||||
}
|
||||
|
||||
viewModel.errorMessage.observe(this) { error ->
|
||||
Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateProfile(user: UserProfile){
|
||||
|
||||
binding.tvNameUser.setText(user.name.toString())
|
||||
binding.tvUsername.setText(user.username)
|
||||
binding.tvEmailUser.setText(user.email)
|
||||
binding.tvDateBirth.setText(formatDate(user.birthDate))
|
||||
binding.tvNumberPhoneUser.setText(user.phone)
|
||||
|
||||
if (user.image != null && user.image is String) {
|
||||
Glide.with(this)
|
||||
.load(user.image)
|
||||
.into(binding.profileImage)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatDate(dateString: String): String {
|
||||
return try {
|
||||
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()) //from json
|
||||
inputFormat.timeZone = TimeZone.getTimeZone("UTC") //get timezone
|
||||
val outputFormat = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault()) // new format
|
||||
val date = inputFormat.parse(dateString) // Parse from json format
|
||||
outputFormat.format(date!!) // convert to new format
|
||||
} catch (e: Exception) {
|
||||
dateString // Return original if error occurs
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +1,95 @@
|
||||
package com.alya.ecommerce_serang.ui.profile
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
||||
import com.alya.ecommerce_serang.R
|
||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||
import com.alya.ecommerce_serang.databinding.FragmentProfileBinding
|
||||
import com.alya.ecommerce_serang.ui.profile.mystore.TokoSayaActivity
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import com.bumptech.glide.Glide
|
||||
|
||||
class ProfileFragment : Fragment() {
|
||||
|
||||
companion object {
|
||||
fun newInstance() = ProfileFragment()
|
||||
}
|
||||
private var _binding: FragmentProfileBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private lateinit var sessionManager: SessionManager
|
||||
|
||||
private val viewModel: ProfileViewModel by viewModels()
|
||||
private val viewModel: ProfileViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val userRepository = UserRepository(apiService)
|
||||
ProfileViewModel(userRepository)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
sessionManager = SessionManager(requireContext())
|
||||
|
||||
// TODO: Use the ViewModel
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return inflater.inflate(R.layout.fragment_profile, container, false)
|
||||
_binding = FragmentProfileBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
observeUserProfile()
|
||||
viewModel.loadUserProfile()
|
||||
|
||||
binding.cardBukaToko.setOnClickListener{
|
||||
val intentBuka = Intent(requireContext(), TokoSayaActivity::class.java)
|
||||
startActivity(intentBuka)
|
||||
}
|
||||
|
||||
binding.btnDetailProfile.setOnClickListener{
|
||||
val intentDetail = Intent(requireContext(), DetailProfileActivity::class.java)
|
||||
startActivity(intentDetail)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeUserProfile() {
|
||||
viewModel.userProfile.observe(viewLifecycleOwner) { user ->
|
||||
user?.let { updateUI(it) }
|
||||
}
|
||||
viewModel.errorMessage.observe(viewLifecycleOwner) { errorMessage ->
|
||||
Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateUI(user: UserProfile) = with(binding){
|
||||
val fullImageUrl = when (val img = user.image) {
|
||||
is String -> {
|
||||
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
|
||||
}
|
||||
else -> R.drawable.placeholder_image // Default image for null
|
||||
}
|
||||
|
||||
Log.d("ProductAdapter", "Loading image: $fullImageUrl")
|
||||
|
||||
tvName.text = user.name.toString()
|
||||
tvUsername.text = user.username.toString()
|
||||
|
||||
// Load image using Glide
|
||||
Glide.with(requireContext())
|
||||
.load(fullImageUrl)
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.into(profileImage)
|
||||
}
|
||||
}
|
@ -1,7 +1,28 @@
|
||||
package com.alya.ecommerce_serang.ui.profile
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ProfileViewModel : ViewModel() {
|
||||
// TODO: Implement the ViewModel
|
||||
class ProfileViewModel(private val userRepository: UserRepository) : ViewModel() {
|
||||
private val _userProfile = MutableLiveData<UserProfile?>()
|
||||
val userProfile: LiveData<UserProfile?> = _userProfile
|
||||
|
||||
private val _errorMessage = MutableLiveData<String>()
|
||||
val errorMessage : LiveData<String> = _errorMessage
|
||||
|
||||
fun loadUserProfile(){
|
||||
viewModelScope.launch {
|
||||
when (val result = userRepository.fetchUserProfile()){
|
||||
is Result.Success -> _userProfile.postValue(result.data)
|
||||
is Result.Error -> _errorMessage.postValue(result.exception.message ?: "Unknown Error")
|
||||
is Result.Loading -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
package com.alya.ecommerce_serang.utils
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.alya.ecommerce_serang.data.api.dto.Category
|
||||
import com.alya.ecommerce_serang.data.api.dto.CategoryItem
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class ProductQuery (
|
||||
val category: Category? = null,
|
||||
val category: CategoryItem,
|
||||
val search:String? = null,
|
||||
val range:Pair<Float,Float> = 0f to 10000f,
|
||||
val rating:Int? = null,
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user