mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-13 10:42:21 +00:00
fix chat activity
This commit is contained in:
@ -220,7 +220,7 @@ interface ApiService {
|
|||||||
@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") chatimg: MultipartBody.Part
|
@Part chatimg: MultipartBody.Part?
|
||||||
): Response<SendChatResponse>
|
): Response<SendChatResponse>
|
||||||
|
|
||||||
@PUT("chatstatus")
|
@PUT("chatstatus")
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.alya.ecommerce_serang.di
|
package com.alya.ecommerce_serang.di
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
import com.alya.ecommerce_serang.ui.chat.SocketIOService
|
import com.alya.ecommerce_serang.ui.chat.SocketIOService
|
||||||
@ -8,7 +7,6 @@ import com.alya.ecommerce_serang.utils.SessionManager
|
|||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -16,12 +14,6 @@ import javax.inject.Singleton
|
|||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
object ChatModule {
|
object ChatModule {
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideSessionManager(@ApplicationContext context: Context): SessionManager {
|
|
||||||
return SessionManager(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideChatRepository(apiService: ApiService): UserRepository {
|
fun provideChatRepository(apiService: ApiService): UserRepository {
|
||||||
|
@ -10,7 +10,6 @@ import androidx.core.app.NotificationManagerCompat
|
|||||||
import com.alya.ecommerce_serang.R
|
import com.alya.ecommerce_serang.R
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
@ -41,12 +40,6 @@ object NotificationModule {
|
|||||||
return ApiConfig.getApiService(sessionManager)
|
return ApiConfig.getApiService(sessionManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideUserRepository(apiService: ApiService): UserRepository {
|
|
||||||
return UserRepository(apiService)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun provideNotificationBuilder(
|
fun provideNotificationBuilder(
|
||||||
|
@ -58,6 +58,7 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
val sessionManager = SessionManager(this)
|
val sessionManager = SessionManager(this)
|
||||||
sessionManager.saveToken(accessToken)
|
sessionManager.saveToken(accessToken)
|
||||||
|
// sessionManager.saveUserId(response.userId)
|
||||||
|
|
||||||
Toast.makeText(this, "Login Successful", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Login Successful", Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
@ -37,12 +37,27 @@ class RegisterActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityRegisterBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
sessionManager = SessionManager(this)
|
sessionManager = SessionManager(this)
|
||||||
if (!sessionManager.getToken().isNullOrEmpty()) {
|
Log.d("RegisterActivity", "Token in storage: '${sessionManager.getToken()}'")
|
||||||
// User already logged in, redirect to MainActivity
|
Log.d("RegisterActivity", "User ID in storage: '${sessionManager.getUserId()}'")
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use the new isLoggedIn method
|
||||||
|
if (sessionManager.isLoggedIn()) {
|
||||||
|
Log.d("RegisterActivity", "User logged in, redirecting to MainActivity")
|
||||||
startActivity(Intent(this, MainActivity::class.java))
|
startActivity(Intent(this, MainActivity::class.java))
|
||||||
finish()
|
finish()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
Log.d("RegisterActivity", "User not logged in, showing RegisterActivity")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Handle any exceptions
|
||||||
|
Log.e("RegisterActivity", "Error checking login status: ${e.message}", e)
|
||||||
|
// Clear potentially corrupt data
|
||||||
|
sessionManager.clearAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
@ -61,8 +76,7 @@ class RegisterActivity : AppCompatActivity() {
|
|||||||
windowInsets
|
windowInsets
|
||||||
}
|
}
|
||||||
|
|
||||||
binding = ActivityRegisterBinding.inflate(layoutInflater)
|
|
||||||
setContentView(binding.root)
|
|
||||||
// Observe OTP state
|
// Observe OTP state
|
||||||
observeOtpState()
|
observeOtpState()
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.alya.ecommerce_serang.ui.chat
|
package com.alya.ecommerce_serang.ui.chat
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
@ -19,24 +21,21 @@ import androidx.core.app.ActivityCompat
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
||||||
import com.alya.ecommerce_serang.R
|
import com.alya.ecommerce_serang.R
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
|
||||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
|
||||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
|
||||||
import com.alya.ecommerce_serang.databinding.ActivityChatBinding
|
import com.alya.ecommerce_serang.databinding.ActivityChatBinding
|
||||||
import com.alya.ecommerce_serang.ui.auth.LoginActivity
|
import com.alya.ecommerce_serang.ui.auth.LoginActivity
|
||||||
import com.alya.ecommerce_serang.ui.product.ProductUserViewModel
|
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
|
||||||
import com.alya.ecommerce_serang.utils.Constants
|
import com.alya.ecommerce_serang.utils.Constants
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.launch
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -47,27 +46,18 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var sessionManager: SessionManager
|
lateinit var sessionManager: SessionManager
|
||||||
private lateinit var socketService: SocketIOService
|
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private lateinit var chatAdapter: ChatAdapter
|
private lateinit var chatAdapter: ChatAdapter
|
||||||
|
|
||||||
private val viewModel: ChatViewModel by viewModels {
|
private val viewModel: ChatViewModel by viewModels()
|
||||||
BaseViewModelFactory {
|
|
||||||
val apiService = ApiConfig.getApiService(sessionManager)
|
|
||||||
val userRepository = UserRepository(apiService)
|
|
||||||
ChatViewModel(userRepository, socketService, sessionManager)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For image attachment
|
// For image attachment
|
||||||
private var tempImageUri: Uri? = null
|
private var tempImageUri: Uri? = null
|
||||||
|
|
||||||
// Chat parameters from intent
|
// // Chat parameters from intent
|
||||||
private var chatRoomId: Int = 0
|
// private var chatRoomId: Int = 0
|
||||||
private var storeId: Int = 0
|
// private var storeId: Int = 0
|
||||||
private var productId: Int = 0
|
// private var productId: Int = 0
|
||||||
|
|
||||||
// Typing indicator handler
|
// Typing indicator handler
|
||||||
private val typingHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
private val typingHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
||||||
@ -101,16 +91,40 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
binding = ActivityChatBinding.inflate(layoutInflater)
|
binding = ActivityChatBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
sessionManager = SessionManager(this)
|
||||||
|
Log.d("ChatActivity", "Token in storage: '${sessionManager.getToken()}'")
|
||||||
|
Log.d("ChatActivity", "User ID in storage: '${sessionManager.getUserId()}'")
|
||||||
|
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
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
|
// Get parameters from intent
|
||||||
chatRoomId = intent.getIntExtra(Constants.EXTRA_CHAT_ROOM_ID, 0)
|
val storeId = intent.getIntExtra(Constants.EXTRA_STORE_ID, 0)
|
||||||
storeId = intent.getIntExtra(Constants.EXTRA_STORE_ID, 0)
|
val productId = intent.getIntExtra(Constants.EXTRA_PRODUCT_ID, 0)
|
||||||
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) ?: ""
|
||||||
|
|
||||||
|
|
||||||
// Check if user is logged in
|
// Check if user is logged in
|
||||||
val userId = sessionManager.getUserId()
|
val userId = sessionManager.getUserId()
|
||||||
val token = sessionManager.getToken()
|
val token = sessionManager.getToken()
|
||||||
|
|
||||||
if (userId.isNullOrEmpty() || token.isNullOrEmpty()) {
|
if (token.isEmpty()) {
|
||||||
// User not logged in, redirect to login
|
// User not logged in, redirect to login
|
||||||
Toast.makeText(this, "Please login first", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Please login first", Toast.LENGTH_SHORT).show()
|
||||||
startActivity(Intent(this, LoginActivity::class.java))
|
startActivity(Intent(this, LoginActivity::class.java))
|
||||||
@ -118,30 +132,23 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Chat Activity started - User ID: $userId, Chat Room: $chatRoomId")
|
// Set chat parameters to ViewModel
|
||||||
|
viewModel.setChatParameters(
|
||||||
// Initialize ViewModel
|
storeId = storeId,
|
||||||
initViewModel()
|
productId = productId,
|
||||||
|
productName = productName,
|
||||||
|
productPrice = productPrice,
|
||||||
|
productImage = productImage,
|
||||||
|
productRating = productRating,
|
||||||
|
storeName = storeName
|
||||||
|
)
|
||||||
// Setup UI components
|
// Setup UI components
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setupListeners()
|
setupListeners()
|
||||||
setupTypingIndicator()
|
setupTypingIndicator()
|
||||||
observeViewModel()
|
observeViewModel()
|
||||||
}
|
|
||||||
|
|
||||||
private fun initViewModel() {
|
|
||||||
// Set chat parameters to ViewModel
|
|
||||||
viewModel.setChatParameters(
|
|
||||||
chatRoomId = chatRoomId,
|
|
||||||
storeId = storeId,
|
|
||||||
productId = productId,
|
|
||||||
productName = intent.getStringExtra(Constants.EXTRA_PRODUCT_NAME) ?: "",
|
|
||||||
productPrice = intent.getStringExtra(Constants.EXTRA_PRODUCT_PRICE) ?: "",
|
|
||||||
productImage = intent.getStringExtra(Constants.EXTRA_PRODUCT_IMAGE) ?: "",
|
|
||||||
productRating = intent.getFloatExtra(Constants.EXTRA_PRODUCT_RATING, 0f),
|
|
||||||
storeName = intent.getStringExtra(Constants.EXTRA_STORE_NAME) ?: ""
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
@ -154,6 +161,7 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun setupListeners() {
|
private fun setupListeners() {
|
||||||
// Back button
|
// Back button
|
||||||
binding.btnBack.setOnClickListener {
|
binding.btnBack.setOnClickListener {
|
||||||
@ -168,7 +176,8 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
// Send button
|
// Send button
|
||||||
binding.btnSend.setOnClickListener {
|
binding.btnSend.setOnClickListener {
|
||||||
val message = binding.editTextMessage.text.toString().trim()
|
val message = binding.editTextMessage.text.toString().trim()
|
||||||
if (message.isNotEmpty() || viewModel.state.value?.hasAttachment ?: false) {
|
val currentState = viewModel.state.value
|
||||||
|
if (message.isNotEmpty() || (currentState != null && currentState.hasAttachment)) {
|
||||||
viewModel.sendMessage(message)
|
viewModel.sendMessage(message)
|
||||||
binding.editTextMessage.text.clear()
|
binding.editTextMessage.text.clear()
|
||||||
}
|
}
|
||||||
@ -197,8 +206,19 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun observeViewModel() {
|
private fun observeViewModel() {
|
||||||
lifecycleScope.launch {
|
viewModel.chatRoomId.observe(this, Observer { chatRoomId ->
|
||||||
viewModel.state.collectLatest { state ->
|
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
|
// Update messages
|
||||||
chatAdapter.submitList(state.messages)
|
chatAdapter.submitList(state.messages)
|
||||||
|
|
||||||
@ -224,10 +244,6 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
.into(binding.imgProduct)
|
.into(binding.imgProduct)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show/hide loading indicators
|
|
||||||
// binding.progressBar.visibility = if (state.isLoading) View.VISIBLE else View.GONE
|
|
||||||
binding.btnSend.isEnabled = !state.isSending
|
|
||||||
|
|
||||||
// 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)
|
||||||
@ -239,37 +255,15 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
binding.tvTypingIndicator.visibility =
|
binding.tvTypingIndicator.visibility =
|
||||||
if (state.isOtherUserTyping) View.VISIBLE else View.GONE
|
if (state.isOtherUserTyping) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
// Handle connection state
|
|
||||||
handleConnectionState(state.connectionState)
|
|
||||||
|
|
||||||
// Show error if any
|
// Show error if any
|
||||||
state.error?.let { error ->
|
state.error?.let { error ->
|
||||||
Toast.makeText(this@ChatActivity, error, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@ChatActivity, error, Toast.LENGTH_SHORT).show()
|
||||||
viewModel.clearError()
|
viewModel.clearError()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showOptionsMenu() {
|
private fun showOptionsMenu() {
|
||||||
val options = arrayOf(
|
val options = arrayOf(
|
||||||
@ -388,5 +382,56 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "ChatActivity"
|
private const val TAG = "ChatActivity"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an intent to start the ChatActivity
|
||||||
|
*/
|
||||||
|
fun createIntent(
|
||||||
|
context: Activity,
|
||||||
|
storeId: Int,
|
||||||
|
productId: Int,
|
||||||
|
productName: String?,
|
||||||
|
productPrice: String,
|
||||||
|
productImage: String?,
|
||||||
|
productRating: String?,
|
||||||
|
storeName: String?,
|
||||||
|
chatRoomId: Int = 0
|
||||||
|
){
|
||||||
|
val intent = Intent(context, ChatActivity::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_PRODUCT_RATING, productRating)
|
||||||
|
putExtra(Constants.EXTRA_STORE_NAME, storeName)
|
||||||
|
|
||||||
|
if (chatRoomId > 0) {
|
||||||
|
putExtra(Constants.EXTRA_CHAT_ROOM_ID, chatRoomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//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)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
@ -10,9 +10,10 @@ import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
|||||||
import com.alya.ecommerce_serang.R
|
import com.alya.ecommerce_serang.R
|
||||||
import com.alya.ecommerce_serang.databinding.ItemMessageReceivedBinding
|
import com.alya.ecommerce_serang.databinding.ItemMessageReceivedBinding
|
||||||
import com.alya.ecommerce_serang.databinding.ItemMessageSentBinding
|
import com.alya.ecommerce_serang.databinding.ItemMessageSentBinding
|
||||||
|
import com.alya.ecommerce_serang.utils.Constants
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
|
||||||
class ChatAdapter : ListAdapter<ChatUiMessage, RecyclerView.ViewHolder>(ChatDiffCallback()) {
|
class ChatAdapter : ListAdapter<ChatUiMessage, RecyclerView.ViewHolder>(ChatMessageDiffCallback()) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val VIEW_TYPE_MESSAGE_SENT = 1
|
private const val VIEW_TYPE_MESSAGE_SENT = 1
|
||||||
@ -67,10 +68,10 @@ class ChatAdapter : ListAdapter<ChatUiMessage, RecyclerView.ViewHolder>(ChatDiff
|
|||||||
|
|
||||||
// Show message status
|
// Show message status
|
||||||
val statusIcon = when (message.status) {
|
val statusIcon = when (message.status) {
|
||||||
Constants.STATUS_SENT -> R.drawable.ic_check
|
Constants.STATUS_SENT -> R.drawable.check_single_24
|
||||||
Constants.STATUS_DELIVERED -> R.drawable.ic_double_check
|
Constants.STATUS_DELIVERED -> R.drawable.check_double_24
|
||||||
Constants.STATUS_READ -> R.drawable.ic_double_check_read
|
Constants.STATUS_READ -> R.drawable.check_double_read_24
|
||||||
else -> R.drawable.ic_check
|
else -> R.drawable.check_single_24
|
||||||
}
|
}
|
||||||
binding.imgStatus.setImageResource(statusIcon)
|
binding.imgStatus.setImageResource(statusIcon)
|
||||||
|
|
||||||
@ -114,7 +115,7 @@ class ChatAdapter : ListAdapter<ChatUiMessage, RecyclerView.ViewHolder>(ChatDiff
|
|||||||
|
|
||||||
// Load avatar image
|
// Load avatar image
|
||||||
Glide.with(binding.root.context)
|
Glide.with(binding.root.context)
|
||||||
.load(R.drawable.ic_person) // Replace with actual avatar URL if available
|
.load(R.drawable.placeholder_image) // Replace with actual avatar URL if available
|
||||||
.circleCrop()
|
.circleCrop()
|
||||||
.into(binding.imgAvatar)
|
.into(binding.imgAvatar)
|
||||||
}
|
}
|
||||||
@ -122,9 +123,9 @@ class ChatAdapter : ListAdapter<ChatUiMessage, RecyclerView.ViewHolder>(ChatDiff
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DiffCallback for optimizing RecyclerView updates
|
* DiffUtil callback for optimizing RecyclerView updates
|
||||||
*/
|
*/
|
||||||
class ChatDiffCallback : DiffUtil.ItemCallback<ChatUiMessage>() {
|
class ChatMessageDiffCallback : DiffUtil.ItemCallback<ChatUiMessage>() {
|
||||||
override fun areItemsTheSame(oldItem: ChatUiMessage, newItem: ChatUiMessage): Boolean {
|
override fun areItemsTheSame(oldItem: ChatUiMessage, newItem: ChatUiMessage): Boolean {
|
||||||
return oldItem.id == newItem.id
|
return oldItem.id == newItem.id
|
||||||
}
|
}
|
||||||
|
@ -1,343 +1,337 @@
|
|||||||
package com.alya.ecommerce_serang.ui.chat
|
//package com.alya.ecommerce_serang.ui.chat
|
||||||
|
//
|
||||||
import android.Manifest
|
//import android.Manifest
|
||||||
import android.app.Activity
|
//import android.app.Activity
|
||||||
import android.content.Intent
|
//import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
//import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
//import android.net.Uri
|
||||||
import android.os.Bundle
|
//import android.os.Bundle
|
||||||
import android.provider.MediaStore
|
//import android.provider.MediaStore
|
||||||
import android.text.Editable
|
//import android.text.Editable
|
||||||
import android.text.TextWatcher
|
//import android.text.TextWatcher
|
||||||
import androidx.fragment.app.Fragment
|
//import androidx.fragment.app.Fragment
|
||||||
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 android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
//import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.app.ActivityCompat
|
//import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
//import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
//import androidx.core.content.FileProvider
|
||||||
import androidx.fragment.app.viewModels
|
//import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
//import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.navArgs
|
//import androidx.navigation.fragment.navArgs
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
//import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
//import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
||||||
import com.alya.ecommerce_serang.R
|
//import com.alya.ecommerce_serang.R
|
||||||
import com.alya.ecommerce_serang.databinding.FragmentChatBinding
|
//import com.alya.ecommerce_serang.databinding.FragmentChatBinding
|
||||||
import com.alya.ecommerce_serang.utils.Constants
|
//import com.alya.ecommerce_serang.utils.Constants
|
||||||
import com.bumptech.glide.Glide
|
//import com.bumptech.glide.Glide
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
//import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.launch
|
//import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
//import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
//import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
//import java.util.Locale
|
||||||
|
//
|
||||||
|
//@AndroidEntryPoint
|
||||||
/**
|
//class ChatFragment : Fragment() {
|
||||||
* A simple [Fragment] subclass.
|
//
|
||||||
* Use the [ChatFragment.newInstance] factory method to
|
// private var _binding: FragmentChatBinding? = null
|
||||||
* create an instance of this fragment.
|
// private val binding get() = _binding!!
|
||||||
*/
|
//
|
||||||
@AndroidEntryPoint
|
// private val viewModel: ChatViewModel by viewModels()
|
||||||
class ChatFragment : Fragment() {
|
//// private val args: ChatFragmentArgs by navArgs()
|
||||||
|
//
|
||||||
private var _binding: FragmentChatBinding? = null
|
// private lateinit var chatAdapter: ChatAdapter
|
||||||
private val binding get() = _binding!!
|
//
|
||||||
|
// // For image attachment
|
||||||
private val viewModel: ChatViewModel by viewModels()
|
// private var tempImageUri: Uri? = null
|
||||||
private val args: ChatFragmentArgs by navArgs()
|
//
|
||||||
|
// // Typing indicator handler
|
||||||
private lateinit var chatAdapter: ChatAdapter
|
// private val typingHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
||||||
|
// private val stopTypingRunnable = Runnable {
|
||||||
// For image attachment
|
// viewModel.sendTypingStatus(false)
|
||||||
private var tempImageUri: Uri? = null
|
// }
|
||||||
|
//
|
||||||
// Typing indicator handler
|
// // Activity Result Launchers
|
||||||
private val typingHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
// private val pickImageLauncher = registerForActivityResult(
|
||||||
private val stopTypingRunnable = Runnable {
|
// ActivityResultContracts.StartActivityForResult()
|
||||||
viewModel.sendTypingStatus(false)
|
// ) { result ->
|
||||||
}
|
// if (result.resultCode == Activity.RESULT_OK) {
|
||||||
|
// result.data?.data?.let { uri ->
|
||||||
// Activity Result Launchers
|
// handleSelectedImage(uri)
|
||||||
private val pickImageLauncher = registerForActivityResult(
|
// }
|
||||||
ActivityResultContracts.StartActivityForResult()
|
// }
|
||||||
) { result ->
|
// }
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
//
|
||||||
result.data?.data?.let { uri ->
|
// private val takePictureLauncher = registerForActivityResult(
|
||||||
handleSelectedImage(uri)
|
// ActivityResultContracts.StartActivityForResult()
|
||||||
}
|
// ) { result ->
|
||||||
}
|
// if (result.resultCode == Activity.RESULT_OK) {
|
||||||
}
|
// tempImageUri?.let { uri ->
|
||||||
|
// handleSelectedImage(uri)
|
||||||
private val takePictureLauncher = registerForActivityResult(
|
// }
|
||||||
ActivityResultContracts.StartActivityForResult()
|
// }
|
||||||
) { result ->
|
// }
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
//
|
||||||
tempImageUri?.let { uri ->
|
// override fun onCreateView(
|
||||||
handleSelectedImage(uri)
|
// inflater: LayoutInflater,
|
||||||
}
|
// container: ViewGroup?,
|
||||||
}
|
// savedInstanceState: Bundle?
|
||||||
}
|
// ): View {
|
||||||
|
// _binding = FragmentChatBinding.inflate(inflater, container, false)
|
||||||
override fun onCreateView(
|
// return binding.root
|
||||||
inflater: LayoutInflater,
|
// }
|
||||||
container: ViewGroup?,
|
//
|
||||||
savedInstanceState: Bundle?
|
// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
): View {
|
// super.onViewCreated(view, savedInstanceState)
|
||||||
_binding = FragmentChatBinding.inflate(inflater, container, false)
|
//
|
||||||
return binding.root
|
// setupRecyclerView()
|
||||||
}
|
// setupListeners()
|
||||||
|
// setupTypingIndicator()
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
// observeViewModel()
|
||||||
super.onViewCreated(view, savedInstanceState)
|
// }
|
||||||
|
//
|
||||||
setupRecyclerView()
|
// private fun setupRecyclerView() {
|
||||||
setupListeners()
|
// chatAdapter = ChatAdapter()
|
||||||
setupTypingIndicator()
|
// binding.recyclerChat.apply {
|
||||||
observeViewModel()
|
// adapter = chatAdapter
|
||||||
}
|
// layoutManager = LinearLayoutManager(requireContext()).apply {
|
||||||
|
// stackFromEnd = true
|
||||||
private fun setupRecyclerView() {
|
// }
|
||||||
chatAdapter = ChatAdapter()
|
// }
|
||||||
binding.recyclerChat.apply {
|
// }
|
||||||
adapter = chatAdapter
|
//
|
||||||
layoutManager = LinearLayoutManager(requireContext()).apply {
|
// private fun setupListeners() {
|
||||||
stackFromEnd = true
|
// // Back button
|
||||||
}
|
// binding.btnBack.setOnClickListener {
|
||||||
}
|
// requireActivity().onBackPressed()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
private fun setupListeners() {
|
// // Options button
|
||||||
// Back button
|
// binding.btnOptions.setOnClickListener {
|
||||||
binding.btnBack.setOnClickListener {
|
// showOptionsMenu()
|
||||||
requireActivity().onBackPressed()
|
// }
|
||||||
}
|
//
|
||||||
|
// // Send button
|
||||||
// Options button
|
// binding.btnSend.setOnClickListener {
|
||||||
binding.btnOptions.setOnClickListener {
|
// val message = binding.editTextMessage.text.toString().trim()
|
||||||
showOptionsMenu()
|
// if (message.isNotEmpty() || viewModel.state.value.hasAttachment) {
|
||||||
}
|
// viewModel.sendMessage(message)
|
||||||
|
// binding.editTextMessage.text.clear()
|
||||||
// Send button
|
// }
|
||||||
binding.btnSend.setOnClickListener {
|
// }
|
||||||
val message = binding.editTextMessage.text.toString().trim()
|
//
|
||||||
if (message.isNotEmpty() || viewModel.state.value.hasAttachment) {
|
// // Attachment button
|
||||||
viewModel.sendMessage(message)
|
// binding.btnAttachment.setOnClickListener {
|
||||||
binding.editTextMessage.text.clear()
|
// checkPermissionsAndShowImagePicker()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// Attachment button
|
// private fun setupTypingIndicator() {
|
||||||
binding.btnAttachment.setOnClickListener {
|
// binding.editTextMessage.addTextChangedListener(object : TextWatcher {
|
||||||
checkPermissionsAndShowImagePicker()
|
// 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)
|
||||||
private fun setupTypingIndicator() {
|
//
|
||||||
binding.editTextMessage.addTextChangedListener(object : TextWatcher {
|
// // Reset the timer
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
// typingHandler.removeCallbacks(stopTypingRunnable)
|
||||||
|
// typingHandler.postDelayed(stopTypingRunnable, 1000)
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
// }
|
||||||
viewModel.sendTypingStatus(true)
|
//
|
||||||
|
// override fun afterTextChanged(s: Editable?) {}
|
||||||
// Reset the timer
|
// })
|
||||||
typingHandler.removeCallbacks(stopTypingRunnable)
|
// }
|
||||||
typingHandler.postDelayed(stopTypingRunnable, 1000)
|
//
|
||||||
}
|
// private fun observeViewModel() {
|
||||||
|
// viewLifecycleOwner.lifecycleScope.launch {
|
||||||
override fun afterTextChanged(s: Editable?) {}
|
// viewModel.state.collectLatest { state ->
|
||||||
})
|
// // Update messages
|
||||||
}
|
// chatAdapter.submitList(state.messages)
|
||||||
|
//
|
||||||
private fun observeViewModel() {
|
// // Scroll to bottom if new message
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
// if (state.messages.isNotEmpty()) {
|
||||||
viewModel.state.collectLatest { state ->
|
// binding.recyclerChat.scrollToPosition(state.messages.size - 1)
|
||||||
// Update messages
|
// }
|
||||||
chatAdapter.submitList(state.messages)
|
//
|
||||||
|
// // Update product info
|
||||||
// Scroll to bottom if new message
|
// binding.tvProductName.text = state.productName
|
||||||
if (state.messages.isNotEmpty()) {
|
// binding.tvProductPrice.text = state.productPrice
|
||||||
binding.recyclerChat.scrollToPosition(state.messages.size - 1)
|
// binding.ratingBar.rating = state.productRating
|
||||||
}
|
// binding.tvRating.text = state.productRating.toString()
|
||||||
|
// binding.tvSellerName.text = state.storeName
|
||||||
// Update product info
|
//
|
||||||
binding.tvProductName.text = state.productName
|
// // Load product image
|
||||||
binding.tvProductPrice.text = state.productPrice
|
// if (state.productImageUrl.isNotEmpty()) {
|
||||||
binding.ratingBar.rating = state.productRating
|
// Glide.with(requireContext())
|
||||||
binding.tvRating.text = state.productRating.toString()
|
// .load(BASE_URL + state.productImageUrl)
|
||||||
binding.tvSellerName.text = state.storeName
|
// .centerCrop()
|
||||||
|
// .placeholder(R.drawable.placeholder_image)
|
||||||
// Load product image
|
// .error(R.drawable.placeholder_image)
|
||||||
if (state.productImageUrl.isNotEmpty()) {
|
// .into(binding.imgProduct)
|
||||||
Glide.with(requireContext())
|
// }
|
||||||
.load(BASE_URL + state.productImageUrl)
|
//
|
||||||
.centerCrop()
|
// // Show/hide loading indicators
|
||||||
.placeholder(R.drawable.placeholder_image)
|
// binding.progressBar.visibility = if (state.isLoading) View.VISIBLE else View.GONE
|
||||||
.error(R.drawable.placeholder_image)
|
// binding.btnSend.isEnabled = !state.isSending
|
||||||
.into(binding.imgProduct)
|
//
|
||||||
}
|
// // Update attachment hint
|
||||||
|
// if (state.hasAttachment) {
|
||||||
// Show/hide loading indicators
|
// binding.editTextMessage.hint = getString(R.string.image_attached)
|
||||||
binding.progressBar.visibility = if (state.isLoading) View.VISIBLE else View.GONE
|
// } else {
|
||||||
binding.btnSend.isEnabled = !state.isSending
|
// binding.editTextMessage.hint = getString(R.string.write_message)
|
||||||
|
// }
|
||||||
// Update attachment hint
|
//
|
||||||
if (state.hasAttachment) {
|
// // Show typing indicator
|
||||||
binding.editTextMessage.hint = getString(R.string.image_attached)
|
// binding.tvTypingIndicator.visibility =
|
||||||
} else {
|
// if (state.isOtherUserTyping) View.VISIBLE else View.GONE
|
||||||
binding.editTextMessage.hint = getString(R.string.write_message)
|
//
|
||||||
}
|
// // Handle connection state
|
||||||
|
// handleConnectionState(state.connectionState)
|
||||||
// Show typing indicator
|
//
|
||||||
binding.tvTypingIndicator.visibility =
|
// // Show error if any
|
||||||
if (state.isOtherUserTyping) View.VISIBLE else View.GONE
|
// state.error?.let { error ->
|
||||||
|
// Toast.makeText(requireContext(), error, Toast.LENGTH_SHORT).show()
|
||||||
// Handle connection state
|
// viewModel.clearError()
|
||||||
handleConnectionState(state.connectionState)
|
// }
|
||||||
|
// }
|
||||||
// Show error if any
|
// }
|
||||||
state.error?.let { error ->
|
// }
|
||||||
Toast.makeText(requireContext(), error, Toast.LENGTH_SHORT).show()
|
//
|
||||||
viewModel.clearError()
|
// private fun handleConnectionState(state: ConnectionState) {
|
||||||
}
|
// when (state) {
|
||||||
}
|
// is ConnectionState.Connected -> {
|
||||||
}
|
// binding.tvConnectionStatus.visibility = View.GONE
|
||||||
}
|
// }
|
||||||
|
// is ConnectionState.Connecting -> {
|
||||||
private fun handleConnectionState(state: ConnectionState) {
|
// binding.tvConnectionStatus.visibility = View.VISIBLE
|
||||||
when (state) {
|
// binding.tvConnectionStatus.text = getString(R.string.connecting)
|
||||||
is ConnectionState.Connected -> {
|
// }
|
||||||
binding.tvConnectionStatus.visibility = View.GONE
|
// is ConnectionState.Disconnected -> {
|
||||||
}
|
// binding.tvConnectionStatus.visibility = View.VISIBLE
|
||||||
is ConnectionState.Connecting -> {
|
// binding.tvConnectionStatus.text = getString(R.string.disconnected_reconnecting)
|
||||||
binding.tvConnectionStatus.visibility = View.VISIBLE
|
// }
|
||||||
binding.tvConnectionStatus.text = getString(R.string.connecting)
|
// is ConnectionState.Error -> {
|
||||||
}
|
// binding.tvConnectionStatus.visibility = View.VISIBLE
|
||||||
is ConnectionState.Disconnected -> {
|
// binding.tvConnectionStatus.text = getString(R.string.connection_error, state.message)
|
||||||
binding.tvConnectionStatus.visibility = View.VISIBLE
|
// }
|
||||||
binding.tvConnectionStatus.text = getString(R.string.disconnected_reconnecting)
|
// }
|
||||||
}
|
// }
|
||||||
is ConnectionState.Error -> {
|
//
|
||||||
binding.tvConnectionStatus.visibility = View.VISIBLE
|
// private fun showOptionsMenu() {
|
||||||
binding.tvConnectionStatus.text = getString(R.string.connection_error, state.message)
|
// val options = arrayOf(
|
||||||
}
|
// getString(R.string.block_user),
|
||||||
}
|
// getString(R.string.report),
|
||||||
}
|
// getString(R.string.clear_chat),
|
||||||
|
// getString(R.string.cancel)
|
||||||
private fun showOptionsMenu() {
|
// )
|
||||||
val options = arrayOf(
|
//
|
||||||
getString(R.string.block_user),
|
// androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||||
getString(R.string.report),
|
// .setTitle(getString(R.string.options))
|
||||||
getString(R.string.clear_chat),
|
// .setItems(options) { dialog, which ->
|
||||||
getString(R.string.cancel)
|
// when (which) {
|
||||||
)
|
// 0 -> Toast.makeText(requireContext(), R.string.block_user_selected, Toast.LENGTH_SHORT).show()
|
||||||
|
// 1 -> Toast.makeText(requireContext(), R.string.report_selected, Toast.LENGTH_SHORT).show()
|
||||||
androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
// 2 -> Toast.makeText(requireContext(), R.string.clear_chat_selected, Toast.LENGTH_SHORT).show()
|
||||||
.setTitle(getString(R.string.options))
|
// }
|
||||||
.setItems(options) { dialog, which ->
|
// dialog.dismiss()
|
||||||
when (which) {
|
// }
|
||||||
0 -> Toast.makeText(requireContext(), R.string.block_user_selected, Toast.LENGTH_SHORT).show()
|
// .show()
|
||||||
1 -> Toast.makeText(requireContext(), R.string.report_selected, Toast.LENGTH_SHORT).show()
|
// }
|
||||||
2 -> Toast.makeText(requireContext(), R.string.clear_chat_selected, Toast.LENGTH_SHORT).show()
|
//
|
||||||
}
|
// private fun checkPermissionsAndShowImagePicker() {
|
||||||
dialog.dismiss()
|
// if (ContextCompat.checkSelfPermission(
|
||||||
}
|
// requireContext(),
|
||||||
.show()
|
// Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
}
|
// ) != PackageManager.PERMISSION_GRANTED
|
||||||
|
// ) {
|
||||||
private fun checkPermissionsAndShowImagePicker() {
|
// ActivityCompat.requestPermissions(
|
||||||
if (ContextCompat.checkSelfPermission(
|
// requireActivity(),
|
||||||
requireContext(),
|
// arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA),
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
// Constants.REQUEST_STORAGE_PERMISSION
|
||||||
) != PackageManager.PERMISSION_GRANTED
|
// )
|
||||||
) {
|
// } else {
|
||||||
ActivityCompat.requestPermissions(
|
// showImagePickerOptions()
|
||||||
requireActivity(),
|
// }
|
||||||
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA),
|
// }
|
||||||
Constants.REQUEST_STORAGE_PERMISSION
|
//
|
||||||
)
|
// private fun showImagePickerOptions() {
|
||||||
} else {
|
// val options = arrayOf(
|
||||||
showImagePickerOptions()
|
// getString(R.string.take_photo),
|
||||||
}
|
// getString(R.string.choose_from_gallery),
|
||||||
}
|
// getString(R.string.cancel)
|
||||||
|
// )
|
||||||
private fun showImagePickerOptions() {
|
//
|
||||||
val options = arrayOf(
|
// androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||||
getString(R.string.take_photo),
|
// .setTitle(getString(R.string.select_attachment))
|
||||||
getString(R.string.choose_from_gallery),
|
// .setItems(options) { dialog, which ->
|
||||||
getString(R.string.cancel)
|
// when (which) {
|
||||||
)
|
// 0 -> openCamera()
|
||||||
|
// 1 -> openGallery()
|
||||||
androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
// }
|
||||||
.setTitle(getString(R.string.select_attachment))
|
// dialog.dismiss()
|
||||||
.setItems(options) { dialog, which ->
|
// }
|
||||||
when (which) {
|
// .show()
|
||||||
0 -> openCamera()
|
// }
|
||||||
1 -> openGallery()
|
//
|
||||||
}
|
// private fun openCamera() {
|
||||||
dialog.dismiss()
|
// val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
||||||
}
|
// val imageFileName = "IMG_${timeStamp}.jpg"
|
||||||
.show()
|
// val storageDir = requireContext().getExternalFilesDir(null)
|
||||||
}
|
// val imageFile = File(storageDir, imageFileName)
|
||||||
|
//
|
||||||
private fun openCamera() {
|
// tempImageUri = FileProvider.getUriForFile(
|
||||||
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
// requireContext(),
|
||||||
val imageFileName = "IMG_${timeStamp}.jpg"
|
// "${requireContext().packageName}.fileprovider",
|
||||||
val storageDir = requireContext().getExternalFilesDir(null)
|
// imageFile
|
||||||
val imageFile = File(storageDir, imageFileName)
|
// )
|
||||||
|
//
|
||||||
tempImageUri = FileProvider.getUriForFile(
|
// val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
|
||||||
requireContext(),
|
// putExtra(MediaStore.EXTRA_OUTPUT, tempImageUri)
|
||||||
"${requireContext().packageName}.fileprovider",
|
// }
|
||||||
imageFile
|
//
|
||||||
)
|
// takePictureLauncher.launch(intent)
|
||||||
|
// }
|
||||||
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
|
//
|
||||||
putExtra(MediaStore.EXTRA_OUTPUT, tempImageUri)
|
// private fun openGallery() {
|
||||||
}
|
// val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
|
||||||
|
// pickImageLauncher.launch(intent)
|
||||||
takePictureLauncher.launch(intent)
|
// }
|
||||||
}
|
//
|
||||||
|
// private fun handleSelectedImage(uri: Uri) {
|
||||||
private fun openGallery() {
|
// // Get the file from Uri
|
||||||
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
|
// val filePathColumn = arrayOf(MediaStore.Images.Media.DATA)
|
||||||
pickImageLauncher.launch(intent)
|
// val cursor = requireContext().contentResolver.query(uri, filePathColumn, null, null, null)
|
||||||
}
|
// cursor?.moveToFirst()
|
||||||
|
// val columnIndex = cursor?.getColumnIndex(filePathColumn[0])
|
||||||
private fun handleSelectedImage(uri: Uri) {
|
// val filePath = cursor?.getString(columnIndex ?: 0)
|
||||||
// Get the file from Uri
|
// cursor?.close()
|
||||||
val filePathColumn = arrayOf(MediaStore.Images.Media.DATA)
|
//
|
||||||
val cursor = requireContext().contentResolver.query(uri, filePathColumn, null, null, null)
|
// if (filePath != null) {
|
||||||
cursor?.moveToFirst()
|
// viewModel.setSelectedImageFile(File(filePath))
|
||||||
val columnIndex = cursor?.getColumnIndex(filePathColumn[0])
|
// Toast.makeText(requireContext(), R.string.image_selected, Toast.LENGTH_SHORT).show()
|
||||||
val filePath = cursor?.getString(columnIndex ?: 0)
|
// }
|
||||||
cursor?.close()
|
// }
|
||||||
|
//
|
||||||
if (filePath != null) {
|
// override fun onRequestPermissionsResult(
|
||||||
viewModel.setSelectedImageFile(File(filePath))
|
// requestCode: Int,
|
||||||
Toast.makeText(requireContext(), R.string.image_selected, Toast.LENGTH_SHORT).show()
|
// permissions: Array<out String>,
|
||||||
}
|
// grantResults: IntArray
|
||||||
}
|
// ) {
|
||||||
|
// super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
override fun onRequestPermissionsResult(
|
// if (requestCode == Constants.REQUEST_STORAGE_PERMISSION) {
|
||||||
requestCode: Int,
|
// if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
permissions: Array<out String>,
|
// showImagePickerOptions()
|
||||||
grantResults: IntArray
|
// } else {
|
||||||
) {
|
// Toast.makeText(requireContext(), R.string.permission_denied, Toast.LENGTH_SHORT).show()
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
// }
|
||||||
if (requestCode == Constants.REQUEST_STORAGE_PERMISSION) {
|
// }
|
||||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
// }
|
||||||
showImagePickerOptions()
|
//
|
||||||
} else {
|
// override fun onDestroyView() {
|
||||||
Toast.makeText(requireContext(), R.string.permission_denied, Toast.LENGTH_SHORT).show()
|
// super.onDestroyView()
|
||||||
}
|
// typingHandler.removeCallbacks(stopTypingRunnable)
|
||||||
}
|
// _binding = null
|
||||||
}
|
// }
|
||||||
|
//}
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
typingHandler.removeCallbacks(stopTypingRunnable)
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +1,56 @@
|
|||||||
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 androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import com.alya.ecommerce_serang.R
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.utils.viewmodel.ChatViewModel
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.FragmentChatListBinding
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
|
||||||
class ChatListFragment : Fragment() {
|
class ChatListFragment : Fragment() {
|
||||||
|
|
||||||
companion object {
|
private var _binding: FragmentChatListBinding? = null
|
||||||
fun newInstance() = ChatListFragment()
|
|
||||||
|
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 userRepository = UserRepository(apiService)
|
||||||
|
ChatViewModel(userRepository, socketService, sessionManager)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val viewModel: ChatViewModel by viewModels()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
sessionManager = SessionManager(requireContext())
|
||||||
|
|
||||||
// TODO: Use the ViewModel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
return inflater.inflate(R.layout.fragment_chat_list, container, false)
|
_binding = FragmentChatListBinding.inflate(inflater, container, false)
|
||||||
|
return _binding!!.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
setupView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupView(){
|
||||||
|
binding.btnTrial.setOnClickListener{
|
||||||
|
val intent = Intent(requireContext(), ChatActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,12 +11,14 @@ import com.alya.ecommerce_serang.data.repository.Result
|
|||||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
import com.alya.ecommerce_serang.utils.Constants
|
import com.alya.ecommerce_serang.utils.Constants
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
class ChatViewModel @Inject constructor(
|
class ChatViewModel @Inject constructor(
|
||||||
private val chatRepository: UserRepository,
|
private val chatRepository: UserRepository,
|
||||||
private val socketService: SocketIOService,
|
private val socketService: SocketIOService,
|
||||||
@ -29,11 +31,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
|
||||||
|
|
||||||
// Chat parameters
|
private val _chatRoomId = MutableLiveData<Int>(0)
|
||||||
private var chatRoomId: Int = 0
|
val chatRoomId: LiveData<Int> = _chatRoomId
|
||||||
|
|
||||||
|
// 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 = 0
|
private var currentUserId: Int? = 0
|
||||||
|
private var defaultUserId: Int = 0
|
||||||
|
|
||||||
|
|
||||||
// Product details for display
|
// Product details for display
|
||||||
private var productName: String = ""
|
private var productName: String = ""
|
||||||
@ -47,8 +53,13 @@ class ChatViewModel @Inject constructor(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
// Try to get current user ID from SessionManager
|
// Try to get current user ID from SessionManager
|
||||||
currentUserId = sessionManager.getUserId()?.toIntOrNull() ?: 0
|
viewModelScope.launch {
|
||||||
|
when (val result = chatRepository.fetchUserProfile()) {
|
||||||
|
is Result.Success -> {
|
||||||
|
currentUserId = result.data?.userId
|
||||||
|
Log.e(TAG, "User ID: $currentUserId")
|
||||||
|
|
||||||
|
// Move the validation and subsequent logic inside the coroutine
|
||||||
if (currentUserId == 0) {
|
if (currentUserId == 0) {
|
||||||
Log.e(TAG, "Error: User ID is not set or invalid")
|
Log.e(TAG, "Error: User ID is not set or invalid")
|
||||||
updateState { it.copy(error = "User authentication error. Please login again.") }
|
updateState { it.copy(error = "User authentication error. Please login again.") }
|
||||||
@ -57,12 +68,21 @@ class ChatViewModel @Inject constructor(
|
|||||||
setupSocketListeners()
|
setupSocketListeners()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
Log.e(TAG, "Error fetching user profile: ${result.exception.message}")
|
||||||
|
updateState { it.copy(error = "User authentication error. Please login again.") }
|
||||||
|
}
|
||||||
|
is Result.Loading -> {
|
||||||
|
// Handle loading state if needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set chat parameters received from activity
|
* Set chat parameters received from activity
|
||||||
*/
|
*/
|
||||||
fun setChatParameters(
|
fun setChatParameters(
|
||||||
chatRoomId: Int,
|
|
||||||
storeId: Int,
|
storeId: Int,
|
||||||
productId: Int,
|
productId: Int,
|
||||||
productName: String,
|
productName: String,
|
||||||
@ -71,7 +91,6 @@ class ChatViewModel @Inject constructor(
|
|||||||
productRating: Float,
|
productRating: Float,
|
||||||
storeName: String
|
storeName: String
|
||||||
) {
|
) {
|
||||||
this.chatRoomId = chatRoomId
|
|
||||||
this.storeId = storeId
|
this.storeId = storeId
|
||||||
this.productId = productId
|
this.productId = productId
|
||||||
this.productName = productName
|
this.productName = productName
|
||||||
@ -92,8 +111,23 @@ class ChatViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Connect to socket and load chat history
|
// Connect to socket and load chat history
|
||||||
socketService.connect()
|
val existingChatRoomId = _chatRoomId.value ?: 0
|
||||||
loadChatHistory()
|
if (existingChatRoomId > 0) {
|
||||||
|
// If we already have a chat room ID, we can load the chat history
|
||||||
|
loadChatHistory(existingChatRoomId)
|
||||||
|
|
||||||
|
// And join the Socket.IO room
|
||||||
|
joinSocketRoom(existingChatRoomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun joinSocketRoom(roomId: Int) {
|
||||||
|
if (roomId <= 0) {
|
||||||
|
Log.e(TAG, "Cannot join room: Invalid room ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
socketService.joinRoom()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,7 +168,7 @@ class ChatViewModel @Inject constructor(
|
|||||||
// Listen for typing status updates
|
// Listen for typing status updates
|
||||||
socketService.typingStatus.collect { typingStatus ->
|
socketService.typingStatus.collect { typingStatus ->
|
||||||
typingStatus?.let {
|
typingStatus?.let {
|
||||||
if (typingStatus.roomId == chatRoomId && typingStatus.userId != currentUserId) {
|
if (typingStatus.roomId == (_chatRoomId.value ?: 0) && typingStatus.userId != currentUserId) {
|
||||||
updateState { it.copy(isOtherUserTyping = typingStatus.isTyping) }
|
updateState { it.copy(isOtherUserTyping = typingStatus.isTyping) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,8 +188,8 @@ class ChatViewModel @Inject constructor(
|
|||||||
/**
|
/**
|
||||||
* Loads chat history
|
* Loads chat history
|
||||||
*/
|
*/
|
||||||
fun loadChatHistory() {
|
fun loadChatHistory(chatRoomId : Int) {
|
||||||
if (chatRoomId == 0) {
|
if (chatRoomId <= 0) {
|
||||||
Log.e(TAG, "Cannot load chat history: Chat room ID is 0")
|
Log.e(TAG, "Cannot load chat history: Chat room ID is 0")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -242,6 +276,17 @@ class ChatViewModel @Inject constructor(
|
|||||||
|
|
||||||
Log.d(TAG, "Message sent successfully: ${chatLine.id}")
|
Log.d(TAG, "Message sent successfully: ${chatLine.id}")
|
||||||
|
|
||||||
|
// 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
|
// Emit the message via Socket.IO for real-time updates
|
||||||
socketService.sendMessage(chatLine)
|
socketService.sendMessage(chatLine)
|
||||||
|
|
||||||
@ -308,9 +353,10 @@ class ChatViewModel @Inject constructor(
|
|||||||
* Sends typing status to the other user
|
* Sends typing status to the other user
|
||||||
*/
|
*/
|
||||||
fun sendTypingStatus(isTyping: Boolean) {
|
fun sendTypingStatus(isTyping: Boolean) {
|
||||||
if (chatRoomId == 0) return
|
val roomId = _chatRoomId.value ?: 0
|
||||||
|
if (roomId <= 0) return
|
||||||
|
|
||||||
socketService.sendTypingStatus(chatRoomId, isTyping)
|
socketService.sendTypingStatus(roomId, isTyping)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,6 +28,7 @@ import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
|||||||
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
import com.alya.ecommerce_serang.data.repository.ProductRepository
|
||||||
import com.alya.ecommerce_serang.data.repository.Result
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding
|
import com.alya.ecommerce_serang.databinding.ActivityDetailProductBinding
|
||||||
|
import com.alya.ecommerce_serang.ui.chat.ChatActivity
|
||||||
import com.alya.ecommerce_serang.ui.home.HorizontalProductAdapter
|
import com.alya.ecommerce_serang.ui.home.HorizontalProductAdapter
|
||||||
import com.alya.ecommerce_serang.ui.order.CheckoutActivity
|
import com.alya.ecommerce_serang.ui.order.CheckoutActivity
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
@ -45,7 +46,6 @@ class DetailProductActivity : AppCompatActivity() {
|
|||||||
private var reviewsAdapter: ReviewsAdapter? = null
|
private var reviewsAdapter: ReviewsAdapter? = null
|
||||||
private var currentQuantity = 1
|
private var currentQuantity = 1
|
||||||
|
|
||||||
|
|
||||||
private val viewModel: ProductUserViewModel by viewModels {
|
private val viewModel: ProductUserViewModel by viewModels {
|
||||||
BaseViewModelFactory {
|
BaseViewModelFactory {
|
||||||
val apiService = ApiConfig.getApiService(sessionManager)
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
@ -219,6 +219,9 @@ class DetailProductActivity : AppCompatActivity() {
|
|||||||
binding.tvDescription.text = product.description
|
binding.tvDescription.text = product.description
|
||||||
|
|
||||||
|
|
||||||
|
binding.btnChat.setOnClickListener{
|
||||||
|
navigateToChat()
|
||||||
|
}
|
||||||
|
|
||||||
val fullImageUrl = when (val img = product.image) {
|
val fullImageUrl = when (val img = product.image) {
|
||||||
is String -> {
|
is String -> {
|
||||||
@ -382,8 +385,30 @@ class DetailProductActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun navigateToChat(){
|
||||||
|
val productDetail = viewModel.productDetail.value ?: return
|
||||||
|
val storeDetail = viewModel.storeDetail.value
|
||||||
|
|
||||||
|
if (storeDetail !is Result.Success || storeDetail.data == null) {
|
||||||
|
Toast.makeText(this, "Store information not available", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ChatActivity.createIntent(
|
||||||
|
context = this,
|
||||||
|
storeId = productDetail.storeId,
|
||||||
|
productId = productDetail.productId,
|
||||||
|
productName = productDetail.productName,
|
||||||
|
productPrice = productDetail.price,
|
||||||
|
productImage = productDetail.image,
|
||||||
|
productRating = productDetail.rating,
|
||||||
|
storeName = storeDetail.data.storeName,
|
||||||
|
chatRoomId = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_PRODUCT_ID = "extra_product_id"
|
private const val EXTRA_PRODUCT_ID = "extra_product_id"
|
||||||
|
|
||||||
fun start(context: Context, productId: Int) {
|
fun start(context: Context, productId: Int) {
|
||||||
val intent = Intent(context, DetailProductActivity::class.java)
|
val intent = Intent(context, DetailProductActivity::class.java)
|
||||||
|
@ -19,10 +19,11 @@ class SessionManager(context: Context) {
|
|||||||
sharedPreferences.edit() {
|
sharedPreferences.edit() {
|
||||||
putString(USER_TOKEN, token)
|
putString(USER_TOKEN, token)
|
||||||
}
|
}
|
||||||
|
Log.d("SessionManager", "Saved token: $token")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getToken(): String? {
|
fun getToken(): String {
|
||||||
val token = sharedPreferences.getString(USER_TOKEN, null)
|
val token = sharedPreferences.getString(USER_TOKEN, "") ?: ""
|
||||||
Log.d("SessionManager", "Retrieved token: $token")
|
Log.d("SessionManager", "Retrieved token: $token")
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
@ -34,12 +35,16 @@ class SessionManager(context: Context) {
|
|||||||
Log.d("SessionManager", "Saved user ID: $userId")
|
Log.d("SessionManager", "Saved user ID: $userId")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserId(): String? {
|
fun getUserId(): String {
|
||||||
val userId = sharedPreferences.getString(USER_ID, null)
|
val userId = sharedPreferences.getString(USER_ID, "") ?: ""
|
||||||
Log.d("SessionManager", "Retrieved user ID: $userId")
|
Log.d("SessionManager", "Retrieved user ID: $userId")
|
||||||
return userId
|
return userId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isLoggedIn(): Boolean {
|
||||||
|
return getToken().isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
fun clearUserId() {
|
fun clearUserId() {
|
||||||
sharedPreferences.edit() {
|
sharedPreferences.edit() {
|
||||||
remove(USER_ID)
|
remove(USER_ID)
|
||||||
@ -52,6 +57,8 @@ class SessionManager(context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//clear data when log out
|
//clear data when log out
|
||||||
fun clearAll() {
|
fun clearAll() {
|
||||||
sharedPreferences.edit() {
|
sharedPreferences.edit() {
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package com.alya.ecommerce_serang.utils.viewmodel
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
|
|
||||||
class ChatViewModel : ViewModel() {
|
|
||||||
// TODO: Implement the ViewModel
|
|
||||||
}
|
|
5
app/src/main/res/drawable/baseline_attach_file_24.xml
Normal file
5
app/src/main/res/drawable/baseline_attach_file_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M16.5,6v11.5c0,2.21 -1.79,4 -4,4s-4,-1.79 -4,-4V5c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5v10.5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1V6H10v9.5c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5V5c0,-2.21 -1.79,-4 -4,-4S7,2.79 7,5v12.5c0,3.04 2.46,5.5 5.5,5.5s5.5,-2.46 5.5,-5.5V6h-1.5z"/>
|
||||||
|
|
||||||
|
</vector>
|
11
app/src/main/res/drawable/bg_edit_text_background.xml
Normal file
11
app/src/main/res/drawable/bg_edit_text_background.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#F5F5F5" />
|
||||||
|
<corners android:radius="20dp" />
|
||||||
|
<padding
|
||||||
|
android:bottom="8dp"
|
||||||
|
android:left="12dp"
|
||||||
|
android:right="12dp"
|
||||||
|
android:top="8dp" />
|
||||||
|
</shape>
|
5
app/src/main/res/drawable/check_double_24.xml
Normal file
5
app/src/main/res/drawable/check_double_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/>
|
||||||
|
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/check_double_read_24.xml
Normal file
5
app/src/main/res/drawable/check_double_read_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#489EC6" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/>
|
||||||
|
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/check_single_24.xml
Normal file
5
app/src/main/res/drawable/check_single_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
|
||||||
|
|
||||||
|
</vector>
|
@ -2,11 +2,12 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/main"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:theme="@style/Theme.Ecommerce_serang"
|
||||||
tools:context=".ui.chat.ChatActivity">
|
tools:context=".ui.chat.ChatActivity">
|
||||||
|
|
||||||
|
<!-- Top Toolbar -->
|
||||||
<androidx.appcompat.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/chatToolbar"
|
android:id="@+id/chatToolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -175,9 +176,23 @@
|
|||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingTop="8dp"
|
android:paddingTop="8dp"
|
||||||
android:paddingBottom="8dp"
|
android:paddingBottom="8dp"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/layoutChatInput"
|
app:layout_constraintBottom_toTopOf="@+id/tvTypingIndicator"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/cardProduct" />
|
app:layout_constraintTop_toBottomOf="@+id/cardProduct" />
|
||||||
|
|
||||||
|
<!-- Typing indicator -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTypingIndicator"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:text="User is typing..."
|
||||||
|
android:textColor="#666666"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/layoutChatInput"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<!-- Chat input area -->
|
<!-- Chat input area -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layoutChatInput"
|
android:id="@+id/layoutChatInput"
|
||||||
@ -196,7 +211,7 @@
|
|||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:contentDescription="Attachment"
|
android:contentDescription="Attachment"
|
||||||
android:src="@drawable/ic_attachment" />
|
android:src="@drawable/baseline_attach_file_24" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/editTextMessage"
|
android:id="@+id/editTextMessage"
|
||||||
@ -205,7 +220,9 @@
|
|||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_edit_text_background"
|
||||||
android:hint="Tulis pesan"
|
android:hint="Tulis pesan"
|
||||||
|
android:fontFamily="@font/dmsans_regular"
|
||||||
android:inputType="textMultiLine"
|
android:inputType="textMultiLine"
|
||||||
android:maxLines="4"
|
android:maxLines="4"
|
||||||
android:minHeight="40dp"
|
android:minHeight="40dp"
|
||||||
@ -218,60 +235,7 @@
|
|||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:contentDescription="Send"
|
android:contentDescription="Send"
|
||||||
android:src="@drawable/ic_send" />
|
android:src="@drawable/baseline_attach_file_24" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvTypingIndicator"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="4dp"
|
|
||||||
android:text="User is typing..."
|
|
||||||
android:textColor="#666666"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textStyle="italic"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/layoutChatInput"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
<!-- Bottom navigation -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/bottomNavigation"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="#FFFFFF"
|
|
||||||
android:elevation="8dp"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent">
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/btnHome"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="Home"
|
|
||||||
android:src="@drawable/ic_home" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/btnMenu"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="Menu"
|
|
||||||
android:src="@drawable/ic_menu" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/btnNotification"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="Notification"
|
|
||||||
android:src="@drawable/ic_notification" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -3,8 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
tools:context=".ui.chat.ChatFragment">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/chatToolbar"
|
android:id="@+id/chatToolbar"
|
||||||
@ -235,7 +234,7 @@
|
|||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:contentDescription="Attachment"
|
android:contentDescription="Attachment"
|
||||||
android:src="@drawable/ic_attachment" />
|
android:src="@drawable/baseline_attach_file_24" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/editTextMessage"
|
android:id="@+id/editTextMessage"
|
||||||
@ -244,7 +243,7 @@
|
|||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@drawable/bg_edit_text_rounded"
|
android:background="@drawable/bg_edit_text_background"
|
||||||
android:hint="Tulis pesan"
|
android:hint="Tulis pesan"
|
||||||
android:inputType="textMultiLine"
|
android:inputType="textMultiLine"
|
||||||
android:maxLines="4"
|
android:maxLines="4"
|
||||||
@ -258,7 +257,7 @@
|
|||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
android:contentDescription="Send"
|
android:contentDescription="Send"
|
||||||
android:src="@drawable/ic_send" />
|
android:src="@drawable/baseline_attach_file_24" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,4 +10,10 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:text="Hello" />
|
android:text="Hello" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_trial"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:text="trial button"/>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
@ -13,7 +13,7 @@
|
|||||||
android:id="@+id/imgAvatar"
|
android:id="@+id/imgAvatar"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="32dp"
|
android:layout_height="32dp"
|
||||||
android:src="@drawable/profile_placeholder"
|
android:src="@drawable/ic_person"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@+id/layoutMessage" />
|
app:layout_constraintTop_toTopOf="@+id/layoutMessage" />
|
||||||
|
|
||||||
|
@ -117,5 +117,25 @@
|
|||||||
<item>Other reason</item>
|
<item>Other reason</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<!-- Chat Activity -->
|
||||||
|
<string name="image_attached">Image attached</string>
|
||||||
|
<string name="write_message">Tulis pesan</string>
|
||||||
|
<string name="options">Options</string>
|
||||||
|
<string name="block_user">Block User</string>
|
||||||
|
<string name="report">Report</string>
|
||||||
|
<string name="clear_chat">Clear Chat</string>
|
||||||
|
<string name="block_user_selected">Block user selected</string>
|
||||||
|
<string name="report_selected">Report selected</string>
|
||||||
|
<string name="clear_chat_selected">Clear chat selected</string>
|
||||||
|
<string name="permission_denied">Permission denied</string>
|
||||||
|
<string name="take_photo">Take Photo</string>
|
||||||
|
<string name="choose_from_gallery">Choose from Gallery</string>
|
||||||
|
<string name="select_attachment">Select Attachment</string>
|
||||||
|
<string name="image_selected">Image selected</string>
|
||||||
|
<string name="connecting">Connecting...</string>
|
||||||
|
<string name="disconnected_reconnecting">Disconnected. Reconnecting...</string>
|
||||||
|
<string name="connection_error">Connection error: %1$s</string>
|
||||||
|
<string name="typing">User is typing...</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Reference in New Issue
Block a user