Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package com.woocommerce.android.ui.bookings.compose

import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.woocommerce.android.R
import com.woocommerce.android.ui.compose.component.WCColoredButton
import com.woocommerce.android.ui.compose.component.WCOutlinedButton
import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews
import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground

@Composable
fun BookingPaymentSection(
model: BookingPaymentDetailsModel,
status: BookingStatus,
onMarkAsPaid: () -> Unit,
onMarkAsRefunded: () -> Unit,
onViewOrder: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier) {
BookingSectionHeader(R.string.booking_payment_header)
HorizontalDivider(thickness = 0.5.dp)
Column(
modifier = Modifier
.background(color = MaterialTheme.colorScheme.surfaceContainer)
.padding(vertical = 16.dp)
) {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
PaymentRow(label = R.string.booking_payment_label_service, value = model.service)
PaymentRow(label = R.string.tax, value = model.tax)
PaymentRow(label = R.string.discount, value = model.discount)
PaymentRow(label = R.string.total, value = model.total, fontWeight = FontWeight.Medium)
}
Spacer(modifier = Modifier.height(16.dp))
HorizontalDivider(
thickness = 0.5.dp,
modifier = Modifier.padding(start = 16.dp)
)
Spacer(modifier = Modifier.height(16.dp))
// TODO Change the logic and use Order info when available
if (status == BookingStatus.Paid) {
WCOutlinedButton(
onClick = onMarkAsRefunded,
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.onSurface
),
text = stringResource(id = R.string.orderdetail_issue_refund_button),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
)
} else {
WCColoredButton(
onClick = onMarkAsPaid,
text = stringResource(id = R.string.booking_payment_mark_as_paid),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
)
}
Spacer(modifier = Modifier.height(8.dp))
WCOutlinedButton(
onClick = onViewOrder,
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.onSurface
),
text = stringResource(id = R.string.booking_payment_view_order),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
)
}
}
}

@Composable
private fun PaymentRow(
@StringRes label: Int,
value: String,
fontWeight: FontWeight = FontWeight.Normal,
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
) {
PaymentText(stringResource(label), fontWeight = fontWeight)
PaymentText(value, modifier = Modifier.padding(start = 8.dp), fontWeight = fontWeight)
}
}

@Composable
private fun PaymentText(
text: String,
fontWeight: FontWeight,
modifier: Modifier = Modifier,
) {
Text(
text = text,
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = fontWeight),
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = modifier
)
}

data class BookingPaymentDetailsModel(
val service: String,
val tax: String,
val discount: String,
val total: String
)

@LightDarkThemePreviews
@Composable
private fun BookingPaymentSectionPreview() {
WooThemeWithBackground {
BookingPaymentSection(
model = BookingPaymentDetailsModel(
service = "$55.00",
tax = "$4.50",
discount = "-",
total = "$59.50"
),
status = BookingStatus.Complete,
onMarkAsPaid = {},
onViewOrder = {},
onMarkAsRefunded = {},
modifier = Modifier.fillMaxWidth()
)
}
}

