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
Expand Up @@ -500,6 +500,7 @@ class AnalyticsTracker private constructor(private val context: Context) {
// -- Free Trial
const val VALUE_BANNER = "banner"
const val VALUE_UPGRADES_SCREEN = "upgrades_screen"
const val VALUE_NOTIFICATION = "notification"

// -- Store Onboarding
const val ONBOARDING_TASK_KEY = "task"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ sealed class LocalNotification(
val delay: Long,
val delayUnit: TimeUnit
) {
open val data: String? = null
val id = type.hashCode()

abstract fun getTitleString(resourceProvider: ResourceProvider): String
abstract fun getDescriptionString(resourceProvider: ResourceProvider): String

open val data: String? = null
open fun getTitleString(resourceProvider: ResourceProvider): String {
return resourceProvider.getString(title)
}

data class StoreCreationFinishedNotification(
val name: String
Expand All @@ -28,10 +30,6 @@ sealed class LocalNotification(
delay = 5,
delayUnit = TimeUnit.MINUTES
) {
override fun getTitleString(resourceProvider: ResourceProvider): String {
return resourceProvider.getString(title)
}

override fun getDescriptionString(resourceProvider: ResourceProvider): String {
return resourceProvider.getString(description, name)
}
Expand All @@ -44,14 +42,24 @@ sealed class LocalNotification(
delay = 24,
delayUnit = TimeUnit.HOURS
) {
override fun getTitleString(resourceProvider: ResourceProvider): String {
return resourceProvider.getString(title)
}
override val data: String = storeName

override fun getDescriptionString(resourceProvider: ResourceProvider): String {
return resourceProvider.getString(description, name, storeName)
}
}

override val data: String = storeName
data class FreeTrialExpiringNotification(val expiryDate: String, val siteId: Long) : LocalNotification(
title = R.string.local_notification_one_day_before_free_trial_expires_title,
description = R.string.local_notification_one_day_before_free_trial_expires_description,
type = LocalNotificationType.FREE_TRIAL_EXPIRING,
delay = 13,
delayUnit = TimeUnit.DAYS
) {
override val data: String = siteId.toString()

override fun getDescriptionString(resourceProvider: ResourceProvider): String {
return resourceProvider.getString(description, expiryDate)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ class LocalNotificationScheduler @Inject constructor(
}

private fun buildPreconditionCheckWorkRequest(notification: LocalNotification): OneTimeWorkRequest {
val conditionData = workDataOf(LOCAL_NOTIFICATION_TYPE to notification.type.value)
val conditionData = workDataOf(
LOCAL_NOTIFICATION_TYPE to notification.type.value,
LOCAL_NOTIFICATION_DATA to notification.data
)
return OneTimeWorkRequestBuilder<PreconditionCheckWorker>()
.setInputData(conditionData)
.addTag(notification.type.value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import androidx.hilt.work.HiltWorker
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.woocommerce.android.extensions.isFreeTrial
import com.woocommerce.android.notifications.local.LocalNotificationScheduler.Companion.LOCAL_NOTIFICATION_DATA
import com.woocommerce.android.notifications.local.LocalNotificationScheduler.Companion.LOCAL_NOTIFICATION_TYPE
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.util.WooLog.T.NOTIFICATIONS
import com.woocommerce.android.util.WooLogWrapper
import com.woocommerce.android.util.WooPermissionUtils
Expand All @@ -18,16 +21,25 @@ import dagger.assisted.AssistedInject
class PreconditionCheckWorker @AssistedInject constructor(
@Assisted private val appContext: Context,
@Assisted workerParams: WorkerParameters,
private val wooLogWrapper: WooLogWrapper
private val wooLogWrapper: WooLogWrapper,
private val selectedSite: SelectedSite
) : Worker(appContext, workerParams) {
override fun doWork(): Result {
val type = inputData.getString(LOCAL_NOTIFICATION_TYPE)
val data = inputData.getString(LOCAL_NOTIFICATION_DATA)
return when {
!canDisplayNotifications -> cancelWork("Notifications permission not granted. Cancelling work.")
type == null -> cancelWork("Notification check data is invalid")
type == LocalNotificationType.STORE_CREATION_FINISHED.value -> Result.success()
type == LocalNotificationType.STORE_CREATION_INCOMPLETE.value -> Result.success()
type == LocalNotificationType.FREE_TRIAL_EXPIRING.value -> Result.success()
type == LocalNotificationType.FREE_TRIAL_EXPIRING.value -> {
val site = selectedSite.get()
if (site.isFreeTrial && site.siteId == data?.toLongOrNull()) {
Result.success()
} else {
cancelWork("Store plan upgraded or a different site. Cancelling work.")
}
}
type == LocalNotificationType.FREE_TRIAL_EXPIRED.value -> Result.success()
else -> {
cancelWork("Unknown notification $type. Cancelling work.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,6 @@ class PlanUpgradeStartFragment : BaseFragment() {
}

enum class PlanUpgradeStartSource {
BANNER, UPGRADES_SCREEN
BANNER, UPGRADES_SCREEN, NOTIFICATION
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import androidx.lifecycle.SavedStateHandle
import com.woocommerce.android.analytics.AnalyticsEvent
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.KEY_SOURCE
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_BANNER
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_NOTIFICATION
import com.woocommerce.android.analytics.AnalyticsTracker.Companion.VALUE_UPGRADES_SCREEN
import com.woocommerce.android.analytics.AnalyticsTrackerWrapper
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.common.wpcomwebview.WPComWebViewAuthenticator
import com.woocommerce.android.ui.common.wpcomwebview.WPComWebViewViewModel
import com.woocommerce.android.ui.login.storecreation.dispatcher.PlanUpgradeStartFragment.PlanUpgradeStartSource.BANNER
import com.woocommerce.android.ui.login.storecreation.dispatcher.PlanUpgradeStartFragment.PlanUpgradeStartSource.NOTIFICATION
import com.woocommerce.android.ui.login.storecreation.dispatcher.PlanUpgradeStartFragment.PlanUpgradeStartSource.UPGRADES_SCREEN
import com.woocommerce.android.ui.plans.domain.SitePlan
import com.woocommerce.android.ui.plans.repository.SitePlanRepository
Expand Down Expand Up @@ -37,6 +39,7 @@ class PlanUpgradeStartViewModel @Inject constructor(
KEY_SOURCE to when (navArgs.source) {
BANNER -> VALUE_BANNER
UPGRADES_SCREEN -> VALUE_UPGRADES_SCREEN
NOTIFICATION -> VALUE_NOTIFICATION
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import com.woocommerce.android.R.string
import com.woocommerce.android.analytics.AnalyticsEvent
import com.woocommerce.android.analytics.AnalyticsTracker
import com.woocommerce.android.analytics.AnalyticsTrackerWrapper
import com.woocommerce.android.notifications.local.LocalNotification.FreeTrialExpiringNotification
import com.woocommerce.android.notifications.local.LocalNotificationScheduler
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.login.storecreation.NewStore
import com.woocommerce.android.ui.login.storecreation.StoreCreationErrorType
Expand All @@ -19,6 +21,8 @@ import com.woocommerce.android.ui.login.storecreation.installation.StoreInstalla
import com.woocommerce.android.ui.login.storecreation.installation.StoreInstallationViewModel.ViewState.StoreCreationLoadingState
import com.woocommerce.android.ui.login.storecreation.installation.StoreInstallationViewModel.ViewState.SuccessState
import com.woocommerce.android.util.FeatureFlag
import com.woocommerce.android.util.IsRemoteFeatureFlagEnabled
import com.woocommerce.android.util.RemoteFeatureFlag.LOCAL_NOTIFICATION_1D_BEFORE_FREE_TRIAL_EXPIRES
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit
import com.woocommerce.android.viewmodel.ScopedViewModel
Expand All @@ -32,6 +36,8 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import org.wordpress.android.fluxc.utils.extensions.slashJoin
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import javax.inject.Inject

@HiltViewModel
Expand All @@ -45,8 +51,14 @@ class StoreInstallationViewModel @Inject constructor(
private val storeInstallationLoadingTimer: StoreInstallationLoadingTimer,
private val installationTransactionLauncher: InstallationTransactionLauncher,
private val observeSiteInstallation: ObserveSiteInstallation,
private val localNotificationScheduler: LocalNotificationScheduler,
private val isRemoteFeatureFlagEnabled: IsRemoteFeatureFlagEnabled
) : ScopedViewModel(savedStateHandle) {

companion object {
const val TRIAL_LENGTH_IN_DAYS = 14L
}

private val newStoreUrl
get() = selectedSite.get().url

Expand Down Expand Up @@ -97,6 +109,8 @@ class StoreInstallationViewModel @Inject constructor(
installationTransactionLauncher.onStoreInstalled(properties)

_viewState.update { SuccessState(newStoreWpAdminUrl) }

scheduleDeferredNotifications()
}

is InstallationState.Failure -> {
Expand Down Expand Up @@ -128,6 +142,17 @@ class StoreInstallationViewModel @Inject constructor(
}
}

private fun scheduleDeferredNotifications() {
if (isRemoteFeatureFlagEnabled(LOCAL_NOTIFICATION_1D_BEFORE_FREE_TRIAL_EXPIRES)) {
val in14days = LocalDateTime.now()
.plusDays(TRIAL_LENGTH_IN_DAYS)
.format(DateTimeFormatter.ofPattern("EEEE, MMMM d"))
localNotificationScheduler.scheduleNotification(
FreeTrialExpiringNotification(in14days, selectedSite.get().siteId)
)
}
}

private fun loadNewStore() {
launch {
combine(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import com.woocommerce.android.ui.main.MainActivityViewModel.ViewOrderList
import com.woocommerce.android.ui.main.MainActivityViewModel.ViewPayments
import com.woocommerce.android.ui.main.MainActivityViewModel.ViewReviewDetail
import com.woocommerce.android.ui.main.MainActivityViewModel.ViewReviewList
import com.woocommerce.android.ui.main.MainActivityViewModel.ViewStorePlanUpgrade
import com.woocommerce.android.ui.main.MainActivityViewModel.ViewTapToPay
import com.woocommerce.android.ui.main.MainActivityViewModel.ViewUrlInWebView
import com.woocommerce.android.ui.main.MainActivityViewModel.ViewZendeskTickets
Expand Down Expand Up @@ -742,6 +743,7 @@ class MainActivity :
is ViewUrlInWebView -> navigateToWebView(event)
is RequestNotificationsPermission -> requestNotificationsPermission()
is ShortcutOpenStoreCreation -> shortcutOpenStoreCreation(event.storeName)
is ViewStorePlanUpgrade -> startUpgradeFlowFactory.create(navController).invoke(event.source)
ViewPayments -> showPayments()
ViewTapToPay -> showTapToPaySummary()
ShortcutOpenPayments -> shortcutShowPayments()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import com.woocommerce.android.notifications.push.NotificationMessageHandler
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.tools.SiteConnectionType.Jetpack
import com.woocommerce.android.tools.connectionType
import com.woocommerce.android.ui.login.storecreation.dispatcher.PlanUpgradeStartFragment.PlanUpgradeStartSource
import com.woocommerce.android.ui.login.storecreation.dispatcher.PlanUpgradeStartFragment.PlanUpgradeStartSource.NOTIFICATION
import com.woocommerce.android.ui.main.MainActivityViewModel.MoreMenuBadgeState.Hidden
import com.woocommerce.android.ui.main.MainActivityViewModel.MoreMenuBadgeState.NewFeature
import com.woocommerce.android.ui.main.MainActivityViewModel.MoreMenuBadgeState.UnseenReviews
Expand Down Expand Up @@ -251,8 +253,13 @@ class MainActivityViewModel @Inject constructor(
mapOf(AnalyticsTracker.KEY_TYPE to notification.tag)
)

if (notification.tag == LocalNotificationType.STORE_CREATION_INCOMPLETE.value) {
triggerEvent(ShortcutOpenStoreCreation(storeName = notification.data))
when (notification.tag) {
LocalNotificationType.STORE_CREATION_INCOMPLETE.value -> {
triggerEvent(ShortcutOpenStoreCreation(storeName = notification.data))
}
LocalNotificationType.FREE_TRIAL_EXPIRING.value -> {
triggerEvent(ViewStorePlanUpgrade(NOTIFICATION))
}
}
}

Expand All @@ -267,6 +274,7 @@ class MainActivityViewModel @Inject constructor(
object ShortcutOpenPayments : Event()
object ShortcutOpenOrderCreation : Event()
data class ShortcutOpenStoreCreation(val storeName: String?) : Event()
data class ViewStorePlanUpgrade(val source: PlanUpgradeStartSource) : Event()
data class RestartActivityForNotification(val pushId: Int, val notification: Notification) : Event()
data class RestartActivityForAppLink(val data: Uri) : Event()
data class ShowFeatureAnnouncement(val announcement: FeatureAnnouncement) : Event()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.woocommerce.android.R.string
import com.woocommerce.android.analytics.AnalyticsEvent
import com.woocommerce.android.analytics.AnalyticsTracker
import com.woocommerce.android.analytics.AnalyticsTrackerWrapper
import com.woocommerce.android.notifications.local.LocalNotificationScheduler
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.login.storecreation.StoreCreationErrorType.STORE_LOADING_FAILED
import com.woocommerce.android.ui.login.storecreation.StoreCreationErrorType.STORE_NOT_READY
Expand All @@ -18,6 +19,7 @@ import com.woocommerce.android.ui.login.storecreation.installation.StoreInstalla
import com.woocommerce.android.ui.login.storecreation.installation.StoreInstallationViewModel.ViewState.StoreCreationLoadingState
import com.woocommerce.android.ui.login.storecreation.installation.StoreInstallationViewModel.ViewState.SuccessState
import com.woocommerce.android.util.FeatureFlag
import com.woocommerce.android.util.IsRemoteFeatureFlagEnabled
import com.woocommerce.android.viewmodel.BaseUnitTest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
Expand All @@ -44,6 +46,8 @@ class StoreInstallationViewModelTest : BaseUnitTest() {
private val storeInstallationLoadingTimer: StoreInstallationLoadingTimer = mock()
private val installationTransactionLauncher: InstallationTransactionLauncher = mock()
private val observeSiteInstallation: ObserveSiteInstallation = mock()
private val localNotificationScheduler: LocalNotificationScheduler = mock()
private val isRemoteFeatureFlagEnabled: IsRemoteFeatureFlagEnabled = mock()

private lateinit var viewModel: StoreInstallationViewModel

Expand All @@ -66,7 +70,9 @@ class StoreInstallationViewModelTest : BaseUnitTest() {
selectedSite,
storeInstallationLoadingTimer,
installationTransactionLauncher,
observeSiteInstallation
observeSiteInstallation,
localNotificationScheduler,
isRemoteFeatureFlagEnabled
)
viewModel.viewState.observeForever {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import org.junit.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.wordpress.android.fluxc.model.AccountModel
import org.wordpress.android.fluxc.store.AccountStore

@OptIn(ExperimentalCoroutinesApi::class)
Expand Down Expand Up @@ -142,13 +140,4 @@ internal class StoreNamePickerViewModelTest : BaseUnitTest() {
// Then
assertThat(latestEvent).isEqualTo(MultiLiveEvent.Event.NavigateToHelpScreen(HelpOrigin.STORE_CREATION))
}


companion object {
val TEST_ACCOUNT = AccountModel().apply {
userId = 123L
email = "mail@a8c.com"
userName = "username"
}
}
}