1
0
Fork 0

For #12364 - Only show PWA prompt the third time a user visits installable site

master
ekager 2020-07-16 14:44:24 -04:00 committed by Emily Kager
parent ac3df6bc5e
commit e358f95eed
9 changed files with 107 additions and 44 deletions

View File

@ -40,7 +40,7 @@ import org.mozilla.fenix.ext.navigateSafe
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.resetPoliciesAfter import org.mozilla.fenix.ext.resetPoliciesAfter
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.shortcut.FirstTimePwaObserver import org.mozilla.fenix.shortcut.PwaOnboardingObserver
import org.mozilla.fenix.trackingprotection.TrackingProtectionOverlay import org.mozilla.fenix.trackingprotection.TrackingProtectionOverlay
/** /**
@ -156,9 +156,9 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
} }
session?.register(toolbarSessionObserver, viewLifecycleOwner, autoPause = true) session?.register(toolbarSessionObserver, viewLifecycleOwner, autoPause = true)
if (settings.shouldShowFirstTimePwaFragment) { if (!settings.userKnowsAboutPwas) {
session?.register( session?.register(
FirstTimePwaObserver( PwaOnboardingObserver(
navController = findNavController(), navController = findNavController(),
settings = settings, settings = settings,
webAppUseCases = context.components.useCases.webAppUseCases webAppUseCases = context.components.useCases.webAppUseCases

View File

@ -74,6 +74,6 @@ object Performance {
* Disables the first time PWA popup. * Disables the first time PWA popup.
*/ */
private fun disableFirstTimePWAPopup(context: Context) { private fun disableFirstTimePWAPopup(context: Context) {
Settings.getInstance(context).userKnowsAboutPWAs = true Settings.getInstance(context).userKnowsAboutPwas = true
} }
} }

View File

