1
0
Fork 0

For #3360 - Adds empty state for TP Exceptions

master
Emily Kager 2019-07-18 15:15:24 -07:00 committed by Emily Kager
parent 7588251f8b
commit 9b5baa2358
9 changed files with 116 additions and 32 deletions

View File

@ -35,6 +35,7 @@ import mozilla.components.support.utils.SafeIntent
import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.isSentryEnabled import org.mozilla.fenix.components.isSentryEnabled
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.exceptions.ExceptionsFragmentDirections
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView import org.mozilla.fenix.ext.getRootView
import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.nav
@ -230,15 +231,21 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
} }
BrowserDirection.FromSearch -> { BrowserDirection.FromSearch -> {
fragmentId = R.id.searchFragment fragmentId = R.id.searchFragment
SearchFragmentDirections.actionSearchFragmentToBrowserFragment(customTabSessionId) SearchFragmentDirections.actionSearchFragmentToBrowserFragment(
customTabSessionId
)
} }
BrowserDirection.FromSettings -> { BrowserDirection.FromSettings -> {
fragmentId = R.id.settingsFragment fragmentId = R.id.settingsFragment
SettingsFragmentDirections.actionSettingsFragmentToBrowserFragment(customTabSessionId) SettingsFragmentDirections.actionSettingsFragmentToBrowserFragment(
customTabSessionId
)
} }
BrowserDirection.FromBookmarks -> { BrowserDirection.FromBookmarks -> {
fragmentId = R.id.bookmarkFragment fragmentId = R.id.bookmarkFragment
BookmarkFragmentDirections.actionBookmarkFragmentToBrowserFragment(customTabSessionId) BookmarkFragmentDirections.actionBookmarkFragmentToBrowserFragment(
customTabSessionId
)
} }
BrowserDirection.FromBookmarksFolderSelect -> { BrowserDirection.FromBookmarksFolderSelect -> {
fragmentId = R.id.bookmarkSelectFolderFragment fragmentId = R.id.bookmarkSelectFolderFragment
@ -247,7 +254,9 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
} }
BrowserDirection.FromHistory -> { BrowserDirection.FromHistory -> {
fragmentId = R.id.historyFragment fragmentId = R.id.historyFragment
HistoryFragmentDirections.actionHistoryFragmentToBrowserFragment(customTabSessionId) HistoryFragmentDirections.actionHistoryFragmentToBrowserFragment(
customTabSessionId
)
} }
} }
} else { } else {
@ -259,7 +268,12 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
} }
} }
private fun load(searchTermOrURL: String, newTab: Boolean, engine: SearchEngine?, forceSearch: Boolean) { private fun load(
searchTermOrURL: String,
newTab: Boolean,
engine: SearchEngine?,
forceSearch: Boolean
) {
val isPrivate = this.browsingModeManager.isPrivate val isPrivate = this.browsingModeManager.isPrivate
val loadUrlUseCase = if (newTab) { val loadUrlUseCase = if (newTab) {
@ -273,7 +287,13 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
val searchUseCase: (String) -> Unit = { searchTerms -> val searchUseCase: (String) -> Unit = { searchTerms ->
if (newTab) { if (newTab) {
components.useCases.searchUseCases.newTabSearch components.useCases.searchUseCases.newTabSearch
.invoke(searchTerms, Session.Source.USER_ENTERED, true, isPrivate, searchEngine = engine) .invoke(
searchTerms,
Session.Source.USER_ENTERED,
true,
isPrivate,
searchEngine = engine
)
} else components.useCases.searchUseCases.defaultSearch.invoke(searchTerms, engine) } else components.useCases.searchUseCases.defaultSearch.invoke(searchTerms, engine)
} }
@ -377,5 +397,5 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
enum class BrowserDirection { enum class BrowserDirection {
FromGlobal, FromHome, FromSearch, FromSettings, FromBookmarks, FromGlobal, FromHome, FromSearch, FromSettings, FromBookmarks,
FromBookmarksFolderSelect, FromHistory FromBookmarksFolderSelect, FromHistory, FromExceptions
} }

View File

@ -5,13 +5,13 @@
package org.mozilla.fenix.exceptions package org.mozilla.fenix.exceptions
import android.view.ViewGroup import android.view.ViewGroup
import org.mozilla.fenix.mvi.ViewState
import org.mozilla.fenix.mvi.Change
import org.mozilla.fenix.mvi.Action import org.mozilla.fenix.mvi.Action
import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.Change
import org.mozilla.fenix.mvi.UIComponent import org.mozilla.fenix.mvi.UIComponent
import org.mozilla.fenix.mvi.UIComponentViewModelBase import org.mozilla.fenix.mvi.UIComponentViewModelBase
import org.mozilla.fenix.mvi.UIComponentViewModelProvider import org.mozilla.fenix.mvi.UIComponentViewModelProvider
import org.mozilla.fenix.mvi.ViewState
import org.mozilla.fenix.test.Mockable import org.mozilla.fenix.test.Mockable
data class ExceptionsItem(val url: String) data class ExceptionsItem(val url: String)
@ -38,6 +38,7 @@ class ExceptionsComponent(
data class ExceptionsState(val items: List<ExceptionsItem>) : ViewState data class ExceptionsState(val items: List<ExceptionsItem>) : ViewState
sealed class ExceptionsAction : Action { sealed class ExceptionsAction : Action {
object LearnMore : ExceptionsAction()
sealed class Delete : ExceptionsAction() { sealed class Delete : ExceptionsAction() {
object All : Delete() object All : Delete()
data class One(val item: ExceptionsItem) : Delete() data class One(val item: ExceptionsItem) : Delete()

View File

@ -17,11 +17,14 @@ import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.FenixViewModelProvider import org.mozilla.fenix.FenixViewModelProvider
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.mvi.ActionBusFactory import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getAutoDisposeObservable import org.mozilla.fenix.mvi.getAutoDisposeObservable
import org.mozilla.fenix.mvi.getManagedEmitter import org.mozilla.fenix.mvi.getManagedEmitter
import org.mozilla.fenix.settings.SupportUtils
class ExceptionsFragment : Fragment() { class ExceptionsFragment : Fragment() {
private lateinit var exceptionsComponent: ExceptionsComponent private lateinit var exceptionsComponent: ExceptionsComponent
@ -56,6 +59,14 @@ class ExceptionsFragment : Fragment() {
getAutoDisposeObservable<ExceptionsAction>() getAutoDisposeObservable<ExceptionsAction>()
.subscribe { .subscribe {
when (it) { when (it) {
is ExceptionsAction.LearnMore -> {
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
(SupportUtils.SumoTopic.TRACKING_PROTECTION),
newTab = true,
from = BrowserDirection.FromExceptions
)
}
is ExceptionsAction.Delete.All -> viewLifecycleOwner.lifecycleScope.launch(IO) { is ExceptionsAction.Delete.All -> viewLifecycleOwner.lifecycleScope.launch(IO) {
val domains = ExceptionDomains.load(context!!) val domains = ExceptionDomains.load(context!!)
ExceptionDomains.remove(context!!, domains) ExceptionDomains.remove(context!!, domains)

View File

@ -4,10 +4,15 @@
package org.mozilla.fenix.exceptions package org.mozilla.fenix.exceptions
import android.text.SpannableString
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.ForegroundColorSpan
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Observer import io.reactivex.Observer
import io.reactivex.functions.Consumer import io.reactivex.functions.Consumer
@ -20,20 +25,45 @@ class ExceptionsUIView(
actionEmitter: Observer<ExceptionsAction>, actionEmitter: Observer<ExceptionsAction>,
changesObservable: Observable<ExceptionsChange> changesObservable: Observable<ExceptionsChange>
) : ) :
UIView<ExceptionsState, ExceptionsAction, ExceptionsChange>(container, actionEmitter, changesObservable) { UIView<ExceptionsState, ExceptionsAction, ExceptionsChange>(
container,
actionEmitter,
changesObservable
) {
override val view: RecyclerView = LayoutInflater.from(container.context) override val view: FrameLayout = LayoutInflater.from(container.context)
.inflate(R.layout.component_exceptions, container, true) .inflate(R.layout.component_exceptions, container, true)
.findViewById(R.id.exceptions_list) .findViewById(R.id.exceptions_wrapper)
init { init {
view.exceptions_list.apply { view.exceptions_list.apply {
adapter = ExceptionsAdapter(actionEmitter) adapter = ExceptionsAdapter(actionEmitter)
layoutManager = LinearLayoutManager(container.context) layoutManager = LinearLayoutManager(container.context)
} }
val descriptionText = String
.format(
view.exceptions_empty_view.text.toString(),
System.getProperty("line.separator")
)
val linkStartIndex = descriptionText.indexOf("\n\n") + 2
val linkAction = object : ClickableSpan() {
override fun onClick(widget: View?) {
actionEmitter.onNext(ExceptionsAction.LearnMore)
}
}
val textWithLink = SpannableString(descriptionText).apply {
setSpan(linkAction, linkStartIndex, descriptionText.length, 0)
val colorSpan = ForegroundColorSpan(view.exceptions_empty_view.currentTextColor)
setSpan(colorSpan, linkStartIndex, descriptionText.length, 0)
}
view.exceptions_empty_view.movementMethod = LinkMovementMethod.getInstance()
view.exceptions_empty_view.text = textWithLink
} }
override fun updateView() = Consumer<ExceptionsState> { override fun updateView() = Consumer<ExceptionsState> {
view.exceptions_empty_view.visibility = if (it.items.isEmpty()) View.VISIBLE else View.GONE
view.exceptions_list.visibility = if (it.items.isEmpty()) View.GONE else View.VISIBLE
(view.exceptions_list.adapter as ExceptionsAdapter).updateData(it.items) (view.exceptions_list.adapter as ExceptionsAdapter).updateData(it.items)
} }
} }

View File

@ -28,7 +28,8 @@ object SupportUtils {
) { ) {
HELP("faq-android"), HELP("faq-android"),
PRIVATE_BROWSING_MYTHS("common-myths-about-private-browsing"), PRIVATE_BROWSING_MYTHS("common-myths-about-private-browsing"),
YOUR_RIGHTS("your-rights") YOUR_RIGHTS("your-rights"),
TRACKING_PROTECTION("tracking-protection-firefox-preview")
} }
fun getSumoURLForTopic(context: Context, topic: SumoTopic): String { fun getSumoURLForTopic(context: Context, topic: SumoTopic): String {

View File

@ -11,7 +11,6 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference import androidx.preference.SwitchPreference
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.exceptions.ExceptionDomains
import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
@ -34,7 +33,8 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() {
preferenceTP?.isChecked = Settings.getInstance(context!!).shouldUseTrackingProtection preferenceTP?.isChecked = Settings.getInstance(context!!).shouldUseTrackingProtection
preferenceTP?.onPreferenceChangeListener = preferenceTP?.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, newValue -> Preference.OnPreferenceChangeListener { _, newValue ->
Settings.getInstance(requireContext()).setTrackingProtection(newValue = newValue as Boolean) Settings.getInstance(requireContext())
.setTrackingProtection(newValue = newValue as Boolean)
with(requireComponents) { with(requireComponents) {
val policy = core.createTrackingProtectionPolicy(newValue) val policy = core.createTrackingProtectionPolicy(newValue)
useCases.settingsUseCases.updateTrackingProtection.invoke(policy) useCases.settingsUseCases.updateTrackingProtection.invoke(policy)
@ -43,20 +43,16 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() {
true true
} }
context?.let { val exceptions =
val exceptionsEmpty = ExceptionDomains.load(it).isEmpty() context!!.getPreferenceKey(R.string.pref_key_tracking_protection_exceptions)
val exceptions = val preferenceExceptions = findPreference<Preference>(exceptions)
it.getPreferenceKey(R.string.pref_key_tracking_protection_exceptions) preferenceExceptions?.onPreferenceClickListener = getClickListenerForExceptions()
val preferenceExceptions = findPreference<Preference>(exceptions)
preferenceExceptions?.shouldDisableView = true
preferenceExceptions?.isEnabled = !exceptionsEmpty
preferenceExceptions?.onPreferenceClickListener = getClickListenerForExceptions()
}
} }
private fun getClickListenerForExceptions(): Preference.OnPreferenceClickListener { private fun getClickListenerForExceptions(): Preference.OnPreferenceClickListener {
return Preference.OnPreferenceClickListener { return Preference.OnPreferenceClickListener {
val directions = TrackingProtectionFragmentDirections.actionTrackingProtectionFragmentToExceptionsFragment() val directions =
TrackingProtectionFragmentDirections.actionTrackingProtectionFragmentToExceptionsFragment()
Navigation.findNavController(view!!).navigate(directions) Navigation.findNavController(view!!).navigate(directions)
true true
} }

View File

@ -2,10 +2,27 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public <!-- 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 - 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/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<androidx.recyclerview.widget.RecyclerView <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/exceptions_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:visibility="gone"
android:id="@+id/exceptions_list" android:id="@+id/exceptions_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:listitem="@layout/exception_item"/> tools:listitem="@layout/exception_item" />
<TextView
android:id="@+id/exceptions_empty_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_margin="12dp"
android:text="@string/exceptions_empty_message"
android:textColor="?secondaryText"
android:textSize="16sp"
android:visibility="visible" />
</FrameLayout>

View File

@ -375,11 +375,17 @@
<fragment <fragment
android:id="@+id/deleteBrowsingDataFragment" android:id="@+id/deleteBrowsingDataFragment"
android:name="org.mozilla.fenix.settings.DeleteBrowsingDataFragment" android:name="org.mozilla.fenix.settings.DeleteBrowsingDataFragment"
android:label="@string/preferences_delete_browsing_data"/> android:label="@string/preferences_delete_browsing_data" />
<fragment <fragment
android:id="@+id/exceptionsFragment" android:id="@+id/exceptionsFragment"
android:name="org.mozilla.fenix.exceptions.ExceptionsFragment" android:name="org.mozilla.fenix.exceptions.ExceptionsFragment"
android:label="@string/preference_exceptions" /> android:label="@string/preference_exceptions">
<action
android:id="@+id/action_exceptionsFragment_to_browserFragment"
app:destination="@id/browserFragment"
app:popUpTo="@id/settingsFragment"
app:popUpToInclusive="true" />
</fragment>
<dialog <dialog
android:id="@+id/createCollectionFragment" android:id="@+id/createCollectionFragment"
android:name="org.mozilla.fenix.collections.CreateCollectionFragment" android:name="org.mozilla.fenix.collections.CreateCollectionFragment"

View File

@ -197,6 +197,8 @@
<string name="preferences_tracking_protection_exceptions_description">Tracking Protection is off for these websites</string> <string name="preferences_tracking_protection_exceptions_description">Tracking Protection is off for these websites</string>
<!-- Button in Exceptions Preference to turn on tracking protection for all sites (remove all exceptions) --> <!-- Button in Exceptions Preference to turn on tracking protection for all sites (remove all exceptions) -->
<string name="preferences_tracking_protection_exceptions_turn_on_for_all">Turn on for all sites</string> <string name="preferences_tracking_protection_exceptions_turn_on_for_all">Turn on for all sites</string>
<!-- Text displayed when there are no exceptions, includes a learn more link that brings users to a tracking protection SUMO page -->
<string name="exceptions_empty_message">Exceptions let you disable tracking protection for selected sites.\n\nLearn more</string>
<!-- Description in Quick Settings that tells user tracking protection is off globally for all sites, and links to Settings to turn it on --> <!-- Description in Quick Settings that tells user tracking protection is off globally for all sites, and links to Settings to turn it on -->
<string name="preferences_tracking_protection_turned_off_globally">Turned off globally, go to Settings to turn it on.</string> <string name="preferences_tracking_protection_turned_off_globally">Turned off globally, go to Settings to turn it on.</string>