Skip to content
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

Issue/12539 blaze push notification navigation #12564

Merged
merged 19 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8976180
Simplifies code handling taps on push notifications
JorgeMucientes Sep 9, 2024
557e86a
Open campaign list from Blaze push notification and pass the campaign_id
JorgeMucientes Sep 9, 2024
2fe3177
Add missing mapping for Blaze push notification type
JorgeMucientes Sep 9, 2024
8e1e434
Open campaign detail directly when tapping on Blaze notification
JorgeMucientes Sep 9, 2024
32c26a6
Updates FluxC changeset
JorgeMucientes Sep 9, 2024
24fdc33
Set more consistent naming
JorgeMucientes Sep 9, 2024
0fe2a3e
Add new tests for Blaze notifications taps
JorgeMucientes Sep 9, 2024
58c24cc
Fix detekt indentation issue
JorgeMucientes Sep 9, 2024
542fb2d
Ensure campaign detail is loaded when a campaign id is passed
JorgeMucientes Sep 9, 2024
72cec8e
Handle Blaze grouped notifications
JorgeMucientes Sep 10, 2024
9f8fc2a
Keep the same Blaze notification types received in push payload
JorgeMucientes Sep 10, 2024
3684707
Fix detekt indentation issues
JorgeMucientes Sep 10, 2024
05172e9
Fix existing unit tests after group notification implementation updates
JorgeMucientes Sep 10, 2024
3b8502d
Add unit tests fro group notification handling
JorgeMucientes Sep 10, 2024
905cf74
Update FluxC changeset
JorgeMucientes Sep 11, 2024
37069bf
Merge branch 'refs/heads/trunk' into issue/12539-blaze-push-notificat…
JorgeMucientes Sep 11, 2024
9f4dd30
Avoid waiting for campaigns to be loaded before loading campaign detail
JorgeMucientes Sep 11, 2024
12b9b3a
Avoid tracking campaign detail tapped event
JorgeMucientes Sep 11, 2024
318fe1f
Fix unit tests compilation issue
JorgeMucientes Sep 11, 2024
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
Expand Up @@ -31,6 +31,12 @@ data class Notification(
@IgnoredOnParcel
val isReviewNotification = noteType == WooNotificationType.PRODUCT_REVIEW

@IgnoredOnParcel
val isBlazeNotification = noteType == WooNotificationType.BLAZE_APPROVED_NOTE ||
noteType == WooNotificationType.BLAZE_REJECTED_NOTE ||
noteType == WooNotificationType.BLAZE_CANCELLED_NOTE ||
noteType == WooNotificationType.BLAZE_PERFORMED_NOTE
Comment on lines +35 to +38
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion for a future improvement: can't we improve the API a bit if we convert WooNotificationType to be a sealed interface instead of an enum? I think if we do so we'll have a common parent for Blaze notification types.


/**
* Notifications are grouped based on the notification type and the store the notification belongs to.
*
Expand Down Expand Up @@ -74,6 +80,11 @@ fun NotificationModel.getUniqueId(): Long {
return when (this.type) {
NotificationModel.Kind.STORE_ORDER -> this.meta?.ids?.order ?: 0L
NotificationModel.Kind.COMMENT -> this.meta?.ids?.comment ?: 0L
NotificationModel.Kind.BLAZE_APPROVED_NOTE,
NotificationModel.Kind.BLAZE_REJECTED_NOTE,
NotificationModel.Kind.BLAZE_CANCELLED_NOTE,
NotificationModel.Kind.BLAZE_PERFORMED_NOTE -> this.meta?.ids?.campaignId ?: 0L

else -> 0L
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ enum class WooNotificationType {
NEW_ORDER,
PRODUCT_REVIEW,
LOCAL_REMINDER,
BLAZE
BLAZE_APPROVED_NOTE,
BLAZE_REJECTED_NOTE,
BLAZE_CANCELLED_NOTE,
BLAZE_PERFORMED_NOTE,
}

fun NotificationModel.getWooType(): WooNotificationType {
return when (this.type) {
NotificationModel.Kind.STORE_ORDER -> WooNotificationType.NEW_ORDER
NotificationModel.Kind.COMMENT -> WooNotificationType.PRODUCT_REVIEW
NotificationModel.Kind.BLAZE_APPROVED_NOTE -> WooNotificationType.BLAZE_APPROVED_NOTE
NotificationModel.Kind.BLAZE_REJECTED_NOTE -> WooNotificationType.BLAZE_REJECTED_NOTE
NotificationModel.Kind.BLAZE_CANCELLED_NOTE -> WooNotificationType.BLAZE_CANCELLED_NOTE
NotificationModel.Kind.BLAZE_PERFORMED_NOTE -> WooNotificationType.BLAZE_PERFORMED_NOTE
else -> WooNotificationType.LOCAL_REMINDER
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ class BlazeCampaignListViewModel @Inject constructor(
if (navArgs.isPostCampaignCreation) {
showCampaignCelebrationIfNeeded()
}
if (navArgs.campaignId != null) {
triggerEvent(
ShowCampaignDetails(
url = blazeUrlsHelper.buildCampaignDetailsUrl(navArgs.campaignId!!),
urlToTriggerExit = blazeUrlsHelper.buildCampaignsListUrl()
)
)
}
launch {
loadCampaigns(offset = 0)
}
Expand Down Expand Up @@ -109,14 +117,13 @@ class BlazeCampaignListViewModel @Inject constructor(
}

private fun onCampaignClicked(campaignId: String) {
val url = blazeUrlsHelper.buildCampaignDetailsUrl(campaignId)
analyticsTrackerWrapper.track(
stat = BLAZE_CAMPAIGN_DETAIL_SELECTED,
properties = mapOf(AnalyticsTracker.KEY_BLAZE_SOURCE to BlazeFlowSource.CAMPAIGN_LIST.trackingName)
)
triggerEvent(
ShowCampaignDetails(
url = url,
url = blazeUrlsHelper.buildCampaignDetailsUrl(campaignId),
urlToTriggerExit = blazeUrlsHelper.buildCampaignsListUrl()
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ import com.woocommerce.android.ui.main.MainActivityViewModel.RestartActivityForP
import com.woocommerce.android.ui.main.MainActivityViewModel.ShortcutOpenOrderCreation
import com.woocommerce.android.ui.main.MainActivityViewModel.ShortcutOpenPayments
import com.woocommerce.android.ui.main.MainActivityViewModel.ShowFeatureAnnouncement
import com.woocommerce.android.ui.main.MainActivityViewModel.ViewBlazeCampaignDetail
import com.woocommerce.android.ui.main.MainActivityViewModel.ViewBlazeCampaignList
import com.woocommerce.android.ui.main.MainActivityViewModel.ViewMyStoreStats
import com.woocommerce.android.ui.main.MainActivityViewModel.ViewOrderDetail
import com.woocommerce.android.ui.main.MainActivityViewModel.ViewOrderList
Expand Down Expand Up @@ -786,7 +788,7 @@ class MainActivity :
intent.removeExtra(FIELD_REMOTE_NOTIFICATION)
intent.removeExtra(FIELD_PUSH_ID)

viewModel.handleIncomingNotification(localPushId, notification)
viewModel.onPushNotificationTapped(localPushId, notification)
} else if (localNotification != null) {
intent.removeExtra(FIELD_LOCAL_NOTIFICATION)
viewModel.onLocalNotificationTapped(localNotification)
Expand All @@ -803,6 +805,8 @@ class MainActivity :
is ViewOrderDetail -> showOrderDetail(event)
is ViewReviewDetail -> showReviewDetail(event.uniqueId, launchedFromNotification = true)
is ViewReviewList -> showReviewList()
is ViewBlazeCampaignDetail -> showBlazeCampaignList(event.campaignId, event.isOpenedFromPush)
ViewBlazeCampaignList -> showBlazeCampaignList(campaignId = null)
is RestartActivityEvent -> onRestartActivityEvent(event)
is ShowFeatureAnnouncement -> navigateToFeatureAnnouncement(event)
is ViewUrlInWebView -> navigateToWebView(event)
Expand Down Expand Up @@ -843,6 +847,18 @@ class MainActivity :
observeBottomBarState()
}

private fun showBlazeCampaignList(campaignId: String?, isOpenedFromPush: Boolean = false) {
binding.bottomNav.currentPosition = MORE
binding.bottomNav.active(MORE.position)

navController.navigateSafely(
MoreMenuFragmentDirections.actionMoreMenuToBlazeCampaignListFragment(
campaignId = campaignId
),
skipThrottling = isOpenedFromPush
)
}

private fun observeNotificationsPermissionBarVisibility() {
viewModel.isNotificationsPermissionCardVisible.observe(this) { isVisible ->
if (isVisible) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ import com.woocommerce.android.model.FeatureAnnouncement
import com.woocommerce.android.model.Notification
import com.woocommerce.android.notifications.NotificationChannelType
import com.woocommerce.android.notifications.UnseenReviewsCountHandler
import com.woocommerce.android.notifications.WooNotificationType.BLAZE_APPROVED_NOTE
import com.woocommerce.android.notifications.WooNotificationType.BLAZE_CANCELLED_NOTE
import com.woocommerce.android.notifications.WooNotificationType.BLAZE_PERFORMED_NOTE
import com.woocommerce.android.notifications.WooNotificationType.BLAZE_REJECTED_NOTE
import com.woocommerce.android.notifications.WooNotificationType.LOCAL_REMINDER
import com.woocommerce.android.notifications.WooNotificationType.NEW_ORDER
import com.woocommerce.android.notifications.WooNotificationType.PRODUCT_REVIEW
import com.woocommerce.android.notifications.local.LocalNotificationType
import com.woocommerce.android.notifications.local.LocalNotificationType.BLAZE_ABANDONED_CAMPAIGN_REMINDER
import com.woocommerce.android.notifications.local.LocalNotificationType.BLAZE_NO_CAMPAIGN_REMINDER
Expand Down Expand Up @@ -110,7 +117,7 @@ class MainActivityViewModel @Inject constructor(
)
}

fun handleIncomingNotification(localPushId: Int, notification: Notification?) {
fun onPushNotificationTapped(localPushId: Int, notification: Notification?) {
notification?.let {
// update current selectSite based on the current notification
val currentSite = selectedSite.get()
Expand All @@ -119,8 +126,8 @@ class MainActivityViewModel @Inject constructor(
changeSiteAndRestart(it.remoteSiteId, RestartActivityForPushNotification(localPushId, notification))
} else {
when (localPushId) {
it.getGroupPushId() -> onGroupMessageOpened(it.channelType, it.remoteSiteId)
else -> onSingleNotificationOpened(localPushId, it)
it.getGroupPushId() -> onGroupMessageOpened(it)
else -> onSinglePushNotificationOpened(localPushId, it)
}
}
} ?: run {
Expand Down Expand Up @@ -171,29 +178,53 @@ class MainActivityViewModel @Inject constructor(
}
}

private fun onGroupMessageOpened(notificationChannelType: NotificationChannelType, remoteSiteId: Long) {
notificationHandler.markNotificationsOfTypeTapped(notificationChannelType)
notificationHandler.removeNotificationsOfTypeFromSystemsBar(notificationChannelType, remoteSiteId)
when (notificationChannelType) {
private fun onGroupMessageOpened(notification: Notification) {
notificationHandler.markNotificationsOfTypeTapped(notification.channelType)
notificationHandler.removeNotificationsOfTypeFromSystemsBar(notification.channelType, notification.remoteSiteId)
when (notification.channelType) {
NotificationChannelType.NEW_ORDER -> triggerEvent(ViewOrderList)
NotificationChannelType.REVIEW -> triggerEvent(ViewReviewList)
else -> triggerEvent(ViewMyStoreStats)
NotificationChannelType.OTHER -> if (notification.isBlazeNotification) {
triggerEvent(ViewBlazeCampaignList)
} else {
triggerEvent(ViewMyStoreStats)
}
}
}

private fun onSingleNotificationOpened(localPushId: Int, notification: Notification) {
private fun onSinglePushNotificationOpened(localPushId: Int, notification: Notification) {
notificationHandler.markNotificationTapped(notification.remoteNoteId)
notificationHandler.removeNotificationByNotificationIdFromSystemsBar(localPushId)
if (notification.channelType == NotificationChannelType.REVIEW) {
analyticsTrackerWrapper.track(REVIEW_OPEN)
triggerEvent(ViewReviewDetail(notification.uniqueId))
} else if (notification.channelType == NotificationChannelType.NEW_ORDER) {
if (siteStore.getSiteBySiteId(notification.remoteSiteId) != null) {
triggerEvent(ViewOrderDetail(notification.uniqueId, notification.remoteNoteId))
} else {
// the site does not exist locally, open order list
triggerEvent(ViewOrderList)
when (notification.noteType) {
NEW_ORDER -> {
when {
siteStore.getSiteBySiteId(notification.remoteSiteId) != null -> triggerEvent(
ViewOrderDetail(
notification.uniqueId,
notification.remoteNoteId
)
)

else -> triggerEvent(ViewOrderList)
}
}

PRODUCT_REVIEW -> {
analyticsTrackerWrapper.track(REVIEW_OPEN)
triggerEvent(ViewReviewDetail(notification.uniqueId))
}

BLAZE_APPROVED_NOTE,
BLAZE_REJECTED_NOTE,
BLAZE_CANCELLED_NOTE,
BLAZE_PERFORMED_NOTE -> triggerEvent(
ViewBlazeCampaignDetail(
campaignId = notification.uniqueId.toString(),
isOpenedFromPush = true
)
)

LOCAL_REMINDER -> error("Local reminder notification should not be handled here")
}
}

Expand Down Expand Up @@ -314,6 +345,7 @@ class MainActivityViewModel @Inject constructor(
data class ViewUrlInWebView(
val url: String,
) : Event()

object ShortcutOpenPayments : Event()
object ShortcutOpenOrderCreation : Event()
object LaunchBlazeCampaignCreation : Event()
Expand All @@ -330,6 +362,8 @@ class MainActivityViewModel @Inject constructor(
data class ShowFeatureAnnouncement(val announcement: FeatureAnnouncement) : Event()
data class ViewReviewDetail(val uniqueId: Long) : Event()
data class ViewOrderDetail(val uniqueId: Long, val remoteNoteId: Long) : Event()
data class ViewBlazeCampaignDetail(val campaignId: String, val isOpenedFromPush: Boolean) : Event()
object ViewBlazeCampaignList : Event()
data class ShowPrivacyPreferenceUpdatedFailed(val analyticsEnabled: Boolean) : Event()
object ShowPrivacySettings : Event()
data class ShowPrivacySettingsWithError(val requestedAnalyticsValue: RequestedAnalyticsValue) : Event()
Expand Down
5 changes: 5 additions & 0 deletions WooCommerce/src/main/res/navigation/nav_graph_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,11 @@
android:name="isPostCampaignCreation"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="campaignId"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
</fragment>
<fragment
android:id="@+id/orderConnectivityToolFragment"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.woocommerce.android.extensions.NumberExtensionsWrapper
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.blaze.BlazeRepository
import com.woocommerce.android.ui.blaze.BlazeUrlsHelper
import com.woocommerce.android.ui.blaze.campaigs.BlazeCampaignListViewModel.ShowCampaignDetails
import com.woocommerce.android.util.CurrencyFormatter
import com.woocommerce.android.util.captureValues
import com.woocommerce.android.util.runAndCaptureValues
Expand All @@ -15,6 +16,7 @@ import com.woocommerce.android.viewmodel.MultiLiveEvent.Event
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flow
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.any
Expand Down Expand Up @@ -54,6 +56,8 @@ class BlazeCampaignListViewModelTest : BaseUnitTest() {
whenever(blazeCampaignsStore.observeBlazeCampaigns(selectedSite.get())).thenReturn(campaignsEntityFlow)
whenever(blazeCampaignsStore.fetchBlazeCampaigns(any(), any(), any(), any(), eq(null)))
.thenReturn(BlazeCampaignsResult(EMPTY_BLAZE_CAMPAIGN_MODEL))
whenever(blazeUrlsHelper.buildCampaignDetailsUrl(CAMPAIGN_ID)).thenReturn(CAMPAIGN_URL)
whenever(blazeUrlsHelper.buildCampaignsListUrl()).thenReturn(CAMPAIGN_LIST_URL)
}

@Test
Expand Down Expand Up @@ -146,9 +150,24 @@ class BlazeCampaignListViewModelTest : BaseUnitTest() {
Assertions.assertThat(state.isCampaignCelebrationShown).isFalse()
}

private fun createViewModel(isPostCampaignCreation: Boolean = false) {
@Test
fun `when screen opened from Blaze campaign status push notification, navigate to campaign detail `() =
testBlocking {
createViewModel(campaignId = CAMPAIGN_ID)
var event: ShowCampaignDetails? = null
viewModel.event.observeForever {
if (it is ShowCampaignDetails) event = it
}

assertThat(event).isEqualTo(ShowCampaignDetails(CAMPAIGN_URL, CAMPAIGN_LIST_URL))
}

private fun createViewModel(isPostCampaignCreation: Boolean = false, campaignId: String? = null) {
viewModel = BlazeCampaignListViewModel(
savedStateHandle = BlazeCampaignListFragmentArgs(isPostCampaignCreation).toSavedStateHandle(),
savedStateHandle = BlazeCampaignListFragmentArgs(
isPostCampaignCreation,
campaignId
).toSavedStateHandle(),
blazeCampaignsStore = blazeCampaignsStore,
selectedSite = selectedSite,
blazeUrlsHelper = blazeUrlsHelper,
Expand All @@ -171,6 +190,8 @@ class BlazeCampaignListViewModelTest : BaseUnitTest() {
const val TOTAL_BUDGET = 100.0
const val SPENT_BUDGET = 0.0
const val TARGET_URN = "urn:wpcom:post:199247490:9"
const val CAMPAIGN_URL = "https://wordpress.com/campaigns"
const val CAMPAIGN_LIST_URL = "https://wordpress.com/campaigns/list"

val BLAZE_CAMPAIGN_MODEL = BlazeCampaignModel(
campaignId = CAMPAIGN_ID,
Expand Down
Loading
Loading