Skip to content

Commit 9e86589

Browse files
committed
Merge branch 'trunk' into dependabot/gradle/androidx.compose-compose-bom-2025.11.00
2 parents 93069c2 + 4baabce commit 9e86589

File tree

38 files changed

+659
-442
lines changed

38 files changed

+659
-442
lines changed

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*** For entries which are touching the Android Wear app's, start entry with `[WEAR]` too.
44
23.7
55
-----
6+
- [Internal][Woo POS] Survey for potential and current users of the POS delivered via local push notification. [https://github.com/woocommerce/woocommerce-android/pull/14751]
67
- [Internal] Handle BLUETOOTH_PEER_REMOVED_PAIRING_INFORMATION disconnect reason from Stripe SDK [https://github.com/woocommerce/woocommerce-android/pull/14886]
78

89
23.6

WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/local/LocalNotificationType.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package com.woocommerce.android.notifications.local
33
enum class LocalNotificationType(val value: String) {
44
BLAZE_NO_CAMPAIGN_REMINDER("blaze_no_campaign_reminder"),
55
BLAZE_ABANDONED_CAMPAIGN_REMINDER("blaze_abandoned_campaign_reminder"),
6-
WOO_POS_SURVEY_POTENTIAL_USER_REMINDER("woo_pos_survey_potential_user_reminder"),
7-
WOO_POS_SURVEY_CURRENT_USER_REMINDER("woo_pos_survey_current_user_reminder");
6+
WOO_POS_SURVEY_POTENTIAL_USER_REMINDER("woo_pos_survey_potential_user_survey"),
7+
WOO_POS_SURVEY_CURRENT_USER_REMINDER("woo_pos_survey_current_user_survey");
88
override fun toString() = value
99

1010
companion object {

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/compose/BookingAttendanceStatusBottomSheet.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.woocommerce.android.ui.bookings.compose
22

33
import androidx.compose.foundation.clickable
44
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Box
56
import androidx.compose.foundation.layout.Column
67
import androidx.compose.foundation.layout.Row
78
import androidx.compose.foundation.layout.Spacer
@@ -34,6 +35,7 @@ fun BookingAttendanceStatusBottomSheet(
3435
onSelect: (BookingAttendanceStatus) -> Unit,
3536
onDismiss: () -> Unit,
3637
modifier: Modifier = Modifier,
38+
selected: BookingAttendanceStatus? = null,
3739
) {
3840
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
3941
val scope = rememberCoroutineScope()
@@ -48,6 +50,7 @@ fun BookingAttendanceStatusBottomSheet(
4850
onSelect(status)
4951
onDismiss()
5052
},
53+
selected = selected,
5154
modifier = modifier
5255
)
5356
}
@@ -57,6 +60,7 @@ fun BookingAttendanceStatusBottomSheet(
5760
private fun BookingAttendanceStatusSelection(
5861
onSelect: (BookingAttendanceStatus) -> Unit,
5962
modifier: Modifier = Modifier,
63+
selected: BookingAttendanceStatus? = null,
6064
) {
6165
Column(
6266
modifier = modifier
@@ -74,9 +78,10 @@ private fun BookingAttendanceStatusSelection(
7478
BookingAttendanceStatus.Booked,
7579
BookingAttendanceStatus.CheckedIn,
7680
BookingAttendanceStatus.NoShow,
77-
).forEachIndexed { index, status ->
81+
).forEach { status ->
7882
AttendanceStatusRow(
7983
status = status,
84+
isSelected = status == selected,
8085
onClick = { onSelect(status) }
8186
)
8287
}
@@ -86,6 +91,7 @@ private fun BookingAttendanceStatusSelection(
8691
@Composable
8792
private fun AttendanceStatusRow(
8893
status: BookingAttendanceStatus,
94+
isSelected: Boolean,
8995
onClick: () -> Unit,
9096
) {
9197
Row(
@@ -117,6 +123,15 @@ private fun AttendanceStatusRow(
117123
color = MaterialTheme.colorScheme.onSurfaceVariant
118124
)
119125
}
126+
Box(Modifier.size(26.dp)) {
127+
if (isSelected) {
128+
Icon(
129+
painter = painterResource(id = R.drawable.ic_done_secondary),
130+
contentDescription = stringResource(R.string.bookings_filters_selected_option_content_description),
131+
tint = MaterialTheme.colorScheme.primary
132+
)
133+
}
134+
}
120135
}
121136
}
122137

@@ -152,6 +167,7 @@ private fun AttendanceStatusRowPreview() {
152167
WooThemeWithBackground {
153168
AttendanceStatusRow(
154169
status = BookingAttendanceStatus.CheckedIn,
170+
isSelected = false,
155171
onClick = {}
156172
)
157173
}

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/compose/BookingSummary.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ fun BookingSummary(
3030
) {
3131
Column(
3232
modifier = modifier
33-
.background(color = MaterialTheme.colorScheme.surfaceContainer)
3433
.padding(16.dp),
3534
verticalArrangement = Arrangement.spacedBy(2.dp),
3635
) {

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/details/BookingDetailsScreen.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ fun BookingDetailsScreen(
130130
onSelect = { status ->
131131
viewState.bookingUiState.onAttendanceStatusSelected(status)
132132
},
133-
onDismiss = { showAttendanceSheet.value = false }
133+
onDismiss = { showAttendanceSheet.value = false },
134+
selected = viewState.bookingUiState.bookingSummary.attendanceStatus
134135
)
135136
}
136137
}
@@ -153,7 +154,9 @@ private fun BookingDetailsContent(
153154
) {
154155
BookingSummary(
155156
model = booking.bookingSummary,
156-
modifier = Modifier.fillMaxWidth()
157+
modifier = Modifier
158+
.fillMaxWidth()
159+
.background(color = MaterialTheme.colorScheme.surfaceContainer)
157160
)
158161
BookingAppointmentDetails(
159162
model = booking.bookingsAppointmentDetails,

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListScreen.kt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,23 @@ import androidx.navigation.compose.NavHost
2828
import androidx.navigation.compose.composable
2929
import androidx.navigation.compose.rememberNavController
3030
import com.woocommerce.android.R
31+
import com.woocommerce.android.ui.bookings.filter.customer.BookingCustomerFilterPage
3132
import com.woocommerce.android.ui.bookings.filter.type.BookingTypeFilterRoute
33+
import com.woocommerce.android.ui.compose.Render
3234
import com.woocommerce.android.ui.compose.component.Toolbar
3335
import com.woocommerce.android.ui.compose.component.WCColoredButton
36+
import com.woocommerce.android.ui.compose.component.getText
3437
import com.woocommerce.android.ui.compose.preview.LightDarkThemePreviews
3538
import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground
39+
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingsFilterOption
3640

3741
@Composable
3842
fun BookingFilterListScreen(state: BookingFilterListUiState) {
3943
Scaffold(
4044
topBar = {
4145
Column {
4246
Toolbar(
43-
title = stringResource(state.currentPage.titleRes),
47+
title = state.title.getText(),
4448
onNavigationButtonClick = state.onClose,
4549
navigationIcon = ImageVector.vectorResource(id = state.navigationIcon)
4650
)
@@ -73,6 +77,8 @@ fun BookingFilterListScreen(state: BookingFilterListUiState) {
7377
.padding(innerPadding)
7478
)
7579

80+
state.dialogState?.Render()
81+
7682
// The navigation is driven by the state, so we handle back navigation by calling onClose
7783
// We need to ensure that this called after NavHost to make sure we receive back events
7884
BackHandler {
@@ -128,7 +134,18 @@ private fun FiltersNavHost(
128134
}
129135
}
130136
composable(BookingFilterPage.Customer.route) {
131-
TODO()
137+
BookingCustomerFilterPage { customer ->
138+
customer.customerId?.let { id ->
139+
state.onUpdateFilterOption(
140+
BookingsFilterOption.Customer(
141+
customerId = id,
142+
customerName = "${customer.firstName} ${customer.lastName}".trim()
143+
.ifBlank { customer.email }.orEmpty()
144+
)
145+
)
146+
}
147+
state.onClose()
148+
}
132149
}
133150
composable(BookingFilterPage.ServiceEvent.route) {
134151
TODO()

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListUiState.kt

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.annotation.StringRes
55
import com.woocommerce.android.R
66
import com.woocommerce.android.model.UiString
77
import com.woocommerce.android.ui.bookings.filter.type.titleRes
8+
import com.woocommerce.android.ui.compose.DialogState
89
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingFilters
910
import org.wordpress.android.fluxc.network.rest.wpcom.wc.bookings.BookingsFilterOption
1011

@@ -24,10 +25,11 @@ data class BookingFilterListUiState(
2425
val initialBookingFilters: BookingFilters? = null,
2526
val newBookingFilters: Set<BookingsFilterOption> = emptySet(),
2627
val currentPage: BookingFilterPage = BookingFilterPage.List,
28+
val dialogState: DialogState? = null,
2729
val onClose: () -> Unit = {},
2830
val onShowBookings: () -> Unit = {},
2931
val openPage: (BookingFilterPage) -> Unit = {},
30-
val onUpdateFilterOption: (BookingsFilterOption) -> Unit = {}
32+
val onUpdateFilterOption: (BookingsFilterOption) -> Unit = {},
3133
) {
3234

3335
val items: List<BookingFilterListItem> = availableBookingFilters().map { page ->
@@ -43,6 +45,23 @@ data class BookingFilterListUiState(
4345
initialBookingFilters?.bookingType
4446
) ?: BookingsFilterOption.BookingType(BookingsFilterOption.BookingType.Type.ANY)
4547

48+
val updatedBookingFilters: BookingFilters
49+
get() {
50+
val initial = initialBookingFilters ?: BookingFilters()
51+
return BookingFilters(
52+
dateRange = newBookingFilters.getOrDefault(initial.dateRange),
53+
customer = newBookingFilters.getOrDefault(initial.customer),
54+
teamMember = newBookingFilters.getOrDefault(initial.teamMember),
55+
attendanceStatus = newBookingFilters.getOrDefault(initial.attendanceStatus),
56+
paymentStatus = newBookingFilters.getOrDefault(initial.paymentStatus),
57+
bookingType = newBookingFilters.getOrDefault(initial.bookingType),
58+
location = newBookingFilters.getOrDefault(initial.location),
59+
serviceEvent = newBookingFilters.getOrDefault(initial.serviceEvent),
60+
)
61+
}
62+
63+
val updatedBookingFiltersCount = updatedBookingFilters.enabledFiltersCount
64+
4665
@DrawableRes
4766
val navigationIcon: Int = when (currentPage) {
4867
BookingFilterPage.List -> R.drawable.ic_gridicons_cross_24dp
@@ -71,6 +90,18 @@ data class BookingFilterListUiState(
7190
BookingFilterPage.TeamMember,
7291
BookingFilterPage.List -> null
7392
}
93+
94+
val title: UiString
95+
get() = if (currentPage != BookingFilterPage.List) {
96+
UiString.UiStringRes(currentPage.titleRes)
97+
} else if (updatedBookingFiltersCount > 0) {
98+
UiString.UiStringRes(
99+
stringRes = R.string.bookings_filters_title_with_count,
100+
params = listOf(UiString.UiStringText(updatedBookingFiltersCount.toString()))
101+
)
102+
} else {
103+
UiString.UiStringRes(R.string.bookings_filters_default_title)
104+
}
74105
}
75106

76107
val BookingFilterPage.titleRes: Int
@@ -79,7 +110,7 @@ val BookingFilterPage.titleRes: Int
79110
BookingFilterPage.AttendanceStatus -> R.string.bookings_filter_title_attendance_status
80111
BookingFilterPage.PaymentStatus -> R.string.bookings_filter_title_payment_status
81112
BookingFilterPage.BookingType -> R.string.bookings_filter_title_type
82-
BookingFilterPage.Customer -> R.string.bookings_filter_customer_name
113+
BookingFilterPage.Customer -> R.string.bookings_filter_customer
83114
BookingFilterPage.Location -> R.string.bookings_filter_location
84115
BookingFilterPage.DateTime -> R.string.bookings_filter_title_date
85116
BookingFilterPage.ServiceEvent -> R.string.bookings_filter_title_service_event

WooCommerce/src/main/kotlin/com/woocommerce/android/ui/bookings/filter/BookingFilterListViewModel.kt

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package com.woocommerce.android.ui.bookings.filter
22

33
import androidx.lifecycle.SavedStateHandle
44
import androidx.lifecycle.asLiveData
5+
import com.woocommerce.android.R
6+
import com.woocommerce.android.model.UiString
57
import com.woocommerce.android.ui.bookings.filter.data.BookingFilterRepository
8+
import com.woocommerce.android.ui.compose.DialogState
69
import com.woocommerce.android.viewmodel.MultiLiveEvent
710
import com.woocommerce.android.viewmodel.ScopedViewModel
811
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -25,7 +28,7 @@ class BookingFilterListViewModel @Inject constructor(
2528
onClose = ::onClose,
2629
onShowBookings = ::onShowBookings,
2730
openPage = ::onOpenPage,
28-
onUpdateFilterOption = ::onUpdateFilterOption,
31+
onUpdateFilterOption = ::onUpdateFilterOption
2932
)
3033
)
3134
val uiState = _uiState.asLiveData()
@@ -66,8 +69,25 @@ class BookingFilterListViewModel @Inject constructor(
6669
current.copy(currentPage = BookingFilterPage.List)
6770
}
6871
} else {
69-
// TODO Verify unsaved changes and close
70-
triggerEvent(MultiLiveEvent.Event.Exit)
72+
if (hasUnsavedChanges()) {
73+
_uiState.update { current ->
74+
current.copy(
75+
dialogState = DialogState(
76+
message = R.string.discard_message,
77+
positiveButton = DialogState.DialogButton(
78+
text = UiString.UiStringRes(R.string.discard),
79+
onClick = ::onDiscardChanges
80+
),
81+
negativeButton = DialogState.DialogButton(
82+
text = UiString.UiStringRes(R.string.keep_changes),
83+
onClick = ::onDismissUnsavedChangesDialog
84+
),
85+
)
86+
)
87+
}
88+
} else {
89+
triggerEvent(MultiLiveEvent.Event.Exit)
90+
}
7191
}
7292
}
7393

@@ -77,23 +97,20 @@ class BookingFilterListViewModel @Inject constructor(
7797
}
7898
triggerEvent(MultiLiveEvent.Event.Exit)
7999
}
80-
}
81100

82-
private val BookingFilterListUiState.updatedBookingFilters: BookingFilters
83-
get() {
84-
val initial = initialBookingFilters ?: BookingFilters()
85-
val updates = this@updatedBookingFilters.newBookingFilters
101+
private fun onDismissUnsavedChangesDialog() {
102+
_uiState.update { current -> current.copy(dialogState = null) }
103+
}
86104

87-
return BookingFilters(
88-
dateRange = updates.getOrDefault(initial.dateRange),
89-
customer = updates.getOrDefault(initial.customer),
90-
teamMember = updates.getOrDefault(
91-
initial.teamMember
92-
),
93-
attendanceStatus = updates.getOrDefault(initial.attendanceStatus),
94-
paymentStatus = updates.getOrDefault(initial.paymentStatus),
95-
bookingType = updates.getOrDefault(initial.bookingType),
96-
location = updates.getOrDefault(initial.location),
97-
serviceEvent = updates.getOrDefault(initial.serviceEvent),
98-
)
105+
private fun onDiscardChanges() {
106+
// Hide dialog and exit without saving
107+
_uiState.update { current -> current.copy(dialogState = null) }
108+
triggerEvent(MultiLiveEvent.Event.Exit)
109+
}
110+
111+
private fun hasUnsavedChanges(): Boolean {
112+
val initial = _uiState.value.initialBookingFilters ?: BookingFilters()
113+
val updated = _uiState.value.updatedBookingFilters
114+
return updated != initial
99115
}
116+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.woocommerce.android.ui.bookings.filter.customer
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
5+
import com.woocommerce.android.model.Order
6+
import com.woocommerce.android.ui.orders.creation.customerlist.CustomerListSelectionScreen
7+
8+
@Composable
9+
fun BookingCustomerFilterPage(
10+
onCustomerSelected: (Order.Customer) -> Unit,
11+
) {
12+
val viewModel = hiltViewModel { factory: BookingCustomerFilterViewModel.Factory ->
13+
factory.create(onCustomerSelected)
14+
}
15+
16+
CustomerListSelectionScreen(
17+
viewModel = viewModel,
18+
handleInsets = false,
19+
showToolbar = false
20+
)
21+
}

0 commit comments

Comments
 (0)