add cancel and logout

This commit is contained in:
shaulascr
2025-05-19 07:09:18 +07:00
parent e71a39747c
commit 2b2418cdf4
26 changed files with 847 additions and 165 deletions

View File

@ -1,50 +1,17 @@
package com.alya.ecommerce_serang.app
import android.app.Application
import android.content.Context
import android.util.Log
import com.google.firebase.FirebaseApp
import com.google.firebase.messaging.FirebaseMessaging
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class App : Application(){
private val TAG = "AppSerang"
// private val TAG = "AppSerang"
//
//// var tokenTes: String? = null
//
// override fun onCreate() {
//
// }
// var tokenTes: String? = null
override fun onCreate() {
super.onCreate()
// Initialize Firebase
FirebaseApp.initializeApp(this)
// Request FCM token at app startup
retrieveFCMToken()
}
private fun retrieveFCMToken() {
FirebaseMessaging.getInstance().token
.addOnCompleteListener { task ->
if (!task.isSuccessful) {
Log.e(TAG, "Failed to get FCM token", task.exception)
return@addOnCompleteListener
}
val token = task.result
// tokenTes = token
Log.d(TAG, "FCM token retrieved: $token")
// Save token locally
val sharedPreferences = getSharedPreferences("FCM_PREFS", Context.MODE_PRIVATE)
sharedPreferences.edit().putString("FCM_TOKEN", token).apply()
// Send to your server
sendTokenToServer(token)
}
}
private fun sendTokenToServer(token: String) {
Log.d(TAG, "Would send token to server: $token")
}
}

View File

@ -0,0 +1,11 @@
package com.alya.ecommerce_serang.data.api.dto
import com.google.gson.annotations.SerializedName
data class CancelOrderReq (
@SerializedName("order_id")
val orderId: Int,
@SerializedName("reason")
val reason: String
)

View File

@ -0,0 +1,14 @@
package com.alya.ecommerce_serang.data.api.response.customer.order
data class CancelOrderResponse(
val data: DataCancel,
val message: String
)
data class DataCancel(
val reason: String,
val createdAt: String,
val id: Int,
val orderId: Int
)

View File

