-
Notifications
You must be signed in to change notification settings - Fork 136
[WOOMOB-1511] - Booking details cancel flow #14752
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b0ec359
1916d68
bf3935b
b8ea834
37a98a6
cbf1857
d0c3f31
841bba3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,7 +70,11 @@ private fun BookingAttendanceStatusSelection( | |
| color = MaterialTheme.colorScheme.onSurfaceVariant, | ||
| modifier = Modifier.padding(bottom = 22.dp) | ||
| ) | ||
| BookingAttendanceStatus.entries.forEachIndexed { index, status -> | ||
| listOf( | ||
| BookingAttendanceStatus.BOOKED, | ||
| BookingAttendanceStatus.CHECKED_IN, | ||
| BookingAttendanceStatus.NO_SHOW, | ||
| ).forEachIndexed { index, status -> | ||
| AttendanceStatusRow( | ||
| status = status, | ||
| onClick = { onSelect(status) } | ||
|
|
@@ -92,12 +96,14 @@ private fun AttendanceStatusRow( | |
| verticalAlignment = Alignment.CenterVertically, | ||
| horizontalArrangement = Arrangement.spacedBy(16.dp) | ||
| ) { | ||
| Icon( | ||
| painter = painterResource(status.iconRes), | ||
| contentDescription = status.text(), | ||
| tint = MaterialTheme.colorScheme.onSurfaceVariant, | ||
| modifier = Modifier.size(24.dp) | ||
| ) | ||
| status.iconRes?.let { iconRes -> | ||
| Icon( | ||
| painter = painterResource(iconRes), | ||
| contentDescription = status.text(), | ||
| tint = MaterialTheme.colorScheme.onSurfaceVariant, | ||
| modifier = Modifier.size(24.dp) | ||
| ) | ||
| } | ||
|
Comment on lines
+99
to
+106
|
||
| Column(modifier = Modifier.weight(1f)) { | ||
| Text( | ||
| text = status.text(), | ||
|
|
@@ -118,16 +124,16 @@ private fun AttendanceStatusRow( | |
| private fun BookingAttendanceStatus.description(): String = when (this) { | ||
| BookingAttendanceStatus.BOOKED -> R.string.booking_attendance_status_booked_desc | ||
| BookingAttendanceStatus.CHECKED_IN -> R.string.booking_attendance_status_checked_in_desc | ||
| BookingAttendanceStatus.CANCELLED -> R.string.booking_attendance_status_cancelled_desc | ||
| BookingAttendanceStatus.NO_SHOW -> R.string.booking_attendance_status_no_show_desc | ||
| }.let { stringResource(it) } | ||
| else -> null | ||
| }?.let { stringResource(it) } ?: "" | ||
|
|
||
| private val BookingAttendanceStatus.iconRes: Int | ||
| private val BookingAttendanceStatus.iconRes: Int? | ||
| get() = when (this) { | ||
| BookingAttendanceStatus.BOOKED -> R.drawable.ic_attendance_booked | ||
| BookingAttendanceStatus.CHECKED_IN -> R.drawable.ic_attendance_checked_in | ||
| BookingAttendanceStatus.CANCELLED -> R.drawable.ic_attendance_cancelled | ||
| BookingAttendanceStatus.NO_SHOW -> R.drawable.ic_attendance_no_show | ||
| else -> null | ||
| } | ||
|
|
||
| @LightDarkThemePreviews | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,13 +5,15 @@ import androidx.lifecycle.SavedStateHandle | |
| import androidx.lifecycle.asLiveData | ||
| import androidx.lifecycle.viewModelScope | ||
| import com.woocommerce.android.R | ||
| import com.woocommerce.android.model.UiString | ||
| import com.woocommerce.android.tools.NetworkStatus | ||
| import com.woocommerce.android.ui.bookings.Booking | ||
| import com.woocommerce.android.ui.bookings.BookingMapper | ||
| import com.woocommerce.android.ui.bookings.BookingResource | ||
| import com.woocommerce.android.ui.bookings.BookingsRepository | ||
| import com.woocommerce.android.ui.bookings.compose.BookingAttendanceStatus | ||
| import com.woocommerce.android.ui.bookings.compose.BookingStaffMemberStatus | ||
| import com.woocommerce.android.ui.compose.DialogState | ||
| import com.woocommerce.android.viewmodel.MultiLiveEvent | ||
| import com.woocommerce.android.viewmodel.ResourceProvider | ||
| import com.woocommerce.android.viewmodel.ScopedViewModel | ||
|
|
@@ -20,6 +22,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel | |
| import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
| import kotlinx.coroutines.async | ||
| import kotlinx.coroutines.awaitAll | ||
| import kotlinx.coroutines.delay | ||
| import kotlinx.coroutines.flow.MutableStateFlow | ||
| import kotlinx.coroutines.flow.SharingStarted | ||
| import kotlinx.coroutines.flow.combine | ||
|
|
@@ -28,6 +31,7 @@ import kotlinx.coroutines.flow.flatMapLatest | |
| import kotlinx.coroutines.flow.flowOf | ||
| import kotlinx.coroutines.flow.shareIn | ||
| import kotlinx.coroutines.launch | ||
| import java.time.Duration | ||
| import javax.inject.Inject | ||
|
|
||
| @OptIn(ExperimentalCoroutinesApi::class) | ||
|
|
@@ -54,25 +58,62 @@ class BookingDetailsViewModel @Inject constructor( | |
| // Temporary, the booking status should come from the stored object | ||
| private val bookingAttendanceStatus = MutableStateFlow<BookingAttendanceStatus?>(null) | ||
|
|
||
| val state: LiveData<BookingDetailsViewState> = combine( | ||
| private val cancelStatusState = MutableStateFlow<CancelStatus>(CancelStatus.Idle) | ||
| private val showCancelBookingDialog = MutableStateFlow(false) | ||
|
|
||
| private val cancelBookingDialogState = combine( | ||
| booking, | ||
| showCancelBookingDialog, | ||
| ) { booking, showCancelBooking -> | ||
| if (showCancelBooking && booking != null) { | ||
| val message = bookingMapper.buildCancelDialogMessage(booking) | ||
| DialogState( | ||
| title = UiString.UiStringRes(R.string.booking_cancel_dialog_title), | ||
| message = message, | ||
| positiveButton = DialogState.DialogButton( | ||
| text = UiString.UiStringRes(R.string.booking_cancel_dialog_confirm), | ||
| onClick = ::onConfirmCancelBooking | ||
| ), | ||
| negativeButton = DialogState.DialogButton( | ||
| text = UiString.UiStringRes(R.string.booking_cancel_dialog_keep), | ||
| onClick = ::onDismissCancelDialog | ||
| ), | ||
| ) | ||
| } else { | ||
| null | ||
| } | ||
| } | ||
|
|
||
| private val bookingUiStateFlow = combine( | ||
| booking, | ||
| bookingAttendanceStatus, | ||
| loadingState, | ||
| resource | ||
| ) { booking, attendanceStatus, loadingState, resource -> | ||
| resource, | ||
| cancelStatusState, | ||
| ) { booking, attendanceStatus, loadingState, resource, cancelStatus -> | ||
| if (booking != null) { | ||
| bookingMapper.buildBookingUiState(booking, attendanceStatus, resource, loadingState, cancelStatus) | ||
| } else { | ||
| null | ||
| } | ||
| } | ||
|
|
||
| val state: LiveData<BookingDetailsViewState> = combine( | ||
| booking, | ||
| bookingUiStateFlow, | ||
| loadingState, | ||
| cancelBookingDialogState, | ||
|
Comment on lines
+102
to
+105
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was becoming quite big, so I extracted some of those to separate Flows (booking Ui state, and cancel booking dialog state) |
||
| ) { booking, bookingUiState, loadingState, cancelBookingDialog -> | ||
| with(bookingMapper) { | ||
| BookingDetailsViewState( | ||
| toolbarTitle = booking?.id?.value?.let { id -> | ||
| resourceProvider.getString(R.string.booking_details_title, id) | ||
| } ?: "", | ||
| bookingUiState = if (booking != null) { | ||
| buildBookingUiState(booking, resource, attendanceStatus, loadingState) | ||
| } else { | ||
| null | ||
| }, | ||
| loadingState = loadingState, | ||
| bookingUiState = bookingUiState, | ||
| onCancelBooking = ::onCancelBooking, | ||
| onAttendanceStatusSelected = ::onAttendanceStatusSelected, | ||
| dialogState = cancelBookingDialog, | ||
| loadingState = loadingState, | ||
| onRefresh = ::fetchBooking, | ||
| ) | ||
| } | ||
|
|
@@ -116,14 +157,27 @@ class BookingDetailsViewModel @Inject constructor( | |
| } | ||
|
|
||
| private fun onCancelBooking() { | ||
| // TODO Add logic to Cancel booking | ||
| showCancelBookingDialog.value = true | ||
| } | ||
|
|
||
| private fun onDismissCancelDialog() { | ||
| showCancelBookingDialog.value = false | ||
| } | ||
|
|
||
| private fun onConfirmCancelBooking() = launch { | ||
| // TODO Add logic to Cancel booking action | ||
| showCancelBookingDialog.value = false | ||
| cancelStatusState.value = CancelStatus.InProgress | ||
| delay(Duration.ofSeconds(1).toMillis()) | ||
| cancelStatusState.value = CancelStatus.Idle | ||
| } | ||
|
|
||
| private suspend fun BookingMapper.buildBookingUiState( | ||
| booking: Booking, | ||
| resource: BookingResource?, | ||
| attendanceStatus: BookingAttendanceStatus?, | ||
| loadingState: BookingDetailsLoadingState | ||
| resource: BookingResource?, | ||
| loadingState: BookingDetailsLoadingState, | ||
| cancelStatus: CancelStatus, | ||
| ): BookingUiState = BookingUiState( | ||
| orderId = booking.orderId, | ||
| bookingSummary = booking.toBookingSummaryModel().let { | ||
|
|
@@ -138,7 +192,8 @@ class BookingDetailsViewModel @Inject constructor( | |
| resourceId = booking.resourceId, | ||
| resource = resource, | ||
| loadingState = loadingState | ||
| ) | ||
| ), | ||
| cancelStatus = cancelStatus | ||
| ), | ||
| bookingCustomerDetails = booking.order.customerInfo.toCustomerDetailsModel(), | ||
| bookingPaymentDetails = booking.order.paymentInfo?.toPaymentDetailsModel(booking.currency) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor remark, WDYT about updating
WCOutlinedButtonto accept aloadingparameter like what we do withWCColoredButtonhere, and move this logic to the component.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you do, please also make sure to update
WCColoredButton's implementation to passenabled = enabled && !loading, to make sure we disable button during loading.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't mind, I would prefer to open a separate PR with it just to isolate the change that goes beyond Booking Details screen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR is here #14768