For #7094 - Adds save login exceptions
parent
69020a1f26
commit
9ed85510ae
|
@ -501,6 +501,7 @@ dependencies {
|
||||||
implementation Deps.mozilla_feature_toolbar
|
implementation Deps.mozilla_feature_toolbar
|
||||||
implementation Deps.mozilla_feature_tabs
|
implementation Deps.mozilla_feature_tabs
|
||||||
implementation Deps.mozilla_feature_findinpage
|
implementation Deps.mozilla_feature_findinpage
|
||||||
|
implementation Deps.mozilla_feature_logins
|
||||||
implementation Deps.mozilla_feature_site_permissions
|
implementation Deps.mozilla_feature_site_permissions
|
||||||
implementation Deps.mozilla_feature_readerview
|
implementation Deps.mozilla_feature_readerview
|
||||||
implementation Deps.mozilla_feature_tab_collections
|
implementation Deps.mozilla_feature_tab_collections
|
||||||
|
|
|
@ -170,6 +170,7 @@ class SettingsPrivacyTest {
|
||||||
verifyDefaultView()
|
verifyDefaultView()
|
||||||
verifyDefaultValueSyncLogins()
|
verifyDefaultValueSyncLogins()
|
||||||
verifyDefaultValueAutofillLogins()
|
verifyDefaultValueAutofillLogins()
|
||||||
|
verifyDefaultValueExceptions()
|
||||||
}.openSavedLogins {
|
}.openSavedLogins {
|
||||||
verifySavedLoginsView()
|
verifySavedLoginsView()
|
||||||
tapSetupLater()
|
tapSetupLater()
|
||||||
|
@ -209,13 +210,13 @@ class SettingsPrivacyTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun doNotSaveLoginFromPromptTest() {
|
fun neverSaveLoginFromPromptTest() {
|
||||||
val saveLoginTest = TestAssetHelper.getSaveLoginAsset(mockWebServer)
|
val saveLoginTest = TestAssetHelper.getSaveLoginAsset(mockWebServer)
|
||||||
|
|
||||||
navigationToolbar {
|
navigationToolbar {
|
||||||
}.enterURLAndEnterToBrowser(saveLoginTest.url) {
|
}.enterURLAndEnterToBrowser(saveLoginTest.url) {
|
||||||
verifySaveLoginPromptIsShown()
|
verifySaveLoginPromptIsShown()
|
||||||
// Don't save the login
|
// Don't save the login, add to exceptions
|
||||||
saveLoginFromPrompt("Never save")
|
saveLoginFromPrompt("Never save")
|
||||||
}.openTabDrawer {
|
}.openTabDrawer {
|
||||||
}.openHomeScreen {
|
}.openHomeScreen {
|
||||||
|
@ -228,7 +229,11 @@ class SettingsPrivacyTest {
|
||||||
verifySavedLoginsView()
|
verifySavedLoginsView()
|
||||||
tapSetupLater()
|
tapSetupLater()
|
||||||
// Verify that the login list is empty
|
// Verify that the login list is empty
|
||||||
verifyNotSavedLoginFromPromt()
|
verifyNotSavedLoginFromPrompt()
|
||||||
|
}.goBack {
|
||||||
|
}.openLoginExceptions {
|
||||||
|
// Verify localhost was added to exceptions list
|
||||||
|
verifyLocalhostExceptionAdded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,8 @@ class SettingsSubMenuLoginsAndPasswordRobot {
|
||||||
mDevice.waitNotNull(Until.findObjects(By.text("On")), TestAssetHelper.waitingTime)
|
mDevice.waitNotNull(Until.findObjects(By.text("On")), TestAssetHelper.waitingTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun verifyDefaultValueExceptions() = assertDefaultValueExceptions()
|
||||||
|
|
||||||
fun verifyDefaultValueAutofillLogins() = assertDefaultValueAutofillLogins()
|
fun verifyDefaultValueAutofillLogins() = assertDefaultValueAutofillLogins()
|
||||||
|
|
||||||
fun verifyDefaultValueSyncLogins() = assertDefaultValueSyncLogins()
|
fun verifyDefaultValueSyncLogins() = assertDefaultValueSyncLogins()
|
||||||
|
@ -60,6 +62,14 @@ class SettingsSubMenuLoginsAndPasswordRobot {
|
||||||
return SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.Transition()
|
return SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.Transition()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun openLoginExceptions(interact: SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.() -> Unit): SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.Transition {
|
||||||
|
fun loginExceptionsButton() = onView(ViewMatchers.withText("Exceptions"))
|
||||||
|
loginExceptionsButton().click()
|
||||||
|
|
||||||
|
SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot().interact()
|
||||||
|
return SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.Transition()
|
||||||
|
}
|
||||||
|
|
||||||
fun openSyncLogins(interact: SettingsTurnOnSyncRobot.() -> Unit): SettingsTurnOnSyncRobot.Transition {
|
fun openSyncLogins(interact: SettingsTurnOnSyncRobot.() -> Unit): SettingsTurnOnSyncRobot.Transition {
|
||||||
fun syncLoginsButton() = onView(ViewMatchers.withText("Sync logins"))
|
fun syncLoginsButton() = onView(ViewMatchers.withText("Sync logins"))
|
||||||
syncLoginsButton().click()
|
syncLoginsButton().click()
|
||||||
|
@ -92,5 +102,8 @@ private fun assertDefaultView() = onView(ViewMatchers.withText("Sync logins"))
|
||||||
private fun assertDefaultValueAutofillLogins() = onView(ViewMatchers.withText("Autofill"))
|
private fun assertDefaultValueAutofillLogins() = onView(ViewMatchers.withText("Autofill"))
|
||||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
|
||||||
|
private fun assertDefaultValueExceptions() = onView(ViewMatchers.withText("Exceptions"))
|
||||||
|
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
|
||||||
private fun assertDefaultValueSyncLogins() = onView(ViewMatchers.withText("Sign in to Sync"))
|
private fun assertDefaultValueSyncLogins() = onView(ViewMatchers.withText("Sign in to Sync"))
|
||||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
|
|
@ -13,6 +13,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
||||||
import androidx.test.uiautomator.By
|
import androidx.test.uiautomator.By
|
||||||
import androidx.test.uiautomator.Until
|
import androidx.test.uiautomator.Until
|
||||||
import org.hamcrest.CoreMatchers
|
import org.hamcrest.CoreMatchers
|
||||||
|
import org.hamcrest.CoreMatchers.containsString
|
||||||
import org.mozilla.fenix.helpers.TestAssetHelper
|
import org.mozilla.fenix.helpers.TestAssetHelper
|
||||||
import org.mozilla.fenix.helpers.ext.waitNotNull
|
import org.mozilla.fenix.helpers.ext.waitNotNull
|
||||||
|
|
||||||
|
@ -24,16 +25,23 @@ class SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot {
|
||||||
fun verifySavedLoginsView() = assertSavedLoginsView()
|
fun verifySavedLoginsView() = assertSavedLoginsView()
|
||||||
|
|
||||||
fun verifySavedLoginsAfterSync() {
|
fun verifySavedLoginsAfterSync() {
|
||||||
mDevice.waitNotNull(Until.findObjects(By.text("https://accounts.google.com")), TestAssetHelper.waitingTime)
|
mDevice.waitNotNull(
|
||||||
|
Until.findObjects(By.text("https://accounts.google.com")),
|
||||||
|
TestAssetHelper.waitingTime
|
||||||
|
)
|
||||||
assertSavedLoginAppears()
|
assertSavedLoginAppears()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tapSetupLater() = onView(ViewMatchers.withText("Later")).perform(ViewActions.click())
|
fun tapSetupLater() = onView(ViewMatchers.withText("Later")).perform(ViewActions.click())
|
||||||
|
|
||||||
fun verifySavedLoginFromPrompt() = mDevice.waitNotNull(Until.findObjects(By.text("test@example.com")))
|
fun verifySavedLoginFromPrompt() =
|
||||||
|
mDevice.waitNotNull(Until.findObjects(By.text("test@example.com")))
|
||||||
|
|
||||||
fun verifyNotSavedLoginFromPromt() = onView(ViewMatchers.withText("test@example.com"))
|
fun verifyNotSavedLoginFromPrompt() = onView(ViewMatchers.withText("test@example.com"))
|
||||||
.check(ViewAssertions.doesNotExist())
|
.check(ViewAssertions.doesNotExist())
|
||||||
|
|
||||||
|
fun verifyLocalhostExceptionAdded() = onView(ViewMatchers.withText(containsString("localhost")))
|
||||||
|
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
|
||||||
class Transition {
|
class Transition {
|
||||||
fun goBack(interact: SettingsSubMenuLoginsAndPasswordRobot.() -> Unit): SettingsSubMenuLoginsAndPasswordRobot.Transition {
|
fun goBack(interact: SettingsSubMenuLoginsAndPasswordRobot.() -> Unit): SettingsSubMenuLoginsAndPasswordRobot.Transition {
|
||||||
|
@ -46,9 +54,10 @@ class SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun goBackButton() =
|
private fun goBackButton() =
|
||||||
onView(CoreMatchers.allOf(ViewMatchers.withContentDescription("Navigate up")))
|
onView(CoreMatchers.allOf(ViewMatchers.withContentDescription("Navigate up")))
|
||||||
|
|
||||||
private fun assertSavedLoginsView() = onView(ViewMatchers.withText("Secure your logins and passwords"))
|
private fun assertSavedLoginsView() =
|
||||||
|
onView(ViewMatchers.withText("Secure your logins and passwords"))
|
||||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
|
||||||
private fun assertSavedLoginAppears() = onView(ViewMatchers.withText("https://accounts.google.com"))
|
private fun assertSavedLoginAppears() = onView(ViewMatchers.withText("https://accounts.google.com"))
|
||||||
|
|
|
@ -21,7 +21,7 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
|
||||||
FromSyncedTabs(R.id.syncedTabsFragment),
|
FromSyncedTabs(R.id.syncedTabsFragment),
|
||||||
FromBookmarks(R.id.bookmarkFragment),
|
FromBookmarks(R.id.bookmarkFragment),
|
||||||
FromHistory(R.id.historyFragment),
|
FromHistory(R.id.historyFragment),
|
||||||
FromExceptions(R.id.exceptionsFragment),
|
FromTrackingProtectionExceptions(R.id.trackingProtectionExceptionsFragment),
|
||||||
FromAbout(R.id.aboutFragment),
|
FromAbout(R.id.aboutFragment),
|
||||||
FromTrackingProtection(R.id.trackingProtectionFragment),
|
FromTrackingProtection(R.id.trackingProtectionFragment),
|
||||||
FromSavedLoginsFragment(R.id.savedLoginsFragment),
|
FromSavedLoginsFragment(R.id.savedLoginsFragment),
|
||||||
|
|
|
@ -62,7 +62,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
|
||||||
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
|
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
|
||||||
import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder
|
import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder
|
||||||
import org.mozilla.fenix.components.metrics.Event
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
import org.mozilla.fenix.exceptions.ExceptionsFragmentDirections
|
import org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragmentDirections
|
||||||
import org.mozilla.fenix.ext.alreadyOnDestination
|
import org.mozilla.fenix.ext.alreadyOnDestination
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.nav
|
import org.mozilla.fenix.ext.nav
|
||||||
|
@ -498,8 +498,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
|
||||||
BookmarkFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
BookmarkFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
||||||
BrowserDirection.FromHistory ->
|
BrowserDirection.FromHistory ->
|
||||||
HistoryFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
HistoryFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
||||||
BrowserDirection.FromExceptions ->
|
BrowserDirection.FromTrackingProtectionExceptions ->
|
||||||
ExceptionsFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
TrackingProtectionExceptionsFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
||||||
BrowserDirection.FromAbout ->
|
BrowserDirection.FromAbout ->
|
||||||
AboutFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
AboutFragmentDirections.actionGlobalBrowser(customTabSessionId)
|
||||||
BrowserDirection.FromTrackingProtection ->
|
BrowserDirection.FromTrackingProtection ->
|
||||||
|
|
|
@ -443,6 +443,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
|
||||||
isSaveLoginEnabled = {
|
isSaveLoginEnabled = {
|
||||||
context.settings().shouldPromptToSaveLogins
|
context.settings().shouldPromptToSaveLogins
|
||||||
},
|
},
|
||||||
|
loginExceptionStorage = context.components.core.loginExceptionStorage,
|
||||||
shareDelegate = object : ShareDelegate {
|
shareDelegate = object : ShareDelegate {
|
||||||
override fun showShareSheet(
|
override fun showShareSheet(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
|
|
@ -30,6 +30,7 @@ import mozilla.components.concept.engine.mediaquery.PreferredColorScheme
|
||||||
import mozilla.components.concept.fetch.Client
|
import mozilla.components.concept.fetch.Client
|
||||||
import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
|
import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
|
||||||
import mozilla.components.feature.downloads.DownloadMiddleware
|
import mozilla.components.feature.downloads.DownloadMiddleware
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginExceptionStorage
|
||||||
import mozilla.components.feature.media.RecordingDevicesNotificationFeature
|
import mozilla.components.feature.media.RecordingDevicesNotificationFeature
|
||||||
import mozilla.components.feature.media.middleware.MediaMiddleware
|
import mozilla.components.feature.media.middleware.MediaMiddleware
|
||||||
import mozilla.components.feature.pwa.ManifestStorage
|
import mozilla.components.feature.pwa.ManifestStorage
|
||||||
|
@ -251,6 +252,8 @@ class Core(private val context: Context) {
|
||||||
|
|
||||||
val webAppManifestStorage by lazy { ManifestStorage(context) }
|
val webAppManifestStorage by lazy { ManifestStorage(context) }
|
||||||
|
|
||||||
|
val loginExceptionStorage by lazy { LoginExceptionStorage(context) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared Preferences that encrypt/decrypt using Android KeyStore and lib-dataprotect for 23+
|
* Shared Preferences that encrypt/decrypt using Android KeyStore and lib-dataprotect for 23+
|
||||||
* only on Nightly/Debug for now, otherwise simply stored.
|
* only on Nightly/Debug for now, otherwise simply stored.
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/* 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.loginexceptions
|
||||||
|
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
|
import mozilla.components.lib.state.Action
|
||||||
|
import mozilla.components.lib.state.State
|
||||||
|
import mozilla.components.lib.state.Store
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [Store] for holding the [ExceptionsFragmentState] and applying [ExceptionsFragmentAction]s.
|
||||||
|
*/
|
||||||
|
class ExceptionsFragmentStore(initialState: ExceptionsFragmentState) :
|
||||||
|
Store<ExceptionsFragmentState, ExceptionsFragmentAction>(initialState, ::exceptionsStateReducer)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions to dispatch through the `ExceptionsStore` to modify `ExceptionsState` through the reducer.
|
||||||
|
*/
|
||||||
|
sealed class ExceptionsFragmentAction : Action {
|
||||||
|
data class Change(val list: List<LoginException>) : ExceptionsFragmentAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state for the Exceptions Screen
|
||||||
|
* @property items List of exceptions to display
|
||||||
|
*/
|
||||||
|
data class ExceptionsFragmentState(val items: List<LoginException>) : State
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ExceptionsState Reducer.
|
||||||
|
*/
|
||||||
|
private fun exceptionsStateReducer(
|
||||||
|
state: ExceptionsFragmentState,
|
||||||
|
action: ExceptionsFragmentAction
|
||||||
|
): ExceptionsFragmentState {
|
||||||
|
return when (action) {
|
||||||
|
is ExceptionsFragmentAction.Change -> state.copy(items = action.list)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
/* 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.loginexceptions
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
|
import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsDeleteButtonViewHolder
|
||||||
|
import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsHeaderViewHolder
|
||||||
|
import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsListItemViewHolder
|
||||||
|
|
||||||
|
sealed class AdapterItem {
|
||||||
|
object DeleteButton : AdapterItem()
|
||||||
|
object Header : AdapterItem()
|
||||||
|
data class Item(val item: LoginException) : AdapterItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter for a list of sites that are exempted from saving logins,
|
||||||
|
* along with controls to remove the exception.
|
||||||
|
*/
|
||||||
|
class LoginExceptionsAdapter(
|
||||||
|
private val interactor: LoginExceptionsInteractor
|
||||||
|
) : ListAdapter<AdapterItem, RecyclerView.ViewHolder>(DiffCallback) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the list of items that are displayed.
|
||||||
|
* Header and footer items are added to the list as well.
|
||||||
|
*/
|
||||||
|
fun updateData(exceptions: List<LoginException>) {
|
||||||
|
val adapterItems: List<AdapterItem> =
|
||||||
|
listOf(AdapterItem.Header) + exceptions.map { AdapterItem.Item(it) } + listOf(
|
||||||
|
AdapterItem.DeleteButton
|
||||||
|
)
|
||||||
|
submitList(adapterItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int) = when (getItem(position)) {
|
||||||
|
AdapterItem.DeleteButton -> LoginExceptionsDeleteButtonViewHolder.LAYOUT_ID
|
||||||
|
AdapterItem.Header -> LoginExceptionsHeaderViewHolder.LAYOUT_ID
|
||||||
|
is AdapterItem.Item -> LoginExceptionsListItemViewHolder.LAYOUT_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||||
|
|
||||||
|
return when (viewType) {
|
||||||
|
LoginExceptionsDeleteButtonViewHolder.LAYOUT_ID -> LoginExceptionsDeleteButtonViewHolder(
|
||||||
|
view,
|
||||||
|
interactor
|
||||||
|
)
|
||||||
|
LoginExceptionsHeaderViewHolder.LAYOUT_ID -> LoginExceptionsHeaderViewHolder(view)
|
||||||
|
LoginExceptionsListItemViewHolder.LAYOUT_ID -> LoginExceptionsListItemViewHolder(
|
||||||
|
view,
|
||||||
|
interactor
|
||||||
|
)
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
if (holder is LoginExceptionsListItemViewHolder) {
|
||||||
|
val adapterItem = getItem(position) as AdapterItem.Item
|
||||||
|
holder.bind(adapterItem.item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object DiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
|
||||||
|
override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
||||||
|
areContentsTheSame(oldItem, newItem)
|
||||||
|
|
||||||
|
@Suppress("DiffUtilEquals")
|
||||||
|
override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
||||||
|
oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
/* 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.loginexceptions
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.android.synthetic.main.fragment_exceptions.view.*
|
||||||
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
|
import mozilla.components.lib.state.ext.consumeFrom
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.components.StoreProvider
|
||||||
|
import org.mozilla.fenix.ext.requireComponents
|
||||||
|
import org.mozilla.fenix.ext.showToolbar
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a list of sites that are exempted from saving logins,
|
||||||
|
* along with controls to remove the exception.
|
||||||
|
*/
|
||||||
|
class LoginExceptionsFragment : Fragment() {
|
||||||
|
private lateinit var exceptionsStore: ExceptionsFragmentStore
|
||||||
|
private lateinit var exceptionsView: LoginExceptionsView
|
||||||
|
private lateinit var exceptionsInteractor: LoginExceptionsInteractor
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
showToolbar(getString(R.string.preference_exceptions))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val view = inflater.inflate(R.layout.fragment_exceptions, container, false)
|
||||||
|
exceptionsStore = StoreProvider.get(this) {
|
||||||
|
ExceptionsFragmentStore(
|
||||||
|
ExceptionsFragmentState(
|
||||||
|
items = listOf()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
exceptionsInteractor =
|
||||||
|
LoginExceptionsInteractor(::deleteOneItem, ::deleteAllItems)
|
||||||
|
exceptionsView = LoginExceptionsView(view.exceptionsLayout, exceptionsInteractor)
|
||||||
|
subscribeToLoginExceptions()
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun subscribeToLoginExceptions(): Observer<List<LoginException>> {
|
||||||
|
return Observer<List<LoginException>> { exceptions ->
|
||||||
|
exceptionsStore.dispatch(ExceptionsFragmentAction.Change(exceptions))
|
||||||
|
}.also { observer ->
|
||||||
|
requireComponents.core.loginExceptionStorage.getLoginExceptions().asLiveData()
|
||||||
|
.observe(viewLifecycleOwner, observer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
consumeFrom(exceptionsStore) {
|
||||||
|
exceptionsView.update(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteAllItems() {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch(IO) {
|
||||||
|
requireComponents.core.loginExceptionStorage.deleteAllLoginExceptions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteOneItem(item: LoginException) {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch(IO) {
|
||||||
|
requireComponents.core.loginExceptionStorage.removeLoginException(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* 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.loginexceptions
|
||||||
|
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interactor for the exceptions screen
|
||||||
|
* Provides implementations for the ExceptionsViewInteractor
|
||||||
|
*/
|
||||||
|
class LoginExceptionsInteractor(
|
||||||
|
private val deleteOne: (LoginException) -> Unit,
|
||||||
|
private val deleteAll: () -> Unit
|
||||||
|
) : ExceptionsViewInteractor {
|
||||||
|
override fun onDeleteAll() {
|
||||||
|
deleteAll.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleteOne(item: LoginException) {
|
||||||
|
deleteOne.invoke(item)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* 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.loginexceptions
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import kotlinx.android.extensions.LayoutContainer
|
||||||
|
import kotlinx.android.synthetic.main.component_exceptions.view.*
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for the ExceptionsViewInteractor. This interface is implemented by objects that want
|
||||||
|
* to respond to user interaction on the ExceptionsView
|
||||||
|
*/
|
||||||
|
interface ExceptionsViewInteractor {
|
||||||
|
/**
|
||||||
|
* Called whenever all exception items are deleted
|
||||||
|
*/
|
||||||
|
fun onDeleteAll()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever one exception item is deleted
|
||||||
|
*/
|
||||||
|
fun onDeleteOne(item: LoginException)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View that contains and configures the Exceptions List
|
||||||
|
*/
|
||||||
|
class LoginExceptionsView(
|
||||||
|
override val containerView: ViewGroup,
|
||||||
|
val interactor: LoginExceptionsInteractor
|
||||||
|
) : LayoutContainer {
|
||||||
|
|
||||||
|
val view: FrameLayout = LayoutInflater.from(containerView.context)
|
||||||
|
.inflate(R.layout.component_exceptions, containerView, true)
|
||||||
|
.findViewById(R.id.exceptions_wrapper)
|
||||||
|
|
||||||
|
private val exceptionsAdapter = LoginExceptionsAdapter(interactor)
|
||||||
|
|
||||||
|
init {
|
||||||
|
view.exceptions_learn_more.isVisible = false
|
||||||
|
view.exceptions_empty_message.text =
|
||||||
|
view.context.getString(R.string.preferences_passwords_exceptions_description_empty)
|
||||||
|
view.exceptions_list.apply {
|
||||||
|
adapter = exceptionsAdapter
|
||||||
|
layoutManager = LinearLayoutManager(containerView.context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(state: ExceptionsFragmentState) {
|
||||||
|
view.exceptions_empty_view.isVisible = state.items.isEmpty()
|
||||||
|
view.exceptions_list.isVisible = state.items.isNotEmpty()
|
||||||
|
exceptionsAdapter.updateData(state.items)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* 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.loginexceptions.viewholders
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.android.synthetic.main.delete_exceptions_button.view.*
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.loginexceptions.LoginExceptionsInteractor
|
||||||
|
|
||||||
|
class LoginExceptionsDeleteButtonViewHolder(
|
||||||
|
view: View,
|
||||||
|
private val interactor: LoginExceptionsInteractor
|
||||||
|
) : RecyclerView.ViewHolder(view) {
|
||||||
|
private val deleteButton = view.removeAllExceptions
|
||||||
|
|
||||||
|
init {
|
||||||
|
deleteButton.setOnClickListener {
|
||||||
|
interactor.onDeleteAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.delete_logins_exceptions_button
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/* 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.loginexceptions.viewholders
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.android.synthetic.main.exceptions_description.view.*
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
|
||||||
|
class LoginExceptionsHeaderViewHolder(
|
||||||
|
view: View
|
||||||
|
) : RecyclerView.ViewHolder(view) {
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.exceptions_description
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
view.exceptions_description.text =
|
||||||
|
view.context.getString(R.string.preferences_passwords_exceptions_description)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/* 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.loginexceptions.viewholders
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.android.synthetic.main.exception_item.view.*
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import org.mozilla.fenix.ext.loadIntoView
|
||||||
|
import org.mozilla.fenix.loginexceptions.LoginExceptionsInteractor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View holder for a single website that is exempted from Tracking Protection.
|
||||||
|
*/
|
||||||
|
class LoginExceptionsListItemViewHolder(
|
||||||
|
view: View,
|
||||||
|
private val interactor: LoginExceptionsInteractor
|
||||||
|
) : RecyclerView.ViewHolder(view) {
|
||||||
|
|
||||||
|
private val favicon = view.favicon_image
|
||||||
|
private val url = view.webAddressView
|
||||||
|
private val deleteButton = view.delete_exception
|
||||||
|
|
||||||
|
private var item: LoginException? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
deleteButton.setOnClickListener {
|
||||||
|
item?.let {
|
||||||
|
interactor.onDeleteOne(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(item: LoginException) {
|
||||||
|
this.item = item
|
||||||
|
url.text = item.origin
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateFavIcon(url: String) {
|
||||||
|
favicon.context.components.core.icons.loadIntoView(favicon, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.exception_item
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ import org.mozilla.fenix.utils.view.addToRadioGroup
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the toggle for tracking protection, options for tracking protection policy and a button
|
* Displays the toggle for tracking protection, options for tracking protection policy and a button
|
||||||
* to open info about the tracking protection [org.mozilla.fenix.exceptions.ExceptionsFragment].
|
* to open info about the tracking protection [org.mozilla.fenix.settings.TrackingProtectionFragment].
|
||||||
*/
|
*/
|
||||||
class TrackingProtectionFragment : PreferenceFragmentCompat() {
|
class TrackingProtectionFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
|
@ -56,7 +56,8 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() {
|
||||||
showToolbar(getString(R.string.preference_enhanced_tracking_protection))
|
showToolbar(getString(R.string.preference_enhanced_tracking_protection))
|
||||||
|
|
||||||
// Tracking Protection Switch
|
// Tracking Protection Switch
|
||||||
val preferenceTP = requirePreference<SwitchPreference>(R.string.pref_key_tracking_protection)
|
val preferenceTP =
|
||||||
|
requirePreference<SwitchPreference>(R.string.pref_key_tracking_protection)
|
||||||
|
|
||||||
preferenceTP.isChecked = requireContext().settings().shouldUseTrackingProtection
|
preferenceTP.isChecked = requireContext().settings().shouldUseTrackingProtection
|
||||||
preferenceTP.setOnPreferenceChangeListener<Boolean> { preference, trackingProtectionOn ->
|
preferenceTP.setOnPreferenceChangeListener<Boolean> { preference, trackingProtectionOn ->
|
||||||
|
@ -86,7 +87,8 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() {
|
||||||
getString(R.string.app_name)
|
getString(R.string.app_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
val preferenceExceptions = requirePreference<Preference>(R.string.pref_key_tracking_protection_exceptions)
|
val preferenceExceptions =
|
||||||
|
requirePreference<Preference>(R.string.pref_key_tracking_protection_exceptions)
|
||||||
preferenceExceptions.onPreferenceClickListener = exceptionsClickListener
|
preferenceExceptions.onPreferenceClickListener = exceptionsClickListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,6 +133,13 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requirePreference<Preference>(R.string.pref_key_login_exceptions).apply {
|
||||||
|
setOnPreferenceClickListener {
|
||||||
|
navigateToLoginExceptionFragment()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
requirePreference<SwitchPreference>(R.string.pref_key_autofill_logins).apply {
|
requirePreference<SwitchPreference>(R.string.pref_key_autofill_logins).apply {
|
||||||
isChecked = context.settings().shouldAutofillLogins
|
isChecked = context.settings().shouldAutofillLogins
|
||||||
onPreferenceChangeListener = object : SharedPreferenceUpdater() {
|
onPreferenceChangeListener = object : SharedPreferenceUpdater() {
|
||||||
|
@ -322,6 +329,12 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver {
|
||||||
findNavController().navigate(directions)
|
findNavController().navigate(directions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun navigateToLoginExceptionFragment() {
|
||||||
|
val directions =
|
||||||
|
SavedLoginsAuthFragmentDirections.actionSavedLoginsAuthFragmentToLoginExceptionsFragment()
|
||||||
|
findNavController().navigate(directions)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val SHORT_DELAY_MS = 100L
|
const val SHORT_DELAY_MS = 100L
|
||||||
private const val LOG_TAG = "LoginsFragment"
|
private const val LOG_TAG = "LoginsFragment"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* 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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions
|
package org.mozilla.fenix.trackingprotectionexceptions
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -10,9 +10,9 @@ import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
||||||
import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder
|
import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsDeleteButtonViewHolder
|
||||||
import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder
|
import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsHeaderViewHolder
|
||||||
import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder
|
import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsListItemViewHolder
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter for a list of sites that are exempted from Tracking Protection,
|
* Adapter for a list of sites that are exempted from Tracking Protection,
|
|
@ -2,7 +2,7 @@
|
||||||
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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions
|
package org.mozilla.fenix.trackingprotectionexceptions
|
||||||
|
|
||||||
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
||||||
import mozilla.components.lib.state.Action
|
import mozilla.components.lib.state.Action
|
|
@ -2,7 +2,7 @@
|
||||||
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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions
|
package org.mozilla.fenix.trackingprotectionexceptions
|
||||||
|
|
||||||
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* 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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions
|
package org.mozilla.fenix.trackingprotectionexceptions
|
||||||
|
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
import android.text.style.UnderlineSpan
|
import android.text.style.UnderlineSpan
|
||||||
|
@ -50,7 +50,10 @@ class ExceptionsView(
|
||||||
.inflate(R.layout.component_exceptions, container, true)
|
.inflate(R.layout.component_exceptions, container, true)
|
||||||
.findViewById(R.id.exceptions_wrapper)
|
.findViewById(R.id.exceptions_wrapper)
|
||||||
|
|
||||||
private val exceptionsAdapter = ExceptionsAdapter(interactor)
|
private val exceptionsAdapter =
|
||||||
|
ExceptionsAdapter(
|
||||||
|
interactor
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
exceptions_list.apply {
|
exceptions_list.apply {
|
|
@ -2,7 +2,7 @@
|
||||||
* 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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions
|
package org.mozilla.fenix.trackingprotectionexceptions
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -27,7 +27,7 @@ import org.mozilla.fenix.settings.SupportUtils
|
||||||
* Displays a list of sites that are exempted from Tracking Protection,
|
* Displays a list of sites that are exempted from Tracking Protection,
|
||||||
* along with controls to remove the exception.
|
* along with controls to remove the exception.
|
||||||
*/
|
*/
|
||||||
class ExceptionsFragment : Fragment() {
|
class TrackingProtectionExceptionsFragment : Fragment() {
|
||||||
|
|
||||||
private lateinit var exceptionsStore: ExceptionsFragmentStore
|
private lateinit var exceptionsStore: ExceptionsFragmentStore
|
||||||
private lateinit var exceptionsView: ExceptionsView
|
private lateinit var exceptionsView: ExceptionsView
|
||||||
|
@ -54,8 +54,16 @@ class ExceptionsFragment : Fragment() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
exceptionsInteractor =
|
exceptionsInteractor =
|
||||||
ExceptionsInteractor(::openLearnMore, ::deleteOneItem, ::deleteAllItems)
|
ExceptionsInteractor(
|
||||||
exceptionsView = ExceptionsView(view.exceptionsLayout, exceptionsInteractor)
|
::openLearnMore,
|
||||||
|
::deleteOneItem,
|
||||||
|
::deleteAllItems
|
||||||
|
)
|
||||||
|
exceptionsView =
|
||||||
|
ExceptionsView(
|
||||||
|
view.exceptionsLayout,
|
||||||
|
exceptionsInteractor
|
||||||
|
)
|
||||||
reloadExceptions()
|
reloadExceptions()
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
@ -83,13 +91,17 @@ class ExceptionsFragment : Fragment() {
|
||||||
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
|
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
|
||||||
(SupportUtils.SumoTopic.TRACKING_PROTECTION),
|
(SupportUtils.SumoTopic.TRACKING_PROTECTION),
|
||||||
newTab = true,
|
newTab = true,
|
||||||
from = BrowserDirection.FromExceptions
|
from = BrowserDirection.FromTrackingProtectionExceptions
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reloadExceptions() {
|
private fun reloadExceptions() {
|
||||||
trackingProtectionUseCases.fetchExceptions { resultList ->
|
trackingProtectionUseCases.fetchExceptions { resultList ->
|
||||||
exceptionsStore.dispatch(ExceptionsFragmentAction.Change(resultList))
|
exceptionsStore.dispatch(
|
||||||
|
ExceptionsFragmentAction.Change(
|
||||||
|
resultList
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,13 +2,13 @@
|
||||||
* 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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions.viewholders
|
package org.mozilla.fenix.trackingprotectionexceptions.viewholders
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlinx.android.synthetic.main.delete_exceptions_button.view.*
|
import kotlinx.android.synthetic.main.delete_exceptions_button.view.*
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.exceptions.ExceptionsInteractor
|
import org.mozilla.fenix.trackingprotectionexceptions.ExceptionsInteractor
|
||||||
|
|
||||||
class ExceptionsDeleteButtonViewHolder(
|
class ExceptionsDeleteButtonViewHolder(
|
||||||
view: View,
|
view: View,
|
|
@ -2,7 +2,7 @@
|
||||||
* 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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions.viewholders
|
package org.mozilla.fenix.trackingprotectionexceptions.viewholders
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
|
@ -2,14 +2,14 @@
|
||||||
* 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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions.viewholders
|
package org.mozilla.fenix.trackingprotectionexceptions.viewholders
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlinx.android.synthetic.main.exception_item.view.*
|
import kotlinx.android.synthetic.main.exception_item.view.*
|
||||||
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.exceptions.ExceptionsInteractor
|
import org.mozilla.fenix.trackingprotectionexceptions.ExceptionsInteractor
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.loadIntoView
|
import org.mozilla.fenix.ext.loadIntoView
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?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/. -->
|
||||||
|
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/removeAllExceptions"
|
||||||
|
style="@style/DestructiveButton"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:text="@string/preferences_passwords_exceptions_remove_all" />
|
|
@ -3,10 +3,7 @@
|
||||||
- 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/. -->
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/exceptionsLayout"
|
android:id="@+id/exceptionsLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"/>
|
||||||
tools:context="org.mozilla.fenix.exceptions.ExceptionsFragment">
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
|
@ -27,35 +27,83 @@
|
||||||
android:id="@+id/action_global_search"
|
android:id="@+id/action_global_search"
|
||||||
app:destination="@id/searchFragment" />
|
app:destination="@id/searchFragment" />
|
||||||
|
|
||||||
<action android:id="@+id/action_global_shareFragment" app:destination="@id/shareFragment" />
|
<action
|
||||||
<action android:id="@+id/action_global_crash_reporter" app:destination="@id/crashReporterFragment" />
|
android:id="@+id/action_global_shareFragment"
|
||||||
<action android:id="@+id/action_global_turn_on_sync" app:destination="@id/turnOnSyncFragment" />
|
app:destination="@id/shareFragment" />
|
||||||
<action android:id="@+id/action_global_settings_addonsManagementFragment" app:destination="@id/addonsManagementFragment" />
|
<action
|
||||||
<action android:id="@+id/action_global_searchEngineFragment" app:destination="@id/searchEngineFragment" />
|
android:id="@+id/action_global_crash_reporter"
|
||||||
<action android:id="@+id/action_global_accessibilityFragment" app:destination="@id/accessibilityFragment" />
|
app:destination="@id/crashReporterFragment" />
|
||||||
<action android:id="@+id/action_global_deleteBrowsingDataFragment" app:destination="@id/deleteBrowsingDataFragment" />
|
<action
|
||||||
<action android:id="@+id/action_global_webExtensionActionPopupFragment" app:destination="@id/webExtensionActionPopupFragment" />
|
android:id="@+id/action_global_turn_on_sync"
|
||||||
<action android:id="@+id/action_global_settingsFragment" app:destination="@id/settingsFragment" />
|
app:destination="@id/turnOnSyncFragment" />
|
||||||
<action android:id="@+id/action_global_syncedTabsFragment" app:destination="@+id/syncedTabsFragment" />
|
<action
|
||||||
<action android:id="@+id/action_global_privateBrowsingFragment" app:destination="@id/privateBrowsingFragment"/>
|
android:id="@+id/action_global_settings_addonsManagementFragment"
|
||||||
<action android:id="@+id/action_global_bookmarkFragment" app:destination="@id/bookmarkFragment"/>
|
app:destination="@id/addonsManagementFragment" />
|
||||||
<action android:id="@+id/action_global_historyFragment" app:destination="@id/historyFragment"/>
|
<action
|
||||||
<action android:id="@+id/action_global_accountProblemFragment" app:destination="@id/accountProblemFragment"/>
|
android:id="@+id/action_global_searchEngineFragment"
|
||||||
<action android:id="@+id/action_global_SitePermissionsManagePhoneFeature" app:destination="@id/SitePermissionsManagePhoneFeature" />
|
app:destination="@id/searchEngineFragment" />
|
||||||
<action android:id="@+id/action_global_collectionCreationFragment" app:destination="@id/collectionCreationFragment" />
|
<action
|
||||||
<action android:id="@+id/action_global_bookmarkEditFragment" app:destination="@id/bookmarkEditFragment" />
|
android:id="@+id/action_global_accessibilityFragment"
|
||||||
<action android:id="@+id/action_global_addonsManagementFragment" app:destination="@id/addonsManagementFragment" />
|
app:destination="@id/accessibilityFragment" />
|
||||||
<action android:id="@+id/action_global_trackingProtectionFragment" app:destination="@id/trackingProtectionFragment" />
|
<action
|
||||||
<action android:id="@+id/action_global_exceptionsFragment" app:destination="@id/exceptionsFragment" />
|
android:id="@+id/action_global_deleteBrowsingDataFragment"
|
||||||
<action android:id="@+id/action_global_accountSettingsFragment" app:destination="@id/accountSettingsFragment" />
|
app:destination="@id/deleteBrowsingDataFragment" />
|
||||||
<action android:id="@+id/action_global_trackingProtectionPanelDialogFragment" app:destination="@id/trackingProtectionPanelDialogFragment" />
|
<action
|
||||||
<action android:id="@+id/action_global_quickSettingsSheetDialogFragment" app:destination="@id/quickSettingsSheetDialogFragment"/>
|
android:id="@+id/action_global_webExtensionActionPopupFragment"
|
||||||
<action android:id="@+id/action_global_tabTrayDialogFragment" app:destination="@id/tabTrayDialogFragment"/>
|
app:destination="@id/webExtensionActionPopupFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_settingsFragment"
|
||||||
|
app:destination="@id/settingsFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_syncedTabsFragment"
|
||||||
|
app:destination="@+id/syncedTabsFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_privateBrowsingFragment"
|
||||||
|
app:destination="@id/privateBrowsingFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_bookmarkFragment"
|
||||||
|
app:destination="@id/bookmarkFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_historyFragment"
|
||||||
|
app:destination="@id/historyFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_accountProblemFragment"
|
||||||
|
app:destination="@id/accountProblemFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_SitePermissionsManagePhoneFeature"
|
||||||
|
app:destination="@id/SitePermissionsManagePhoneFeature" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_collectionCreationFragment"
|
||||||
|
app:destination="@id/collectionCreationFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_bookmarkEditFragment"
|
||||||
|
app:destination="@id/bookmarkEditFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_addonsManagementFragment"
|
||||||
|
app:destination="@id/addonsManagementFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_trackingProtectionFragment"
|
||||||
|
app:destination="@id/trackingProtectionFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_trackingProtectionExceptionsFragment"
|
||||||
|
app:destination="@id/trackingProtectionExceptionsFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_accountSettingsFragment"
|
||||||
|
app:destination="@id/accountSettingsFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_trackingProtectionPanelDialogFragment"
|
||||||
|
app:destination="@id/trackingProtectionPanelDialogFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_quickSettingsSheetDialogFragment"
|
||||||
|
app:destination="@id/quickSettingsSheetDialogFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_tabTrayDialogFragment"
|
||||||
|
app:destination="@id/tabTrayDialogFragment" />
|
||||||
|
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/tabTrayDialogFragment"
|
android:id="@+id/tabTrayDialogFragment"
|
||||||
android:name="org.mozilla.fenix.tabtray.TabTrayDialogFragment"
|
android:name="org.mozilla.fenix.tabtray.TabTrayDialogFragment"
|
||||||
tools:layout="@layout/fragment_tab_tray_dialog"/>
|
tools:layout="@layout/fragment_tab_tray_dialog" />
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/homeFragment"
|
android:id="@+id/homeFragment"
|
||||||
|
@ -73,9 +121,9 @@
|
||||||
app:argType="boolean" />
|
app:argType="boolean" />
|
||||||
<argument
|
<argument
|
||||||
android:name="session_to_delete"
|
android:name="session_to_delete"
|
||||||
|
android:defaultValue="@null"
|
||||||
app:argType="string"
|
app:argType="string"
|
||||||
app:nullable="true"
|
app:nullable="true" />
|
||||||
android:defaultValue="@null" />
|
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
|
@ -93,8 +141,8 @@
|
||||||
app:nullable="true" />
|
app:nullable="true" />
|
||||||
<argument
|
<argument
|
||||||
android:name="search_access_point"
|
android:name="search_access_point"
|
||||||
app:argType="org.mozilla.fenix.components.metrics.Event$PerformedSearch$SearchAccessPoint"
|
android:defaultValue="NONE"
|
||||||
android:defaultValue="NONE" />
|
app:argType="org.mozilla.fenix.components.metrics.Event$PerformedSearch$SearchAccessPoint" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
|
@ -149,17 +197,17 @@
|
||||||
tools:layout="@layout/fragment_browser">
|
tools:layout="@layout/fragment_browser">
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_browserFragment_to_searchFragment"
|
android:id="@+id/action_browserFragment_to_searchFragment"
|
||||||
|
app:destination="@id/searchFragment"
|
||||||
app:enterAnim="@anim/fade_in_up"
|
app:enterAnim="@anim/fade_in_up"
|
||||||
app:popExitAnim="@anim/fade_out_down"
|
app:popExitAnim="@anim/fade_out_down" />
|
||||||
app:destination="@id/searchFragment" />
|
|
||||||
<argument
|
<argument
|
||||||
android:name="activeSessionId"
|
android:name="activeSessionId"
|
||||||
app:argType="string"
|
app:argType="string"
|
||||||
app:nullable="true" />
|
app:nullable="true" />
|
||||||
<argument
|
<argument
|
||||||
android:name="shouldAnimate"
|
android:name="shouldAnimate"
|
||||||
app:argType="boolean"
|
android:defaultValue="false"
|
||||||
android:defaultValue="false" />
|
app:argType="boolean" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_browserFragment_to_syncedTabsFragment"
|
android:id="@+id/action_browserFragment_to_syncedTabsFragment"
|
||||||
app:destination="@id/syncedTabsFragment" />
|
app:destination="@id/syncedTabsFragment" />
|
||||||
|
@ -234,8 +282,8 @@
|
||||||
app:destination="@id/bookmarkSelectFolderFragment" />
|
app:destination="@id/bookmarkSelectFolderFragment" />
|
||||||
<argument
|
<argument
|
||||||
android:name="requiresSnackbarPaddingForToolbar"
|
android:name="requiresSnackbarPaddingForToolbar"
|
||||||
app:argType="boolean"
|
android:defaultValue="false"
|
||||||
android:defaultValue="false" />
|
app:argType="boolean" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
|
@ -274,25 +322,32 @@
|
||||||
android:label="@string/preferences_passwords_logins_and_passwords">
|
android:label="@string/preferences_passwords_logins_and_passwords">
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_savedLoginsAuthFragment_to_loginsListFragment"
|
android:id="@+id/action_savedLoginsAuthFragment_to_loginsListFragment"
|
||||||
|
app:destination="@id/savedLoginsFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/savedLoginsFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_savedLoginsAuthFragment_to_turnOnSyncFragment"
|
android:id="@+id/action_savedLoginsAuthFragment_to_turnOnSyncFragment"
|
||||||
|
app:destination="@id/turnOnSyncFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/turnOnSyncFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_savedLoginsAuthFragment_to_savedLoginsSettingFragment"
|
android:id="@+id/action_savedLoginsAuthFragment_to_savedLoginsSettingFragment"
|
||||||
|
app:destination="@id/saveLoginSettingFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/saveLoginSettingFragment" />
|
<action
|
||||||
|
android:id="@+id/action_savedLoginsAuthFragment_to_loginExceptionsFragment"
|
||||||
|
app:destination="@id/loginExceptionsFragment"
|
||||||
|
app:enterAnim="@anim/slide_in_right"
|
||||||
|
app:exitAnim="@anim/slide_out_left"
|
||||||
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
|
@ -313,8 +368,13 @@
|
||||||
android:id="@+id/syncedTabsFragment"
|
android:id="@+id/syncedTabsFragment"
|
||||||
android:name="org.mozilla.fenix.sync.SyncedTabsFragment"
|
android:name="org.mozilla.fenix.sync.SyncedTabsFragment"
|
||||||
android:label="@string/synced_tabs"
|
android:label="@string/synced_tabs"
|
||||||
tools:layout="@layout/fragment_synced_tabs">
|
tools:layout="@layout/fragment_synced_tabs"/>
|
||||||
</fragment>
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/loginExceptionsFragment"
|
||||||
|
android:name="org.mozilla.fenix.loginexceptions.LoginExceptionsFragment"
|
||||||
|
android:label="@string/preferences_passwords_exceptions"
|
||||||
|
tools:layout="@layout/fragment_exceptions"/>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/loginDetailFragment"
|
android:id="@+id/loginDetailFragment"
|
||||||
|
@ -323,12 +383,12 @@
|
||||||
<argument
|
<argument
|
||||||
android:name="savedLoginId"
|
android:name="savedLoginId"
|
||||||
app:argType="string"
|
app:argType="string"
|
||||||
app:nullable="false"/>
|
app:nullable="false" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_loginDetailFragment_to_editLoginFragment"
|
android:id="@+id/action_loginDetailFragment_to_editLoginFragment"
|
||||||
app:destination="@id/editLoginFragment"
|
app:destination="@id/editLoginFragment"
|
||||||
app:popUpTo="@id/editLoginFragment"
|
app:popUpTo="@id/editLoginFragment"
|
||||||
app:popUpToInclusive="true"/>
|
app:popUpToInclusive="true" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
|
@ -338,12 +398,12 @@
|
||||||
<argument
|
<argument
|
||||||
android:name="savedLoginItem"
|
android:name="savedLoginItem"
|
||||||
app:argType="org.mozilla.fenix.settings.logins.SavedLogin"
|
app:argType="org.mozilla.fenix.settings.logins.SavedLogin"
|
||||||
app:nullable="false"/>
|
app:nullable="false" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_editLoginFragment_to_loginDetailFragment"
|
android:id="@+id/action_editLoginFragment_to_loginDetailFragment"
|
||||||
app:destination="@id/loginDetailFragment"
|
app:destination="@id/loginDetailFragment"
|
||||||
app:popUpTo="@id/loginDetailFragment"
|
app:popUpTo="@id/loginDetailFragment"
|
||||||
app:popUpToInclusive="true"/>
|
app:popUpToInclusive="true" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
|
@ -352,124 +412,124 @@
|
||||||
android:label="@string/settings_title">
|
android:label="@string/settings_title">
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_dataChoicesFragment"
|
android:id="@+id/action_settingsFragment_to_dataChoicesFragment"
|
||||||
|
app:destination="@id/dataChoicesFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right"
|
||||||
app:destination="@id/dataChoicesFragment"
|
|
||||||
app:popUpTo="@+id/settingsFragment" />
|
app:popUpTo="@+id/settingsFragment" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_sitePermissionsFragment"
|
android:id="@+id/action_settingsFragment_to_sitePermissionsFragment"
|
||||||
|
app:destination="@id/sitePermissionsFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/sitePermissionsFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_savedLoginsAuthFragment"
|
android:id="@+id/action_settingsFragment_to_savedLoginsAuthFragment"
|
||||||
|
app:destination="@id/savedLoginsAuthFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/savedLoginsAuthFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_accessibilityFragment"
|
android:id="@+id/action_settingsFragment_to_accessibilityFragment"
|
||||||
|
app:destination="@id/accessibilityFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/accessibilityFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_accountSettingsFragment"
|
android:id="@+id/action_settingsFragment_to_accountSettingsFragment"
|
||||||
|
app:destination="@id/accountSettingsFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/accountSettingsFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_searchEngineFragment"
|
android:id="@+id/action_settingsFragment_to_searchEngineFragment"
|
||||||
|
app:destination="@id/searchEngineFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/searchEngineFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_turnOnSyncFragment"
|
android:id="@+id/action_settingsFragment_to_turnOnSyncFragment"
|
||||||
|
app:destination="@id/turnOnSyncFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/turnOnSyncFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_aboutFragment"
|
android:id="@+id/action_settingsFragment_to_aboutFragment"
|
||||||
|
app:destination="@id/aboutFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/aboutFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_secretSettingsFragment"
|
android:id="@+id/action_settingsFragment_to_secretSettingsFragment"
|
||||||
|
app:destination="@id/secretSettingsPreference"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/secretSettingsPreference" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_customizationFragment"
|
android:id="@+id/action_settingsFragment_to_customizationFragment"
|
||||||
|
app:destination="@id/customizationFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/customizationFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_privateBrowsingFragment"
|
android:id="@+id/action_settingsFragment_to_privateBrowsingFragment"
|
||||||
|
app:destination="@id/privateBrowsingFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/privateBrowsingFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_trackingProtectionFragment"
|
android:id="@+id/action_settingsFragment_to_trackingProtectionFragment"
|
||||||
|
app:destination="@id/trackingProtectionFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/trackingProtectionFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_deleteBrowsingDataFragment"
|
android:id="@+id/action_settingsFragment_to_deleteBrowsingDataFragment"
|
||||||
|
app:destination="@id/deleteBrowsingDataFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/deleteBrowsingDataFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_accountProblemFragment"
|
android:id="@+id/action_settingsFragment_to_accountProblemFragment"
|
||||||
|
app:destination="@id/accountProblemFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/accountProblemFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_deleteBrowsingDataOnQuitFragment"
|
android:id="@+id/action_settingsFragment_to_deleteBrowsingDataOnQuitFragment"
|
||||||
|
app:destination="@id/deleteBrowsingDataOnQuitFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/deleteBrowsingDataOnQuitFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_localeSettingsFragment"
|
android:id="@+id/action_settingsFragment_to_localeSettingsFragment"
|
||||||
|
app:destination="@id/localeSettingsFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/localeSettingsFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_settingsFragment_to_addonsFragment"
|
android:id="@+id/action_settingsFragment_to_addonsFragment"
|
||||||
|
app:destination="@id/addonsManagementFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/addonsManagementFragment" />
|
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/dataChoicesFragment"
|
android:id="@+id/dataChoicesFragment"
|
||||||
|
@ -481,19 +541,19 @@
|
||||||
android:label="@string/preferences_site_permissions">
|
android:label="@string/preferences_site_permissions">
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_site_permissions_to_manage_phone_features"
|
android:id="@+id/action_site_permissions_to_manage_phone_features"
|
||||||
|
app:destination="@id/SitePermissionsManagePhoneFeature"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right"
|
||||||
app:destination="@id/SitePermissionsManagePhoneFeature"
|
|
||||||
app:popUpTo="@id/sitePermissionsFragment" />
|
app:popUpTo="@id/sitePermissionsFragment" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_site_permissions_to_exceptions"
|
android:id="@+id/action_site_permissions_to_exceptions"
|
||||||
|
app:destination="@id/sitePermissionsExceptionsFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right"
|
||||||
app:destination="@id/sitePermissionsExceptionsFragment"
|
|
||||||
app:popUpTo="@id/sitePermissionsFragment" />
|
app:popUpTo="@id/sitePermissionsFragment" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
|
@ -541,7 +601,7 @@
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/aboutFragment"
|
android:id="@+id/aboutFragment"
|
||||||
android:name="org.mozilla.fenix.settings.about.AboutFragment"/>
|
android:name="org.mozilla.fenix.settings.about.AboutFragment" />
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/secretSettingsPreference"
|
android:id="@+id/secretSettingsPreference"
|
||||||
android:name="org.mozilla.fenix.settings.SecretSettingsFragment"
|
android:name="org.mozilla.fenix.settings.SecretSettingsFragment"
|
||||||
|
@ -566,27 +626,27 @@
|
||||||
android:name="org.mozilla.fenix.settings.TrackingProtectionFragment">
|
android:name="org.mozilla.fenix.settings.TrackingProtectionFragment">
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_trackingProtectionFragment_to_exceptionsFragment"
|
android:id="@+id/action_trackingProtectionFragment_to_exceptionsFragment"
|
||||||
|
app:destination="@id/trackingProtectionExceptionsFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/exceptionsFragment" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_trackingProtectionFragment_to_trackingProtectionBlockingFragment"
|
android:id="@+id/action_trackingProtectionFragment_to_trackingProtectionBlockingFragment"
|
||||||
|
app:destination="@id/trackingProtectionBlockingFragment"
|
||||||
app:enterAnim="@anim/slide_in_right"
|
app:enterAnim="@anim/slide_in_right"
|
||||||
app:exitAnim="@anim/slide_out_left"
|
app:exitAnim="@anim/slide_out_left"
|
||||||
app:popEnterAnim="@anim/slide_in_left"
|
app:popEnterAnim="@anim/slide_in_left"
|
||||||
app:popExitAnim="@anim/slide_out_right"
|
app:popExitAnim="@anim/slide_out_right" />
|
||||||
app:destination="@id/trackingProtectionBlockingFragment" />
|
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/deleteBrowsingDataFragment"
|
android:id="@+id/deleteBrowsingDataFragment"
|
||||||
android:name="org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataFragment"
|
android:name="org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataFragment"
|
||||||
android:label="@string/preferences_delete_browsing_data" />
|
android:label="@string/preferences_delete_browsing_data" />
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/exceptionsFragment"
|
android:id="@+id/trackingProtectionExceptionsFragment"
|
||||||
android:name="org.mozilla.fenix.exceptions.ExceptionsFragment"
|
android:name="org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragment"
|
||||||
android:label="@string/preference_exceptions"/>
|
android:label="@string/preference_exceptions" />
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/collectionCreationFragment"
|
android:id="@+id/collectionCreationFragment"
|
||||||
android:name="org.mozilla.fenix.collections.CollectionCreationFragment"
|
android:name="org.mozilla.fenix.collections.CollectionCreationFragment"
|
||||||
|
@ -795,6 +855,6 @@
|
||||||
<argument
|
<argument
|
||||||
android:name="webExtensionTitle"
|
android:name="webExtensionTitle"
|
||||||
app:argType="string"
|
app:argType="string"
|
||||||
app:nullable="true"/>
|
app:nullable="true" />
|
||||||
</fragment>
|
</fragment>
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|
|
@ -185,4 +185,6 @@
|
||||||
<string name="pref_key_search_widget_cfr_manually_dismissed" translatable="false">pref_key_search_widget_cfr_manually_dismissed</string>
|
<string name="pref_key_search_widget_cfr_manually_dismissed" translatable="false">pref_key_search_widget_cfr_manually_dismissed</string>
|
||||||
<string name="pref_key_is_in_search_widget_experiment" translatable="false">pref_key_is_in_search_widget_experiment</string>
|
<string name="pref_key_is_in_search_widget_experiment" translatable="false">pref_key_is_in_search_widget_experiment</string>
|
||||||
<string name="pref_key_show_search_widget_cfr" translatable="false">pref_key_show_search_widget_cfr</string>
|
<string name="pref_key_show_search_widget_cfr" translatable="false">pref_key_show_search_widget_cfr</string>
|
||||||
|
|
||||||
|
<string name="pref_key_login_exceptions" translatable="false">pref_key_login_exceptions</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1217,6 +1217,8 @@
|
||||||
<string name="preferences_passwords_exceptions_description_empty">Logins and passwords that are not saved will be shown here.</string>
|
<string name="preferences_passwords_exceptions_description_empty">Logins and passwords that are not saved will be shown here.</string>
|
||||||
<!-- Description of list of login exceptions that we never save logins for -->
|
<!-- Description of list of login exceptions that we never save logins for -->
|
||||||
<string name="preferences_passwords_exceptions_description">Logins and passwords will not be saved for these sites.</string>
|
<string name="preferences_passwords_exceptions_description">Logins and passwords will not be saved for these sites.</string>
|
||||||
|
<!-- Text on button to remove all saved login exceptions -->
|
||||||
|
<string name="preferences_passwords_exceptions_remove_all">Delete all exceptions</string>
|
||||||
<!-- Hint for search box in logins list -->
|
<!-- Hint for search box in logins list -->
|
||||||
<string name="preferences_passwords_saved_logins_search">Search logins</string>
|
<string name="preferences_passwords_saved_logins_search">Search logins</string>
|
||||||
<!-- Option to sort logins list A-Z, alphabetically -->
|
<!-- Option to sort logins list A-Z, alphabetically -->
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
<!-- 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/. -->
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<Preference
|
<Preference
|
||||||
android:key="@string/pref_key_save_logins_settings"
|
android:key="@string/pref_key_save_logins_settings"
|
||||||
android:summary="@string/preferences_passwords_save_logins_ask_to_save"
|
android:summary="@string/preferences_passwords_save_logins_ask_to_save"
|
||||||
|
@ -18,4 +19,9 @@
|
||||||
<Preference
|
<Preference
|
||||||
android:key="@string/pref_key_saved_logins"
|
android:key="@string/pref_key_saved_logins"
|
||||||
android:title="@string/preferences_passwords_saved_logins" />
|
android:title="@string/preferences_passwords_saved_logins" />
|
||||||
|
<androidx.preference.Preference
|
||||||
|
android:key="@string/pref_key_login_exceptions"
|
||||||
|
android:title="@string/preferences_passwords_exceptions"
|
||||||
|
app:icon="@drawable/ic_internet" />
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
||||||
|
|
|
@ -2,19 +2,21 @@
|
||||||
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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions
|
package org.mozilla.fenix.loginexceptions
|
||||||
|
|
||||||
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNotSame
|
import org.junit.Assert.assertNotSame
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class ExceptionsFragmentStoreTest {
|
class LoginExceptionFragmentStoreTest {
|
||||||
@Test
|
@Test
|
||||||
fun onChange() = runBlocking {
|
fun onChange() = runBlocking {
|
||||||
val initialState = emptyDefaultState()
|
val initialState = emptyDefaultState()
|
||||||
val store = ExceptionsFragmentStore(initialState)
|
val store = ExceptionsFragmentStore(initialState)
|
||||||
val newExceptionsItem = ExceptionItem("URL")
|
val newExceptionsItem: LoginException = mockk()
|
||||||
|
|
||||||
store.dispatch(ExceptionsFragmentAction.Change(listOf(newExceptionsItem))).join()
|
store.dispatch(ExceptionsFragmentAction.Change(listOf(newExceptionsItem))).join()
|
||||||
assertNotSame(initialState, store.state)
|
assertNotSame(initialState, store.state)
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* 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.loginexceptions
|
||||||
|
|
||||||
|
import io.mockk.mockk
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class LoginExceptionsInteractorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onDeleteAll() {
|
||||||
|
var onDeleteAll = false
|
||||||
|
val interactor = LoginExceptionsInteractor(
|
||||||
|
mockk(),
|
||||||
|
{ onDeleteAll = true }
|
||||||
|
)
|
||||||
|
interactor.onDeleteAll()
|
||||||
|
assertEquals(true, onDeleteAll)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onDeleteOne() {
|
||||||
|
var exceptionsItemReceived: LoginException? = null
|
||||||
|
val exceptionsItem: LoginException = mockk()
|
||||||
|
val interactor = LoginExceptionsInteractor(
|
||||||
|
{ exceptionsItemReceived = exceptionsItem },
|
||||||
|
mockk()
|
||||||
|
)
|
||||||
|
interactor.onDeleteOne(exceptionsItem)
|
||||||
|
assertEquals(exceptionsItemReceived, exceptionsItem)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
* 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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions
|
package org.mozilla.fenix.trackingprotectionexceptions
|
||||||
|
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
@ -11,10 +11,10 @@ import org.junit.Assert.assertEquals
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder
|
|
||||||
import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder
|
|
||||||
import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder
|
|
||||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsDeleteButtonViewHolder
|
||||||
|
import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsHeaderViewHolder
|
||||||
|
import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsListItemViewHolder
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
@RunWith(FenixRobolectricTestRunner::class)
|
@RunWith(FenixRobolectricTestRunner::class)
|
|
@ -2,7 +2,7 @@
|
||||||
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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions
|
package org.mozilla.fenix.trackingprotectionexceptions
|
||||||
|
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
||||||
|
@ -14,11 +14,12 @@ class ExceptionsInteractorTest {
|
||||||
@Test
|
@Test
|
||||||
fun onLearnMore() {
|
fun onLearnMore() {
|
||||||
var learnMoreClicked = false
|
var learnMoreClicked = false
|
||||||
val interactor = ExceptionsInteractor(
|
val interactor =
|
||||||
{ learnMoreClicked = true },
|
ExceptionsInteractor(
|
||||||
mockk(),
|
{ learnMoreClicked = true },
|
||||||
mockk()
|
mockk(),
|
||||||
)
|
mockk()
|
||||||
|
)
|
||||||
interactor.onLearnMore()
|
interactor.onLearnMore()
|
||||||
assertEquals(true, learnMoreClicked)
|
assertEquals(true, learnMoreClicked)
|
||||||
}
|
}
|
||||||
|
@ -26,11 +27,12 @@ class ExceptionsInteractorTest {
|
||||||
@Test
|
@Test
|
||||||
fun onDeleteAll() {
|
fun onDeleteAll() {
|
||||||
var onDeleteAll = false
|
var onDeleteAll = false
|
||||||
val interactor = ExceptionsInteractor(
|
val interactor =
|
||||||
mockk(),
|
ExceptionsInteractor(
|
||||||
mockk(),
|
mockk(),
|
||||||
{ onDeleteAll = true }
|
mockk(),
|
||||||
)
|
{ onDeleteAll = true }
|
||||||
|
)
|
||||||
interactor.onDeleteAll()
|
interactor.onDeleteAll()
|
||||||
assertEquals(true, onDeleteAll)
|
assertEquals(true, onDeleteAll)
|
||||||
}
|
}
|
||||||
|
@ -38,12 +40,14 @@ class ExceptionsInteractorTest {
|
||||||
@Test
|
@Test
|
||||||
fun onDeleteOne() {
|
fun onDeleteOne() {
|
||||||
var exceptionsItemReceived: TrackingProtectionException? = null
|
var exceptionsItemReceived: TrackingProtectionException? = null
|
||||||
val exceptionsItem = ExceptionItem("url")
|
val exceptionsItem =
|
||||||
val interactor = ExceptionsInteractor(
|
ExceptionItem("url")
|
||||||
mockk(),
|
val interactor =
|
||||||
{ exceptionsItemReceived = exceptionsItem },
|
ExceptionsInteractor(
|
||||||
mockk()
|
mockk(),
|
||||||
)
|
{ exceptionsItemReceived = exceptionsItem },
|
||||||
|
mockk()
|
||||||
|
)
|
||||||
interactor.onDeleteOne(exceptionsItem)
|
interactor.onDeleteOne(exceptionsItem)
|
||||||
assertEquals(exceptionsItemReceived, exceptionsItem)
|
assertEquals(exceptionsItemReceived, exceptionsItem)
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
* 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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions
|
package org.mozilla.fenix.trackingprotectionexceptions
|
||||||
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
|
@ -0,0 +1,35 @@
|
||||||
|
/* 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.trackingprotectionexceptions
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotSame
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class TrackingProtectionExceptionsFragmentStoreTest {
|
||||||
|
@Test
|
||||||
|
fun onChange() = runBlocking {
|
||||||
|
val initialState = emptyDefaultState()
|
||||||
|
val store =
|
||||||
|
ExceptionsFragmentStore(
|
||||||
|
initialState
|
||||||
|
)
|
||||||
|
val newExceptionsItem =
|
||||||
|
ExceptionItem("URL")
|
||||||
|
|
||||||
|
store.dispatch(ExceptionsFragmentAction.Change(listOf(newExceptionsItem))).join()
|
||||||
|
assertNotSame(initialState, store.state)
|
||||||
|
assertEquals(
|
||||||
|
store.state.items,
|
||||||
|
listOf(newExceptionsItem)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emptyDefaultState(): ExceptionsFragmentState =
|
||||||
|
ExceptionsFragmentState(
|
||||||
|
items = listOf()
|
||||||
|
)
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
* 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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions.viewholders
|
package org.mozilla.fenix.trackingprotectionexceptions.viewholders
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -15,7 +15,7 @@ import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.exceptions.ExceptionsInteractor
|
import org.mozilla.fenix.trackingprotectionexceptions.ExceptionsInteractor
|
||||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
|
||||||
@RunWith(FenixRobolectricTestRunner::class)
|
@RunWith(FenixRobolectricTestRunner::class)
|
|
@ -2,7 +2,7 @@
|
||||||
* 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/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.exceptions.viewholders
|
package org.mozilla.fenix.trackingprotectionexceptions.viewholders
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -15,7 +15,7 @@ import org.junit.Assert.assertEquals
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mozilla.fenix.exceptions.ExceptionsInteractor
|
import org.mozilla.fenix.trackingprotectionexceptions.ExceptionsInteractor
|
||||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
|
||||||
@RunWith(FenixRobolectricTestRunner::class)
|
@RunWith(FenixRobolectricTestRunner::class)
|
|
@ -106,6 +106,7 @@ object Deps {
|
||||||
const val mozilla_feature_pwa = "org.mozilla.components:feature-pwa:${Versions.mozilla_android_components}"
|
const val mozilla_feature_pwa = "org.mozilla.components:feature-pwa:${Versions.mozilla_android_components}"
|
||||||
const val mozilla_feature_toolbar = "org.mozilla.components:feature-toolbar:${Versions.mozilla_android_components}"
|
const val mozilla_feature_toolbar = "org.mozilla.components:feature-toolbar:${Versions.mozilla_android_components}"
|
||||||
const val mozilla_feature_findinpage = "org.mozilla.components:feature-findinpage:${Versions.mozilla_android_components}"
|
const val mozilla_feature_findinpage = "org.mozilla.components:feature-findinpage:${Versions.mozilla_android_components}"
|
||||||
|
const val mozilla_feature_logins = "org.mozilla.components:feature-logins:${Versions.mozilla_android_components}"
|
||||||
const val mozilla_feature_site_permissions = "org.mozilla.components:feature-sitepermissions:${Versions.mozilla_android_components}"
|
const val mozilla_feature_site_permissions = "org.mozilla.components:feature-sitepermissions:${Versions.mozilla_android_components}"
|
||||||
const val mozilla_feature_readerview = "org.mozilla.components:feature-readerview:${Versions.mozilla_android_components}"
|
const val mozilla_feature_readerview = "org.mozilla.components:feature-readerview:${Versions.mozilla_android_components}"
|
||||||
const val mozilla_feature_tab_collections = "org.mozilla.components:feature-tab-collections:${Versions.mozilla_android_components}"
|
const val mozilla_feature_tab_collections = "org.mozilla.components:feature-tab-collections:${Versions.mozilla_android_components}"
|
||||||
|
|
Loading…
Reference in New Issue