@ -22,6 +22,9 @@ class ApiConfig {
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(authInterceptor)
.connectTimeout(60, TimeUnit.SECONDS) // Increase to 60 seconds
.readTimeout(60, TimeUnit.SECONDS) // Increase to 60 seconds
.writeTimeout(60, TimeUnit.SECONDS)
.build()
val retrofit = Retrofit.Builder()

View File

@ -3,6 +3,7 @@ package com.alya.ecommerce_serang.data.api.retrofit
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceRequest
import com.alya.ecommerce_serang.data.api.dto.AddPaymentInfoResponse
import com.alya.ecommerce_serang.data.api.dto.CancelOrderReq
import com.alya.ecommerce_serang.data.api.dto.CartItem
import com.alya.ecommerce_serang.data.api.dto.CityResponse
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
@ -35,6 +36,7 @@ import com.alya.ecommerce_serang.data.api.response.customer.cart.AddCartResponse
import com.alya.ecommerce_serang.data.api.response.customer.cart.DeleteCartResponse
import com.alya.ecommerce_serang.data.api.response.customer.cart.ListCartResponse
import com.alya.ecommerce_serang.data.api.response.customer.cart.UpdateCartResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CancelOrderResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateOrderResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateReviewResponse
@ -163,6 +165,11 @@ interface ApiService {
@Body request: OrderRequest
): Response<CreateOrderResponse>
@POST("order/cancel")
suspend fun cancelOrder(
@Body cancelReq: CancelOrderReq
): Response<CancelOrderResponse>
@GET("order/detail/{id}")
suspend fun getDetailOrder(
@Path("id") orderId: Int

View File

@ -2,16 +2,17 @@ package com.alya.ecommerce_serang.data.repository
import android.util.Log
import com.alya.ecommerce_serang.data.api.dto.AddEvidenceMultipartRequest
import com.alya.ecommerce_serang.data.api.dto.CancelOrderReq
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
import com.alya.ecommerce_serang.data.api.dto.CreateAddressRequest
import com.alya.ecommerce_serang.data.api.dto.OrderRequest
import com.alya.ecommerce_serang.data.api.dto.OrderRequestBuy
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
import com.alya.ecommerce_serang.data.api.dto.ReviewProductItem
import com.alya.ecommerce_serang.data.api.dto.UpdateCart
import com.alya.ecommerce_serang.data.api.dto.UserProfile
import com.alya.ecommerce_serang.data.api.response.customer.cart.DataItemCart
import com.alya.ecommerce_serang.data.api.response.customer.order.CancelOrderResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateOrderResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.CreateReviewResponse
@ -491,4 +492,23 @@ class OrderRepository(private val apiService: ApiService) {
}
suspend fun cancelOrder(cancelReq: CancelOrderReq): Result<CancelOrderResponse>{
return try{
val response= apiService.cancelOrder(cancelReq)
if (response.isSuccessful){
response.body()?.let { cancelOrderResponse ->
Result.Success(cancelOrderResponse)
} ?: run {
Result.Error(Exception("Failed to cancel order"))
}
} else {
val errorMsg = response.errorBody()?.string() ?: "Unknown Error"
Result.Error(Exception(errorMsg))
}
}catch (e: Exception){
Result.Error(e)
}
}
}

View File

@ -1,8 +1,10 @@
package com.alya.ecommerce_serang.ui
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
@ -20,11 +22,15 @@ import com.alya.ecommerce_serang.data.api.retrofit.ApiService
import com.alya.ecommerce_serang.databinding.ActivityMainBinding
import com.alya.ecommerce_serang.ui.notif.WebSocketManager
import com.alya.ecommerce_serang.utils.SessionManager
import com.google.firebase.FirebaseApp
import com.google.firebase.messaging.FirebaseMessaging
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val TAG = "MainActivity"
private lateinit var binding: ActivityMainBinding
private lateinit var apiService: ApiService
private lateinit var sessionManager: SessionManager
@ -65,6 +71,11 @@ class MainActivity : AppCompatActivity() {
)
windowInsets
}
// Initialize Firebase
FirebaseApp.initializeApp(this)
// Request FCM token at app startup
retrieveFCMToken()
requestNotificationPermissionIfNeeded()
@ -151,4 +162,31 @@ class MainActivity : AppCompatActivity() {
}
}
}
private fun retrieveFCMToken() {
FirebaseMessaging.getInstance().token
.addOnCompleteListener { task ->
if (!task.isSuccessful) {
Log.e(TAG, "Failed to get FCM token", task.exception)
return@addOnCompleteListener
}
val token = task.result
// tokenTes = token
Log.d(TAG, "FCM token retrieved: $token")
// Save token locally
val sharedPreferences = getSharedPreferences("FCM_PREFS", Context.MODE_PRIVATE)
sharedPreferences.edit().putString("FCM_TOKEN", token).apply()
// Send to your server
sendTokenToServer(token)
}
}
private fun sendTokenToServer(token: String) {
Log.d(TAG, "Would send token to server: $token")
}
}

View File

@ -508,6 +508,14 @@ class RegisterStoreActivity : AppCompatActivity() {
viewModel.subdistrict.value = s.toString()
}
})
binding.etBankName.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
viewModel.subdistrict.value = s.toString()
}
})
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

View File

