Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Commit

Permalink
For #9488: Add search widget CFR
Browse files Browse the repository at this point in the history
  • Loading branch information
sblatz committed May 27, 2020
1 parent 2482372 commit 1cfbcde
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 1 deletion.
90 changes: 90 additions & 0 deletions app/src/main/java/org/mozilla/fenix/cfr/SearchWidgetCFR.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.cfr

import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.marginTop
import kotlinx.android.synthetic.main.search_widget_cfr.view.*
import kotlinx.android.synthetic.main.tracking_protection_onboarding_popup.view.drop_down_triangle
import kotlinx.android.synthetic.main.tracking_protection_onboarding_popup.view.pop_up_triangle
import org.mozilla.fenix.R
import org.mozilla.fenix.components.SearchWidgetCreator
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.Settings

/**
* Displays a CFR above the HomeFragment toolbar that recommends usage / installation of the search widget.
*/
class SearchWidgetCFR(
private val context: Context,
private val getToolbar: () -> View
) {

// TODO: Based on pref && is in the bucket...?
fun displayIfNecessary() {
if (!context.settings().shouldDisplaySearchWidgetCFR()) { return }
showSearchWidgetCFR()
}

@Suppress("MagicNumber", "InflateParams")
private fun showSearchWidgetCFR() {
context.settings().incrementSearchWidgetCFRDisplayed()

val searchWidgetCFRDialog = Dialog(context)
val layout = LayoutInflater.from(context)
.inflate(R.layout.search_widget_cfr, null)
val isBottomToolbar = Settings.getInstance(context).shouldUseBottomToolbar

layout.drop_down_triangle.isGone = isBottomToolbar
layout.pop_up_triangle.isVisible = isBottomToolbar

val toolbar = getToolbar()

val gravity = if (isBottomToolbar) {
Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
} else {
Gravity.CENTER_HORIZONTAL or Gravity.TOP
}

layout.cfr_neg_button.setOnClickListener {
searchWidgetCFRDialog.dismiss()
context.settings().manuallyDismissSearchWidgetCFR()
}

layout.cfr_pos_button.setOnClickListener {
//context.components.analytics.metrics.track(Event.)
SearchWidgetCreator.createSearchWidget(context)
searchWidgetCFRDialog.dismiss()
//context.settings().manuallyDismissSearchWidgetCFR()
}

searchWidgetCFRDialog.apply {
setContentView(layout)
}

searchWidgetCFRDialog.window?.let {
it.setGravity(gravity)
val attr = it.attributes
attr.y =
(toolbar.y + toolbar.height - toolbar.marginTop - toolbar.paddingTop).toInt()
it.attributes = attr
it.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
}

searchWidgetCFRDialog.setOnDismissListener {
context.settings().incrementSearchWidgetCFRDismissed()
}

searchWidgetCFRDialog.show()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.components

import android.annotation.TargetApi
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.os.Build
import org.mozilla.gecko.search.SearchWidgetProvider

/**
* Handles the creation of search widget.
*/
object SearchWidgetCreator {

/**
* Attempts to display a prompt requesting the user pin the search widget
* Returns true if the prompt is displayed successfully, and false otherwise.
*/
@TargetApi(Build.VERSION_CODES.O)
fun createSearchWidget(context: Context): Boolean {
val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
if (!appWidgetManager.isRequestPinAppWidgetSupported) { return false }

val myProvider = ComponentName(context, SearchWidgetProvider::class.java)
appWidgetManager.requestPinAppWidget(myProvider, null, null)

return true
}
}
10 changes: 10 additions & 0 deletions app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.view.doOnLayout
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
Expand Down Expand Up @@ -78,6 +79,7 @@ import org.mozilla.fenix.addons.runIfFragmentIsAttached
import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.collections.SaveCollectionStep
import org.mozilla.fenix.cfr.SearchWidgetCFR
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.PrivateShortcutCreateManager
import org.mozilla.fenix.components.StoreProvider
Expand Down Expand Up @@ -459,6 +461,14 @@ class HomeFragment : Fragment() {
)
}
}

// We call this onLayout so that the bottom bar width is correctly set for us to center
// the CFR in.
view.toolbar_wrapper.doOnLayout {
if (!browsingModeManager.mode.isPrivate) {
SearchWidgetCFR(view.context) { view.toolbar_wrapper }.displayIfNecessary()
}
}
}

override fun onDestroyView() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ class DefaultSearchController(
val event = if (url.isUrl()) {
Event.EnteredUrl(false)
} else {
context.settings().incrementActiveSearchCount()

val searchAccessPoint = when (store.state.searchAccessPoint) {
NONE -> ACTION
else -> store.state.searchAccessPoint
Expand Down Expand Up @@ -142,6 +144,8 @@ class DefaultSearchController(
}

override fun handleSearchTermsTapped(searchTerms: String) {
context.settings().incrementActiveSearchCount()

activity.openToBrowserAndLoad(
searchTermOrURL = searchTerms,
newTab = store.state.session == null,
Expand Down
57 changes: 57 additions & 0 deletions app/src/main/java/org/mozilla/fenix/utils/Settings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,63 @@ class Settings private constructor(
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(
appContext.getPreferenceKey(R.string.pref_key_search_count),
default = 0
)

fun incrementActiveSearchCount() {
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_search_count),
activeSearchCount + 1
).apply()
}

private val isActiveSearcher: Boolean
get() = activeSearchCount > 2

fun shouldDisplaySearchWidgetCFR(): Boolean =
isActiveSearcher &&
searchWidgetCFRDismissCount < 3 &&
!searchWidgetInstalled &&
!searchWidgetCFRManuallyDismissed

private val searchWidgetCFRDisplayCount by intPreference(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count),
default = 0
)

fun incrementSearchWidgetCFRDisplayed() {
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count),
searchWidgetCFRDisplayCount + 1
).apply()
}

private val searchWidgetCFRManuallyDismissed by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_manually_dismissed),
default = false
)

