mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-13 10:42:21 +00:00
add chat list (miss image, attach product in chat and date chat)
This commit is contained in:
@ -0,0 +1,42 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response.chat
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class ChatListResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("chat")
|
||||||
|
val chat: List<ChatItemList>,
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ChatItemList(
|
||||||
|
|
||||||
|
@field:SerializedName("store_id")
|
||||||
|
val storeId: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("user_id")
|
||||||
|
val userId: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("user_image")
|
||||||
|
val userImage: String? = null,
|
||||||
|
|
||||||
|
@field:SerializedName("user_name")
|
||||||
|
val userName: String,
|
||||||
|
|
||||||
|
@field:SerializedName("chat_room_id")
|
||||||
|
val chatRoomId: Int,
|
||||||
|
|
||||||
|
@field:SerializedName("latest_message_time")
|
||||||
|
val latestMessageTime: String,
|
||||||
|
|
||||||
|
@field:SerializedName("store_name")
|
||||||
|
val storeName: String,
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String,
|
||||||
|
|
||||||
|
@field:SerializedName("store_image")
|
||||||
|
val storeImage: String? = null
|
||||||
|
)
|
@ -1,5 +1,6 @@
|
|||||||
package com.alya.ecommerce_serang.data.api.retrofit
|
package com.alya.ecommerce_serang.data.api.retrofit
|
||||||
|
|
||||||
|
|
||||||
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceRequest
|
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CartItem
|
import com.alya.ecommerce_serang.data.api.dto.CartItem
|
||||||
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
|
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
|
||||||
@ -12,24 +13,17 @@ 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.RegisterRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.SearchRequest
|
import com.alya.ecommerce_serang.data.api.dto.SearchRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
|
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
|
||||||
import com.alya.ecommerce_serang.data.api.response.store.product.CreateProductResponse
|
|
||||||
import com.alya.ecommerce_serang.data.api.response.store.product.ViewStoreProductsResponse
|
|
||||||
import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest
|
import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest
|
||||||
|
|
||||||
import okhttp3.MultipartBody
|
|
||||||
import okhttp3.RequestBody
|
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.RegisterResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.chat.ChatHistoryResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.chat.ChatListResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.chat.SendChatResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.chat.UpdateChatResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.AddCartResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.cart.AddCartResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.ListCartResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.cart.ListCartResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.cart.UpdateCartResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.cart.UpdateCartResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.chat.ChatHistoryResponse
|
|
||||||
import com.alya.ecommerce_serang.data.api.response.chat.SendChatResponse
|
|
||||||
import com.alya.ecommerce_serang.data.api.response.chat.UpdateChatResponse
|
|
||||||
import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse
|
|
||||||
import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse
|
|
||||||
import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse
|
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateOrderResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateOrderResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.order.ListCityResponse
|
||||||
@ -45,21 +39,22 @@ import com.alya.ecommerce_serang.data.api.response.customer.product.StoreRespons
|
|||||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.ProfileResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.profile.ProfileResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.product.CreateSearchResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.product.SearchHistoryResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.store.product.CreateProductResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.store.product.DeleteProductResponse
|
import com.alya.ecommerce_serang.data.api.response.store.product.DeleteProductResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.store.product.UpdateProductResponse
|
import com.alya.ecommerce_serang.data.api.response.store.product.UpdateProductResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.product.SearchHistoryResponse
|
import com.alya.ecommerce_serang.data.api.response.store.product.ViewStoreProductsResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.product.CreateSearchResponse
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.DELETE
|
import retrofit2.http.DELETE
|
||||||
import retrofit2.http.Field
|
|
||||||
import retrofit2.http.FormUrlEncoded
|
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
|
||||||
import retrofit2.http.HeaderMap
|
|
||||||
import retrofit2.http.Multipart
|
import retrofit2.http.Multipart
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
import retrofit2.http.PUT
|
import retrofit2.http.PUT
|
||||||
@ -250,11 +245,10 @@ interface ApiService {
|
|||||||
suspend fun sendChatLine(
|
suspend fun sendChatLine(
|
||||||
@Part("store_id") storeId: RequestBody,
|
@Part("store_id") storeId: RequestBody,
|
||||||
@Part("message") message: RequestBody,
|
@Part("message") message: RequestBody,
|
||||||
@Part("product_id") productId: RequestBody,
|
@Part("product_id") productId: RequestBody?,
|
||||||
@Part chatimg: MultipartBody.Part?
|
@Part chatimg: MultipartBody.Part?
|
||||||
): Response<SendChatResponse>
|
): Response<SendChatResponse>
|
||||||
|
|
||||||
|
|
||||||
@PUT("chatstatus")
|
@PUT("chatstatus")
|
||||||
suspend fun updateChatStatus(
|
suspend fun updateChatStatus(
|
||||||
@Body request: UpdateChatRequest
|
@Body request: UpdateChatRequest
|
||||||
@ -264,4 +258,8 @@ interface ApiService {
|
|||||||
suspend fun getChatDetail(
|
suspend fun getChatDetail(
|
||||||
@Path("chatRoomId") chatRoomId: Int
|
@Path("chatRoomId") chatRoomId: Int
|
||||||
): Response<ChatHistoryResponse>
|
): Response<ChatHistoryResponse>
|
||||||
|
|
||||||
|
@GET("chat")
|
||||||
|
suspend fun getChatList(
|
||||||
|
): Response<ChatListResponse>
|
||||||
}
|
}
|
@ -4,12 +4,14 @@ import android.util.Log
|
|||||||
import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest
|
import com.alya.ecommerce_serang.data.api.dto.UpdateChatRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||||
import com.alya.ecommerce_serang.data.api.response.chat.ChatHistoryResponse
|
import com.alya.ecommerce_serang.data.api.response.chat.ChatHistoryResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.chat.ChatItemList
|
||||||
import com.alya.ecommerce_serang.data.api.response.chat.SendChatResponse
|
import com.alya.ecommerce_serang.data.api.response.chat.SendChatResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.chat.UpdateChatResponse
|
import com.alya.ecommerce_serang.data.api.response.chat.UpdateChatResponse
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -36,55 +38,56 @@ class ChatRepository @Inject constructor(
|
|||||||
suspend fun sendChatMessage(
|
suspend fun sendChatMessage(
|
||||||
storeId: Int,
|
storeId: Int,
|
||||||
message: String,
|
message: String,
|
||||||
productId: Int,
|
productId: Int? = null,
|
||||||
imageFile: File? = null
|
imageFile: File? = null,
|
||||||
|
chatRoomId: Int? = null // Not used in the actual API call but kept for compatibility
|
||||||
): Result<SendChatResponse> {
|
): Result<SendChatResponse> {
|
||||||
return try {
|
return try {
|
||||||
// Create request bodies for text fields
|
// Create multipart request parts
|
||||||
val storeIdBody = RequestBody.create("text/plain".toMediaTypeOrNull(), storeId.toString())
|
val storeIdPart = storeId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
val messageBody = RequestBody.create("text/plain".toMediaTypeOrNull(), message)
|
val messagePart = message.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
val productIdBody = RequestBody.create("text/plain".toMediaTypeOrNull(), productId.toString())
|
|
||||||
|
|
||||||
// Create multipart body for the image file
|
// Add product ID part if provided
|
||||||
val imageMultipart = if (imageFile != null && imageFile.exists()) {
|
val productIdPart = if (productId != null && productId > 0) {
|
||||||
// Log detailed file information
|
productId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
Log.d(TAG, "Image file: ${imageFile.absolutePath}")
|
|
||||||
Log.d(TAG, "Image file size: ${imageFile.length()} bytes")
|
|
||||||
Log.d(TAG, "Image file exists: ${imageFile.exists()}")
|
|
||||||
Log.d(TAG, "Image file can read: ${imageFile.canRead()}")
|
|
||||||
|
|
||||||
val requestFile = RequestBody.create("image/*".toMediaTypeOrNull(), imageFile)
|
|
||||||
MultipartBody.Part.createFormData("chatimg", imageFile.name, requestFile)
|
|
||||||
} else {
|
} else {
|
||||||
// Pass null when no image is provided
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log request info
|
// Create image part if file is provided
|
||||||
Log.d(TAG, "Sending message to store ID: $storeId, product ID: $productId")
|
val imagePart = if (imageFile != null && imageFile.exists()) {
|
||||||
Log.d(TAG, "Message content: $message")
|
val requestFile = imageFile.asRequestBody("image/*".toMediaTypeOrNull())
|
||||||
Log.d(TAG, "Has image: ${imageFile != null && imageFile.exists()}")
|
MultipartBody.Part.createFormData("chatimg", imageFile.name, requestFile)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
// Make the API call
|
// Debug log the request parameters
|
||||||
|
Log.d("ChatRepository", "Sending chat with: storeId=$storeId, productId=$productId, " +
|
||||||
|
"message length=${message.length}, hasImage=${imageFile != null}")
|
||||||
|
|
||||||
|
// Make API call using your actual endpoint and parameter names
|
||||||
val response = apiService.sendChatLine(
|
val response = apiService.sendChatLine(
|
||||||
storeId = storeIdBody,
|
storeId = storeIdPart,
|
||||||
message = messageBody,
|
message = messagePart,
|
||||||
productId = productIdBody,
|
productId = productIdPart,
|
||||||
chatimg = imageMultipart
|
chatimg = imagePart
|
||||||
)
|
)
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
response.body()?.let {
|
val body = response.body()
|
||||||
Result.Success(it)
|
if (body != null) {
|
||||||
} ?: Result.Error(Exception("Send chat response is empty"))
|
Result.Success(body)
|
||||||
|
} else {
|
||||||
|
Result.Error(Exception("Empty response body"))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val errorBody = response.errorBody()?.string() ?: "Unknown error"
|
val errorBody = response.errorBody()?.string() ?: "{}"
|
||||||
Log.e(TAG, "HTTP Error: ${response.code()}, Body: $errorBody")
|
Log.e("ChatRepository", "API Error: ${response.code()} - $errorBody")
|
||||||
Result.Error(Exception("API Error: ${response.code()} - $errorBody"))
|
Result.Error(Exception("API Error: ${response.code()} - $errorBody"))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Exception sending message", e)
|
Log.e("ChatRepository", "Exception sending message", e)
|
||||||
e.printStackTrace()
|
|
||||||
Result.Error(e)
|
Result.Error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,4 +131,19 @@ class ChatRepository @Inject constructor(
|
|||||||
Result.Error(e)
|
Result.Error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getListChat(): Result<List<ChatItemList>> {
|
||||||
|
return try {
|
||||||
|
val response = apiService.getChatList()
|
||||||
|
|
||||||
|
if (response.isSuccessful){
|
||||||
|
val chat = response.body()?.chat ?: emptyList()
|
||||||
|
Result.Success(chat)
|
||||||
|
} else {
|
||||||
|
Result.Error(Exception("Failed to fetch categories. Code: ${response.code()}"))
|
||||||
|
}
|
||||||
|
} catch (e: Exception){
|
||||||
|
Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -100,7 +100,6 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
apiService = ApiConfig.getApiService(sessionManager)
|
apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
|
||||||
Log.d("ChatActivity", "Token in storage: '${sessionManager.getToken()}'")
|
Log.d("ChatActivity", "Token in storage: '${sessionManager.getToken()}'")
|
||||||
// Log.d("ChatActivity", "User ID in storage: '${sessionManager.getUserId()}'")
|
|
||||||
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
@ -125,7 +124,7 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
val productImage = intent.getStringExtra(Constants.EXTRA_PRODUCT_IMAGE) ?: ""
|
val productImage = intent.getStringExtra(Constants.EXTRA_PRODUCT_IMAGE) ?: ""
|
||||||
val productRating = intent.getFloatExtra(Constants.EXTRA_PRODUCT_RATING, 0f)
|
val productRating = intent.getFloatExtra(Constants.EXTRA_PRODUCT_RATING, 0f)
|
||||||
val storeName = intent.getStringExtra(Constants.EXTRA_STORE_NAME) ?: ""
|
val storeName = intent.getStringExtra(Constants.EXTRA_STORE_NAME) ?: ""
|
||||||
|
val chatRoomId = intent.getIntExtra(Constants.EXTRA_CHAT_ROOM_ID, 0)
|
||||||
|
|
||||||
// Check if user is logged in
|
// Check if user is logged in
|
||||||
val token = sessionManager.getToken()
|
val token = sessionManager.getToken()
|
||||||
@ -148,13 +147,18 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
productRating = productRating,
|
productRating = productRating,
|
||||||
storeName = storeName
|
storeName = storeName
|
||||||
)
|
)
|
||||||
|
|
||||||
// Setup UI components
|
// Setup UI components
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setupListeners()
|
setupListeners()
|
||||||
setupTypingIndicator()
|
setupTypingIndicator()
|
||||||
observeViewModel()
|
observeViewModel()
|
||||||
|
|
||||||
|
// If opened from ChatListFragment with a valid chatRoomId
|
||||||
|
if (chatRoomId > 0) {
|
||||||
|
// Directly set the chatRoomId and load chat history
|
||||||
|
viewModel._chatRoomId.value = chatRoomId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
@ -234,22 +238,31 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update product info
|
// Update product info
|
||||||
binding.tvProductName.text = state.productName
|
if (!state.productName.isNullOrEmpty()) {
|
||||||
binding.tvProductPrice.text = state.productPrice
|
binding.tvProductName.text = state.productName
|
||||||
binding.ratingBar.rating = state.productRating
|
binding.tvProductPrice.text = state.productPrice
|
||||||
binding.tvRating.text = state.productRating.toString()
|
binding.ratingBar.rating = state.productRating
|
||||||
binding.tvSellerName.text = state.storeName
|
binding.tvRating.text = state.productRating.toString()
|
||||||
|
binding.tvSellerName.text = state.storeName
|
||||||
|
|
||||||
// Load product image
|
// Load product image
|
||||||
if (state.productImageUrl.isNotEmpty()) {
|
if (!state.productImageUrl.isNullOrEmpty()) {
|
||||||
Glide.with(this@ChatActivity)
|
Glide.with(this@ChatActivity)
|
||||||
.load(BASE_URL + state.productImageUrl)
|
.load(BASE_URL + state.productImageUrl)
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.placeholder(R.drawable.placeholder_image)
|
.placeholder(R.drawable.placeholder_image)
|
||||||
.error(R.drawable.placeholder_image)
|
.error(R.drawable.placeholder_image)
|
||||||
.into(binding.imgProduct)
|
.into(binding.imgProduct)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the product section is visible
|
||||||
|
binding.productContainer.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
// Hide the product section if info is missing
|
||||||
|
binding.productContainer.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Update attachment hint
|
// Update attachment hint
|
||||||
if (state.hasAttachment) {
|
if (state.hasAttachment) {
|
||||||
binding.editTextMessage.hint = getString(R.string.image_attached)
|
binding.editTextMessage.hint = getString(R.string.image_attached)
|
||||||
@ -352,17 +365,70 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSelectedImage(uri: Uri) {
|
private fun handleSelectedImage(uri: Uri) {
|
||||||
// Get the file from Uri
|
try {
|
||||||
val filePathColumn = arrayOf(MediaStore.Images.Media.DATA)
|
Log.d(TAG, "Processing selected image: $uri")
|
||||||
val cursor = contentResolver.query(uri, filePathColumn, null, null, null)
|
|
||||||
cursor?.moveToFirst()
|
|
||||||
val columnIndex = cursor?.getColumnIndex(filePathColumn[0])
|
|
||||||
val filePath = cursor?.getString(columnIndex ?: 0)
|
|
||||||
cursor?.close()
|
|
||||||
|
|
||||||
if (filePath != null) {
|
// First try the direct approach to get the file path
|
||||||
viewModel.setSelectedImageFile(File(filePath))
|
var filePath: String? = null
|
||||||
Toast.makeText(this, R.string.image_selected, Toast.LENGTH_SHORT).show()
|
|
||||||
|
// For newer Android versions, we need to handle content URIs properly
|
||||||
|
if (uri.scheme == "content") {
|
||||||
|
val cursor = contentResolver.query(uri, null, null, null, null)
|
||||||
|
cursor?.use {
|
||||||
|
if (it.moveToFirst()) {
|
||||||
|
val columnIndex = it.getColumnIndex(MediaStore.Images.Media.DATA)
|
||||||
|
if (columnIndex != -1) {
|
||||||
|
filePath = it.getString(columnIndex)
|
||||||
|
Log.d(TAG, "Found file path from cursor: $filePath")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we couldn't get the path directly, create a copy in our cache directory
|
||||||
|
if (filePath == null) {
|
||||||
|
contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||||
|
val fileName = "img_${System.currentTimeMillis()}.jpg"
|
||||||
|
val outputFile = File(cacheDir, fileName)
|
||||||
|
|
||||||
|
outputFile.outputStream().use { outputStream ->
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath = outputFile.absolutePath
|
||||||
|
Log.d(TAG, "Created temp file from input stream: $filePath")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (uri.scheme == "file") {
|
||||||
|
// Direct file URI
|
||||||
|
filePath = uri.path
|
||||||
|
Log.d(TAG, "Got file path directly from URI: $filePath")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the file path
|
||||||
|
if (filePath != null) {
|
||||||
|
val file = File(filePath)
|
||||||
|
if (file.exists()) {
|
||||||
|
// Check file size (limit to 5MB)
|
||||||
|
if (file.length() > 5 * 1024 * 1024) {
|
||||||
|
Toast.makeText(this, "Image too large (max 5MB), please select a smaller image", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the file to the ViewModel
|
||||||
|
viewModel.setSelectedImageFile(file)
|
||||||
|
Toast.makeText(this, R.string.image_selected, Toast.LENGTH_SHORT).show()
|
||||||
|
Log.d(TAG, "Successfully set image file: ${file.absolutePath}, size: ${file.length()} bytes")
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "File does not exist: $filePath")
|
||||||
|
Toast.makeText(this, "Could not access the selected image", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Could not get file path from URI: $uri")
|
||||||
|
Toast.makeText(this, "Could not process the selected image", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error handling selected image", e)
|
||||||
|
Toast.makeText(this, "Error processing image: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,21 +461,32 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
fun createIntent(
|
fun createIntent(
|
||||||
context: Activity,
|
context: Activity,
|
||||||
storeId: Int,
|
storeId: Int,
|
||||||
productId: Int,
|
productId: Int = 0,
|
||||||
productName: String?,
|
productName: String? = null,
|
||||||
productPrice: String,
|
productPrice: String = "",
|
||||||
productImage: String?,
|
productImage: String? = null,
|
||||||
productRating: String?,
|
productRating: String? = null,
|
||||||
storeName: String?,
|
storeName: String? = null,
|
||||||
chatRoomId: Int = 0
|
chatRoomId: Int = 0
|
||||||
){
|
) {
|
||||||
val intent = Intent(context, ChatActivity::class.java).apply {
|
val intent = Intent(context, ChatActivity::class.java).apply {
|
||||||
putExtra(Constants.EXTRA_STORE_ID, storeId)
|
putExtra(Constants.EXTRA_STORE_ID, storeId)
|
||||||
putExtra(Constants.EXTRA_PRODUCT_ID, productId)
|
putExtra(Constants.EXTRA_PRODUCT_ID, productId)
|
||||||
putExtra(Constants.EXTRA_PRODUCT_NAME, productName)
|
putExtra(Constants.EXTRA_PRODUCT_NAME, productName)
|
||||||
putExtra(Constants.EXTRA_PRODUCT_PRICE, productPrice)
|
putExtra(Constants.EXTRA_PRODUCT_PRICE, productPrice)
|
||||||
putExtra(Constants.EXTRA_PRODUCT_IMAGE, productImage)
|
putExtra(Constants.EXTRA_PRODUCT_IMAGE, productImage)
|
||||||
putExtra(Constants.EXTRA_PRODUCT_RATING, productRating)
|
|
||||||
|
// Convert productRating string to float if provided
|
||||||
|
if (productRating != null) {
|
||||||
|
try {
|
||||||
|
putExtra(Constants.EXTRA_PRODUCT_RATING, productRating.toFloat())
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
putExtra(Constants.EXTRA_PRODUCT_RATING, 0f)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
putExtra(Constants.EXTRA_PRODUCT_RATING, 0f)
|
||||||
|
}
|
||||||
|
|
||||||
putExtra(Constants.EXTRA_STORE_NAME, storeName)
|
putExtra(Constants.EXTRA_STORE_NAME, storeName)
|
||||||
|
|
||||||
if (chatRoomId > 0) {
|
if (chatRoomId > 0) {
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.chat
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.chat.ChatItemList
|
||||||
|
import com.alya.ecommerce_serang.databinding.ItemChatBinding
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
class ChatListAdapter(
|
||||||
|
private val chatList: List<ChatItemList>,
|
||||||
|
private val onClick: (ChatItemList) -> Unit
|
||||||
|
) : RecyclerView.Adapter<ChatListAdapter.ChatViewHolder>() {
|
||||||
|
|
||||||
|
inner class ChatViewHolder(private val binding: ItemChatBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
fun bind(chat: ChatItemList) {
|
||||||
|
binding.txtStoreName.text = chat.storeName
|
||||||
|
binding.txtMessage.text = chat.message
|
||||||
|
binding.txtTime.text = formatTime(chat.latestMessageTime)
|
||||||
|
|
||||||
|
// Process image URL properly
|
||||||
|
val imageUrl = chat.storeImage?.let {
|
||||||
|
if (it.startsWith("/")) BASE_URL + it else it
|
||||||
|
}
|
||||||
|
|
||||||
|
Glide.with(binding.imgStore.context)
|
||||||
|
.load(imageUrl)
|
||||||
|
.placeholder(R.drawable.ic_person)
|
||||||
|
.error(R.drawable.placeholder_image)
|
||||||
|
.into(binding.imgStore)
|
||||||
|
|
||||||
|
// Handle click event
|
||||||
|
binding.root.setOnClickListener {
|
||||||
|
onClick(chat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatTime(isoTime: String): String {
|
||||||
|
return try {
|
||||||
|
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
|
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
val date = inputFormat.parse(isoTime)
|
||||||
|
|
||||||
|
val outputFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
|
||||||
|
outputFormat.format(date ?: Date())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChatViewHolder {
|
||||||
|
val binding = ItemChatBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return ChatViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = chatList.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ChatViewHolder, position: Int) {
|
||||||
|
holder.bind(chatList[position])
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,15 @@
|
|||||||
package com.alya.ecommerce_serang.ui.chat
|
package com.alya.ecommerce_serang.ui.chat
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
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 android.widget.Toast
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
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.ChatRepository
|
import com.alya.ecommerce_serang.data.repository.ChatRepository
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
import com.alya.ecommerce_serang.databinding.FragmentChatListBinding
|
import com.alya.ecommerce_serang.databinding.FragmentChatListBinding
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
@ -30,6 +31,7 @@ class ChatListFragment : Fragment() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
sessionManager = SessionManager(requireContext())
|
sessionManager = SessionManager(requireContext())
|
||||||
|
socketService = SocketIOService(sessionManager)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,13 +46,43 @@ class ChatListFragment : Fragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
setupView()
|
viewModel.getChatList()
|
||||||
|
observeChatList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupView(){
|
private fun observeChatList() {
|
||||||
binding.btnTrial.setOnClickListener{
|
viewModel.chatList.observe(viewLifecycleOwner) { result ->
|
||||||
val intent = Intent(requireContext(), ChatActivity::class.java)
|
when (result) {
|
||||||
startActivity(intent)
|
is Result.Success -> {
|
||||||
|
val adapter = ChatListAdapter(result.data) { chatItem ->
|
||||||
|
// Use the ChatActivity.createIntent factory method for proper navigation
|
||||||
|
ChatActivity.createIntent(
|
||||||
|
context = requireActivity(),
|
||||||
|
storeId = chatItem.storeId,
|
||||||
|
productId = 0, // Default value since we don't have it in ChatListItem
|
||||||
|
productName = null, // Null is acceptable as per ChatActivity
|
||||||
|
productPrice = "",
|
||||||
|
productImage = null,
|
||||||
|
productRating = null,
|
||||||
|
storeName = chatItem.storeName,
|
||||||
|
chatRoomId = chatItem.chatRoomId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
binding.chatListRecyclerView.adapter = adapter
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
Toast.makeText(requireContext(), "Failed to load chats", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
Result.Loading -> {
|
||||||
|
// Optional: show progress bar
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
}
|
}
|
@ -6,6 +6,7 @@ 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.chat.ChatItem
|
import com.alya.ecommerce_serang.data.api.response.chat.ChatItem
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.chat.ChatItemList
|
||||||
import com.alya.ecommerce_serang.data.api.response.chat.ChatLine
|
import com.alya.ecommerce_serang.data.api.response.chat.ChatLine
|
||||||
import com.alya.ecommerce_serang.data.repository.ChatRepository
|
import com.alya.ecommerce_serang.data.repository.ChatRepository
|
||||||
import com.alya.ecommerce_serang.data.repository.Result
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
@ -31,12 +32,15 @@ class ChatViewModel @Inject constructor(
|
|||||||
private val _state = MutableLiveData(ChatUiState())
|
private val _state = MutableLiveData(ChatUiState())
|
||||||
val state: LiveData<ChatUiState> = _state
|
val state: LiveData<ChatUiState> = _state
|
||||||
|
|
||||||
private val _chatRoomId = MutableLiveData<Int>(0)
|
val _chatRoomId = MutableLiveData<Int>(0)
|
||||||
val chatRoomId: LiveData<Int> = _chatRoomId
|
val chatRoomId: LiveData<Int> = _chatRoomId
|
||||||
|
|
||||||
|
private val _chatList = MutableLiveData<Result<List<ChatItemList>>>()
|
||||||
|
val chatList: LiveData<Result<List<ChatItemList>>> = _chatList
|
||||||
|
|
||||||
// Store and product parameters
|
// Store and product parameters
|
||||||
private var storeId: Int = 0
|
private var storeId: Int = 0
|
||||||
private var productId: Int = 0
|
private var productId: Int? = 0
|
||||||
private var currentUserId: Int? = null
|
private var currentUserId: Int? = null
|
||||||
private var defaultUserId: Int = 0
|
private var defaultUserId: Int = 0
|
||||||
|
|
||||||
@ -83,27 +87,27 @@ class ChatViewModel @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
fun setChatParameters(
|
fun setChatParameters(
|
||||||
storeId: Int,
|
storeId: Int,
|
||||||
productId: Int,
|
productId: Int? = 0,
|
||||||
productName: String,
|
productName: String? = null,
|
||||||
productPrice: String,
|
productPrice: String? = null,
|
||||||
productImage: String,
|
productImage: String? = null,
|
||||||
productRating: Float,
|
productRating: Float? = 0f,
|
||||||
storeName: String
|
storeName: String
|
||||||
) {
|
) {
|
||||||
this.storeId = storeId
|
this.storeId = storeId
|
||||||
this.productId = productId
|
this.productId = productId!!
|
||||||
this.productName = productName
|
this.productName = productName.toString()
|
||||||
this.productPrice = productPrice
|
this.productPrice = productPrice.toString()
|
||||||
this.productImage = productImage
|
this.productImage = productImage.toString()
|
||||||
this.productRating = productRating
|
this.productRating = productRating!!
|
||||||
this.storeName = storeName
|
this.storeName = storeName
|
||||||
|
|
||||||
// Update state with product info
|
// Update state with product info
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
productName = productName,
|
productName = productName.toString(),
|
||||||
productPrice = productPrice,
|
productPrice = productPrice.toString(),
|
||||||
productImageUrl = productImage,
|
productImageUrl = productImage.toString(),
|
||||||
productRating = productRating,
|
productRating = productRating,
|
||||||
storeName = storeName
|
storeName = storeName
|
||||||
)
|
)
|
||||||
@ -237,78 +241,113 @@ class ChatViewModel @Inject constructor(
|
|||||||
* Sends a chat message
|
* Sends a chat message
|
||||||
*/
|
*/
|
||||||
fun sendMessage(message: String) {
|
fun sendMessage(message: String) {
|
||||||
if (message.isBlank()) return
|
if (message.isBlank() && selectedImageFile == null) {
|
||||||
|
Log.e(TAG, "Cannot send message: Both message and image are empty")
|
||||||
if (storeId == 0 || productId == 0) {
|
|
||||||
Log.e(TAG, "Cannot send message: Store ID or Product ID is 0")
|
|
||||||
updateState { it.copy(error = "Cannot send message. Invalid parameters.") }
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if we have the necessary parameters
|
||||||
|
if (storeId <= 0) {
|
||||||
|
Log.e(TAG, "Cannot send message: Store ID is invalid")
|
||||||
|
updateState { it.copy(error = "Cannot send message. Invalid store ID.") }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the existing chatRoomId (not used in API but may be needed for Socket.IO)
|
||||||
|
val existingChatRoomId = _chatRoomId.value ?: 0
|
||||||
|
|
||||||
|
// Log debug information
|
||||||
|
Log.d(TAG, "Sending message with params: storeId=$storeId, productId=$productId")
|
||||||
|
Log.d(TAG, "Current user ID: $currentUserId")
|
||||||
|
Log.d(TAG, "Has attachment: ${selectedImageFile != null}")
|
||||||
|
|
||||||
|
// Check image file size if present
|
||||||
|
selectedImageFile?.let { file ->
|
||||||
|
if (file.exists() && file.length() > 5 * 1024 * 1024) { // 5MB limit
|
||||||
|
updateState { it.copy(error = "Image file is too large. Please select a smaller image.") }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
updateState { it.copy(isSending = true) }
|
updateState { it.copy(isSending = true) }
|
||||||
|
|
||||||
when (val result = chatRepository.sendChatMessage(
|
try {
|
||||||
storeId = storeId,
|
// Send the message using the repository
|
||||||
message = message,
|
// Note: We keep the chatRoomId parameter for compatibility with the repository method signature,
|
||||||
productId = productId,
|
// but it's not actually used in the API call
|
||||||
imageFile = selectedImageFile
|
val result = chatRepository.sendChatMessage(
|
||||||
)) {
|
storeId = storeId,
|
||||||
is Result.Success -> {
|
message = message,
|
||||||
// Add new message to the list
|
productId = productId,
|
||||||
val chatLine = result.data.chatLine
|
imageFile = selectedImageFile,
|
||||||
val newMessage = convertChatLineToUiMessage(chatLine)
|
chatRoomId = existingChatRoomId
|
||||||
|
)
|
||||||
|
|
||||||
val currentMessages = _state.value?.messages ?: listOf()
|
when (result) {
|
||||||
val updatedMessages = currentMessages.toMutableList().apply {
|
is Result.Success -> {
|
||||||
add(newMessage)
|
// Add new message to the list
|
||||||
|
val chatLine = result.data.chatLine
|
||||||
|
val newMessage = convertChatLineToUiMessage(chatLine)
|
||||||
|
|
||||||
|
val currentMessages = _state.value?.messages ?: listOf()
|
||||||
|
val updatedMessages = currentMessages.toMutableList().apply {
|
||||||
|
add(newMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateState {
|
||||||
|
it.copy(
|
||||||
|
messages = updatedMessages,
|
||||||
|
isSending = false,
|
||||||
|
hasAttachment = false,
|
||||||
|
error = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Message sent successfully: ${chatLine.id}")
|
||||||
|
|
||||||
|
// Update the chat room ID if it's the first message
|
||||||
|
val newChatRoomId = chatLine.chatRoomId
|
||||||
|
if (existingChatRoomId == 0 && newChatRoomId > 0) {
|
||||||
|
Log.d(TAG, "Chat room created: $newChatRoomId")
|
||||||
|
_chatRoomId.value = newChatRoomId
|
||||||
|
|
||||||
|
// Now that we have a chat room ID, we can join the Socket.IO room
|
||||||
|
joinSocketRoom(newChatRoomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit the message via Socket.IO for real-time updates
|
||||||
|
socketService.sendMessage(chatLine)
|
||||||
|
|
||||||
|
// Clear the image attachment
|
||||||
|
selectedImageFile = null
|
||||||
}
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
val errorMsg = if (result.exception.message.isNullOrEmpty() || result.exception.message == "{}") {
|
||||||
|
"Failed to send message. Please try again."
|
||||||
|
} else {
|
||||||
|
result.exception.message
|
||||||
|
}
|
||||||
|
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
messages = updatedMessages,
|
isSending = false,
|
||||||
isSending = false,
|
error = errorMsg
|
||||||
hasAttachment = false,
|
)
|
||||||
error = null
|
}
|
||||||
)
|
Log.e(TAG, "Error sending message: ${result.exception.message}")
|
||||||
}
|
}
|
||||||
|
is Result.Loading -> {
|
||||||
Log.d(TAG, "Message sent successfully: ${chatLine.id}")
|
updateState { it.copy(isSending = true) }
|
||||||
|
|
||||||
// Update the chat room ID if it's the first message
|
|
||||||
// This is the key part - we get the chat room ID from the response
|
|
||||||
val newChatRoomId = chatLine.chatRoomId
|
|
||||||
if ((_chatRoomId.value ?: 0) == 0 && newChatRoomId > 0) {
|
|
||||||
Log.d(TAG, "Chat room created: $newChatRoomId")
|
|
||||||
_chatRoomId.value = newChatRoomId
|
|
||||||
|
|
||||||
// Now that we have a chat room ID, we can join the Socket.IO room
|
|
||||||
joinSocketRoom(newChatRoomId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit the message via Socket.IO for real-time updates
|
|
||||||
socketService.sendMessage(chatLine)
|
|
||||||
|
|
||||||
// Clear the image attachment
|
|
||||||
selectedImageFile = null
|
|
||||||
}
|
}
|
||||||
is Result.Error -> {
|
} catch (e: Exception) {
|
||||||
val errorMsg = if (result.exception.message.isNullOrEmpty() || result.exception.message == "{}") {
|
Log.e(TAG, "Exception in sendMessage", e)
|
||||||
"Failed to send message. Please try again."
|
updateState {
|
||||||
} else {
|
it.copy(
|
||||||
result.exception.message
|
isSending = false,
|
||||||
}
|
error = "An unexpected error occurred: ${e.message}"
|
||||||
|
)
|
||||||
updateState {
|
|
||||||
it.copy(
|
|
||||||
isSending = false,
|
|
||||||
error = errorMsg
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Log.e(TAG, "Error sending message: ${result.exception.message}")
|
|
||||||
}
|
|
||||||
is Result.Loading -> {
|
|
||||||
updateState { it.copy(isSending = true) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -428,6 +467,13 @@ class ChatViewModel @Inject constructor(
|
|||||||
socketService.disconnect()
|
socketService.disconnect()
|
||||||
Log.d(TAG, "ViewModel cleared, Socket.IO disconnected")
|
Log.d(TAG, "ViewModel cleared, Socket.IO disconnected")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getChatList() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_chatList.value = com.alya.ecommerce_serang.data.repository.Result.Loading
|
||||||
|
_chatList.value = chatRepository.getListChat()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -92,14 +92,15 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@+id/chatToolbar">
|
app:layout_constraintTop_toBottomOf="@+id/chatToolbar">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/product_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/imgProduct"
|
android:id="@+id/imgProduct"
|
||||||
android:layout_width="0dp"
|
android:layout_width="64dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="64dp"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:src="@drawable/placeholder_image"
|
android:src="@drawable/placeholder_image"
|
||||||
app:layout_constraintDimensionRatio="1:1"
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
@ -1,19 +1,36 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout 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="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
tools:context=".ui.chat.ChatListFragment">
|
tools:context=".ui.chat.ChatListFragment">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/chatHeaderTitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Pesan"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:fontFamily="@font/dmsans_bold" />
|
||||||
|
|
||||||
|
<com.google.android.material.divider.MaterialDivider
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
app:dividerColor="@color/black_100"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/chatListRecyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:text="Hello" />
|
android:padding="8dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
tools:listitem="@layout/item_chat"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||||
|
|
||||||
<Button
|
</LinearLayout>
|
||||||
android:id="@+id/btn_trial"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:text="trial button"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
60
app/src/main/res/layout/item_chat.xml
Normal file
60
app/src/main/res/layout/item_chat.xml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:elevation="2dp"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imgStore"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@drawable/circle_background"
|
||||||
|
android:clipToOutline="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/ic_person" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginStart="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/txtStoreName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Store Name"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/txtMessage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Last message"
|
||||||
|
android:textColor="#666" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/txtTime"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="09.30"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#999" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
Reference in New Issue
Block a user