diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 31e1666..4edca1b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,6 +34,7 @@
android:exported="false" />
+
+
+ android:exported="false"
+ android:windowSoftInputMode="adjustResize|stateHidden" />
@@ -88,6 +97,7 @@
android:exported="false" />
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
index 57e4742..099f70f 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/api/retrofit/ApiService.kt
@@ -450,6 +450,13 @@ interface ApiService {
@Part chatimg: MultipartBody.Part?
): Response
+ @Multipart
+ @POST("sendchat")
+ suspend fun sendChatMessage(
+ @PartMap parts: Map,
+ @Part chatimg: MultipartBody.Part? = null
+ ): Response
+
@PUT("chatstatus")
suspend fun updateChatStatus(
@Body request: UpdateChatRequest
diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/ChatRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ChatRepository.kt
index 68c0a94..ccfb887 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/ChatRepository.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/ChatRepository.kt
@@ -8,8 +8,9 @@ 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.UpdateChatResponse
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
-import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
+import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
@@ -38,62 +39,130 @@ class ChatRepository @Inject constructor(
suspend fun sendChatMessage(
storeId: Int,
message: String,
- productId: Int? = null,
- imageFile: File? = null,
- chatRoomId: Int? = null // Not used in the actual API call but kept for compatibility
+ productId: Int?, // Nullable and optional
+ imageFile: File? = null // Nullable and optional
): Result {
return try {
- // Create multipart request parts
- val storeIdPart = storeId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
- val messagePart = message.toRequestBody("text/plain".toMediaTypeOrNull())
+ val parts = mutableMapOf()
- // Add product ID part if provided
- val productIdPart = if (productId != null && productId > 0) {
- productId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
- } else {
- null
+ // Required fields
+ parts["store_id"] = storeId.toString().toRequestBody("text/plain".toMediaType())
+ parts["message"] = message.toRequestBody("text/plain".toMediaType())
+
+ // Optional: Only include if productId is valid
+ if (productId != null && productId > 0) {
+ parts["product_id"] = productId.toString().toRequestBody("text/plain".toMediaType())
}
- // Create image part if file is provided
- val imagePart = if (imageFile != null && imageFile.exists()) {
- val requestFile = imageFile.asRequestBody("image/*".toMediaTypeOrNull())
- MultipartBody.Part.createFormData("chatimg", imageFile.name, requestFile)
- } else {
- null
+ // Optional: Only include if imageFile is valid
+ val imagePart = imageFile?.takeIf { it.exists() }?.let { file ->
+// val requestFile = file.asRequestBody("image/*".toMediaType())
+ val mimeType = when {
+ file.name.endsWith(".png", ignoreCase = true) -> "image/png"
+ file.name.endsWith(".jpg", ignoreCase = true) || file.name.endsWith(".jpeg", ignoreCase = true) -> "image/jpeg"
+ else -> "image/jpeg" // fallback
+ }
+ val requestFile = file.asRequestBody(mimeType.toMediaType())
+ MultipartBody.Part.createFormData("chatimg", file.name, requestFile)
}
- // Debug log the request parameters
- Log.d("ChatRepository", "Sending chat with: storeId=$storeId, productId=$productId, " +
- "message length=${message.length}, hasImage=${imageFile != null}")
+ // Log the parts map keys and values (string representations)
+ Log.d("ChatRepository", "Sending chat message with parts:")
+ parts.forEach { (key, body) ->
+ Log.d("ChatRepository", "Key: $key, Value (approx): ${bodyToString(body)}")
+ }
+ Log.d("ChatRepository", "Sending chat message with imagePart: ${imagePart != null}")
- // Make API call using your actual endpoint and parameter names
- val response = apiService.sendChatLine(
- storeId = storeIdPart,
- message = messagePart,
- productId = productIdPart,
- chatimg = imagePart
- )
-
- Log.d("ChatRepository", "check data productId=$productIdPart, storeId=$storeIdPart, messageTxt=$messagePart, chatImg=$imagePart")
+ // Send request
+ val response = apiService.sendChatMessage(parts, imagePart)
if (response.isSuccessful) {
- val body = response.body()
- if (body != null) {
- Result.Success(body)
- } else {
- Result.Error(Exception("Empty response body"))
- }
+ response.body()?.let { Result.Success(it) } ?: Result.Error(Exception("Empty response body"))
} else {
- val errorBody = response.errorBody()?.string() ?: "{}"
- Log.e("ChatRepository", "API Error: ${response.code()} - $errorBody")
- Result.Error(Exception("API Error: ${response.code()} - $errorBody"))
+ val errorMsg = response.errorBody()?.string().orEmpty()
+ Log.e("ChatRepository", "API Error: ${response.code()} - $errorMsg")
+ Result.Error(Exception("API Error: ${response.code()} - $errorMsg"))
}
+
} catch (e: Exception) {
- Log.e("ChatRepository", "Exception sending message", e)
+ Log.e("ChatRepository", "Exception sending chat message", e)
Result.Error(e)
}
}
+ // Helper function to get string content from RequestBody (best effort)
+ private fun bodyToString(requestBody: RequestBody): String {
+ return try {
+ val buffer = okio.Buffer()
+ requestBody.writeTo(buffer)
+ buffer.readUtf8()
+ } catch (e: Exception) {
+ "Could not read body"
+ }
+ }
+
+// suspend fun sendChatMessage(
+// storeId: Int,
+// message: String,
+// productId: Int?,
+// imageFile: File? = null,
+// chatRoomId: Int? = null
+// ): Result {
+// return try {
+// Log.d(TAG, "=== SEND CHAT MESSAGE ===")
+// Log.d(TAG, "StoreId: $storeId")
+// Log.d(TAG, "Message: '$message'")
+// Log.d(TAG, "ProductId: $productId")
+// Log.d(TAG, "ImageFile: ${imageFile?.absolutePath}")
+// Log.d(TAG, "ImageFile exists: ${imageFile?.exists()}")
+// Log.d(TAG, "ImageFile size: ${imageFile?.length()} bytes")
+//
+// // Convert primitive fields to RequestBody
+// val storeIdBody = storeId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
+// val messageBody = message.toRequestBody("text/plain".toMediaTypeOrNull())
+// val productIdBody = productId?.takeIf { it > 0 } // null if 0
+// ?.toString()
+// ?.toRequestBody("text/plain".toMediaTypeOrNull())
+//
+// // Convert image file to MultipartBody.Part if exists
+// val imagePart: MultipartBody.Part? = imageFile?.takeIf { it.exists() }?.let { file ->
+// val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull())
+// MultipartBody.Part.createFormData("chatimg", file.name, requestFile)
+// }
+//
+//
+//
+// // Call the API
+// Log.d(TAG, "Sending request. ProductIdBody is null: ${productIdBody == null}")
+//
+// val response = apiService.sendChatLine(
+// storeId = storeIdBody,
+// message = messageBody,
+// productId = productIdBody,
+// chatimg = imagePart
+// )
+//
+// // Handle API response
+// if (response.isSuccessful) {
+// response.body()?.let {
+// Log.d(TAG, "Success: ${it.message}")
+// Result.Success(it)
+// } ?: run {
+// Log.e(TAG, "Response body is null")
+// Result.Error(Exception("Empty response body"))
+// }
+// } else {
+// val errorMsg = response.errorBody()?.string() ?: "Unknown error"
+// Log.e(TAG, "API Error: ${response.code()} - $errorMsg")
+// Result.Error(Exception("API Error: ${response.code()} - $errorMsg"))
+// }
+//
+// } catch (e: Exception) {
+// Log.e(TAG, "Exception sending chat message", e)
+// Result.Error(e)
+// }
+// }
+
suspend fun updateMessageStatus(
messageId: Int,
status: String
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterActivity.kt
index c8f6585..04b5780 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/RegisterActivity.kt
@@ -59,6 +59,8 @@ class RegisterActivity : AppCompatActivity() {
windowInsets
}
+
+
Log.d("RegisterActivity", "Token in storage: '${sessionManager.getToken()}'")
Log.d("RegisterActivity", "User ID in storage: '${sessionManager.getUserId()}'")
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/auth/fragments/RegisterStep3Fragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/fragments/RegisterStep3Fragment.kt
index ec34458..e73281e 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/auth/fragments/RegisterStep3Fragment.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/fragments/RegisterStep3Fragment.kt
@@ -8,6 +8,9 @@ import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsAnimationCompat
+import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.alya.ecommerce_serang.R
@@ -91,6 +94,8 @@ class RegisterStep3Fragment : Fragment() {
// Set up province and city dropdowns
setupAutoComplete()
+ setupEdgeToEdge()
+
// Set up button listeners
binding.btnPrevious.setOnClickListener {
// Go back to the previous step
@@ -116,6 +121,55 @@ class RegisterStep3Fragment : Fragment() {
setupCityObserver()
}
+ private fun setupEdgeToEdge() {
+ // Apply insets to your fragment's root view
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
+ val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ view.setPadding(
+ systemBars.left,
+ systemBars.top,
+ systemBars.right,
+ systemBars.bottom
+ )
+ windowInsets
+ }
+
+ // Set up IME animation callback
+ ViewCompat.setWindowInsetsAnimationCallback(
+ binding.root,
+ object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
+ var startBottom = 0f
+ var endBottom = 0f
+
+ override fun onPrepare(animation: WindowInsetsAnimationCompat) {
+ startBottom = binding.root.bottom.toFloat()
+ }
+
+ override fun onStart(
+ animation: WindowInsetsAnimationCompat,
+ bounds: WindowInsetsAnimationCompat.BoundsCompat
+ ): WindowInsetsAnimationCompat.BoundsCompat {
+ endBottom = binding.root.bottom.toFloat()
+ return bounds
+ }
+
+ override fun onProgress(
+ insets: WindowInsetsCompat,
+ runningAnimations: MutableList
+ ): WindowInsetsCompat {
+ val imeAnimation = runningAnimations.find {
+ it.typeMask and WindowInsetsCompat.Type.ime() != 0
+ } ?: return insets
+
+ binding.root.translationY =
+ (startBottom - endBottom) * (1 - imeAnimation.interpolatedFraction)
+
+ return insets
+ }
+ }
+ )
+ }
+
private fun setupAutoComplete() {
// Same implementation as before
binding.autoCompleteProvinsi.setAdapter(provinceAdapter)
@@ -351,6 +405,8 @@ class RegisterStep3Fragment : Fragment() {
override fun onDestroyView() {
super.onDestroyView()
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root, null)
+ ViewCompat.setWindowInsetsAnimationCallback(binding.root, null)
_binding = null
}
//
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt
index 453c9b2..d9cc397 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt
@@ -3,6 +3,7 @@ package com.alya.ecommerce_serang.ui.chat
import android.Manifest
import android.app.Activity
import android.app.AlertDialog
+import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
@@ -12,6 +13,7 @@ import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.View
+import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
@@ -22,6 +24,7 @@ import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
@@ -40,6 +43,7 @@ import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import javax.inject.Inject
+import kotlin.math.max
@AndroidEntryPoint
class ChatActivity : AppCompatActivity() {
@@ -100,16 +104,7 @@ class ChatActivity : AppCompatActivity() {
enableEdgeToEdge()
// Apply insets to your root layout
- ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
- val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
- view.setPadding(
- systemBars.left,
- systemBars.top,
- systemBars.right,
- systemBars.bottom
- )
- windowInsets
- }
+
// Get parameters from intent
val storeId = intent.getIntExtra(Constants.EXTRA_STORE_ID, 0)
@@ -146,6 +141,84 @@ class ChatActivity : AppCompatActivity() {
.placeholder(R.drawable.placeholder_image)
.into(binding.imgProfile)
+ ViewCompat.setOnApplyWindowInsetsListener(binding.layoutChatInput) { view, insets ->
+ val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime())
+ val navBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
+
+ val bottomPadding = max(imeInsets.bottom, navBarInsets.bottom)
+ view.setPadding(view.paddingLeft, view.paddingTop, view.paddingRight, bottomPadding)
+ insets
+ }
+
+// Handle top inset on toolbar (status bar height)
+ ViewCompat.setOnApplyWindowInsetsListener(binding.chatToolbar) { view, insets ->
+ val statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top
+ view.setPadding(view.paddingLeft, statusBarHeight, view.paddingRight, view.paddingBottom)
+ insets
+ }
+
+ ViewCompat.setOnApplyWindowInsetsListener(binding.recyclerChat) { view, insets ->
+ val navBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
+ val bottomPadding = binding.layoutChatInput.height + navBarInsets.bottom
+
+ view.setPadding(
+ view.paddingLeft,
+ view.paddingTop,
+ view.paddingRight,
+ bottomPadding
+ )
+ insets
+ }
+
+// For RecyclerView, add bottom padding = chat input height + nav bar height (to avoid last message hidden)
+
+ ViewCompat.setWindowInsetsAnimationCallback(binding.root,
+ object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
+
+ private var startPaddingBottom = 0
+ private var endPaddingBottom = 0
+
+ override fun onPrepare(animation: WindowInsetsAnimationCompat) {
+ startPaddingBottom = binding.layoutChatInput.paddingBottom
+ }
+
+ override fun onStart(
+ animation: WindowInsetsAnimationCompat,
+ bounds: WindowInsetsAnimationCompat.BoundsCompat
+ ): WindowInsetsAnimationCompat.BoundsCompat {
+ endPaddingBottom = binding.layoutChatInput.paddingBottom
+ return bounds
+ }
+
+ override fun onProgress(
+ insets: WindowInsetsCompat,
+ runningAnimations: MutableList
+ ): WindowInsetsCompat {
+ val imeAnimation = runningAnimations.find {
+ it.typeMask and WindowInsetsCompat.Type.ime() != 0
+ } ?: return insets
+
+ val animatedBottomPadding = startPaddingBottom +
+ (endPaddingBottom - startPaddingBottom) * imeAnimation.interpolatedFraction
+
+ binding.layoutChatInput.setPadding(
+ binding.layoutChatInput.paddingLeft,
+ binding.layoutChatInput.paddingTop,
+ binding.layoutChatInput.paddingRight,
+ animatedBottomPadding.toInt()
+ )
+
+ binding.recyclerChat.setPadding(
+ binding.recyclerChat.paddingLeft,
+ binding.recyclerChat.paddingTop,
+ binding.recyclerChat.paddingRight,
+ animatedBottomPadding.toInt() + binding.layoutChatInput.height
+ )
+
+ return insets
+ }
+ })
+
// Set chat parameters to ViewModel
viewModel.setChatParameters(
storeId = storeId,
@@ -178,6 +251,12 @@ class ChatActivity : AppCompatActivity() {
stackFromEnd = true
}
}
+// binding.recyclerChat.setPadding(
+// binding.recyclerChat.paddingLeft,
+// binding.recyclerChat.paddingTop,
+// binding.recyclerChat.paddingRight,
+// binding.layoutChatInput.height + binding.root.rootWindowInsets?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom ?: 0
+// )
}
@@ -222,6 +301,11 @@ class ChatActivity : AppCompatActivity() {
override fun afterTextChanged(s: Editable?) {}
})
+
+ binding.editTextMessage.requestFocus()
+ val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.showSoftInput(binding.editTextMessage, InputMethodManager.SHOW_IMPLICIT)
+
}
private fun observeViewModel() {
@@ -256,10 +340,17 @@ class ChatActivity : AppCompatActivity() {
binding.tvSellerName.text = state.storeName
binding.tvStoreName.text=state.storeName
+ val fullImageUrl = when (val img = state.productImageUrl) {
+ is String -> {
+ if (img.startsWith("/")) BASE_URL + img.substring(1) else img
+ }
+ else -> R.drawable.placeholder_image
+ }
+
// Load product image
if (!state.productImageUrl.isNullOrEmpty()) {
Glide.with(this@ChatActivity)
- .load(BASE_URL + state.productImageUrl)
+ .load(fullImageUrl)
.centerCrop()
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
@@ -294,8 +385,6 @@ class ChatActivity : AppCompatActivity() {
})
}
-
-
private fun showOptionsMenu() {
val options = arrayOf(
getString(R.string.block_user),
@@ -380,67 +469,36 @@ class ChatActivity : AppCompatActivity() {
try {
Log.d(TAG, "Processing selected image: $uri")
- // First try the direct approach to get the file path
- var filePath: String? = null
+ // Always use the copy-to-cache approach for reliability
+ contentResolver.openInputStream(uri)?.use { inputStream ->
+ val fileName = "chat_img_${System.currentTimeMillis()}.jpg"
+ val outputFile = File(cacheDir, fileName)
- // 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")
- }
- }
+ outputFile.outputStream().use { outputStream ->
+ inputStream.copyTo(outputStream)
}
- // 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()
+ if (outputFile.exists() && outputFile.length() > 0) {
+ if (outputFile.length() > 5 * 1024 * 1024) {
+ Log.e(TAG, "File too large: ${outputFile.length()} bytes")
+ Toast.makeText(this, "Image too large (max 5MB)", 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")
+ Log.d(TAG, "Image processed successfully: ${outputFile.absolutePath}, size: ${outputFile.length()}")
+ viewModel.setSelectedImageFile(outputFile)
+ Toast.makeText(this, "Image selected", Toast.LENGTH_SHORT).show()
} else {
- Log.e(TAG, "File does not exist: $filePath")
- Toast.makeText(this, "Could not access the selected image", Toast.LENGTH_SHORT).show()
+ Log.e(TAG, "Failed to create image file")
+ Toast.makeText(this, "Failed to process 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()
+ } ?: run {
+ Log.e(TAG, "Could not open input stream for URI: $uri")
+ Toast.makeText(this, "Could not access 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()
+ Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatAdapter.kt
index 0e2f083..484803f 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatAdapter.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatAdapter.kt
@@ -78,8 +78,16 @@ class ChatAdapter : ListAdapter(ChatMess
// Handle attachment if exists
if (message.attachment?.isNotEmpty() == true) {
binding.imgAttachment.visibility = View.VISIBLE
+
+ val fullImageUrl = when (val img = message.attachment) {
+ is String -> {
+ if (img.startsWith("/")) BASE_URL + img.substring(1) else img
+ }
+ else -> R.drawable.placeholder_image
+ }
+
Glide.with(binding.root.context)
- .load(BASE_URL + message.attachment)
+ .load(fullImageUrl)
.centerCrop()
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
@@ -101,10 +109,17 @@ class ChatAdapter : ListAdapter(ChatMess
binding.tvTimestamp.text = message.time
// Handle attachment if exists
+ val fullImageUrl = when (val img = message.attachment) {
+ is String -> {
+ if (img.startsWith("/")) BASE_URL + img.substring(1) else img
+ }
+ else -> R.drawable.placeholder_image
+ }
+
if (message.attachment?.isNotEmpty() == true) {
binding.imgAttachment.visibility = View.VISIBLE
Glide.with(binding.root.context)
- .load(BASE_URL + message.attachment)
+ .load(fullImageUrl)
.centerCrop()
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.placeholder_image)
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt
index 989dedd..1f15130 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt
@@ -46,7 +46,7 @@ class ChatViewModel @Inject constructor(
// Store and product parameters
private var storeId: Int = 0
- private var productId: Int? = 0
+ private var productId: Int = 0
private var currentUserId: Int? = null
private var defaultUserId: Int = 0
@@ -100,8 +100,9 @@ class ChatViewModel @Inject constructor(
productRating: Float? = 0f,
storeName: String
) {
+ this.productId = if (productId != null && productId > 0) productId else 0
+
this.storeId = storeId
- this.productId = productId!!
this.productName = productName.toString()
this.productPrice = productPrice.toString()
this.productImage = productImage.toString()
@@ -247,6 +248,11 @@ class ChatViewModel @Inject constructor(
* Sends a chat message
*/
fun sendMessage(message: String) {
+ Log.d(TAG, "=== SEND MESSAGE ===")
+ Log.d(TAG, "Message: '$message'")
+ Log.d(TAG, "Has attachment: ${selectedImageFile != null}")
+ Log.d(TAG, "Selected image file: ${selectedImageFile?.absolutePath}")
+ Log.d(TAG, "File exists: ${selectedImageFile?.exists()}")
if (message.isBlank() && selectedImageFile == null) {
Log.e(TAG, "Cannot send message: Both message and image are empty")
return
@@ -282,12 +288,14 @@ class ChatViewModel @Inject constructor(
// Send the message using the repository
// Note: We keep the chatRoomId parameter for compatibility with the repository method signature,
// but it's not actually used in the API call
+ val safeProductId = if (productId == 0) null else productId
+
+
val result = chatRepository.sendChatMessage(
storeId = storeId,
message = message,
- productId = productId,
- imageFile = selectedImageFile,
- chatRoomId = existingChatRoomId
+ productId = safeProductId,
+ imageFile = selectedImageFile
)
when (result) {
diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/notif/PersonalNotificationAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/PersonalNotificationAdapter.kt
index e60aa1c..cafb9e5 100644
--- a/app/src/main/java/com/alya/ecommerce_serang/ui/notif/PersonalNotificationAdapter.kt
+++ b/app/src/main/java/com/alya/ecommerce_serang/ui/notif/PersonalNotificationAdapter.kt
@@ -44,7 +44,14 @@ class PersonalNotificationAdapter(
fun bind(notification: NotifItem) {
binding.apply {
- tvNotificationType.text = notification.type
+ val typeNotif = notification.type.toString()
+ if(typeNotif == "User"){
+ tvNotificationType.text = "Pembelian"
+ } else if (typeNotif == "Store"){
+ tvNotificationType.text = "Penjualan"
+ } else {
+ tvNotificationType.text = notification.type
+ }
tvTitle.text = notification.title
tvDescription.text = notification.message
@@ -63,7 +70,7 @@ class PersonalNotificationAdapter(
try {
// Parse the date with the expected format from API
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
- val outputFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
+ val outputFormat = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.getDefault())
val date = inputFormat.parse(createdAt)
date?.let {
diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml
index 902de35..d60c39e 100644
--- a/app/src/main/res/layout/activity_chat.xml
+++ b/app/src/main/res/layout/activity_chat.xml
@@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="false"
android:theme="@style/Theme.Ecommerce_serang"
tools:context=".ui.chat.ChatActivity">
@@ -55,17 +56,17 @@
app:layout_constraintTop_toTopOf="@+id/imgProfile"
app:layout_constraintEnd_toStartOf="@+id/btnOptions" />
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_checkout.xml b/app/src/main/res/layout/activity_checkout.xml
index ba30bd8..8c793d4 100644
--- a/app/src/main/res/layout/activity_checkout.xml
+++ b/app/src/main/res/layout/activity_checkout.xml
@@ -7,6 +7,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black_800"
+ android:theme="@style/Theme.Ecommerce_serang"
tools:context=".ui.order.CheckoutActivity">
+ android:fontFamily="@font/dmsans_semibold" />
-
+
+
+
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/tabLayout" />
diff --git a/app/src/main/res/layout/activity_register_store.xml b/app/src/main/res/layout/activity_register_store.xml
index 36f8916..2ac26d7 100644
--- a/app/src/main/res/layout/activity_register_store.xml
+++ b/app/src/main/res/layout/activity_register_store.xml
@@ -21,7 +21,7 @@
android:text="Buka Toko"
android:textColor="@android:color/white"
android:textSize="20sp"
- android:textStyle="bold" />
+ android:fontFamily="@font/dmsans_medium" />
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 24782af..adeeaac 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -40,6 +40,9 @@
- @android:color/transparent
- @android:color/transparent
+
+ - @style/body_medium
+