Skip to content

Commit 7afedd2

Browse files
committed
Merge trunk into woomob-2073-woo-posfts-add-analytics-tracking-for-search
2 parents 0ac89e7 + ecb8a65 commit 7afedd2

File tree

122 files changed

+9977
-629
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

122 files changed

+9977
-629
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<!--
22
Contains editorialized release notes. Raw release notes should go into `RELEASE-NOTES.txt`.
33
-->
4+
## 24.3
5+
We’ve improved stability in Site Picker by fixing a crash that could happen when re-adding a previously hidden site. This update makes switching back to hidden stores smoother and more reliable.
6+
47
## 24.2
58
Signing in just got easier and more secure! This update adds passkey support for WordPress.com accounts during Jetpack setup, so you can skip the password and log in with a tap. We also introduced booking management for eligible stores, helping you handle appointments right from the app.
69

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
-----
77
- [Internal] Add full-text search for POS product search with analytics tracking [https://github.com/woocommerce/woocommerce-android/pull/15484]
88

9+
910
24.3
1011
-----
1112
- [*] Fixed a crash when re-adding a previously hidden site in Site Picker [https://github.com/woocommerce/woocommerce-android/pull/15439]

WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -505,8 +505,24 @@ enum class AnalyticsEvent(override val siteless: Boolean = false) : IAnalyticsEv
505505

506506
MAIN_TAB_HUB_MENU_SELECTED,
507507
MAIN_TAB_HUB_MENU_RESELECTED,
508-
MAIN_TAB_BOOKINGS_SELECTED,
509-
MAIN_TAB_BOOKINGS_RESELECTED,
508+
MAIN_TAB_BOOKINGS_SELECT,
509+
MAIN_TAB_BOOKINGS_RESELECT,
510+
511+
// -- Bookings
512+
BOOKING_LIST_TAB_SELECT,
513+
BOOKING_LIST_VIEW,
514+
BOOKING_LIST_FAILED_TO_FETCH_BOOKINGS,
515+
BOOKING_LIST_FAILED_TO_UPDATE_BOOKING_DETAILS,
516+
BOOKING_LIST_BOOKING_TAP,
517+
BOOKING_LIST_FILTERS_TAP,
518+
BOOKING_LIST_APPLY_FILTERS,
519+
BOOKING_LIST_SEARCH_TAP,
520+
BOOKING_DETAIL_CANCEL_BOOKING,
521+
BOOKING_DETAIL_ATTENDANCE_STATUS_UPDATE,
522+
BOOKING_DETAIL_ADD_NOTE_TAP,
523+
BOOKING_DETAIL_VIEW_LINKED_ORDER_TAP,
524+
BOOKING_LIST_SORT_BY_TAP,
525+
BOOKING_LIST_SORT_BY_OPTION_TAP,
510526

511527
// -- Settings
512528
SETTING_CHANGE,

WooCommerce/src/main/kotlin/com/woocommerce/android/model/FeatureAnnouncement.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ data class FeatureAnnouncement(
99
val minimumAppVersion: String,
1010
val maximumAppVersion: String,
1111
val appVersionTargets: List<String>,
12+
val detailsUrl: String,
1213
val isLocalized: Boolean = false,
1314
val features: List<FeatureAnnouncementItem>
1415
) : Parcelable {

WooCommerce/src/main/kotlin/com/woocommerce/android/notifications/push/ShouldShowEnablePushNotificationsUi.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import javax.inject.Inject
1515
/**
1616
* Checks whether the "Enable Push Notifications" UI should be shown.
1717
*
18-
* This is part of the Woo Core push notifications system for app-password authenticated sites.
18+
* This is part of the Woo Core push notifications system for non-Jetpack sites
19+
* (app-password authenticated sites and Jetpack Connection Package sites).
1920
*/
2021
@OptIn(ExperimentalCoroutinesApi::class)
2122
class ShouldShowEnablePushNotificationsUi @Inject constructor(
@@ -26,7 +27,7 @@ class ShouldShowEnablePushNotificationsUi @Inject constructor(
2627
if (!FeatureFlag.WOO_PUSH_NOTIFICATIONS_SYSTEM_M2.isEnabled()) return flowOf(false)
2728
return selectedSite.observe()
2829
.flatMapLatest { site ->
29-
if (site == null || site.connectionType != SiteConnectionType.ApplicationPasswords) {
30+
if (site == null || site.connectionType == SiteConnectionType.Jetpack) {
3031
flowOf(false)
3132
} else {
3233
pushNotificationRegistrationStatus.observe(site.siteId).map { registrationStatus ->
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.woocommerce.android.ui.bookings
2+
3+
import com.woocommerce.android.WooException
4+
import com.woocommerce.android.analytics.AnalyticsEvent
5+
import com.woocommerce.android.analytics.AnalyticsTracker
6+
import com.woocommerce.android.analytics.AnalyticsTrackerWrapper
7+
8+
class BookingAnalyticsHelper {
9+
fun AnalyticsTrackerWrapper.trackError(
10+
event: AnalyticsEvent,
11+
throwable: Throwable,
12+
errorContext: String,
13+
additionalProperties: Map<String, Any> = emptyMap()
14+
) {
15+
track(
16+
stat = event,
17+
properties = buildMap {
18+
throwable.errorCode()?.let { code -> put(AnalyticsTracker.KEY_ERROR_CODE, code) }
19+
putAll(additionalProperties)
20+
},
21+
errorContext = errorContext,
22+
errorType = throwable.errorType(),
23+
errorDescription = throwable.message
24+
)
25+
}
26+
27+
private fun Throwable.errorCode(): String? = (this as? WooException)?.error?.apiErrorCode
28+
29+
private fun Throwable.errorType(): String? = when (this) {
30+
is WooException -> error.type.name
31+
else -> javaClass.simpleName
32+
}
33+
34+
companion object {
35+
const val KEY_BOOKING_STATUS = "booking_status"
36+
const val KEY_ACTION = "action"
37+
const val KEY_SELECTED_TAB = "selected_tab"
38+
const val KEY_IS_SEARCH_ACTIVE = "is_search_active"
39+
const val KEY_IS_FILTERING_ACTIVE = "is_filtering_active"
40+
const val KEY_SORT_OPTION = "sort_option"
41+
const val KEY_IS_DEFAULT_TAB = "is_default_tab"
42+
const val KEY_IS_LIST_EMPTY = "is_list_empty"
43+
const val KEY_IS_FILTERED = "is_filtered"
44+
const val KEY_SELECTED_FILTERS = "selected_filters"
45+
}
46+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.woocommerce.android.model.UiString
88
import com.woocommerce.android.ui.bookings.compose.BookingAppointmentDetailsModel
99
import com.woocommerce.android.ui.bookings.compose.BookingAttendanceStatus
1010
import com.woocommerce.android.ui.bookings.compose.BookingCustomerDetailsUiModel
11+
import com.woocommerce.android.ui.bookings.compose.BookingLocationStatus
1112
import com.woocommerce.android.ui.bookings.compose.BookingPaymentDetailsModel
1213
import com.woocommerce.android.ui.bookings.compose.BookingStaffMemberStatus
1314
import com.woocommerce.android.ui.bookings.compose.BookingSummaryModel
@@ -69,6 +70,7 @@ class BookingMapper @Inject constructor(
6970
staffMemberStatus: BookingStaffMemberStatus?,
7071
cancelStatus: CancelStatus,
7172
attendanceUpdateStatus: AttendanceUpdateStatus = AttendanceUpdateStatus.Idle,
73+
locationStatus: BookingLocationStatus = BookingLocationStatus.Loading,
7274
): BookingAppointmentDetailsModel {
7375
val duration = Duration.between(start, end)
7476
.normalizeDuration()
@@ -77,8 +79,7 @@ class BookingMapper @Inject constructor(
7779
date = detailsDateFormatter.format(start),
7880
time = "${dateFormatter.formatTime(start)} - ${dateFormatter.formatTime(end)}",
7981
staff = staffMemberStatus,
80-
// TODO replace mocked values when available from API
81-
location = "238 Willow Creek Drive, Montgomery AL 36109",
82+
location = locationStatus,
8283
cancelStatus = cancelStatus,
8384
cancelButtonVisible = isCancellable,
8485
duration = duration,

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ class BookingsRepository @Inject constructor(
6161
order = order
6262
)
6363

64+
suspend fun getBookingsList(
65+
limit: Int? = null,
66+
filters: BookingFilters? = null,
67+
order: BookingsOrderOption
68+
): List<Booking> =
69+
bookingsStore.getBookings(
70+
site = selectedSite.get(),
71+
limit = limit,
72+
filters = filters,
73+
order = order
74+
)
75+
6476
fun observeBookingsCount(): Flow<Long> = bookingsStore.observeBookingCount(site = selectedSite.get())
6577

6678
fun observeBooking(bookingId: Long): Flow<Booking?> =
@@ -135,18 +147,18 @@ class BookingsRepository @Inject constructor(
135147
suspend fun updateAttendanceStatus(
136148
bookingId: Long,
137149
attendanceStatus: BookingEntity.AttendanceStatus,
138-
): Result<Unit> = appCoroutineScope.async {
150+
): Result<Unit> {
139151
val result = bookingsStore.updateBooking(
140152
site = selectedSite.get(),
141153
bookingId = bookingId,
142154
bookingUpdatePayload = BookingUpdatePayload(attendanceStatus = attendanceStatus)
143155
)
144-
if (result.isError) {
156+
return if (result.isError) {
145157
Result.failure(WooException(result.error))
146158
} else {
147159
Result.success(Unit)
148160
}
149-
}.await()
161+
}
150162

151163
suspend fun updateNote(
152164
bookingId: Long,
@@ -180,10 +192,11 @@ class BookingsRepository @Inject constructor(
180192
}
181193
}.await()
182194

183-
suspend fun fetchProductBookingLocation(productId: Long): Result<String?> {
195+
suspend fun fetchProductBookingLocation(productId: Long, bookingId: Long? = null): Result<String?> {
184196
val result = bookingsStore.fetchProductBookingLocation(
185197
site = selectedSite.get(),
186-
productId = productId
198+
productId = productId,
199+
bookingId = bookingId
187200
)
188201
return if (result.isError) {
189202
Result.failure(WooException(result.error))

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

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

33
import com.woocommerce.android.tools.SelectedSite
4+
import org.wordpress.android.fluxc.persistence.entity.OrderEntity
45
import org.wordpress.android.fluxc.store.WCOrderStore
56
import java.math.BigDecimal
67
import javax.inject.Inject
@@ -13,6 +14,22 @@ class PaymentStatusResolver @Inject constructor(
1314
val order = orderStore.getOrderByIdAndSite(orderId, selectedSite.get())
1415
?: return PaymentStatus.UNPAID
1516

17+
return statusForOrder(order)
18+
}
19+
20+
suspend fun resolveAll(orderIds: List<Long>): Map<Long, PaymentStatus> {
21+
val uniqueOrderIds = orderIds.distinct()
22+
if (uniqueOrderIds.isEmpty()) return emptyMap()
23+
24+
val ordersById = orderStore.getOrdersByIdsAndSite(uniqueOrderIds, selectedSite.get())
25+
.associateBy { it.orderId }
26+
27+
return uniqueOrderIds.associateWith { orderId ->
28+
ordersById[orderId]?.let(::statusForOrder) ?: PaymentStatus.UNPAID
29+
}
30+
}
31+
32+
private fun statusForOrder(order: OrderEntity): PaymentStatus {
1633
val total = order.total.toBigDecimalOrNull() ?: BigDecimal.ZERO
1734
return computeStatus(order.refundTotal.abs(), total, order.datePaid, order.status)
1835
}

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

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,29 @@ fun BookingAppointmentDetails(
7373
}
7474
}
7575
AppointmentDetailsRow(
76-
label = R.string.booking_appointment_label_location,
77-
value = model.location
78-
)
76+
label = R.string.booking_appointment_label_location
77+
) {
78+
when (model.location) {
79+
is BookingLocationStatus.Loaded, is BookingLocationStatus.Unavailable -> {
80+
Text(
81+
text = (model.location as? BookingLocationStatus.Loaded)?.location ?: "-",
82+
style = MaterialTheme.typography.bodyMedium,
83+
color = MaterialTheme.colorScheme.onSurfaceVariant,
84+
maxLines = 1,
85+
overflow = TextOverflow.Ellipsis
86+
)
87+
}
88+
89+
BookingLocationStatus.Loading -> {
90+
SkeletonView(
91+
width = 80.dp,
92+
height = with(LocalDensity.current) {
93+
MaterialTheme.typography.bodyMedium.fontSize.toDp()
94+
},
95+
)
96+
}
97+
}
98+
}
7999
AppointmentDetailsRow(
80100
label = R.string.booking_appointment_label_duration,
81101
value = model.duration,
@@ -166,7 +186,7 @@ data class BookingAppointmentDetailsModel(
166186
val date: String,
167187
val time: String,
168188
val staff: BookingStaffMemberStatus?,
169-
val location: String,
189+
val location: BookingLocationStatus,
170190
val duration: String,
171191
val cancelButtonVisible: Boolean,
172192
val cancelStatus: CancelStatus,
@@ -181,6 +201,12 @@ data class BookingAppointmentDetailsModel(
181201
val attendanceInProgressShown: Boolean = attendanceUpdateStatus == AttendanceUpdateStatus.InProgress
182202
}
183203

204+
sealed interface BookingLocationStatus {
205+
data object Loading : BookingLocationStatus
206+
data class Loaded(val location: String) : BookingLocationStatus
207+
data object Unavailable : BookingLocationStatus
208+
}
209+
184210
sealed interface BookingStaffMemberStatus {
185211
data object Loading : BookingStaffMemberStatus
186212
data class Loaded(val name: String) : BookingStaffMemberStatus
@@ -196,7 +222,7 @@ private fun BookingAppointmentDetailsPreview() {
196222
date = "05/07/2025, 11:00 AM",
197223
time = "11:00 am - 12:00 pm",
198224
staff = BookingStaffMemberStatus.Loading,
199-
location = "238 Willow Creek Drive, Montgomery AL 36109",
225+
location = BookingLocationStatus.Loaded("238 Willow Creek Drive, Montgomery AL 36109"),
200226
duration = "60 min",
201227
cancelButtonVisible = true,
202228
cancelStatus = CancelStatus.Idle,
@@ -219,7 +245,7 @@ private fun BookingAppointmentDetailsCancelHiddenPreview() {
219245
date = "05/07/2025, 11:00 AM",
220246
time = "11:00 am - 12:00 pm",
221247
staff = BookingStaffMemberStatus.Loading,
222-
location = "238 Willow Creek Drive, Montgomery AL 36109",
248+
location = BookingLocationStatus.Loaded("238 Willow Creek Drive, Montgomery AL 36109"),
223249
duration = "60 min",
224250
cancelButtonVisible = false,
225251
cancelStatus = CancelStatus.Idle,

0 commit comments

Comments
 (0)