@ -1,5 +1,6 @@
package com.alya.ecommerce_serang.ui.order
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@ -9,6 +10,7 @@ import com.alya.ecommerce_serang.data.api.dto.CourierCostRequest
import com.alya.ecommerce_serang.data.api.response.customer.order.CourierCostsItem
import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class ShippingViewModel(
@ -30,12 +32,71 @@ class ShippingViewModel(
/**
* Load shipping options based on address, product, and quantity
*/
// fun loadShippingOptions(addressId: Int, productId: Int, quantity: Int) {
// _isLoading.value = true
// _errorMessage.value = ""
//
// val costProduct = CostProduct(
// productId = productId,
// quantity = quantity
// )
//
// viewModelScope.launch {
// // Define the courier services to try
// val courierServices = listOf("pos", "jne", "tiki")
//
// // Create a mutable list to collect successful courier options
// val availableCourierOptions = mutableListOf<CourierCostsItem>()
//
// // Try each courier service
// for (courier in courierServices) {
// try {
// // Create a request for this specific courier
// val courierRequest = CourierCostRequest(
// addressId = addressId,
// itemCost = listOf(costProduct),
// courier = courier // Add the courier to the request
// )
//
// // Make a separate API call for each courier
// val result = repository.getCountCourierCost(courierRequest)
//
// when (result) {
// is Result.Success -> {
// // Add this courier's options to our collection
// result.data.courierCosts?.let { costs ->
// availableCourierOptions.addAll(costs)
// }
// // Update UI with what we have so far
// _shippingOptions.value = availableCourierOptions
// }
// is Result.Error -> {
// // Log the error but continue with next courier
// Log.e("ShippingViewModel", "Error fetching cost for courier $courier: ${result.exception.message}")
// }
// is Result.Loading -> {
// // Handle loading state
// }
// }
// } catch (e: Exception) {
// // Log the exception but continue with next courier
// Log.e("ShippingViewModel", "Exception for courier $courier: ${e.message}")
// }
// }
//
// // Show error only if we couldn't get any shipping options
// if (availableCourierOptions.isEmpty()) {
// _errorMessage.value = "No shipping options available. Please try again later."
// }
//
// _isLoading.value = false
// }
// }
fun loadShippingOptions(addressId: Int, productId: Int, quantity: Int) {
// Reset previous state
_isLoading.value = true
_errorMessage.value = ""
// Prepare the request
val costProduct = CostProduct(
productId = productId,
quantity = quantity
@ -43,34 +104,47 @@ class ShippingViewModel(
val request = CourierCostRequest(
addressId = addressId,
itemCost = listOf(costProduct) // Wrap in a list
itemCost = listOf(costProduct)
)
viewModelScope.launch {
try {
// Fetch courier costs
val result = repository.getCountCourierCost(request)
var success = false
var attempt = 0
val maxAttempts = 3
when (result) {
is Result.Success -> {
// Update shipping options directly with courier costs
_shippingOptions.value = result.data.courierCosts
}
is Result.Error -> {
// Handle error case
_errorMessage.value = result.exception.message ?: "Unknown error occurred"
}
is Result.Loading -> {
// Typically handled by the loading state
while (!success && attempt < maxAttempts) {
attempt++
try {
val result = repository.getCountCourierCost(request)
when (result) {
is Result.Success -> {
_shippingOptions.value = result.data.courierCosts
success = true
}
is com.alya.ecommerce_serang.data.repository.Result.Error -> {
Log.e("ShippingViewModel", "Attempt $attempt failed: ${result.exception.message}")
// Wait before retrying
delay(120000)
}
is com.alya.ecommerce_serang.data.repository.Result.Loading -> {
// Handle loading state
}
}
} catch (e: Exception) {
Log.e("ShippingViewModel", "Attempt $attempt exception: ${e.message}")
// Wait before retrying
delay(1000)
}
} catch (e: Exception) {
// Catch any unexpected exceptions
_errorMessage.value = e.localizedMessage ?: "An unexpected error occurred"
} finally {
// Always set loading to false
_isLoading.value = false
}
// After all attempts, check if we have any shipping options
if (!success || _shippingOptions.value.isNullOrEmpty()) {
_errorMessage.value = "No shipping options available. Please try again later."
}
_isLoading.value = false
}
}
}

View File

@ -398,7 +398,7 @@ class AddAddressActivity : AppCompatActivity() {
isRequestingLocation = false
Toast.makeText(this, "Timeout lokasi, menggunakan lokasi default", Toast.LENGTH_SHORT).show()
}
}, 15000) // 15 seconds timeout
}, 60000) // 15 seconds timeout
// Try getting last known location first
try {

View File

@ -5,8 +5,10 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.alya.ecommerce_serang.data.api.dto.CancelOrderReq
import com.alya.ecommerce_serang.data.api.dto.CompletedOrderRequest
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
import com.alya.ecommerce_serang.data.api.response.customer.order.CancelOrderResponse
import com.alya.ecommerce_serang.data.api.response.customer.order.OrderListItemsItem
import com.alya.ecommerce_serang.data.api.response.customer.order.Orders
import com.alya.ecommerce_serang.data.api.response.order.CompletedOrderResponse
@ -31,6 +33,11 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() {
private val _orderDetails = MutableLiveData<Orders>()
val orderDetails: LiveData<Orders> get() = _orderDetails
private val _cancelOrderStatus = MutableLiveData<Result<CancelOrderResponse>>()
val cancelOrderStatus: LiveData<Result<CancelOrderResponse>> = _cancelOrderStatus
private val _isCancellingOrder = MutableLiveData<Boolean>()
val isCancellingOrder: LiveData<Boolean> = _isCancellingOrder
// LiveData untuk OrderItems
private val _orderItems = MutableLiveData<List<OrderListItemsItem>>()
val orderItems: LiveData<List<OrderListItemsItem>> get() = _orderItems
@ -131,4 +138,26 @@ class HistoryViewModel(private val repository: OrderRepository) : ViewModel() {
}
}
}
fun cancelOrder(cancelReq: CancelOrderReq) {
viewModelScope.launch {
try {
_cancelOrderStatus.value = Result.Loading
val result = repository.cancelOrder(cancelReq)
_cancelOrderStatus.value = result
} catch (e: Exception) {
Log.e("HistoryViewModel", "Error cancelling order: ${e.message}")
_cancelOrderStatus.value = Result.Error(e)
}
}
}
fun refreshOrders(status: String = "all") {
Log.d(TAG, "Refreshing orders with status: $status")
// Clear current orders before fetching new ones
_orders.value = ViewState.Loading
// Re-fetch the orders with the current status
getOrderList(status)
}
}

View File

@ -2,6 +2,7 @@ package com.alya.ecommerce_serang.ui.order.history
import android.app.Activity
import android.app.Dialog
import android.content.ContextWrapper
import android.content.Intent
import android.graphics.Color
import android.net.Uri
@ -17,6 +18,7 @@ import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -24,6 +26,7 @@ import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.OrdersItem
import com.alya.ecommerce_serang.data.api.dto.ReviewUIItem
import com.alya.ecommerce_serang.ui.order.detail.PaymentActivity
import com.alya.ecommerce_serang.ui.order.history.cancelorder.CancelOrderBottomSheet
import com.alya.ecommerce_serang.ui.order.review.CreateReviewActivity
import com.alya.ecommerce_serang.ui.product.ReviewProductActivity
import com.google.android.material.button.MaterialButton
@ -150,7 +153,7 @@ class OrderHistoryAdapter(
visibility = View.VISIBLE
text = itemView.context.getString(R.string.canceled_order_btn)
setOnClickListener {
showCancelOrderDialog(order.orderId.toString())
showCancelOrderBottomSheet(order.orderId)
}
}
deadlineDate.apply {
@ -171,7 +174,7 @@ class OrderHistoryAdapter(
visibility = View.VISIBLE
text = itemView.context.getString(R.string.canceled_order_btn)
setOnClickListener {
showCancelOrderDialog(order.orderId.toString())
showCancelOrderBottomSheet(order.orderId)
}
}
@ -226,7 +229,6 @@ class OrderHistoryAdapter(
text = itemView.context.getString(R.string.claim_complaint)
setOnClickListener {
showCancelOrderDialog(order.orderId.toString())
// Handle click event
}
}
btnRight.apply {
@ -492,6 +494,48 @@ class OrderHistoryAdapter(
dialog.show()
}
private fun showCancelOrderBottomSheet(orderId : Int) {
val context = itemView.context
// We need a FragmentManager to show the bottom sheet
// Try to get it from the context
val fragmentActivity = when (context) {
is FragmentActivity -> context
is ContextWrapper -> {
val baseContext = context.baseContext
if (baseContext is FragmentActivity) {
baseContext
} else {
// Log error and show a Toast instead if we can't get a FragmentManager
Log.e("OrderHistoryAdapter", "Cannot show bottom sheet: Context is not a FragmentActivity")
Toast.makeText(context, "Cannot show cancel order dialog", Toast.LENGTH_SHORT).show()
return
}
}
else -> {
// Log error and show a Toast instead if we can't get a FragmentManager
Log.e("OrderHistoryAdapter", "Cannot show bottom sheet: Context is not a FragmentActivity")
Toast.makeText(context, "Cannot show cancel order dialog", Toast.LENGTH_SHORT).show()
return
}
}
// Create and show the bottom sheet using the obtained FragmentManager
val bottomSheet = CancelOrderBottomSheet(
orderId = orderId,
onOrderCancelled = {
// Handle the successful cancellation
// Refresh the data
viewModel.refreshOrders() // Assuming there's a method to refresh orders
// Show a success message
Toast.makeText(context, "Order cancelled successfully", Toast.LENGTH_SHORT).show()
}
)
bottomSheet.show(fragmentActivity.supportFragmentManager, CancelOrderBottomSheet.TAG)
}
private fun addReviewProduct(order: OrdersItem) {
// Use ViewModel to fetch order details
viewModel.getOrderDetails(order.orderId)

View File

@ -0,0 +1,173 @@
package com.alya.ecommerce_serang.ui.order.history.cancelorder
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.Button
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.viewModels
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.CancelOrderReq
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.OrderRepository
import com.alya.ecommerce_serang.data.repository.Result
import com.alya.ecommerce_serang.ui.order.history.HistoryViewModel
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
class CancelOrderBottomSheet(
private val orderId: Int,
private val onOrderCancelled: () -> Unit
) : BottomSheetDialogFragment() {
private lateinit var sessionManager: SessionManager
private val viewModel: HistoryViewModel by viewModels {
BaseViewModelFactory {
val apiService = ApiConfig.getApiService(sessionManager)
val orderRepository = OrderRepository(apiService)
HistoryViewModel(orderRepository)
}
}
private var selectedReason: CancelOrderReq? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.layout_cancel_order_bottom, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sessionManager = SessionManager(requireContext())
val tvTitle = view.findViewById<TextView>(R.id.tv_title)
val spinnerReason = view.findViewById<Spinner>(R.id.spinner_reason)
val btnCancel = view.findViewById<Button>(R.id.btn_cancel)
val btnConfirm = view.findViewById<Button>(R.id.btn_confirm)
// Set the title
tvTitle.text = "Cancel Order #$orderId"
// Set up the spinner with cancellation reasons
setupReasonSpinner(spinnerReason)
// Handle button clicks
btnCancel.setOnClickListener {
dismiss()
}
btnConfirm.setOnClickListener {
if (selectedReason == null) {
Toast.makeText(context, "Please select a reason", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
cancelOrder()
}
}
private fun setupReasonSpinner(spinner: Spinner) {
val reasons = getCancellationReasons()
val adapter = CancelReasonAdapter(requireContext(), reasons)
spinner.adapter = adapter
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
selectedReason = reasons[position]
}
override fun onNothingSelected(parent: AdapterView<*>?) {
selectedReason = null
}
}
}
private fun getCancellationReasons(): List<CancelOrderReq> {
// These should ideally come from the server or a configuration
return listOf(
CancelOrderReq(1, "Changed my mind"),
CancelOrderReq(2, "Found a better option"),
CancelOrderReq(3, "Ordered by mistake"),
CancelOrderReq(4, "Delivery time too long"),
CancelOrderReq(5, "Other reason")
)
}
private fun cancelOrder() {
// Validate reason selection
if (selectedReason == null) {
Toast.makeText(context, "Mohon pilih alasan pembatalan", Toast.LENGTH_SHORT).show()
return
}
// Create cancel request
val cancelRequest = CancelOrderReq(
orderId = orderId,
reason = selectedReason!!.reason
)
Log.d(TAG, "Sending cancel request to ViewModel: orderId=${cancelRequest.orderId}, reason='${cancelRequest.reason}'")
// Submit the cancellation
viewModel.cancelOrder(cancelRequest)
// Observe the status
viewModel.cancelOrderStatus.observe(viewLifecycleOwner) { result ->
when (result) {
is Result.Loading -> {
// Show loading indicator
// showLoading(true)
}
is Result.Success -> {
// Hide loading indicator
showLoading(false)
// Show success message
Toast.makeText(
context,
"Pesanan berhasil dibatalkan",
Toast.LENGTH_SHORT
).show()
Log.d(TAG, "Cancel order status: SUCCESS, message: ${result.data.message}")
// Notify callback and close dialog
onOrderCancelled()
dismiss()
}
is Result.Error -> {
// Hide loading indicator
showLoading(false)
Log.e(TAG, "Cancel order status: ERROR", result.exception)
// Show error message
val errorMsg = result.exception.message ?: "Gagal membatalkan pesanan"
Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show()
}
}
}
}
// private fun showLoading(isLoading: Boolean) {
// binding.progressBar.isVisible = isLoading
// binding.btnCancel.isEnabled = !isLoading
// binding.btnConfirm.isEnabled = !isLoading
// }
private fun showLoading(isLoading: Boolean) {
// Implement loading indicator if needed
}
companion object {
const val TAG = "CancelOrderBottomSheet"
}
}

View File

@ -0,0 +1,36 @@
package com.alya.ecommerce_serang.ui.order.history.cancelorder
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import com.alya.ecommerce_serang.R
import com.alya.ecommerce_serang.data.api.dto.CancelOrderReq
class CancelReasonAdapter(
context: Context,
private val reasons: List<CancelOrderReq>
) : ArrayAdapter<CancelOrderReq>(context, 0, reasons) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return createItemView(position, convertView, parent)
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return createItemView(position, convertView, parent)
}
private fun createItemView(position: Int, recycledView: View?, parent: ViewGroup): View {
val reason = getItem(position) ?: return recycledView ?: View(context)
val view = recycledView ?: LayoutInflater.from(context)
.inflate(R.layout.item_cancel_order, parent, false)
val tvReason = view.findViewById<TextView>(R.id.tv_reason)
tvReason.text = reason.reason
return view
}
}

View File

@ -1,5 +1,7 @@
package com.alya.ecommerce_serang.ui.profile
import android.app.AlertDialog
import android.app.ProgressDialog
import android.content.Intent
import android.os.Bundle
import android.util.Log
@ -9,19 +11,24 @@ import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
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.UserProfile
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
import com.alya.ecommerce_serang.data.repository.UserRepository
import com.alya.ecommerce_serang.databinding.FragmentProfileBinding
import com.alya.ecommerce_serang.ui.auth.LoginActivity
import com.alya.ecommerce_serang.ui.auth.RegisterStoreActivity
import com.alya.ecommerce_serang.ui.order.address.AddressActivity
import com.alya.ecommerce_serang.ui.order.history.HistoryActivity
import com.alya.ecommerce_serang.ui.profile.mystore.MyStoreActivity
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
import com.alya.ecommerce_serang.utils.SessionManager
import com.alya.ecommerce_serang.utils.viewmodel.ProfileViewModel
import com.bumptech.glide.Glide
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class ProfileFragment : Fragment() {
@ -85,6 +92,16 @@ class ProfileFragment : Fragment() {
val intent = Intent(requireContext(), HistoryActivity::class.java)
startActivity(intent)
}
binding.cardLogout.setOnClickListener({
logout()
})
binding.cardAddress.setOnClickListener({
val intent = Intent(requireContext(), AddressActivity::class.java)
startActivity(intent)
})
}
private fun observeUserProfile() {
@ -115,4 +132,41 @@ class ProfileFragment : Fragment() {
.placeholder(R.drawable.placeholder_image)
.into(profileImage)
}
private fun logout(){
AlertDialog.Builder(requireContext())
.setTitle("Konfirmasi")
.setMessage("Apakah anda yakin ingin keluar?")
.setPositiveButton("Ya") { _, _ ->
actionLogout()
}
.setNegativeButton("Tidak", null)
.show()
}
private fun actionLogout(){
val loadingDialog = ProgressDialog(requireContext()).apply {
setMessage("Mohon ditunggu")
setCancelable(false)
show()
}
lifecycleScope.launch {
try {
delay(500)
loadingDialog.dismiss()
sessionManager.clearAll()
val intent = Intent(requireContext(), LoginActivity::class.java)
startActivity(intent)
} catch (e: Exception) {
Toast.makeText(
requireContext(),
"Gagal keluar: ${e.message}",
Toast.LENGTH_SHORT
).show()
}
}
}
}

View File

@ -57,8 +57,6 @@ class SessionManager(context: Context) {
}
}
//clear data when log out
fun clearAll() {
sharedPreferences.edit() {

View File

@ -27,6 +27,9 @@ class ProfileViewModel(private val userRepository: UserRepository) : ViewModel()
private val _checkStore = MutableLiveData<Boolean>()
val checkStore: LiveData<Boolean> = _checkStore
private val _logout = MutableLiveData<Boolean>()
val logout : LiveData<Boolean> = _checkStore
fun loadUserProfile(){
viewModelScope.launch {
when (val result = userRepository.fetchUserProfile()){
@ -57,8 +60,6 @@ class ProfileViewModel(private val userRepository: UserRepository) : ViewModel()
}
}
fun editProfileDirect(
context: Context,
username: String,
@ -97,6 +98,17 @@ class ProfileViewModel(private val userRepository: UserRepository) : ViewModel()
}
}
fun logout(){
viewModelScope.launch {
try{
} catch (e: Exception){
}
}
}
companion object {
private const val TAG = "ProfileViewModel"
}

View 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="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#211E1E" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M17,7l-1.41,1.41L18.17,11H8v2h10.17l-2.58,2.58L17,17l5,-5zM4,5h8V3H4c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h8v-2H4V5z"/>
</vector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<corners
android:topLeftRadius="16dp"
android:topRightRadius="16dp" />
</shape>

View File

@ -0,0 +1,8 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="@color/light_gray" />
</shape>

View File

@ -231,13 +231,14 @@
android:text="10. Bank *"
android:textColor="@android:color/black" />
<Spinner
android:id="@+id/spinner_bank"
<EditText
android:id="@+id/et_bank_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@android:drawable/btn_dropdown"
android:hint="Pilih jawaban Anda di sini"
android:background="@android:drawable/editbox_background"
android:hint="Isi jawaban Anda di sini"
android:inputType="text"
android:padding="12dp" />
<TextView

View File

@ -118,6 +118,7 @@
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:paddingVertical="8dp"
android:textSize="16sp"
android:fontFamily="@font/dmsans_medium"
android:text="Pesanan Saya"
app:layout_constraintStart_toStartOf="parent"
@ -129,7 +130,7 @@
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_marginEnd="16dp"
android:textSize="12sp"
android:textSize="14sp"
android:padding="0dp"
android:fontFamily="@font/dmsans_light"
android:text="Lihat Riwayat Pesanan"
@ -164,6 +165,8 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:fontFamily="@font/dmsans_regular"
android:layout_marginTop="8dp"
android:text="@string/waiting_payment" />
</LinearLayout>
@ -185,6 +188,8 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/dmsans_regular"
android:textSize="12sp"
android:layout_marginTop="8dp"
android:text="@string/packages" />
</LinearLayout>
@ -206,6 +211,8 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/dmsans_regular"
android:textSize="12sp"
android:layout_marginTop="8dp"
android:text="@string/delivery" />
</LinearLayout>
@ -223,103 +230,176 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:padding="16dp"
android:textSize="16sp"
android:text="Pengaturan Akun"
android:fontFamily="@font/dmsans_medium"
app:layout_constraintTop_toBottomOf="@id/cardPesanan" />
<!-- Address -->
<ImageView
android:id="@+id/ivAddress"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:src="@drawable/ic_address"
app:layout_constraintBottom_toBottomOf="@id/tvAddress"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tvAddress" />
<TextView
android:id="@+id/tvAddress"
android:layout_width="0dp"
<LinearLayout
android:id="@+id/container_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:orientation="vertical"
android:padding="16dp"
android:text="Alamat"
app:layout_constraintEnd_toStartOf="@id/ivAddressArrow"
app:layout_constraintStart_toEndOf="@id/ivAddress"
app:layout_constraintTop_toBottomOf="@id/tvPengaturanAkun" />
app:layout_constraintTop_toBottomOf="@id/tvPengaturanAkun"
app:layout_constraintStart_toStartOf="parent">
<!-- Address Card -->
<androidx.cardview.widget.CardView
android:id="@+id/card_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:foreground="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ImageView
android:id="@+id/ivAddress"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_address"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/tvAddress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Alamat"
android:textSize="14sp"
android:fontFamily="@font/dmsans_regular"
app:layout_constraintStart_toEndOf="@id/ivAddress"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/ivAddressArrow" />
<ImageView
android:id="@+id/ivAddressArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_arrow_right"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<!-- About Card -->
<androidx.cardview.widget.CardView
android:id="@+id/card_about"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:foreground="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ImageView
android:id="@+id/ivAbout"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/baseline_info_24"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/tvAbout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Tentang"
android:textSize="14sp"
app:layout_constraintStart_toEndOf="@id/ivAbout"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/ivAboutArrow" />
<ImageView
android:id="@+id/ivAboutArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_arrow_right"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<!-- Logout Card -->
<androidx.cardview.widget.CardView
android:id="@+id/card_logout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ImageView
android:id="@+id/ivLogout"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/baseline_logout_24"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<TextView
android:id="@+id/tvLogout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Keluar"
android:textSize="14sp"
app:layout_constraintStart_toEndOf="@id/ivLogout"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/ivLogoutArrow" />
<ImageView
android:id="@+id/ivLogoutArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_arrow_right"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
<ImageView
android:id="@+id/ivAddressArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_arrow_right"
app:layout_constraintBottom_toBottomOf="@id/tvAddress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvAddress" />
<!-- About -->
<ImageView
android:id="@+id/ivAbout"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@id/tvAbout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tvAbout" />
<TextView
android:id="@+id/tvAbout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:padding="16dp"
android:text="Tentang"
app:layout_constraintEnd_toStartOf="@id/ivAboutArrow"
app:layout_constraintStart_toEndOf="@id/ivAbout"
app:layout_constraintTop_toBottomOf="@id/tvAddress" />
<ImageView
android:id="@+id/ivAboutArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_arrow_right"
app:layout_constraintBottom_toBottomOf="@id/tvAbout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvAbout" />
<!-- Logout -->
<ImageView
android:id="@+id/ivLogout"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="@id/tvLogout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/tvLogout" />
<TextView
android:id="@+id/tvLogout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:padding="16dp"
android:text="Keluar"
app:layout_constraintEnd_toStartOf="@id/ivLogoutArrow"
app:layout_constraintStart_toEndOf="@id/ivLogout"
app:layout_constraintTop_toBottomOf="@id/tvAbout" />
<ImageView
android:id="@+id/ivLogoutArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_arrow_right"
app:layout_constraintBottom_toBottomOf="@id/tvLogout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvLogout" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_reason"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:textColor="@color/black"
android:textSize="14sp" />

View File

@ -1,16 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="start"
android:layout_marginStart="16dp"
android:layout_marginVertical="8dp"
android:layout_marginBottom="4dp"
android:layout_gravity="center_horizontal"
android:orientation="horizontal">
<RadioButton
@ -27,7 +29,7 @@
android:orientation="vertical">
<TextView
android:id="@+id/courier_name_cost"
android:fontFamily="@font/dmsans_bold"
android:fontFamily="@font/dmsans_semibold"
android:textSize="20sp"
android:padding="4dp"
android:layout_width="wrap_content"
@ -40,8 +42,8 @@
android:textSize="16sp"
android:paddingHorizontal="8dp"
android:text="Estimasi 3-4 hari"/>
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/cost_price"
@ -53,12 +55,16 @@
android:layout_height="match_parent"
android:text="Rp15.0000"/>
</LinearLayout>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="4dp"
android:background="#E0E0E0"
app:layout_constraintTop_toBottomOf="@id/linear_toolbar" />
app:layout_constraintTop_toBottomOf="@id/content" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_bottom_sheet"
android:padding="16dp">
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="@font/dmsans_semibold"
android:text="Batalkan Pesanan"
android:textAlignment="center"
android:textColor="@color/black"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_reason_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Alasan batalkan pesanan:"
android:fontFamily="@font/dmsans_regular"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title" />
<Spinner
android:id="@+id/spinner_reason"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/bg_spinner_reason"
android:padding="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_reason_label" />
<Button
android:id="@+id/btn_cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginEnd="8dp"
android:backgroundTint="@color/black_200"
android:text="Kembali"
android:fontFamily="@font/dmsans_medium"
android:textColor="@color/white"
app:layout_constraintEnd_toStartOf="@+id/btn_confirm"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/spinner_reason" />
<Button
android:id="@+id/btn_confirm"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:backgroundTint="@color/blue_500"
android:text="Batal"
android:fontFamily="@font/dmsans_medium"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="@+id/btn_cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/btn_cancel"
app:layout_constraintTop_toTopOf="@+id/btn_cancel" />
</androidx.constraintlayout.widget.ConstraintLayout>