fun manuallyDismissSearchWidgetCFR() {
preferences.edit().putBoolean(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_manually_dismissed),
true
).apply()
}

private val searchWidgetCFRDismissCount by intPreference(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_dismiss_count),
default = 0
)

fun incrementSearchWidgetCFRDismissed() {
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_dismiss_count),
searchWidgetCFRDismissCount + 1
).apply()
}

var defaultSearchEngineName by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_search_engine),
default = ""
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 100 additions & 0 deletions app/src/main/res/layout/search_widget_cfr.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_gravity="center_horizontal"
android:paddingHorizontal="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">

<androidx.appcompat.widget.AppCompatImageView
android:visibility="gone"
android:id="@+id/drop_down_triangle"
android:layout_width="@dimen/tp_onboarding_triangle_width"
android:layout_height="@dimen/tp_onboarding_triangle_height"
android:importantForAccessibility="no"
android:rotation="0"
app:srcCompat="@drawable/ic_pbm_triangle"
android:layout_gravity="center" />

<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/message"
android:layout_width="@dimen/etp_onboarding_popup_width"
android:layout_height="wrap_content"
android:background="@drawable/cfr_background_gradient"
app:layout_constraintTop_toTopOf="parent">

<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/cfr_image"
app:srcCompat="@drawable/search_widget_illustration"
android:padding="16dp"
android:scaleType="fitCenter"
android:layout_width="0dp"
android:layout_height="140dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

<TextView
android:id="@+id/cfr_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:lineSpacingExtra="2dp"
android:text="@string/search_widget_cfr_message"
android:textColor="@color/primary_text_dark_theme"
android:textSize="16sp"
app:fontFamily="@font/metropolis_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cfr_image" />


<Button
android:id="@+id/cfr_pos_button"
style="@style/MetropolisButton"
android:layout_width="0dp"
android:layout_height="36dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="@drawable/rounded_gray_corners"
android:text="@string/search_widget_cfr_pos_button_text"
android:textAllCaps="false"
android:textColor="@color/above_dark_theme"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@id/cfr_neg_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cfr_message" />

<Button
android:id="@+id/cfr_neg_button"
style="@style/MetropolisButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:text="@string/search_widget_cfr_neg_button_text"
android:textAllCaps="false"
android:textColor="@color/white_color"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cfr_pos_button" />
</androidx.constraintlayout.widget.ConstraintLayout>

<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/pop_up_triangle"
android:layout_width="16dp"
android:layout_height="@dimen/tp_onboarding_triangle_height"
android:importantForAccessibility="no"
android:rotation="180"
app:srcCompat="@drawable/ic_pbm_triangle"
android:layout_gravity="center" />
</LinearLayout>
6 changes: 6 additions & 0 deletions app/src/main/res/values/preference_keys.xml
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,10 @@
<string name="pref_key_enable_new_tab_tray" translatable="false">pref_key_new_tab_tray</string>

<string name="pref_key_debug_settings" translatable="false">pref_key_debug_settings</string>

<string name="pref_key_search_count" translatable="false">pref_key_search_count</string>
<string name="pref_key_search_widget_cfr_display_count" translatable="false">pref_key_search_widget_cfr_display_count</string>
<string name="pref_key_search_widget_cfr_dismiss_count" translatable="false">pref_key_search_widget_cfr_dismiss_count</string>
<string name="pref_key_search_widget_cfr_manually_dismissed" translatable="false">pref_key_search_widget_cfr_manually_dismissed</string>
<string name="pref_key_show_search_widget_cfr" translatable="false">pref_key_show_search_widget_cfr</string>
</resources>
10 changes: 9 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,22 @@
<!-- Delete session button to erase your history in a private session -->
<string name="private_browsing_delete_session">Delete session</string>

<!-- Private mode shortcut "contextual feature recommender" (CFR) -->
<!-- Private mode shortcut "contextual feature recommendation" (CFR) -->
<!-- Text for the main message -->
<string name="cfr_message">Add a shortcut to open private tabs from your Home screen.</string>
<!-- Text for the positive button -->
<string name="cfr_pos_button_text">Add shortcut</string>
<!-- Text for the negative button -->
<string name="cfr_neg_button_text">No thanks</string>

<!-- Search widget "contextual feature recommendation" (CFR) -->
<!-- Text for the main message. Placeholder text replaced with app name. -->
<string name="search_widget_cfr_message">Get to Firefox faster. Add a widget to your Home screen.</string>
<!-- Text for the positive button -->
<string name="search_widget_cfr_pos_button_text">Add widget</string>
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Not now</string>

<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">New tab</string>
Expand Down

0 comments on commit 1cfbcde

Please sign in to comment.