diff --git a/app/build.gradle b/app/build.gradle index 409c84b97..85d61d442 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -501,6 +501,7 @@ dependencies { implementation Deps.mozilla_feature_toolbar implementation Deps.mozilla_feature_tabs implementation Deps.mozilla_feature_findinpage + implementation Deps.mozilla_feature_logins implementation Deps.mozilla_feature_site_permissions implementation Deps.mozilla_feature_readerview implementation Deps.mozilla_feature_tab_collections diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivacyTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivacyTest.kt index 2adeee32b..c146a5213 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivacyTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsPrivacyTest.kt @@ -170,6 +170,7 @@ class SettingsPrivacyTest { verifyDefaultView() verifyDefaultValueSyncLogins() verifyDefaultValueAutofillLogins() + verifyDefaultValueExceptions() }.openSavedLogins { verifySavedLoginsView() tapSetupLater() @@ -209,13 +210,13 @@ class SettingsPrivacyTest { } @Test - fun doNotSaveLoginFromPromptTest() { + fun neverSaveLoginFromPromptTest() { val saveLoginTest = TestAssetHelper.getSaveLoginAsset(mockWebServer) navigationToolbar { }.enterURLAndEnterToBrowser(saveLoginTest.url) { verifySaveLoginPromptIsShown() - // Don't save the login + // Don't save the login, add to exceptions saveLoginFromPrompt("Never save") }.openTabDrawer { }.openHomeScreen { @@ -228,7 +229,11 @@ class SettingsPrivacyTest { verifySavedLoginsView() tapSetupLater() // Verify that the login list is empty - verifyNotSavedLoginFromPromt() + verifyNotSavedLoginFromPrompt() + }.goBack { + }.openLoginExceptions { + // Verify localhost was added to exceptions list + verifyLocalhostExceptionAdded() } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuLoginsAndPasswordRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuLoginsAndPasswordRobot.kt index c45612625..46b8656ce 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuLoginsAndPasswordRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuLoginsAndPasswordRobot.kt @@ -38,6 +38,8 @@ class SettingsSubMenuLoginsAndPasswordRobot { mDevice.waitNotNull(Until.findObjects(By.text("On")), TestAssetHelper.waitingTime) } + fun verifyDefaultValueExceptions() = assertDefaultValueExceptions() + fun verifyDefaultValueAutofillLogins() = assertDefaultValueAutofillLogins() fun verifyDefaultValueSyncLogins() = assertDefaultValueSyncLogins() @@ -60,6 +62,14 @@ class SettingsSubMenuLoginsAndPasswordRobot { 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 syncLoginsButton() = onView(ViewMatchers.withText("Sync logins")) syncLoginsButton().click() @@ -92,5 +102,8 @@ private fun assertDefaultView() = onView(ViewMatchers.withText("Sync logins")) private fun assertDefaultValueAutofillLogins() = onView(ViewMatchers.withText("Autofill")) .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")) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.kt index 83ac1f47f..3c093ff5e 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.kt @@ -13,6 +13,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import org.hamcrest.CoreMatchers +import org.hamcrest.CoreMatchers.containsString import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.ext.waitNotNull @@ -24,16 +25,23 @@ class SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot { fun verifySavedLoginsView() = assertSavedLoginsView() 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() } 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")) - .check(ViewAssertions.doesNotExist()) + fun verifyNotSavedLoginFromPrompt() = onView(ViewMatchers.withText("test@example.com")) + .check(ViewAssertions.doesNotExist()) + + fun verifyLocalhostExceptionAdded() = onView(ViewMatchers.withText(containsString("localhost"))) + .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) class Transition { fun goBack(interact: SettingsSubMenuLoginsAndPasswordRobot.() -> Unit): SettingsSubMenuLoginsAndPasswordRobot.Transition { @@ -46,9 +54,10 @@ class SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot { } 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))) private fun assertSavedLoginAppears() = onView(ViewMatchers.withText("https://accounts.google.com")) diff --git a/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt b/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt index 5b4ee3afe..3077e1c26 100644 --- a/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt +++ b/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt @@ -21,7 +21,7 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) { FromSyncedTabs(R.id.syncedTabsFragment), FromBookmarks(R.id.bookmarkFragment), FromHistory(R.id.historyFragment), - FromExceptions(R.id.exceptionsFragment), + FromTrackingProtectionExceptions(R.id.trackingProtectionExceptionsFragment), FromAbout(R.id.aboutFragment), FromTrackingProtection(R.id.trackingProtectionFragment), FromSavedLoginsFragment(R.id.savedLoginsFragment), diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 8ba8477b5..73480dd96 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -62,7 +62,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder 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.components import org.mozilla.fenix.ext.nav @@ -498,8 +498,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { BookmarkFragmentDirections.actionGlobalBrowser(customTabSessionId) BrowserDirection.FromHistory -> HistoryFragmentDirections.actionGlobalBrowser(customTabSessionId) - BrowserDirection.FromExceptions -> - ExceptionsFragmentDirections.actionGlobalBrowser(customTabSessionId) + BrowserDirection.FromTrackingProtectionExceptions -> + TrackingProtectionExceptionsFragmentDirections.actionGlobalBrowser(customTabSessionId) BrowserDirection.FromAbout -> AboutFragmentDirections.actionGlobalBrowser(customTabSessionId) BrowserDirection.FromTrackingProtection -> diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 254740e31..326b01b60 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -443,6 +443,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session isSaveLoginEnabled = { context.settings().shouldPromptToSaveLogins }, + loginExceptionStorage = context.components.core.loginExceptionStorage, shareDelegate = object : ShareDelegate { override fun showShareSheet( context: Context, diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index 9bfe78f1c..752de367b 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -30,6 +30,7 @@ import mozilla.components.concept.engine.mediaquery.PreferredColorScheme import mozilla.components.concept.fetch.Client import mozilla.components.feature.customtabs.store.CustomTabsServiceStore import mozilla.components.feature.downloads.DownloadMiddleware +import mozilla.components.feature.logins.exceptions.LoginExceptionStorage import mozilla.components.feature.media.RecordingDevicesNotificationFeature import mozilla.components.feature.media.middleware.MediaMiddleware import mozilla.components.feature.pwa.ManifestStorage @@ -251,6 +252,8 @@ class Core(private val context: 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+ * only on Nightly/Debug for now, otherwise simply stored. diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/ExceptionsFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/loginexceptions/ExceptionsFragmentStore.kt new file mode 100644 index 000000000..88ddfbfea --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/loginexceptions/ExceptionsFragmentStore.kt @@ -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(initialState, ::exceptionsStateReducer) + +/** + * Actions to dispatch through the `ExceptionsStore` to modify `ExceptionsState` through the reducer. + */ +sealed class ExceptionsFragmentAction : Action { + data class Change(val list: List) : ExceptionsFragmentAction() +} + +/** + * The state for the Exceptions Screen + * @property items List of exceptions to display + */ +data class ExceptionsFragmentState(val items: List) : State + +/** + * The ExceptionsState Reducer. + */ +private fun exceptionsStateReducer( + state: ExceptionsFragmentState, + action: ExceptionsFragmentAction +): ExceptionsFragmentState { + return when (action) { + is ExceptionsFragmentAction.Change -> state.copy(items = action.list) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsAdapter.kt b/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsAdapter.kt new file mode 100644 index 000000000..5a5031490 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsAdapter.kt @@ -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(DiffCallback) { + + /** + * Change the list of items that are displayed. + * Header and footer items are added to the list as well. + */ + fun updateData(exceptions: List) { + val adapterItems: List = + 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() { + override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = + areContentsTheSame(oldItem, newItem) + + @Suppress("DiffUtilEquals") + override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = + oldItem == newItem + } +} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsFragment.kt new file mode 100644 index 000000000..d2be2d66f --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsFragment.kt @@ -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> { + return Observer> { 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) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsInteractor.kt b/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsInteractor.kt new file mode 100644 index 000000000..8a5881c05 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsInteractor.kt @@ -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) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsView.kt b/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsView.kt new file mode 100644 index 000000000..26854bba7 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsView.kt @@ -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) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsDeleteButtonViewHolder.kt b/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsDeleteButtonViewHolder.kt new file mode 100644 index 000000000..a69ecf312 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsDeleteButtonViewHolder.kt @@ -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 + } +} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsHeaderViewHolder.kt b/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsHeaderViewHolder.kt new file mode 100644 index 000000000..0a2033f16 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsHeaderViewHolder.kt @@ -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) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsListItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsListItemViewHolder.kt new file mode 100644 index 000000000..688da089a --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsListItemViewHolder.kt @@ -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 + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt index 9c21ec3ab..e984b0013 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt @@ -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 - * 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() { @@ -56,7 +56,8 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { showToolbar(getString(R.string.preference_enhanced_tracking_protection)) // Tracking Protection Switch - val preferenceTP = requirePreference(R.string.pref_key_tracking_protection) + val preferenceTP = + requirePreference(R.string.pref_key_tracking_protection) preferenceTP.isChecked = requireContext().settings().shouldUseTrackingProtection preferenceTP.setOnPreferenceChangeListener { preference, trackingProtectionOn -> @@ -86,7 +87,8 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { getString(R.string.app_name) ) - val preferenceExceptions = requirePreference(R.string.pref_key_tracking_protection_exceptions) + val preferenceExceptions = + requirePreference(R.string.pref_key_tracking_protection_exceptions) preferenceExceptions.onPreferenceClickListener = exceptionsClickListener } diff --git a/app/src/main/java/org/mozilla/fenix/settings/logins/SavedLoginsAuthFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/logins/SavedLoginsAuthFragment.kt index d0831e62b..d8051e7f3 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/logins/SavedLoginsAuthFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/logins/SavedLoginsAuthFragment.kt @@ -133,6 +133,13 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver { } } + requirePreference(R.string.pref_key_login_exceptions).apply { + setOnPreferenceClickListener { + navigateToLoginExceptionFragment() + true + } + } + requirePreference(R.string.pref_key_autofill_logins).apply { isChecked = context.settings().shouldAutofillLogins onPreferenceChangeListener = object : SharedPreferenceUpdater() { @@ -322,6 +329,12 @@ class SavedLoginsAuthFragment : PreferenceFragmentCompat(), AccountObserver { findNavController().navigate(directions) } + private fun navigateToLoginExceptionFragment() { + val directions = + SavedLoginsAuthFragmentDirections.actionSavedLoginsAuthFragmentToLoginExceptionsFragment() + findNavController().navigate(directions) + } + companion object { const val SHORT_DELAY_MS = 100L private const val LOG_TAG = "LoginsFragment" diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsAdapter.kt similarity index 89% rename from app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt rename to app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsAdapter.kt index 1f5c62dd7..a2adfdd5d 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsAdapter.kt @@ -2,7 +2,7 @@ * 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.exceptions +package org.mozilla.fenix.trackingprotectionexceptions import android.view.LayoutInflater import android.view.ViewGroup @@ -10,9 +10,9 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import mozilla.components.concept.engine.content.blocking.TrackingProtectionException -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.trackingprotectionexceptions.viewholders.ExceptionsDeleteButtonViewHolder +import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsHeaderViewHolder +import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsListItemViewHolder /** * Adapter for a list of sites that are exempted from Tracking Protection, diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsFragmentStore.kt similarity index 96% rename from app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragmentStore.kt rename to app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsFragmentStore.kt index 1ba13b0dd..6d51f98a3 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsFragmentStore.kt @@ -2,7 +2,7 @@ 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.exceptions +package org.mozilla.fenix.trackingprotectionexceptions import mozilla.components.concept.engine.content.blocking.TrackingProtectionException import mozilla.components.lib.state.Action diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsInteractor.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsInteractor.kt similarity index 93% rename from app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsInteractor.kt rename to app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsInteractor.kt index 9ec50c71f..e609cd003 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsInteractor.kt @@ -2,7 +2,7 @@ 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.exceptions +package org.mozilla.fenix.trackingprotectionexceptions import mozilla.components.concept.engine.content.blocking.TrackingProtectionException diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsView.kt similarity index 94% rename from app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt rename to app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsView.kt index b40dcf5f0..090d9c303 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsView.kt @@ -2,7 +2,7 @@ * 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.exceptions +package org.mozilla.fenix.trackingprotectionexceptions import android.text.method.LinkMovementMethod import android.text.style.UnderlineSpan @@ -50,7 +50,10 @@ class ExceptionsView( .inflate(R.layout.component_exceptions, container, true) .findViewById(R.id.exceptions_wrapper) - private val exceptionsAdapter = ExceptionsAdapter(interactor) + private val exceptionsAdapter = + ExceptionsAdapter( + interactor + ) init { exceptions_list.apply { diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/TrackingProtectionExceptionsFragment.kt similarity index 83% rename from app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt rename to app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/TrackingProtectionExceptionsFragment.kt index f1ba7582e..79b37e13c 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/TrackingProtectionExceptionsFragment.kt @@ -2,7 +2,7 @@ * 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.exceptions +package org.mozilla.fenix.trackingprotectionexceptions import android.os.Bundle 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, * along with controls to remove the exception. */ -class ExceptionsFragment : Fragment() { +class TrackingProtectionExceptionsFragment : Fragment() { private lateinit var exceptionsStore: ExceptionsFragmentStore private lateinit var exceptionsView: ExceptionsView @@ -54,8 +54,16 @@ class ExceptionsFragment : Fragment() { ) } exceptionsInteractor = - ExceptionsInteractor(::openLearnMore, ::deleteOneItem, ::deleteAllItems) - exceptionsView = ExceptionsView(view.exceptionsLayout, exceptionsInteractor) + ExceptionsInteractor( + ::openLearnMore, + ::deleteOneItem, + ::deleteAllItems + ) + exceptionsView = + ExceptionsView( + view.exceptionsLayout, + exceptionsInteractor + ) reloadExceptions() return view } @@ -83,13 +91,17 @@ class ExceptionsFragment : Fragment() { searchTermOrURL = SupportUtils.getGenericSumoURLForTopic (SupportUtils.SumoTopic.TRACKING_PROTECTION), newTab = true, - from = BrowserDirection.FromExceptions + from = BrowserDirection.FromTrackingProtectionExceptions ) } private fun reloadExceptions() { trackingProtectionUseCases.fetchExceptions { resultList -> - exceptionsStore.dispatch(ExceptionsFragmentAction.Change(resultList)) + exceptionsStore.dispatch( + ExceptionsFragmentAction.Change( + resultList + ) + ) } } } diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt similarity index 84% rename from app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt rename to app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt index 9a0ed172c..d25754bdf 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt @@ -2,13 +2,13 @@ * 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.exceptions.viewholders +package org.mozilla.fenix.trackingprotectionexceptions.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.exceptions.ExceptionsInteractor +import org.mozilla.fenix.trackingprotectionexceptions.ExceptionsInteractor class ExceptionsDeleteButtonViewHolder( view: View, diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsHeaderViewHolder.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsHeaderViewHolder.kt similarity index 87% rename from app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsHeaderViewHolder.kt rename to app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsHeaderViewHolder.kt index 7902c3505..2a5c7b54d 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsHeaderViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsHeaderViewHolder.kt @@ -2,7 +2,7 @@ * 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.exceptions.viewholders +package org.mozilla.fenix.trackingprotectionexceptions.viewholders import android.view.View import androidx.recyclerview.widget.RecyclerView diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsListItemViewHolder.kt similarity index 91% rename from app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolder.kt rename to app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsListItemViewHolder.kt index 25e51824c..ec43919b9 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsListItemViewHolder.kt @@ -2,14 +2,14 @@ * 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.exceptions.viewholders +package org.mozilla.fenix.trackingprotectionexceptions.viewholders import android.view.View import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.exception_item.view.* import mozilla.components.concept.engine.content.blocking.TrackingProtectionException 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.loadIntoView diff --git a/app/src/main/res/layout/delete_logins_exceptions_button.xml b/app/src/main/res/layout/delete_logins_exceptions_button.xml new file mode 100644 index 000000000..291e5e775 --- /dev/null +++ b/app/src/main/res/layout/delete_logins_exceptions_button.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_exceptions.xml b/app/src/main/res/layout/fragment_exceptions.xml index 6fed0a94b..2b0e69fc2 100644 --- a/app/src/main/res/layout/fragment_exceptions.xml +++ b/app/src/main/res/layout/fragment_exceptions.xml @@ -3,10 +3,7 @@ - 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/. --> - + android:orientation="vertical"/> diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 77eba73a6..e3b1859cb 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -27,35 +27,83 @@ android:id="@+id/action_global_search" app:destination="@id/searchFragment" /> - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + tools:layout="@layout/fragment_tab_tray_dialog" /> + app:nullable="true" /> + android:defaultValue="NONE" + app:argType="org.mozilla.fenix.components.metrics.Event$PerformedSearch$SearchAccessPoint" /> + app:popExitAnim="@anim/fade_out_down" /> + android:defaultValue="false" + app:argType="boolean" /> @@ -234,8 +282,8 @@ app:destination="@id/bookmarkSelectFolderFragment" /> + android:defaultValue="false" + app:argType="boolean" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + - + tools:layout="@layout/fragment_synced_tabs"/> + + + app:nullable="false" /> + app:popUpToInclusive="true" /> + app:nullable="false" /> + app:popUpToInclusive="true" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> @@ -541,7 +601,7 @@ + android:name="org.mozilla.fenix.settings.about.AboutFragment" /> + app:popExitAnim="@anim/slide_out_right" /> + app:popExitAnim="@anim/slide_out_right" /> + android:id="@+id/trackingProtectionExceptionsFragment" + android:name="org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragment" + android:label="@string/preference_exceptions" /> + app:nullable="true" /> diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 7e2c43baa..e06e0524f 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -185,4 +185,6 @@ pref_key_search_widget_cfr_manually_dismissed pref_key_is_in_search_widget_experiment pref_key_show_search_widget_cfr + + pref_key_login_exceptions diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 96fea71e2..f8a03c6af 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1217,6 +1217,8 @@ Logins and passwords that are not saved will be shown here. Logins and passwords will not be saved for these sites. + + Delete all exceptions Search logins diff --git a/app/src/main/res/xml/logins_preferences.xml b/app/src/main/res/xml/logins_preferences.xml index f71114007..0c003b154 100644 --- a/app/src/main/res/xml/logins_preferences.xml +++ b/app/src/main/res/xml/logins_preferences.xml @@ -2,7 +2,8 @@ - + + + diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsFragmentStoreTest.kt b/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionFragmentStoreTest.kt similarity index 79% rename from app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsFragmentStoreTest.kt rename to app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionFragmentStoreTest.kt index a6aec4f2f..ee0bd3171 100644 --- a/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsFragmentStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionFragmentStoreTest.kt @@ -2,19 +2,21 @@ 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.exceptions +package org.mozilla.fenix.loginexceptions +import io.mockk.mockk import kotlinx.coroutines.runBlocking +import mozilla.components.feature.logins.exceptions.LoginException import org.junit.Assert.assertEquals import org.junit.Assert.assertNotSame import org.junit.Test -class ExceptionsFragmentStoreTest { +class LoginExceptionFragmentStoreTest { @Test fun onChange() = runBlocking { val initialState = emptyDefaultState() val store = ExceptionsFragmentStore(initialState) - val newExceptionsItem = ExceptionItem("URL") + val newExceptionsItem: LoginException = mockk() store.dispatch(ExceptionsFragmentAction.Change(listOf(newExceptionsItem))).join() assertNotSame(initialState, store.state) diff --git a/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionsInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionsInteractorTest.kt new file mode 100644 index 000000000..309f3e681 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionsInteractorTest.kt @@ -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) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsAdapterTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsAdapterTest.kt similarity index 80% rename from app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsAdapterTest.kt rename to app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsAdapterTest.kt index dae9ff464..10fff5ab3 100644 --- a/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsAdapterTest.kt +++ b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsAdapterTest.kt @@ -2,7 +2,7 @@ * 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.exceptions +package org.mozilla.fenix.trackingprotectionexceptions import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -11,10 +11,10 @@ import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test 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.trackingprotectionexceptions.viewholders.ExceptionsDeleteButtonViewHolder +import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsHeaderViewHolder +import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsListItemViewHolder @ExperimentalCoroutinesApi @RunWith(FenixRobolectricTestRunner::class) diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsInteractorTest.kt similarity index 59% rename from app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsInteractorTest.kt rename to app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsInteractorTest.kt index 3132101b2..84cfa928d 100644 --- a/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsInteractorTest.kt @@ -2,7 +2,7 @@ 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.exceptions +package org.mozilla.fenix.trackingprotectionexceptions import io.mockk.mockk import mozilla.components.concept.engine.content.blocking.TrackingProtectionException @@ -14,11 +14,12 @@ class ExceptionsInteractorTest { @Test fun onLearnMore() { var learnMoreClicked = false - val interactor = ExceptionsInteractor( - { learnMoreClicked = true }, - mockk(), - mockk() - ) + val interactor = + ExceptionsInteractor( + { learnMoreClicked = true }, + mockk(), + mockk() + ) interactor.onLearnMore() assertEquals(true, learnMoreClicked) } @@ -26,11 +27,12 @@ class ExceptionsInteractorTest { @Test fun onDeleteAll() { var onDeleteAll = false - val interactor = ExceptionsInteractor( - mockk(), - mockk(), - { onDeleteAll = true } - ) + val interactor = + ExceptionsInteractor( + mockk(), + mockk(), + { onDeleteAll = true } + ) interactor.onDeleteAll() assertEquals(true, onDeleteAll) } @@ -38,12 +40,14 @@ class ExceptionsInteractorTest { @Test fun onDeleteOne() { var exceptionsItemReceived: TrackingProtectionException? = null - val exceptionsItem = ExceptionItem("url") - val interactor = ExceptionsInteractor( - mockk(), - { exceptionsItemReceived = exceptionsItem }, - mockk() - ) + val exceptionsItem = + ExceptionItem("url") + val interactor = + ExceptionsInteractor( + mockk(), + { exceptionsItemReceived = exceptionsItem }, + mockk() + ) interactor.onDeleteOne(exceptionsItem) assertEquals(exceptionsItemReceived, exceptionsItem) } diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsViewTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsViewTest.kt similarity index 98% rename from app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsViewTest.kt rename to app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsViewTest.kt index 4adc07709..a69c067b9 100644 --- a/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsViewTest.kt +++ b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsViewTest.kt @@ -2,7 +2,7 @@ * 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.exceptions +package org.mozilla.fenix.trackingprotectionexceptions import android.text.Spannable import android.text.method.LinkMovementMethod diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/TrackingProtectionExceptionsFragmentStoreTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/TrackingProtectionExceptionsFragmentStoreTest.kt new file mode 100644 index 000000000..027f37058 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/TrackingProtectionExceptionsFragmentStoreTest.kt @@ -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() + ) +} diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt similarity index 90% rename from app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt rename to app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt index 28bb40dc2..8c53e28ff 100644 --- a/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt @@ -2,7 +2,7 @@ * 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.exceptions.viewholders +package org.mozilla.fenix.trackingprotectionexceptions.viewholders import android.view.LayoutInflater import android.view.View @@ -15,7 +15,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mozilla.fenix.R -import org.mozilla.fenix.exceptions.ExceptionsInteractor +import org.mozilla.fenix.trackingprotectionexceptions.ExceptionsInteractor import org.mozilla.fenix.helpers.FenixRobolectricTestRunner @RunWith(FenixRobolectricTestRunner::class) diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsListItemViewHolderTest.kt similarity index 92% rename from app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolderTest.kt rename to app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsListItemViewHolderTest.kt index 7dd1ddb5e..3efb3d907 100644 --- a/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsListItemViewHolderTest.kt @@ -2,7 +2,7 @@ * 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.exceptions.viewholders +package org.mozilla.fenix.trackingprotectionexceptions.viewholders import android.view.LayoutInflater import android.view.View @@ -15,7 +15,7 @@ import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mozilla.fenix.exceptions.ExceptionsInteractor +import org.mozilla.fenix.trackingprotectionexceptions.ExceptionsInteractor import org.mozilla.fenix.helpers.FenixRobolectricTestRunner @RunWith(FenixRobolectricTestRunner::class) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 731b33ee1..69f2d65e0 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -106,6 +106,7 @@ object Deps { 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_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_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}"