diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..fc987f5 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ 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 801c8ea..0da1f9b 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 @@ -5,6 +5,7 @@ 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 import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse +import com.alya.ecommerce_serang.data.api.response.store.sells.OrderListResponse import com.alya.ecommerce_serang.data.api.retrofit.ApiService import okhttp3.MultipartBody import okhttp3.RequestBody @@ -71,4 +72,90 @@ class MyStoreRepository(private val apiService: ApiService) { street, subdistrict, detail, postalCode, latitude, longitude, userPhone, storeType, storeimg ) } + + suspend fun getSellList(status: String): Result { + return try { + Log.d("SellsRepository", "Add Evidence : $status") + val response = apiService.getSellList(status) + + if (response.isSuccessful) { + val allListSell = response.body() + if (allListSell != null) { + Log.d("SellsRepository", "Add Evidence successfully: ${allListSell.message}") + Result.Success(allListSell) + } else { + Log.e("SellsRepository", "Response body was null") + Result.Error(Exception("Empty response from server")) + } + } else { + val errorBody = response.errorBody()?.string() ?: "Unknown error" + Log.e("SellsRepository", "Error Add Evidence : $errorBody") + Result.Error(Exception(errorBody)) + } + } catch (e: Exception) { + Log.e("SellsRepository", "Exception Add Evidence ", e) + Result.Error(e) + } + } + + suspend fun getBalance(): Result { + return try { + val response = apiService.getMyStoreData() + + if (response.isSuccessful) { + val body = response.body() + ?: return Result.Error(IllegalStateException("Response body is null")) + + // Validate the balance field + val balanceRaw = body.store.balance + balanceRaw.toDoubleOrNull() + ?: return Result.Error(NumberFormatException("Invalid balance format: $balanceRaw")) + + Result.Success(body) + } else { + Result.Error( + Exception("Failed to load balance: ${response.code()} ${response.message()}") + ) + } + } catch (e: Exception) { + Log.e("MyStoreRepository", "Error fetching balance", e) + Result.Error(e) + } + } + +// private fun fetchBalance() { +// showLoading(true) +// lifecycleScope.launch { +// try { +// val response = ApiConfig.getApiService(sessionManager).getMyStoreData() +// if (response.isSuccessful && response.body() != null) { +// val storeData = response.body()!! +// val balance = storeData.store.balance +// +// // Format the balance +// try { +// val balanceValue = balance.toDouble() +// binding.tvBalance.text = String.format("Rp%,.0f", balanceValue) +// } catch (e: Exception) { +// binding.tvBalance.text = "Rp$balance" +// } +// } else { +// Toast.makeText( +// this@BalanceActivity, +// "Gagal memuat data saldo: ${response.message()}", +// Toast.LENGTH_SHORT +// ).show() +// } +// } catch (e: Exception) { +// Log.e(TAG, "Error fetching balance", e) +// Toast.makeText( +// this@BalanceActivity, +// "Error: ${e.message}", +// Toast.LENGTH_SHORT +// ).show() +// } finally { +// showLoading(false) +// } +// } +// } } \ No newline at end of file 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 39fc4a1..92c3322 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 @@ -63,6 +63,8 @@ class ChatActivity : AppCompatActivity() { // For image attachment private var tempImageUri: Uri? = null + private var imageAttach = false + // Typing indicator handler private val typingHandler = android.os.Handler(android.os.Looper.getMainLooper()) private val stopTypingRunnable = Runnable { @@ -269,6 +271,7 @@ class ChatActivity : AppCompatActivity() { } // Options button + binding.btnOptions.visibility = View.GONE binding.btnOptions.setOnClickListener { showOptionsMenu() } @@ -281,6 +284,7 @@ class ChatActivity : AppCompatActivity() { // This will automatically handle product attachment if enabled viewModel.sendMessage(message) binding.editTextMessage.text.clear() + binding.layoutAttachImage.visibility = View.GONE // Instantly scroll to show new message binding.recyclerChat.postDelayed({ @@ -291,24 +295,33 @@ class ChatActivity : AppCompatActivity() { // Attachment button binding.btnAttachment.setOnClickListener { + this.currentFocus?.let { view -> + val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + imm?.hideSoftInputFromWindow(view.windowToken, 0) + } checkPermissionsAndShowImagePicker() } + binding.btnCloseChat.setOnClickListener{ + binding.layoutAttachImage.visibility = View.GONE + imageAttach = false + viewModel.clearSelectedImage() + } + // Product card click to enable/disable product attachment binding.productContainer.setOnClickListener { toggleProductAttachment() } } + private fun toggleProductAttachment() { val currentState = viewModel.state.value if (currentState?.hasProductAttachment == true) { - // Disable product attachment viewModel.disableProductAttachment() updateProductAttachmentUI(false) Toast.makeText(this, "Product attachment disabled", Toast.LENGTH_SHORT).show() } else { - // Enable product attachment viewModel.enableProductAttachment() updateProductAttachmentUI(true) Toast.makeText(this, "Product will be attached to your next message", Toast.LENGTH_SHORT).show() @@ -405,7 +418,7 @@ class ChatActivity : AppCompatActivity() { } } - // Update product info + // layout attach product if (!state.productName.isNullOrEmpty()) { binding.tvProductName.text = state.productName binding.tvProductPrice.text = state.productPrice @@ -440,15 +453,11 @@ class ChatActivity : AppCompatActivity() { // Update attachment hint if (state.hasAttachment) { - binding.editTextMessage.hint = getString(R.string.image_attached) + binding.layoutAttachImage.visibility = View.VISIBLE } else { binding.editTextMessage.hint = getString(R.string.write_message) } - // Show typing indicator - binding.tvTypingIndicator.visibility = - if (state.isOtherUserTyping) View.VISIBLE else View.GONE - // Show error if any state.error?.let { error -> Toast.makeText(this@ChatActivity, error, Toast.LENGTH_SHORT).show() @@ -459,7 +468,7 @@ class ChatActivity : AppCompatActivity() { private fun updateInputHint(state: ChatUiState) { binding.editTextMessage.hint = when { - state.hasAttachment -> getString(R.string.image_attached) + state.hasAttachment -> getString(R.string.write_message) state.hasProductAttachment -> "Type your message (product will be attached)" else -> getString(R.string.write_message) } @@ -504,6 +513,7 @@ class ChatActivity : AppCompatActivity() { getString(R.string.cancel) ) + AlertDialog.Builder(this) .setTitle(getString(R.string.options)) .setItems(options) { dialog, which -> @@ -578,7 +588,21 @@ class ChatActivity : AppCompatActivity() { private fun handleSelectedImage(uri: Uri) { try { - Log.d(TAG, "Processing selected image: $uri") + Log.d(TAG, "Processing selected image: ${uri.toString()}") + imageAttach = true + binding.layoutAttachImage.visibility = View.VISIBLE + val fullImageUrl = when (val img = uri.toString()) { + is String -> { + if (img.startsWith("/")) BASE_URL + img.substring(1) else img + } + else -> R.drawable.placeholder_image + } + + Glide.with(this) + .load(fullImageUrl) + .placeholder(R.drawable.placeholder_image) + .into(binding.ivAttach) + Log.d(TAG, "Display attach image: $uri") // Always use the copy-to-cache approach for reliability contentResolver.openInputStream(uri)?.use { inputStream -> @@ -598,6 +622,7 @@ class ChatActivity : AppCompatActivity() { Log.d(TAG, "Image processed successfully: ${outputFile.absolutePath}, size: ${outputFile.length()}") viewModel.setSelectedImageFile(outputFile) + Toast.makeText(this, "Image selected", Toast.LENGTH_SHORT).show() } else { Log.e(TAG, "Failed to create image file") @@ -681,25 +706,4 @@ class ChatActivity : AppCompatActivity() { 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) -// } -// } -// } \ No newline at end of file +} \ No newline at end of file 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 3b550f5..108993f 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 @@ -23,6 +23,28 @@ import java.util.Locale import java.util.TimeZone import javax.inject.Inject +/** + * ChatViewModel - Manages chat functionality for both buyers and store owners + * + * ARCHITECTURE OVERVIEW: + * - Handles real-time messaging via Socket.IO + * - Manages chat state using LiveData/MutableLiveData pattern + * - Supports multiple message types: TEXT, IMAGE, PRODUCT + * - Maintains separate flows for buyer and store owner chat + * + * KEY RESPONSIBILITIES: + * 1. Socket connection management and real-time message handling + * 2. Message sending/receiving with different attachment types + * 3. Chat history loading and message status updates + * 4. Product attachment functionality for commerce integration + * 5. User session management and authentication + * + * STATE MANAGEMENT PATTERN: + * - All UI state updates go through updateState() helper function + * - State updates are atomic and follow immutable pattern + * - Error states are cleared explicitly via clearError() + */ + @HiltViewModel class ChatViewModel @Inject constructor( private val chatRepository: ChatRepository, @@ -730,6 +752,19 @@ class ChatViewModel @Inject constructor( Log.d(TAG, "Image attachment ${if (file != null) "selected: ${file.name}" else "cleared"}") } + fun clearSelectedImage() { + Log.d(TAG, "Clearing selected image attachment") + + selectedImageFile?.let { file -> + Log.d(TAG, "Clearing image file: ${file.name}") + } + + selectedImageFile = null + updateState { it.copy(hasAttachment = false) } + + Log.d(TAG, "Image attachment cleared successfully") + } + // convert form chatLine api to UI chat messages private fun convertChatLineToUiMessage(chatLine: ChatLine): ChatUiMessage { val formattedTime = formatTimestamp(chatLine.createdAt) 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 a1d78b1..c2a9200 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 @@ -7,12 +7,14 @@ import android.widget.Toast import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope import com.alya.ecommerce_serang.BuildConfig.BASE_URL import com.alya.ecommerce_serang.R import com.alya.ecommerce_serang.data.api.dto.Store import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig import com.alya.ecommerce_serang.data.api.retrofit.ApiService import com.alya.ecommerce_serang.data.repository.MyStoreRepository +import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.databinding.ActivityMyStoreBinding import com.alya.ecommerce_serang.ui.profile.mystore.balance.BalanceActivity import com.alya.ecommerce_serang.ui.profile.mystore.chat.ChatListStoreActivity @@ -24,6 +26,7 @@ import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.viewmodel.MyStoreViewModel import com.bumptech.glide.Glide +import kotlinx.coroutines.launch class MyStoreActivity : AppCompatActivity() { private lateinit var binding: ActivityMyStoreBinding @@ -66,6 +69,9 @@ class MyStoreActivity : AppCompatActivity() { } setUpClickListeners() + getCountOrder() + viewModel.fetchBalance() + fetchBalance() } private fun myStoreProfileOverview(store: Store){ @@ -147,6 +153,46 @@ class MyStoreActivity : AppCompatActivity() { } } + private fun getCountOrder(){ + lifecycleScope.launch { + try { + val allCounts = viewModel.getAllStatusCounts() + val totalUnpaid = allCounts["unpaid"] + val totalPaid = allCounts["paid"] + val totalProcessed = allCounts["processed"] + Log.d("MyStoreActivity", + "Total orders: unpaid=$totalUnpaid, processed=$totalProcessed, paid=$totalPaid") + + binding.tvNumPesananMasuk.text = totalUnpaid.toString() + binding.tvNumPembayaran.text = totalPaid.toString() + binding.tvNumPerluDikirim.text = totalProcessed.toString() + } catch (e:Exception){ + Log.e("MyStoreActivity", "Error getting order counts: ${e.message}") + } + } + } + + private fun fetchBalance(){ + viewModel.balanceResult.observe(this){result -> + 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}" + ) + } + } + } + } + companion object { private const val PROFILE_REQUEST_CODE = 100 } diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceActivity.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceActivity.kt index b0c36ea..68ee66f 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceActivity.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/balance/BalanceActivity.kt @@ -408,6 +408,10 @@ class BalanceActivity : AppCompatActivity() { } } + private fun navigateTotalBalance(){ + + } + companion object { private const val TOP_UP_REQUEST_CODE = 101 } 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 169f1de..f988123 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 @@ -426,8 +426,8 @@ class ChatStoreActivity : AppCompatActivity() { } // Show typing indicator - binding.tvTypingIndicator.visibility = - if (state.isOtherUserTyping) View.VISIBLE else View.GONE +// binding.tvTypingIndicator.visibility = +// if (state.isOtherUserTyping) View.VISIBLE else View.GONE // Show error if any state.error?.let { error -> @@ -520,6 +520,19 @@ class ChatStoreActivity : AppCompatActivity() { private fun handleSelectedImage(uri: Uri) { try { Log.d(TAG, "Processing selected image: $uri") + binding.layoutAttachImage.visibility = View.VISIBLE + val fullImageUrl = when (val img = uri.toString()) { + is String -> { + if (img.startsWith("/")) BASE_URL + img.substring(1) else img + } + else -> R.drawable.placeholder_image + } + + Glide.with(this) + .load(fullImageUrl) + .placeholder(R.drawable.placeholder_image) + .into(binding.ivAttach) + Log.d(TAG, "Display attach image: $uri") // Always use the copy-to-cache approach for reliability contentResolver.openInputStream(uri)?.use { inputStream -> diff --git a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/sells/SellsListFragment.kt b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/sells/SellsListFragment.kt index 5f7ff36..48680cd 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/sells/SellsListFragment.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/ui/profile/mystore/sells/SellsListFragment.kt @@ -9,6 +9,7 @@ import android.view.ViewGroup import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.alya.ecommerce_serang.data.api.response.store.sells.OrdersItem import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig @@ -16,12 +17,14 @@ import com.alya.ecommerce_serang.data.repository.Result import com.alya.ecommerce_serang.data.repository.SellsRepository import com.alya.ecommerce_serang.databinding.FragmentSellsListBinding import com.alya.ecommerce_serang.ui.order.address.ViewState +import com.alya.ecommerce_serang.ui.profile.mystore.MyStoreActivity import com.alya.ecommerce_serang.ui.profile.mystore.sells.payment.DetailPaymentActivity import com.alya.ecommerce_serang.ui.profile.mystore.sells.shipment.DetailShipmentActivity import com.alya.ecommerce_serang.utils.BaseViewModelFactory import com.alya.ecommerce_serang.utils.SessionManager import com.alya.ecommerce_serang.utils.viewmodel.SellsViewModel import com.google.gson.Gson +import kotlinx.coroutines.launch class SellsListFragment : Fragment() { @@ -84,6 +87,7 @@ class SellsListFragment : Fragment() { observeSellsList() observePaymentConfirmation() loadSells() +// getAllOrderCountsAndNavigate() } private fun setupRecyclerView() { @@ -183,6 +187,30 @@ class SellsListFragment : Fragment() { context.startActivity(intent) } + private fun getAllOrderCountsAndNavigate() { + lifecycleScope.launch { + try { + // Show loading if needed + binding.progressBar.visibility = View.VISIBLE + + val allCounts = viewModel.getAllStatusCounts() + + binding.progressBar.visibility = View.GONE + + val intent = Intent(requireContext(), MyStoreActivity::class.java) + intent.putExtra("total_unpaid", allCounts["unpaid"]) + intent.putExtra("total_paid", allCounts["paid"]) + intent.putExtra("total_processed", allCounts["processed"]) + Log.d("SellsListFragment", "Total orders: unpaid=${allCounts["unpaid"]}, processed=${allCounts["processed"]}, Paid=${allCounts["paid"]}") + + + } catch (e: Exception) { + binding.progressBar.visibility = View.GONE + Log.e(TAG, "Error getting order counts: ${e.message}") + } + } + } + override fun onDestroyView() { super.onDestroyView() _binding = null 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 a04e593..6fcc546 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 @@ -1,11 +1,14 @@ package com.alya.ecommerce_serang.utils.viewmodel +import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.map import androidx.lifecycle.viewModelScope 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 import com.alya.ecommerce_serang.data.api.response.store.profile.StoreDataResponse import com.alya.ecommerce_serang.data.repository.MyStoreRepository import com.alya.ecommerce_serang.data.repository.Result @@ -13,6 +16,8 @@ import kotlinx.coroutines.launch import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody +import java.text.NumberFormat +import java.util.Locale class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() { private val _myStoreProfile = MutableLiveData() @@ -30,6 +35,9 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() { private val _errorMessage = MutableLiveData() val errorMessage : LiveData = _errorMessage + private val _balanceResult = MutableLiveData>() + val balanceResult: LiveData> get() = _balanceResult + fun loadMyStore(){ viewModelScope.launch { when (val result = repository.fetchMyStoreProfile()){ @@ -100,6 +108,56 @@ class MyStoreViewModel(private val repository: MyStoreRepository): ViewModel() { } } + suspend fun getTotalOrdersByStatus(status: String): Int { + return try { + when (val result = repository.getSellList(status)) { + is Result.Success -> { + // Access the orders list from the response + result.data.orders.size ?: 0 + } + is Result.Error -> { + Log.e("SellsViewModel", "Error getting orders count: ${result.exception.message}") + 0 + } + is Result.Loading -> 0 + } + } catch (e: Exception) { + Log.e("SellsViewModel", "Exception getting orders count", e) + 0 + } + } + + //count the order + suspend fun getAllStatusCounts(): Map { + val statuses = listOf( "unpaid", "paid", "processed") + val counts = mutableMapOf() + + statuses.forEach { status -> + counts[status] = getTotalOrdersByStatus(status) + Log.d("SellsViewModel", "Status: $status, countOrder=${counts[status]}") + } + + return counts + } + + val formattedBalance: LiveData = balanceResult.map { result -> + when (result) { + is Result.Success -> { + val raw = result.data.store.balance.toDouble() + NumberFormat.getCurrencyInstance(Locale("in", "ID")).format(raw) + } + else -> "" + } + } + + /** Trigger the network call */ + fun fetchBalance() { + viewModelScope.launch { + _balanceResult.value = Result.Loading + _balanceResult.value = repository.getBalance() + } + } + private fun String.toRequestBody(): RequestBody = RequestBody.create("text/plain".toMediaTypeOrNull(), this) } \ No newline at end of file diff --git a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/SellsViewModel.kt b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/SellsViewModel.kt index 63ec528..c5c0d73 100644 --- a/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/SellsViewModel.kt +++ b/app/src/main/java/com/alya/ecommerce_serang/utils/viewmodel/SellsViewModel.kt @@ -146,6 +146,38 @@ class SellsViewModel(private val repository: SellsRepository) : ViewModel() { Log.d(TAG, "========== getSellList method completed ==========") } + //get total order each status + suspend fun getTotalOrdersByStatus(status: String): Int { + return try { + when (val result = repository.getSellList(status)) { + is Result.Success -> { + // Access the orders list from the response + result.data.orders.size ?: 0 + } + is Result.Error -> { + Log.e("SellsViewModel", "Error getting orders count: ${result.exception.message}") + 0 + } + is Result.Loading -> 0 + } + } catch (e: Exception) { + Log.e("SellsViewModel", "Exception getting orders count", e) + 0 + } + } + + //count the order + suspend fun getAllStatusCounts(): Map { + val statuses = listOf( "unpaid", "paid", "processed") + val counts = mutableMapOf() + + statuses.forEach { status -> + counts[status] = getTotalOrdersByStatus(status) + Log.d("SellsViewModel", "Status: $status, countOrder=${counts[status]}") + } + + return counts + } fun getSellDetails(orderId: Int) { Log.d(TAG, "========== Starting getSellDetails ==========") diff --git a/app/src/main/res/drawable/ic_close_chat.xml b/app/src/main/res/drawable/ic_close_chat.xml new file mode 100644 index 0000000..948d953 --- /dev/null +++ b/app/src/main/res/drawable/ic_close_chat.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index 07d5da9..ca3826a 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,170 +1,74 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + xmlns:android="http://schemas.android.com/apk/res/android"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 2b068d1..df70974 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -1,30 +1,20 @@ - - - - - - - - + android:viewportWidth="64" + android:viewportHeight="64"> + - \ No newline at end of file + android:pathData="M0,0h64v64h-64z" + android:fillColor="#489EC6"/> + + + + diff --git a/app/src/main/res/drawable/ic_sent.xml b/app/src/main/res/drawable/ic_sent.xml new file mode 100644 index 0000000..b054e7d --- /dev/null +++ b/app/src/main/res/drawable/ic_sent.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index d60c39e..6009f05 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -54,19 +54,9 @@ android:textStyle="bold" app:layout_constraintStart_toEndOf="@+id/imgProfile" app:layout_constraintTop_toTopOf="@+id/imgProfile" - app:layout_constraintEnd_toStartOf="@+id/btnOptions" /> + app:layout_constraintEnd_toStartOf="@+id/btnOptions" + app:layout_constraintBottom_toBottomOf="parent"/> - - - - - - - - - - - - + android:padding="4dp" + app:layout_constraintBottom_toTopOf="@id/layoutChatInput" + app:layout_constraintStart_toStartOf="parent"> + + + + + + + + android:src="@drawable/ic_sent" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_category.xml b/app/src/main/res/layout/item_category.xml deleted file mode 100644 index 77d9ef6..0000000 --- a/app/src/main/res/layout/item_category.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 6f3b755..bbd3e02 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,5 @@ - - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 6f3b755..bbd3e02 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,6 +1,5 @@ - - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp index c209e78..168a2e4 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp index b2dfe3d..769f2f6 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp index 4f0f1d6..66262dc 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp index 62b611d..d9a8ab1 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp index 948a307..eee7576 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp index 1b9a695..2446349 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp index 28d4b77..b92c222 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index 9287f50..711722c 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index aa7d642..3f19ec4 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index 9126ae3..0c00f6f 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 136d62b..f85637e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - ecommerce_serang + Bisa UMKM