@LightDarkThemePreviews
@Composable
private fun BookingPaymentSectionWithRefundOptionPreview() {
WooThemeWithBackground {
BookingPaymentSection(
model = BookingPaymentDetailsModel(
service = "$55.00",
tax = "$4.50",
discount = "-",
total = "$59.50"
),
status = BookingStatus.Paid,
onMarkAsPaid = {},
onViewOrder = {},
onMarkAsRefunded = {},
modifier = Modifier.fillMaxWidth()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ class BookingDetailsFragment : BaseFragment() {
return composeView {
BookingDetailsScreen(
viewModel = viewModel,
onBack = { findNavController().popBackStack() }
onBack = { findNavController().popBackStack() },
onViewOrder = { orderId ->
findNavController().navigate(
BookingDetailsFragmentDirections
.actionBookingDetailsFragmentToOrderDetailFragment(orderId)
)
}
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.woocommerce.android.ui.bookings.compose.BookingAttendanceSection
import com.woocommerce.android.ui.bookings.compose.BookingAttendanceStatus
import com.woocommerce.android.ui.bookings.compose.BookingAttendanceStatusBottomSheet
import com.woocommerce.android.ui.bookings.compose.BookingCustomerDetails
import com.woocommerce.android.ui.bookings.compose.BookingPaymentSection
import com.woocommerce.android.ui.bookings.compose.BookingStatus
import com.woocommerce.android.ui.bookings.compose.BookingSummary
import com.woocommerce.android.ui.bookings.compose.BookingSummaryModel
Expand All @@ -36,16 +37,16 @@ import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground
@Composable
fun BookingDetailsScreen(
viewModel: BookingDetailsViewModel,
onBack: () -> Unit
onBack: () -> Unit,
onViewOrder: (Long) -> Unit
) {
val viewState by viewModel.state.observeAsState()

viewState?.let {
BookingDetailsScreen(
viewState = it,
onBack = onBack,
onCancelBooking = viewModel::onCancelBooking,
onAttendanceStatusSelected = viewModel::onAttendanceStatusSelected
onViewOrder = onViewOrder,
)
}
}
Expand All @@ -54,8 +55,7 @@ fun BookingDetailsScreen(
fun BookingDetailsScreen(
viewState: BookingDetailsViewState,
onBack: () -> Unit,
onCancelBooking: () -> Unit,
onAttendanceStatusSelected: (BookingAttendanceStatus) -> Unit
onViewOrder: (Long) -> Unit,
) {
val showAttendanceSheet = remember { mutableStateOf(false) }
Scaffold(
Expand All @@ -82,7 +82,7 @@ fun BookingDetailsScreen(
)
BookingAppointmentDetails(
model = viewState.bookingsAppointmentDetails,
onCancelBooking = onCancelBooking,
onCancelBooking = viewState.onCancelBooking,
modifier = Modifier.fillMaxWidth()
)
BookingCustomerDetails(
Expand All @@ -96,11 +96,19 @@ fun BookingDetailsScreen(
onClick = { showAttendanceSheet.value = true },
modifier = Modifier.fillMaxWidth()
)
BookingPaymentSection(
model = viewState.bookingPaymentDetails,
status = viewState.bookingSummary.status,
onMarkAsPaid = { onViewOrder(viewState.orderId) },
onViewOrder = { onViewOrder(viewState.orderId) },
onMarkAsRefunded = { onViewOrder(viewState.orderId) },
modifier = Modifier.fillMaxWidth()
)
}
if (showAttendanceSheet.value) {
BookingAttendanceStatusBottomSheet(
onSelect = { status ->
onAttendanceStatusSelected(status)
viewState.onAttendanceStatusSelected(status)
},
onDismiss = { showAttendanceSheet.value = false }
)
Expand Down Expand Up @@ -133,8 +141,7 @@ private fun BookingDetailsPreview() {
)
),
onBack = {},
onCancelBooking = {},
onAttendanceStatusSelected = {}
onViewOrder = {}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ class BookingDetailsViewModel @Inject constructor(

private val navArgs: BookingDetailsFragmentArgs by savedState.navArgs()

private val _state = MutableStateFlow(BookingDetailsViewState())
private val _state = MutableStateFlow(
BookingDetailsViewState(
onCancelBooking = ::onCancelBooking,
onAttendanceStatusSelected = ::onAttendanceStatusSelected,
)
)
val state: LiveData<BookingDetailsViewState> = _state.asLiveData()

init {
Expand All @@ -32,15 +37,15 @@ class BookingDetailsViewModel @Inject constructor(
}
}

fun onAttendanceStatusSelected(status: BookingAttendanceStatus) {
private fun onAttendanceStatusSelected(status: BookingAttendanceStatus) {
_state.update { current ->
current.copy(
bookingSummary = current.bookingSummary.copy(attendanceStatus = status)
)
}
}

fun onCancelBooking() {
private fun onCancelBooking() {
// TODO Add logic to Cancel booking
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package com.woocommerce.android.ui.bookings.details
import com.woocommerce.android.ui.bookings.compose.BookingAppointmentDetailsModel
import com.woocommerce.android.ui.bookings.compose.BookingAttendanceStatus
import com.woocommerce.android.ui.bookings.compose.BookingCustomerDetailsModel
import com.woocommerce.android.ui.bookings.compose.BookingPaymentDetailsModel
import com.woocommerce.android.ui.bookings.compose.BookingStatus
import com.woocommerce.android.ui.bookings.compose.BookingSummaryModel

data class BookingDetailsViewState(
val toolbarTitle: String = "",
val orderId: Long = 0L,
val bookingSummary: BookingSummaryModel = BookingSummaryModel(
date = "05/07/2025, 11:00 AM",
name = "Women’s Haircut",
Expand All @@ -32,5 +34,13 @@ data class BookingDetailsViewState(
"Montgomery AL 36109",
"United States"
)
)
),
val bookingPaymentDetails: BookingPaymentDetailsModel = BookingPaymentDetailsModel(
service = "$55.00",
tax = "$4.50",
discount = "-",
total = "$59.50"
),
val onCancelBooking: () -> Unit = {},
val onAttendanceStatusSelected: (BookingAttendanceStatus) -> Unit = { _ -> }
)
9 changes: 8 additions & 1 deletion WooCommerce/src/main/res/navigation/nav_graph_bookings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<fragment
android:id="@+id/bookingListFragment"
android:name="com.woocommerce.android.ui.bookings.list.BookingListFragment"
android:label="fragment_bookings" >
android:label="fragment_bookings">
<action
android:id="@+id/action_bookingListFragment_to_bookingDetailsFragment"
app:destination="@id/bookingDetailsFragment" />
Expand All @@ -20,6 +20,13 @@
android:name="bookingId"
android:defaultValue="-1L"
app:argType="long" />
<action
android:id="@+id/action_bookingDetailsFragment_to_orderDetailFragment"
app:destination="@id/nav_graph_orders">
<argument
android:name="orderId"
app:argType="long" />
</action>
</fragment>

</navigation>
4 changes: 4 additions & 0 deletions WooCommerce/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4895,5 +4895,9 @@
<string name="booking_attendance_status_checked_in_desc">The customer arrived and the session took place as planned.</string>
<string name="booking_attendance_status_cancelled_desc">The client will no longer be able to attend.</string>
<string name="booking_attendance_status_no_show_desc">The client missed the appointment without canceling in advance.</string>
<string name="booking_payment_header">PAYMENT</string>
<string name="booking_payment_label_service">Service</string>
<string name="booking_payment_mark_as_paid">Mark as paid</string>
<string name="booking_payment_view_order">View order</string>

</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.bookings.details
import androidx.lifecycle.SavedStateHandle
import com.woocommerce.android.R
import com.woocommerce.android.ui.bookings.compose.BookingAttendanceStatus
import com.woocommerce.android.util.getOrAwaitValue
import com.woocommerce.android.viewmodel.BaseUnitTest
import com.woocommerce.android.viewmodel.ResourceProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down Expand Up @@ -42,7 +43,8 @@ class BookingDetailsViewModelTest : BaseUnitTest() {
val viewModel = createViewModel(savedState, resourceProvider)

// When
viewModel.onAttendanceStatusSelected(BookingAttendanceStatus.CANCELLED)
val state = viewModel.state.getOrAwaitValue()
state.onAttendanceStatusSelected(BookingAttendanceStatus.CANCELLED)

// Then
val updated = viewModel.state.value?.bookingSummary?.attendanceStatus
Expand Down