From 6194dca259e9fb01fb96132c4b5af83b7bce859d Mon Sep 17 00:00:00 2001 From: shaulascr Date: Thu, 7 Aug 2025 01:11:55 +0700 Subject: [PATCH] update count product, chat, address --- app/src/main/AndroidManifest.xml | 10 +- .../data/api/dto/CreateAddressRequest.kt | 27 ++- .../ecommerce_serang/data/api/dto/Store.kt | 2 +- .../customer/profile/AddressResponse.kt | 11 +- .../data/repository/MyStoreRepository.kt | 15 ++ .../auth/fragments/RegisterStep3Fragment.kt | 15 +- .../ecommerce_serang/ui/cart/CartActivity.kt | 2 + .../ecommerce_serang/ui/chat/ChatActivity.kt | 115 ++++----- .../ecommerce_serang/ui/chat/ChatAdapter.kt | 4 +- .../ecommerce_serang/ui/chat/ChatViewModel.kt | 227 +++++++++++++++--- .../ui/chat/SocketIOService.kt | 177 +++++++++----- .../ui/order/address/AddAddressActivity.kt | 9 +- .../ui/order/address/ProvinceAdapter.kt | 79 ------ .../ui/profile/mystore/MyStoreActivity.kt | 39 ++- .../profile/mystore/chat/ChatStoreActivity.kt | 7 +- .../product/DetailStoreProductActivity.kt | 17 +- .../mystore/product/ProductActivity.kt | 12 +- .../utils/viewmodel/MyStoreViewModel.kt | 16 ++ app/src/main/res/layout/activity_cart.xml | 6 +- .../layout/activity_detail_store_product.xml | 2 +- app/src/main/res/layout/activity_my_store.xml | 3 +- app/src/main/res/layout/activity_product.xml | 2 +- .../main/res/layout/fragment_chat_list.xml | 4 +- 23 files changed, 502 insertions(+), 299 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 54fdf3b..4455d19 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -82,11 +82,11 @@ - + + + + + , - @field:SerializedName("message") + @field:SerializedName("message") val message: String ) data class AddressesItem( + @field:SerializedName("village_id") + val villageId: String, + @field:SerializedName("is_store_location") val isStoreLocation: Boolean, @@ -23,7 +26,7 @@ data class AddressesItem( val userId: Int, @field:SerializedName("province_id") - val provinceId: Int, + val provinceId: String, @field:SerializedName("phone") val phone: String, @@ -50,5 +53,5 @@ data class AddressesItem( val longitude: String, @field:SerializedName("city_id") - val cityId: Int + val cityId: String ) diff --git a/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt b/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt index 0da1f9b..0100c5e 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/data/repository/MyStoreRepository.kt @@ -1,6 +1,7 @@ package com.alya.ecommerce_serang.data.repository import android.util.Log +import com.alya.ecommerce_serang.data.api.dto.ProductsItem import com.alya.ecommerce_serang.data.api.dto.Store import com.alya.ecommerce_serang.data.api.response.auth.ListStoreTypeResponse import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse @@ -123,6 +124,20 @@ class MyStoreRepository(private val apiService: ApiService) { } } + suspend fun fetchMyStoreProducts(): List { + return try { + val response = apiService.getStoreProduct() + if (response.isSuccessful) { + response.body()?.products?.filterNotNull() ?: emptyList() + } else { + throw Exception("Failed to fetch store products: ${response.message()}") + } + } catch (e: Exception) { + Log.e("ProductRepository", "Error fetching store products", e) + throw e + } + } + // private fun fetchBalance() { // showLoading(true) // lifecycleScope.launch { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/auth/fragments/RegisterStep3Fragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/fragments/RegisterStep3Fragment.kt index f5757a1..7c9df73 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/auth/fragments/RegisterStep3Fragment.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/auth/fragments/RegisterStep3Fragment.kt @@ -30,6 +30,7 @@ import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.viewmodel.RegisterViewModel import com.google.android.material.progressindicator.LinearProgressIndicator +import com.google.gson.Gson class RegisterStep3Fragment : Fragment() { private var _binding: FragmentRegisterStep3Binding? = null @@ -376,6 +377,8 @@ class RegisterStep3Fragment : Fragment() { val subDistrict = registerViewModel.selectedSubdistrict.toString() val postalCode = registerViewModel.selectedPostalCode.toString() + val villageId = registerViewModel.selectedVillages ?: "" + Log.d(TAG, "Address data - Street: $street, SubDistrict: $subDistrict, PostalCode: $postalCode") Log.d(TAG, "Address data - Recipient: $recipient, Phone: $phone") Log.d(TAG, "Address data - ProvinceId: $provinceId, CityId: $cityId") @@ -383,21 +386,25 @@ class RegisterStep3Fragment : Fragment() { // Create address request val addressRequest = CreateAddressRequest( + userId = user.id, // must match the type expected in the DB lat = defaultLatitude, long = defaultLongitude, street = street, subDistrict = subDistrict, - cityId = cityId, + cityId = cityId, // ⚠️ Make sure this is Int provId = provinceId, postCode = postalCode, + idVillage = villageId, // Or provide a real ID if needed detailAddress = street, - userId = userId, + isStoreLocation = false, recipient = recipient, - phone = phone, - isStoreLocation = false + phone = phone ) Log.d(TAG, "Address request created: $addressRequest") + val gson = Gson() + val jsonString = gson.toJson(addressRequest) + Log.d(TAG, "Request JSON: $jsonString") // Show loading binding.progressBar.visibility = View.VISIBLE diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/cart/CartActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/cart/CartActivity.kt index fe0d9b3..6a6c69d 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/cart/CartActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/cart/CartActivity.kt @@ -146,7 +146,9 @@ class CartActivity : AppCompatActivity() { private fun observeViewModel() { viewModel.cartItems.observe(this) { cartItems -> if (cartItems.isNullOrEmpty()) { + binding.emptyCart.visibility = View.VISIBLE showEmptyState(true) + } else { showEmptyState(false) storeAdapter.submitList(cartItems) diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt index e677422..cf9fa48 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatActivity.kt @@ -27,6 +27,7 @@ import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsAnimationCompat import androidx.core.view.WindowInsetsCompat import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.alya.ecommerce_serang.BuildConfig.BASE_URL import com.alya.ecommerce_serang.R @@ -170,8 +171,7 @@ class ChatActivity : AppCompatActivity() { // If opened from ChatListFragment with a valid chatRoomId if (chatRoomId > 0) { - // Directly set the chatRoomId and load chat history - viewModel._chatRoomId.value = chatRoomId + viewModel.setChatRoomId(chatRoomId) } } @@ -405,68 +405,71 @@ class ChatActivity : AppCompatActivity() { } }) - viewModel.state.observe(this, Observer { state -> - Log.d(TAG, "State updated - Messages: ${state.messages.size}") + lifecycleScope.launchWhenStarted { + viewModel.state.collect() { state -> + Log.d(TAG, "State updated - Messages: ${state.messages.size}") - // Update messages - val previousCount = chatAdapter.itemCount + // Update messages + val previousCount = chatAdapter.itemCount - val displayItems = viewModel.getDisplayItems() + val displayItems = viewModel.getDisplayItems() - chatAdapter.submitList(displayItems) { - Log.d(TAG, "Messages submitted to adapter") - // Only auto-scroll for new messages or initial load - if (previousCount == 0 || state.messages.size > previousCount) { - scrollToBottomInstant() - } - } - - // layout attach product - 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 + chatAdapter.submitList(displayItems) { + Log.d(TAG, "Messages submitted to adapter") + // Only auto-scroll for new messages or initial load + if (previousCount == 0 || state.messages.size > previousCount) { + scrollToBottomInstant() } - else -> R.drawable.placeholder_image } - if (!state.productImageUrl.isNullOrEmpty()) { - Glide.with(this@ChatActivity) - .load(fullImageUrl) - .centerCrop() - .placeholder(R.drawable.placeholder_image) - .error(R.drawable.placeholder_image) - .into(binding.imgProduct) + // layout attach product + 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 + } + + if (!state.productImageUrl.isNullOrEmpty()) { + Glide.with(this@ChatActivity) + .load(fullImageUrl) + .centerCrop() + .placeholder(R.drawable.placeholder_image) + .error(R.drawable.placeholder_image) + .into(binding.imgProduct) + } + updateProductCardUI(state.hasProductAttachment) + + binding.productContainer.visibility = View.GONE + } else { + binding.productContainer.visibility = View.GONE } - updateProductCardUI(state.hasProductAttachment) - binding.productContainer.visibility = View.GONE - } else { - binding.productContainer.visibility = View.GONE + updateInputHint(state) + + // Update attachment hint + if (state.hasAttachment) { + binding.layoutAttachImage.visibility = View.VISIBLE + } else { + binding.editTextMessage.hint = getString(R.string.write_message) + } + + // Show error if any + state.error?.let { error -> + Toast.makeText(this@ChatActivity, error, Toast.LENGTH_SHORT).show() + viewModel.clearError() + } } - - updateInputHint(state) - - // Update attachment hint - if (state.hasAttachment) { - binding.layoutAttachImage.visibility = View.VISIBLE - } else { - binding.editTextMessage.hint = getString(R.string.write_message) - } - - // Show error if any - state.error?.let { error -> - Toast.makeText(this@ChatActivity, error, Toast.LENGTH_SHORT).show() - viewModel.clearError() - } - }) + } } private fun updateInputHint(state: ChatUiState) { @@ -492,7 +495,7 @@ class ChatActivity : AppCompatActivity() { Toast.makeText(this, "Opening: ${productInfo.productName}", Toast.LENGTH_SHORT).show() // You can navigate to product detail here - navigateToProductDetail(productInfo.productId) + navigateToProductDetail(productInfo.productId) } private fun navigateToProductDetail(productId: Int) { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatAdapter.kt index 8a136f0..68cd079 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatAdapter.kt @@ -209,7 +209,7 @@ class ChatAdapter( binding.tvProductPrice.text = product.productPrice // Load product image - val fullImageUrl = if (product.productImage.startsWith("/")) { + val fullImageUrl = if (product.productImage!!.startsWith("/")) { BASE_URL + product.productImage.substring(1) } else { product.productImage @@ -246,7 +246,7 @@ class ChatAdapter( binding.tvProductPrice.text = product.productPrice // Load product image - val fullImageUrl = if (product.productImage.startsWith("/")) { + val fullImageUrl = if (product.productImage!!.startsWith("/")) { BASE_URL + product.productImage.substring(1) } else { product.productImage diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt index 8a496cb..8a6d001 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/ChatViewModel.kt @@ -14,6 +14,9 @@ import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.utils.Constants import com.alya.ecommerce_serang.utils.SessionManager import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import java.io.File import java.text.SimpleDateFormat @@ -56,9 +59,9 @@ class ChatViewModel @Inject constructor( // Product attachment flag private var shouldAttachProduct = false - // UI state using LiveData - private val _state = MutableLiveData(ChatUiState()) - val state: LiveData = _state + // use state for more seamless responsive + private val _state = MutableStateFlow(ChatUiState()) + val state: StateFlow = _state private val _isLoading = MutableLiveData() val isLoading: LiveData = _isLoading @@ -93,6 +96,8 @@ class ChatViewModel @Inject constructor( init { Log.d(TAG, "ChatViewModel initialized") + socketService.connect() // 🛠 force connection + setupSocketListeners() // 🛠 always listen, even before user data initializeUser() } @@ -113,6 +118,7 @@ class ChatViewModel @Inject constructor( updateState { it.copy(error = "User authentication error. Please login again.") } } else { Log.d(TAG, "Setting up socket listeners...") + socketService.connect() setupSocketListeners() } } @@ -231,26 +237,116 @@ class ChatViewModel @Inject constructor( if (connectionState is ConnectionState.Connected) { Log.d(TAG, "Socket connected, joining room...") - socketService.joinRoom() + val roomId = _chatRoomId.value + if (roomId != null && roomId > 0) { + socketService.joinRoom(roomId) + } } } } +// viewModelScope.launch { +// socketService.newMessages.collect { chatLine -> +// chatLine?.let { +// Log.d(TAG, "NEW message received in ViewModel: ${it.message}") +// val updatedMessages = _state.value.messages.toMutableList() +// updatedMessages.add(convertChatLineToUiMessage(it)) +// updateState { it.copy(messages = updatedMessages) } +// +// if (it.senderId != currentUserId) { +// updateMessageStatus(it.id, Constants.STATUS_READ) +// } +// } +// } +// } viewModelScope.launch { socketService.newMessages.collect { chatLine -> - chatLine?.let { - Log.d(TAG, "New message received via socket - ID: ${it.id}, SenderID: ${it.senderId}") - val currentMessages = _state.value?.messages ?: listOf() - val updatedMessages = currentMessages.toMutableList().apply { - add(convertChatLineToUiMessage(it)) + Log.d("ChatViewModel", "Collected new message from SocketIOService: ${chatLine.message}") + chatLine?.let { incomingChatLine -> + // 1. First update: Add the message to the list (potentially without full product info) + _state.update { currentState -> + val existingMessageIndex = + currentState.messages.indexOfFirst { it.id == incomingChatLine.id } + val messagesAfterInitialUpdate = if (existingMessageIndex != -1) { + // If message exists (e.g., status update), just update it + val updatedList = currentState.messages.toMutableList() + updatedList[existingMessageIndex] = mapChatLineToUiMessage( + incomingChatLine, + updatedList[existingMessageIndex].productInfo + ) // Preserve existing productInfo if any + updatedList + } else { + // New message, add it + (currentState.messages + mapChatLineToUiMessage(incomingChatLine)).distinctBy { msg -> msg.id } + } + // Sort after any update/addition + currentState.copy(messages = messagesAfterInitialUpdate.sortedBy { msg -> + SimpleDateFormat( + "yyyy-MM-dd HH:mm:ss", + Locale.getDefault() + ).parse(msg.createdAt)?.time + }) } - updateState { it.copy(messages = updatedMessages) } - if (it.senderId != currentUserId) { - Log.d(TAG, "Marking message as read: ${it.id}") - updateMessageStatus(it.id, Constants.STATUS_READ) + // 2. If it's a product message and needs details, fetch them + if (incomingChatLine.productId != 0) { // Check if it's a product message + viewModelScope.launch { + Log.d( + TAG, + "Fetching product detail for ID: ${incomingChatLine.productId}" + ) + + // Call your repository function directly + val productResponse = + chatRepository.fetchProductDetail(incomingChatLine.productId) + + if (productResponse != null && productResponse.product != null) { + val fetchedProduct = + productResponse.product // Access the nested product object + Log.d( + TAG, + "Successfully fetched product: ${fetchedProduct.productName}" + ) + + // Create a complete ProductInfo object + val fullProductInfo = ProductInfo( + productId = fetchedProduct.productId, + productName = fetchedProduct.productName, // Use productName from fetched data + productPrice = fetchedProduct.price, // Use productPrice from fetched data + productImage = fetchedProduct.image, // Use productImage from fetched data + productRating = fetchedProduct.rating.toFloat(), + storeName = fetchedProduct.productName // Use storeName from fetched data + ) + + // --- PHASE 3: Second UI update (fill in full product info) --- + _state.update { currentState -> + val updatedMessages = currentState.messages.map { msg -> + if (msg.id == incomingChatLine.id) { + // Found the message, update its productInfo with full details + msg.copy(productInfo = fullProductInfo) + } else { + msg + } + } + currentState.copy(messages = updatedMessages) + } + } else { + Log.e( + TAG, + "Failed to fetch product detail for ID ${incomingChatLine.productId} or product data is null." + ) + // Optionally, update message status to indicate error in product loading + } + } } + } + +// // Your existing logic for clearing typing status etc. +// if (incomingChatLine.isTyping == false && incomingChatLine.from?.id != sessionManager.getUserId()?.toIntOrNull()) { +// _state.update { it.copy(isOtherUserTyping = false) } +// } + } } @@ -271,10 +367,10 @@ class ChatViewModel @Inject constructor( if (roomId <= 0) { Log.e(TAG, "Cannot join room: Invalid room ID") return + } else if (roomId > 0){ + Log.d(TAG, "Joining socket room: $roomId") + socketService.joinRoom(roomId) } - - Log.d(TAG, "Joining socket room: $roomId") - socketService.joinRoom() } fun sendTypingStatus(isTyping: Boolean) { @@ -728,7 +824,7 @@ class ChatViewModel @Inject constructor( } } - //update message status + //update message status fun updateMessageStatus(messageId: Int, status: String) { Log.d(TAG, "Updating message status - ID: $messageId, Status: $status") @@ -756,7 +852,7 @@ class ChatViewModel @Inject constructor( } } - //set image attachment + //set image attachment fun setSelectedImageFile(file: File?) { selectedImageFile = file updateState { it.copy(hasAttachment = file != null) } @@ -791,7 +887,7 @@ class ChatViewModel @Inject constructor( ) } - // convert chat history item to ui + // convert chat history item to ui private fun convertChatLineToUiMessageHistory(chatItem: ChatItem): ChatUiMessage { val formattedTime = formatTimestamp(chatItem.createdAt) @@ -932,7 +1028,7 @@ class ChatViewModel @Inject constructor( } } - //format price + //format price private fun formatPrice(price: String): String { return if (price.startsWith("Rp")) price else "Rp$price" } @@ -958,9 +1054,7 @@ class ChatViewModel @Inject constructor( // helper function to update live data private fun updateState(update: (ChatUiState) -> ChatUiState) { - _state.value?.let { - _state.value = update(it) - } + _state.value = update(_state.value) } //clear any error messages @@ -1053,6 +1147,73 @@ class ChatViewModel @Inject constructor( private fun isThisYear(messageCalendar: Calendar, today: Calendar): Boolean { return messageCalendar.get(Calendar.YEAR) == today.get(Calendar.YEAR) } + + fun setChatRoomId(roomId: Int) { + _chatRoomId.value = roomId + joinSocketRoom(roomId) + loadChatHistory(roomId) + } + + private fun convertToUiMessage(chatLine: ChatLine): ChatUiMessage { + + val formattedTime = formatTimestamp(chatLine.createdAt) + return ChatUiMessage( + id = chatLine.id, + message = chatLine.message, + attachment = chatLine.attachment, + status = chatLine.status, + time = formattedTime, // or format from createdAt if needed + isSentByMe = chatLine.senderId == currentUserId, + messageType = MessageType.TEXT, // or detect from chatLine if needed + productInfo = null, // optional, if applicable + createdAt = chatLine.createdAt + ) + } + + private fun mapChatLineToUiMessage(chatLine: ChatLine, fetchedProductInfo: ProductInfo? = null): ChatUiMessage { + val isSentByMe = chatLine.senderId == sessionManager.getUserId()?.toIntOrNull() // Using senderId now + val formattedTime = try { + val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()).apply { + timeZone = TimeZone.getTimeZone("UTC") + } + val outputFormat = SimpleDateFormat("HH:mm", Locale.getDefault()) + val date = inputFormat.parse(chatLine.createdAt) + date?.let { outputFormat.format(it) } ?: "" + } catch (e: Exception) { + Log.e("ChatViewModel", "Error parsing date: ${chatLine.createdAt}", e) + "" + } + + // Determine message type based on what ChatLine provides + val messageType = when { + chatLine.attachment?.isNotEmpty() == true -> MessageType.IMAGE + chatLine.productId != 0 -> MessageType.PRODUCT // If productId is non-zero, it's a product message + else -> MessageType.TEXT + } + + // Initialize productInfo: if fetchedProductInfo is provided, use it. + // Otherwise, if ChatLine has a productId, create a ProductInfo with just the ID. + // If no productId, it's null. + val productInfo = fetchedProductInfo ?: if (chatLine.productId != 0) { + // Create a placeholder ProductInfo with just the ID for initial display + // The full details will be fetched later + ProductInfo(productId = chatLine.productId) + } else { + null + } + + return ChatUiMessage( + id = chatLine.id, + message = chatLine.message, + attachment = chatLine.attachment, + status = chatLine.status, + time = formattedTime, + isSentByMe = isSentByMe, + messageType = messageType, + productInfo = productInfo, // Use the determined productInfo + createdAt = chatLine.createdAt + ) + } } enum class MessageType { @@ -1062,12 +1223,12 @@ enum class MessageType { } data class ProductInfo( - val productId: Int, - val productName: String, - val productPrice: String, - val productImage: String, - val productRating: Float, - val storeName: String + val productId: Int, // Keep productId here + val productName: String? = null, // Make nullable + val productPrice: String? = null, // Make nullable + val productImage: String? = null, // Make nullable + val productRating: Float = 0f, // Default value + val storeName: String? = null ) // representing chat messages to UI @@ -1083,8 +1244,6 @@ data class ChatUiMessage( val createdAt: String ) - - // representing UI state to screen data class ChatUiState( val messages: List = emptyList(), @@ -1102,4 +1261,8 @@ data class ChatUiState( val productImageUrl: String = "", val productRating: Float = 0f, val storeName: String = "" -) \ No newline at end of file +) + +//data class ChatUiState( +// val messages: List = emptyList() +//) diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/SocketIOService.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/SocketIOService.kt index 1ac378f..bb088c0 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/chat/SocketIOService.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/chat/SocketIOService.kt @@ -10,14 +10,24 @@ import com.alya.ecommerce_serang.utils.SessionManager import com.google.gson.Gson import io.socket.client.IO import io.socket.client.Socket +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch import org.json.JSONObject import java.net.URISyntaxException +import javax.inject.Inject +import javax.inject.Singleton -class SocketIOService( +@Singleton +class SocketIOService @Inject constructor( private val sessionManager: SessionManager ) { + private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val TAG = "SocketIOService" // Socket.IO client @@ -30,8 +40,8 @@ class SocketIOService( private val _connectionState = MutableStateFlow(ConnectionState.Disconnected()) val connectionState: StateFlow = _connectionState - private val _newMessages = MutableStateFlow(null) - val newMessages: StateFlow = _newMessages + private val _newMessages = MutableSharedFlow(extraBufferCapacity = 1) // Using extraBufferCapacity for a non-suspending emit + val newMessages: SharedFlow = _newMessages private val _typingStatus = MutableStateFlow(null) val typingStatus: StateFlow = _typingStatus @@ -85,63 +95,95 @@ class SocketIOService( * Sets up Socket.IO event listeners */ private fun setupSocketListeners() { - socket?.let { socket -> - // Connection events - socket.on(Socket.EVENT_CONNECT) { - Log.d(TAG, "Socket.IO connected") - isConnected = true - _connectionState.value = ConnectionState.Connected - _connectionStateLiveData.postValue(ConnectionState.Connected) - } - socket.on(Socket.EVENT_DISCONNECT) { - Log.d(TAG, "Socket.IO disconnected") - isConnected = false - _connectionState.value = ConnectionState.Disconnected("Disconnected from server") - _connectionStateLiveData.postValue(ConnectionState.Disconnected("Disconnected from server")) - } + socket?.on(Constants.EVENT_NEW_MESSAGE) { args -> // Use the event name your server emits + Log.d(TAG, "Raw event received on ${Constants.EVENT_NEW_MESSAGE}: ${args.firstOrNull()}") // Check raw args - socket.on(Socket.EVENT_CONNECT_ERROR) { args -> - val error = if (args.isNotEmpty() && args[0] != null) args[0].toString() else "Unknown error" - Log.e(TAG, "Socket.IO connection error: $error") - isConnected = false - _connectionState.value = ConnectionState.Error("Connection error: $error") - _connectionStateLiveData.postValue(ConnectionState.Error("Connection error: $error")) - } - - // Chat events - socket.on(Constants.EVENT_NEW_MESSAGE) { args -> + if (args.isNotEmpty()) { try { - if (args.isNotEmpty() && args[0] != null) { - val messageJson = args[0].toString() - Log.d(TAG, "Received new message: $messageJson") - val chatLine = Gson().fromJson(messageJson, ChatLine::class.java) - _newMessages.value = chatLine - _newMessagesLiveData.postValue(chatLine) + val messageJson = args[0].toString() + val chatLine = Gson().fromJson(messageJson, ChatLine::class.java) + Log.d(TAG, "Successfully parsed ChatLine: ${chatLine.message}") + Log.d(TAG, "Emitting new message to _newMessages SharedFlow...") // New log + + // Use the serviceScope to launch a coroutine for emit() + serviceScope.launch { + _newMessages.emit(chatLine) // This ensures every message is processed + Log.d(TAG, "New message emitted to SharedFlow.") // New log after emit + } } catch (e: Exception) { - Log.e(TAG, "Error parsing new message event", e) - } - } - - socket.on(Constants.EVENT_TYPING) { args -> - try { - if (args.isNotEmpty() && args[0] != null) { - val typingData = args[0] as JSONObject - val userId = typingData.getInt("userId") - val roomId = typingData.getInt("roomId") - val isTyping = typingData.getBoolean("isTyping") - - Log.d(TAG, "Received typing status: User $userId in room $roomId is typing: $isTyping") - val status = TypingStatus(userId, roomId, isTyping) - _typingStatus.value = status - _typingStatusLiveData.postValue(status) - } - } catch (e: Exception) { - Log.e(TAG, "Error parsing typing event", e) + Log.e(TAG, "Error parsing or emitting new message: ${e.message}", e) } + } else { + Log.w(TAG, "Received empty args for ${Constants.EVENT_NEW_MESSAGE}") } } +// socket?.on(Constants.EVENT_NEW_MESSAGE) { args -> +// if (args.isNotEmpty()) { +// val messageJson = args[0].toString() +// val chatLine = Gson().fromJson(messageJson, ChatLine::class.java) +// Log.d("SocketIOService", "Message received: ${chatLine.message}") +// _newMessages.value = chatLine +// } +// } +// socket?.let { socket -> +// // Connection events +// socket.on(Socket.EVENT_CONNECT) { +// Log.d(TAG, "Socket.IO connected") +// isConnected = true +// _connectionState.value = ConnectionState.Connected +// _connectionStateLiveData.postValue(ConnectionState.Connected) +// } +// +// socket.on(Socket.EVENT_DISCONNECT) { +// Log.d(TAG, "Socket.IO disconnected") +// isConnected = false +// _connectionState.value = ConnectionState.Disconnected("Disconnected from server") +// _connectionStateLiveData.postValue(ConnectionState.Disconnected("Disconnected from server")) +// } +// +// socket.on(Socket.EVENT_CONNECT_ERROR) { args -> +// val error = if (args.isNotEmpty() && args[0] != null) args[0].toString() else "Unknown error" +// Log.e(TAG, "Socket.IO connection error: $error") +// isConnected = false +// _connectionState.value = ConnectionState.Error("Connection error: $error") +// _connectionStateLiveData.postValue(ConnectionState.Error("Connection error: $error")) +// } +// +// // Chat events +// socket.on(Constants.EVENT_NEW_MESSAGE) { args -> +// try { +// if (args.isNotEmpty() && args[0] != null) { +// val messageJson = args[0].toString() +// Log.d(TAG, "Received new message: $messageJson") +// val chatLine = Gson().fromJson(messageJson, ChatLine::class.java) +// _newMessages.value = chatLine +// _newMessagesLiveData.postValue(chatLine) +// } +// } catch (e: Exception) { +// Log.e(TAG, "Error parsing new message event", e) +// } +// } +// +// socket.on(Constants.EVENT_TYPING) { args -> +// try { +// if (args.isNotEmpty() && args[0] != null) { +// val typingData = args[0] as JSONObject +// val userId = typingData.getInt("userId") +// val roomId = typingData.getInt("roomId") +// val isTyping = typingData.getBoolean("isTyping") +// +// Log.d(TAG, "Received typing status: User $userId in room $roomId is typing: $isTyping") +// val status = TypingStatus(userId, roomId, isTyping) +// _typingStatus.value = status +// _typingStatusLiveData.postValue(status) +// } +// } catch (e: Exception) { +// Log.e(TAG, "Error parsing typing event", e) +// } +// } +// } } /** @@ -159,22 +201,29 @@ class SocketIOService( /** * Joins a specific chat room */ - fun joinRoom() { + fun joinRoom(roomId: Int) { +// if (!isConnected) { +// connect() +// return +// } +// +// // Get user ID from SessionManager +// val userId = sessionManager.getUserId() +// if (userId.isNullOrEmpty()) { +// Log.e(TAG, "Cannot join room: User ID is null or empty") +// return +// } +// +// // Join the room using the current user's ID +// socket?.emit("joinRoom", roomId) // ✅ +// Log.d(TAG, "Joined room ID: $roomId") +// Log.d(TAG, "Joined room for user: $userId") if (!isConnected) { connect() - return } - // Get user ID from SessionManager - val userId = sessionManager.getUserId() - if (userId.isNullOrEmpty()) { - Log.e(TAG, "Cannot join room: User ID is null or empty") - return - } - - // Join the room using the current user's ID - socket?.emit("joinRoom", userId) - Log.d(TAG, "Joined room for user: $userId") + socket?.emit("joinRoom", roomId) + Log.d(TAG, "Joined room ID: $roomId") } /** diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt index 4c8f0c1..cd36fe7 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/AddAddressActivity.kt @@ -333,18 +333,19 @@ class AddAddressActivity : AppCompatActivity() { // Create request with all fields val request = CreateAddressRequest( + userId = userId, lat = latitude!!, // Safe to use !! as we've checked above long = longitude!!, street = street, subDistrict = subDistrict, - cityId = cityId, + cityId = cityId, // ⚠️ Make sure this is Int provId = provinceId, postCode = postalCode, + idVillage = "", // Or provide a real ID if needed detailAddress = street, - userId = userId, + isStoreLocation = false, recipient = recipient, - phone = phone, - isStoreLocation = isStoreLocation + phone = phone ) Log.d(TAG, "Form validation successful, submitting address: $request") diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt index 20d5718..85f674b 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/order/address/ProvinceAdapter.kt @@ -14,85 +14,6 @@ class ProvinceAdapter( resource: Int = android.R.layout.simple_dropdown_item_1line ) : ArrayAdapter(context, resource, ArrayList()) { -// companion object { -// private const val TAG = "ProvinceAdapter" -// } -// -// // --- Static list of provinces --------------------------------------------------------------- -// private val provinces = listOf( -// ProvincesItem(1, "Aceh"), -// ProvincesItem(2, "Sumatera Utara"), -// ProvincesItem(3, "Sumatera Barat"), -// ProvincesItem(4, "Riau"), -// ProvincesItem(5, "Kepulauan Riau"), -// ProvincesItem(6, "Jambi"), -// ProvincesItem(7, "Sumatera Selatan"), -// ProvincesItem(8, "Kepulauan Bangka Belitung"), -// ProvincesItem(9, "Bengkulu"), -// ProvincesItem(10, "Lampung"), -// ProvincesItem(11, "DKI Jakarta"), -// ProvincesItem(12, "Jawa Barat"), -// ProvincesItem(13, "Banten"), -// ProvincesItem(14, "Jawa Tengah"), -// ProvincesItem(15, "Daerah Istimewa Yogyakarta"), -// ProvincesItem(16, "Jawa Timur"), -// ProvincesItem(17, "Bali"), -// ProvincesItem(18, "Nusa Tenggara Barat"), -// ProvincesItem(19, "Nusa Tenggara Timur"), -// ProvincesItem(20, "Kalimantan Barat"), -// ProvincesItem(21, "Kalimantan Tengah"), -// ProvincesItem(22, "Kalimantan Selatan"), -// ProvincesItem(23, "Kalimantan Timur"), -// ProvincesItem(24, "Kalimantan Utara"), -// ProvincesItem(25, "Sulawesi Utara"), -// ProvincesItem(26, "Gorontalo"), -// ProvincesItem(27, "Sulawesi Tengah"), -// ProvincesItem(28, "Sulawesi Barat"), -// ProvincesItem(29, "Sulawesi Selatan"), -// ProvincesItem(30, "Sulawesi Tenggara"), -// ProvincesItem(31, "Maluku"), -// ProvincesItem(32, "Maluku Utara"), -// ProvincesItem(33, "Papua Barat"), -// ProvincesItem(34, "Papua"), -// ProvincesItem(35, "Papua Tengah"), -// ProvincesItem(36, "Papua Pegunungan"), -// ProvincesItem(37, "Papua Selatan"), -// ProvincesItem(38, "Papua Barat Daya") -// ) -// -// // --- Init block ----------------------------------------------------------------------------- -// init { -// addAll(getProvinceNames()) // pre‑populate adapter -// Log.d(TAG, "Adapter created with ${count} provinces") -// } -// -// // --- Public helper functions ---------------------------------------------------------------- -// fun updateData(newProvinces: List) { -// // If you actually want to replace the list, comment this line -// // provinces = newProvinces // (make `provinces` var instead of val) -// -// clear() -// addAll(newProvinces.map { it.province }) -// notifyDataSetChanged() -// -// Log.d(TAG, "updateData(): updated with ${newProvinces.size} provinces") -// } -// -// fun getProvinceId(position: Int): Int? { -// val id = provinces.getOrNull(position)?.provinceId -// Log.d(TAG, "getProvinceId(): position=$position, id=$id") -// return id -// } -// -// fun getProvinceItem(position: Int): ProvincesItem? { -// val item = provinces.getOrNull(position) -// Log.d(TAG, "getProvinceItem(): position=$position, item=$item") -// return item -// } -// -// // --- Private helpers ------------------------------------------------------------------------ -// private fun getProvinceNames(): List = provinces.map { it.province } - //call from endpoint private val provinces = ArrayList() diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt index 4b89434..d32939a 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/MyStoreActivity.kt @@ -21,7 +21,6 @@ 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.ReviewActivity -import com.alya.ecommerce_serang.ui.profile.mystore.review.ReviewFragment import com.alya.ecommerce_serang.ui.profile.mystore.sells.SellsActivity import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager @@ -52,14 +51,16 @@ class MyStoreActivity : AppCompatActivity() { enableEdgeToEdge() - binding.header.headerTitle.text = "Toko Saya" - binding.header.headerLeftIcon.setOnClickListener { + binding.headerMyStore.headerTitle.text = "Toko Saya" + + binding.headerMyStore.headerLeftIcon.setOnClickListener { onBackPressed() finish() } viewModel.loadMyStore() + viewModel.loadMyStoreProducts() viewModel.myStoreProfile.observe(this){ user -> user?.let { myStoreProfileOverview(it) } @@ -68,9 +69,9 @@ class MyStoreActivity : AppCompatActivity() { viewModel.errorMessage.observe(this) { error -> Toast.makeText(this, error, Toast.LENGTH_SHORT).show() } - setUpClickListeners() getCountOrder() + observeViewModel() viewModel.fetchBalance() fetchBalance() } @@ -170,13 +171,11 @@ class MyStoreActivity : AppCompatActivity() { when (result) { is com.alya.ecommerce_serang.data.repository.Result.Loading -> null -// binding.progressBar.isVisible = true is com.alya.ecommerce_serang.data.repository.Result.Success -> viewModel.formattedBalance.observe(this) { binding.tvBalance.text = it } is Result.Error -> { -// binding.progressBar.isVisible = false Log.e( "MyStoreActivity", "Gagal memuat saldo: ${result.exception.localizedMessage}" @@ -186,15 +185,29 @@ class MyStoreActivity : AppCompatActivity() { } } + private fun observeViewModel() { + viewModel.productList.observe(this) { result -> + when (result) { + is Result.Loading -> { + null + } + is Result.Success -> { + val productList = result.data + val count = productList.size + Log.d("MyStoreActivty", "You have $count products") + + // Example: update UI + binding.tvNumProduct.text = "$count produk" + } + is Result.Error -> { + Log.e("MyStoreActivity", "Failed load product : ${result.exception.message}" ) + } + } + } + } + companion object { private const val PROFILE_REQUEST_CODE = 100 } -// private fun navigateToSellsFragment(status: String) { -// val sellsFragment = SellsListFragment.newInstance(status) -// supportFragmentManager.beginTransaction() -// .replace(android.R.id.content, sellsFragment) -// .addToBackStack(null) -// .commit() -// } } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/chat/ChatStoreActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/chat/ChatStoreActivity.kt index f988123..6dcdf7c 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/chat/ChatStoreActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/chat/ChatStoreActivity.kt @@ -25,6 +25,7 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsAnimationCompat import androidx.core.view.WindowInsetsCompat import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.alya.ecommerce_serang.BuildConfig.BASE_URL import com.alya.ecommerce_serang.R @@ -373,7 +374,8 @@ class ChatStoreActivity : AppCompatActivity() { } }) - viewModel.state.observe(this, Observer { state -> + lifecycleScope.launchWhenStarted { + viewModel.state.collect { state -> Log.d(TAG, "State updated - Messages: ${state.messages.size}") // Update messages @@ -434,7 +436,8 @@ class ChatStoreActivity : AppCompatActivity() { Toast.makeText(this@ChatStoreActivity, error, Toast.LENGTH_SHORT).show() viewModel.clearError() } - }) + } + } } private fun showOptionsMenu() { diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/DetailStoreProductActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/DetailStoreProductActivity.kt index cb7385d..9e0bca5 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/DetailStoreProductActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/DetailStoreProductActivity.kt @@ -12,30 +12,29 @@ import android.view.View import android.widget.ArrayAdapter import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts -import com.alya.ecommerce_serang.R import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import androidx.core.widget.doAfterTextChanged +import com.alya.ecommerce_serang.BuildConfig.BASE_URL +import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.dto.CategoryItem import com.alya.ecommerce_serang.data.api.dto.Preorder +import com.alya.ecommerce_serang.data.api.dto.Wholesale import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.databinding.ActivityDetailStoreProductBinding -import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager +import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel import com.bumptech.glide.Glide import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody import java.io.File import java.io.FileOutputStream -import kotlin.getValue -import androidx.core.net.toUri -import androidx.core.widget.doAfterTextChanged -import com.alya.ecommerce_serang.BuildConfig.BASE_URL -import com.alya.ecommerce_serang.data.api.dto.Wholesale class DetailStoreProductActivity : AppCompatActivity() { @@ -93,7 +92,7 @@ class DetailStoreProductActivity : AppCompatActivity() { val isEditing = intent.getBooleanExtra("is_editing", false) productId = intent.getIntExtra("product_id", -1) - binding.header.headerTitle.text = if (isEditing) "Ubah Produk" else "Tambah Produk" + binding.headerStoreProduct.headerTitle.text = if (isEditing) "Ubah Produk" else "Tambah Produk" if (isEditing && productId != null && productId != -1) { viewModel.loadProductDetail(productId!!) @@ -140,7 +139,7 @@ class DetailStoreProductActivity : AppCompatActivity() { } } - binding.header.headerLeftIcon.setOnClickListener { + binding.headerStoreProduct.headerLeftIcon.setOnClickListener { onBackPressedDispatcher.onBackPressed() } } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductActivity.kt index e28b2f6..aac131e 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/product/ProductActivity.kt @@ -11,9 +11,9 @@ import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.repository.ProductRepository import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.databinding.ActivityProductBinding -import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager +import com.alya.ecommerce_serang.utils.viewmodel.ProductViewModel class ProductActivity : AppCompatActivity() { @@ -94,14 +94,14 @@ class ProductActivity : AppCompatActivity() { } private fun setupHeader() { - binding.header.headerTitle.text = "Produk Saya" - binding.header.headerRightText.visibility = View.VISIBLE + binding.headerListProduct.headerTitle.text = "Produk Saya" + binding.headerListProduct.headerRightText.visibility = View.VISIBLE - binding.header.headerLeftIcon.setOnClickListener { + binding.headerListProduct.headerLeftIcon.setOnClickListener { onBackPressedDispatcher.onBackPressed() } - binding.header.headerRightText.setOnClickListener { + binding.headerListProduct.headerRightText.setOnClickListener { val intent = Intent(this, DetailStoreProductActivity::class.java) intent.putExtra("is_editing", false) startActivity(intent) @@ -111,4 +111,6 @@ class ProductActivity : AppCompatActivity() { private fun setupRecyclerView() { binding.rvStoreProduct.layoutManager = LinearLayoutManager(this) } + + } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/MyStoreViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/MyStoreViewModel.kt index 6fcc546..2a8792b 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/MyStoreViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/MyStoreViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.map import androidx.lifecycle.viewModelScope +import com.alya.ecommerce_serang.data.api.dto.ProductsItem import com.alya.ecommerce_serang.data.api.dto.Store import com.alya.ecommerce_serang.data.api.response.auth.StoreTypesItem import com.alya.ecommerce_serang.data.api.response.store.StoreResponse @@ -38,6 +39,9 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() { private val _balanceResult = MutableLiveData>() val balanceResult: LiveData> get() = _balanceResult + private val _productList = MutableLiveData>>() + val productList: LiveData>> get() = _productList + fun loadMyStore(){ viewModelScope.launch { when (val result = repository.fetchMyStoreProfile()){ @@ -158,6 +162,18 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() { } } + fun loadMyStoreProducts() { + viewModelScope.launch { + _productList.value = Result.Loading + try { + val result = repository.fetchMyStoreProducts() + _productList.value = Result.Success(result) + } catch (e: Exception) { + _productList.value = Result.Error(e) + } + } + } + private fun String.toRequestBody(): RequestBody = RequestBody.create("text/plain".toMediaTypeOrNull(), this) } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_cart.xml b/app/src/main/res/layout/activity_cart.xml index 57d1ece..daf3864 100644 --- a/app/src/main/res/layout/activity_cart.xml +++ b/app/src/main/res/layout/activity_cart.xml @@ -122,12 +122,14 @@ android:src="@drawable/outline_shopping_cart_24" /> + android:textSize="16sp" /> diff --git a/app/src/main/res/layout/fragment_chat_list.xml b/app/src/main/res/layout/fragment_chat_list.xml index 69d919e..4a1a8c9 100644 --- a/app/src/main/res/layout/fragment_chat_list.xml +++ b/app/src/main/res/layout/fragment_chat_list.xml @@ -48,8 +48,8 @@ android:layout_marginTop="16dp" android:gravity="center" android:visibility="gone" - android:text="Keranjang Anda kosong" + android:text="Pesan anda kosong" android:textColor="@android:color/black" - android:textSize="18sp" /> + android:textSize="16sp" /> \ No newline at end of file