mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-10 09:22:21 +00:00
Merge branch 'screen-features'
# Conflicts: # app/src/main/AndroidManifest.xml
This commit is contained in:
@ -29,13 +29,16 @@
|
||||
android:theme="@style/Theme.Ecommerce_serang"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".ui.profile.mystore.chat.ChatListStoreActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.product.storeDetail.StoreDetailActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.auth.RegisterStoreActivity"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="false" />
|
||||
android:exported="false"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".ui.profile.editprofile.EditProfileCustActivity"
|
||||
android:exported="false" />
|
||||
@ -66,6 +69,10 @@
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
<activity
|
||||
android:name=".ui.profile.mystore.chat.ChatStoreActivity"
|
||||
android:exported="false"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.profile.mystore.profile.shipping_service.ShippingServiceActivity"
|
||||
@ -90,8 +97,8 @@
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.order.address.AddAddressActivity"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="false" />
|
||||
android:exported="false"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".ui.order.address.AddressActivity"
|
||||
android:exported="false" />
|
||||
@ -139,8 +146,8 @@
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.auth.RegisterActivity"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
android:exported="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
@ -450,6 +450,17 @@ interface ApiService {
|
||||
@Part chatimg: MultipartBody.Part?
|
||||
): Response<SendChatResponse>
|
||||
|
||||
@Multipart
|
||||
@POST("store/sendchat")
|
||||
suspend fun sendChatMessageStore(
|
||||
@PartMap parts: Map<String, @JvmSuppressWildcards RequestBody>,
|
||||
@Part chatimg: MultipartBody.Part? = null
|
||||
): Response<SendChatResponse>
|
||||
|
||||
@GET("store/chat")
|
||||
suspend fun getChatListStore(
|
||||
): Response<ChatListResponse>
|
||||
|
||||
@Multipart
|
||||
@POST("sendchat")
|
||||
suspend fun sendChatMessage(
|
||||
|
@ -90,6 +90,60 @@ class ChatRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun sendChatMessageStore(
|
||||
storeId: Int,
|
||||
message: String,
|
||||
productId: Int?, // Nullable and optional
|
||||
imageFile: File? = null // Nullable and optional
|
||||
): Result<SendChatResponse> {
|
||||
return try {
|
||||
val parts = mutableMapOf<String, RequestBody>()
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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}")
|
||||
|
||||
// Send request
|
||||
val response = apiService.sendChatMessageStore(parts, imagePart)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
response.body()?.let { Result.Success(it) } ?: Result.Error(Exception("Empty response body"))
|
||||
} else {
|
||||
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 chat message", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get string content from RequestBody (best effort)
|
||||
private fun bodyToString(requestBody: RequestBody): String {
|
||||
return try {
|
||||
@ -217,4 +271,26 @@ class ChatRepository @Inject constructor(
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getListChatStore(): Result<List<ChatItemList>> {
|
||||
return try {
|
||||
Log.d("ChatRepository", "Calling getChatListStore() from ApiService")
|
||||
|
||||
val response = apiService.getChatListStore()
|
||||
|
||||
Log.d("ChatRepository", "Response received: isSuccessful=${response.isSuccessful}, code=${response.code()}")
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val chat = response.body()?.chat ?: emptyList()
|
||||
Log.d("ChatRepository", "Chat list size: ${chat.size}")
|
||||
Result.Success(chat)
|
||||
} else {
|
||||
Log.e("ChatRepository", "Failed response: ${response.errorBody()?.string()}")
|
||||
Result.Error(Exception("Failed to fetch chat list. Code: ${response.code()}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("ChatRepository", "Exception during getChatListStore", e)
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
}
|
@ -41,6 +41,9 @@ class ChatViewModel @Inject constructor(
|
||||
private val _chatList = MutableLiveData<Result<List<ChatItemList>>>()
|
||||
val chatList: LiveData<Result<List<ChatItemList>>> = _chatList
|
||||
|
||||
private val _chatListStore = MutableLiveData<Result<List<ChatItemList>>>()
|
||||
val chatListStore: LiveData<Result<List<ChatItemList>>> = _chatListStore
|
||||
|
||||
private val _storeDetail = MutableLiveData<Result<StoreProduct?>>()
|
||||
val storeDetail : LiveData<Result<StoreProduct?>> get() = _storeDetail
|
||||
|
||||
@ -367,6 +370,126 @@ class ChatViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun sendMessageStore(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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
updateState { it.copy(isSending = true) }
|
||||
|
||||
try {
|
||||
// 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.sendChatMessageStore(
|
||||
storeId = storeId,
|
||||
message = message,
|
||||
productId = safeProductId,
|
||||
imageFile = selectedImageFile
|
||||
)
|
||||
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
// 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 {
|
||||
it.copy(
|
||||
isSending = false,
|
||||
error = errorMsg
|
||||
)
|
||||
}
|
||||
Log.e(TAG, "Error sending message: ${result.exception.message}")
|
||||
}
|
||||
is Result.Loading -> {
|
||||
updateState { it.copy(isSending = true) }
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Exception in sendMessage", e)
|
||||
updateState {
|
||||
it.copy(
|
||||
isSending = false,
|
||||
error = "An unexpected error occurred: ${e.message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a message status (delivered, read)
|
||||
*/
|
||||
@ -488,6 +611,17 @@ class ChatViewModel @Inject constructor(
|
||||
_chatList.value = chatRepository.getListChat()
|
||||
}
|
||||
}
|
||||
|
||||
fun getChatListStore() {
|
||||
Log.d("ChatViewModel", "getChatListStore() called")
|
||||
_chatListStore.value = Result.Loading
|
||||
|
||||
viewModelScope.launch {
|
||||
val result = chatRepository.getListChatStore()
|
||||
Log.d("ChatViewModel", "getChatListStore() result: $result")
|
||||
_chatListStore.value = result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,8 +14,8 @@ 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.MyStoreRepository
|
||||
import com.alya.ecommerce_serang.databinding.ActivityMyStoreBinding
|
||||
import com.alya.ecommerce_serang.ui.chat.ChatListFragment
|
||||
import com.alya.ecommerce_serang.ui.profile.mystore.balance.BalanceActivity
|
||||
import com.alya.ecommerce_serang.ui.profile.mystore.chat.ChatListStoreActivity
|
||||
import com.alya.ecommerce_serang.ui.profile.mystore.product.ProductActivity
|
||||
import com.alya.ecommerce_serang.ui.profile.mystore.profile.DetailStoreProfileActivity
|
||||
import com.alya.ecommerce_serang.ui.profile.mystore.review.ReviewFragment
|
||||
@ -126,10 +126,8 @@ class MyStoreActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
binding.layoutInbox.setOnClickListener {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(android.R.id.content, ChatListFragment())
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
val intent = Intent(this, ChatListStoreActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,69 @@
|
||||
package com.alya.ecommerce_serang.ui.profile.mystore.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])
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package com.alya.ecommerce_serang.ui.profile.mystore.chat
|
||||
|
||||
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 androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
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.ChatRepository
|
||||
import com.alya.ecommerce_serang.data.repository.Result
|
||||
import com.alya.ecommerce_serang.databinding.ActivityChatListStoreBinding
|
||||
import com.alya.ecommerce_serang.ui.chat.ChatViewModel
|
||||
import com.alya.ecommerce_serang.ui.chat.SocketIOService
|
||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
|
||||
class ChatListStoreActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivityChatListStoreBinding
|
||||
private lateinit var socketService: SocketIOService
|
||||
private lateinit var apiService: ApiService
|
||||
private lateinit var sessionManager: SessionManager
|
||||
|
||||
private val TAG = "ChatListStoreActivity"
|
||||
|
||||
private val viewModel: ChatViewModel by viewModels {
|
||||
BaseViewModelFactory {
|
||||
val apiService = ApiConfig.getApiService(sessionManager)
|
||||
val chatRepository = ChatRepository(apiService)
|
||||
ChatViewModel(chatRepository, socketService, sessionManager)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Initialize SessionManager and SocketService
|
||||
sessionManager = SessionManager(this)
|
||||
socketService = SocketIOService(sessionManager)
|
||||
|
||||
// Inflate the layout and set content view
|
||||
binding = ActivityChatListStoreBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
apiService = ApiConfig.getApiService(sessionManager)
|
||||
|
||||
enableEdgeToEdge()
|
||||
|
||||
setupToolbar()
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||
val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
view.setPadding(
|
||||
systemBars.left,
|
||||
systemBars.top,
|
||||
systemBars.right,
|
||||
systemBars.bottom
|
||||
)
|
||||
windowInsets
|
||||
}
|
||||
|
||||
Log.d(TAG, "Fetching chat list from ViewModel")
|
||||
viewModel.getChatListStore()
|
||||
observeChatList()
|
||||
}
|
||||
|
||||
private fun setupToolbar(){
|
||||
binding.header.headerLeftIcon.setOnClickListener{
|
||||
finish()
|
||||
}
|
||||
binding.header.headerTitle.text = "Pesan"
|
||||
}
|
||||
|
||||
private fun observeChatList() {
|
||||
viewModel.chatListStore.observe(this) { result ->
|
||||
Log.d(TAG, "Observer triggered with result: $result")
|
||||
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
Log.d(TAG, "Chat list fetch success. Data size: ${result.data.size}")
|
||||
val adapter = ChatListAdapter(result.data) { chatItem ->
|
||||
Log.d(TAG, "Chat item clicked: storeId=${chatItem.storeId}, chatRoomId=${chatItem.chatRoomId}")
|
||||
val intent = ChatStoreActivity.createIntent(
|
||||
context = this,
|
||||
storeId = chatItem.storeId,
|
||||
productId = 0,
|
||||
productName = null,
|
||||
productPrice = "",
|
||||
productImage = null,
|
||||
productRating = null,
|
||||
storeName = chatItem.storeName,
|
||||
chatRoomId = chatItem.chatRoomId,
|
||||
storeImage = chatItem.storeImage
|
||||
)
|
||||
startActivity(intent)
|
||||
}
|
||||
binding.chatListRecyclerView.adapter = adapter
|
||||
Log.d(TAG, "Adapter set successfully")
|
||||
}
|
||||
|
||||
is Result.Error -> {
|
||||
Log.e(TAG, "Failed to load chats: ${result.exception.message}")
|
||||
Toast.makeText(this, "Failed to load chats", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
Result.Loading -> {
|
||||
Log.d(TAG, "Chat list is loading...")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
//package com.alya.ecommerce_serang.ui.profile.mystore.chat
|
||||
//
|
||||
//import android.os.Bundle
|
||||
//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.data.api.retrofit.ApiConfig
|
||||
//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.ui.chat.ChatViewModel
|
||||
//import com.alya.ecommerce_serang.ui.chat.SocketIOService
|
||||
//import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||
//import com.alya.ecommerce_serang.utils.SessionManager
|
||||
//
|
||||
//class ChatListStoreFragment : Fragment() {
|
||||
//
|
||||
// private var _binding: FragmentChatListBinding? = null
|
||||
//
|
||||
// private val binding get() = _binding!!
|
||||
// private lateinit var socketService: SocketIOService
|
||||
// private lateinit var sessionManager: SessionManager
|
||||
//
|
||||
// private val viewModel: com.alya.ecommerce_serang.ui.chat.ChatViewModel by viewModels {
|
||||
// BaseViewModelFactory {
|
||||
// val apiService = ApiConfig.getApiService(sessionManager)
|
||||
// val chatRepository = ChatRepository(apiService)
|
||||
// ChatViewModel(chatRepository, socketService, sessionManager)
|
||||
// }
|
||||
// }
|
||||
// override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// super.onCreate(savedInstanceState)
|
||||
// sessionManager = SessionManager(requireContext())
|
||||
// socketService = SocketIOService(sessionManager)
|
||||
//
|
||||
// }
|
||||
//
|
||||
// override fun onCreateView(
|
||||
// inflater: LayoutInflater, container: ViewGroup?,
|
||||
// savedInstanceState: Bundle?
|
||||
// ): View {
|
||||
// _binding = FragmentChatListBinding.inflate(inflater, container, false)
|
||||
// return _binding!!.root
|
||||
// }
|
||||
//
|
||||
// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
// super.onViewCreated(view, savedInstanceState)
|
||||
//
|
||||
// viewModel.getChatListStore()
|
||||
// observeChatList()
|
||||
// }
|
||||
//
|
||||
// private fun observeChatList() {
|
||||
// viewModel.chatListStore.observe(viewLifecycleOwner) { result ->
|
||||
// when (result) {
|
||||
// is Result.Success -> {
|
||||
// val adapter = ChatListAdapter(result.data) { chatItem ->
|
||||
// // Use the ChatActivity.createIntent factory method for proper navigation
|
||||
// ChatStoreActivity.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,
|
||||
// storeImage = chatItem.storeImage
|
||||
// )
|
||||
// }
|
||||
// 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
|
||||
// }
|
||||
//
|
||||
// companion object{
|
||||
//
|
||||
// }
|
||||
//}
|
@ -0,0 +1,593 @@
|
||||
package com.alya.ecommerce_serang.ui.profile.mystore.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
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
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
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
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
|
||||
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
||||
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.ActivityChatBinding
|
||||
import com.alya.ecommerce_serang.ui.auth.LoginActivity
|
||||
import com.alya.ecommerce_serang.ui.chat.ChatAdapter
|
||||
import com.alya.ecommerce_serang.ui.chat.ChatViewModel
|
||||
import com.alya.ecommerce_serang.utils.Constants
|
||||
import com.alya.ecommerce_serang.utils.SessionManager
|
||||
import com.bumptech.glide.Glide
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.max
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ChatStoreActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityChatBinding
|
||||
|
||||
@Inject
|
||||
lateinit var sessionManager: SessionManager
|
||||
|
||||
@Inject
|
||||
lateinit var apiService: ApiService
|
||||
|
||||
private lateinit var chatAdapter: ChatAdapter
|
||||
|
||||
private val viewModel: ChatViewModel by viewModels()
|
||||
|
||||
// For image attachment
|
||||
private var tempImageUri: Uri? = null
|
||||
|
||||
// Typing indicator handler
|
||||
private val typingHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
||||
private val stopTypingRunnable = Runnable {
|
||||
viewModel.sendTypingStatus(false)
|
||||
}
|
||||
|
||||
// Activity Result Launchers
|
||||
private val pickImageLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let { uri ->
|
||||
handleSelectedImage(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val takePictureLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
tempImageUri?.let { uri ->
|
||||
handleSelectedImage(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityChatBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
sessionManager = SessionManager(this)
|
||||
apiService = ApiConfig.getApiService(sessionManager)
|
||||
|
||||
Log.d("ChatActivity", "Token in storage: '${sessionManager.getToken()}'")
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
enableEdgeToEdge()
|
||||
|
||||
// Apply insets to your root layout
|
||||
|
||||
|
||||
// Get parameters from intent
|
||||
val storeId = intent.getIntExtra(Constants.EXTRA_STORE_ID, 0)
|
||||
val productId = intent.getIntExtra(Constants.EXTRA_PRODUCT_ID, 0)
|
||||
val productName = intent.getStringExtra(Constants.EXTRA_PRODUCT_NAME) ?: ""
|
||||
val productPrice = intent.getStringExtra(Constants.EXTRA_PRODUCT_PRICE) ?: ""
|
||||
val productImage = intent.getStringExtra(Constants.EXTRA_PRODUCT_IMAGE) ?: ""
|
||||
val productRating = intent.getFloatExtra(Constants.EXTRA_PRODUCT_RATING, 0f)
|
||||
val storeName = intent.getStringExtra(Constants.EXTRA_STORE_NAME) ?: ""
|
||||
val chatRoomId = intent.getIntExtra(Constants.EXTRA_CHAT_ROOM_ID, 0)
|
||||
val storeImg = intent.getStringExtra(Constants.EXTRA_STORE_IMAGE) ?: ""
|
||||
|
||||
// Check if user is logged in
|
||||
val token = sessionManager.getToken()
|
||||
|
||||
if (token.isEmpty()) {
|
||||
// User not logged in, redirect to login
|
||||
Toast.makeText(this, "Please login first", Toast.LENGTH_SHORT).show()
|
||||
startActivity(Intent(this, LoginActivity::class.java))
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
binding.tvStoreName.text = storeName
|
||||
val fullImageUrl = when (val img = storeImg) {
|
||||
is String -> {
|
||||
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
|
||||
}
|
||||
else -> R.drawable.placeholder_image
|
||||
}
|
||||
|
||||
Glide.with(this)
|
||||
.load(fullImageUrl)
|
||||
.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<WindowInsetsAnimationCompat>
|
||||
): 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,
|
||||
productId = productId,
|
||||
productName = productName,
|
||||
productPrice = productPrice,
|
||||
productImage = productImage,
|
||||
productRating = productRating,
|
||||
storeName = storeName
|
||||
)
|
||||
|
||||
// Setup UI components
|
||||
setupRecyclerView()
|
||||
setupListeners()
|
||||
setupTypingIndicator()
|
||||
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() {
|
||||
chatAdapter = ChatAdapter()
|
||||
binding.recyclerChat.apply {
|
||||
adapter = chatAdapter
|
||||
layoutManager = LinearLayoutManager(this@ChatStoreActivity).apply {
|
||||
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
|
||||
// )
|
||||
}
|
||||
|
||||
|
||||
private fun setupListeners() {
|
||||
// Back button
|
||||
binding.btnBack.setOnClickListener {
|
||||
onBackPressed()
|
||||
}
|
||||
|
||||
// Options button
|
||||
binding.btnOptions.setOnClickListener {
|
||||
showOptionsMenu()
|
||||
}
|
||||
|
||||
// Send button
|
||||
binding.btnSend.setOnClickListener {
|
||||
val message = binding.editTextMessage.text.toString().trim()
|
||||
val currentState = viewModel.state.value
|
||||
if (message.isNotEmpty() || (currentState != null && currentState.hasAttachment)) {
|
||||
viewModel.sendMessageStore(message)
|
||||
binding.editTextMessage.text.clear()
|
||||
}
|
||||
}
|
||||
|
||||
// Attachment button
|
||||
binding.btnAttachment.setOnClickListener {
|
||||
checkPermissionsAndShowImagePicker()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTypingIndicator() {
|
||||
binding.editTextMessage.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
viewModel.sendTypingStatus(true)
|
||||
|
||||
// Reset the timer
|
||||
typingHandler.removeCallbacks(stopTypingRunnable)
|
||||
typingHandler.postDelayed(stopTypingRunnable, 1000)
|
||||
}
|
||||
|
||||
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() {
|
||||
viewModel.chatRoomId.observe(this, Observer { chatRoomId ->
|
||||
if (chatRoomId > 0) {
|
||||
// Chat room has been created, now we can join the Socket.IO room
|
||||
viewModel.joinSocketRoom(chatRoomId)
|
||||
|
||||
// Now we can also load chat history
|
||||
viewModel.loadChatHistory(chatRoomId)
|
||||
Log.d(TAG, "Chat Activity started - Chat Room: $chatRoomId")
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
// Observe state changes using LiveData
|
||||
viewModel.state.observe(this, Observer { state ->
|
||||
// Update messages
|
||||
chatAdapter.submitList(state.messages)
|
||||
|
||||
// Scroll to bottom if new message
|
||||
if (state.messages.isNotEmpty()) {
|
||||
binding.recyclerChat.scrollToPosition(state.messages.size - 1)
|
||||
}
|
||||
|
||||
// Update product info
|
||||
if (!state.productName.isNullOrEmpty()) {
|
||||
binding.tvProductName.text = state.productName
|
||||
binding.tvProductPrice.text = state.productPrice
|
||||
binding.ratingBar.rating = state.productRating
|
||||
binding.tvRating.text = state.productRating.toString()
|
||||
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@ChatStoreActivity)
|
||||
.load(fullImageUrl)
|
||||
.centerCrop()
|
||||
.placeholder(R.drawable.placeholder_image)
|
||||
.error(R.drawable.placeholder_image)
|
||||
.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
|
||||
if (state.hasAttachment) {
|
||||
binding.editTextMessage.hint = getString(R.string.image_attached)
|
||||
} else {
|
||||
binding.editTextMessage.hint = getString(R.string.write_message)
|
||||
}
|
||||
|
||||
|
||||
// Show typing indicator
|
||||
binding.tvTypingIndicator.visibility =
|
||||
if (state.isOtherUserTyping) View.VISIBLE else View.GONE
|
||||
|
||||
// Show error if any
|
||||
state.error?.let { error ->
|
||||
Toast.makeText(this@ChatStoreActivity, error, Toast.LENGTH_SHORT).show()
|
||||
viewModel.clearError()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun showOptionsMenu() {
|
||||
val options = arrayOf(
|
||||
getString(R.string.block_user),
|
||||
getString(R.string.report),
|
||||
getString(R.string.clear_chat),
|
||||
getString(R.string.cancel)
|
||||
)
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(getString(R.string.options))
|
||||
.setItems(options) { dialog, which ->
|
||||
when (which) {
|
||||
0 -> Toast.makeText(this, R.string.block_user_selected, Toast.LENGTH_SHORT).show()
|
||||
1 -> Toast.makeText(this, R.string.report_selected, Toast.LENGTH_SHORT).show()
|
||||
2 -> Toast.makeText(this, R.string.clear_chat_selected, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun checkPermissionsAndShowImagePicker() {
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA),
|
||||
Constants.REQUEST_STORAGE_PERMISSION
|
||||
)
|
||||
} else {
|
||||
showImagePickerOptions()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showImagePickerOptions() {
|
||||
val options = arrayOf(
|
||||
getString(R.string.take_photo),
|
||||
getString(R.string.choose_from_gallery),
|
||||
getString(R.string.cancel)
|
||||
)
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(getString(R.string.select_attachment))
|
||||
.setItems(options) { dialog, which ->
|
||||
when (which) {
|
||||
0 -> openCamera()
|
||||
1 -> openGallery()
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun openCamera() {
|
||||
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
||||
val imageFileName = "IMG_${timeStamp}.jpg"
|
||||
val storageDir = getExternalFilesDir(null)
|
||||
val imageFile = File(storageDir, imageFileName)
|
||||
|
||||
tempImageUri = FileProvider.getUriForFile(
|
||||
this,
|
||||
"${applicationContext.packageName}.fileprovider",
|
||||
imageFile
|
||||
)
|
||||
|
||||
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
|
||||
putExtra(MediaStore.EXTRA_OUTPUT, tempImageUri)
|
||||
}
|
||||
|
||||
takePictureLauncher.launch(intent)
|
||||
}
|
||||
|
||||
private fun openGallery() {
|
||||
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
|
||||
pickImageLauncher.launch(intent)
|
||||
}
|
||||
|
||||
private fun handleSelectedImage(uri: Uri) {
|
||||
try {
|
||||
Log.d(TAG, "Processing selected image: $uri")
|
||||
|
||||
// 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)
|
||||
|
||||
outputFile.outputStream().use { outputStream ->
|
||||
inputStream.copyTo(outputStream)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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, "Failed to create image file")
|
||||
Toast.makeText(this, "Failed to process 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: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == Constants.REQUEST_STORAGE_PERMISSION) {
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
showImagePickerOptions()
|
||||
} else {
|
||||
Toast.makeText(this, R.string.permission_denied, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
typingHandler.removeCallbacks(stopTypingRunnable)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ChatActivity"
|
||||
|
||||
/**
|
||||
* Create an intent to start the ChatActivity
|
||||
*/
|
||||
fun createIntent(
|
||||
context: Activity,
|
||||
storeId: Int,
|
||||
productId: Int = 0,
|
||||
productName: String? = null,
|
||||
productPrice: String = "",
|
||||
productImage: String? = null,
|
||||
productRating: String? = null,
|
||||
storeName: String? = null,
|
||||
chatRoomId: Int = 0,
|
||||
storeImage: String? = null
|
||||
): Intent {
|
||||
return Intent(context, ChatStoreActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_STORE_ID, storeId)
|
||||
putExtra(Constants.EXTRA_PRODUCT_ID, productId)
|
||||
putExtra(Constants.EXTRA_PRODUCT_NAME, productName)
|
||||
putExtra(Constants.EXTRA_PRODUCT_PRICE, productPrice)
|
||||
putExtra(Constants.EXTRA_PRODUCT_IMAGE, productImage)
|
||||
putExtra(Constants.EXTRA_STORE_IMAGE, storeImage)
|
||||
|
||||
// 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)
|
||||
|
||||
if (chatRoomId > 0) {
|
||||
putExtra(Constants.EXTRA_CHAT_ROOM_ID, chatRoomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if implement typing status
|
||||
// private fun handleConnectionState(state: ConnectionState) {
|
||||
// when (state) {
|
||||
// is ConnectionState.Connected -> {
|
||||
// binding.tvConnectionStatus.visibility = View.GONE
|
||||
// }
|
||||
// is ConnectionState.Connecting -> {
|
||||
// binding.tvConnectionStatus.visibility = View.VISIBLE
|
||||
// binding.tvConnectionStatus.text = getString(R.string.connecting)
|
||||
// }
|
||||
// is ConnectionState.Disconnected -> {
|
||||
// binding.tvConnectionStatus.visibility = View.VISIBLE
|
||||
// binding.tvConnectionStatus.text = getString(R.string.disconnected_reconnecting)
|
||||
// }
|
||||
// is ConnectionState.Error -> {
|
||||
// binding.tvConnectionStatus.visibility = View.VISIBLE
|
||||
// binding.tvConnectionStatus.text = getString(R.string.connection_error, state.message)
|
||||
// }
|
||||
// }
|
||||
// }
|
40
app/src/main/res/layout/activity_chat_list_store.xml
Normal file
40
app/src/main/res/layout/activity_chat_list_store.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.profile.mystore.chat.ChatListStoreActivity">
|
||||
|
||||
<include
|
||||
android:id="@+id/header"
|
||||
layout="@layout/header" />
|
||||
|
||||
<!-- <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"
|
||||
app:dividerColor="@color/black_100" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/chatListRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="8dp"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/item_chat"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
</LinearLayout>
|
Reference in New Issue
Block a user