@ -16,9 +16,9 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
/** /**
* Dialog displayed the first time the user navigates to an installable web app. * Dialog displayed the third time the user navigates to an installable web app.
*/ */
class FirstTimePwaFragment : DialogFragment() { class PwaOnboardingDialogFragment : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.CreateShortcutDialogStyle) setStyle(STYLE_NO_TITLE, R.style.CreateShortcutDialogStyle)
@ -28,7 +28,7 @@ class FirstTimePwaFragment : DialogFragment() {
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_pwa_first_time, container, false) ): View? = inflater.inflate(R.layout.fragment_pwa_onboarding, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View File

@ -14,20 +14,23 @@ import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
/** /**
* Displays the [FirstTimePwaFragment] info dialog when a PWA is first opened in the browser. * Displays the [PwaOnboardingDialogFragment] info dialog when a PWA is opened in the browser for the third time.
*/ */
class FirstTimePwaObserver( class PwaOnboardingObserver(
private val navController: NavController, private val navController: NavController,
private val settings: Settings, private val settings: Settings,
private val webAppUseCases: WebAppUseCases private val webAppUseCases: WebAppUseCases
) : Session.Observer { ) : Session.Observer {
override fun onWebAppManifestChanged(session: Session, manifest: WebAppManifest?) { override fun onWebAppManifestChanged(session: Session, manifest: WebAppManifest?) {
if (webAppUseCases.isInstallable() && settings.shouldShowFirstTimePwaFragment) { if (webAppUseCases.isInstallable() && !settings.userKnowsAboutPwas) {
val directions = BrowserFragmentDirections.actionBrowserFragmentToFirstTimePwaFragment() settings.incrementVisitedInstallableCount()
navController.nav(R.id.browserFragment, directions) if (settings.shouldShowPwaOnboarding) {
val directions =
settings.userKnowsAboutPWAs = true BrowserFragmentDirections.actionBrowserFragmentToPwaOnboardingDialogFragment()
navController.nav(R.id.browserFragment, directions)
settings.userKnowsAboutPwas = true
}
} }
} }
} }

View File

@ -53,6 +53,7 @@ class Settings private constructor(
const val showLoginsSecureWarningSyncMaxCount = 1 const val showLoginsSecureWarningSyncMaxCount = 1
const val showLoginsSecureWarningMaxCount = 1 const val showLoginsSecureWarningMaxCount = 1
const val trackingProtectionOnboardingMaximumCount = 1 const val trackingProtectionOnboardingMaximumCount = 1
const val pwaVisitsToShowPromptMaxCount = 3
const val FENIX_PREFERENCES = "fenix_preferences" const val FENIX_PREFERENCES = "fenix_preferences"
private const val showSearchWidgetCFRMaxCount = 3 private const val showSearchWidgetCFRMaxCount = 3
@ -146,9 +147,18 @@ class Settings private constructor(
// If any of the prefs have been modified, quit displaying the fenix moved tip // If any of the prefs have been modified, quit displaying the fenix moved tip
fun shouldDisplayFenixMovingTip(): Boolean = fun shouldDisplayFenixMovingTip(): Boolean =
preferences.getBoolean(appContext.getString(R.string.pref_key_migrating_from_fenix_nightly_tip), true) && preferences.getBoolean(
preferences.getBoolean(appContext.getString(R.string.pref_key_migrating_from_firefox_nightly_tip), true) && appContext.getString(R.string.pref_key_migrating_from_fenix_nightly_tip),
preferences.getBoolean(appContext.getString(R.string.pref_key_migrating_from_fenix_tip), true) true
) &&
preferences.getBoolean(
appContext.getString(R.string.pref_key_migrating_from_firefox_nightly_tip),
true
) &&
preferences.getBoolean(
appContext.getString(R.string.pref_key_migrating_from_fenix_tip),
true
)
private val activeSearchCount by intPreference( private val activeSearchCount by intPreference(
appContext.getPreferenceKey(R.string.pref_key_search_count), appContext.getPreferenceKey(R.string.pref_key_search_count),
@ -167,9 +177,9 @@ class Settings private constructor(
fun shouldDisplaySearchWidgetCFR(): Boolean = fun shouldDisplaySearchWidgetCFR(): Boolean =
isActiveSearcher && isActiveSearcher &&
searchWidgetCFRDismissCount < showSearchWidgetCFRMaxCount && searchWidgetCFRDismissCount < showSearchWidgetCFRMaxCount &&
!searchWidgetInstalled && !searchWidgetInstalled &&
!searchWidgetCFRManuallyDismissed !searchWidgetCFRManuallyDismissed
private val searchWidgetCFRDisplayCount by intPreference( private val searchWidgetCFRDisplayCount by intPreference(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count), appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count),
@ -236,10 +246,10 @@ class Settings private constructor(
val isCrashReportingEnabled: Boolean val isCrashReportingEnabled: Boolean
get() = isCrashReportEnabledInBuild && get() = isCrashReportEnabledInBuild &&
preferences.getBoolean( preferences.getBoolean(
appContext.getPreferenceKey(R.string.pref_key_crash_reporter), appContext.getPreferenceKey(R.string.pref_key_crash_reporter),
true true
) )
val isRemoteDebuggingEnabled by booleanPreference( val isRemoteDebuggingEnabled by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_remote_debugging), appContext.getPreferenceKey(R.string.pref_key_remote_debugging),
@ -267,7 +277,7 @@ class Settings private constructor(
val shouldShowTrackingProtectionOnboarding: Boolean val shouldShowTrackingProtectionOnboarding: Boolean
get() = !isOverrideTPPopupsForPerformanceTest && get() = !isOverrideTPPopupsForPerformanceTest &&
(trackingProtectionOnboardingCount < trackingProtectionOnboardingMaximumCount && (trackingProtectionOnboardingCount < trackingProtectionOnboardingMaximumCount &&
!trackingProtectionOnboardingShownThisSession) !trackingProtectionOnboardingShownThisSession)
var showSecretDebugMenuThisSession = false var showSecretDebugMenuThisSession = false
@ -418,14 +428,14 @@ class Settings private constructor(
BrowsingMode.Normal BrowsingMode.Normal
} }
} }
set(value) { set(value) {
val lastKnownModeWasPrivate = (value == BrowsingMode.Private) val lastKnownModeWasPrivate = (value == BrowsingMode.Private)
preferences.edit() preferences.edit()
.putBoolean( .putBoolean(
appContext.getPreferenceKey(R.string.pref_key_last_known_mode_private), appContext.getPreferenceKey(R.string.pref_key_last_known_mode_private),
lastKnownModeWasPrivate) lastKnownModeWasPrivate
)
.apply() .apply()
field = value field = value
@ -495,7 +505,9 @@ class Settings private constructor(
} }
val accessibilityServicesEnabled: Boolean val accessibilityServicesEnabled: Boolean
get() { return touchExplorationIsEnabled || switchServiceIsEnabled } get() {
return touchExplorationIsEnabled || switchServiceIsEnabled
}
val toolbarSettingString: String val toolbarSettingString: String
get() = when { get() = when {
@ -569,22 +581,41 @@ class Settings private constructor(
default = false default = false
) )
val shouldShowFirstTimePwaFragment: Boolean fun incrementVisitedInstallableCount() {
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_install_pwa_visits),
pwaInstallableVisitCount + 1
).apply()
}
@VisibleForTesting(otherwise = PRIVATE)
internal val pwaInstallableVisitCount by intPreference(
appContext.getPreferenceKey(R.string.pref_key_install_pwa_visits),
default = 0
)
private val userNeedsToVisitInstallableSites: Boolean
get() = pwaInstallableVisitCount < pwaVisitsToShowPromptMaxCount
val shouldShowPwaOnboarding: Boolean
get() { get() {
// We only want to show this on the 3rd time a user visits a site
if (userNeedsToVisitInstallableSites) return false
// ShortcutManager::pinnedShortcuts is only available on Oreo+ // ShortcutManager::pinnedShortcuts is only available on Oreo+
if (!userKnowsAboutPWAs && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (!userKnowsAboutPwas && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val alreadyHavePWaInstalled = val alreadyHavePwaInstalled =
appContext.getSystemService(ShortcutManager::class.java) appContext.getSystemService(ShortcutManager::class.java)
.pinnedShortcuts.size > 0 .pinnedShortcuts.size > 0
// Users know about PWAs onboarding if they already have PWAs installed. // Users know about PWAs onboarding if they already have PWAs installed.
userKnowsAboutPWAs = alreadyHavePWaInstalled userKnowsAboutPwas = alreadyHavePwaInstalled
} }
// Show dialog only if user does not know abut PWAs // Show dialog only if user does not know abut PWAs
return !userKnowsAboutPWAs return !userKnowsAboutPwas
} }
var userKnowsAboutPWAs by booleanPreference( var userKnowsAboutPwas by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_user_knows_about_pwa), appContext.getPreferenceKey(R.string.pref_key_user_knows_about_pwa),
default = false default = false
) )
@ -809,8 +840,12 @@ class Settings private constructor(
var savedLoginsSortingStrategy: SortingStrategy var savedLoginsSortingStrategy: SortingStrategy
get() { get() {
return when (savedLoginsSortingStrategyString) { return when (savedLoginsSortingStrategyString) {
SavedLoginsFragment.SORTING_STRATEGY_ALPHABETICALLY -> SortingStrategy.Alphabetically(appContext) SavedLoginsFragment.SORTING_STRATEGY_ALPHABETICALLY -> SortingStrategy.Alphabetically(
SavedLoginsFragment.SORTING_STRATEGY_LAST_USED -> SortingStrategy.LastUsed(appContext) appContext
)
SavedLoginsFragment.SORTING_STRATEGY_LAST_USED -> SortingStrategy.LastUsed(
appContext
)
else -> SortingStrategy.Alphabetically(appContext) else -> SortingStrategy.Alphabetically(appContext)
} }
} }

View File

@ -10,7 +10,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/scrim_background" android:background="@drawable/scrim_background"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context="org.mozilla.fenix.shortcut.FirstTimePwaFragment"> tools:context="org.mozilla.fenix.shortcut.PwaOnboardingDialogFragment">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -221,8 +221,8 @@
android:id="@+id/action_browserFragment_to_createShortcutFragment" android:id="@+id/action_browserFragment_to_createShortcutFragment"
app:destination="@id/createShortcutFragment" /> app:destination="@id/createShortcutFragment" />
<action <action
android:id="@+id/action_browserFragment_to_firstTimePwaFragment" android:id="@+id/action_browserFragment_to_pwaOnboardingDialogFragment"
app:destination="@id/firstTimePwaFragment" /> app:destination="@id/pwaOnboardingDialogFragment" />
<action <action
android:id="@+id/action_browserFragment_to_quickSettingsSheetDialogFragment" android:id="@+id/action_browserFragment_to_quickSettingsSheetDialogFragment"
app:destination="@id/quickSettingsSheetDialogFragment" /> app:destination="@id/quickSettingsSheetDialogFragment" />
@ -680,10 +680,9 @@
android:name="org.mozilla.fenix.shortcut.CreateShortcutFragment" android:name="org.mozilla.fenix.shortcut.CreateShortcutFragment"
tools:layout="@layout/fragment_create_shortcut" /> tools:layout="@layout/fragment_create_shortcut" />
<dialog <dialog
android:id="@+id/firstTimePwaFragment" android:id="@+id/pwaOnboardingDialogFragment"
android:name="org.mozilla.fenix.shortcut.FirstTimePwaFragment" android:name="org.mozilla.fenix.shortcut.PwaOnboardingDialogFragment"
android:label="fragment_pwa_first_time" tools:layout="@layout/fragment_pwa_onboarding" />
tools:layout="@layout/fragment_pwa_first_time" />
<dialog <dialog
android:id="@+id/shareFragment" android:id="@+id/shareFragment"

View File

@ -56,6 +56,7 @@
<string name="pref_key_private_mode_opened" translatable="false">pref_key_private_mode_opened</string> <string name="pref_key_private_mode_opened" translatable="false">pref_key_private_mode_opened</string>
<string name="pref_key_open_in_app_opened" translatable="false">pref_key_open_in_app_opened</string> <string name="pref_key_open_in_app_opened" translatable="false">pref_key_open_in_app_opened</string>
<string name="pref_key_install_pwa_opened" translatable="false">pref_key_install_pwa_opened</string> <string name="pref_key_install_pwa_opened" translatable="false">pref_key_install_pwa_opened</string>
<string name="pref_key_install_pwa_visits" translatable="false">pref_key_install_pwa_visits</string>
<!-- Data Choices --> <!-- Data Choices -->
<string name="pref_key_telemetry" translatable="false">pref_key_telemetry</string> <string name="pref_key_telemetry" translatable="false">pref_key_telemetry</string>

View File

@ -351,6 +351,31 @@ class SettingsTest {
assertTrue(settings.shouldShowSearchSuggestions) assertTrue(settings.shouldShowSearchSuggestions)
} }
@Test
fun showPwaFragment() {
// When just created
// Then
assertFalse(settings.shouldShowPwaOnboarding)
// When visited once
settings.incrementVisitedInstallableCount()
// Then
assertFalse(settings.shouldShowPwaOnboarding)
// When visited twice
settings.incrementVisitedInstallableCount()
// Then
assertFalse(settings.shouldShowPwaOnboarding)
// When visited thrice
settings.incrementVisitedInstallableCount()
// Then
assertTrue(settings.shouldShowPwaOnboarding)
}
@Test @Test
fun sitePermissionsPhoneFeatureCameraAction() { fun sitePermissionsPhoneFeatureCameraAction() {
// When just created // When just created