From 0663e0ab964d6d7db845fd1a849c04bf4236bccb Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Wed, 18 Nov 2020 15:36:19 +0000 Subject: [PATCH 001/202] Update Android Components version to 67.0.20201117190147. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index e8d1126bb..b942cd600 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "67.0.20201117165227" + const val VERSION = "67.0.20201117190147" } From cf162c69d1b2c311b70e651ed474c1a1f363cb8f Mon Sep 17 00:00:00 2001 From: mcarare Date: Tue, 17 Nov 2020 19:39:46 +0200 Subject: [PATCH 002/202] For #15788: Remove nested layouts in search dialog layout. --- .../fenix/search/SearchDialogFragment.kt | 8 +- .../res/layout/fragment_search_dialog.xml | 130 +++++++++++------- 2 files changed, 84 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt index b670be096..a6b321327 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt @@ -501,9 +501,15 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { searchState.query.isEmpty() && !clipboardUrl.isNullOrEmpty() - fill_link_from_clipboard.visibility = if (shouldShowView) View.VISIBLE else View.GONE + fill_link_from_clipboard.isVisible = shouldShowView + clipboard_url.isVisible = shouldShowView + clipboard_title.isVisible = shouldShowView + link_icon.isVisible = shouldShowView + clipboard_url.text = clipboardUrl + fill_link_from_clipboard.contentDescription = "${clipboard_title.text}, ${clipboard_url.text}." + if (clipboardUrl != null && !((activity as HomeActivity).browsingModeManager.mode.isPrivate)) { requireComponents.core.engine.speculativeConnect(clipboardUrl) } diff --git a/app/src/main/res/layout/fragment_search_dialog.xml b/app/src/main/res/layout/fragment_search_dialog.xml index fec3110b8..91cdcc888 100644 --- a/app/src/main/res/layout/fragment_search_dialog.xml +++ b/app/src/main/res/layout/fragment_search_dialog.xml @@ -4,6 +4,7 @@ - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - + app:layout_constraintTop_toBottomOf="@+id/toolbar" + tools:visibility="visible" /> - + - + - - + - + app:layout_constraintStart_toStartOf="parent" /> - + - - + From 519885da4342f5aee74dd5b06ee3ed657d3aca73 Mon Sep 17 00:00:00 2001 From: mcarare Date: Wed, 18 Nov 2020 15:52:16 +0200 Subject: [PATCH 003/202] For #15788: Remove nested constraint layout in tabs tray. --- .../main/res/layout/component_tabstray.xml | 183 +++++++++--------- .../res/layout/tabstray_multiselect_items.xml | 14 +- 2 files changed, 98 insertions(+), 99 deletions(-) diff --git a/app/src/main/res/layout/component_tabstray.xml b/app/src/main/res/layout/component_tabstray.xml index 20aff53f0..be2e438de 100644 --- a/app/src/main/res/layout/component_tabstray.xml +++ b/app/src/main/res/layout/component_tabstray.xml @@ -47,105 +47,104 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/infoBanner" /> - - - - - + + + + + + + + + + - - - - + + - - - - - - - - - - - + android:layout_height="match_parent" + android:contentDescription="@string/tabs_header_private_tabs_title" + android:icon="@drawable/ic_private_browsing" /> + + + + + + @@ -29,9 +29,9 @@ android:background="?android:attr/selectableItemBackgroundBorderless" android:contentDescription="@string/tab_tray_multiselect_share_content_description" android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toBottomOf="@id/topBar" app:layout_constraintEnd_toStartOf="@id/menu_multi_select" - app:layout_constraintTop_toTopOf="parent" + app:layout_constraintTop_toTopOf="@id/topBar" app:srcCompat="@drawable/ic_share_filled" app:tint="@color/contrast_text_normal_theme" /> @@ -42,9 +42,9 @@ android:background="?android:attr/selectableItemBackgroundBorderless" android:contentDescription="@string/tab_tray_multiselect_menu_content_description" android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="@id/topBar" + app:layout_constraintEnd_toEndOf="@id/topBar" + app:layout_constraintTop_toTopOf="@id/topBar" app:srcCompat="@drawable/ic_menu" app:tint="@color/contrast_text_normal_theme" /> From b7fe809ae460450680461497b61072ce5d81cedf Mon Sep 17 00:00:00 2001 From: ekager Date: Tue, 17 Nov 2020 21:55:48 -0800 Subject: [PATCH 004/202] For #16351 - Make homescreen interactive when search dialog is up --- .../org/mozilla/fenix/home/HomeFragment.kt | 14 ++++++++ .../SessionControlController.kt | 24 +++++++++++-- .../SessionControlInteractor.kt | 19 ++++++++++ .../viewholders/CollectionViewHolder.kt | 1 + .../topsites/TopSiteItemViewHolder.kt | 1 + .../fenix/search/SearchDialogFragment.kt | 36 +++++++++++++++++-- .../DefaultSessionControlControllerTest.kt | 26 ++++++++++++++ .../home/SessionControlInteractorTest.kt | 12 +++++++ .../topsites/TopSiteItemViewHolderTest.kt | 8 +++++ 9 files changed, 135 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 520c21fde..44ec66b96 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -698,6 +698,20 @@ class HomeFragment : Fragment() { } private fun navigateToSearch() { + // Dismisses the search dialog when the home content is scrolled + val recyclerView = sessionControlView!!.view + val listener = object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + if (newState == RecyclerView.SCROLL_STATE_DRAGGING || newState == RecyclerView.SCROLL_STATE_SETTLING) { + findNavController().navigateUp() + recyclerView.removeOnScrollListener(this) + } + } + } + + recyclerView.addOnScrollListener(listener) + val directions = HomeFragmentDirections.actionGlobalSearchDialog( sessionId = null diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt index 9bc885577..d52c1ac18 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt @@ -34,7 +34,6 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.sessionsOfType -import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.HomeFragment import org.mozilla.fenix.home.HomeFragmentAction import org.mozilla.fenix.home.HomeFragmentDirections @@ -158,6 +157,11 @@ interface SessionControlController { * @see [CollectionInteractor.onRemoveCollectionsPlaceholder] */ fun handleRemoveCollectionsPlaceholder() + + /** + * @see [CollectionInteractor.onCollectionMenuOpened] and [TopSiteInteractor.onTopSiteMenuOpened] + */ + fun handleMenuOpened() } @Suppress("TooManyFunctions", "LargeClass") @@ -193,7 +197,12 @@ class DefaultSessionControlController( ) } + override fun handleMenuOpened() { + dismissSearchDialogIfDisplayed() + } + override fun handleCollectionOpenTabClicked(tab: ComponentTab) { + dismissSearchDialogIfDisplayed() sessionManager.restore( activity, engine, @@ -256,6 +265,7 @@ class DefaultSessionControlController( } override fun handleCollectionShareTabsClicked(collection: TabCollection) { + dismissSearchDialogIfDisplayed() showShareFragment( collection.title, collection.tabs.map { ShareData(url = it.url, title = it.title) } @@ -282,6 +292,7 @@ class DefaultSessionControlController( } override fun handlePrivateBrowsingLearnMoreClicked() { + dismissSearchDialogIfDisplayed() activity.openToBrowserAndLoad( searchTermOrURL = SupportUtils.getGenericSumoURLForTopic (SupportUtils.SumoTopic.PRIVATE_BROWSING_MYTHS), @@ -293,9 +304,9 @@ class DefaultSessionControlController( override fun handleRenameTopSiteClicked(topSite: TopSite) { activity.let { val customLayout = - LayoutInflater.from(it).inflate(R.layout.top_sites_rename_dialog, null) + LayoutInflater.from(it).inflate(R.layout.top_sites_rename_dialog, null) val topSiteLabelEditText: EditText = - customLayout.findViewById(R.id.top_site_title) + customLayout.findViewById(R.id.top_site_title) topSiteLabelEditText.setText(topSite.title) AlertDialog.Builder(it).apply { @@ -344,6 +355,7 @@ class DefaultSessionControlController( } override fun handleSelectTopSite(url: String, type: TopSite.Type) { + dismissSearchDialogIfDisplayed() metrics.track(Event.TopSiteOpenInNewTab) when (type) { TopSite.Type.DEFAULT -> metrics.track(Event.TopSiteOpenDefault) @@ -362,6 +374,12 @@ class DefaultSessionControlController( activity.openToBrowser(BrowserDirection.FromHome) } + private fun dismissSearchDialogIfDisplayed() { + if (navController.currentDestination?.id == R.id.searchDialogFragment) { + navController.navigateUp() + } + } + override fun handleStartBrowsingClicked() { hideOnboarding() } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt index 6237920ae..33bdd0f31 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt @@ -23,6 +23,7 @@ interface TabSessionInteractor { /** * Interface for collection related actions in the [SessionControlInteractor]. */ +@SuppressWarnings("TooManyFunctions") interface CollectionInteractor { /** * Shows the Collection Creation fragment for selecting the tabs to add to the given tab @@ -98,6 +99,11 @@ interface CollectionInteractor { * User has removed the collections placeholder from home. */ fun onRemoveCollectionsPlaceholder() + + /** + * User has opened collection 3 dot menu. + */ + fun onCollectionMenuOpened() } interface ToolbarInteractor { @@ -177,6 +183,11 @@ interface TopSiteInteractor { * @param type The type of the top site. */ fun onSelectTopSite(url: String, type: TopSite.Type) + + /** + * Called when top site menu is opened. + */ + fun onTopSiteMenuOpened() } /** @@ -276,4 +287,12 @@ class SessionControlInteractor( override fun onRemoveCollectionsPlaceholder() { controller.handleRemoveCollectionsPlaceholder() } + + override fun onCollectionMenuOpened() { + controller.handleMenuOpened() + } + + override fun onTopSiteMenuOpened() { + controller.handleMenuOpened() + } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt index c643f43d9..6e8e3ea9c 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/CollectionViewHolder.kt @@ -47,6 +47,7 @@ class CollectionViewHolder( } collection_overflow_button.setOnClickListener { + interactor.onCollectionMenuOpened() collectionMenu.menuBuilder .build(view.context) .show(anchor = it) diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt index efc572aec..48c4ee3e1 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolder.kt @@ -36,6 +36,7 @@ class TopSiteItemViewHolder( } top_site_item.setOnLongClickListener { + interactor.onTopSiteMenuOpened() it.context.components.analytics.metrics.track(Event.TopSiteLongPress(topSite.type)) val topSiteMenu = TopSiteItemMenu(view.context, topSite.type != FRECENT) { item -> diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt index a6b321327..33c00362e 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt @@ -10,7 +10,9 @@ import android.app.Dialog import android.content.Context import android.content.DialogInterface import android.content.Intent +import android.graphics.Color import android.graphics.Typeface +import android.graphics.drawable.ColorDrawable import android.os.Build import android.os.Bundle import android.os.StrictMode @@ -21,6 +23,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewStub import android.view.WindowManager +import android.view.inputmethod.InputMethodManager import android.view.accessibility.AccessibilityEvent import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDialogFragment @@ -183,6 +186,15 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { requireComponents.core.engine.speculativeCreateSession(isPrivate) + if (findNavController().previousBackStackEntry?.destination?.id == R.id.homeFragment) { + // When displayed above home, dispatches the touch events to scrim area to the HomeFragment + view.search_wrapper.background = ColorDrawable(Color.TRANSPARENT) + dialog?.window?.decorView?.setOnTouchListener { _, event -> + requireActivity().dispatchTouchEvent(event) + false + } + } + return view } @@ -193,9 +205,12 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { setupConstraints(view) - search_wrapper.setOnClickListener { - it.hideKeyboardAndSave() - dismissAllowingStateLoss() + // When displayed above browser, dismisses dialog on clicking scrim area + if (findNavController().previousBackStackEntry?.destination?.id == R.id.browserFragment) { + search_wrapper.setOnClickListener { + it.hideKeyboardAndSave() + dismissAllowingStateLoss() + } } view.search_engines_shortcut_button.setOnClickListener { @@ -327,6 +342,21 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { toolbarView.view.requestFocus() } + /* + * This way of dismissing the keyboard is needed to smoothly dismiss the keyboard while the dialog + * is also dismissing. For example, when clicking a top site on home while this dialog is showing. + */ + private fun hideDeviceKeyboard() { + val imm = + requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0) + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + hideDeviceKeyboard() + } + override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { if (requestCode == VoiceSearchActivity.SPEECH_REQUEST_CODE && resultCode == Activity.RESULT_OK) { intent?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.first()?.also { diff --git a/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt b/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt index 7a4080fe8..4041b6305 100644 --- a/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt @@ -434,4 +434,30 @@ class DefaultSessionControlControllerTest { fragmentStore.dispatch(HomeFragmentAction.RemoveCollectionsPlaceholder) } } + + @Test + fun handleMenuOpenedWhileSearchShowing() { + every { navController.currentDestination } returns mockk { + every { id } returns R.id.searchDialogFragment + } + + controller.handleMenuOpened() + + verify { + navController.navigateUp() + } + } + + @Test + fun handleMenuOpenedWhileSearchNotShowing() { + every { navController.currentDestination } returns mockk { + every { id } returns R.id.homeFragment + } + + controller.handleMenuOpened() + + verify(exactly = 0) { + navController.navigateUp() + } + } } diff --git a/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt index 6357cf51f..70372c1d7 100644 --- a/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/SessionControlInteractorTest.kt @@ -116,4 +116,16 @@ class SessionControlInteractorTest { interactor.onRemoveCollectionsPlaceholder() verify { controller.handleRemoveCollectionsPlaceholder() } } + + @Test + fun onCollectionMenuOpened() { + interactor.onCollectionMenuOpened() + verify { controller.handleMenuOpened() } + } + + @Test + fun onTopSiteMenuOpened() { + interactor.onTopSiteMenuOpened() + verify { controller.handleMenuOpened() } + } } diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolderTest.kt index 54d1b91f0..03036eb87 100644 --- a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSiteItemViewHolderTest.kt @@ -44,4 +44,12 @@ class TopSiteItemViewHolderTest { view.top_site_item.performClick() verify { interactor.onSelectTopSite("https://getpocket.com", TopSite.Type.DEFAULT) } } + + @Test + fun `calls interactor on long click`() { + TopSiteItemViewHolder(view, interactor).bind(pocket) + + view.top_site_item.performLongClick() + verify { interactor.onTopSiteMenuOpened() } + } } From 30d49a7227beb42baa56877eefb45e33e4382d4a Mon Sep 17 00:00:00 2001 From: Jonathan Almeida Date: Wed, 18 Nov 2020 16:31:02 -0500 Subject: [PATCH 005/202] Update Android Components to 68.0.20201118210655 --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index b942cd600..774b36b01 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "67.0.20201117190147" + const val VERSION = "68.0.20201118210655" } From 359759753c6a2697a0b45cfa8ad5badfd047d591 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Thu, 19 Nov 2020 00:08:38 +0000 Subject: [PATCH 006/202] Import l10n. --- app/src/main/res/values-br/strings.xml | 4 ++ app/src/main/res/values-cak/strings.xml | 29 +++++++++ app/src/main/res/values-cy/strings.xml | 2 + app/src/main/res/values-el/strings.xml | 2 + app/src/main/res/values-eu/strings.xml | 31 ++++++++- app/src/main/res/values-fr/strings.xml | 25 ++++++++ app/src/main/res/values-fy-rNL/strings.xml | 29 +++++++++ app/src/main/res/values-hr/strings.xml | 2 + app/src/main/res/values-hu/strings.xml | 2 + app/src/main/res/values-hy-rAM/strings.xml | 71 ++++++++++++++++++--- app/src/main/res/values-iw/strings.xml | 2 + app/src/main/res/values-kab/strings.xml | 61 +++++++++++++++++- app/src/main/res/values-nl/strings.xml | 8 +++ app/src/main/res/values-ru/strings.xml | 29 +++++++++ app/src/main/res/values-sl/strings.xml | 29 +++++++++ app/src/main/res/values-sr/strings.xml | 74 +++++++++++++++++++--- app/src/main/res/values-sv-rSE/strings.xml | 4 ++ app/src/main/res/values-tr/strings.xml | 4 ++ app/src/main/res/values-zh-rCN/strings.xml | 2 + app/src/main/res/values-zh-rTW/strings.xml | 2 + 20 files changed, 390 insertions(+), 22 deletions(-) diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml index d9ec6e4e6..325af5759 100644 --- a/app/src/main/res/values-br/strings.xml +++ b/app/src/main/res/values-br/strings.xml @@ -92,6 +92,8 @@ Argas + + Cheñchit ar mod ma vez diskouezet an ivinelloù digoret. Kit en arventennoù ha diuzit eta gael ha dindan mod-diskouez an ivinelloù. Mont en arventennoù @@ -581,6 +583,8 @@ Ivinelloù digor Enrollañ en dastumad + + Diuzañ Rannañ an holl ivinelloù diff --git a/app/src/main/res/values-cak/strings.xml b/app/src/main/res/values-cak/strings.xml index 796c1ece4..6a5a9e2b8 100644 --- a/app/src/main/res/values-cak/strings.xml +++ b/app/src/main/res/values-cak/strings.xml @@ -22,6 +22,10 @@ Wawe\' xkeq\'alajin pe ri ichinan taq ruwi\'. + + Baidu + + JD 1 ruwi\' jaqon. Tachapa\' richin nak\'ëx ruwi\'. @@ -97,6 +101,13 @@ Tewäx + + Tijal kiwachib\'enik jaqon taq ruwi\'. Jät pa Runuk\'ulem k\'a ri\' tacha\' ri kajtz\'ikil pa peraj kitzub\'al taq ruwi\'. + + Jät pa runuk\'ulem + + Tewäx + K’ak’a’ ruwi’ @@ -601,6 +612,8 @@ Kejaq taq Ruwi\' Tiyak pa mol + + Ticha\' Kekomonïx ronojel taq ruwi\' @@ -615,8 +628,18 @@ Tib\'e pa tikirib\'äl Tik\'exlöx pa ruwi\' b\'anikil + + Yaketal + + Titz\'apïx + + Kekomonïx cha\'on taq ruwi\' + + Cha\'on kik\'utsamaj ruwi\' Tiyuj ruwi\' pa molb\'äl + + Kecha\' taq ruwi\' Titz\'apïx ruwi\' @@ -996,6 +1019,12 @@ Ruwi\' xtz\'apïx Taq ruwi\' xetz\'apïx + + ¡Taq ruwi\' xetz\'apïx! + + ¡Xeyak taq yaketal! + + Titz\'et ¡Xtz\'aqatisäx pa ri jutaqil taq ruxaq! diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml index d7eaf9179..473886979 100644 --- a/app/src/main/res/values-cy/strings.xml +++ b/app/src/main/res/values-cy/strings.xml @@ -591,6 +591,8 @@ Agor Tabiau Cadw i gasgliad + + Dewis Rhannu pob tab diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 956f05966..ede060a71 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -602,6 +602,8 @@ Ανοικτές καρτέλες Αποθήκευση στη συλλογή + + Επιλογή Κοινή χρήση όλων των καρτελών diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 5a7fd989f..dc71c3369 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -20,6 +20,10 @@ Zure fitxa pribatuak hemen erakutsiko dira. + + Baidu + + JD Irekitako fitxa bat. Sakatu fitxaz aldatzeko. @@ -95,6 +99,13 @@ Baztertu + + Aldatu irekitako fitxen diseinua. Zoaz ezarpenetara eta fitxen ikuspegian, hautatu sareta. + + Joan ezarpenetara + + Baztertu + Fitxa berria @@ -487,7 +498,7 @@ Bateria-aurrezlea ezarrita - Jarraitu gailuaren itxura + Erabili gailuaren itxura @@ -591,6 +602,8 @@ Irekitako fitxak Gorde bilduman + + Hautatu Partekatu fitxa guztiak @@ -605,8 +618,18 @@ Joan hasierara Txandakatu fitxen modua + + Egin laster-marka + + Itxi + + Partekatu hautatutako fitxak + + Hautatutako fitxen menua Kendu fitxa bildumatik + + Hautatu fitxak Itxi fitxa @@ -982,6 +1005,12 @@ Fitxa itxita Fitxak itxita + + Fitxak itxita! + + Laster-markak gordeta! + + Ikusi Gune nagusietara gehituta! diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b2e7cdc2d..f68f9305a 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -22,6 +22,10 @@ Les onglets privés sont affichés ici. + + Baidu + + JD 1 onglet ouvert. Appuyez pour changer d’onglet. @@ -94,6 +98,11 @@ Ignorer + + Modifiez la disposition des onglets ouverts. Allez dans les paramètres et sélectionnez « Grille » sous « Affichage des onglets ». + + Ignorer + Nouvel onglet @@ -590,6 +599,8 @@ Onglets ouverts Enregistrer dans une collection + + Sélectionner Partager tous les onglets @@ -604,6 +615,14 @@ Accueil Changer de mode d’onglets + + Marquer ces onglets + + Fermer ces onglets + + Partager les onglets sélectionnés + + Menu des onglets sélectionnés Supprimer l’onglet de la collection @@ -997,6 +1016,12 @@ Onglet fermé Onglets fermés + + Onglets fermés ! + + Marque-pages enregistrés ! + + Afficher Ajouté aux sites principaux ! diff --git a/app/src/main/res/values-fy-rNL/strings.xml b/app/src/main/res/values-fy-rNL/strings.xml index 43acc763c..d642e9a13 100644 --- a/app/src/main/res/values-fy-rNL/strings.xml +++ b/app/src/main/res/values-fy-rNL/strings.xml @@ -19,6 +19,10 @@ Jo priveeljepblêden wurde hjir werjûn. + + Baidu + + JD 1 iepen ljepblêd. Tik om tusken ljepblêden te wikseljen. @@ -95,6 +99,13 @@ Slute + + Wizigje de opmaak fan iepen ljepblêden. Gean nei Ynstellingen en selektearje ûnder Ljepblêdwerjefte ‘Raster’. + + Nei Ynstellingen + + Slute + Nij ljepblêd @@ -581,6 +592,8 @@ Iepen ljeplêden Yn kolleksje bewarje + + Selektearje Alle ljepblêden diele @@ -595,8 +608,18 @@ Nei startside Ljepblêdmodus wikselje + + Blêdwizer meitsje + + Slute + + Selektearre ljepblêden diele + + Menu Selektearre ljepblêden Ljepblêd út kolleksje fuortsmite + + Ljepblêden selektearje Ljepblêd slute @@ -967,6 +990,12 @@ Ljepblêd sluten Ljepblêden sluten + + Ljepblêden sluten! + + Blêdwizers bewarre! + + Werjaan Tafoege oan topwebsites! diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 67cc1f534..654f0318e 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -594,6 +594,8 @@ Otvorene kartice Spremi u zbirku + + Odaberi Podijeli sve kartice diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index b309b71f1..14f961ef3 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -596,6 +596,8 @@ Nyitott lapok Gyűjteménybe mentés + + Kijelölés Az összes lap megosztása diff --git a/app/src/main/res/values-hy-rAM/strings.xml b/app/src/main/res/values-hy-rAM/strings.xml index d1300e498..942bf7e2f 100644 --- a/app/src/main/res/values-hy-rAM/strings.xml +++ b/app/src/main/res/values-hy-rAM/strings.xml @@ -20,6 +20,10 @@ Ձեր գաղտնի ներդիրները կցուցադրվեն այստեղ: + + Baidu + + JD 1 բաց ներդիր: Հպեք՝ փոխարկելու համար: @@ -70,14 +74,6 @@ Ոչ, շնորհակալ եմ - - - Ավելի արագ անցեք Firefox-ին: Ավելացրեք վիջեթը ձեր Տնային էկրանին: - - Ավելացնել վիջեթը - - Ոչ հիմա - Դուք կարող եք կայել Firefox-ը ինքնաշխատ բացել հղումները հավելվածներում: @@ -100,6 +96,11 @@ Բաց թողնել + + Անցնել Կարգավորումներին + + Բաց թողնել + Նոր ներդիր @@ -562,6 +563,15 @@ Մեկ ամիս անց + + Ձեռքով փակեք + + Փակել մեկ օր անց + + Փակել մեկ շաբաթ անց + + Փակել մեկ ամիս անց + Բաց ներդիրներ @@ -579,6 +589,8 @@ Բացել ներդիրները Պահպանել հավաքածուում + + Ընտրել Համօգտագործել ներդիրները @@ -593,8 +605,18 @@ Անցնել Տուն Փոխարկել ներդիրի կերպը + + էջանիշ + + Փակել + + Համօգտագործել ընտրված ներդիրները + + Ընտրված ներդիրների ցանկը Հեռացնել ներդիրը հավաքածուից + + Ընտրեք ներդիրները Փակել ներդիրը @@ -629,8 +651,10 @@ Բաց ներդիրներ Հավաքածուի անունը - - Ջնջել + + Վերանվանել + + Ջնջել Ջնջել Պատմությունից @@ -683,12 +707,24 @@ Այստեղ պատմություն չկա + + Ջնջել ներբեռնումները + + Համոզվա՞ծ եք, որ ցանկանում եք մաքրել ներբեռնումերը: + + Ներբեռնումները ջնջվեցին Չկան նեևբեռնումներ Ընտրված է %1$d-ը + + Բացել + + Ջնջել + + Ներողություն. %1$s-ը չի կարող բեռնել այդ էջը: @@ -955,6 +991,12 @@ Ներդիրները փակվեցին + + Ներդիրները փակ են + + Էջանիշները պահված են + + Տեսք Ավելացվել է ընտրյալ կայքերին: @@ -1616,6 +1658,15 @@ Ցուցադրել ամենաշատ այցելվող կայքերը + + Անուն + + Ընտրյալ կայքի անունը + + Լավ + + Չեղարկել + Հեռացնել diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 3b31485dc..a3c4d8356 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -590,6 +590,8 @@ לשוניות פתוחות שמירה לאוסף + + בחירה שיתוף כל הלשוניות diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index 25cbdc48e..5ec7c3b03 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -20,6 +20,8 @@ Accaren-ik n tbaḍnit ad d-ttwaseknen dagi. + + Baidu JD @@ -98,6 +100,13 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Zgel + + Senfel taneɣruft n waccaren yeldin. Ddu ɣer yiɣewwaren syen fren iẓiki deg n yiccer. + + Ddu ɣer yiɣewwaren + + Zgel + Iccer amaynut @@ -567,6 +576,14 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Mdel s ufus + + Mdel seld yiwen wass + + + Mdel seld yiwen umalas + + Mdel seld yiwen wayyur + Iccaren yeldin @@ -584,6 +601,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Ldi acarren Sekles ɣer tagrumma + + Fren Bḍu akk accaren @@ -598,8 +617,18 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Uɣal s axxam Sken/ffer askar n waccaren + + Ticreḍt n usebter + + Mdel + + Bḍu accaren yettwafernen + + Umuɣ n waccaren i yettwafernen Kkes iccer seg tegrumma + + Fren accaren Mdel iccer @@ -635,7 +664,9 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Isem n tegrumma - + + Senfel isem + Kkes @@ -689,6 +720,13 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Ulac amazray dagi + + + Kkes isadaren + + D tidet tebɣiḍ ad tsefḍeḍ isadaren? + + Isadaren ttwakksen Ulac isidar da + Ldi + + Kkes + + Suref-aɣ. %1$s ur izmir ara ad isali asebter-nni. @@ -960,6 +1004,12 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Iccer imdel Medlen iccaren + + Accaren medlen! + + Ticraḍ n yisebtar ttwakelsent! + + Sken Rnu ɣer ismal ifazen! @@ -1628,6 +1678,15 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Sken-d ismal ittwarzan aṭas + + Isem + + Isem n useɣwen ifazen + + IH + + Sefsex + Kkes diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 52dd40042..b0631b2a8 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -102,6 +102,8 @@ Sluiten + + Wijzig de opmaak van open tabbladen. Ga naar instellingen en selecteer onder Tabbladweergave ‘Raster’. Naar Instellingen @@ -598,6 +600,8 @@ Open tabbladen In collectie opslaan + + Selecteren Alle tabbladen delen @@ -618,6 +622,8 @@ Sluiten Geselecteerde tabbladen delen + + Menu Geselecteerde tabbladen Tabblad uit collectie verwijderen @@ -1000,6 +1006,8 @@ Tabbladen gesloten! Bladwijzers opgeslagen! + + Weergeven Toegevoegd aan topwebsites! diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b7f871b5a..8be8d71a2 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -22,6 +22,10 @@ Ваши приватные вкладки будут показаны здесь. + + Baidu + + JD 1 открытая вкладка. Нажмите, чтобы переключить вкладки. @@ -99,6 +103,13 @@ Пропустить + + Измените отображение открытых вкладок. Перейдите в настройки и выберите «Сеткой» в секции «Просмотр вкладок». + + Перейти в настройки + + Закрыть + Новая вкладка @@ -594,6 +605,8 @@ Открытые вкладки Сохранить в коллекцию + + Выбрать Поделиться всеми вкладками @@ -608,8 +621,18 @@ На главную Переключить режим просмотра + + Закладка + + Закрыть + + Поделиться выбранными вкладками + + Меню выбранных вкладок Удалить вкладку из коллекции + + Выбрать вкладки Закрыть вкладку @@ -1000,6 +1023,12 @@ Вкладка закрыта Вкладки закрыты + + Вкладки закрыты! + + Закладки сохранены! + + Просмотреть Добавлено в топ сайтов! diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index d14c53ca5..2f0e41008 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -20,6 +20,10 @@ Tu bodo prikazani vaši zasebni zavihki. + + Baidu + + JD 1 odprt zavihek. Tapnite za preklop zavihkov. @@ -94,6 +98,13 @@ Zapri + + Spremenite postavitev odprtih zavihkov. Pojdite v nastavitve in v pogledu zavihkov izberite mrežo. + + Pojdi v nastavitve + + Skrij + Nov zavihek @@ -587,6 +598,8 @@ Odprti zavihki Shrani v zbirko + + Izberi Deli vse zavihke @@ -601,8 +614,18 @@ Domov Preklopi način zavihkov + + Dodaj med zaznamke + + Zapri + + Deli izbrane zavihke + + Meni izbranih zavihkov Odstrani zavihek iz zbirke + + Izberi zavihke Zapri zavihek @@ -977,6 +1000,12 @@ Zavihek zaprt Zavihki zaprti + + Zavihki zaprti! + + Zaznamki shranjeni! + + Prikaži Dodano med glavne strani! diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 74ffd75a6..e8f46dd5c 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -20,6 +20,10 @@ Ваши приватни језичци биће приказани овде. + + Baidu + + JD 1 отворен језичак. Додирните за пребацивање језичака. @@ -69,14 +73,6 @@ Не, хвала - - - Дођите брже до Firefox-а. Додајте виџет на ваш почетни екран. - - Додајте виџет - - Не сада - Можете поставити Firefox да аутоматски отвара везе у апликацијама. @@ -99,6 +95,13 @@ Одбаци + + Промените приказ отворених језичака. Идите у подешавања и одаберите мрежу под приказом језичака. + + Иди у подешавања + + Одбаци + Нови језичак @@ -564,6 +567,15 @@ После једног месеца + + Затвори ручно + + Затвори након једног дана + + Затвори након једне седмице + + Затвори након једног месеца + Отворени језичци @@ -581,6 +593,8 @@ Отворени језичци Сачувај у збирку + + Изабери Дели све језичке @@ -595,8 +609,18 @@ Иди кући Промени режим језичка + + Забелешке + + Затвори + + Дели одабране језичке + + Мени одабраних језичака Уклони језичак из колекције + + Изабери језичке Затвори језичак @@ -631,8 +655,10 @@ Отворени језичци Назив колекције - - Уклони + + Преименуј + + Уклони Избриши из историје @@ -685,12 +711,25 @@ Овде нема историјата + + Обриши преузимања + + Јесте ли сигурни да желите да обришете преузимања? + + Преузимања обрисана Овде нема преузимања Изабрано %1$d + + + Отвори + + Избриши + + Нажалост, %1$s не може учитати ту страницу. @@ -954,6 +993,12 @@ Језичак затворен Језичци затворени + + Језичци затворени! + + Забелешке сачуване! + + Прикажи Додато у омиљене странице! @@ -1621,6 +1666,15 @@ Прикажи најпосећеније странице + + Назив + + Назив омиљене странице + + У реду + + Откажи + Уклони diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 3f3f7181d..106df49c6 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -602,6 +602,8 @@ Öppna flikar Spara i samling + + Välj Dela alla flikar @@ -622,6 +624,8 @@ Stäng Dela valda flikar + + Meny för valda flikar Ta bort flik från samlingen diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 0df16948d..67f8c93aa 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -97,6 +97,8 @@ Kapat + + Açık sekmelerin görünümünü değiştirebilirsiniz. Ayarlara gidip “sekme görünümü” altından “ızgara”yı seçin. Ayarlara git @@ -591,6 +593,8 @@ Açık sekmeler Koleksiyona kaydet + + Seç Tüm sekmeleri paylaş diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 82e3ee2b3..d12e2f687 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -608,6 +608,8 @@ 打开的标签页 保存到收藏集 + + 选择 分享所有标签页 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 8a2f744ed..3f446e909 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -601,6 +601,8 @@ 開啟分頁 儲存至收藏集 + + 選擇 分享所有分頁 From 9cdfb6db4a2eb67e83359ac5ac545fa4f7d81509 Mon Sep 17 00:00:00 2001 From: jhugman Date: Thu, 19 Nov 2020 11:17:25 +0000 Subject: [PATCH 007/202] Nimbus Global Opt Out (#16543) r=gl --- app/build.gradle | 2 +- .../SettingsSubMenuDataCollectionRobot.kt | 11 ++++++++++ .../java/org/mozilla/fenix/FeatureFlags.kt | 6 ++++++ .../org/mozilla/fenix/FenixApplication.kt | 21 ------------------- .../org/mozilla/fenix/components/Analytics.kt | 19 +++++++++++++++++ .../fenix/settings/DataChoicesFragment.kt | 7 +++++-- .../java/org/mozilla/fenix/utils/Settings.kt | 2 +- app/src/main/res/values/strings.xml | 4 ++++ .../main/res/xml/data_choices_preferences.xml | 4 ++-- buildSrc/src/main/java/Dependencies.kt | 3 +-- 10 files changed, 50 insertions(+), 29 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 994dfd671..3fcbe08fd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -435,11 +435,11 @@ dependencies { implementation Deps.mozilla_feature_webcompat_reporter implementation Deps.mozilla_service_digitalassetlinks - implementation Deps.mozilla_service_experiments implementation Deps.mozilla_service_sync_logins implementation Deps.mozilla_service_firefox_accounts implementation Deps.mozilla_service_glean implementation Deps.mozilla_service_location + implementation Deps.mozilla_service_nimbus implementation Deps.mozilla_support_base implementation Deps.mozilla_support_images diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDataCollectionRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDataCollectionRobot.kt index 8fd2d9259..431680d24 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDataCollectionRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuDataCollectionRobot.kt @@ -32,10 +32,13 @@ class SettingsSubMenuDataCollectionRobot { fun verifyMarketingDataSwitchDefault() = assertMarketingDataValueSwitchDefault() + fun verifyExperimentsSwitchDefault() = assertExperimentsSwitchDefault() + fun verifyDataCollectionSubMenuItems() { verifyDataCollectionOptions() verifyUsageAndTechnicalDataSwitchDefault() verifyMarketingDataSwitchDefault() + verifyExperimentsSwitchDefault() } class Transition { @@ -76,6 +79,9 @@ private fun assertDataCollectionOptions() { onView(withText(marketingDataText)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + + onView(withText(R.string.preference_experiments_2)).check(matches(withEffectiveVisibility(Visibility.VISIBLE))) + onView(withText(R.string.preference_experiments_summary_2)).check(matches(withEffectiveVisibility(Visibility.VISIBLE))) } private fun usageAndTechnicalDataButton() = onView(withText(R.string.preference_usage_data)) @@ -87,3 +93,8 @@ private fun marketingDataButton() = onView(withText(R.string.preferences_marketi private fun assertMarketingDataValueSwitchDefault() = marketingDataButton() .assertIsEnabled(isEnabled = true) + +private fun experimentsButton() = onView(withText(R.string.preference_experiments_2)) + +private fun assertExperimentsSwitchDefault() = experimentsButton() + .assertIsEnabled(isEnabled = true) diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt index fa6389129..e53b31aac 100644 --- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt +++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -35,4 +35,10 @@ object FeatureFlags { * Enables ETP cookie purging */ val etpCookiePurging = Config.channel.isNightlyOrDebug + + /** + * Enables the Nimbus experiments library, especially the settings toggle to opt-out of + * all experiments. + */ + val nimbusExperiments = Config.channel.isNightlyOrDebug } diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index be727ba63..67299d145 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -25,7 +25,6 @@ import mozilla.components.browser.state.action.SystemAction import mozilla.components.concept.push.PushProcessor import mozilla.components.feature.addons.update.GlobalAddonDependencyProvider import mozilla.components.lib.crash.CrashReporter -import mozilla.components.service.experiments.Experiments import mozilla.components.service.glean.Glean import mozilla.components.service.glean.config.Configuration import mozilla.components.service.glean.net.ConceptFetchHttpUploader @@ -170,25 +169,6 @@ open class FenixApplication : LocaleAwareApplication(), Provider { registerActivityLifecycleCallbacks(PerformanceActivityLifecycleCallbacks(queue)) } - fun queueInitExperiments() { - @Suppress("ControlFlowWithEmptyBody") - if (settings().isExperimentationEnabled) { - queue.runIfReadyOrQueue { - Experiments.initialize( - applicationContext = applicationContext, - onExperimentsUpdated = null, - configuration = mozilla.components.service.experiments.Configuration( - httpClient = components.core.client, - kintoEndpoint = KINTO_ENDPOINT_PROD - ) - ) - } - } else { - // We should make a better way to opt out for when we have more experiments - // See https://github.com/mozilla-mobile/fenix/issues/6278 - } - } - fun queueInitStorageAndServices() { components.performance.visualCompletenessQueue.queue.runIfReadyOrQueue { GlobalScope.launch(Dispatchers.IO) { @@ -229,7 +209,6 @@ open class FenixApplication : LocaleAwareApplication(), Provider { // We init these items in the visual completeness queue to avoid them initing in the critical // startup path, before the UI finishes drawing (i.e. visual completeness). - queueInitExperiments() queueInitStorageAndServices() queueMetrics() queueReviewPrompt() diff --git a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt index 0f9288bbc..40fd9356b 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt @@ -13,8 +13,10 @@ import mozilla.components.lib.crash.service.CrashReporterService import mozilla.components.lib.crash.service.GleanCrashReporterService import mozilla.components.lib.crash.service.MozillaSocorroService import mozilla.components.lib.crash.service.SentryService +import mozilla.components.service.nimbus.Nimbus import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.Config +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.ReleaseChannel @@ -98,6 +100,23 @@ class Analytics( isMarketingDataTelemetryEnabled = { context.settings().isMarketingTelemetryEnabled } ) } + + val experiments by lazyMonitored { + Nimbus().apply { + if (FeatureFlags.nimbusExperiments) { + initialize(context) + // Global opt out state is stored in Nimbus, and shouldn't be toggled to `true` + // from the app unless the user does so from a UI control. + // However, the user may have opt-ed out of mako experiments already, so + // we should respect that setting here. + val enabled = context.settings().isExperimentationEnabled + if (!enabled) { + globalUserParticipation = enabled + } + context.settings().isExperimentationEnabled = globalUserParticipation + } + } + } } fun isSentryEnabled() = !BuildConfig.SENTRY_TOKEN.isNullOrEmpty() diff --git a/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt index be6f1a274..03f7d5deb 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/DataChoicesFragment.kt @@ -7,7 +7,7 @@ package org.mozilla.fenix.settings import android.os.Bundle import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference -import org.mozilla.fenix.Config +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.R import org.mozilla.fenix.components.metrics.MetricServiceType import org.mozilla.fenix.ext.components @@ -37,6 +37,9 @@ class DataChoicesFragment : PreferenceFragmentCompat() { } else { context.components.analytics.metrics.stop(MetricServiceType.Marketing) } + } else if (key == getPreferenceKey(R.string.pref_key_experimentation)) { + val enabled = context.settings().isExperimentationEnabled + context.components.analytics.experiments.globalUserParticipation = enabled } } } @@ -72,7 +75,7 @@ class DataChoicesFragment : PreferenceFragmentCompat() { requirePreference(R.string.pref_key_experimentation).apply { isChecked = context.settings().isExperimentationEnabled - isVisible = Config.channel.isReleaseOrBeta + isVisible = FeatureFlags.nimbusExperiments onPreferenceChangeListener = SharedPreferenceUpdater() } } diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index c1b7f4ae7..985572be6 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -231,7 +231,7 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = true ) - val isExperimentationEnabled by booleanPreference( + var isExperimentationEnabled by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_experimentation), default = true ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d4dcd07ec..607517016 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -433,6 +433,10 @@ Marketing data Shares data about what features you use in %1$s with Leanplum, our mobile marketing vendor. + + Studies + + Allows Mozilla to install and run studies Experiments diff --git a/app/src/main/res/xml/data_choices_preferences.xml b/app/src/main/res/xml/data_choices_preferences.xml index 7dc5c1cc8..8026e973a 100644 --- a/app/src/main/res/xml/data_choices_preferences.xml +++ b/app/src/main/res/xml/data_choices_preferences.xml @@ -13,6 +13,6 @@ android:title="@string/preferences_marketing_data" /> + android:summary="@string/preference_experiments_summary_2" + android:title="@string/preference_experiments_2" /> diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 52d3dcfa6..6749a0365 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -129,13 +129,12 @@ object Deps { const val mozilla_service_digitalassetlinks = "org.mozilla.components:service-digitalassetlinks:${Versions.mozilla_android_components}" - const val mozilla_service_experiments = - "org.mozilla.components:service-experiments:${Versions.mozilla_android_components}" const val mozilla_service_sync_logins = "org.mozilla.components:service-sync-logins:${Versions.mozilla_android_components}" const val mozilla_service_firefox_accounts = "org.mozilla.components:service-firefox-accounts:${Versions.mozilla_android_components}" const val mozilla_service_glean = "org.mozilla.components:service-glean:${Versions.mozilla_android_components}" const val mozilla_service_location = "org.mozilla.components:service-location:${Versions.mozilla_android_components}" + const val mozilla_service_nimbus = "org.mozilla.components:service-nimbus:${Versions.mozilla_android_components}" const val mozilla_ui_colors = "org.mozilla.components:ui-colors:${Versions.mozilla_android_components}" const val mozilla_ui_icons = "org.mozilla.components:ui-icons:${Versions.mozilla_android_components}" From 72a745a5f2ad269bccf58c66362d53b9baec154f Mon Sep 17 00:00:00 2001 From: Johan Lorenzo Date: Thu, 19 Nov 2020 15:32:35 +0100 Subject: [PATCH 008/202] Bug 1635488 - part 2: Add missing branch in version bump payload (#16650) --- taskcluster/fenix_taskgraph/transforms/version_bump.py | 4 +++- taskcluster/fenix_taskgraph/worker_types.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/taskcluster/fenix_taskgraph/transforms/version_bump.py b/taskcluster/fenix_taskgraph/transforms/version_bump.py index 9e380e2fe..dc77cb944 100644 --- a/taskcluster/fenix_taskgraph/transforms/version_bump.py +++ b/taskcluster/fenix_taskgraph/transforms/version_bump.py @@ -34,7 +34,9 @@ def resolve_keys(config, tasks): @transforms.add def build_worker_definition(config, tasks): for task in tasks: - worker_definition = {} + worker_definition = { + "branch": config.params["head_ref"].decode("utf-8"), + } task["worker"].update(worker_definition) yield task diff --git a/taskcluster/fenix_taskgraph/worker_types.py b/taskcluster/fenix_taskgraph/worker_types.py index 920037728..901793210 100644 --- a/taskcluster/fenix_taskgraph/worker_types.py +++ b/taskcluster/fenix_taskgraph/worker_types.py @@ -172,6 +172,7 @@ def build_github_release_payload(config, task, task_def): Required("bump"): bool, Optional("bump-files"): [text_type], Optional("push"): bool, + Optional("branch"): text_type, }, ) def build_version_bump_payload(config, task, task_def): @@ -196,3 +197,6 @@ def build_version_bump_payload(config, task, task_def): if worker.get('force-dry-run'): task_def['payload']['dry_run'] = True + + if worker.get("branch"): + task_def["payload"]["branch"] = worker["branch"] From 62bb4d3eb489aaf4ca45447d9fd5a67ad1303ee1 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Thu, 19 Nov 2020 15:37:48 +0000 Subject: [PATCH 009/202] Update Android Components version to 68.0.20201119143053. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 774b36b01..90ac58347 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201118210655" + const val VERSION = "68.0.20201119143053" } From 36475fc3fea3c286e0a0b7cab9bda67f39e9e82e Mon Sep 17 00:00:00 2001 From: mcarare Date: Thu, 19 Nov 2020 16:25:22 +0200 Subject: [PATCH 010/202] For #15788: Remove fixed issues from lint baseline. --- app/lint-baseline.xml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml index a5aab8c1d..5e2116b6a 100644 --- a/app/lint-baseline.xml +++ b/app/lint-baseline.xml @@ -48,17 +48,6 @@ column="6"/> - - - - - - - - Date: Thu, 19 Nov 2020 20:12:40 +0000 Subject: [PATCH 011/202] Update Android Components version to 68.0.20201119190404. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 90ac58347..1cb11e491 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201119143053" + const val VERSION = "68.0.20201119190404" } From 4e598d16fc8a16edb992d82fc54eb1b8d2837b93 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Fri, 20 Nov 2020 00:05:58 +0000 Subject: [PATCH 012/202] Import l10n. --- app/src/main/res/values-ar/strings.xml | 18 ++++++++++++++ app/src/main/res/values-ast/strings.xml | 11 ++++++++ app/src/main/res/values-co/strings.xml | 26 +++++++++++++++++++ app/src/main/res/values-oc/strings.xml | 25 +++++++++++++++++++ app/src/main/res/values-vi/strings.xml | 29 ++++++++++++++++++++++ app/src/main/res/values-zh-rTW/strings.xml | 8 +++--- 6 files changed, 113 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 94205fe97..5f2332706 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -94,6 +94,9 @@ أهمِل + + انتقل إلى الإعدادات + لسان جديد @@ -558,6 +561,9 @@ بعد شهر واحد + + أغلِق يدويًا + الألسنة المفتوحة @@ -589,6 +595,14 @@ انتقل إلى الصفحة الرئيسية بدّل وضع اللسان + + علّم + + أغلِق + + شارِك الألسنة المحددة + + قائمة الألسنة المحددة أزِل اللسان من المجموعة @@ -962,6 +976,10 @@ أُغلق اللسان أُغلقت الألسنة + + أُغلقت الألسنة! + + حُفظت العلامات! أُضيف إلى المواقع الشائعة! diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml index 248061e4e..82fa2b1f0 100644 --- a/app/src/main/res/values-ast/strings.xml +++ b/app/src/main/res/values-ast/strings.xml @@ -20,6 +20,10 @@ Equí van amosase les llingüetes privaes abiertes. + + Baidu + + JD 1 llingüeta abierta. Toca pa cambiar a otra. @@ -92,6 +96,13 @@ Escartar + + Cambia la distribución de les llingüetes abiertes. Vete a axustes y esbilla\'l rexáu na vista de llingüetes. + + Dir a Axustes + + Escartar + Llingüeta nueva diff --git a/app/src/main/res/values-co/strings.xml b/app/src/main/res/values-co/strings.xml index 2dd3c307f..275e9adf2 100644 --- a/app/src/main/res/values-co/strings.xml +++ b/app/src/main/res/values-co/strings.xml @@ -20,6 +20,10 @@ E vostre unghjette private seranu affissate quì. + + Baidu + + JD 1 unghjetta aperta. Picchichjà per cambià d’unghjetta. @@ -96,6 +100,12 @@ Ricusà + + Mudificà l’accunciamentu di l’unghjette aperte. Andate in e preferenze è selezziunate « Quadrittere » sottu « Vista di l’unghjette ». + + + Ignurà + Nova unghjetta @@ -588,6 +598,8 @@ Unghjette aperte Arregistrà in una cullezzione + + Selezziunà Sparte tutte l’unghjette @@ -602,6 +614,14 @@ Andà à l’accolta Cambià di modu d’unghjette + + Marcà st’unghjette + + Chjode st’unghjette + + Sparte l’unghjette selezziunate + + Listinu di l’unghjette selezziunate Caccià l’unghjetta da a cullezzione @@ -978,6 +998,12 @@ Unghjetta chjosa Unghjette chjose + + Unghjette chjose ! + + Indette arregistrate ! + + Affissà Aghjuntu à i siti principale ! diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index dc02dd10c..453010bf2 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -19,6 +19,10 @@ Los onglets privats se mostraràn aquí. + + Baidu + + JD 1 onglet dobèrt. Tocatz per i bascular. @@ -91,6 +95,11 @@ Ignorar + + Anar als paramètres + + Ignorar + Onglet novèl @@ -586,6 +595,8 @@ Onglets dobèrts Salvar a la colleccion + + Seleccionar Partejar totes los onglets @@ -600,6 +611,14 @@ Acuèlh Bascular de mòde d’onglets + + Marcar los onglets + + Tampar los onglets + + Partejar los onglets seleccionats + + Menú dels onglets seleccionats Suprimir l’onglet de la colleccion @@ -980,6 +999,12 @@ Onglet tampat Onglets tampats + + Onglets tampats ! + + Marcapagina apondut ! + + Veire Ajustat als sites principals diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 44909f050..e35dcce4f 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -20,6 +20,10 @@ Các thẻ riêng tư của bạn sẽ được hiển thị ở đây. + + Baidu + + JD 1 thẻ đang mở. Chạm để chuyển thẻ. @@ -93,6 +97,13 @@ Bỏ qua + + Thay đổi bố cục của các thẻ đang mở. Đi tới cài đặt và chọn lưới trong chế độ xem thẻ. + + Đi tới cài đặt + + Bỏ qua + Thẻ mới @@ -579,6 +590,8 @@ Các thẻ đang mở Lưu vào bộ sưu tập + + Chọn Chia sẻ tất cả các thẻ @@ -593,8 +606,18 @@ Đi đến trang chủ Chuyển đổi chế độ thẻ + + Đánh dấu + + Đóng + + Chia sẻ các thẻ đã chọn + + Menu thẻ đã chọn Xóa thẻ khỏi bộ sưu tập + + Chọn các thẻ Đóng thẻ @@ -965,6 +988,12 @@ Đã đóng thẻ Đã đóng các thẻ + + Đã đóng các thẻ! + + Đã lưu dấu trang! + + Xem Đã thêm vào trang web hàng đầu! diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 3f446e909..464fee941 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -966,7 +966,7 @@ 最近使用 - 登入至 Sync + 登入 Sync 傳送到所有裝置 @@ -1184,7 +1184,7 @@ 登入中… - 登入至 Firefox + 登入 Firefox 不要登入 @@ -1449,7 +1449,7 @@ 重新連結 - 登入至 Sync + 登入 Sync 儲存的登入資訊 @@ -1669,7 +1669,7 @@ 檢視您其他裝置中的分頁清單。 - 登入至 Sync + 登入 Sync 無已開啟的分頁 From 302bd45f8f4146d9fd73658b22bf1a5e5d7c48e0 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Fri, 20 Nov 2020 13:47:11 +0100 Subject: [PATCH 013/202] Add initial mergify configuration --- .mergify.yml | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .mergify.yml diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 000000000..73ec94942 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,46 @@ +pull_request_rules: + - name: Resolve conflict + conditions: + - conflict + actions: + comment: + message: This pull request has conflicts when rebasing. Could you fix it @{{author}}? 🙏 + - name: MickeyMoz - Auto Merge + conditions: + - author=MickeyMoz + - status-success=pr-complete + - files~=(Gecko.kt|AndroidComponents.kt) + actions: + review: + type: APPROVE + message: MickeyMoz 💪 + merge: + method: rebase + strict: smart + - name: L10N - Auto Merge + conditions: + - author=mozilla-l10n-automation-bot + - status-success=pr-complete + - files~=(strings.xml) + actions: + review: + type: APPROVE + message: LGTM 😎 + merge: + method: rebase + strict: smart + - name: Release automation + conditions: + - base~=releases/.* + - author=st3fan + - status-success=pr-complete + - files~=(AndroidComponents.kt) + actions: + review: + type: APPROVE + message: 🚢 + merge: + method: rebase + strict: smart + delete_head_branch: + force: false From 4f2f814bac85d18e6bcbe3bf50e5f85d400b426c Mon Sep 17 00:00:00 2001 From: Johan Lorenzo Date: Fri, 20 Nov 2020 14:45:24 +0100 Subject: [PATCH 014/202] Bug 1678572 - A github-release shouldn't kick off releases if the bot made it (#16673) --- .taskcluster.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.taskcluster.yml b/.taskcluster.yml index ce21a56a1..3441951e6 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -104,7 +104,7 @@ tasks: tasks_for in ["action", "cron"] || (tasks_for == "github-pull-request" && pullRequestAction in ["opened", "reopened", "synchronize"]) || (tasks_for == "github-push" && head_branch[:10] != "refs/tags/") && (head_branch != "staging.tmp") && (head_branch != "trying.tmp") - || (tasks_for == "github-release" && releaseAction == "published") + || (tasks_for == "github-release" && releaseAction == "published" && (ownerEmail != "mozilla-release-automation-bot@users.noreply.github.com") && (ownerEmail != "mozilla-release-automation-bot-staging@users.noreply.github.com")) then: $let: level: From 2904ca8ac085371bd9fe1fe5fa2b65a8c205bb81 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Fri, 20 Nov 2020 13:21:28 +0000 Subject: [PATCH 015/202] Update Android Components version to 68.0.20201120125142. --- app/src/main/java/org/mozilla/fenix/components/Analytics.kt | 4 ++-- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt index 40fd9356b..fc4a8e6fb 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt @@ -102,9 +102,9 @@ class Analytics( } val experiments by lazyMonitored { - Nimbus().apply { + Nimbus(context, server = null).apply { if (FeatureFlags.nimbusExperiments) { - initialize(context) + initialize() // Global opt out state is stored in Nimbus, and shouldn't be toggled to `true` // from the app unless the user does so from a UI control. // However, the user may have opt-ed out of mako experiments already, so diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 1cb11e491..0e81992d2 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201119190404" + const val VERSION = "68.0.20201120125142" } From 2b759e9d6f326bf20faa4ca6091a2558b90b47f6 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Mon, 9 Nov 2020 12:16:48 +0100 Subject: [PATCH 016/202] Integrate new search code from Android Components into Fenix. --- .../fenix/ui/robots/HomeScreenRobot.kt | 6 +- .../java/org/mozilla/fenix/HomeActivity.kt | 26 +- .../org/mozilla/fenix/components/Analytics.kt | 3 +- .../mozilla/fenix/components/Components.kt | 2 - .../java/org/mozilla/fenix/components/Core.kt | 22 +- .../org/mozilla/fenix/components/Search.kt | 37 --- .../org/mozilla/fenix/components/UseCases.kt | 6 +- .../mozilla/fenix/components/metrics/Event.kt | 4 +- .../components/metrics/GleanMetricsService.kt | 26 +- .../fenix/components/metrics/MetricsUtils.kt | 13 +- .../components/search/SearchMigration.kt | 70 ++++ .../searchengine/CustomSearchEngineStore.kt | 129 -------- .../searchengine/FenixSearchEngineProvider.kt | 306 ------------------ .../searchengine/SearchEngineWriter.kt | 84 ----- .../java/org/mozilla/fenix/ext/Context.kt | 7 - .../org/mozilla/fenix/home/HomeFragment.kt | 62 ++-- .../intent/SpeechProcessingIntentProcessor.kt | 53 ++- .../SessionControlController.kt | 13 +- .../fenix/search/SearchDialogController.kt | 67 ++-- .../fenix/search/SearchDialogFragment.kt | 44 +-- .../fenix/search/SearchDialogInteractor.kt | 2 +- .../fenix/search/SearchFragmentStore.kt | 68 ++-- .../search/awesomebar/AwesomeBarInteractor.kt | 2 +- .../fenix/search/awesomebar/AwesomeBarView.kt | 35 +- .../awesomebar/ShortcutsSuggestionProvider.kt | 11 +- .../fenix/search/ext/SearchEngineProvider.kt | 17 - .../fenix/search/toolbar/ToolbarView.kt | 24 +- .../search/AddSearchEngineFragment.kt | 108 +++---- .../search/EditCustomSearchEngineFragment.kt | 73 ++--- .../search/RadioSearchEngineListPreference.kt | 156 ++++++++- .../settings/search/SearchEngineFragment.kt | 4 - .../search/SearchEngineListPreference.kt | 242 -------------- app/src/main/res/navigation/nav_graph.xml | 3 - .../fenix/MigratingFenixApplication.kt | 1 - .../fenix/components/TestComponents.kt | 2 - .../metrics/GleanMetricsServiceTest.kt | 5 +- .../metrics/MetricsUtilsTestRoboelectric.kt | 16 +- .../components/metrics/PerformedSearchTest.kt | 207 ------------ .../FenixSearchEngineProviderTest.kt | 174 ---------- .../DefaultSessionControlControllerTest.kt | 28 +- .../SpeechProcessingIntentProcessorTest.kt | 59 ++-- .../search/SearchDialogControllerTest.kt | 10 +- .../search/SearchDialogInteractorTest.kt | 2 +- .../fenix/search/SearchFragmentStoreTest.kt | 167 +++++++--- .../ShortcutsSuggestionProviderTest.kt | 38 +-- .../fenix/search/toolbar/ToolbarViewTest.kt | 3 +- .../perf/MozillaUseLazyMonitored.kt | 1 - 47 files changed, 801 insertions(+), 1637 deletions(-) delete mode 100644 app/src/main/java/org/mozilla/fenix/components/Search.kt create mode 100644 app/src/main/java/org/mozilla/fenix/components/search/SearchMigration.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/components/searchengine/CustomSearchEngineStore.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/components/searchengine/FenixSearchEngineProvider.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/components/searchengine/SearchEngineWriter.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/search/ext/SearchEngineProvider.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineListPreference.kt delete mode 100644 app/src/test/java/org/mozilla/fenix/components/metrics/PerformedSearchTest.kt delete mode 100644 app/src/test/java/org/mozilla/fenix/components/searchengine/FenixSearchEngineProviderTest.kt diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt index a68b19f32..a4234354c 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt @@ -38,6 +38,7 @@ import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.Until import androidx.test.uiautomator.Until.findObject import mozilla.components.support.ktx.android.content.appName +import mozilla.components.browser.state.state.searchEngines import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.containsString import org.hamcrest.CoreMatchers.instanceOf @@ -45,7 +46,7 @@ import org.hamcrest.CoreMatchers.not import org.hamcrest.Matchers import org.junit.Assert import org.mozilla.fenix.R -import org.mozilla.fenix.components.Search +import org.mozilla.fenix.ext.components import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText @@ -579,10 +580,11 @@ private fun verifySearchEngineIcon(searchEngineIcon: Bitmap, searchEngineName: S } private fun getSearchEngine(searchEngineName: String) = - Search(appContext).searchEngineManager.getDefaultSearchEngine(appContext, searchEngineName) + appContext.components.core.store.state.search.searchEngines.find { it.name == searchEngineName } private fun verifySearchEngineIcon(searchEngineName: String) { val ddgSearchEngine = getSearchEngine(searchEngineName) + ?: throw AssertionError("No search engine with name $searchEngineName") verifySearchEngineIcon(ddgSearchEngine.icon, ddgSearchEngine.name) } diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 67f662879..a8bfd89d9 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -37,7 +37,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import mozilla.components.browser.search.SearchEngine +import mozilla.components.browser.state.search.SearchEngine import mozilla.components.browser.state.selector.getNormalOrPrivateTabs import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.WebExtensionState @@ -46,6 +46,7 @@ import mozilla.components.concept.engine.EngineView import mozilla.components.feature.contextmenu.DefaultSelectionActionDelegate import mozilla.components.feature.privatemode.notification.PrivateNotificationFeature import mozilla.components.feature.search.BrowserStoreSearchAdapter +import mozilla.components.feature.search.ext.legacy import mozilla.components.service.fxa.sync.SyncReason import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.ktx.android.arch.lifecycle.addObservers @@ -139,7 +140,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { private val externalSourceIntentProcessors by lazy { listOf( - SpeechProcessingIntentProcessor(this, components.analytics.metrics), + SpeechProcessingIntentProcessor(this, components.core.store, components.analytics.metrics), StartSearchIntentProcessor(components.analytics.metrics), DeepLinkIntentProcessor(this, components.analytics.leanplumMetricsService), OpenBrowserIntentProcessor(this, ::getIntentSessionId), @@ -737,23 +738,24 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { } } else components.useCases.sessionUseCases.loadUrl - val searchUseCase: (String) -> Unit = { searchTerms -> + // In situations where we want to perform a search but have no search engine (e.g. the user + // has removed all of them, or we couldn't load any) we will pass searchTermOrURL to Gecko + // and let it try to load whatever was entered. + if ((!forceSearch && searchTermOrURL.isUrl()) || engine == null) { + loadUrlUseCase.invoke(searchTermOrURL.toNormalizedUrl(), flags) + } else { if (newTab) { components.useCases.searchUseCases.newTabSearch .invoke( - searchTerms, + searchTermOrURL, SessionState.Source.USER_ENTERED, true, mode.isPrivate, - searchEngine = engine + searchEngine = engine.legacy() ) - } else components.useCases.searchUseCases.defaultSearch.invoke(searchTerms, engine) - } - - if (!forceSearch && searchTermOrURL.isUrl()) { - loadUrlUseCase.invoke(searchTermOrURL.toNormalizedUrl(), flags) - } else { - searchUseCase.invoke(searchTermOrURL) + } else { + components.useCases.searchUseCases.defaultSearch.invoke(searchTermOrURL, engine.legacy()) + } } if (components.core.engine.profiler?.isProfilerActive() == true) { diff --git a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt index fc4a8e6fb..6510777aa 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt @@ -24,6 +24,7 @@ import org.mozilla.fenix.components.metrics.AdjustMetricsService import org.mozilla.fenix.components.metrics.GleanMetricsService import org.mozilla.fenix.components.metrics.LeanplumMetricsService import org.mozilla.fenix.components.metrics.MetricController +import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings import org.mozilla.fenix.perf.lazyMonitored import org.mozilla.fenix.utils.Mockable @@ -92,7 +93,7 @@ class Analytics( val metrics: MetricController by lazyMonitored { MetricController.create( listOf( - GleanMetricsService(context), + GleanMetricsService(context, lazy { context.components.core.store }), leanplumMetricsService, AdjustMetricsService(context as Application) ), diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index 38ee5d26c..55f7b8035 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -54,14 +54,12 @@ class Components(private val context: Context) { } val services by lazyMonitored { Services(context, backgroundServices.accountManager) } val core by lazyMonitored { Core(context, analytics.crashReporter, strictMode) } - val search by lazyMonitored { Search(context) } val useCases by lazyMonitored { UseCases( context, core.engine, core.sessionManager, core.store, - search.searchEngineManager, core.webAppShortcutManager, core.topSitesStorage ) 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 7e7aee6ae..644319e3a 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -46,6 +46,8 @@ import mozilla.components.feature.pwa.ManifestStorage import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.readerview.ReaderViewMiddleware import mozilla.components.feature.recentlyclosed.RecentlyClosedMiddleware +import mozilla.components.feature.search.middleware.SearchMiddleware +import mozilla.components.feature.search.region.RegionMiddleware import mozilla.components.feature.session.HistoryDelegate import mozilla.components.feature.top.sites.DefaultTopSitesStorage import mozilla.components.feature.top.sites.PinnedSiteStorage @@ -57,14 +59,18 @@ import mozilla.components.lib.dataprotect.generateEncryptionKey import mozilla.components.service.digitalassetlinks.RelationChecker import mozilla.components.service.digitalassetlinks.local.StatementApi import mozilla.components.service.digitalassetlinks.local.StatementRelationChecker +import mozilla.components.service.location.LocationService +import mozilla.components.service.location.MozillaLocationService import mozilla.components.service.sync.logins.SyncableLoginsStorage import mozilla.components.support.locale.LocaleManager import org.mozilla.fenix.AppRequestInterceptor +import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.Config import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.perf.StrictModeManager import org.mozilla.fenix.TelemetryMiddleware +import org.mozilla.fenix.components.search.SearchMigration import org.mozilla.fenix.downloads.DownloadService import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings @@ -153,6 +159,14 @@ class Core( SessionStorage(context, engine = engine) } + private val locationService: LocationService by lazyMonitored { + if (Config.channel.isDebug || BuildConfig.MLS_TOKEN.isEmpty()) { + LocationService.default() + } else { + MozillaLocationService(context, client, BuildConfig.MLS_TOKEN) + } + } + /** * The [BrowserStore] holds the global [BrowserState]. */ @@ -169,7 +183,13 @@ class Core( metrics ), ThumbnailsMiddleware(thumbnailStorage), - UndoMiddleware(::lookupSessionManager, context.getUndoDelay()) + UndoMiddleware(::lookupSessionManager, context.getUndoDelay()), + RegionMiddleware(context, locationService), + SearchMiddleware( + context, + additionalBundledSearchEngineIds = listOf("reddit", "youtube"), + migration = SearchMigration(context) + ) ) + EngineMiddleware.create(engine, ::findSessionById) ).also { it.dispatch(RecentlyClosedAction.InitializeRecentlyClosedState) diff --git a/app/src/main/java/org/mozilla/fenix/components/Search.kt b/app/src/main/java/org/mozilla/fenix/components/Search.kt deleted file mode 100644 index 9b752b20a..000000000 --- a/app/src/main/java/org/mozilla/fenix/components/Search.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.components - -import android.content.Context -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import mozilla.components.browser.search.SearchEngineManager -import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider -import org.mozilla.fenix.perf.lazyMonitored -import org.mozilla.fenix.utils.Mockable - -/** - * Component group for all search engine integration related functionality. - */ -@Mockable -class Search(private val context: Context) { - val provider = FenixSearchEngineProvider(context) - - /** - * This component provides access to a centralized registry of search engines. - */ - val searchEngineManager by lazyMonitored { - SearchEngineManager( - coroutineContext = IO, - providers = listOf(provider) - ).apply { - registerForLocaleUpdates(context) - GlobalScope.launch { - defaultSearchEngine = provider.getDefaultEngine(context) - } - } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/components/UseCases.kt b/app/src/main/java/org/mozilla/fenix/components/UseCases.kt index 86b9bfabc..317a971fd 100644 --- a/app/src/main/java/org/mozilla/fenix/components/UseCases.kt +++ b/app/src/main/java/org/mozilla/fenix/components/UseCases.kt @@ -5,7 +5,6 @@ package org.mozilla.fenix.components import android.content.Context -import mozilla.components.browser.search.SearchEngineManager import mozilla.components.browser.session.SessionManager import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.Engine @@ -15,7 +14,7 @@ import mozilla.components.feature.downloads.DownloadsUseCases import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.pwa.WebAppUseCases import mozilla.components.feature.search.SearchUseCases -import mozilla.components.browser.search.ext.toDefaultSearchEngineProvider +import mozilla.components.feature.search.ext.toDefaultSearchEngineProvider import mozilla.components.feature.session.SessionUseCases import mozilla.components.feature.session.SettingsUseCases import mozilla.components.feature.session.TrackingProtectionUseCases @@ -36,7 +35,6 @@ class UseCases( private val engine: Engine, private val sessionManager: SessionManager, private val store: BrowserStore, - private val searchEngineManager: SearchEngineManager, private val shortcutManager: WebAppShortcutManager, private val topSitesStorage: TopSitesStorage ) { @@ -56,7 +54,7 @@ class UseCases( val searchUseCases by lazyMonitored { SearchUseCases( store, - searchEngineManager.toDefaultSearchEngineProvider(context), + store.toDefaultSearchEngineProvider(), sessionManager ) } diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt index d25cf3770..87d10a519 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt @@ -6,7 +6,7 @@ package org.mozilla.fenix.components.metrics import android.content.Context import mozilla.components.browser.errorpages.ErrorType -import mozilla.components.browser.search.SearchEngine +import mozilla.components.browser.state.search.SearchEngine import mozilla.components.feature.top.sites.TopSite import org.mozilla.fenix.GleanMetrics.Addons import org.mozilla.fenix.GleanMetrics.AppTheme @@ -404,7 +404,7 @@ sealed class Event { // https://github.com/mozilla-mobile/fenix/issues/1607 // Sanitize identifiers for custom search engines. val identifier: String - get() = if (isCustom) "custom" else engine.identifier + get() = if (isCustom) "custom" else engine.id val searchEngine: SearchEngine get() = when (this) { diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt index b624e2906..82e462d7b 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt @@ -5,6 +5,9 @@ package org.mozilla.fenix.components.metrics import android.content.Context +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.feature.search.ext.legacy +import mozilla.components.feature.search.ext.waitForSelectedOrDefaultSearchEngine import mozilla.components.service.fxa.manager.SyncEnginesStorage import mozilla.components.service.glean.Glean import mozilla.components.service.glean.private.NoExtraKeys @@ -682,6 +685,7 @@ private val Event.wrapper: EventWrapper<*>? class GleanMetricsService( private val context: Context, + private val store: Lazy, private val browsersCache: BrowsersCache = BrowsersCache, private val mozillaProductDetector: MozillaProductDetector = MozillaProductDetector ) : MetricsService { @@ -756,20 +760,18 @@ class GleanMetricsService( closeTabSetting.set(context.settings().getTabTimeoutPingString()) } - SearchDefaultEngine.apply { - val defaultEngine = context - .components - .search - .searchEngineManager - .defaultSearchEngine ?: return@apply + store.value.waitForSelectedOrDefaultSearchEngine { searchEngine -> + if (searchEngine != null) { + SearchDefaultEngine.apply { + code.set(searchEngine.id) + name.set(searchEngine.name) + submissionUrl.set(searchEngine.legacy().buildSearchUrl("")) + } + } - code.set(defaultEngine.identifier) - name.set(defaultEngine.name) - submissionUrl.set(defaultEngine.buildSearchUrl("")) + activationPing.checkAndSend() + installationPing.checkAndSend() } - - activationPing.checkAndSend() - installationPing.checkAndSend() } private fun setPreferenceMetrics() { diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/MetricsUtils.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/MetricsUtils.kt index 2c9bf3eff..18b53961a 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/MetricsUtils.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/MetricsUtils.kt @@ -12,11 +12,12 @@ import com.google.android.gms.common.GooglePlayServicesNotAvailableException import com.google.android.gms.common.GooglePlayServicesRepairableException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import mozilla.components.browser.search.SearchEngine +import mozilla.components.browser.state.search.SearchEngine +import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine +import mozilla.components.browser.state.store.BrowserStore import mozilla.components.support.base.log.logger.Logger import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint -import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore -import org.mozilla.fenix.ext.searchEngineManager +import org.mozilla.fenix.ext.components import java.io.IOException import java.security.NoSuchAlgorithmException import java.security.spec.InvalidKeySpecException @@ -26,11 +27,11 @@ import javax.crypto.spec.PBEKeySpec object MetricsUtils { fun createSearchEvent( engine: SearchEngine, - context: Context, + store: BrowserStore, searchAccessPoint: SearchAccessPoint ): Event.PerformedSearch? { - val isShortcut = engine != context.searchEngineManager.defaultSearchEngine - val isCustom = CustomSearchEngineStore.isCustomSearchEngine(context, engine.identifier) + val isShortcut = engine != store.state.search.selectedOrDefaultSearchEngine + val isCustom = engine.type == SearchEngine.Type.CUSTOM val engineSource = if (isShortcut) Event.PerformedSearch.EngineSource.Shortcut(engine, isCustom) diff --git a/app/src/main/java/org/mozilla/fenix/components/search/SearchMigration.kt b/app/src/main/java/org/mozilla/fenix/components/search/SearchMigration.kt new file mode 100644 index 000000000..9d421a82d --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/search/SearchMigration.kt @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.components.search + +import android.content.Context +import android.content.SharedPreferences +import mozilla.components.browser.search.SearchEngineParser +import mozilla.components.browser.state.search.SearchEngine +import mozilla.components.feature.search.ext.migrate +import mozilla.components.feature.search.middleware.SearchMiddleware +import org.mozilla.fenix.ext.components +import org.xmlpull.v1.XmlPullParserException +import java.io.BufferedInputStream +import java.io.IOException + +private const val PREF_FILE_SEARCH_ENGINES = "custom-search-engines" + +private const val PREF_KEY_CUSTOM_SEARCH_ENGINES = "pref_custom_search_engines" +private const val PREF_KEY_MIGRATED = "pref_search_migrated" + +/** + * Helper class to migrate the search related data in Fenix to the "Android Components" implementation. + */ +internal class SearchMigration( + private val context: Context +) : SearchMiddleware.Migration { + + override fun getValuesToMigrate(): SearchMiddleware.Migration.MigrationValues? { + val preferences = context.getSharedPreferences(PREF_FILE_SEARCH_ENGINES, Context.MODE_PRIVATE) + if (preferences.getBoolean(PREF_KEY_MIGRATED, false)) { + return null + } + + val values = SearchMiddleware.Migration.MigrationValues( + customSearchEngines = loadCustomSearchEngines(preferences), + defaultSearchEngineName = context.components.settings.defaultSearchEngineName + ) + + preferences.edit() + .putBoolean(PREF_KEY_MIGRATED, true) + .apply() + + return values + } + + private fun loadCustomSearchEngines( + preferences: SharedPreferences + ): List { + val ids = preferences.getStringSet(PREF_KEY_CUSTOM_SEARCH_ENGINES, emptySet()) ?: emptySet() + + val parser = SearchEngineParser() + + return ids.mapNotNull { id -> + val xml = preferences.getString(id, null) + parser.loadSafely(id, xml?.byteInputStream()?.buffered()) + } + } +} + +private fun SearchEngineParser.loadSafely(id: String, stream: BufferedInputStream?): SearchEngine? { + return try { + stream?.let { load(id, it).migrate() } + } catch (e: IOException) { + null + } catch (e: XmlPullParserException) { + null + } +} diff --git a/app/src/main/java/org/mozilla/fenix/components/searchengine/CustomSearchEngineStore.kt b/app/src/main/java/org/mozilla/fenix/components/searchengine/CustomSearchEngineStore.kt deleted file mode 100644 index ef0c840e5..000000000 --- a/app/src/main/java/org/mozilla/fenix/components/searchengine/CustomSearchEngineStore.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.components.searchengine - -import android.content.Context -import android.content.SharedPreferences -import mozilla.components.browser.icons.IconRequest -import mozilla.components.browser.search.SearchEngine -import mozilla.components.browser.search.SearchEngineParser -import mozilla.components.browser.search.provider.SearchEngineList -import mozilla.components.browser.search.provider.SearchEngineProvider -import mozilla.components.support.ktx.android.content.PreferencesHolder -import mozilla.components.support.ktx.android.content.stringSetPreference -import org.mozilla.fenix.ext.components - -/** - * SearchEngineProvider implementation to load user entered custom search engines. - */ -class CustomSearchEngineProvider : SearchEngineProvider { - override suspend fun loadSearchEngines(context: Context): SearchEngineList { - return SearchEngineList(CustomSearchEngineStore.loadCustomSearchEngines(context), null) - } -} - -/** - * Object to handle storing custom search engines - */ -object CustomSearchEngineStore { - class EngineNameAlreadyExists : Exception() - - /** - * Add a search engine to the store. - * @param context [Context] used for various Android interactions. - * @param engineName The name of the search engine - * @param searchQuery The templated search string for the search engine - * @throws EngineNameAlreadyExists if you try to add a search engine that already exists - */ - suspend fun addSearchEngine(context: Context, engineName: String, searchQuery: String) { - val storage = engineStorage(context) - if (storage.customSearchEngineIds.contains(engineName)) { throw EngineNameAlreadyExists() } - - val icon = context.components.core.icons.loadIcon(IconRequest(searchQuery)).await() - val searchEngineXml = SearchEngineWriter.buildSearchEngineXML(engineName, searchQuery, icon.bitmap) - val engines = storage.customSearchEngineIds.toMutableSet() - engines.add(engineName) - storage.customSearchEngineIds = engines - storage[engineName] = searchEngineXml - } - - /** - * Updates an existing search engine. - * To prevent duplicate search engines we want to remove the old engine before adding the new one - * @param context [Context] used for various Android interactions. - * @param oldEngineName the name of the engine you want to replace - * @param newEngineName the name of the engine you want to save - * @param searchQuery The templated search string for the search engine - */ - suspend fun updateSearchEngine( - context: Context, - oldEngineName: String, - newEngineName: String, - searchQuery: String - ) { - removeSearchEngine(context, oldEngineName) - addSearchEngine(context, newEngineName, searchQuery) - } - - /** - * Removes a search engine from the store - * @param context [Context] used for various Android interactions. - * @param engineId the id of the engine you want to remove - */ - fun removeSearchEngine(context: Context, engineId: String) { - val storage = engineStorage(context) - val customEngines = storage.customSearchEngineIds - storage.customSearchEngineIds = customEngines.filterNot { it == engineId }.toSet() - storage[engineId] = null - } - - /** - * Checks the store to see if it contains a search engine - * @param context [Context] used for various Android interactions. - * @param engineId The name of the engine to check - */ - fun isCustomSearchEngine(context: Context, engineId: String): Boolean { - val storage = engineStorage(context) - return storage.customSearchEngineIds.contains(engineId) - } - - /** - * Creates a list of [SearchEngine] from the store - * @param context [Context] used for various Android interactions. - */ - fun loadCustomSearchEngines(context: Context): List { - val storage = engineStorage(context) - val parser = SearchEngineParser() - val engines = storage.customSearchEngineIds - - return engines.mapNotNull { - val engineXml = storage[it] ?: return@mapNotNull null - val engineInputStream = engineXml.byteInputStream().buffered() - parser.load(it, engineInputStream) - } - } - - /** - * Creates a helper object to help interact with [SharedPreferences] - * @param context [Context] used for various Android interactions. - */ - private fun engineStorage(context: Context) = object : PreferencesHolder { - override val preferences: SharedPreferences - get() = context.getSharedPreferences(PREF_FILE_SEARCH_ENGINES, Context.MODE_PRIVATE) - - var customSearchEngineIds by stringSetPreference(PREF_KEY_CUSTOM_SEARCH_ENGINES, emptySet()) - - operator fun get(engineId: String): String? { - return preferences.getString(engineId, null) - } - - operator fun set(engineId: String, value: String?) { - preferences.edit().putString(engineId, value).apply() - } - } - - private const val PREF_KEY_CUSTOM_SEARCH_ENGINES = "pref_custom_search_engines" - const val PREF_FILE_SEARCH_ENGINES = "custom-search-engines" -} diff --git a/app/src/main/java/org/mozilla/fenix/components/searchengine/FenixSearchEngineProvider.kt b/app/src/main/java/org/mozilla/fenix/components/searchengine/FenixSearchEngineProvider.kt deleted file mode 100644 index 6b4b2a78d..000000000 --- a/app/src/main/java/org/mozilla/fenix/components/searchengine/FenixSearchEngineProvider.kt +++ /dev/null @@ -1,306 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.components.searchengine - -import android.content.Context -import androidx.annotation.VisibleForTesting -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.async -import kotlinx.coroutines.launch -import mozilla.components.browser.search.SearchEngine -import mozilla.components.browser.search.provider.AssetsSearchEngineProvider -import mozilla.components.browser.search.provider.SearchEngineList -import mozilla.components.browser.search.provider.SearchEngineProvider -import mozilla.components.browser.search.provider.filter.SearchEngineFilter -import mozilla.components.browser.search.provider.localization.LocaleSearchLocalizationProvider -import mozilla.components.browser.search.provider.localization.SearchLocalizationProvider -import mozilla.components.service.location.LocationService -import mozilla.components.service.location.MozillaLocationService -import mozilla.components.service.location.search.RegionSearchLocalizationProvider -import org.mozilla.fenix.BuildConfig -import org.mozilla.fenix.Config -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.perf.runBlockingIncrement -import java.util.Locale - -@SuppressWarnings("TooManyFunctions") -open class FenixSearchEngineProvider( - private val context: Context -) : SearchEngineProvider, CoroutineScope by CoroutineScope(Job() + Dispatchers.IO) { - private val shouldMockMLS = Config.channel.isDebug || BuildConfig.MLS_TOKEN.isEmpty() - private val locationService: LocationService = if (shouldMockMLS) { - LocationService.dummy() - } else { - MozillaLocationService( - context, - context.components.core.client, - BuildConfig.MLS_TOKEN - ) - } - - // We have two search engine types: one based on MLS reported region, one based only on Locale. - // There are multiple steps involved in returning the default search engine for example. - // Simplest and most effective way to make sure the MLS engines do not mix with Locale based engines - // is to use the same type of engines for the entire duration of the app's run. - // See fenix/issues/11875 - private val isRegionCachedByLocationService = locationService.hasRegionCached() - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - open val localizationProvider: SearchLocalizationProvider = - RegionSearchLocalizationProvider(locationService) - - /** - * Unfiltered list of search engines based on locale. - */ - open var baseSearchEngines = async { - AssetsSearchEngineProvider(localizationProvider) - .loadSearchEngines(context) - } - - private val loadedRegion = async { localizationProvider.determineRegion() } - - // https://github.com/mozilla-mobile/fenix/issues/9935 - // Adds a Locale search engine provider as a fallback in case the MLS lookup takes longer - // than the time it takes for a user to try to search. - private val fallbackLocationService: SearchLocalizationProvider = LocaleSearchLocalizationProvider() - private val fallBackProvider = - AssetsSearchEngineProvider(fallbackLocationService) - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - open val fallbackEngines = async { fallBackProvider.loadSearchEngines(context) } - private val fallbackRegion = async { fallbackLocationService.determineRegion() } - - /** - * Default bundled search engines based on locale. - */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - open val bundledSearchEngines = async { - val defaultEngineIdentifiers = - baseSearchEngines.await().list.map { it.identifier }.toSet() - AssetsSearchEngineProvider( - localizationProvider, - filters = listOf(object : SearchEngineFilter { - override fun filter(context: Context, searchEngine: SearchEngine): Boolean { - return BUNDLED_SEARCH_ENGINES.contains(searchEngine.identifier) && - !defaultEngineIdentifiers.contains(searchEngine.identifier) - } - }), - additionalIdentifiers = BUNDLED_SEARCH_ENGINES - ).loadSearchEngines(context) - } - - /** - * Search engines that have been manually added by a user. - */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - open var customSearchEngines = async { - CustomSearchEngineProvider().loadSearchEngines(context) - } - - private var loadedSearchEngines = refreshInstalledEngineListAsync(baseSearchEngines) - - // https://github.com/mozilla-mobile/fenix/issues/9935 - // Create new getter that will return the fallback SearchEngineList if - // the main one hasn't completed yet - private val searchEngines: Deferred - get() = - if (isRegionCachedByLocationService) { - loadedSearchEngines - } else { - refreshInstalledEngineListAsync(fallbackEngines) - } - - fun getDefaultEngine(context: Context): SearchEngine { - val engines = installedSearchEngines(context) - val selectedName = context.settings().defaultSearchEngineName - - return engines.list.find { it.name == selectedName } - ?: engines.default - ?: engines.list.first() - } - - // We should only be setting the default search engine here - fun setDefaultEngine(context: Context, id: String) { - val engines = installedSearchEngines(context) - val newDefault = engines.list.find { it.name == id } - ?: engines.default - ?: engines.list.first() - - context.settings().defaultSearchEngineName = newDefault.name - context.components.search.searchEngineManager.defaultSearchEngine = newDefault - } - - /** - * @return a list of all SearchEngines that are currently active. These are the engines that - * are readily available throughout the app. Includes all installed engines, both - * default and custom - */ - fun installedSearchEngines(context: Context): SearchEngineList = runBlockingIncrement { - val installedIdentifiers = installedSearchEngineIdentifiers(context) - val defaultList = searchEngines.await() - - defaultList.copy( - list = defaultList.list.filter { - installedIdentifiers.contains(it.identifier) - }.sortedBy { - it.name.toLowerCase(Locale.getDefault()) - }, - default = defaultList.default?.let { - if (installedIdentifiers.contains(it.identifier)) { - it - } else { - null - } - } - ) - } - - fun allSearchEngineIdentifiers() = runBlockingIncrement { - loadedSearchEngines.await().list.map { it.identifier } - } - - fun uninstalledSearchEngines(context: Context): SearchEngineList = runBlockingIncrement { - val installedIdentifiers = installedSearchEngineIdentifiers(context) - val engineList = loadedSearchEngines.await() - - return@runBlockingIncrement engineList.copy( - list = engineList.list.filterNot { installedIdentifiers.contains(it.identifier) } - ) - } - - override suspend fun loadSearchEngines(context: Context): SearchEngineList { - return installedSearchEngines(context) - } - - fun installSearchEngine( - context: Context, - searchEngine: SearchEngine, - isCustom: Boolean = false - ) = runBlockingIncrement { - if (isCustom) { - val searchUrl = searchEngine.getSearchTemplate() - CustomSearchEngineStore.addSearchEngine(context, searchEngine.name, searchUrl) - reload() - } else { - val installedIdentifiers = installedSearchEngineIdentifiers(context).toMutableSet() - installedIdentifiers.add(searchEngine.identifier) - prefs(context).edit() - .putStringSet( - localeAwareInstalledEnginesKey(), installedIdentifiers - ).apply() - } - } - - fun uninstallSearchEngine( - context: Context, - searchEngine: SearchEngine, - isCustom: Boolean = false - ) = runBlockingIncrement { - if (isCustom) { - CustomSearchEngineStore.removeSearchEngine(context, searchEngine.identifier) - reload() - } else { - val installedIdentifiers = installedSearchEngineIdentifiers(context).toMutableSet() - installedIdentifiers.remove(searchEngine.identifier) - prefs(context).edit().putStringSet( - localeAwareInstalledEnginesKey(), - installedIdentifiers - ).apply() - } - } - - fun reload() { - launch { - customSearchEngines = async { CustomSearchEngineProvider().loadSearchEngines(context) } - loadedSearchEngines = refreshInstalledEngineListAsync(baseSearchEngines) - } - } - - // When we change the locale we need to update the baseSearchEngines list - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - open fun updateBaseSearchEngines() { - baseSearchEngines = async { - AssetsSearchEngineProvider(localizationProvider) - .loadSearchEngines(context) - } - } - - private fun refreshInstalledEngineListAsync( - engines: Deferred - ): Deferred = async { - val engineList = engines.await() - val bundledList = bundledSearchEngines.await().list - val customList = customSearchEngines.await().list - - return@async engineList.copy(list = engineList.list + bundledList + customList) - } - - private fun prefs(context: Context) = context.getSharedPreferences( - PREF_FILE_SEARCH_ENGINES, - Context.MODE_PRIVATE - ) - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - suspend fun installedSearchEngineIdentifiers(context: Context): Set { - val prefs = prefs(context) - val installedEnginesKey = localeAwareInstalledEnginesKey() - - if (!prefs.contains(installedEnginesKey)) { - val searchEngines = - if (isRegionCachedByLocationService) { - baseSearchEngines - } else { - fallbackEngines - } - - val defaultSet = searchEngines.await() - .list - .map { it.identifier } - .toSet() - - prefs.edit().putStringSet(installedEnginesKey, defaultSet).apply() - } - - val installedIdentifiers: Set = - prefs(context).getStringSet(installedEnginesKey, setOf()) ?: setOf() - - val customEngineIdentifiers = - customSearchEngines.await().list.map { it.identifier }.toSet() - - return installedIdentifiers + customEngineIdentifiers - } - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - suspend fun localeAwareInstalledEnginesKey(): String { - val tag = if (isRegionCachedByLocationService) { - val localization = loadedRegion.await() - val region = localization.region?.let { - if (it.isEmpty()) "" else "-$it" - } - - "${localization.languageTag}$region" - } else { - val localization = fallbackRegion.await() - val region = localization.region?.let { - if (it.isEmpty()) "" else "-$it" - } - - "${localization.languageTag}$region-fallback" - } - - return "$INSTALLED_ENGINES_KEY-$tag" - } - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - companion object { - val BUNDLED_SEARCH_ENGINES = listOf("reddit", "youtube") - const val PREF_FILE_SEARCH_ENGINES = "fenix-search-engine-provider" - const val INSTALLED_ENGINES_KEY = "fenix-installed-search-engines" - } -} diff --git a/app/src/main/java/org/mozilla/fenix/components/searchengine/SearchEngineWriter.kt b/app/src/main/java/org/mozilla/fenix/components/searchengine/SearchEngineWriter.kt deleted file mode 100644 index 7366942e8..000000000 --- a/app/src/main/java/org/mozilla/fenix/components/searchengine/SearchEngineWriter.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.components.searchengine - -import android.graphics.Bitmap -import android.util.Base64 -import android.util.Log -import org.w3c.dom.Document -import java.io.ByteArrayOutputStream -import java.io.StringWriter -import javax.xml.parsers.DocumentBuilderFactory -import javax.xml.parsers.ParserConfigurationException -import javax.xml.transform.OutputKeys -import javax.xml.transform.TransformerConfigurationException -import javax.xml.transform.TransformerException -import javax.xml.transform.TransformerFactory -import javax.xml.transform.dom.DOMSource -import javax.xml.transform.stream.StreamResult - -private const val BITMAP_COMPRESS_QUALITY = 100 -private fun Bitmap.toBase64(): String { - val stream = ByteArrayOutputStream() - compress(Bitmap.CompressFormat.PNG, BITMAP_COMPRESS_QUALITY, stream) - val encodedImage = Base64.encodeToString(stream.toByteArray(), Base64.DEFAULT) - return "data:image/png;base64,$encodedImage" -} - -class SearchEngineWriter { - companion object { - private const val LOG_TAG = "SearchEngineWriter" - - fun buildSearchEngineXML(engineName: String, searchQuery: String, iconBitmap: Bitmap): String? { - try { - val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() - val rootElement = document!!.createElement("OpenSearchDescription") - rootElement.setAttribute("xmlns", "http://a9.com/-/spec/opensearch/1.1/") - document.appendChild(rootElement) - - val shortNameElement = document.createElement("ShortName") - shortNameElement.textContent = engineName - rootElement.appendChild(shortNameElement) - - val imageElement = document.createElement("Image") - imageElement.setAttribute("width", "16") - imageElement.setAttribute("height", "16") - imageElement.textContent = iconBitmap.toBase64() - rootElement.appendChild(imageElement) - - val descriptionElement = document.createElement("Description") - descriptionElement.textContent = engineName - rootElement.appendChild(descriptionElement) - - val urlElement = document.createElement("Url") - urlElement.setAttribute("type", "text/html") - - val templateSearchString = searchQuery.replace("%s", "{searchTerms}") - urlElement.setAttribute("template", templateSearchString) - rootElement.appendChild(urlElement) - - return xmlToString(document) - } catch (e: ParserConfigurationException) { - Log.e(LOG_TAG, "Couldn't create new Document for building search engine XML", e) - return null - } - } - - private fun xmlToString(doc: Document): String? { - val writer = StringWriter() - try { - val tf = TransformerFactory.newInstance().newTransformer() - tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8") - tf.transform(DOMSource(doc), StreamResult(writer)) - } catch (e: TransformerConfigurationException) { - return null - } catch (e: TransformerException) { - return null - } - - return writer.toString() - } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/ext/Context.kt b/app/src/main/java/org/mozilla/fenix/ext/Context.kt index d10637026..e12382da5 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/Context.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/Context.kt @@ -14,7 +14,6 @@ import android.view.View import android.view.ViewGroup import android.view.accessibility.AccessibilityManager import androidx.annotation.StringRes -import mozilla.components.browser.search.SearchEngineManager import mozilla.components.support.locale.LocaleManager import org.mozilla.fenix.FenixApplication import org.mozilla.fenix.components.Components @@ -41,12 +40,6 @@ val Context.components: Components val Context.metrics: MetricController get() = this.components.analytics.metrics -/** - * Helper function to get the SearchEngineManager off of context. - */ -val Context.searchEngineManager: SearchEngineManager - get() = this.components.search.searchEngineManager - fun Context.asActivity() = (this as? ContextThemeWrapper)?.baseContext as? Activity ?: this as? Activity diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 44ec66b96..94dd6c009 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -40,15 +40,26 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.fragment_home.* -import kotlinx.android.synthetic.main.fragment_home.view.* -import kotlinx.android.synthetic.main.no_collections_message.view.* +import kotlinx.android.synthetic.main.fragment_home.privateBrowsingButton +import kotlinx.android.synthetic.main.fragment_home.search_engine_icon +import kotlinx.android.synthetic.main.fragment_home.toolbarLayout +import kotlinx.android.synthetic.main.fragment_home.view.bottomBarShadow +import kotlinx.android.synthetic.main.fragment_home.view.bottom_bar +import kotlinx.android.synthetic.main.fragment_home.view.homeAppBar +import kotlinx.android.synthetic.main.fragment_home.view.menuButton +import kotlinx.android.synthetic.main.fragment_home.view.sessionControlRecyclerView +import kotlinx.android.synthetic.main.fragment_home.view.tab_button +import kotlinx.android.synthetic.main.fragment_home.view.toolbar +import kotlinx.android.synthetic.main.fragment_home.view.toolbarLayout +import kotlinx.android.synthetic.main.fragment_home.view.toolbar_wrapper +import kotlinx.android.synthetic.main.no_collections_message.view.add_tabs_to_collections_button import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import mozilla.appservices.places.BookmarkRoot import mozilla.components.browser.menu.view.MenuButton import mozilla.components.browser.session.Session @@ -58,6 +69,7 @@ import mozilla.components.browser.state.selector.getNormalOrPrivateTabs import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.selector.privateTabs import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AuthType @@ -65,9 +77,11 @@ import mozilla.components.concept.sync.OAuthAccount import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.top.sites.TopSitesConfig import mozilla.components.feature.top.sites.TopSitesFeature +import mozilla.components.lib.state.ext.consumeFlow import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.ktx.android.content.res.resolveAttribute +import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R @@ -162,6 +176,7 @@ class HomeFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + postponeEnterTransition() bundleArgs = args.toBundle() lifecycleScope.launch(IO) { @@ -227,6 +242,7 @@ class HomeFragment : Fragment() { settings = components.settings, engine = components.core.engine, metrics = components.analytics.metrics, + store = store, sessionManager = sessionManager, tabCollectionStorage = components.core.tabCollectionStorage, addTabUseCase = components.useCases.tabsUseCases.addTab, @@ -338,23 +354,7 @@ class HomeFragment : Fragment() { delay(ANIMATION_DELAY) } - viewLifecycleOwner.lifecycleScope.launch(IO) { - // This is necessary due to a bug in viewLifecycleOwner. See: - // https://github.com/mozilla-mobile/android-components/blob/master/components/lib/state/src/main/java/mozilla/components/lib/state/ext/Fragment.kt#L32-L56 - // TODO remove when viewLifecycleOwner is fixed - val context = context ?: return@launch - - val iconSize = - context.resources.getDimensionPixelSize(R.dimen.preference_icon_drawable_size) - - val searchEngine = context.components.search.provider.getDefaultEngine(context) - val searchIcon = BitmapDrawable(context.resources, searchEngine.icon) - searchIcon.setBounds(0, 0, iconSize, iconSize) - - withContext(Main) { - search_engine_icon?.setImageDrawable(searchIcon) - } - } + observeSearchEngineChanges() createHomeMenu(requireContext(), WeakReference(view.menuButton)) val tabCounterMenu = TabCounterMenu( @@ -446,6 +446,24 @@ class HomeFragment : Fragment() { } } + private fun observeSearchEngineChanges() { + consumeFlow(store) { flow -> + flow.map { state -> state.search.selectedOrDefaultSearchEngine } + .ifChanged() + .collect { searchEngine -> + if (searchEngine != null) { + val iconSize = + requireContext().resources.getDimensionPixelSize(R.dimen.preference_icon_drawable_size) + val searchIcon = BitmapDrawable(requireContext().resources, searchEngine.icon) + searchIcon.setBounds(0, 0, iconSize, iconSize) + search_engine_icon?.setImageDrawable(searchIcon) + } else { + search_engine_icon.setImageDrawable(null) + } + } + } + } + private fun removeAllTabsAndShowSnackbar(sessionCode: String) { if (sessionCode == ALL_PRIVATE_TABS) { sessionManager.removePrivateSessions() @@ -499,6 +517,7 @@ class HomeFragment : Fragment() { override fun onDestroyView() { super.onDestroyView() + _sessionControlInteractor = null sessionControlView = null bundleArgs.clear() @@ -507,6 +526,7 @@ class HomeFragment : Fragment() { override fun onStart() { super.onStart() + subscribeToTabCollections() val context = requireContext() diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessor.kt index be6fe57de..ace0aeacc 100644 --- a/app/src/main/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessor.kt +++ b/app/src/main/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessor.kt @@ -7,6 +7,9 @@ package org.mozilla.fenix.home.intent import android.content.Intent import android.os.StrictMode import androidx.navigation.NavController +import mozilla.components.browser.state.search.SearchEngine +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.feature.search.ext.waitForSelectedOrDefaultSearchEngine import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.components.metrics.Event @@ -21,30 +24,48 @@ import org.mozilla.fenix.widget.VoiceSearchActivity.Companion.SPEECH_PROCESSING */ class SpeechProcessingIntentProcessor( private val activity: HomeActivity, + private val store: BrowserStore, private val metrics: MetricController ) : HomeIntentProcessor { override fun process(intent: Intent, navController: NavController, out: Intent): Boolean { - return if (intent.extras?.getBoolean(HomeActivity.OPEN_TO_BROWSER_AND_LOAD) == true) { - out.putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, false) - activity.components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { - val searchEvent = MetricsUtils.createSearchEvent( - activity.components.search.provider.getDefaultEngine(activity), - activity, - Event.PerformedSearch.SearchAccessPoint.WIDGET + if ( + !intent.hasExtra(SPEECH_PROCESSING) || + intent.extras?.getBoolean(HomeActivity.OPEN_TO_BROWSER_AND_LOAD) != true + ) { + return false + } + + out.putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, false) + + store.waitForSelectedOrDefaultSearchEngine { searchEngine -> + if (searchEngine != null) { + launchToBrowser( + searchEngine, + intent.getStringExtra(SPEECH_PROCESSING).orEmpty() ) - searchEvent?.let { metrics.track(it) } } + } - activity.openToBrowserAndLoad( - searchTermOrURL = intent.getStringExtra(SPEECH_PROCESSING).orEmpty(), - newTab = true, - from = BrowserDirection.FromGlobal, - forceSearch = true + return true + } + + private fun launchToBrowser(searchEngine: SearchEngine, text: String) { + activity.components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { + val searchEvent = MetricsUtils.createSearchEvent( + searchEngine, + store, + Event.PerformedSearch.SearchAccessPoint.WIDGET ) - true - } else { - false + searchEvent?.let { metrics.track(it) } } + + activity.openToBrowserAndLoad( + searchTermOrURL = text, + newTab = true, + from = BrowserDirection.FromGlobal, + engine = searchEngine, + forceSearch = true + ) } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt index d52c1ac18..9e94fcaff 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt @@ -12,6 +12,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import mozilla.components.browser.session.SessionManager +import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine +import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.prompt.ShareData import mozilla.components.feature.tab.collections.TabCollection @@ -171,6 +173,7 @@ class DefaultSessionControlController( private val engine: Engine, private val metrics: MetricController, private val sessionManager: SessionManager, + private val store: BrowserStore, private val tabCollectionStorage: TabCollectionStorage, private val addTabUseCase: TabsUseCases.AddNewTabUseCase, private val fragmentStore: HomeFragmentStore, @@ -462,21 +465,23 @@ class DefaultSessionControlController( } override fun handlePasteAndGo(clipboardText: String) { + val searchEngine = store.state.search.selectedOrDefaultSearchEngine + activity.openToBrowserAndLoad( searchTermOrURL = clipboardText, newTab = true, from = BrowserDirection.FromHome, - engine = activity.components.search.provider.getDefaultEngine(activity) + engine = searchEngine ) - val event = if (clipboardText.isUrl()) { + val event = if (clipboardText.isUrl() || searchEngine == null) { Event.EnteredUrl(false) } else { val searchAccessPoint = Event.PerformedSearch.SearchAccessPoint.ACTION searchAccessPoint.let { sap -> MetricsUtils.createSearchEvent( - activity.components.search.provider.getDefaultEngine(activity), - activity, + searchEngine, + store, sap ) } diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt index c6ffbf66b..92e848bb6 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogController.kt @@ -12,9 +12,10 @@ import android.text.SpannableString import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AlertDialog import androidx.navigation.NavController -import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager +import mozilla.components.browser.state.search.SearchEngine +import mozilla.components.browser.state.store.BrowserStore import mozilla.components.support.ktx.kotlin.isUrl import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity @@ -22,7 +23,6 @@ import org.mozilla.fenix.R import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.MetricsUtils -import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore import org.mozilla.fenix.crashes.CrashListActivity import org.mozilla.fenix.ext.navigateSafe import org.mozilla.fenix.settings.SupportUtils @@ -50,7 +50,8 @@ interface SearchController { class SearchDialogController( private val activity: HomeActivity, private val sessionManager: SessionManager, - private val store: SearchFragmentStore, + private val store: BrowserStore, + private val fragmentStore: SearchFragmentStore, private val navController: NavController, private val settings: Settings, private val metrics: MetricController, @@ -81,25 +82,27 @@ class SearchDialogController( } private fun openSearchOrUrl(url: String) { + val searchEngine = fragmentStore.state.searchEngineSource.searchEngine + activity.openToBrowserAndLoad( searchTermOrURL = url, - newTab = store.state.tabId == null, + newTab = fragmentStore.state.tabId == null, from = BrowserDirection.FromSearchDialog, - engine = store.state.searchEngineSource.searchEngine + engine = searchEngine ) - val event = if (url.isUrl()) { + val event = if (url.isUrl() || searchEngine == null) { Event.EnteredUrl(false) } else { - val searchAccessPoint = when (store.state.searchAccessPoint) { + val searchAccessPoint = when (fragmentStore.state.searchAccessPoint) { Event.PerformedSearch.SearchAccessPoint.NONE -> Event.PerformedSearch.SearchAccessPoint.ACTION - else -> store.state.searchAccessPoint + else -> fragmentStore.state.searchAccessPoint } searchAccessPoint?.let { sap -> MetricsUtils.createSearchEvent( - store.state.searchEngineSource.searchEngine, - activity, + searchEngine, + store, sap ) } @@ -114,17 +117,17 @@ class SearchDialogController( override fun handleTextChanged(text: String) { // Display the search shortcuts on each entry of the search fragment (see #5308) - val textMatchesCurrentUrl = store.state.url == text - val textMatchesCurrentSearch = store.state.searchTerms == text + val textMatchesCurrentUrl = fragmentStore.state.url == text + val textMatchesCurrentSearch = fragmentStore.state.searchTerms == text - store.dispatch(SearchFragmentAction.UpdateQuery(text)) - store.dispatch( + fragmentStore.dispatch(SearchFragmentAction.UpdateQuery(text)) + fragmentStore.dispatch( SearchFragmentAction.ShowSearchShortcutEnginePicker( (textMatchesCurrentUrl || textMatchesCurrentSearch || text.isEmpty()) && settings.shouldShowSearchShortcuts ) ) - store.dispatch( + fragmentStore.dispatch( SearchFragmentAction.AllowSearchSuggestionsInPrivateModePrompt( text.isNotEmpty() && activity.browsingModeManager.mode.isPrivate && @@ -139,7 +142,7 @@ class SearchDialogController( activity.openToBrowserAndLoad( searchTermOrURL = url, - newTab = store.state.tabId == null, + newTab = fragmentStore.state.tabId == null, from = BrowserDirection.FromSearchDialog ) @@ -149,39 +152,41 @@ class SearchDialogController( override fun handleSearchTermsTapped(searchTerms: String) { clearToolbarFocus() + val searchEngine = fragmentStore.state.searchEngineSource.searchEngine + activity.openToBrowserAndLoad( searchTermOrURL = searchTerms, - newTab = store.state.tabId == null, + newTab = fragmentStore.state.tabId == null, from = BrowserDirection.FromSearchDialog, - engine = store.state.searchEngineSource.searchEngine, + engine = searchEngine, forceSearch = true ) - val searchAccessPoint = when (store.state.searchAccessPoint) { + val searchAccessPoint = when (fragmentStore.state.searchAccessPoint) { Event.PerformedSearch.SearchAccessPoint.NONE -> Event.PerformedSearch.SearchAccessPoint.SUGGESTION - else -> store.state.searchAccessPoint + else -> fragmentStore.state.searchAccessPoint } - val event = searchAccessPoint?.let { sap -> + if (searchAccessPoint != null && searchEngine != null) { MetricsUtils.createSearchEvent( - store.state.searchEngineSource.searchEngine, - activity, - sap - ) + searchEngine, + store, + searchAccessPoint + )?.apply { + metrics.track(this) + } } - event?.let { metrics.track(it) } } override fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) { - store.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine)) - val isCustom = - CustomSearchEngineStore.isCustomSearchEngine(activity, searchEngine.identifier) + fragmentStore.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine)) + val isCustom = searchEngine.type == SearchEngine.Type.CUSTOM metrics.track(Event.SearchShortcutSelected(searchEngine, isCustom)) } override fun handleSearchShortcutsButtonClicked() { - val isOpen = store.state.showSearchShortcuts - store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(!isOpen)) + val isOpen = fragmentStore.state.showSearchShortcuts + fragmentStore.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(!isOpen)) } override fun handleClickSearchEngineSettings() { diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt index 33c00362e..54d6413c6 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt @@ -15,7 +15,6 @@ import android.graphics.Typeface import android.graphics.drawable.ColorDrawable import android.os.Build import android.os.Bundle -import android.os.StrictMode import android.speech.RecognizerIntent import android.text.style.StyleSpan import android.view.LayoutInflater @@ -41,9 +40,12 @@ import kotlinx.android.synthetic.main.fragment_search_dialog.view.* import kotlinx.android.synthetic.main.search_suggestions_hint.view.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.concept.storage.HistoryStorage import mozilla.components.feature.qr.QrFeature +import mozilla.components.lib.state.ext.consumeFlow import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper @@ -52,13 +54,12 @@ import mozilla.components.support.ktx.android.content.hasCamera import mozilla.components.support.ktx.android.content.isPermissionGranted import mozilla.components.support.ktx.android.content.res.getSpanned import mozilla.components.support.ktx.android.view.hideKeyboard +import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged import mozilla.components.ui.autocomplete.InlineAutocompleteEditText import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore -import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.isKeyboardVisible @@ -67,7 +68,6 @@ import org.mozilla.fenix.ext.settings import org.mozilla.fenix.search.awesomebar.AwesomeBarView import org.mozilla.fenix.search.toolbar.ToolbarView import org.mozilla.fenix.settings.SupportUtils -import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener import org.mozilla.fenix.widget.VoiceSearchActivity typealias SearchDialogFragmentStore = SearchFragmentStore @@ -143,7 +143,8 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { SearchDialogController( activity = activity, sessionManager = requireComponents.core.sessionManager, - store = store, + store = requireComponents.core.store, + fragmentStore = store, navController = findNavController(), settings = requireContext().settings(), metrics = requireComponents.analytics.metrics, @@ -170,9 +171,6 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { view.awesome_bar ) - setShortcutsChangedListener(CustomSearchEngineStore.PREF_FILE_SEARCH_ENGINES) - setShortcutsChangedListener(FenixSearchEngineProvider.PREF_FILE_SEARCH_ENGINES) - view.awesome_bar.setOnTouchListener { _, _ -> view.hideKeyboardAndSave() false @@ -203,6 +201,14 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + consumeFlow(requireComponents.core.store) { flow -> + flow.map { state -> state.search } + .ifChanged() + .collect { search -> + store.dispatch(SearchFragmentAction.UpdateSearchState(search)) + } + } + setupConstraints(view) // When displayed above browser, dismisses dialog on clicking scrim area @@ -475,12 +481,14 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { } private fun addSearchButton(toolbarView: ToolbarView) { + val searchEngine = store.state.searchEngineSource.searchEngine + toolbarView.view.addEditAction( BrowserToolbar.Button( AppCompatResources.getDrawable(requireContext(), R.drawable.ic_microphone)!!, requireContext().getString(R.string.voice_search_content_description), visible = { - store.state.searchEngineSource.searchEngine.identifier.contains("google") && + searchEngine?.id?.contains("google") == true && isSpeechAvailable() && requireContext().settings().shouldShowVoiceSearch }, @@ -515,17 +523,6 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { private fun isSpeechAvailable(): Boolean = speechIntent.resolveActivity(requireContext().packageManager) != null - private fun setShortcutsChangedListener(preferenceFileName: String) { - requireComponents.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { - requireContext().getSharedPreferences( - preferenceFileName, - Context.MODE_PRIVATE - ).registerOnSharedPreferenceChangeListener(viewLifecycleOwner) { _, _ -> - awesomeBarView.update(store.state) - } - } - } - private fun updateClipboardSuggestion(searchState: SearchFragmentState, clipboardUrl: String?) { val shouldShowView = searchState.showClipboardSuggestions && searchState.query.isEmpty() && @@ -548,8 +545,11 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { private fun updateToolbarContentDescription(searchState: SearchFragmentState) { val urlView = toolbarView.view .findViewById(R.id.mozac_browser_toolbar_edit_url_view) - toolbarView.view.contentDescription = - searchState.searchEngineSource.searchEngine.name + ", " + urlView.hint + + searchState.searchEngineSource.searchEngine?.let { engine -> + toolbarView.view.contentDescription = engine.name + ", " + urlView.hint + } + urlView?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO } diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt index cd35bdbce..cb7385ec7 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogInteractor.kt @@ -4,8 +4,8 @@ package org.mozilla.fenix.search -import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.session.Session +import mozilla.components.browser.state.search.SearchEngine import org.mozilla.fenix.search.awesomebar.AwesomeBarInteractor import org.mozilla.fenix.search.toolbar.ToolbarInteractor diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt index e55814e21..1c6570fe9 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchFragmentStore.kt @@ -4,8 +4,11 @@ package org.mozilla.fenix.search -import mozilla.components.browser.search.SearchEngine +import mozilla.components.browser.state.search.SearchEngine import mozilla.components.browser.state.selector.findTab +import mozilla.components.browser.state.state.SearchState +import mozilla.components.browser.state.state.searchEngines +import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine import mozilla.components.lib.state.Action import mozilla.components.lib.state.State import mozilla.components.lib.state.Store @@ -13,7 +16,6 @@ import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.search.ext.areShortcutsAvailable /** * The [Store] for holding the [SearchFragmentState] and applying [SearchFragmentAction]s. @@ -29,7 +31,11 @@ class SearchFragmentStore( * Wraps a `SearchEngine` to give consumers the context that it was selected as a shortcut */ sealed class SearchEngineSource { - abstract val searchEngine: SearchEngine + abstract val searchEngine: SearchEngine? + + object None : SearchEngineSource() { + override val searchEngine: SearchEngine? = null + } data class Default(override val searchEngine: SearchEngine) : SearchEngineSource() data class Shortcut(override val searchEngine: SearchEngine) : SearchEngineSource() @@ -37,17 +43,20 @@ sealed class SearchEngineSource { /** * The state for the Search Screen + * * @property query The current search query string * @property url The current URL of the tab (if this fragment is shown for an already existing tab) * @property searchTerms The search terms used to search previously in this tab (if this fragment is shown * for an already existing tab) * @property searchEngineSource The current selected search engine with the context of how it was selected - * @property defaultEngineSource The current default search engine source + * @property defaultEngine The current default search engine (or null if none is available yet) * @property showSearchSuggestions Whether or not to show search suggestions from the search engine in the AwesomeBar * @property showSearchSuggestionsHint Whether or not to show search suggestions in private hint panel * @property showSearchShortcuts Whether or not to show search shortcuts in the AwesomeBar * @property areShortcutsAvailable Whether or not there are >=2 search engines installed - * so to know to present users with certain options or not. + * so to know to present users with certain options or not. + * @property showSearchShortcutsSetting Whether the setting for showing search shortcuts is enabled + * or disabled. * @property showClipboardSuggestions Whether or not to show clipboard suggestion in the AwesomeBar * @property showHistorySuggestions Whether or not to show history suggestions in the AwesomeBar * @property showBookmarkSuggestions Whether or not to show the bookmark suggestion in the AwesomeBar @@ -58,11 +67,12 @@ data class SearchFragmentState( val url: String, val searchTerms: String, val searchEngineSource: SearchEngineSource, - val defaultEngineSource: SearchEngineSource.Default, + val defaultEngine: SearchEngine?, val showSearchSuggestions: Boolean, val showSearchSuggestionsHint: Boolean, val showSearchShortcuts: Boolean, val areShortcutsAvailable: Boolean, + val showSearchShortcutsSetting: Boolean, val showClipboardSuggestions: Boolean, val showHistorySuggestions: Boolean, val showBookmarkSuggestions: Boolean, @@ -81,16 +91,9 @@ fun createInitialSearchFragmentState( ): SearchFragmentState { val settings = components.settings val tab = tabId?.let { components.core.store.state.findTab(it) } - val url = tab?.content?.url.orEmpty() - val currentSearchEngine = SearchEngineSource.Default( - components.search.provider.getDefaultEngine(activity) - ) - val browsingMode = activity.browsingModeManager.mode - val areShortcutsAvailable = components.search.provider.areShortcutsAvailable(activity) - - val shouldShowSearchSuggestions = when (browsingMode) { + val shouldShowSearchSuggestions = when (activity.browsingModeManager.mode) { BrowsingMode.Normal -> settings.shouldShowSearchSuggestions BrowsingMode.Private -> settings.shouldShowSearchSuggestions && settings.shouldShowSearchSuggestionsInPrivate @@ -100,14 +103,13 @@ fun createInitialSearchFragmentState( query = url, url = url, searchTerms = tab?.content?.searchTerms.orEmpty(), - searchEngineSource = currentSearchEngine, - defaultEngineSource = currentSearchEngine, + searchEngineSource = SearchEngineSource.None, + defaultEngine = null, showSearchSuggestions = shouldShowSearchSuggestions, showSearchSuggestionsHint = false, - showSearchShortcuts = url.isEmpty() && - areShortcutsAvailable && - settings.shouldShowSearchShortcuts, - areShortcutsAvailable = areShortcutsAvailable, + showSearchShortcuts = false, + areShortcutsAvailable = false, + showSearchShortcutsSetting = settings.shouldShowSearchShortcuts, showClipboardSuggestions = settings.shouldShowClipboardSuggestions, showHistorySuggestions = settings.shouldShowHistorySuggestions, showBookmarkSuggestions = settings.shouldShowBookmarkSuggestions, @@ -124,11 +126,14 @@ fun createInitialSearchFragmentState( sealed class SearchFragmentAction : Action { data class SetShowSearchSuggestions(val show: Boolean) : SearchFragmentAction() data class SearchShortcutEngineSelected(val engine: SearchEngine) : SearchFragmentAction() - data class SelectNewDefaultSearchEngine(val engine: SearchEngine) : SearchFragmentAction() data class ShowSearchShortcutEnginePicker(val show: Boolean) : SearchFragmentAction() - data class UpdateShortcutsAvailability(val areShortcutsAvailable: Boolean) : SearchFragmentAction() data class AllowSearchSuggestionsInPrivateModePrompt(val show: Boolean) : SearchFragmentAction() data class UpdateQuery(val query: String) : SearchFragmentAction() + + /** + * Updates the local `SearchFragmentState` from the global `SearchState` in `BrowserStore`. + */ + data class UpdateSearchState(val search: SearchState) : SearchFragmentAction() } /** @@ -143,15 +148,26 @@ private fun searchStateReducer(state: SearchFragmentState, action: SearchFragmen ) is SearchFragmentAction.ShowSearchShortcutEnginePicker -> state.copy(showSearchShortcuts = action.show && state.areShortcutsAvailable) - is SearchFragmentAction.UpdateShortcutsAvailability -> - state.copy(areShortcutsAvailable = action.areShortcutsAvailable) is SearchFragmentAction.UpdateQuery -> state.copy(query = action.query) - is SearchFragmentAction.SelectNewDefaultSearchEngine -> - state.copy(searchEngineSource = SearchEngineSource.Default(action.engine)) is SearchFragmentAction.AllowSearchSuggestionsInPrivateModePrompt -> state.copy(showSearchSuggestionsHint = action.show) is SearchFragmentAction.SetShowSearchSuggestions -> state.copy(showSearchSuggestions = action.show) + is SearchFragmentAction.UpdateSearchState -> { + state.copy( + defaultEngine = action.search.selectedOrDefaultSearchEngine, + areShortcutsAvailable = action.search.searchEngines.size > 1, + showSearchShortcuts = state.url.isEmpty() && + state.showSearchShortcutsSetting && + action.search.searchEngines.size > 1, + searchEngineSource = if (state.searchEngineSource !is SearchEngineSource.Shortcut) { + action.search.selectedOrDefaultSearchEngine?.let { SearchEngineSource.Default(it) } + ?: SearchEngineSource.None + } else { + state.searchEngineSource + } + ) + } } } diff --git a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarInteractor.kt b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarInteractor.kt index e0dac77d0..e8f18cb7e 100644 --- a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarInteractor.kt @@ -4,8 +4,8 @@ package org.mozilla.fenix.search.awesomebar -import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.session.Session +import mozilla.components.browser.state.search.SearchEngine /** * Interface for the AwesomeBarView Interactor. This interface is implemented by objects that want diff --git a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt index 69ea1b7a1..c24d17b6c 100644 --- a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt +++ b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt @@ -10,8 +10,8 @@ import androidx.core.graphics.BlendModeCompat.SRC_IN import androidx.core.graphics.drawable.toBitmap import mozilla.components.browser.awesomebar.BrowserAwesomeBar import mozilla.components.browser.search.DefaultSearchEngineProvider -import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.session.Session +import mozilla.components.browser.state.search.SearchEngine import mozilla.components.concept.awesomebar.AwesomeBar import mozilla.components.concept.engine.EngineSession import mozilla.components.feature.awesomebar.provider.BookmarksStorageSuggestionProvider @@ -20,9 +20,10 @@ import mozilla.components.feature.awesomebar.provider.SearchActionProvider import mozilla.components.feature.awesomebar.provider.SearchSuggestionProvider import mozilla.components.feature.awesomebar.provider.SessionSuggestionProvider import mozilla.components.feature.search.SearchUseCases -import mozilla.components.browser.search.ext.toDefaultSearchEngineProvider -import mozilla.components.feature.syncedtabs.DeviceIndicators +import mozilla.components.feature.search.ext.legacy +import mozilla.components.feature.search.ext.toDefaultSearchEngineProvider import mozilla.components.feature.session.SessionUseCases +import mozilla.components.feature.syncedtabs.DeviceIndicators import mozilla.components.feature.syncedtabs.SyncedTabsStorageSuggestionProvider import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.support.ktx.android.content.getColorFromAttr @@ -32,6 +33,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.ext.components import org.mozilla.fenix.search.SearchEngineSource import org.mozilla.fenix.search.SearchFragmentState +import mozilla.components.browser.search.SearchEngine as LegacySearchEngine /** * View that contains and configures the BrowserAwesomeBar @@ -65,7 +67,7 @@ class AwesomeBarView( private val searchUseCase = object : SearchUseCases.SearchUseCase { override fun invoke( searchTerms: String, - searchEngine: SearchEngine?, + searchEngine: mozilla.components.browser.search.SearchEngine?, parentSession: Session? ) { interactor.onSearchTermsTapped(searchTerms) @@ -75,7 +77,7 @@ class AwesomeBarView( private val shortcutSearchUseCase = object : SearchUseCases.SearchUseCase { override fun invoke( searchTerms: String, - searchEngine: SearchEngine?, + searchEngine: mozilla.components.browser.search.SearchEngine?, parentSession: Session? ) { interactor.onSearchTermsTapped(searchTerms) @@ -148,9 +150,7 @@ class AwesomeBarView( defaultSearchSuggestionProvider = SearchSuggestionProvider( context = activity, - defaultSearchEngineProvider = components.search.searchEngineManager.toDefaultSearchEngineProvider( - activity - ), + defaultSearchEngineProvider = components.core.store.toDefaultSearchEngineProvider(), searchUseCase = searchUseCase, fetchClient = components.core.client, mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS, @@ -163,9 +163,7 @@ class AwesomeBarView( defaultSearchActionProvider = SearchActionProvider( - defaultSearchEngineProvider = components.search.searchEngineManager.toDefaultSearchEngineProvider( - activity - ), + defaultSearchEngineProvider = components.core.store.toDefaultSearchEngineProvider(), searchUseCase = searchUseCase, icon = searchBitmap, showDescription = false @@ -173,7 +171,7 @@ class AwesomeBarView( shortcutsEnginePickerProvider = ShortcutsSuggestionProvider( - searchEngineProvider = components.search.provider, + store = components.core.store, context = activity, selectShortcutEngine = interactor::onSearchShortcutEngineSelected, selectShortcutEngineSettings = interactor::onClickSearchEngineSettings @@ -288,6 +286,7 @@ class AwesomeBarView( is SearchEngineSource.Shortcut -> getSuggestionProviderForEngine( state.searchEngineSource.searchEngine ) + is SearchEngineSource.None -> emptyList() } } @@ -311,22 +310,20 @@ class AwesomeBarView( BrowsingMode.Normal -> components.core.engine BrowsingMode.Private -> null } - val searchEngine = - components.search.provider.installedSearchEngines(activity).list.find { it.name == engine.name } - ?: components.search.provider.getDefaultEngine(activity) listOf( SearchActionProvider( defaultSearchEngineProvider = object : DefaultSearchEngineProvider { - override fun getDefaultSearchEngine(): SearchEngine? = searchEngine - override suspend fun retrieveDefaultSearchEngine(): SearchEngine? = - searchEngine + override fun getDefaultSearchEngine(): LegacySearchEngine? = + engine.legacy() + override suspend fun retrieveDefaultSearchEngine(): LegacySearchEngine? = + engine.legacy() }, searchUseCase = shortcutSearchUseCase, icon = searchBitmap ), SearchSuggestionProvider( - searchEngine, + engine.legacy(), shortcutSearchUseCase, components.core.client, limit = 3, diff --git a/app/src/main/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProvider.kt b/app/src/main/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProvider.kt index 9bba4db2b..039b2bf24 100644 --- a/app/src/main/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProvider.kt +++ b/app/src/main/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProvider.kt @@ -7,17 +7,18 @@ package org.mozilla.fenix.search.awesomebar import android.content.Context import androidx.appcompat.content.res.AppCompatResources import androidx.core.graphics.drawable.toBitmap -import mozilla.components.browser.search.SearchEngine +import mozilla.components.browser.state.search.SearchEngine +import mozilla.components.browser.state.state.searchEngines +import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.awesomebar.AwesomeBar import org.mozilla.fenix.R -import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider import java.util.UUID /** * A [AwesomeBar.SuggestionProvider] implementation that provides search engine suggestions. */ class ShortcutsSuggestionProvider( - private val searchEngineProvider: FenixSearchEngineProvider, + private val store: BrowserStore, private val context: Context, private val selectShortcutEngine: (engine: SearchEngine) -> Unit, private val selectShortcutEngineSettings: () -> Unit @@ -34,10 +35,10 @@ class ShortcutsSuggestionProvider( override suspend fun onInputChanged(text: String): List { val suggestions = mutableListOf() - searchEngineProvider.installedSearchEngines(context).list.mapTo(suggestions) { + store.state.search.searchEngines.mapTo(suggestions) { AwesomeBar.Suggestion( provider = this, - id = it.identifier, + id = it.id, icon = it.icon, title = it.name, onSuggestionClicked = { diff --git a/app/src/main/java/org/mozilla/fenix/search/ext/SearchEngineProvider.kt b/app/src/main/java/org/mozilla/fenix/search/ext/SearchEngineProvider.kt deleted file mode 100644 index cbb598fd8..000000000 --- a/app/src/main/java/org/mozilla/fenix/search/ext/SearchEngineProvider.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* 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.search.ext - -import android.content.Context -import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider - -private const val MINIMUM_SEARCH_ENGINES_NUMBER_TO_SHOW_SHORTCUTS = 2 - -/** - * Return if the user has *at least 2* installed search engines. - * Useful to decide whether to show / enable certain functionalities. - */ -fun FenixSearchEngineProvider.areShortcutsAvailable(context: Context) = - installedSearchEngines(context).list.size >= MINIMUM_SEARCH_ENGINES_NUMBER_TO_SHOW_SHORTCUTS diff --git a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt index 037c08708..dea282f36 100644 --- a/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt +++ b/app/src/main/java/org/mozilla/fenix/search/toolbar/ToolbarView.kt @@ -148,18 +148,22 @@ class ToolbarView( isInitialized = true } - val iconSize = - context.resources.getDimensionPixelSize(R.dimen.preference_icon_drawable_size) + val searchEngine = searchState.searchEngineSource.searchEngine - val scaledIcon = Bitmap.createScaledBitmap( - searchState.searchEngineSource.searchEngine.icon, - iconSize, - iconSize, - true - ) + if (searchEngine != null) { + val iconSize = + context.resources.getDimensionPixelSize(R.dimen.preference_icon_drawable_size) - val icon = BitmapDrawable(context.resources, scaledIcon) + val scaledIcon = Bitmap.createScaledBitmap( + searchEngine.icon, + iconSize, + iconSize, + true + ) + + val icon = BitmapDrawable(context.resources, scaledIcon) - view.edit.setIcon(icon, searchState.searchEngineSource.searchEngine.name) + view.edit.setIcon(icon, searchEngine.name) + } } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/search/AddSearchEngineFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/search/AddSearchEngineFragment.kt index d08520e2d..4521cc365 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/search/AddSearchEngineFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/search/AddSearchEngineFragment.kt @@ -19,26 +19,34 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController -import kotlinx.android.synthetic.main.custom_search_engine.* -import kotlinx.android.synthetic.main.fragment_add_search_engine.* -import kotlinx.android.synthetic.main.search_engine_radio_button.view.* +import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engine_form +import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engine_name_field +import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engine_search_string_field +import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engines_learn_more +import kotlinx.android.synthetic.main.custom_search_engine.edit_engine_name +import kotlinx.android.synthetic.main.custom_search_engine.edit_search_string +import kotlinx.android.synthetic.main.fragment_add_search_engine.search_engine_group +import kotlinx.android.synthetic.main.search_engine_radio_button.view.engine_icon +import kotlinx.android.synthetic.main.search_engine_radio_button.view.engine_text +import kotlinx.android.synthetic.main.search_engine_radio_button.view.overflow_menu +import kotlinx.android.synthetic.main.search_engine_radio_button.view.radio_button import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import mozilla.components.browser.search.SearchEngine +import mozilla.components.browser.icons.IconRequest +import mozilla.components.browser.state.search.SearchEngine +import mozilla.components.browser.state.state.availableSearchEngines +import mozilla.components.feature.search.ext.createSearchEngine import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.showToolbar -import org.mozilla.fenix.perf.runBlockingIncrement import org.mozilla.fenix.settings.SupportUtils -import java.util.Locale @SuppressWarnings("LargeClass", "TooManyFunctions") class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine), @@ -51,14 +59,13 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine), super.onCreate(savedInstanceState) setHasOptionsMenu(true) - availableEngines = runBlockingIncrement { - requireContext() - .components - .search - .provider - .uninstalledSearchEngines(requireContext()) - .list - } + availableEngines = requireContext() + .components + .core + .store + .state + .search + .availableSearchEngines selectedIndex = if (availableEngines.isEmpty()) CUSTOM_INDEX else FIRST_INDEX } @@ -72,7 +79,7 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine), ) val setupSearchEngineItem: (Int, SearchEngine) -> Unit = { index, engine -> - val engineId = engine.identifier + val engineId = engine.id val engineItem = makeButtonFromSearchEngine( engine = engine, layoutInflater = layoutInflater, @@ -123,7 +130,8 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine), CUSTOM_INDEX -> createCustomEngine() else -> { val engine = availableEngines[selectedIndex] - installEngine(engine) + requireComponents.useCases.searchUseCases.addSearchEngine(engine) + findNavController().popBackStack() } } @@ -141,9 +149,9 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine), val name = edit_engine_name.text?.toString()?.trim() ?: "" val searchString = edit_search_string.text?.toString() ?: "" - val hasError = checkForErrors(name, searchString) - - if (hasError) { return } + if (checkForErrors(name, searchString)) { + return + } viewLifecycleOwner.lifecycleScope.launch(Main) { val result = withContext(IO) { @@ -159,22 +167,14 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine), .getString(R.string.search_add_custom_engine_error_cannot_reach, name) } SearchStringValidator.Result.Success -> { - try { - CustomSearchEngineStore.addSearchEngine( - context = requireContext(), - engineName = name, - searchQuery = searchString - ) - } catch (engineNameExists: CustomSearchEngineStore.EngineNameAlreadyExists) { - custom_search_engine_name_field.error = - String.format( - resources.getString( - R.string.search_add_custom_engine_error_existing_name - ), name - ) - return@launch - } - requireComponents.search.provider.reload() + val searchEngine = createSearchEngine( + name, + searchString.toSearchUrl(), + requireComponents.core.icons.loadIcon(IconRequest(searchString)).await().bitmap + ) + + requireComponents.useCases.searchUseCases.addSearchEngine(searchEngine) + val successMessage = resources .getString(R.string.search_add_custom_engine_success_message, name) @@ -196,27 +196,12 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine), } fun checkForErrors(name: String, searchString: String): Boolean { - val existingIdentifiers = requireComponents - .search - .provider - .allSearchEngineIdentifiers() - .map { it.toLowerCase(Locale.ROOT) } - - val hasError = when { + return when { name.isEmpty() -> { custom_search_engine_name_field.error = resources .getString(R.string.search_add_custom_engine_error_empty_name) true } - existingIdentifiers.contains(name.toLowerCase(Locale.ROOT)) -> { - custom_search_engine_name_field.error = - String.format( - resources.getString( - R.string.search_add_custom_engine_error_existing_name - ), name - ) - true - } searchString.isEmpty() -> { custom_search_engine_search_string_field.error = resources.getString(R.string.search_add_custom_engine_error_empty_search_string) @@ -229,21 +214,6 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine), } else -> false } - - return hasError - } - - private fun installEngine(engine: SearchEngine) { - viewLifecycleOwner.lifecycleScope.launch(Main) { - withContext(IO) { - requireContext().components.search.provider.installSearchEngine( - requireContext(), - engine - ) - } - - findNavController().popBackStack() - } } override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { @@ -303,3 +273,7 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine), private const val FIRST_INDEX = 0 } } + +private fun String.toSearchUrl(): String { + return replace("%s", "{searchTerms}") +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/search/EditCustomSearchEngineFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/search/EditCustomSearchEngineFragment.kt index 677bd349a..bc70833fe 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/search/EditCustomSearchEngineFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/search/EditCustomSearchEngineFragment.kt @@ -4,7 +4,6 @@ package org.mozilla.fenix.settings.search -import android.net.Uri import android.os.Bundle import android.view.Menu import android.view.MenuInflater @@ -14,21 +13,23 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs -import kotlinx.android.synthetic.main.custom_search_engine.* +import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engine_name_field +import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engine_search_string_field +import kotlinx.android.synthetic.main.custom_search_engine.custom_search_engines_learn_more +import kotlinx.android.synthetic.main.custom_search_engine.edit_engine_name +import kotlinx.android.synthetic.main.custom_search_engine.edit_search_string import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import mozilla.components.browser.search.SearchEngine +import mozilla.components.browser.state.search.SearchEngine import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar -import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.settings.SupportUtils -import java.util.Locale /** * Fragment to enter a custom search engine name and URL template. @@ -41,17 +42,21 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) - searchEngine = CustomSearchEngineStore.loadCustomSearchEngines(requireContext()).first { - it.identifier == args.searchEngineIdentifier - } + + searchEngine = requireNotNull( + requireComponents.core.store.state.search.customSearchEngines.find { engine -> + engine.id == args.searchEngineIdentifier + } + ) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + val url = searchEngine.resultUrls[0] + edit_engine_name.setText(searchEngine.name) - val decodedUrl = Uri.decode(searchEngine.buildSearchUrl("%s")) - edit_search_string.setText(decodedUrl) + edit_search_string.setText(url.toEditableUrl()) custom_search_engines_learn_more.setOnClickListener { (activity as HomeActivity).openToBrowserAndLoad( @@ -92,9 +97,7 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng val name = edit_engine_name.text?.toString()?.trim() ?: "" val searchString = edit_search_string.text?.toString() ?: "" - val hasError = checkForErrors(name, searchString) - - if (hasError) { + if (checkForErrors(name, searchString)) { return } @@ -111,14 +114,15 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng custom_search_engine_search_string_field.error = resources .getString(R.string.search_add_custom_engine_error_cannot_reach, name) } + SearchStringValidator.Result.Success -> { - CustomSearchEngineStore.updateSearchEngine( - context = requireContext(), - oldEngineName = args.searchEngineIdentifier, - newEngineName = name, - searchQuery = searchString + val update = searchEngine.copy( + name = name, + resultUrls = listOf(searchString.toSearchUrl()) ) - requireComponents.search.provider.reload() + + requireComponents.useCases.searchUseCases.addSearchEngine(update) + val successMessage = resources .getString(R.string.search_edit_custom_engine_success_message, name) @@ -131,9 +135,7 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng .setText(successMessage) .show() } - if (args.isDefaultSearchEngine) { - requireComponents.search.provider.setDefaultEngine(requireContext(), name) - } + findNavController().popBackStack() } } @@ -141,28 +143,12 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng } private fun checkForErrors(name: String, searchString: String): Boolean { - val existingIdentifiers = requireComponents - .search - .provider - .allSearchEngineIdentifiers() - .map { it.toLowerCase(Locale.ROOT) } - - val nameHasChanged = name != args.searchEngineIdentifier - val hasError = when { + return when { name.isEmpty() -> { custom_search_engine_name_field.error = resources .getString(R.string.search_add_custom_engine_error_empty_name) true } - existingIdentifiers.contains(name.toLowerCase(Locale.ROOT)) && nameHasChanged -> { - custom_search_engine_name_field.error = - String.format( - resources.getString( - R.string.search_add_custom_engine_error_existing_name - ), name - ) - true - } searchString.isEmpty() -> { custom_search_engine_search_string_field.error = resources.getString(R.string.search_add_custom_engine_error_empty_search_string) @@ -175,6 +161,13 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng } else -> false } - return hasError } } + +private fun String.toEditableUrl(): String { + return replace("{searchTerms}", "%s") +} + +private fun String.toSearchUrl(): String { + return replace("%s", "{searchTerms}") +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/search/RadioSearchEngineListPreference.kt b/app/src/main/java/org/mozilla/fenix/settings/search/RadioSearchEngineListPreference.kt index 107b908dc..caea80564 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/search/RadioSearchEngineListPreference.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/search/RadioSearchEngineListPreference.kt @@ -5,27 +5,165 @@ package org.mozilla.fenix.settings.search import android.content.Context +import android.content.res.Resources +import android.graphics.drawable.BitmapDrawable import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import android.widget.CompoundButton -import mozilla.components.browser.search.SearchEngine +import android.widget.LinearLayout +import android.widget.RadioGroup +import androidx.core.view.isVisible +import androidx.navigation.Navigation +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import kotlinx.android.synthetic.main.search_engine_radio_button.view.engine_icon +import kotlinx.android.synthetic.main.search_engine_radio_button.view.engine_text +import kotlinx.android.synthetic.main.search_engine_radio_button.view.overflow_menu +import kotlinx.android.synthetic.main.search_engine_radio_button.view.radio_button +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import mozilla.components.browser.state.search.SearchEngine +import mozilla.components.browser.state.state.SearchState +import mozilla.components.browser.state.state.searchEngines +import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.lib.state.ext.flow +import mozilla.components.support.ktx.android.view.toScope +import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged import org.mozilla.fenix.R import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.ext.getRootView +import org.mozilla.fenix.utils.allowUndo class RadioSearchEngineListPreference @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.preferenceStyle -) : SearchEngineListPreference(context, attrs, defStyleAttr) { - override val itemResId: Int +) : Preference(context, attrs, defStyleAttr), CompoundButton.OnCheckedChangeListener { + val itemResId: Int get() = R.layout.search_engine_radio_button - override fun updateDefaultItem(defaultButton: CompoundButton) { - defaultButton.isChecked = true + init { + layoutResource = R.layout.preference_search_engine_chooser } - override fun onSearchEngineSelected(searchEngine: SearchEngine) { - context.components.search.provider.setDefaultEngine(context, searchEngine.identifier) - context.settings().defaultSearchEngineName = searchEngine.name + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + + subscribeToSearchEngineUpdates( + context.components.core.store, + holder.itemView + ) + } + + @OptIn(ExperimentalCoroutinesApi::class) + private fun subscribeToSearchEngineUpdates(store: BrowserStore, view: View) = view.toScope().launch { + store.flow() + .map { state -> state.search } + .ifChanged() + .collect { state -> refreshSearchEngineViews(view, state) } + } + + private fun refreshSearchEngineViews(view: View, state: SearchState) { + val searchEngineGroup = view.findViewById(R.id.search_engine_group) + searchEngineGroup!!.removeAllViews() + + val layoutInflater = LayoutInflater.from(context) + val layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + + state.searchEngines.forEach { engine -> + val searchEngineView = makeButtonFromSearchEngine( + engine = engine, + layoutInflater = layoutInflater, + res = context.resources, + allowDeletion = state.searchEngines.size > 1, + isSelected = engine == state.selectedOrDefaultSearchEngine + ) + + searchEngineGroup.addView(searchEngineView, layoutParams) + } + } + + private fun makeButtonFromSearchEngine( + engine: SearchEngine, + layoutInflater: LayoutInflater, + res: Resources, + allowDeletion: Boolean, + isSelected: Boolean + ): View { + val isCustomSearchEngine = engine.type == SearchEngine.Type.CUSTOM + + val wrapper = layoutInflater.inflate(itemResId, null) as LinearLayout + wrapper.setOnClickListener { wrapper.radio_button.isChecked = true } + wrapper.radio_button.tag = engine.id + wrapper.radio_button.isChecked = isSelected + wrapper.radio_button.setOnCheckedChangeListener(this) + wrapper.engine_text.text = engine.name + wrapper.overflow_menu.isVisible = allowDeletion || isCustomSearchEngine + wrapper.overflow_menu.setOnClickListener { + SearchEngineMenu( + context = context, + allowDeletion = allowDeletion, + isCustomSearchEngine = isCustomSearchEngine, + onItemTapped = { + when (it) { + is SearchEngineMenu.Item.Edit -> editCustomSearchEngine(wrapper, engine) + is SearchEngineMenu.Item.Delete -> deleteSearchEngine( + context, + engine + ) + } + } + ).menuBuilder.build(context).show(wrapper.overflow_menu) + } + val iconSize = res.getDimension(R.dimen.preference_icon_drawable_size).toInt() + val engineIcon = BitmapDrawable(res, engine.icon) + engineIcon.setBounds(0, 0, iconSize, iconSize) + wrapper.engine_icon.setImageDrawable(engineIcon) + return wrapper + } + + override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { + val searchEngineId = buttonView.tag.toString() + val engine = requireNotNull( + context.components.core.store.state.search.searchEngines.find { searchEngine -> + searchEngine.id == searchEngineId + } + ) + + context.components.useCases.searchUseCases.selectSearchEngine(engine) + } + + private fun editCustomSearchEngine(view: View, engine: SearchEngine) { + val directions = SearchEngineFragmentDirections + .actionSearchEngineFragmentToEditCustomSearchEngineFragment(engine.id) + + Navigation.findNavController(view).navigate(directions) + } + + private fun deleteSearchEngine( + context: Context, + engine: SearchEngine + ) { + context.components.useCases.searchUseCases.removeSearchEngine(engine) + + MainScope().allowUndo( + view = context.getRootView()!!, + message = context + .getString(R.string.search_delete_search_engine_success_message, engine.name), + undoActionTitle = context.getString(R.string.snackbar_deleted_undo), + onCancel = { + context.components.useCases.searchUseCases.addSearchEngine(engine) + }, + operation = {} + ) } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineFragment.kt index 05b3ef201..c5971e7f9 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineFragment.kt @@ -70,15 +70,11 @@ class SearchEngineFragment : PreferenceFragmentCompat() { isChecked = context.settings().shouldShowClipboardSuggestions } - val searchEngineListPreference = - requirePreference(R.string.pref_key_search_engine_list) - val showVoiceSearchPreference = requirePreference(R.string.pref_key_show_voice_search).apply { isChecked = context.settings().shouldShowVoiceSearch } - searchEngineListPreference.reload(requireContext()) searchSuggestionsPreference.onPreferenceChangeListener = SharedPreferenceUpdater() showSearchShortcuts.onPreferenceChangeListener = SharedPreferenceUpdater() showHistorySuggestions.onPreferenceChangeListener = SharedPreferenceUpdater() diff --git a/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineListPreference.kt b/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineListPreference.kt deleted file mode 100644 index c01c305d0..000000000 --- a/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineListPreference.kt +++ /dev/null @@ -1,242 +0,0 @@ -/* 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.settings.search - -import android.content.Context -import android.content.res.Resources -import android.graphics.drawable.BitmapDrawable -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.CompoundButton -import android.widget.LinearLayout -import android.widget.RadioGroup -import androidx.core.view.isVisible -import androidx.navigation.Navigation -import androidx.preference.Preference -import androidx.preference.PreferenceViewHolder -import kotlinx.android.synthetic.main.search_engine_radio_button.view.engine_icon -import kotlinx.android.synthetic.main.search_engine_radio_button.view.engine_text -import kotlinx.android.synthetic.main.search_engine_radio_button.view.overflow_menu -import kotlinx.android.synthetic.main.search_engine_radio_button.view.radio_button -import kotlinx.coroutines.MainScope -import mozilla.components.browser.search.SearchEngine -import mozilla.components.browser.search.provider.SearchEngineList -import org.mozilla.fenix.R -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.getRootView -import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.utils.allowUndo -import java.util.Locale - -abstract class SearchEngineListPreference @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = android.R.attr.preferenceStyle -) : Preference(context, attrs, defStyleAttr), CompoundButton.OnCheckedChangeListener { - - protected lateinit var searchEngineList: SearchEngineList - protected var searchEngineGroup: RadioGroup? = null - - protected abstract val itemResId: Int - - init { - layoutResource = R.layout.preference_search_engine_chooser - } - - override fun onBindViewHolder(holder: PreferenceViewHolder?) { - super.onBindViewHolder(holder) - searchEngineGroup = holder!!.itemView.findViewById(R.id.search_engine_group) - reload(searchEngineGroup!!.context) - } - - fun reload(context: Context) { - searchEngineList = context.components.search.provider.installedSearchEngines(context) - refreshSearchEngineViews(context) - } - - protected abstract fun onSearchEngineSelected(searchEngine: SearchEngine) - protected abstract fun updateDefaultItem(defaultButton: CompoundButton) - - private fun refreshSearchEngineViews(context: Context) { - if (searchEngineGroup == null) { - // We want to refresh the search engine list of this preference in onResume, - // but the first time this preference is created onResume is called before onCreateView - // so searchEngineGroup is not set yet. - return - } - - val defaultEngineId = context.components.search.provider.getDefaultEngine(context).identifier - - val selectedEngine = (searchEngineList.list.find { - it.identifier == defaultEngineId - } ?: searchEngineList.list.first()).identifier - - // set the search engine manager default - context.components.search.provider.setDefaultEngine(context, selectedEngine) - - searchEngineGroup!!.removeAllViews() - - val layoutInflater = LayoutInflater.from(context) - val layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - - val setupSearchEngineItem: (Int, SearchEngine) -> Unit = { index, engine -> - val engineId = engine.identifier - val engineItem = makeButtonFromSearchEngine( - engine = engine, - layoutInflater = layoutInflater, - res = context.resources, - allowDeletion = searchEngineList.list.size > 1 - ) - - engineItem.id = index + (searchEngineList.default?.let { 1 } ?: 0) - engineItem.tag = engineId - if (engineId == selectedEngine) { - updateDefaultItem(engineItem.radio_button) - /* #11465 -> radio_button.isChecked = true does not trigger - * onSearchEngineSelected because searchEngineGroup has null views at that point. - * So we trigger it here.*/ - onSearchEngineSelected(engine) - } - searchEngineGroup!!.addView(engineItem, layoutParams) - } - - searchEngineList.default?.apply { - setupSearchEngineItem(0, this) - } - - searchEngineList.list - .filter { it.identifier != searchEngineList.default?.identifier } - .sortedBy { it.name.toLowerCase(Locale.getDefault()) } - .forEachIndexed(setupSearchEngineItem) - } - - private fun makeButtonFromSearchEngine( - engine: SearchEngine, - layoutInflater: LayoutInflater, - res: Resources, - allowDeletion: Boolean - ): View { - val isCustomSearchEngine = - CustomSearchEngineStore.isCustomSearchEngine(context, engine.identifier) - - val wrapper = layoutInflater.inflate(itemResId, null) as LinearLayout - wrapper.setOnClickListener { wrapper.radio_button.isChecked = true } - wrapper.radio_button.setOnCheckedChangeListener(this) - wrapper.engine_text.text = engine.name - wrapper.overflow_menu.isVisible = allowDeletion || isCustomSearchEngine - wrapper.overflow_menu.setOnClickListener { - SearchEngineMenu( - context = context, - allowDeletion = allowDeletion, - isCustomSearchEngine = isCustomSearchEngine, - onItemTapped = { - when (it) { - is SearchEngineMenu.Item.Edit -> editCustomSearchEngine(engine) - is SearchEngineMenu.Item.Delete -> deleteSearchEngine( - context, - engine, - isCustomSearchEngine - ) - } - } - ).menuBuilder.build(context).show(wrapper.overflow_menu) - } - val iconSize = res.getDimension(R.dimen.preference_icon_drawable_size).toInt() - val engineIcon = BitmapDrawable(res, engine.icon) - engineIcon.setBounds(0, 0, iconSize, iconSize) - wrapper.engine_icon.setImageDrawable(engineIcon) - return wrapper - } - - override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { - searchEngineList.list.forEach { engine -> - val wrapper: LinearLayout = - searchEngineGroup?.findViewWithTag(engine.identifier) ?: return - - when (wrapper.radio_button == buttonView) { - true -> onSearchEngineSelected(engine) - false -> { - wrapper.radio_button.setOnCheckedChangeListener(null) - wrapper.radio_button.isChecked = false - wrapper.radio_button.setOnCheckedChangeListener(this) - } - } - } - } - - private fun editCustomSearchEngine(engine: SearchEngine) { - val wasDefault = context.components.search.provider.getDefaultEngine(context).identifier == engine.identifier - val directions = SearchEngineFragmentDirections - .actionSearchEngineFragmentToEditCustomSearchEngineFragment(engine.identifier, wasDefault) - Navigation.findNavController(searchEngineGroup!!).navigate(directions) - } - - private fun deleteSearchEngine( - context: Context, - engine: SearchEngine, - isCustomSearchEngine: Boolean - ) { - val isDefaultEngine = engine == context.components.search.provider.getDefaultEngine(context) - val initialEngineList = searchEngineList.copy() - val initialDefaultEngine = searchEngineList.default - - context.components.search.provider.uninstallSearchEngine( - context, - engine, - isCustomSearchEngine - ) - - MainScope().allowUndo( - view = context.getRootView()!!, - message = context - .getString(R.string.search_delete_search_engine_success_message, engine.name), - undoActionTitle = context.getString(R.string.snackbar_deleted_undo), - onCancel = { - context.components.search.provider.installSearchEngine( - context, - engine, - isCustomSearchEngine - ) - - searchEngineList = initialEngineList.copy( - default = initialDefaultEngine - ) - - refreshSearchEngineViews(context) - }, - operation = { - if (isDefaultEngine) { - val default = context.components.search.provider.getDefaultEngine(context) - context.components.search.provider.setDefaultEngine(context, default.identifier) - context.settings().defaultSearchEngineName = default.name - } - if (isCustomSearchEngine) { - context.components.analytics.metrics.track(Event.CustomEngineDeleted) - } - refreshSearchEngineViews(context) - } - ) - - searchEngineList = searchEngineList.copy( - list = searchEngineList.list.filter { - it.identifier != engine.identifier - }, - default = if (searchEngineList.default?.identifier == engine.identifier) { - null - } else { - searchEngineList.default - } - ) - - refreshSearchEngineViews(context) - } -} diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 9a6cf4ee5..2fca420f1 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -943,9 +943,6 @@ - diff --git a/app/src/migration/java/org/mozilla/fenix/MigratingFenixApplication.kt b/app/src/migration/java/org/mozilla/fenix/MigratingFenixApplication.kt index 9c51b44ef..d64043f14 100644 --- a/app/src/migration/java/org/mozilla/fenix/MigratingFenixApplication.kt +++ b/app/src/migration/java/org/mozilla/fenix/MigratingFenixApplication.kt @@ -38,7 +38,6 @@ class MigratingFenixApplication : FenixApplication() { this.components.addonUpdater ) .migrateTelemetryIdentifiers() - .migrateSearchEngine(this.components.search.searchEngineManager) .build() } diff --git a/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt b/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt index 511a24e81..577790914 100644 --- a/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt +++ b/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt @@ -16,14 +16,12 @@ class TestComponents(private val context: Context) : Components(context) { } override val services by lazy { Services(context, backgroundServices.accountManager) } override val core by lazy { TestCore(context, analytics.crashReporter) } - override val search by lazy { Search(context) } override val useCases by lazy { UseCases( context, core.engine, core.sessionManager, core.store, - search.searchEngineManager, core.webAppShortcutManager, core.topSitesStorage ) diff --git a/app/src/test/java/org/mozilla/fenix/components/metrics/GleanMetricsServiceTest.kt b/app/src/test/java/org/mozilla/fenix/components/metrics/GleanMetricsServiceTest.kt index 5645a2eb0..16e932db2 100644 --- a/app/src/test/java/org/mozilla/fenix/components/metrics/GleanMetricsServiceTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/metrics/GleanMetricsServiceTest.kt @@ -7,6 +7,7 @@ package org.mozilla.fenix.components.metrics import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK +import mozilla.components.browser.state.store.BrowserStore import mozilla.components.service.glean.testing.GleanTestRule import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals @@ -35,7 +36,9 @@ class GleanMetricsServiceTest { @Before fun setup() { MockKAnnotations.init(this) - gleanService = GleanMetricsService(testContext, browsersCache, mozillaProductDetector) + + val store = BrowserStore() + gleanService = GleanMetricsService(testContext, lazy { store }, browsersCache, mozillaProductDetector) } @Test diff --git a/app/src/test/java/org/mozilla/fenix/components/metrics/MetricsUtilsTestRoboelectric.kt b/app/src/test/java/org/mozilla/fenix/components/metrics/MetricsUtilsTestRoboelectric.kt index 059929f86..2f867021a 100644 --- a/app/src/test/java/org/mozilla/fenix/components/metrics/MetricsUtilsTestRoboelectric.kt +++ b/app/src/test/java/org/mozilla/fenix/components/metrics/MetricsUtilsTestRoboelectric.kt @@ -6,8 +6,8 @@ package org.mozilla.fenix.components.metrics import io.mockk.every import io.mockk.mockk -import mozilla.components.browser.search.SearchEngine -import mozilla.components.support.test.robolectric.testContext +import mozilla.components.browser.state.search.SearchEngine +import mozilla.components.browser.state.store.BrowserStore import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith @@ -22,16 +22,16 @@ class MetricsUtilsTestRoboelectric { @Test fun createSearchEvent() { - val context = testContext + val store = BrowserStore() val engine: SearchEngine = mockk(relaxed = true) - every { engine.identifier } returns MetricsUtilsTest.ENGINE_SOURCE_IDENTIFIER + every { engine.id } returns MetricsUtilsTest.ENGINE_SOURCE_IDENTIFIER Assert.assertEquals( "${MetricsUtilsTest.ENGINE_SOURCE_IDENTIFIER}.suggestion", MetricsUtils.createSearchEvent( engine, - context, + store, Event.PerformedSearch.SearchAccessPoint.SUGGESTION )?.eventSource?.countLabel ) @@ -39,7 +39,7 @@ class MetricsUtilsTestRoboelectric { "${MetricsUtilsTest.ENGINE_SOURCE_IDENTIFIER}.action", MetricsUtils.createSearchEvent( engine, - context, + store, Event.PerformedSearch.SearchAccessPoint.ACTION )?.eventSource?.countLabel ) @@ -47,7 +47,7 @@ class MetricsUtilsTestRoboelectric { "${MetricsUtilsTest.ENGINE_SOURCE_IDENTIFIER}.widget", MetricsUtils.createSearchEvent( engine, - context, + store, Event.PerformedSearch.SearchAccessPoint.WIDGET )?.eventSource?.countLabel ) @@ -55,7 +55,7 @@ class MetricsUtilsTestRoboelectric { "${MetricsUtilsTest.ENGINE_SOURCE_IDENTIFIER}.shortcut", MetricsUtils.createSearchEvent( engine, - context, + store, Event.PerformedSearch.SearchAccessPoint.SHORTCUT )?.eventSource?.countLabel ) diff --git a/app/src/test/java/org/mozilla/fenix/components/metrics/PerformedSearchTest.kt b/app/src/test/java/org/mozilla/fenix/components/metrics/PerformedSearchTest.kt deleted file mode 100644 index 56c47c64f..000000000 --- a/app/src/test/java/org/mozilla/fenix/components/metrics/PerformedSearchTest.kt +++ /dev/null @@ -1,207 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.components.metrics - -import mozilla.components.browser.search.SearchEngine -import mozilla.components.browser.search.SearchEngineManager -import mozilla.components.browser.search.provider.AssetsSearchEngineProvider -import mozilla.components.browser.search.provider.localization.LocaleSearchLocalizationProvider -import mozilla.components.support.test.robolectric.testContext -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mozilla.fenix.components.metrics.Event.PerformedSearch -import org.mozilla.fenix.components.metrics.Event.PerformedSearch.EngineSource -import org.mozilla.fenix.components.metrics.Event.PerformedSearch.EventSource -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner - -@RunWith(FenixRobolectricTestRunner::class) -class PerformedSearchTest { - private lateinit var searchEngines: List - - // Match against the Regex defined at - // https://github.com/mozilla-mobile/android-components/blob/master/components/service/glean/src/main/java/mozilla/components/service/glean/private/LabeledMetricType.kt#L43 - // We're temporarily using it until better Glean testing APIs are available. - private val countLabelRegex = Regex("^[a-z_][a-z0-9_-]{0,29}(\\.[a-z0-9_-]{0,29})*$") - - @Before - fun setUp() { - searchEngines = SearchEngineManager(listOf(provider)).getSearchEngines(testContext) - } - - @Test - fun testThatCountLabelIsValid() { - val labels = searchEngines.map { - PerformedSearch(EventSource.Action(EngineSource.Shortcut(it, false))).eventSource.countLabel - } - - labels.forEach { - assertTrue("$it does not match!", it.matches(countLabelRegex)) - } - } - - private val provider = AssetsSearchEngineProvider( - localizationProvider = LocaleSearchLocalizationProvider(), - additionalIdentifiers = listOf( - "amazon-au", - "amazon-br", - "amazon-ca", - "amazon-co-uk", - "amazon-de", - "amazon-fr", - "amazon-in", - "amazon-it", - "amazon-jp", - "amazon-mx", - "amazon-nl", - "amazondotcom", - "azerdict", - "azet-sk", - "baidu", - "bing", - "bolcom-fy-NL", - "bolcom-nl", - "ceneje", - "coccoc", - "danawa-kr", - "daum-kr", - "ddg", - "diec2", - "drae", - "duckduckgo", - "elebila", - "faclair-beag", - "google-2018", - "google-b-1-m", - "google-b-m", - "google", - "gulesider-mobile-NO", - "heureka-cz", - "hotline-ua", - "leit-is", - "leo_ende_de", - "list-am", - "mapy-cz", - "mercadolibre-ar", - "mercadolibre-cl", - "mercadolibre-mx", - "naver-kr", - "odpiralni", - "pazaruvaj", - "pledarigrond", - "prisjakt-sv-SE", - "qwant", - "rediff", - "reta-vortaro", - "salidzinilv", - "seznam-cz", - "skroutz", - "slovnik-sk", - "sslv", - "sztaki-en-hu", - "taobao", - "tearma", - "twitter-ja", - "twitter", - "vatera", - "wikipedia-NN", - "wikipedia-NO", - "wikipedia-an", - "wikipedia-ar", - "wikipedia-as", - "wikipedia-ast", - "wikipedia-az", - "wikipedia-be", - "wikipedia-bg", - "wikipedia-bn", - "wikipedia-br", - "wikipedia-bs", - "wikipedia-ca", - "wikipedia-cy", - "wikipedia-cz", - "wikipedia-da", - "wikipedia-de", - "wikipedia-dsb", - "wikipedia-el", - "wikipedia-eo", - "wikipedia-es", - "wikipedia-et", - "wikipedia-eu", - "wikipedia-fa", - "wikipedia-fi", - "wikipedia-fr", - "wikipedia-fy-NL", - "wikipedia-ga-IE", - "wikipedia-gd", - "wikipedia-gl", - "wikipedia-gn", - "wikipedia-gu", - "wikipedia-he", - "wikipedia-hi", - "wikipedia-hr", - "wikipedia-hsb", - "wikipedia-hu", - "wikipedia-hy-AM", - "wikipedia-ia", - "wikipedia-id", - "wikipedia-is", - "wikipedia-it", - "wikipedia-ja", - "wikipedia-ka", - "wikipedia-kab", - "wikipedia-kk", - "wikipedia-km", - "wikipedia-kn", - "wikipedia-lij", - "wikipedia-lo", - "wikipedia-lt", - "wikipedia-ltg", - "wikipedia-lv", - "wikipedia-ml", - "wikipedia-mr", - "wikipedia-ms", - "wikipedia-my", - "wikipedia-ne", - "wikipedia-nl", - "wikipedia-oc", - "wikipedia-or", - "wikipedia-pa", - "wikipedia-pl", - "wikipedia-pt", - "wikipedia-rm", - "wikipedia-ro", - "wikipedia-ru", - "wikipedia-sk", - "wikipedia-sl", - "wikipedia-sq", - "wikipedia-sr", - "wikipedia-sv-SE", - "wikipedia-ta", - "wikipedia-te", - "wikipedia-th", - "wikipedia-tr", - "wikipedia-uk", - "wikipedia-ur", - "wikipedia-uz", - "wikipedia-vi", - "wikipedia-wo", - "wikipedia-zh-CN", - "wikipedia-zh-TW", - "wikipedia", - "wiktionary-kn", - "wiktionary-oc", - "wiktionary-or", - "wiktionary-ta", - "wiktionary-te", - "yahoo-jp", - "yandex-en", - "yandex-ru", - "yandex-tr", - "yandex.by", - "yandex" - ) - ) -} diff --git a/app/src/test/java/org/mozilla/fenix/components/searchengine/FenixSearchEngineProviderTest.kt b/app/src/test/java/org/mozilla/fenix/components/searchengine/FenixSearchEngineProviderTest.kt deleted file mode 100644 index ecc3c40d8..000000000 --- a/app/src/test/java/org/mozilla/fenix/components/searchengine/FenixSearchEngineProviderTest.kt +++ /dev/null @@ -1,174 +0,0 @@ -package org.mozilla.fenix.components.searchengine - -import android.content.Context -import io.mockk.Runs -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import io.mockk.mockkObject -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest -import mozilla.components.browser.search.SearchEngine -import mozilla.components.browser.search.provider.SearchEngineList -import mozilla.components.browser.search.provider.localization.LocaleSearchLocalizationProvider -import mozilla.components.browser.search.provider.localization.SearchLocalizationProvider -import mozilla.components.support.test.robolectric.testContext -import org.junit.Assert.assertEquals -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner - -@ExperimentalCoroutinesApi -@RunWith(FenixRobolectricTestRunner::class) -class FenixSearchEngineProviderTest { - - private lateinit var fenixSearchEngineProvider: FenixSearchEngineProvider - - @Before - fun before() { - fenixSearchEngineProvider = FakeFenixSearchEngineProvider(testContext) - mockkObject(CustomSearchEngineStore) - fenixSearchEngineProvider.let { - every { CustomSearchEngineStore.loadCustomSearchEngines(testContext) } returns listOf( - (it as FakeFenixSearchEngineProvider) - .mockSearchEngine("my custom site", "my custom site") - ) - } - } - - /* - TODO TEST: - - public API happy path - - list ordering - - deduping - - the above after adding/removing - */ - - @Suppress("DEPRECATION") - @Test - fun `add custom engine`() = runBlockingTest { - val engineName = "Ecosia" - val engineQuery = "www.ecosia.com/%s" - val searchEngine: SearchEngine = mockk(relaxed = true) - every { searchEngine.getSearchTemplate() } returns engineQuery - every { searchEngine.name } returns engineName - mockkObject(CustomSearchEngineStore) - coEvery { - CustomSearchEngineStore.addSearchEngine( - testContext, - engineName, - engineQuery - ) - } just Runs - - fenixSearchEngineProvider.installSearchEngine(testContext, searchEngine, true) - - coVerify { CustomSearchEngineStore.addSearchEngine(testContext, engineName, engineQuery) } - } - - @Test - fun `GIVEN sharedprefs does not contain installed engines WHEN installedSearchEngineIdentifiers THEN defaultEngines + customEngines ids are returned`() = runBlockingTest { - val expectedDefaults = fenixSearchEngineProvider.baseSearchEngines.toIdSet() - val expectedCustom = fenixSearchEngineProvider.customSearchEngines.toIdSet() - val expected = expectedDefaults + expectedCustom - - val actual = fenixSearchEngineProvider.installedSearchEngineIdentifiers(testContext) - assertEquals(expected, actual) - } - - @Test - fun `GIVEN sharedprefs contains installed engines WHEN installedSearchEngineIdentifiers THEN defaultEngines + customEngines ids are returned`() = runBlockingTest { - val sp = testContext.getSharedPreferences( - FenixSearchEngineProvider.PREF_FILE_SEARCH_ENGINES, - Context.MODE_PRIVATE - ) - sp.edit().putStringSet( - fenixSearchEngineProvider.localeAwareInstalledEnginesKey(), - persistedInstalledEngines - ).apply() - - val expectedStored = persistedInstalledEngines - val expectedCustom = fenixSearchEngineProvider.customSearchEngines.toIdSet() - val expected = expectedStored + expectedCustom - - val actual = fenixSearchEngineProvider.installedSearchEngineIdentifiers(testContext) - assertEquals(expected, actual) - } -} - -private suspend fun Deferred.toIdSet() = - await().list.map { it.identifier }.toSet() - -private val persistedInstalledEngines = setOf("bing", "ecosia") - -class FakeFenixSearchEngineProvider(context: Context) : FenixSearchEngineProvider(context) { - override val localizationProvider: SearchLocalizationProvider - get() = LocaleSearchLocalizationProvider() - - override var baseSearchEngines: Deferred - set(_) { throw NotImplementedError("Setting not currently supported on this fake") } - get() { - val google = mockSearchEngine(id = "google-b-1-m", n = "Google") - - return CompletableDeferred( - SearchEngineList( - listOf( - google, - mockSearchEngine("bing", "Bing"), - mockSearchEngine("amazondotcom", "Amazon.com") - ), default = google - ) - ) - } - - override val fallbackEngines: Deferred - get() { - val google = mockSearchEngine(id = "google-b-1-m", n = "Google") - - return CompletableDeferred( - SearchEngineList( - listOf( - google, - mockSearchEngine("bing", "Bing"), - mockSearchEngine("amazondotcom", "Amazon.com") - ), default = google - ) - ) - } - - override val bundledSearchEngines = CompletableDeferred( - SearchEngineList( - listOf( - mockSearchEngine("ecosia", "Ecosia"), - mockSearchEngine("reddit", "Reddit"), - mockSearchEngine("startpage", "Startpage.com") - ), default = null - ) - ) - - override var customSearchEngines: Deferred = CompletableDeferred( - SearchEngineList( - listOf( - mockSearchEngine("my custom site", "my custom site") - ), default = null - ) - ) - - override fun updateBaseSearchEngines() { } - - fun mockSearchEngine( - id: String, - n: String = id - ): SearchEngine { - val engine = mockk() - every { engine.identifier } returns id - every { engine.name } returns n - every { engine.icon } returns mockk() - return engine - } -} diff --git a/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt b/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt index 4041b6305..f0873c2e2 100644 --- a/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/DefaultSessionControlControllerTest.kt @@ -12,9 +12,11 @@ import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.TestCoroutineScope -import mozilla.components.browser.search.SearchEngine -import mozilla.components.browser.search.SearchEngineManager import mozilla.components.browser.session.SessionManager +import mozilla.components.browser.state.search.SearchEngine +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.SearchState +import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.Engine import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tabs.TabsUseCases @@ -33,7 +35,6 @@ import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.tips.Tip import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.searchEngineManager import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController import org.mozilla.fenix.settings.SupportUtils @@ -67,16 +68,27 @@ class DefaultSessionControlControllerTest { wasSwiped: Boolean, handleSwipedItemDeletionCancel: () -> Unit ) -> Unit = mockk(relaxed = true) - private val searchEngine = mockk(relaxed = true) - private val searchEngineManager = mockk(relaxed = true) private val settings: Settings = mockk(relaxed = true) private val analytics: Analytics = mockk(relaxed = true) private val scope = TestCoroutineScope() - + private val searchEngine = SearchEngine( + id = "test", + name = "Test Engine", + icon = mockk(relaxed = true), + type = SearchEngine.Type.BUNDLED, + resultUrls = listOf("https://example.org/?q={searchTerms}") + ) + private lateinit var store: BrowserStore private lateinit var controller: DefaultSessionControlController @Before fun setup() { + store = BrowserStore(BrowserState( + search = SearchState( + regionSearchEngines = listOf(searchEngine) + ) + )) + every { fragmentStore.state } returns HomeFragmentState( collections = emptyList(), expandedCollections = emptySet(), @@ -90,15 +102,13 @@ class DefaultSessionControlControllerTest { every { id } returns R.id.homeFragment } every { activity.components.settings } returns settings - every { activity.components.search.provider.getDefaultEngine(activity) } returns searchEngine every { activity.settings() } returns settings - every { activity.searchEngineManager } returns searchEngineManager - every { searchEngineManager.defaultSearchEngine } returns searchEngine every { activity.components.analytics } returns analytics every { analytics.metrics } returns metrics controller = DefaultSessionControlController( activity = activity, + store = store, settings = settings, engine = engine, metrics = metrics, diff --git a/app/src/test/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessorTest.kt b/app/src/test/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessorTest.kt index 7e58c1570..649c3e89e 100644 --- a/app/src/test/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessorTest.kt @@ -6,18 +6,21 @@ package org.mozilla.fenix.home.intent import android.content.Intent import androidx.navigation.NavController +import androidx.test.core.app.ApplicationProvider import io.mockk.Called import io.mockk.every import io.mockk.mockk import io.mockk.verify -import mozilla.components.browser.search.SearchEngine +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.SearchState +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.feature.search.ext.createSearchEngine import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.components.metrics.MetricController -import org.mozilla.fenix.ext.components import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.widget.VoiceSearchActivity.Companion.SPEECH_PROCESSING @@ -29,16 +32,32 @@ class SpeechProcessingIntentProcessorTest { private val out: Intent = mockk(relaxed = true) private val metrics: MetricController = mockk(relaxed = true) + private val searchEngine = createSearchEngine( + name = "Test", + url = "https://www.example.org/?q={searchTerms}", + icon = mockk() + ) + + private lateinit var store: BrowserStore + @Before fun setup() { - val searchEngine = mockk(relaxed = true) - every { activity.components.search.searchEngineManager.defaultSearchEngine } returns searchEngine - every { activity.components.search.provider.getDefaultEngine(activity) } returns searchEngine + val searchEngine = searchEngine + + store = BrowserStore(BrowserState( + search = SearchState( + customSearchEngines = listOf(searchEngine), + userSelectedSearchEngineId = searchEngine.id, + complete = true + ) + )) + + every { activity.applicationContext } returns ApplicationProvider.getApplicationContext() } @Test fun `do not process blank intents`() { - val processor = SpeechProcessingIntentProcessor(activity, metrics) + val processor = SpeechProcessingIntentProcessor(activity, store, metrics) processor.process(Intent(), navController, out) verify { activity wasNot Called } @@ -52,7 +71,7 @@ class SpeechProcessingIntentProcessorTest { val intent = Intent().apply { putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, false) } - val processor = SpeechProcessingIntentProcessor(activity, metrics) + val processor = SpeechProcessingIntentProcessor(activity, store, metrics) processor.process(intent, navController, out) verify { activity wasNot Called } @@ -61,35 +80,14 @@ class SpeechProcessingIntentProcessorTest { verify { metrics wasNot Called } } - @Test - fun `process when open extra is true`() { - val intent = Intent().apply { - putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, true) - } - val processor = SpeechProcessingIntentProcessor(activity, metrics) - - processor.process(intent, navController, out) - - verify { - activity.openToBrowserAndLoad( - searchTermOrURL = "", - newTab = true, - from = BrowserDirection.FromGlobal, - forceSearch = true - ) - } - verify { navController wasNot Called } - verify { out.putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, false) } - } - @Test fun `reads the speech processing extra`() { val intent = Intent().apply { putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, true) putExtra(SPEECH_PROCESSING, "hello world") } - val processor = SpeechProcessingIntentProcessor(activity, metrics) + val processor = SpeechProcessingIntentProcessor(activity, store, metrics) processor.process(intent, mockk(), mockk(relaxed = true)) verify { @@ -97,7 +95,8 @@ class SpeechProcessingIntentProcessorTest { searchTermOrURL = "hello world", newTab = true, from = BrowserDirection.FromGlobal, - forceSearch = true + forceSearch = true, + engine = searchEngine ) } } diff --git a/app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt b/app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt index 7c888d283..8155cc043 100644 --- a/app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/SearchDialogControllerTest.kt @@ -19,9 +19,10 @@ import io.mockk.unmockkObject import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runBlockingTest -import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager +import mozilla.components.browser.state.search.SearchEngine +import mozilla.components.browser.state.store.BrowserStore import org.junit.After import org.junit.Before import org.junit.Test @@ -56,18 +57,21 @@ class SearchDialogControllerTest { MockKAnnotations.init(this) mockkObject(MetricsUtils) + val browserStore = BrowserStore() + every { store.state.tabId } returns "test-tab-id" every { store.state.searchEngineSource.searchEngine } returns searchEngine every { sessionManager.select(any()) } just Runs every { navController.currentDestination } returns mockk { every { id } returns R.id.searchDialogFragment } - every { MetricsUtils.createSearchEvent(searchEngine, activity, any()) } returns null + every { MetricsUtils.createSearchEvent(searchEngine, browserStore, any()) } returns null controller = SearchDialogController( activity = activity, sessionManager = sessionManager, - store = store, + store = browserStore, + fragmentStore = store, navController = navController, settings = settings, metrics = metrics, diff --git a/app/src/test/java/org/mozilla/fenix/search/SearchDialogInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/search/SearchDialogInteractorTest.kt index 7fc3761d1..53a224c19 100644 --- a/app/src/test/java/org/mozilla/fenix/search/SearchDialogInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/SearchDialogInteractorTest.kt @@ -8,8 +8,8 @@ import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runBlockingTest -import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.session.Session +import mozilla.components.browser.state.search.SearchEngine import org.junit.Before import org.junit.Test diff --git a/app/src/test/java/org/mozilla/fenix/search/SearchFragmentStoreTest.kt b/app/src/test/java/org/mozilla/fenix/search/SearchFragmentStoreTest.kt index 2e5b0d0e9..af3324002 100644 --- a/app/src/test/java/org/mozilla/fenix/search/SearchFragmentStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/SearchFragmentStoreTest.kt @@ -10,14 +10,17 @@ import io.mockk.impl.annotations.MockK import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking -import mozilla.components.browser.search.SearchEngine -import mozilla.components.browser.search.provider.SearchEngineList +import mozilla.components.browser.state.search.RegionState +import mozilla.components.browser.state.search.SearchEngine import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.ContentState +import mozilla.components.browser.state.state.SearchState import mozilla.components.browser.state.state.TabSessionState import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotSame +import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -26,14 +29,12 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.metrics.Event.PerformedSearch.SearchAccessPoint -import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider import org.mozilla.fenix.utils.Settings @ExperimentalCoroutinesApi class SearchFragmentStoreTest { @MockK private lateinit var searchEngine: SearchEngine - @MockK private lateinit var searchProvider: FenixSearchEngineProvider @MockK private lateinit var activity: HomeActivity @MockK(relaxed = true) private lateinit var components: Components @MockK(relaxed = true) private lateinit var settings: Settings @@ -45,12 +46,6 @@ class SearchFragmentStoreTest { override var mode: BrowsingMode = BrowsingMode.Normal } every { components.settings } returns settings - every { components.search.provider } returns searchProvider - every { searchProvider.getDefaultEngine(activity) } returns searchEngine - every { searchProvider.installedSearchEngines(activity) } returns SearchEngineList( - list = listOf(mockk(), mockk()), - default = searchEngine - ) } @Test @@ -63,12 +58,13 @@ class SearchFragmentStoreTest { query = "", url = "", searchTerms = "", - searchEngineSource = SearchEngineSource.Default(searchEngine), - defaultEngineSource = SearchEngineSource.Default(searchEngine), + searchEngineSource = SearchEngineSource.None, + defaultEngine = null, + showSearchShortcutsSetting = true, showSearchSuggestions = false, showSearchSuggestionsHint = false, - showSearchShortcuts = true, - areShortcutsAvailable = true, + showSearchShortcuts = false, + areShortcutsAvailable = false, showClipboardSuggestions = false, showHistorySuggestions = false, showBookmarkSuggestions = false, @@ -120,12 +116,13 @@ class SearchFragmentStoreTest { query = "https://example.com", url = "https://example.com", searchTerms = "search terms", - searchEngineSource = SearchEngineSource.Default(searchEngine), - defaultEngineSource = SearchEngineSource.Default(searchEngine), + searchEngineSource = SearchEngineSource.None, + defaultEngine = null, showSearchSuggestions = false, + showSearchShortcutsSetting = false, showSearchSuggestionsHint = false, showSearchShortcuts = false, - areShortcutsAvailable = true, + areShortcutsAvailable = false, showClipboardSuggestions = false, showHistorySuggestions = false, showBookmarkSuggestions = false, @@ -176,16 +173,6 @@ class SearchFragmentStoreTest { assertEquals(true, store.state.showSearchShortcuts) } - @Test - fun hideSearchShortcutEnginePicker() = runBlocking { - val initialState = emptyDefaultState() - val store = SearchFragmentStore(initialState) - - store.dispatch(SearchFragmentAction.UpdateShortcutsAvailability(false)).join() - assertNotSame(initialState, store.state) - assertEquals(false, store.state.showSearchShortcuts) - } - @Test fun showSearchSuggestions() = runBlocking { val initialState = emptyDefaultState() @@ -213,26 +200,132 @@ class SearchFragmentStoreTest { } @Test - fun selectNewDefaultEngine() = runBlocking { - val initialState = emptyDefaultState() - val store = SearchFragmentStore(initialState) + fun `Updating SearchFragmentState from SearchState`() = runBlocking { + val store = SearchFragmentStore(emptyDefaultState( + searchEngineSource = SearchEngineSource.None, + areShortcutsAvailable = false, + defaultEngine = null, + showSearchShortcutsSetting = true + )) - store.dispatch(SearchFragmentAction.SelectNewDefaultSearchEngine(searchEngine)).join() - assertNotSame(initialState, store.state) - assertEquals(SearchEngineSource.Default(searchEngine), store.state.searchEngineSource) + assertNull(store.state.defaultEngine) + assertFalse(store.state.areShortcutsAvailable) + assertFalse(store.state.showSearchShortcuts) + assertEquals(SearchEngineSource.None, store.state.searchEngineSource) + + store.dispatch( + SearchFragmentAction.UpdateSearchState( + SearchState( + region = RegionState("US", "US"), + regionSearchEngines = listOf( + SearchEngine("engine-a", "Engine A", mockk(), type = SearchEngine.Type.BUNDLED), + SearchEngine("engine-b", "Engine B", mockk(), type = SearchEngine.Type.BUNDLED), + SearchEngine("engine-c", "Engine C", mockk(), type = SearchEngine.Type.BUNDLED) + ), + customSearchEngines = listOf( + SearchEngine("engine-d", "Engine D", mockk(), type = SearchEngine.Type.CUSTOM), + SearchEngine("engine-e", "Engine E", mockk(), type = SearchEngine.Type.CUSTOM) + ), + additionalSearchEngines = listOf( + SearchEngine("engine-f", "Engine F", mockk(), type = SearchEngine.Type.BUNDLED_ADDITIONAL) + ), + additionalAvailableSearchEngines = listOf( + SearchEngine("engine-g", "Engine G", mockk(), type = SearchEngine.Type.BUNDLED_ADDITIONAL), + SearchEngine("engine-h", "Engine H", mockk(), type = SearchEngine.Type.BUNDLED_ADDITIONAL) + ), + hiddenSearchEngines = listOf( + SearchEngine("engine-i", "Engine I", mockk(), type = SearchEngine.Type.BUNDLED) + ), + regionDefaultSearchEngineId = "engine-b", + userSelectedSearchEngineId = null, + userSelectedSearchEngineName = null + ) + ) + ).join() + + assertNotNull(store.state.defaultEngine) + assertEquals("Engine B", store.state.defaultEngine!!.name) + + assertTrue(store.state.areShortcutsAvailable) + assertTrue(store.state.showSearchShortcuts) + + assertTrue(store.state.searchEngineSource is SearchEngineSource.Default) + assertNotNull(store.state.searchEngineSource.searchEngine) + assertEquals("Engine B", store.state.searchEngineSource.searchEngine!!.name) + } + + @Test + fun `Updating SearchFragmentState from SearchState - shortcuts disabled`() = runBlocking { + val store = SearchFragmentStore(emptyDefaultState( + searchEngineSource = SearchEngineSource.None, + areShortcutsAvailable = false, + defaultEngine = null, + showSearchShortcutsSetting = false + )) + + assertNull(store.state.defaultEngine) + assertFalse(store.state.areShortcutsAvailable) + assertFalse(store.state.showSearchShortcuts) + assertEquals(SearchEngineSource.None, store.state.searchEngineSource) + + store.dispatch( + SearchFragmentAction.UpdateSearchState( + SearchState( + region = RegionState("US", "US"), + regionSearchEngines = listOf( + SearchEngine("engine-a", "Engine A", mockk(), type = SearchEngine.Type.BUNDLED), + SearchEngine("engine-b", "Engine B", mockk(), type = SearchEngine.Type.BUNDLED), + SearchEngine("engine-c", "Engine C", mockk(), type = SearchEngine.Type.BUNDLED) + ), + customSearchEngines = listOf( + SearchEngine("engine-d", "Engine D", mockk(), type = SearchEngine.Type.CUSTOM), + SearchEngine("engine-e", "Engine E", mockk(), type = SearchEngine.Type.CUSTOM) + ), + additionalSearchEngines = listOf( + SearchEngine("engine-f", "Engine F", mockk(), type = SearchEngine.Type.BUNDLED_ADDITIONAL) + ), + additionalAvailableSearchEngines = listOf( + SearchEngine("engine-g", "Engine G", mockk(), type = SearchEngine.Type.BUNDLED_ADDITIONAL), + SearchEngine("engine-h", "Engine H", mockk(), type = SearchEngine.Type.BUNDLED_ADDITIONAL) + ), + hiddenSearchEngines = listOf( + SearchEngine("engine-i", "Engine I", mockk(), type = SearchEngine.Type.BUNDLED) + ), + regionDefaultSearchEngineId = "engine-b", + userSelectedSearchEngineId = null, + userSelectedSearchEngineName = null + ) + ) + ).join() + + assertNotNull(store.state.defaultEngine) + assertEquals("Engine B", store.state.defaultEngine!!.name) + + assertTrue(store.state.areShortcutsAvailable) + assertFalse(store.state.showSearchShortcuts) + + assertTrue(store.state.searchEngineSource is SearchEngineSource.Default) + assertNotNull(store.state.searchEngineSource.searchEngine) + assertEquals("Engine B", store.state.searchEngineSource.searchEngine!!.name) } - private fun emptyDefaultState(): SearchFragmentState = SearchFragmentState( + private fun emptyDefaultState( + searchEngineSource: SearchEngineSource = mockk(), + defaultEngine: SearchEngine? = mockk(), + areShortcutsAvailable: Boolean = true, + showSearchShortcutsSetting: Boolean = false + ): SearchFragmentState = SearchFragmentState( tabId = null, url = "", searchTerms = "", query = "", - searchEngineSource = mockk(), - defaultEngineSource = mockk(), + searchEngineSource = searchEngineSource, + defaultEngine = defaultEngine, showSearchSuggestionsHint = false, + showSearchShortcutsSetting = showSearchShortcutsSetting, showSearchSuggestions = false, showSearchShortcuts = false, - areShortcutsAvailable = true, + areShortcutsAvailable = areShortcutsAvailable, showClipboardSuggestions = false, showHistorySuggestions = false, showBookmarkSuggestions = false, diff --git a/app/src/test/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProviderTest.kt b/app/src/test/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProviderTest.kt index d5ad43485..655c8cc1a 100644 --- a/app/src/test/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProviderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/awesomebar/ShortcutsSuggestionProviderTest.kt @@ -13,15 +13,16 @@ import io.mockk.unmockkStatic import io.mockk.verifySequence import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runBlockingTest -import mozilla.components.browser.search.SearchEngine -import mozilla.components.browser.search.provider.SearchEngineList +import mozilla.components.browser.state.search.SearchEngine +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.SearchState +import mozilla.components.browser.state.store.BrowserStore import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Before import org.junit.Test import org.mozilla.fenix.R -import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider @ExperimentalCoroutinesApi class ShortcutsSuggestionProviderTest { @@ -52,33 +53,32 @@ class ShortcutsSuggestionProviderTest { @Test fun `returns suggestions from search engine provider`() = runBlockingTest { val engineOne = mockk { - every { identifier } returns "1" + every { id } returns "1" every { name } returns "EngineOne" every { icon } returns mockk() } val engineTwo = mockk { - every { identifier } returns "2" + every { id } returns "2" every { name } returns "EngineTwo" every { icon } returns mockk() } - val searchEngineProvider = mockk { - every { installedSearchEngines(context) } returns SearchEngineList( - list = listOf(engineOne, engineTwo), - default = null + val store = BrowserStore(BrowserState( + search = SearchState( + regionSearchEngines = listOf(engineOne, engineTwo) ) - } - val provider = ShortcutsSuggestionProvider(searchEngineProvider, context, mockk(), mockk()) + )) + val provider = ShortcutsSuggestionProvider(store, context, mockk(), mockk()) val suggestions = provider.onInputChanged("") assertEquals(3, suggestions.size) assertEquals(provider, suggestions[0].provider) - assertEquals(engineOne.identifier, suggestions[0].id) + assertEquals(engineOne.id, suggestions[0].id) assertEquals(engineOne.icon, suggestions[0].icon) assertEquals(engineOne.name, suggestions[0].title) assertEquals(provider, suggestions[1].provider) - assertEquals(engineTwo.identifier, suggestions[1].id) + assertEquals(engineTwo.id, suggestions[1].id) assertEquals(engineTwo.icon, suggestions[1].icon) assertEquals(engineTwo.name, suggestions[1].title) @@ -90,16 +90,16 @@ class ShortcutsSuggestionProviderTest { @Test fun `callbacks are triggered when suggestions are clicked`() = runBlockingTest { val engineOne = mockk(relaxed = true) - val searchEngineProvider = mockk { - every { installedSearchEngines(context) } returns SearchEngineList( - list = listOf(engineOne), - default = null + val store = BrowserStore(BrowserState( + search = SearchState( + regionSearchEngines = listOf(engineOne) ) - } + )) + val selectShortcutEngine = mockk<(SearchEngine) -> Unit>(relaxed = true) val selectShortcutEngineSettings = mockk<() -> Unit>(relaxed = true) val provider = ShortcutsSuggestionProvider( - searchEngineProvider, + store, context, selectShortcutEngine, selectShortcutEngineSettings diff --git a/app/src/test/java/org/mozilla/fenix/search/toolbar/ToolbarViewTest.kt b/app/src/test/java/org/mozilla/fenix/search/toolbar/ToolbarViewTest.kt index 2312ebb94..0cb32ce9c 100644 --- a/app/src/test/java/org/mozilla/fenix/search/toolbar/ToolbarViewTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/toolbar/ToolbarViewTest.kt @@ -48,7 +48,8 @@ class ToolbarViewTest { every { name } returns "Search Engine" every { icon } returns testContext.getDrawable(R.drawable.ic_search)!!.toBitmap() }), - defaultEngineSource = mockk(relaxed = true), + defaultEngine = null, + showSearchShortcutsSetting = false, showSearchSuggestionsHint = false, showSearchSuggestions = false, showSearchShortcuts = false, diff --git a/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaUseLazyMonitored.kt b/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaUseLazyMonitored.kt index 3a9430af3..abfaf8720 100644 --- a/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaUseLazyMonitored.kt +++ b/mozilla-detekt-rules/src/main/java/org/mozilla/fenix/detektrules/perf/MozillaUseLazyMonitored.kt @@ -78,7 +78,6 @@ open class MozillaUseLazyMonitored(config: Config) : Rule(config) { "IntentProcessors", "PerformanceComponent", "Push", - "Search", "Services", "UseCases" ).map { "app/src/main/java/org/mozilla/fenix/components/$it.kt" } From b9737bde956019af30397ff46f61bac73ece3a38 Mon Sep 17 00:00:00 2001 From: ekager Date: Wed, 18 Nov 2020 14:57:06 -0800 Subject: [PATCH 017/202] For #16351 - Add divider lines for search dialog --- .../mozilla/fenix/search/SearchDialogFragment.kt | 6 ++++++ app/src/main/res/layout/fragment_search_dialog.xml | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt index 54d6413c6..70febec65 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchDialogFragment.kt @@ -467,6 +467,9 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { clear(fill_link_from_clipboard.id, TOP) connect(fill_link_from_clipboard.id, BOTTOM, pill_wrapper.id, TOP) + clear(fill_link_divider.id, TOP) + connect(fill_link_divider.id, BOTTOM, fill_link_from_clipboard.id, TOP) + applyTo(search_wrapper) } } @@ -529,6 +532,9 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { !clipboardUrl.isNullOrEmpty() fill_link_from_clipboard.isVisible = shouldShowView + fill_link_divider.isVisible = shouldShowView + pill_wrapper_divider.isVisible = + !(shouldShowView && requireComponents.settings.shouldUseBottomToolbar) clipboard_url.isVisible = shouldShowView clipboard_title.isVisible = shouldShowView link_icon.isVisible = shouldShowView diff --git a/app/src/main/res/layout/fragment_search_dialog.xml b/app/src/main/res/layout/fragment_search_dialog.xml index 91cdcc888..27852ee87 100644 --- a/app/src/main/res/layout/fragment_search_dialog.xml +++ b/app/src/main/res/layout/fragment_search_dialog.xml @@ -141,6 +141,20 @@ tools:text="url" tools:visibility="visible" /> + + + + Date: Sat, 21 Nov 2020 00:05:16 +0000 Subject: [PATCH 018/202] Import l10n. --- app/src/main/res/values-be/strings.xml | 25 ++++++++++++++++ app/src/main/res/values-cak/strings.xml | 4 +++ app/src/main/res/values-cy/strings.xml | 4 +++ app/src/main/res/values-de/strings.xml | 4 +++ app/src/main/res/values-el/strings.xml | 4 +++ app/src/main/res/values-es-rAR/strings.xml | 4 +++ app/src/main/res/values-es-rCL/strings.xml | 6 ++++ app/src/main/res/values-fr/strings.xml | 2 ++ app/src/main/res/values-fy-rNL/strings.xml | 4 +++ app/src/main/res/values-hr/strings.xml | 4 +++ app/src/main/res/values-hsb/strings.xml | 4 +++ app/src/main/res/values-it/strings.xml | 27 ++++++++++++++++++ app/src/main/res/values-iw/strings.xml | 4 +++ app/src/main/res/values-ko/strings.xml | 4 +++ app/src/main/res/values-nb-rNO/strings.xml | 4 +++ app/src/main/res/values-nl/strings.xml | 4 +++ app/src/main/res/values-pt-rBR/strings.xml | 4 +++ app/src/main/res/values-rm/strings.xml | 7 +++++ app/src/main/res/values-sq/strings.xml | 33 ++++++++++++++++++++++ app/src/main/res/values-su/strings.xml | 33 ++++++++++++++++++++++ app/src/main/res/values-sv-rSE/strings.xml | 4 +++ app/src/main/res/values-tr/strings.xml | 4 +++ app/src/main/res/values-uk/strings.xml | 4 +++ app/src/main/res/values-vi/strings.xml | 4 +++ app/src/main/res/values-zh-rCN/strings.xml | 4 +++ app/src/main/res/values-zh-rTW/strings.xml | 4 +++ l10n.toml | 1 + 27 files changed, 210 insertions(+) diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 93f735133..dd3a98295 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -91,6 +91,9 @@ Адхіліць + + Перайсці ў налады + Новая картка @@ -428,6 +431,10 @@ Маркетынгавыя дадзеныя Дзяліцца звесткамі пра магчымасці, якімі вы карыстаецеся ў %1$s, з нашым пастаўшчыком мабільнага маркетынгу Leanplum. + + Даследаванні + + Дазволіць Mozilla ўсталёўваць і запускаць даследаванні Доследы @@ -584,6 +591,8 @@ Адкрытыя карткі Захаваць у калекцыі + + Выбраць Падзяліцца ўсімі карткамі @@ -598,8 +607,18 @@ Дадому Пераключыць рэжым картак + + Закладка + + Закрыць + + Падзяліцца выбранымі карткамі + + Меню выбраных картак Выдаліць картку з калекцыі + + Выбраць карткі Закрыць картку @@ -980,6 +999,12 @@ Карткі закрыты + + Карткі закрыты! + + Закладкі захаваны! + + Паглядзець Дададзена да папулярных сайтаў diff --git a/app/src/main/res/values-cak/strings.xml b/app/src/main/res/values-cak/strings.xml index 6a5a9e2b8..7b017020b 100644 --- a/app/src/main/res/values-cak/strings.xml +++ b/app/src/main/res/values-cak/strings.xml @@ -458,6 +458,10 @@ Marketin tzij Ke\'akomonij taq tzij chi rij ri taq samaj nawokisaj pa %1$s rik\'in Leanplum, ri ya\'öl qamarketin richin oyonib\'äl. + + Taq tijonïk + + Taya\' q\'ij chi ri Mozilla nuyäk chuqa\' nub\'än taq tijonïk Taq solna\'oj diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml index 473886979..df363230a 100644 --- a/app/src/main/res/values-cy/strings.xml +++ b/app/src/main/res/values-cy/strings.xml @@ -443,6 +443,10 @@ Data marchnata Yn rhannu data am ba nodweddion rydych chi’n eu defnyddio yn %1$s gyda Leanplum, ein gwerthwr marchnata symudol. + + Astudiaethau + + Yn caniatáu i Mozilla osod a rhedeg astudiaethau Arbrofion diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 9e87c8b07..2088e226f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -452,6 +452,10 @@ Marketing-Daten Gibt Daten darüber, welche Funktionen Sie in %1$s verwenden, an Leanplum weiter, unseren Anbieter für mobiles Marketing. + + Studien + + Ermöglicht Mozilla die Installation und Durchführung von Studien Experimente diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index ede060a71..06ba0b7d3 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -450,6 +450,10 @@ Δεδομένα μάρκετινγκ Αποστέλλει δεδομένα σχετικά με τις λειτουργίες που χρησιμοποιείτε στο %1$s με το Leanplum, την εταιρεία μάρκετινγκ για κινητές συσκευές. + + Μελέτες + + Επιτρέπει στο Mozilla την εγκατάσταση και εκτέλεση μελετών Πειράματα diff --git a/app/src/main/res/values-es-rAR/strings.xml b/app/src/main/res/values-es-rAR/strings.xml index 8074584b5..46c8522f0 100644 --- a/app/src/main/res/values-es-rAR/strings.xml +++ b/app/src/main/res/values-es-rAR/strings.xml @@ -451,6 +451,10 @@ Datos de marketing Comparte datos acerca de las funciones que usás en %1$s con Leanplum, nuestro proveedor de marketing para móviles. + + Estudios + + Permite que Mozilla instale y realice estudios Experimentos diff --git a/app/src/main/res/values-es-rCL/strings.xml b/app/src/main/res/values-es-rCL/strings.xml index 9e9643ad0..e04303375 100644 --- a/app/src/main/res/values-es-rCL/strings.xml +++ b/app/src/main/res/values-es-rCL/strings.xml @@ -441,6 +441,10 @@ Datos de marketing Comparte datos acerca de las funcionalidades que usas en %1$s con Leanplum, nuestro proveedor de marketing para móviles. + + Estudios + + Permite a Mozilla instalar y realizar estudios Experimentos @@ -590,6 +594,8 @@ Pestañas abiertas Guardar en la colección + + Seleccionar Compartir todas las pestañas diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f68f9305a..2e37ef108 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -625,6 +625,8 @@ Menu des onglets sélectionnés Supprimer l’onglet de la collection + + Sélectionner des onglets Fermer l’onglet diff --git a/app/src/main/res/values-fy-rNL/strings.xml b/app/src/main/res/values-fy-rNL/strings.xml index d642e9a13..4ba850124 100644 --- a/app/src/main/res/values-fy-rNL/strings.xml +++ b/app/src/main/res/values-fy-rNL/strings.xml @@ -444,6 +444,10 @@ Marketinggegevens Dielt gegevens oer hokker funksjes jo yn %1$s brûke mei Leanplum, ús leveransier fan mobile marketing. + + Undersiken + + Stelt Mozilla yn steat om ûndersiken te ynstallearjen en út te fieren Eksperiminten diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 654f0318e..4600da802 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -442,6 +442,10 @@ Marketinški podaci Dijeli podatke o funkcijama koje upotrebljavaš u %1$s s Leanplumom, našim pružateljem usluga mobilnog marketinga. + + Istraživanja + + Omogućuje Mozilli instalirati i pokrenuti istraživanja Eksperimenti diff --git a/app/src/main/res/values-hsb/strings.xml b/app/src/main/res/values-hsb/strings.xml index 4f81b4440..58c5c4fa6 100644 --- a/app/src/main/res/values-hsb/strings.xml +++ b/app/src/main/res/values-hsb/strings.xml @@ -444,6 +444,10 @@ Marketingowe daty Dźěli daty wo tym, kotre funkcije w %1$s wužiwaće, z Leanplum, našim poskićowarjom za mobilny marketing. + + Studije + + Mozilla dowolić, studije instalować a přewjesć Eksperimenty diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 51beaed8b..c149d2c78 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -22,6 +22,10 @@ Le schede anonime verranno mostrate qui. + + Baidu + + JD Aperta 1 scheda. Tocca per cambiare scheda. @@ -93,6 +97,13 @@ Chiudi + + Cambia la disposizione delle schede aperte. Vai alle impostazioni e seleziona Griglia sotto Visualizzazione schede. + + Vai alle impostazioni + + Annulla + Nuova scheda @@ -608,8 +619,18 @@ Torna alla schermata principale Attiva/disattiva la modalità scheda + + Segnalibro + + Chiudi + + Condividi le schede selezionate + + Menu schede selezionate Rimuovi scheda dalla raccolta + + Seleziona schede Chiudi scheda @@ -1001,6 +1022,12 @@ Scheda chiusa Schede chiuse + + Schede chiuse. + + Segnalibri salvati. + + Visualizza Aggiunto ai siti principali diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index a3c4d8356..e9be788a1 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -443,6 +443,10 @@ נתוני שיווק שיתוף מידע על התכונות שהכי משמשות אותך ב־%1$s עם Leanplum, ספק השיווק שלנו למכשירים ניידים. + + מחקרים + + מאפשר ל־Mozilla להתקין ולהריץ מחקרים ניסויים diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index e5546b792..db42b30d8 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -457,6 +457,10 @@ 마케팅 데이터 %1$s에서 사용하는 기능에 대한 데이터를 모바일 마케팅 공급업체인 Leanplum과 공유합니다. + + 연구 + + Mozilla가 연구를 설치하고 실행하도록 허용 실험 diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 717443796..c8b80cd31 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -447,6 +447,10 @@ Markedsføringsdata Deler data om hvilke funksjoner du bruker i %1$s med Leanplum, vår mobile markedsføringsleverandør. + + Undersøkelse + + Tillater Mozilla å installere og kjøre undersøkelser Eksperimenter diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index b0631b2a8..efab2f832 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -449,6 +449,10 @@ Marketinggegevens Deelt gegevens over welke functies u in %1$s gebruikt met Leanplum, onze leverancier van mobiele marketing. + + Onderzoeken + + Stelt Mozilla in staat om onderzoeken te installeren en uit te voeren Experimenten diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 83c865841..1cbb724fd 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -443,6 +443,10 @@ Dados de marketing Compartilhar dados sobre que recursos você usa no %1$s com o Leanplum, nosso fornecedor de marketing para celular. + + Estudos + + Permitir que a Mozilla instale e execute estudos Experimentos diff --git a/app/src/main/res/values-rm/strings.xml b/app/src/main/res/values-rm/strings.xml index 757004e05..209fa1972 100644 --- a/app/src/main/res/values-rm/strings.xml +++ b/app/src/main/res/values-rm/strings.xml @@ -19,6 +19,10 @@ Tabs privats averts vegnan mussads qua. + + Baidu + + JD 1 tab avert. Tutgar per midar tab. @@ -90,6 +94,9 @@ Serrar + + Mida il layout da tabs averts. Acceda als parameters e tscherna griglia sut vista da tabs. + Nov tab diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml index b2754a0e7..c23c28a6a 100644 --- a/app/src/main/res/values-sq/strings.xml +++ b/app/src/main/res/values-sq/strings.xml @@ -18,6 +18,10 @@ Skedat tuaja private do të shfaqen këtu. + + Baidu + + JD 1 skedë e hapur. Prekeni që të ndërroni skeda. @@ -91,6 +95,13 @@ Hidhe tej + + Ndryshoni skemën e skedave të hapura. Shkoni te rregullimet dhe përzgjidhni rrjetë nën pamjen skeda. + + Shko te rregullimet + + Hidhe tej + Skedë e re @@ -430,6 +441,10 @@ Të dhëna marketingu Ndan me Leanplum-in, furnizuesi ynë i marketingut për celular, të dhëna rreth se cilat veçori përdorni në %1$s. + + Studime + + E lejon Mozilla-n të instalojë dhe xhirojë studime Eksperimente @@ -578,6 +593,8 @@ Hapi Skedat Ruaje në koleksion + + Përzgjidhe Nda krejt skedat @@ -592,8 +609,18 @@ Shko te kreu Ndërroni mënyrë skedash + + Faqeruaji + + Mbylle + + Nda skedat e përzgjedhura + + Menu skedash të përzgjedhura Hiq skedë prej koleksionit + + Përzgjidhni skeda Mbylle skedën @@ -965,6 +992,12 @@ Skeda u mbyll Skedat u mbyllën + + Skedat u mbyllën! + + Faqerojtësit u ruajtën! + + Shihini U shtua te sajtet kryesuese! diff --git a/app/src/main/res/values-su/strings.xml b/app/src/main/res/values-su/strings.xml index 95e371705..1b26f7923 100644 --- a/app/src/main/res/values-su/strings.xml +++ b/app/src/main/res/values-su/strings.xml @@ -19,6 +19,10 @@ Sakur tab anu muka bakal ditémbongkeun di dieu. Sakur tab pribadi anjeun bakal ditémbongkeun di dieu. + + Baidu + + JD 1 tab muka. Toél pikeun ngagilir tab. @@ -90,6 +94,13 @@ Tutup + + Ngarobah perenah tab anu maruka. Buka setélan tur pilih grid handapeun témbongan tab. + + Buka setélan + + Tutup + Tab anyar @@ -434,6 +445,10 @@ Data pamasaran Bagikeun data ngeunaan fitur naon baé anu dipaké di %1$s ka Leanplum, péndor pamasaran ider kami. + + Studi + + Ngidinan Mozilla masang jeung ngajalankeun studi Éksperimén @@ -586,6 +601,8 @@ Buka Tab Simpen kana koléksi + + Pilih Bagikeun sadaya tab @@ -600,8 +617,18 @@ Mulang Mode tab pindah + + Markah + + Tutup + + Bagikeun tab anu dipilih + + Menu tab anu dipilih Cabut tab tina koléksi + + Pilih tab Tutup tab @@ -974,6 +1001,12 @@ Tab ditutup Tab ditutup + + Tab ditutup! + + Markah diteundeun! + + Témbong Ditambahkeun kana loka kawentar! diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 106df49c6..99eb04ad8 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -451,6 +451,10 @@ Marknadsföringsdata Delar data om vilka funktioner du använder i %1$s med Leanplum, vår mobila marknadsföringsleverantör. + + Undersökningar + + Tillåter Mozilla att installera och köra undersökningar Experiment diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 67f8c93aa..1a1bb8235 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -444,6 +444,10 @@ Pazarlama verileri Kullandığınız %1$s özellikleri ile ilgili verileri mobil pazarlama hizmeti aldığımız Leanplum ile paylaşır. + + Araştırmalar + + Mozilla’nın araştırmalar yükleyip çalıştırmasına izin verir Deneyler diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 7181e8667..75cd63a8b 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -449,6 +449,10 @@ Маркетингові дані Надсилання даних про функції, які ви використовуєте в %1$s, нашій компанії з мобільного маркетингу Leanplum. + + Дослідження + + Дозволяє Mozilla встановлювати та запускати дослідження Експерименти diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index e35dcce4f..cf5cf4b56 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -442,6 +442,10 @@ Dữ liệu tiếp thị Chia sẻ dữ liệu về những tính năng bạn sử dụng trong %1$s với Leanplum, nhà cung cấp tiếp thị di động của chúng tôi. + + Nghiên cứu + + Cho phép Mozilla cài đặt và chạy các nghiên cứu Thử nghiệm diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index d12e2f687..ab0c00b9f 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -456,6 +456,10 @@ 营销数据 与我们的移动营销供应商 Leanplum 分享您使用 %1$s 的哪些功能。 + + 研究 + + 允许 Mozilla 安装和运行研究 实验 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 464fee941..e755167b6 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -451,6 +451,10 @@ 行銷資料 與我們的行動行銷服務供應商 Leanplum 分享您在 %1$s 使用了哪些功能。 + + 使用者研究 + + 允許 Mozilla 安裝執行使用者研究 實驗 diff --git a/l10n.toml b/l10n.toml index 840b4e636..077ea1753 100644 --- a/l10n.toml +++ b/l10n.toml @@ -58,6 +58,7 @@ locales = [ "lij", "lo", "lt", + "mix", "ml", "mr", "ms", From 3d161b26ec91a17c9ccc4d5335dfd03308781df2 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Sat, 21 Nov 2020 15:35:54 +0000 Subject: [PATCH 019/202] Update Android Components version to 68.0.20201121143054. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 0e81992d2..2a7e99a58 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201120125142" + const val VERSION = "68.0.20201121143054" } From 7166cf2b4819135ca5a602dff7e4c36ab169fa6e Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Sun, 22 Nov 2020 00:04:21 +0000 Subject: [PATCH 020/202] Import l10n. --- app/src/main/res/values-co/strings.xml | 2 ++ app/src/main/res/values-dsb/strings.xml | 4 ++++ app/src/main/res/values-fi/strings.xml | 6 +++++ app/src/main/res/values-hu/strings.xml | 4 ++++ app/src/main/res/values-th/strings.xml | 31 +++++++++++++++++++++++++ 5 files changed, 47 insertions(+) diff --git a/app/src/main/res/values-co/strings.xml b/app/src/main/res/values-co/strings.xml index 275e9adf2..46329a414 100644 --- a/app/src/main/res/values-co/strings.xml +++ b/app/src/main/res/values-co/strings.xml @@ -624,6 +624,8 @@ Listinu di l’unghjette selezziunate Caccià l’unghjetta da a cullezzione + + Selezziunà unghjette Chjode l’unghjetta diff --git a/app/src/main/res/values-dsb/strings.xml b/app/src/main/res/values-dsb/strings.xml index c33309af9..8264a533b 100644 --- a/app/src/main/res/values-dsb/strings.xml +++ b/app/src/main/res/values-dsb/strings.xml @@ -441,6 +441,10 @@ Marketingowe daty Źěli daty wó tom, kótare funkcije w %1$s wužywaśo, z Leanplum, našym póbitowarjom za mobilny marketing. + + Studije + + Mozilla dowóliś, studije instalěrowaś a pśewjasć Eksperimenty diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index c5d4f37b3..035dfd00d 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -449,6 +449,10 @@ Markkinointitiedot Jakaa tietoa %1$sissa käyttämistäsi ominaisuuksista mobiilimarkkinointia meille toteuttavan Leanplumin kanssa. + + Tutkimukset + + Salli Mozillan asentaa ja suorittaa tutkimuksia Kokeilut @@ -600,6 +604,8 @@ Avoimet välilehdet Tallenna kokoelmaan + + Valitse Jaa kaikki välilehdet diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 14f961ef3..233b5fda0 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -444,6 +444,10 @@ Marketing adatok Megosztja a Leanplummal, a mobil marketing szolgáltatónkkal, hogy mely funkciókat használja a %1$s böngészőben. + + Tanulmányok + + Engedélyezés, hogy a Mozilla tanulmányokat telepítsen és futtasson Kísérletek diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index dcbb3e268..479b0d6e3 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -19,6 +19,10 @@ แท็บส่วนตัวของคุณจะถูกแสดงที่นี่ + + Baidu + + JD 1 แท็บที่เปิด แตะเพื่อสลับไปยังแท็บ @@ -91,6 +95,11 @@ ยกเลิก + + ไปยังการตั้งค่า + + ปิด + แท็บใหม่ @@ -431,6 +440,10 @@ ข้อมูลการตลาด แบ่งปันข้อมูลเกี่ยวกับคุณลักษณะที่คุณใช้ใน %1$s กับ Leanplum ผู้ให้บริการการตลาดมือถือของเรา + + การศึกษา + + อนุญาตให้ Mozilla ติดตั้งและเรียกใช้การศึกษา การทดลอง @@ -579,6 +592,8 @@ แท็บที่เปิด บันทึกไปยังชุดสะสม + + เลือก แบ่งปันแท็บทั้งหมด @@ -593,8 +608,18 @@ ไปยังหน้าแรก สลับโหมดแท็บ + + ที่คั่นหน้า + + ปิด + + แบ่งปันแท็บที่เลือก + + เมนูแท็บที่เลือก เอาแท็บออกจากชุดสะสม + + เลือกแท็บ ปิดแท็บ @@ -966,6 +991,12 @@ ปิดแท็บแล้ว ปิดแท็บแล้ว + + ปิดแท็บแล้ว! + + บันทึกที่คั่นหน้าแล้ว! + + ดู เพิ่มไปยังไซต์เด่นแล้ว! From 98b58090188fdf92d3158aed3e53acea3d665244 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Sun, 22 Nov 2020 15:35:22 +0000 Subject: [PATCH 021/202] Update Android Components version to 68.0.20201122143118. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 2a7e99a58..95eee0d3e 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201121143054" + const val VERSION = "68.0.20201122143118" } From c5b9491619a6d74817934c0036890edfa4d06677 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Mon, 23 Nov 2020 00:03:59 +0000 Subject: [PATCH 022/202] Import l10n. --- app/src/main/res/values-es-rES/strings.xml | 33 ++++++++++++++++++++++ app/src/main/res/values-eu/strings.xml | 4 +++ app/src/main/res/values-hy-rAM/strings.xml | 6 ++++ app/src/main/res/values-lt/strings.xml | 31 ++++++++++++++++++++ app/src/main/res/values-oc/strings.xml | 8 ++++++ app/src/main/res/values-pl/strings.xml | 33 ++++++++++++++++++++++ 6 files changed, 115 insertions(+) diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index a57029d6d..4ca8336cf 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -22,6 +22,10 @@ Tus pestañas privadas se mostrarán aquí. + + Baidu + + JD 1 pestaña abierta. Toca para cambiar de pestaña. @@ -95,6 +99,13 @@ Descartar + + Cambia la disposición de las pestañas abiertas. Ve a ajustes y selecciona cuadrícula en el menú de vista de pestañas. + + Ir a ajustes + + Descartar + Nueva pestaña @@ -442,6 +453,10 @@ Datos de marketing Comparte datos acerca de las funcionalidades que usas en %1$s con Leanplum, nuestro proveedor de marketing para móviles. + + Estudios + + Permite a Mozilla instalar y ejecutar estudios Experimentos @@ -591,6 +606,8 @@ Pestañas abiertas Guardar en la colección + + Seleccionar Compartir todas las pestañas @@ -605,8 +622,18 @@ Ir al inicio Alternar modo de pestaña + + Marcador + + Cerrar + + Compartir pestañas seleccionadas + + Menú de pestañas seleccionadas Eliminar pestaña de la colección + + Seleccionar pestañas Cerrar pestaña @@ -997,6 +1024,12 @@ Pestaña cerrada Pestañas cerradas + + ¡Pestañas cerradas! + + ¡Marcadores guardados! + + Ver ¡Añadido a sitios favoritos! diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index dc71c3369..82c71618e 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -451,6 +451,10 @@ Marketing datuak %1$s(r)en erabiltzen dituzun eginbideei buruzko datuak partekatzen ditu Leanplum-ekin, mugikorrerako gure marketing hornitzailearekin. + + Esperimentuak + + Mozillari esperimentuak instalatu eta exekutatzea baimentzen du Esperimentuak diff --git a/app/src/main/res/values-hy-rAM/strings.xml b/app/src/main/res/values-hy-rAM/strings.xml index 942bf7e2f..f454bc3f7 100644 --- a/app/src/main/res/values-hy-rAM/strings.xml +++ b/app/src/main/res/values-hy-rAM/strings.xml @@ -96,6 +96,8 @@ Բաց թողնել + + Փոխել բաց ներդիրների դասավորությունը: Գնացեք կարգավորումներ և ընտրեք ցանցը՝ ներդիրի տեսքի ներքո: Անցնել Կարգավորումներին @@ -440,6 +442,10 @@ Շուկայավարման տվյալներ Համօգտագործում է տվյալներն այն գործառույթների վերաբերյալ, որոնք դուք օգտագործում եք %1$s-ում Leanplum-ի հետ, բջջային մարկետինգի մեր մատակարարի: + + Ուսումնասիրություններ + + Թույլ է տալիս Mozilla-ին տեղադրել և իրականացնել ուսումնասիրություններ Փորձեր diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index d54468733..a0107cd84 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -20,6 +20,10 @@ Čia bus rodomos jūsų privačiosios kortelės. + + Baidu + + JD 1 atverta kortelė. Bakstelėkite, norėdami pereiti tarp kortelių. @@ -92,6 +96,13 @@ Paslėpti + + Pakeiskite atvertų kortelių išdėstymą. Eikite į nuostatas, ir pasirinkite tinklelį prie kortelių rodinio. + + Eiti į nuostatas + + Paslėpti + Nauja kortelė @@ -431,6 +442,10 @@ Rinkodaros duomenys Duomenys apie jūsų naudojamas „%1$s“ funkcijas bus perduodami „Leanplum“, mūsų mobiliosios rinkodaros pardavėjui. + + Tyrimai + + Leidžia „Mozillai“ diegti ir vykdyti tyrimus Eksperimentai @@ -596,8 +611,18 @@ Eiti į pradžią Keisti kortelių veikseną + + Įtraukti į adresyną + + Užverti + + Dalintis pasirinktomis kortelėmis + + Pasirinktų kortelių meniu Pašalinti kortelę iš rinkinio + + Pasirinkti korteles Užverti kortelę @@ -973,6 +998,12 @@ Kortelė užverta Kortelės užvertos + + Kortelės užvertos! + + Adresynas papildytas! + + Peržiūrėti Įtraukta į mėgstamiausias svetaines! diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index 453010bf2..8d4c52133 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -95,6 +95,8 @@ Ignorar + + Cambiatz la disposicion dels onglet dubèrts. Anatz als paramètres e seleccionatz « »Gresilha » jos « Vista onglet ». Anar als paramètres @@ -443,6 +445,10 @@ Donadas marketing Parteja de donadas sus las foncionalitats qu’utilizatz amb Leanplum, nòstre provesidor de marketing mobil. + + Estudis + + Permet a Mozilla d’installar e lançar estudis Experimentacions @@ -621,6 +627,8 @@ Menú dels onglets seleccionats Suprimir l’onglet de la colleccion + + Seleccionar d’onglets Tampar l’onglet diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a3014648f..bfa0a194d 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -20,6 +20,10 @@ Tutaj będą wyświetlane prywatne karty. + + Baidu + + JD Otwarte karty: 1. Stuknij, aby przełączyć karty. @@ -94,6 +98,13 @@ Zamknij + + Zmień układ otwartych kart. Otwórz ustawienia i wybierz siatkę w sekcji wyglądu kart. + + Otwórz ustawienia + + Zamknij + Nowa karta @@ -437,6 +448,10 @@ Dane marketingowe Udostępnia dane o funkcjach przeglądarki %1$s używanych przez użytkownika firmie Leanplum, naszemu dostawcy marketingu mobilnego. + + Badania + + Pozwala Mozilli instalować i wykonywać badania. Eksperymenty @@ -586,6 +601,8 @@ Otwarte karty Zachowaj w kolekcji + + Wybierz Udostępnij wszystkie karty @@ -600,8 +617,18 @@ Przejdź na stronę startową Przełącz tryb kart + + Dodaj zakładkę + + Zamknij + + Udostępnij zaznaczone karty + + Menu zaznaczonych kart Usuń kartę z kolekcji + + Zaznacz karty Zamknij kartę @@ -976,6 +1003,12 @@ Zamknięto kartę Zamknięto karty + + Zamknięto karty + + Dodano zakładki + + Wyświetl Dodano do popularnych stron From 451f60b095b7e5d39967a26a9974f7099c5f0c5c Mon Sep 17 00:00:00 2001 From: mcarare Date: Mon, 16 Nov 2020 18:22:03 +0200 Subject: [PATCH 023/202] For #16353: Do not finish onboarding on search dialog opening. --- app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 94dd6c009..3f77c22a8 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -385,7 +385,6 @@ class HomeFragment : Fragment() { view.toolbar.compoundDrawablePadding = view.resources.getDimensionPixelSize(R.dimen.search_bar_search_engine_icon_padding) view.toolbar_wrapper.setOnClickListener { - hideOnboardingIfNeeded() navigateToSearch() requireComponents.analytics.metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.HOME)) } From 089c79f868d893e7c7adb41e7ad74c39a34a6531 Mon Sep 17 00:00:00 2001 From: mcarare Date: Tue, 17 Nov 2020 13:49:06 +0200 Subject: [PATCH 024/202] For #16353: Finish onboarding on loading a web page, with exceptions. --- .../fenix/browser/BaseBrowserFragment.kt | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) 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 3fe1a59a8..56a6b2a02 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -117,6 +117,8 @@ import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.HomeScreenViewModel import org.mozilla.fenix.home.SharedViewModel +import org.mozilla.fenix.onboarding.FenixOnboarding +import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.utils.allowUndo import org.mozilla.fenix.wifi.SitePermissionsWifiIntegration @@ -174,6 +176,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, protected var webAppToolbarShouldBeVisible = true private val sharedViewModel: SharedViewModel by activityViewModels() + private val onboarding by lazy { FenixOnboarding(requireContext()) } @CallSuper override fun onCreateView( @@ -217,6 +220,11 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, } observeTabSelection(requireComponents.core.store) + + if (!onboarding.userHasBeenOnboarded()) { + observeTabSource(requireComponents.core.store) + } + requireContext().accessibilityManager.addAccessibilityStateChangeListener(this) } @@ -841,6 +849,25 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, } } + @VisibleForTesting + @Suppress("ComplexCondition") + internal fun observeTabSource(store: BrowserStore) { + consumeFlow(store) { flow -> + flow.mapNotNull { state -> + state.selectedTab + } + .collect { + if (!onboarding.userHasBeenOnboarded() && + it.content.loadRequest?.triggeredByRedirect != true && + it.source !in intentSourcesList && + it.content.url !in onboardingLinksList + ) { + onboarding.finish() + } + } + } + } + private fun handleTabSelected(selectedTab: TabSessionState) { if (!this.isRemoving) { updateThemeForSession(selectedTab) @@ -1206,6 +1233,17 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, private const val REQUEST_CODE_DOWNLOAD_PERMISSIONS = 1 private const val REQUEST_CODE_PROMPT_PERMISSIONS = 2 private const val REQUEST_CODE_APP_PERMISSIONS = 3 + + val onboardingLinksList: List = listOf( + SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE), + SupportUtils.getFirefoxAccountSumoUrl() + ) + + val intentSourcesList: List = listOf( + SessionState.Source.ACTION_SEARCH, + SessionState.Source.ACTION_SEND, + SessionState.Source.ACTION_VIEW + ) } override fun onAccessibilityStateChanged(enabled: Boolean) { From de1fa74ac13cfb2b381e824e1c858244d3b0a264 Mon Sep 17 00:00:00 2001 From: mcarare Date: Fri, 20 Nov 2020 16:40:12 +0200 Subject: [PATCH 025/202] For #16353: Add unit tests for onboarding finishing conditions. --- .../fenix/browser/BaseBrowserFragment.kt | 4 +- .../fenix/browser/BrowserFragmentTest.kt | 79 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) 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 56a6b2a02..9727a8684 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -176,7 +176,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, protected var webAppToolbarShouldBeVisible = true private val sharedViewModel: SharedViewModel by activityViewModels() - private val onboarding by lazy { FenixOnboarding(requireContext()) } + + @VisibleForTesting + internal val onboarding by lazy { FenixOnboarding(requireContext()) } @CallSuper override fun onCreateView( diff --git a/app/src/test/java/org/mozilla/fenix/browser/BrowserFragmentTest.kt b/app/src/test/java/org/mozilla/fenix/browser/BrowserFragmentTest.kt index 29ddf88da..8c7ea5193 100644 --- a/app/src/test/java/org/mozilla/fenix/browser/BrowserFragmentTest.kt +++ b/app/src/test/java/org/mozilla/fenix/browser/BrowserFragmentTest.kt @@ -21,6 +21,7 @@ import mozilla.components.browser.state.action.RestoreCompleteAction import mozilla.components.browser.state.action.TabListAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.LoadRequestState +import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.createTab import mozilla.components.browser.state.store.BrowserStore @@ -38,6 +39,7 @@ import org.mozilla.fenix.components.toolbar.BrowserToolbarView import org.mozilla.fenix.ext.application import org.mozilla.fenix.ext.components import org.mozilla.fenix.helpers.FenixRobolectricTestRunner +import org.mozilla.fenix.onboarding.FenixOnboarding @ExperimentalCoroutinesApi @RunWith(FenixRobolectricTestRunner::class) @@ -52,6 +54,7 @@ class BrowserFragmentTest { private lateinit var context: Context private lateinit var lifecycleOwner: MockedLifecycleOwner private lateinit var navController: NavController + private lateinit var onboarding: FenixOnboarding private val testDispatcher = TestCoroutineDispatcher() @@ -68,6 +71,7 @@ class BrowserFragmentTest { view = mockk(relaxed = true) lifecycleOwner = MockedLifecycleOwner(Lifecycle.State.STARTED) navController = mockk(relaxed = true) + onboarding = mockk(relaxed = true) browserFragment = spyk(BrowserFragment()) every { browserFragment.view } returns view @@ -75,6 +79,8 @@ class BrowserFragmentTest { every { browserFragment.browserToolbarView } returns mockk(relaxed = true) every { browserFragment.activity } returns homeActivity every { browserFragment.lifecycle } returns lifecycleOwner.lifecycle + every { browserFragment.onboarding } returns onboarding + every { browserFragment.requireContext() } returns context every { browserFragment.initializeUI(any()) } returns mockk() every { browserFragment.fullScreenChanged(any()) } returns Unit @@ -205,6 +211,79 @@ class BrowserFragmentTest { verify(exactly = 1) { navController.popBackStack(R.id.homeFragment, false) } } + @Test + fun `GIVEN the onboarding is finished WHEN visiting any link THEN the onboarding is not dismissed `() { + every { onboarding.userHasBeenOnboarded() } returns true + + browserFragment.observeTabSource(store) + + val newSelectedTab = createTab("any-tab.org") + addAndSelectTab(newSelectedTab) + + verify(exactly = 0) { onboarding.finish() } + } + + @Test + fun `GIVEN the onboarding is not finished WHEN visiting a link THEN the onboarding is dismissed `() { + every { onboarding.userHasBeenOnboarded() } returns false + + browserFragment.observeTabSource(store) + + val newSelectedTab = createTab("any-tab.org") + addAndSelectTab(newSelectedTab) + + verify(exactly = 1) { onboarding.finish() } + } + + @Test + fun `GIVEN the onboarding is not finished WHEN visiting an onboarding link THEN the onboarding is not dismissed `() { + every { onboarding.userHasBeenOnboarded() } returns false + + browserFragment.observeTabSource(store) + + val newSelectedTab = createTab(BaseBrowserFragment.onboardingLinksList[0]) + addAndSelectTab(newSelectedTab) + + verify(exactly = 0) { onboarding.finish() } + } + + @Test + fun `GIVEN the onboarding is not finished WHEN opening a page from another app THEN the onboarding is not dismissed `() { + every { onboarding.userHasBeenOnboarded() } returns false + + browserFragment.observeTabSource(store) + + val newSelectedTab1: TabSessionState = mockk(relaxed = true) + val newSelectedTab2: TabSessionState = mockk(relaxed = true) + val newSelectedTab3: TabSessionState = mockk(relaxed = true) + + every { newSelectedTab1.source } returns SessionState.Source.ACTION_SEARCH + every { newSelectedTab2.source } returns SessionState.Source.ACTION_SEND + every { newSelectedTab3.source } returns SessionState.Source.ACTION_VIEW + + addAndSelectTab(newSelectedTab1) + verify(exactly = 0) { onboarding.finish() } + + addAndSelectTab(newSelectedTab2) + verify(exactly = 0) { onboarding.finish() } + + addAndSelectTab(newSelectedTab3) + verify(exactly = 0) { onboarding.finish() } + } + + @Test + fun `GIVEN the onboarding is not finished WHEN visiting an link after redirect THEN the onboarding is not dismissed `() { + every { onboarding.userHasBeenOnboarded() } returns false + + val newSelectedTab: TabSessionState = mockk(relaxed = true) + every { newSelectedTab.content.loadRequest?.triggeredByRedirect } returns true + + browserFragment.observeTabSource(store) + addAndSelectTab(newSelectedTab) + + verify(exactly = 0) { onboarding.finish() } + } + private fun addAndSelectTab(tab: TabSessionState) { store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking() store.dispatch(TabListAction.SelectTabAction(tab.id)).joinBlocking() From f823a5d24695af5f4f6b45117586b87108780024 Mon Sep 17 00:00:00 2001 From: mcarare Date: Mon, 23 Nov 2020 12:06:42 +0200 Subject: [PATCH 026/202] For #16353: Avoid strict mode death on reading onboarding preference. --- .../java/org/mozilla/fenix/browser/BaseBrowserFragment.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 9727a8684..de85e69f4 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -223,8 +223,10 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, observeTabSelection(requireComponents.core.store) - if (!onboarding.userHasBeenOnboarded()) { - observeTabSource(requireComponents.core.store) + lifecycleScope.launch(IO) { + if (!onboarding.userHasBeenOnboarded()) { + observeTabSource(requireComponents.core.store) + } } requireContext().accessibilityManager.addAccessibilityStateChangeListener(this) From 39f9d992fb71f28224b181366bc86552905fda1a Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Mon, 23 Nov 2020 15:35:56 +0000 Subject: [PATCH 027/202] Update Android Components version to 68.0.20201122190139. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 95eee0d3e..833886927 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201122143118" + const val VERSION = "68.0.20201122190139" } From f89f6fd2a22b031adcc72654a6eab2d04de3da8e Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Tue, 24 Nov 2020 00:04:24 +0000 Subject: [PATCH 028/202] Import l10n. --- app/src/main/res/values-fr/strings.xml | 4 ++++ app/src/main/res/values-it/strings.xml | 6 ++++++ app/src/main/res/values-nn-rNO/strings.xml | 15 +++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 2e37ef108..d56aacbe9 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -448,6 +448,10 @@ Données marketing Partage des données sur les fonctionnalités que vous utilisez dans %1$s avec Leanplum, notre fournisseur de marketing mobile. + + Études + + Autoriser Mozilla à installer et exécuter des études Expérimentations diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c149d2c78..76b6909af 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -454,6 +454,10 @@ Dati di marketing Condividi dati sulle funzioni utilizzate in %1$s con Leanplum, il nostro partner per il marketing su piattaforma mobile. + + Studi + + Consenti a Mozilla di installare e condurre studi Esperimenti @@ -605,6 +609,8 @@ Schede aperte Salva in una raccolta + + Seleziona Condividi tutte le schede diff --git a/app/src/main/res/values-nn-rNO/strings.xml b/app/src/main/res/values-nn-rNO/strings.xml index 8eb6014aa..ab0273152 100644 --- a/app/src/main/res/values-nn-rNO/strings.xml +++ b/app/src/main/res/values-nn-rNO/strings.xml @@ -22,6 +22,10 @@ Dine private faner vil visast her. + + Baidu + + JD 1 open fane. Trykk for å byte fane. @@ -96,6 +100,11 @@ Ignorer + + Gå til Innstillingar + + Avvis + Ny fane @@ -435,6 +444,8 @@ Marknadsføringsdata Deler data om kva for funksjonar du brukar i %1$s med Leanplum, den mobile marknadsføringsleverandøren vår. + + undersøking Eksperiment @@ -588,6 +599,8 @@ Opne faner Lagre i samling + + Vel Del alle faner @@ -602,6 +615,8 @@ Gå til startskjerm Veksle fanemodus + + Bokmerke Fjern fane frå samlinga From 4b56aeb12fb5307e06e23fec848d88c835554243 Mon Sep 17 00:00:00 2001 From: Codrut Topliceanu <60002907+codrut-topliceanu@users.noreply.github.com> Date: Tue, 24 Nov 2020 09:11:27 +0200 Subject: [PATCH 029/202] For #16592 - Fix missing collection snackbar for a11y (#16593) Set null anchor for snackbar when using a11y services --- .../fenix/tabtray/TabTrayDialogFragment.kt | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt index c0dcd4d38..7ed8f6a62 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt @@ -72,14 +72,18 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler private lateinit var tabTrayDialogStore: TabTrayDialogFragmentStore private val snackbarAnchor: View? - get() = if (tabTrayView.fabView.new_tab_button.isVisible || - tabTrayView.mode != Mode.Normal - ) tabTrayView.fabView.new_tab_button - /* During selection of the tabs to the collection, the FAB is not visible, - which leads to not attaching a needed AnchorView. That's why, we're not only checking, if it's not visible, - but also if we're not in a "Normal" mode, so after selecting tabs for a collection, we're pushing snackbar - above the FAB, as we're switching from "Multiselect" to "Normal". */ - else null + get() = + // Fab is hidden when Talkback is activated. See #16592 + if (requireContext().settings().accessibilityServicesEnabled) null + else if (tabTrayView.fabView.new_tab_button.isVisible || + tabTrayView.mode != Mode.Normal + ) tabTrayView.fabView.new_tab_button + /* During selection of the tabs to the collection, the FAB is not visible, + which leads to not attaching a needed AnchorView. That's why, we're not only + checking, if it's not visible, but also if we're not in a "Normal" mode, so after + selecting tabs for a collection, we're pushing snackbar + above the FAB, as we're switching from "Multiselect" to "Normal". */ + else null private val collectionStorageObserver = object : TabCollectionStorage.Observer { override fun onCollectionCreated(title: String, sessions: List) { From 7173e475d28294091f8d4cee09880c6f43034dcc Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Tue, 24 Nov 2020 15:36:50 +0000 Subject: [PATCH 030/202] Update Android Components version to 68.0.20201124143116. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 833886927..27317161a 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201122190139" + const val VERSION = "68.0.20201124143116" } From 23460fac51943c6b582ccf619d62a5328620d496 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Wed, 25 Nov 2020 00:02:09 +0000 Subject: [PATCH 031/202] Import l10n. --- app/src/main/res/values-co/strings.xml | 6 ++ app/src/main/res/values-ja/strings.xml | 33 ++++++++++ app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-sl/strings.xml | 6 ++ app/src/main/res/values-tl/strings.xml | 73 ++++++++++++++++++++++ 5 files changed, 119 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-co/strings.xml b/app/src/main/res/values-co/strings.xml index 46329a414..6fd905c78 100644 --- a/app/src/main/res/values-co/strings.xml +++ b/app/src/main/res/values-co/strings.xml @@ -103,6 +103,8 @@ Mudificà l’accunciamentu di l’unghjette aperte. Andate in e preferenze è selezziunate « Quadrittere » sottu « Vista di l’unghjette ». + + Andà à e preferenze Ignurà @@ -447,6 +449,10 @@ Dati di cummercializazione Scumparte i dati nant’à e funzioni di %1$s chì voi impiegate cù Leanplum, u nostru furnidore di cummercializazione mubile. + + Studii + + Permette à Mozilla d’installà è di lancià studii Sperimentazioni diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 840003b50..541ddb8be 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -22,6 +22,10 @@ プライベートタブがここに表示されます。 + + Baidu + + JD 開いているタブ 1 個。タップしてタブを切り替えます。 @@ -96,6 +100,13 @@ 閉じる + + 開いたタブのレイアウトを変更します。設定画面を開き、タブビューの下のグリッドを選択してください。 + + 設定を開く + + 閉じる + 新しいタブ @@ -441,6 +452,10 @@ マーケティングデータ %1$s で使用した機能に関するデータをモバイルマーケティングベンダーの Leanplum と共有します。 + + 調査 + + 調査機能のインストールと実行を Mozilla に許可する 実験的な機能 @@ -590,6 +605,8 @@ 開いているタブ コレクションに保存 + + 選択 すべてのタブを共有 @@ -604,8 +621,18 @@ ホーム画面を開く タブモードを切り替え + + ブックマーク + + 閉じる + + 選択したタブを共有 + + 選択したタブのメニュー タブをコレクションから削除 + + タブを選択 タブを閉じる @@ -986,6 +1013,12 @@ タブを閉じました すべてのタブを閉じました + + タブを閉じました + + ブックマークを保存しました + + 表示 トップサイトに追加しました。 diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 1cbb724fd..0a8b3c443 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1242,7 +1242,7 @@ Entre com sua câmera - Usar e-mail + Usar email Crie uma para sincronizar o Firefox entre dispositivos.]]> diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 2f0e41008..c88bde699 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -446,6 +446,10 @@ Podatki za trženje Deli podatke o možnostih, ki jih uporabljate v %1$su z Leanplumom, našim ponudnikom mobilnega oglaševanja. + + Raziskave + + Dovoli Mozilli namestitev in izvajanje raziskav Poskusi @@ -501,6 +505,8 @@ Povlecite za osvežitev + + Pomaknite se, da skrijete orodno vrstico Podrsaj orodno vrstico vstran za preklop med zavihki diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index 7c57285b9..6f18d734a 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -9,6 +9,10 @@ Iba pang pagpipilian Paganahin ang private browsing + + Baidu + + JD %1$d ang napili @@ -121,6 +125,8 @@ Powered by %1$s Reader view + + Buksan sa app Itsura @@ -163,6 +169,10 @@ Tungkol sa %1$s Mga Password + + Pribasiya + + Pribasiya at Seguridad Accessibility @@ -181,21 +191,41 @@ Wika + + Developer tools + OK + + Kanselahin + + + Kasaysayan Mga Bookmark + + Mga Login mag-Sign out Ngalan ng device + + %1$s ng %2$s %3$s + Telemetry + + Marketing data Mga eksperimento + + Mozilla location service + Buksan ang Camera @@ -207,6 +237,9 @@ Ilalim + + + Light Madilim @@ -223,18 +256,58 @@ Isara + + %d mga tab + + %d tab + Mga Tab + + Listahan Grid Magdagdag ng Tab + + Pumili Bagong Tab Burahin + + i-Save + + Alisin + + i-Save + + + + Kopyahin + + + + Kopyahin + + + Kopyahin ang URL + + + + Kopyahin + + + Kopyahin ang password + + Kopyahin ang username + + Kopyahin ang site + From 2d652acbf26ebebabedd6bcf70b7344e8efb0f2c Mon Sep 17 00:00:00 2001 From: Oana Horvath Date: Tue, 24 Nov 2020 19:46:28 +0200 Subject: [PATCH 032/202] For #16615: UI smoke test addPredefinedSearchEngineTest --- .../java/org/mozilla/fenix/ui/SmokeTest.kt | 27 +++++++++++-- .../mozilla/fenix/ui/robots/SearchRobot.kt | 22 +++++++---- .../ui/robots/SettingsSubMenuSearchRobot.kt | 39 ++++++++++++++++--- 3 files changed, 71 insertions(+), 17 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt index 55e13cf94..15e2f414a 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt @@ -214,7 +214,7 @@ class SmokeTest { homeScreen { }.openSearch { verifyKeyboardVisibility() - clickSearchEngineButton() + clickSearchEngineShortcutButton() verifySearchEngineList() changeDefaultSearchEngine("Amazon.com") verifySearchEngineIcon("Amazon.com") @@ -222,7 +222,7 @@ class SmokeTest { }.enterURLAndEnterToBrowser(defaultWebPage.url) { }.openTabDrawer { }.openNewTab { - clickSearchEngineButton() + clickSearchEngineShortcutButton() mDevice.waitForIdle() changeDefaultSearchEngine("Bing") verifySearchEngineIcon("Bing") @@ -230,7 +230,7 @@ class SmokeTest { }.enterURLAndEnterToBrowser(defaultWebPage.url) { }.openTabDrawer { }.openNewTab { - clickSearchEngineButton() + clickSearchEngineShortcutButton() mDevice.waitForIdle() changeDefaultSearchEngine("DuckDuckGo") verifySearchEngineIcon("DuckDuckGo") @@ -238,7 +238,7 @@ class SmokeTest { }.enterURLAndEnterToBrowser(defaultWebPage.url) { }.openTabDrawer { }.openNewTab { - clickSearchEngineButton() + clickSearchEngineShortcutButton() changeDefaultSearchEngine("Wikipedia") verifySearchEngineIcon("Wikipedia") }.goToSearchEngine { @@ -254,4 +254,23 @@ class SmokeTest { } } } + + @Test + fun addPredefinedSearchEngineTest() { + homeScreen { + }.openThreeDotMenu { + }.openSettings { + }.openSearchSubMenu { + openAddSearchEngineMenu() + verifyAddSearchEngineList() + addNewSearchEngine("YouTube") + verifyEngineListContains("YouTube") + }.goBack { + }.goBack { + }.openSearch { + verifyKeyboardVisibility() + clickSearchEngineShortcutButton() + verifyEnginesListShortcutContains("YouTube") + } + } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SearchRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SearchRobot.kt index 22be73813..31efbbb8d 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SearchRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SearchRobot.kt @@ -6,18 +6,19 @@ package org.mozilla.fenix.ui.robots -import android.widget.ToggleButton import androidx.recyclerview.widget.RecyclerView import androidx.test.espresso.Espresso.onView import androidx.test.espresso.ViewInteraction import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.closeSoftKeyboard +import androidx.test.espresso.action.ViewActions.swipeDown import androidx.test.espresso.action.ViewActions.typeText import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.Visibility +import androidx.test.espresso.matcher.ViewMatchers.hasDescendant import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withId @@ -62,15 +63,16 @@ class SearchRobot { } fun verifyDefaultSearchEngine(expectedText: String) = assertDefaultSearchEngine(expectedText) + fun verifyEnginesListShortcutContains(searchEngineName: String) = assertEngineListShortcutContains(searchEngineName) + fun changeDefaultSearchEngine(searchEngineName: String) = selectDefaultSearchEngine(searchEngineName) - fun clickSearchEngineButton() { - val searchEngineButton = mDevice.findObject(UiSelector() - .instance(1) - .className(ToggleButton::class.java)) - searchEngineButton.waitForExists(waitingTime) - searchEngineButton.click() + fun clickSearchEngineShortcutButton() { + val searchEnginesShortcutButton = mDevice.findObject(UiSelector() + .resourceId("org.mozilla.fenix.debug:id/search_engines_shortcut_button")) + searchEnginesShortcutButton.waitForExists(waitingTime) + searchEnginesShortcutButton.click() } fun clickScanButton() { @@ -266,6 +268,12 @@ private fun assertSearchEngineList() { .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) } +private fun assertEngineListShortcutContains(searchEngineName: String) { + onView(withId(R.id.awesome_bar)) + .perform(swipeDown()) + .check(matches(hasDescendant(withText(searchEngineName)))) +} + private fun selectDefaultSearchEngine(searchEngine: String) { onView(withId(R.id.mozac_browser_toolbar_edit_icon)).click() onView(withText(searchEngine)) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuSearchRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuSearchRobot.kt index 13f9cd795..94e857a83 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuSearchRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuSearchRobot.kt @@ -13,6 +13,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.matcher.ViewMatchers.Visibility import androidx.test.espresso.matcher.ViewMatchers.hasDescendant +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText @@ -20,6 +21,8 @@ import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import org.hamcrest.CoreMatchers +import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.click /** * Implementation of Robot Pattern for the settings search sub menu. @@ -32,12 +35,26 @@ class SettingsSubMenuSearchRobot { fun verifyShowClipboardSuggestions() = assertShowClipboardSuggestions() fun verifySearchBrowsingHistory() = assertSearchBrowsingHistory() fun verifySearchBookmarks() = assertSearchBookmarks() + fun changeDefaultSearchEngine(searchEngineName: String) = - selectDefaultSearchEngine(searchEngineName) + selectSearchEngine(searchEngineName) fun disableShowSearchSuggestions() = toggleShowSearchSuggestions() fun enableShowSearchShortcuts() = toggleShowSearchShortcuts() + fun openAddSearchEngineMenu() = addSearchEngineButton().click() + + fun verifyAddSearchEngineList() = assertAddSearchEngineList() + + fun verifyEngineListContains(searchEngineName: String) = assertEngineListContains(searchEngineName) + + fun saveNewSearchEngine() = addSearchEngineSaveButton().click() + + fun addNewSearchEngine(searchEngineName: String) { + selectSearchEngine(searchEngineName) + saveNewSearchEngine() + } + class Transition { val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) @@ -120,16 +137,12 @@ private fun assertSearchBookmarks() { .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) } -private fun selectDefaultSearchEngine(searchEngine: String) { +private fun selectSearchEngine(searchEngine: String) { onView(withText(searchEngine)) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .perform(click()) } -private fun selectDuckDuckGoAsSearchEngine() { - selectDefaultSearchEngine("DuckDuckGo") -} - private fun toggleShowSearchSuggestions() { onView(withId(androidx.preference.R.id.recycler_view)).perform( RecyclerViewActions.scrollTo( @@ -154,3 +167,17 @@ private fun toggleShowSearchShortcuts() { private fun goBackButton() = onView(CoreMatchers.allOf(withContentDescription("Navigate up"))) + +private fun addSearchEngineButton() = onView(withText("Add search engine")) + +private fun assertAddSearchEngineList() { + onView(withText("Reddit")).check(matches(isDisplayed())) + onView(withText("YouTube")).check(matches(isDisplayed())) + onView(withText("Other")).check(matches(isDisplayed())) +} + +private fun addSearchEngineSaveButton() = onView(withId(R.id.add_search_engine)) + +private fun assertEngineListContains(searchEngineName: String) { + onView(withId(R.id.search_engine_group)).check(matches(hasDescendant(withText(searchEngineName)))) +} From 94c59cb3ca9cec990a07712b52f8973fadc33b83 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Thu, 26 Nov 2020 00:04:46 +0000 Subject: [PATCH 033/202] Import l10n. --- app/src/main/res/values-da/strings.xml | 80 +++++++++++++++++++--- app/src/main/res/values-kk/strings.xml | 18 +++++ app/src/main/res/values-pt-rPT/strings.xml | 77 ++++++++++++++++++--- 3 files changed, 154 insertions(+), 21 deletions(-) diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 1b1e07a0b..f2200eb90 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -19,6 +19,10 @@ Dine private faneblade vil blive vist her. + + Baidu + + JD 1 åbent faneblad. Tryk for at skifte faneblade. @@ -70,14 +74,6 @@ Nej tak - - - Åbn Firefox hurtigere. Tilføj en widget til din startskærm. - - Tilføj widget - - Ikke nu - Du kan sætte Firefox til automatisk at åbne links i apps. @@ -100,6 +96,14 @@ Afvis + + Skift visning af åbne faneblade. Gå til indstillingerne og vælg gitter under fanebladsvisning. + + Gå til indstillinger + + + Afvis + Nyt faneblad @@ -435,6 +439,10 @@ Markedsføringsdata Deler data om de funktioner, du bruger i %1$s, med Leanplum, vores samarbejdspartner inden for markedsføring. + + Undersøgelser + + Tillad at Mozilla installerer og afvikler undersøgelser Eksperimenter @@ -558,6 +566,15 @@ Efter en måned + + Luk manuelt + + Luk efter en dag + + Luk efter en uge + + Luk efter en måned + Åbn faneblade @@ -575,6 +592,8 @@ Åbne faneblade Gem til samling + + Vælg Del alle faneblade @@ -589,8 +608,18 @@ Gå til startsiden Skift tilstand for faneblade + + Bogmærk + + Luk + + Del valgte faneblde + + Menu for valgte faneblade Fjern faneblad fra samling + + Vælg faneblade Luk faneblad @@ -625,8 +654,10 @@ Åbn faneblade Samlingens navn - - Fjern + + Omdøb + + Fjern Slet fra historik @@ -679,12 +710,24 @@ Ingen historik + + Ryd filhentninger + + Er du sikker på, at du vil rydde dine filhentninger? + + Filhentninger ryddet Ingen filhentninger her %1$d valgt + + Åbn + + Slet + + %1$s kan ikke indlæse siden. @@ -945,6 +988,12 @@ Faneblad lukket Faneblade lukket + + Faneblade lukket! + + Bogmærker gemt! + + Vis Føjet til foretrukne websteder! @@ -1593,7 +1642,7 @@ - Grænsen for Mest besøgte websider er nået + Grænsen for foretrukne websteder er nået Fjern en webside for at erstatte den med en ny. Tryk og hold på websiden, og vælg så Fjern. @@ -1602,6 +1651,15 @@ Vis mest besøgte websteder + + Navn + + Navn på foretrukket websted + + OK + + Annuller + Fjern diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml index 6df0c0558..f7a115a62 100644 --- a/app/src/main/res/values-kk/strings.xml +++ b/app/src/main/res/values-kk/strings.xml @@ -95,6 +95,8 @@ Тайдыру + + Ашық беттер жаймасын өзгерту. Баптауларға өтіп, беттер көрінісі астындағы торды таңдаңыз. Баптауларға өту @@ -434,6 +436,10 @@ Маркетингтік мәліметтер Leanplum, біздің мобильді маркетинг өндірушісімен, %1$s ішінде қандай мүмкіндіктерді пайдаланғаныңыз туралы деректермен бөліседі. + + Зерттеулер + + Mozilla-ға зерттеулерді орнатуға және жүргізуге рұқсат ету Эксперименттер @@ -582,6 +588,8 @@ Ашық беттер Жинаққа сақтау + + Таңдау Барлық беттермен бөлісу @@ -602,8 +610,12 @@ Жабу Таңдалған беттермен бөлісу + + Таңдалған беттер мәзірі Бетті жинақтан өшіру + + Беттерді таңдау Бетті жабу @@ -974,6 +986,12 @@ Бет жабылды Беттер жабылды + + Беттер жабылды! + + Бетбелгілер сақталды! + + Қарап шығу Үздік сайттарға қосылды! diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index a62743df8..225aeb63f 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -20,6 +20,10 @@ Os seus separadores privados serão mostrados aqui. + + Baidu + + JD 1 separador aberto. Toque para mudar de separador. @@ -72,14 +76,6 @@ Não, obrigado - - - Aceda ao Firefox mais rapidamente. Adicione um widget ao seu ecrã inicial. - - Adicionar widget - - Agora não - Pode configurar o Firefox para abrir automaticamente as ligações nas aplicações. @@ -102,6 +98,13 @@ Dispensar + + Altere a disposição dos separadores abertos. Aceda às configurações e selecione grelha sob vista de separadores. + + Ir para as definições + + Ignorar + Novo separador @@ -440,6 +443,10 @@ Partilha dados sobre as funcionalidades que utiliza no %1$s com o Leanplum, o nosso parceiro de marketing para dispositivos móveis. + + Estudos + + Permitir que a Mozilla instale e faça estudos Experiências @@ -566,6 +573,15 @@ Depois de um mês + + Fechar manualmente + + Fechar depois de um dia + + Fechar depois de uma semana + + Fechar depois um mês + Separadores abertos @@ -584,6 +600,8 @@ Separadores abertos Guardar na coleção + + Selecionar Partilhar todos os separadores @@ -598,8 +616,18 @@ Ir para início Permutar modo de separador + + Marcador + + Fechar + + Partilhar os separadores selecionados + + Menu de separadores selecionados Remover separador da coleção + + Selecionar separadores Fechar separador @@ -634,8 +662,10 @@ Abrir separadores Nome da coleção - - Remover + + Mudar o nome + + Remover Eliminar do histórico @@ -688,12 +718,24 @@ Sem histórico + + Limpar transferências + + Tem certeza de que pretende limpar as transferências? + + Transferências eliminadas Sem transferências aqui %1$d selecionados + + Abrir + + Apagar + + Desculpe. O %1$s não pode carregar essa página. @@ -956,6 +998,12 @@ Separador fechado Separadores fechados + + Separadores fechados! + + Marcadores guardados! + + Ver Adicionado aos sites principais! @@ -1622,6 +1670,15 @@ Mostrar os sites mais visitados + + Nome + + Nome do site principal + + OK + + Cancelar + Remover From d49d710343e3612072571372cc85256724711bb4 Mon Sep 17 00:00:00 2001 From: ekager Date: Fri, 20 Nov 2020 19:50:44 -0800 Subject: [PATCH 034/202] For #16676 - Do not switch mode on last private tab via tab context menu --- .../fenix/components/toolbar/BrowserToolbarController.kt | 3 --- .../components/toolbar/DefaultBrowserToolbarControllerTest.kt | 3 --- 2 files changed, 6 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt index d62ab00c2..588a3a860 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt @@ -13,7 +13,6 @@ import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions import org.mozilla.fenix.browser.BrowserFragmentDirections -import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.readermode.ReaderModeController import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController @@ -108,8 +107,6 @@ class DefaultBrowserToolbarController( sessionManager.selectedSession?.let { // When closing the last tab we must show the undo snackbar in the home fragment if (sessionManager.sessionsOfType(it.private).count() == 1) { - // The tab tray always returns to normal mode so do that here too - activity.browsingModeManager.mode = BrowsingMode.Normal homeViewModel.sessionToDelete = it.id navController.navigate( BrowserFragmentDirections.actionGlobalHome() diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt index 3eca1edd4..a5f543f2b 100644 --- a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt @@ -208,7 +208,6 @@ class DefaultBrowserToolbarControllerTest { @Test fun handleToolbarCloseTabPressWithLastPrivateSession() { - val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Private) val item = TabCounterMenu.Item.CloseTab val sessions = listOf( mockk { @@ -218,7 +217,6 @@ class DefaultBrowserToolbarControllerTest { every { currentSession.private } returns true every { sessionManager.sessions } returns sessions - every { activity.browsingModeManager } returns browsingModeManager val controller = createController() controller.handleTabCounterItemInteraction(item) @@ -226,7 +224,6 @@ class DefaultBrowserToolbarControllerTest { homeViewModel.sessionToDelete = "1" navController.navigate(BrowserFragmentDirections.actionGlobalHome()) } - assertEquals(BrowsingMode.Normal, browsingModeManager.mode) } @Test From bb6a0ec0b461ce85aa22281cf13a1c4088bdc72c Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Thu, 26 Nov 2020 15:33:30 +0000 Subject: [PATCH 035/202] Update Android Components version to 68.0.20201125190141. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 27317161a..0fb52267a 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201124143116" + const val VERSION = "68.0.20201125190141" } From 34b4dad614086f1b319e71761efa1acaa18d3e75 Mon Sep 17 00:00:00 2001 From: Mihai Adrian Carare <48995920+mcarare@users.noreply.github.com> Date: Thu, 26 Nov 2020 20:09:49 +0200 Subject: [PATCH 036/202] For #16735 - Do not show grid view CFR if grid view is already selected. (#16740) --- app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt index 5512320b0..27161f2fc 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -270,6 +270,7 @@ class TabTrayView( if ( view.context.settings().shouldShowGridViewBanner && view.context.settings().canShowCfr && + view.context.settings().listTabView && tabs.size >= TAB_COUNT_SHOW_CFR ) { InfoBanner( From b2b558fe39d0af79acf7d03ea772402cf9727235 Mon Sep 17 00:00:00 2001 From: Mihai Adrian Carare <48995920+mcarare@users.noreply.github.com> Date: Thu, 26 Nov 2020 20:36:39 +0200 Subject: [PATCH 037/202] For #16745 - Allow preference layout to increase height if needed. (#16752) --- app/src/main/res/layout/checkbox_left_preference.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/checkbox_left_preference.xml b/app/src/main/res/layout/checkbox_left_preference.xml index 3fc661557..b3d33ad86 100644 --- a/app/src/main/res/layout/checkbox_left_preference.xml +++ b/app/src/main/res/layout/checkbox_left_preference.xml @@ -5,7 +5,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="48dp" + android:layout_height="wrap_content" + android:minHeight="48dp" android:layout_marginBottom="8dp" android:background="?android:selectableItemBackground" android:clickable="true" From 2e676a047d6e514c7da73a7b02780a754612cd69 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Fri, 27 Nov 2020 00:04:06 +0000 Subject: [PATCH 038/202] Import l10n. --- app/src/main/res/values-cs/strings.xml | 68 +++++++++++++++++++++---- app/src/main/res/values-hr/strings.xml | 4 +- app/src/main/res/values-kab/strings.xml | 4 ++ app/src/main/res/values-sat/strings.xml | 29 +++++++++++ app/src/main/res/values-tr/strings.xml | 6 +-- 5 files changed, 95 insertions(+), 16 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index c248ea96f..1ed02a0c8 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -21,6 +21,10 @@ Tady se zobrazí vaše anonymní panely. + + Baidu + + JD Jeden otevřený panel. Klepnutím panely přepnete. @@ -72,14 +76,6 @@ Ne, děkuji - - - Otevřete Firefox rychleji. Přidejte si widget na domovskou obrazovku. - - Přidat widget - - Teď ne - Firefox můžete nastavit tak, aby automaticky otevíral odkazy v této aplikaci. @@ -102,6 +98,13 @@ Ne, děkuji + + Zobrazení otevřených panelů můžete změnit v nastavení aplikace. + + Přejít do nastavení + + Zavřít + Nový panel @@ -447,6 +450,10 @@ Marketingová data Sdílí data o funkcích, které v aplikaci %1$s používáte, se společností Leanplum, naším partnerem pro mobilní marketing. + + Studie + + Povolí Mozille instalovat a spouštět studie. Experimenty @@ -572,6 +579,15 @@ Po měsíci + + Zavírat ručně + + Zavírat po jednom dni + + Zavírat po týdnu + + Zavírat po měsíci + Otevřené panely @@ -603,8 +619,18 @@ Domů Přepnout režim panelů + + Přidat do záložek + + Zavřít + + Sdílet vybrané panely + + Nabídka pro vybrané panely Odstranit panel ze sbírky + + Vybrat panely Zavřít panel @@ -647,8 +673,10 @@ Název sbírky - - Odebrat + + Přejmenovat + + Odebrat Vymazat z historie @@ -701,13 +729,16 @@ Zatím nemáte žádnou historii prohlížení - Žádná stahování Vybráno stahování: %1$d + + Otevřít + + Promiňte, aplikace %1$s nemůže tuto stránku načíst. @@ -972,6 +1003,12 @@ Panel byl zavřen Panely byly zavřeny + + Panely byly zavřeny + + Záložky uloženy + + Zobrazit Přidáno do top stránek @@ -1630,6 +1667,15 @@ Zobrazovat nejnavštěvovanější stránky + + Název + + Název top stránky + + OK + + Zrušit + Odstranit diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 4600da802..14132a2e7 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -445,7 +445,7 @@ Istraživanja - Omogućuje Mozilli instalirati i pokrenuti istraživanja + Omogućuje Mozilli instalaciju i pokretanje istraživanja Eksperimenti @@ -1615,7 +1615,7 @@ Dodaj u omiljene stranice - Potvrđeno od: %1$s + Potvrđuje: %1$s Izbriši diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index 5ec7c3b03..6c592a52e 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -448,6 +448,10 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Isefka n uzenzi Bḍu isefka ɣef timahilin i tseqdaceḍ deg %1$s s Leanplum, Imzenzi-nneɣ n izirazen. + + Studies + + Sireg Mozilla ad tesbedded sakin ad tseddu studies Tisermiyin diff --git a/app/src/main/res/values-sat/strings.xml b/app/src/main/res/values-sat/strings.xml index aa20d4264..e6f06c2b4 100644 --- a/app/src/main/res/values-sat/strings.xml +++ b/app/src/main/res/values-sat/strings.xml @@ -17,6 +17,10 @@ ᱟᱢᱟᱜ ᱠᱷᱩᱞᱟᱹ ᱟᱠᱟᱱ ᱴᱮᱵ ᱠᱚ ᱫᱚ ᱱᱚᱰᱮ ᱫᱮᱠᱷᱟᱣᱼᱜᱟ ᱾ ᱟᱢᱟᱜ ᱱᱤᱡᱚᱨᱟᱜ ᱴᱮᱵᱽ ᱠᱚ ᱫᱚ ᱱᱚᱰᱮ ᱩᱫᱩᱜ ᱦᱩᱭᱩᱜ-ᱟ ᱾ + + ᱵᱟᱭᱰᱩ + + JD 1 ᱴᱮᱵᱽ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮᱸ ᱾ ᱴᱮᱵᱽ ᱵᱚᱫᱚᱞ ᱞᱟᱹᱜᱤᱫ ᱴᱤᱯᱟᱹᱣ ᱢᱮᱸ ᱾ @@ -90,6 +94,13 @@ ᱵᱟᱹᱰ + + ᱠᱷᱩᱞᱟᱹ ᱴᱮᱵ ᱞᱮᱭᱟᱩᱴ ᱠᱚ ᱵᱚᱫᱚᱞ ᱢᱮᱸ ᱾ ᱥᱟᱡᱟᱣ ᱴᱷᱮᱱ ᱪᱟᱞᱟᱣ ᱠᱟᱛᱮᱫᱽ ᱴᱮᱵ ᱣᱤᱣ ᱞᱟᱛᱟᱨ ᱨᱮ ᱜᱨᱤᱰ ᱪᱚᱭᱚᱱ ᱢᱮᱸ ᱾ + + ᱥᱟᱡᱟᱣ ᱛᱮ ᱪᱟᱞᱟᱜᱽ ᱢᱮᱸ + + ᱵᱚᱸᱫᱽ + ᱱᱟᱶᱟ ᱴᱮᱵᱽ @@ -427,6 +438,10 @@ ᱢᱟᱨᱠᱮᱴᱤᱝ ᱰᱟᱴᱟ Leanplum ᱚᱠᱚᱭ ᱫᱚ ᱟᱞᱮᱭᱤᱡ ᱢᱚᱵᱟᱭᱤᱞ ᱢᱟᱨᱠᱮᱴᱤᱝ ᱣᱮᱱᱰᱚᱨ ᱠᱟᱱᱟᱭ ᱩᱱᱤ ᱥᱟᱞᱟᱜ %1$s ᱨᱮ ᱯᱷᱤᱪᱚᱨ ᱵᱟᱵᱚᱛ ᱵᱮᱵᱷᱟᱨᱚᱜ ᱠᱟᱱ ᱰᱟᱴᱟ ᱦᱟᱹᱴᱤᱧᱚᱜᱼᱟ ᱾ + + ᱯᱟᱲᱦᱟᱣ ᱠᱚ + + Mozilla ᱥᱚᱫᱽᱷ ᱞᱚ ᱟᱹᱛᱩᱨ ᱟᱨ ᱫᱟᱹᱲ ᱪᱷᱚ ᱞᱟᱹᱤᱫᱽ ᱢᱮᱛᱟᱭ ᱢᱮᱸ ᱵᱤᱰᱟᱣ @@ -581,6 +596,8 @@ ᱡᱷᱤᱜ ᱴᱮᱵ ᱠᱚ ᱛᱩᱢᱟᱹᱞ ᱨᱮ ᱥᱟᱸᱪᱟᱣ ᱢᱮᱸ + + ᱪᱚᱭᱚᱱ ᱡᱷᱚᱛᱚ ᱴᱮᱵ ᱠᱚ ᱦᱟᱹᱴᱧ ᱢᱮᱸ @@ -595,6 +612,14 @@ ᱚᱲᱟᱜ ᱪᱟᱞᱟᱜ ᱢᱮᱸ ᱴᱮᱵ ᱢᱳᱰ ᱛᱚᱞᱟᱜ ᱢᱮᱸ + + ᱵᱩᱻᱠᱢᱟᱨᱠ + + ᱵᱚᱸᱫᱽ + + ᱪᱚᱭᱚᱱ ᱟᱠᱟᱱ ᱴᱮᱵᱽ ᱠᱚ ᱦᱟᱹᱴᱤᱧ ᱢᱮᱸ + + ᱪᱚᱭᱚᱱ ᱟᱠᱟᱱ ᱴᱮᱵ ᱢᱮᱱᱭᱩ ᱠᱚ ᱴᱮᱵ ᱛᱩᱢᱟᱹᱞ ᱠᱷᱚᱱ ᱚᱪᱚᱜ ᱢᱮᱸ @@ -967,6 +992,10 @@ ᱴᱮᱵ ᱵᱚᱸᱫᱚᱭᱮᱱᱟ ᱴᱮᱵ ᱠᱚ ᱵᱚᱸᱫᱚᱭᱮᱱᱟ + + ᱵᱩᱻᱠᱢᱟᱨᱠ ᱠᱚ ᱥᱟᱺᱪᱟᱣ ᱮᱱᱟ! + + ᱧᱮᱞ ᱪᱮᱛᱟᱱ ᱥᱟᱭᱤᱴ ᱨᱮ ᱥᱮᱞᱮᱫᱽ ᱮᱱᱥ! diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 1a1bb8235..7f2b2526a 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -260,7 +260,7 @@ Kredi kartları ve adresler - Varsayılan tarayıcı olarak ayarla + Varsayılan tarayıcı yap Gelişmiş @@ -1225,9 +1225,9 @@ Açık tema - Sekmeler gönderildi! + Sekmeler gönderildi - Sekme gönderildi! + Sekme gönderildi Gönderilemedi From 894b25855e1a4cf9b219b4b50b07b4e5f166a1b2 Mon Sep 17 00:00:00 2001 From: Oana Horvath Date: Thu, 26 Nov 2020 11:10:21 +0200 Subject: [PATCH 039/202] For #16615: UI smoke test toggleSearchSuggestions --- .../helpers/RecyclerViewIdlingResource.kt | 2 +- .../mozilla/fenix/ui/SettingsBasicsTest.kt | 19 ------- .../java/org/mozilla/fenix/ui/SmokeTest.kt | 50 +++++++++++++++++++ .../fenix/ui/robots/NavigationToolbarRobot.kt | 20 +++----- 4 files changed, 59 insertions(+), 32 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/helpers/RecyclerViewIdlingResource.kt b/app/src/androidTest/java/org/mozilla/fenix/helpers/RecyclerViewIdlingResource.kt index 61de022cf..68b671b55 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/helpers/RecyclerViewIdlingResource.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/helpers/RecyclerViewIdlingResource.kt @@ -9,7 +9,7 @@ class RecyclerViewIdlingResource constructor(private val recycler: androidx.recy private var callback: ResourceCallback? = null override fun isIdleNow(): Boolean { - if (recycler.adapter != null && recycler.adapter!!.itemCount > minItemCount) { + if (recycler.adapter != null && recycler.adapter!!.itemCount >= minItemCount) { if (callback != null) { callback!!.onTransitionToIdle() } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt index ebafc68ac..b21e7044e 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt @@ -105,25 +105,6 @@ class SettingsBasicsTest { } } - @Ignore("This test works locally, fails on firebase. https://github.com/mozilla-mobile/fenix/issues/8174") - @Test - fun toggleSearchSuggestions() { - // Goes through the settings and changes the search suggestion toggle, then verifies it changes. - homeScreen { - }.openNavigationToolbar { - verifySearchSuggestionsAreMoreThan(1, "mozilla") - }.goBack { - }.openThreeDotMenu { - }.openSettings { - }.openSearchSubMenu { - disableShowSearchSuggestions() - }.goBack { - }.goBack { - }.openNavigationToolbar { - verifySearchSuggestionsAreEqualTo(0, "mozilla") - } - } - @Test fun toggleShowVisitedSitesAndBookmarks() { // Bookmarks a few websites, toggles the history and bookmarks setting to off, then verifies if the visited and bookmarked websites do not show in the suggestions. diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt index 15e2f414a..772e7765f 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt @@ -4,7 +4,10 @@ package org.mozilla.fenix.ui +import android.view.View import androidx.core.net.toUri +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.IdlingRegistry import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import okhttp3.mockwebserver.MockWebServer @@ -12,9 +15,12 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mozilla.fenix.R import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.HomeActivityTestRule +import org.mozilla.fenix.helpers.RecyclerViewIdlingResource import org.mozilla.fenix.helpers.TestAssetHelper +import org.mozilla.fenix.helpers.ViewVisibilityIdlingResource import org.mozilla.fenix.ui.robots.clickUrlbar import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.navigationToolbar @@ -27,6 +33,8 @@ import org.mozilla.fenix.ui.robots.navigationToolbar class SmokeTest { private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) private lateinit var mockWebServer: MockWebServer + private var awesomeBar: ViewVisibilityIdlingResource? = null + private var searchSuggestionsIdlingResource: RecyclerViewIdlingResource? = null @get:Rule val activityTestRule = HomeActivityTestRule() @@ -273,4 +281,46 @@ class SmokeTest { verifyEnginesListShortcutContains("YouTube") } } + + @Test + fun toggleSearchSuggestions() { + // Goes through the settings and changes the search suggestion toggle, then verifies it changes. + homeScreen { + }.openNavigationToolbar { + typeSearchTerm("mozilla") + val awesomeBarView = getAwesomebarView() + awesomeBarView?.let { + awesomeBar = ViewVisibilityIdlingResource(it, View.VISIBLE) + } + IdlingRegistry.getInstance().register(awesomeBar!!) + searchSuggestionsIdlingResource = + RecyclerViewIdlingResource(awesomeBarView as RecyclerView, 1) + IdlingRegistry.getInstance().register(searchSuggestionsIdlingResource!!) + verifySearchSuggestionsAreMoreThan(1) + IdlingRegistry.getInstance().unregister(searchSuggestionsIdlingResource!!) + }.goBack { + }.openThreeDotMenu { + }.openSettings { + }.openSearchSubMenu { + disableShowSearchSuggestions() + }.goBack { + }.goBack { + }.openNavigationToolbar { + typeSearchTerm("mozilla") + searchSuggestionsIdlingResource = + RecyclerViewIdlingResource(getAwesomebarView() as RecyclerView) + IdlingRegistry.getInstance().register(searchSuggestionsIdlingResource!!) + verifySearchSuggestionsAreEqualTo(0) + IdlingRegistry.getInstance().unregister(searchSuggestionsIdlingResource!!) + } + } + + // This finds the dialog fragment child of the homeFragment, otherwise the awesomeBar would return null + private fun getAwesomebarView(): View? { + val homeFragment = activityTestRule.activity.supportFragmentManager.primaryNavigationFragment + val searchDialogFragment = homeFragment?.childFragmentManager?.fragments?.first { + it.javaClass.simpleName == "SearchDialogFragment" + } + return searchDialogFragment?.view?.findViewById(R.id.awesome_bar) + } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/NavigationToolbarRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/NavigationToolbarRobot.kt index 184bf7eaf..250aa2546 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/NavigationToolbarRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/NavigationToolbarRobot.kt @@ -43,16 +43,18 @@ import org.mozilla.fenix.helpers.ext.waitNotNull */ class NavigationToolbarRobot { - fun verifySearchSuggestionsAreMoreThan(suggestionSize: Int, searchTerm: String) = - assertSuggestionsAreMoreThan(suggestionSize, searchTerm) + fun verifySearchSuggestionsAreMoreThan(suggestionSize: Int) = + assertSuggestionsAreMoreThan(suggestionSize) - fun verifySearchSuggestionsAreEqualTo(suggestionSize: Int, searchTerm: String) = - assertSuggestionsAreEqualTo(suggestionSize, searchTerm) + fun verifySearchSuggestionsAreEqualTo(suggestionSize: Int) = + assertSuggestionsAreEqualTo(suggestionSize) fun verifyNoHistoryBookmarks() = assertNoHistoryBookmarks() fun verifyTabButtonShortcutMenuItems() = assertTabButtonShortcutMenuItems() + fun typeSearchTerm(searchTerm: String) = awesomeBar().perform(typeText(searchTerm)) + class Transition { private lateinit var sessionLoadedIdlingResource: SessionLoadedIdlingResource @@ -246,18 +248,12 @@ fun clickUrlbar(interact: SearchRobot.() -> Unit): SearchRobot.Transition { return SearchRobot.Transition() } -private fun assertSuggestionsAreEqualTo(suggestionSize: Int, searchTerm: String) { - mDevice.waitForIdle() - awesomeBar().perform(typeText(searchTerm)) - +private fun assertSuggestionsAreEqualTo(suggestionSize: Int) { mDevice.waitForIdle() onView(withId(R.id.awesome_bar)).check(suggestionsAreEqualTo(suggestionSize)) } -private fun assertSuggestionsAreMoreThan(suggestionSize: Int, searchTerm: String) { - mDevice.waitForIdle() - awesomeBar().perform(typeText(searchTerm)) - +private fun assertSuggestionsAreMoreThan(suggestionSize: Int) { mDevice.waitForIdle() onView(withId(R.id.awesome_bar)).check(suggestionsAreGreaterThan(suggestionSize)) } From c8f8e2e665046839b378433832341464a3b843d2 Mon Sep 17 00:00:00 2001 From: Oana Horvath Date: Thu, 26 Nov 2020 11:37:41 +0200 Subject: [PATCH 040/202] Updates to UI tests using the modified recyclerViewIdlingResources minItemCount --- .../org/mozilla/fenix/ui/BookmarksTest.kt | 36 ++++++++++--------- .../java/org/mozilla/fenix/ui/HistoryTest.kt | 24 ++++++------- .../mozilla/fenix/ui/SettingsBasicsTest.kt | 1 - .../java/org/mozilla/fenix/ui/SmokeTest.kt | 2 +- 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt index b011e6181..8be03fb46 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt @@ -74,7 +74,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) selectFolder("Desktop Bookmarks") @@ -112,7 +112,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) verifyBookmarkedURL(defaultWebPage.url.toString()) @@ -126,7 +126,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) clickAddFolderButton() @@ -159,7 +159,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) }.openThreeDotMenu(defaultWebPage.url) { }.clickEdit { @@ -185,7 +185,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) }.openThreeDotMenu(defaultWebPage.url) { }.clickCopy { @@ -202,7 +202,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) }.openThreeDotMenu(defaultWebPage.url) { }.clickShare { @@ -222,7 +222,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) }.openThreeDotMenu(defaultWebPage.url) { }.clickOpenInNewTab { @@ -241,7 +241,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) }.openThreeDotMenu(defaultWebPage.url) { }.clickOpenInPrivateTab { @@ -260,7 +260,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) }.openThreeDotMenu(defaultWebPage.url) { IdlingRegistry.getInstance().unregister(bookmarksListIdlingResource!!) @@ -279,13 +279,17 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) }.openThreeDotMenu(defaultWebPage.url) { + IdlingRegistry.getInstance().unregister(bookmarksListIdlingResource!!) }.clickDelete { verifyUndoDeleteSnackBarButton() clickUndoDeleteButton() verifySnackBarHidden() + bookmarksListIdlingResource = + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) + IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) verifyBookmarkedURL(defaultWebPage.url.toString()) } } @@ -299,7 +303,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) longTapSelectItem(defaultWebPage.url) @@ -329,7 +333,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) longTapSelectItem(defaultWebPage.url) @@ -352,7 +356,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) longTapSelectItem(defaultWebPage.url) @@ -377,7 +381,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 3) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) longTapSelectItem(firstWebPage.url) @@ -404,7 +408,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) longTapSelectItem(defaultWebPage.url) @@ -460,7 +464,7 @@ class BookmarksTest { }.openThreeDotMenu { }.openBookmarks { bookmarksListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.bookmark_list), 2) IdlingRegistry.getInstance().register(bookmarksListIdlingResource!!) }.openThreeDotMenu(defaultWebPage.url) { IdlingRegistry.getInstance().unregister(bookmarksListIdlingResource!!) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt index ea6f88a16..8267e85be 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt @@ -84,7 +84,7 @@ class HistoryTest { }.openHistory { verifyHistoryListExists() historyListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list), 1) IdlingRegistry.getInstance().register(historyListIdlingResource!!) verifyHistoryMenuView() verifyVisitedTimeTitle() @@ -104,7 +104,7 @@ class HistoryTest { }.openHistory { verifyHistoryListExists() historyListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list), 1) IdlingRegistry.getInstance().register(historyListIdlingResource!!) }.openThreeDotMenu { }.clickCopy { @@ -123,7 +123,7 @@ class HistoryTest { }.openHistory { verifyHistoryListExists() historyListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list), 1) IdlingRegistry.getInstance().register(historyListIdlingResource!!) }.openThreeDotMenu { }.clickShare { @@ -145,7 +145,7 @@ class HistoryTest { }.openHistory { verifyHistoryListExists() historyListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list), 1) IdlingRegistry.getInstance().register(historyListIdlingResource!!) }.openThreeDotMenu { }.clickOpenInNormalTab { @@ -166,7 +166,7 @@ class HistoryTest { }.openHistory { verifyHistoryListExists() historyListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list), 1) IdlingRegistry.getInstance().register(historyListIdlingResource!!) }.openThreeDotMenu { }.clickOpenInPrivateTab { @@ -187,7 +187,7 @@ class HistoryTest { }.openHistory { verifyHistoryListExists() historyListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list), 1) IdlingRegistry.getInstance().register(historyListIdlingResource!!) }.openThreeDotMenu { IdlingRegistry.getInstance().unregister(historyListIdlingResource!!) @@ -208,7 +208,7 @@ class HistoryTest { }.openHistory { verifyHistoryListExists() historyListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list), 1) IdlingRegistry.getInstance().register(historyListIdlingResource!!) clickDeleteHistoryButton() IdlingRegistry.getInstance().unregister(historyListIdlingResource!!) @@ -230,7 +230,7 @@ class HistoryTest { }.openHistory { verifyHistoryListExists() historyListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list), 1) IdlingRegistry.getInstance().register(historyListIdlingResource!!) longTapSelectItem(firstWebPage.url) } @@ -260,7 +260,7 @@ class HistoryTest { }.openHistory { verifyHistoryListExists() historyListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list), 1) IdlingRegistry.getInstance().register(historyListIdlingResource!!) longTapSelectItem(firstWebPage.url) openActionBarOverflowOrOptionsMenu(activityTestRule.activity) @@ -284,7 +284,7 @@ class HistoryTest { }.openHistory { verifyHistoryListExists() historyListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list), 1) IdlingRegistry.getInstance().register(historyListIdlingResource!!) longTapSelectItem(firstWebPage.url) openActionBarOverflowOrOptionsMenu(activityTestRule.activity) @@ -311,7 +311,7 @@ class HistoryTest { }.openHistory { verifyHistoryListExists() historyListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list), 1) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list), 2) IdlingRegistry.getInstance().register(historyListIdlingResource!!) longTapSelectItem(firstWebPage.url) longTapSelectItem(secondWebPage.url) @@ -339,7 +339,7 @@ class HistoryTest { }.openHistory { verifyHistoryListExists() historyListIdlingResource = - RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list)) + RecyclerViewIdlingResource(activityTestRule.activity.findViewById(R.id.history_list), 1) IdlingRegistry.getInstance().register(historyListIdlingResource!!) longTapSelectItem(firstWebPage.url) } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt index b21e7044e..5345f38cb 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt @@ -10,7 +10,6 @@ import androidx.test.uiautomator.UiDevice import okhttp3.mockwebserver.MockWebServer import org.junit.After import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.mozilla.fenix.FenixApplication diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt index 772e7765f..97ca48e72 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt @@ -296,7 +296,7 @@ class SmokeTest { searchSuggestionsIdlingResource = RecyclerViewIdlingResource(awesomeBarView as RecyclerView, 1) IdlingRegistry.getInstance().register(searchSuggestionsIdlingResource!!) - verifySearchSuggestionsAreMoreThan(1) + verifySearchSuggestionsAreMoreThan(0) IdlingRegistry.getInstance().unregister(searchSuggestionsIdlingResource!!) }.goBack { }.openThreeDotMenu { From 3f99c92f3496b96a54cc8661f270020ba3888024 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Fri, 27 Nov 2020 15:35:29 +0000 Subject: [PATCH 041/202] Update Android Components version to 68.0.20201127143054. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 0fb52267a..1dbcb05ac 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201125190141" + const val VERSION = "68.0.20201127143054" } From 806ed78b7f622999ec4fd79b2acab9f36cd0db0b Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Sat, 28 Nov 2020 00:07:28 +0000 Subject: [PATCH 042/202] Import l10n. --- app/src/main/res/values-sk/strings.xml | 34 +++++++++++++++++++ app/src/main/res/values-tl/strings.xml | 47 ++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 1b5a044bc..d3abfae00 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -20,6 +20,10 @@ Vaše súkromné karty budú zobrazené tu. + + Baidu + + JD 1 otvorená karta. Ťuknutím prepnete karty. @@ -94,6 +98,11 @@ Zavrieť + + Prejsť do nastavení + + Zavrieť + Nová karta @@ -437,6 +446,10 @@ Marketingové údaje Zdieľa údaje o používaní funkcií v aplikácii %1$s so spoločnosťou Leanplum, našim partnerom v oblasti mobilného marketingu. + + Štúdie + + Umožní Mozille inštalovať a spúšťať štúdie Experimenty @@ -590,6 +603,12 @@ Domov Prepnúť režim kariet + + Pridať medzi záložky + + Zavrieť + + Zdieľať vybrané karty Odstrániť kartu z kolekcie @@ -967,6 +986,12 @@ Karta bola zavretá Karty boli zavreté + + Karty boli zavreté! + + Záložky boli uložené! + + Zobraziť Pridané do top stránok! @@ -1114,6 +1139,10 @@ S účtom Firefox môžete synchronizovať záložky, históriu prehliadania a omnoho viac. Ďalšie informácie + + V inom Firefoxe na tomto telefóne ste prihlásení ako %s. Chceli by ste sa prihlásiť s týmto účtom? Prihlásiť sa @@ -1205,6 +1234,8 @@ Prihláste sa pomocou fotoaparátu Použiť radšej e-mailovú adresu + + Vytvorte si ho a synchronizujte svoj Firefox medzi zariadeniami.]]> Firefox ukončí synchronizáciu s týmto účtom, ale neodstráni žiadnu históriu prehliadania. @@ -1357,6 +1388,9 @@ Názov skratky + + Túto webovú stránku si môžete jednoducho pridať na svoju domovskú obrazovku a mať tak okamžitý prístup k prehliadaniu. + Prihlasovacie údaje diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index 6f18d734a..dde094de2 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -293,9 +293,56 @@ Kopyahin + + Ngayon + + Kahapon + + Mas luma + + + Buksan + + Burahin + + + + i-Edit + + Pumili Kopyahin + + Burahin + + i-Save + + URL + + FOLDER + + PANGALAN + + + Camera + + Mikropono + + Lokasyon + + On + + Off + + + + Mga Koleksyon + + Piliin lahat + + Isara + Kopyahin ang URL From 038fb4126966ae0b857f62db75281f63d2c07689 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Sat, 28 Nov 2020 15:34:40 +0000 Subject: [PATCH 043/202] Update Android Components version to 68.0.20201128143055. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 1dbcb05ac..3b1af5d40 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201127143054" + const val VERSION = "68.0.20201128143055" } From 7e56b0cd53d7085231f500117b8531f9115828eb Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Sun, 29 Nov 2020 00:07:14 +0000 Subject: [PATCH 044/202] Import l10n. --- app/src/main/res/values-be/strings.xml | 5 ++++ app/src/main/res/values-en-rGB/strings.xml | 33 ++++++++++++++++++++++ app/src/main/res/values-ru/strings.xml | 4 +++ 3 files changed, 42 insertions(+) diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index dd3a98295..c46048b85 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -91,9 +91,14 @@ Адхіліць + + Змяніце выклад адкрытых картак. Перайдзіце ў налады і абярыце сетку ў раздзеле выгляду картак. Перайсці ў налады + + Закрыць + Новая картка diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index db8070029..85b7d4018 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -19,6 +19,10 @@ Your private tabs will be shown here. + + Baidu + + JD 1 open tab. Tap to switch tabs. @@ -92,6 +96,13 @@ Dismiss + + Change the layout of open tabs. Go to settings and select grid under tab view. + + Go to settings + + Dismiss + New tab @@ -429,6 +440,10 @@ Marketing data Shares data about what features you use in %1$s with Leanplum, our mobile marketing vendor. + + Studies + + Allows Mozilla to install and run studies Experiments @@ -578,6 +593,8 @@ Open Tabs Save to collection + + Select Share all tabs @@ -592,8 +609,18 @@ Go home Toggle tab mode + + Bookmark + + Close + + Share selected tabs + + Selected tabs menu Remove tab from collection + + Select tabs Close tab @@ -964,6 +991,12 @@ Tab closed Tabs closed + + Tabs closed! + + Bookmarks saved! + + View Added to top sites! diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 8be8d71a2..02d5172ea 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -455,6 +455,10 @@ Маркетинговые данные Делиться данными о том, что вы используете в %1$s, с Leanplum, нашим мобильным маркетинговым вендором. + + Исследования + + Разрешить Mozilla устанавливать и запускать исследования Исследования From 8a738320a60c3d8427814cdf999433fbfcc1e746 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Sun, 29 Nov 2020 15:34:33 +0000 Subject: [PATCH 045/202] Update Android Components version to 68.0.20201128190115. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 3b1af5d40..c5223b563 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201128143055" + const val VERSION = "68.0.20201128190115" } From e8fa1a66f030b0c373a48562dd283c8a082b24ff Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Mon, 30 Nov 2020 00:04:31 +0000 Subject: [PATCH 046/202] Import l10n. --- app/src/main/res/values-ar/strings.xml | 20 +++++++++++++++++ app/src/main/res/values-ast/strings.xml | 29 +++++++++++++++++++++++++ app/src/main/res/values-be/strings.xml | 4 ++++ app/src/main/res/values-sr/strings.xml | 4 ++++ app/src/main/res/values-th/strings.xml | 2 ++ 5 files changed, 59 insertions(+) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 5f2332706..3ecd2d0e1 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -19,6 +19,10 @@ ستظهر الألسنة الخاصة هنا. + + بايدو + + JD لسان واحد مفتوح. انقر لتبديل الألسنة. @@ -94,9 +98,14 @@ أهمِل + + غيّر تخطيط الألسنة المفتوحة. انتقل إلى الإعدادات واختر ”شبكة“ من ”منظور الألسنة“. انتقل إلى الإعدادات + + تجاهَل + لسان جديد @@ -436,6 +445,10 @@ بيانات التسويق يُشارك بيانات الميزات التي تستعملها في %1$s مع Leanplum، شركة التسويق للمحمول التي نتعامل معها. + + الدراسات + + تسمح لمؤسسة Mozilla بتثبيت الدراسات وتشغيلها التجارب @@ -564,6 +577,13 @@ أغلِق يدويًا + + أغلِق بعد يوم واحد + + أغلِق بعد أسبوع واحد + + أغلِق بعد شهر واحد + الألسنة المفتوحة diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml index 82fa2b1f0..580c7c95c 100644 --- a/app/src/main/res/values-ast/strings.xml +++ b/app/src/main/res/values-ast/strings.xml @@ -417,6 +417,10 @@ Datos de marketing Comparte los datos tocante a les carauterístiques qu\'uses en %1$s con Leanplum, el nuesu fornidor de marketing móvil. + + Estudios + + Permite a Mozilla qu\'instale o execute estudios Esperimentos @@ -461,6 +465,10 @@ Siguir l\'estilu del preséu + + + Arrastrar p\'abaxo p\'anovar + Sesiones @@ -507,6 +515,8 @@ Llingüetes + + Llista de les llinguüetes En llista @@ -522,6 +532,15 @@ Dempués d\'un mes + + Zarrales a mano + + Zarrales dempués d\'un día + + Zarrales dempués d\'una selmana + + Zarrales dempués d\'un mes + Llingüetes abiertes @@ -546,6 +565,16 @@ Zarrar toles llingüetes Llingüeta nueva + + Amestar a Marcadores + + Zarrar + + Compartir les llingüetes abiertes + + Menú de llingüetes abiertes + + Esbillar llingüetes Zarrar la llingüeta diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index c46048b85..5fedcb8c4 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -20,6 +20,10 @@ Тут будуць паказаны вашы прыватныя карткі. + + Baidu + + JD 1 адкрытая картка. Націсніце, каб пераключыць карткі. diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index e8f46dd5c..391d52f44 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -441,6 +441,10 @@ Маркетиншки подаци Дели податке о функцијама које користите у апликацији %1$s са Leanplum-ом, нашим провајдером мобилног маркетинга. + + Студије + + Омогућава Mozilla-и да инсталира и покреће студије Експерименти diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 479b0d6e3..c1d8082f8 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -95,6 +95,8 @@ ยกเลิก + + เปลี่ยนเค้าโครงของแท็บที่เปิดอยู่ ไปยังการตั้งค่าแล้วเลือกเส้นตารางภายใต้มุมมองแท็บ ไปยังการตั้งค่า From d84b8ebdc626a8788b8fcb968c1630c17a8dcf98 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Mon, 30 Nov 2020 11:38:42 +0100 Subject: [PATCH 047/202] Issue #12731: Log and record breadcrumb when onTrimMemory() gets invoked. --- .../java/org/mozilla/fenix/FenixApplication.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 67299d145..34296a696 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.launch import mozilla.appservices.Megazord import mozilla.components.browser.session.Session import mozilla.components.browser.state.action.SystemAction +import mozilla.components.concept.base.crash.Breadcrumb import mozilla.components.concept.push.PushProcessor import mozilla.components.feature.addons.update.GlobalAddonDependencyProvider import mozilla.components.lib.crash.CrashReporter @@ -296,6 +297,21 @@ open class FenixApplication : LocaleAwareApplication(), Provider { override fun onTrimMemory(level: Int) { super.onTrimMemory(level) + // Additional logging and breadcrumb to debug memory issues: + // https://github.com/mozilla-mobile/fenix/issues/12731 + + logger.info("onTrimMemory(), level=$level, main=${isMainProcess()}") + + components.analytics.crashReporter.recordCrashBreadcrumb(Breadcrumb( + category = "Memory", + message = "onTrimMemory()", + data = mapOf( + "level" to level.toString(), + "main" to isMainProcess().toString() + ), + level = Breadcrumb.Level.INFO + )) + runOnlyInMainProcess { components.core.icons.onTrimMemory(level) components.core.store.dispatch(SystemAction.LowMemoryAction(level)) From 1ab3465fedeaf96faed36a83beeafdb31f407983 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Mon, 30 Nov 2020 15:33:03 +0000 Subject: [PATCH 048/202] Update Android Components version to 68.0.20201130143056. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index c5223b563..fb5849a4a 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201128190115" + const val VERSION = "68.0.20201130143056" } From 0a419efa92e126de524f1ead06f750b877122aea Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Mon, 30 Nov 2020 14:54:46 +0100 Subject: [PATCH 049/202] Introduce build flag for disabling optmization. --- app/build.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3fcbe08fd..ec767e1c4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,8 +46,10 @@ android { } def releaseTemplate = { - shrinkResources true - minifyEnabled true + // We allow disabling optimization by passing `-PdisableOptimization` to gradle. This is used + // in automation for UI testing non-debug builds. + shrinkResources !project.hasProperty("disableOptimization") + minifyEnabled !project.hasProperty("disableOptimization") proguardFiles 'proguard-android-optimize-3.5.0-modified.txt', 'proguard-rules.pro' matchingFallbacks = ['release'] // Use on the "release" build type in dependencies (AARs) From 23edda5f659c9e22bf657ee2b182e48ec6b3e067 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Mon, 30 Nov 2020 15:09:15 +0100 Subject: [PATCH 050/202] Introduce build flag for configuring test build type. --- app/build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index ec767e1c4..99d71bc28 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,6 +19,13 @@ import static org.gradle.api.tasks.testing.TestResult.ResultType android { compileSdkVersion Config.compileSdkVersion + + if (project.hasProperty("testBuildType")) { + // Allowing to configure the test build type via command line flag (./gradlew -PtestBuildType=beta ..) + // in order to run UI tests against other build variants than debug in automation. + testBuildType project.property("testBuildType") + } + defaultConfig { applicationId "org.mozilla" minSdkVersion Config.minSdkVersion From 055c76b7ba96873107306a25e07e3ec42eb74801 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Mon, 30 Nov 2020 11:32:40 +0100 Subject: [PATCH 051/202] FenixApplication: Remove unused kinto endpoint. --- app/src/main/java/org/mozilla/fenix/FenixApplication.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt index 34296a696..8016e5296 100644 --- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt +++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt @@ -438,9 +438,5 @@ open class FenixApplication : LocaleAwareApplication(), Provider { } } - companion object { - private const val KINTO_ENDPOINT_PROD = "https://firefox.settings.services.mozilla.com/v1" - } - override fun getWorkManagerConfiguration() = Builder().setMinimumLoggingLevel(INFO).build() } From 0bd3654bc70d121f3c5d513fc9afd6027cd3b54b Mon Sep 17 00:00:00 2001 From: mcarare Date: Fri, 27 Nov 2020 18:43:57 +0200 Subject: [PATCH 052/202] For #16759: Prevent crash on showing info banner. --- .../org/mozilla/fenix/tabtray/TabTrayView.kt | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt index 27161f2fc..cb1f411cd 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -53,6 +53,7 @@ import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.updateAccessibilityCollectionInfo import org.mozilla.fenix.tabtray.SaveToCollectionsButtonAdapter.MultiselectModeChange import org.mozilla.fenix.tabtray.TabTrayDialogFragmentState.Mode +import org.mozilla.fenix.utils.Settings import java.text.NumberFormat import kotlin.math.max import kotlin.math.roundToInt @@ -266,11 +267,15 @@ class TabTrayView( adjustNewTabButtonsForNormalMode() + displayInfoBannerIfNeccessary(tabs, view.context.settings()) + } + + private fun displayInfoBannerIfNeccessary(tabs: List, settings: Settings) { @Suppress("ComplexCondition") - if ( - view.context.settings().shouldShowGridViewBanner && - view.context.settings().canShowCfr && - view.context.settings().listTabView && + val infoBanner = if ( + settings.shouldShowGridViewBanner && + settings.canShowCfr && + settings.listTabView && tabs.size >= TAB_COUNT_SHOW_CFR ) { InfoBanner( @@ -280,17 +285,14 @@ class TabTrayView( actionText = view.context.getString(R.string.tab_tray_grid_view_banner_positive_button_text), container = view.infoBanner, dismissByHiding = true, - dismissAction = { view.context.settings().shouldShowGridViewBanner = false } + dismissAction = { settings.shouldShowGridViewBanner = false } ) { interactor.onGoToTabsSettings() - view.context.settings().shouldShowGridViewBanner = false - }.apply { - view.infoBanner.visibility = View.VISIBLE - showBanner() + settings.shouldShowGridViewBanner = false } } else if ( - view.context.settings().shouldShowAutoCloseTabsBanner && - view.context.settings().canShowCfr && + settings.shouldShowAutoCloseTabsBanner && + settings.canShowCfr && tabs.size >= TAB_COUNT_SHOW_CFR ) { InfoBanner( @@ -300,14 +302,18 @@ class TabTrayView( actionText = view.context.getString(R.string.tab_tray_close_tabs_banner_positive_button_text), container = view.infoBanner, dismissByHiding = true, - dismissAction = { view.context.settings().shouldShowAutoCloseTabsBanner = false } + dismissAction = { settings.shouldShowAutoCloseTabsBanner = false } ) { interactor.onGoToTabsSettings() - view.context.settings().shouldShowAutoCloseTabsBanner = false - }.apply { - view.infoBanner.visibility = View.VISIBLE - showBanner() + settings.shouldShowAutoCloseTabsBanner = false } + } else { + null + } + + infoBanner?.apply { + view.infoBanner.visibility = View.VISIBLE + showBanner() } } From 776b64f95984152cc2e185dd3fae31fb1172fef1 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Mon, 30 Nov 2020 17:08:00 +0100 Subject: [PATCH 053/202] Issue #16435: Finish ExternalAppBrowserActivity and remove task if linked tab doesn't exist anymore. This doesn't fix the underlying issue of #16435, but a symptom we have seen quite often in different situations: When the ExternalAppBrowserActivity is not linked to a tab anymore then it falls back to displaying a (partially broken) browser UI. With multiple browser activities at the same time sooner or later we crash with a "display already acquired" error because both activities try to render the same tab. --- .../customtabs/ExternalAppBrowserActivity.kt | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt index 5168c31e4..4d04e7e20 100644 --- a/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt @@ -9,6 +9,8 @@ import androidx.navigation.NavDestination import androidx.navigation.NavDirections import kotlinx.android.synthetic.main.activity_home.* import mozilla.components.browser.session.runWithSession +import mozilla.components.browser.state.selector.findCustomTab +import mozilla.components.browser.state.state.SessionState import mozilla.components.concept.engine.manifest.WebAppManifestParser import mozilla.components.feature.intent.ext.getSessionId import mozilla.components.feature.pwa.ext.getWebAppManifest @@ -24,7 +26,20 @@ import java.security.InvalidParameterException * Activity that holds the [ExternalAppBrowserFragment] that is launched within an external app, * such as custom tabs and progressive web apps. */ +@Suppress("TooManyFunctions") open class ExternalAppBrowserActivity : HomeActivity() { + override fun onResume() { + super.onResume() + + if (!hasExternalTab()) { + // An ExternalAppBrowserActivity is always bound to a specific tab. If this tab doesn't + // exist anymore on resume then this activity has nothing to display anymore. Let's just + // finish it AND remove this task to avoid it hanging around in the recent apps screen. + // Without this the parent HomeActivity class may decide to show the browser UI and we + // end up with multiple browsers (causing "display already acquired" crashes). + finishAndRemoveTask() + } + } final override fun getBreadcrumbMessage(destination: NavDestination): String { val fragmentName = resources.getResourceEntryName(destination.id) @@ -86,8 +101,7 @@ open class ExternalAppBrowserActivity : HomeActivity() { // When this activity finishes, the process is staying around and the session still // exists then remove it now to free all its resources. Once this activity is finished // then there's no way to get back to it other than relaunching it. - val sessionId = getIntentSessionId(SafeIntent(intent)) - components.core.sessionManager.runWithSession(sessionId) { session -> + components.core.sessionManager.runWithSession(getExternalTabId()) { session -> // If the custom tag config has been removed we are opening this in normal browsing if (session.customTabConfig != null) { remove(session) @@ -96,4 +110,17 @@ open class ExternalAppBrowserActivity : HomeActivity() { } } } + + private fun hasExternalTab(): Boolean { + return getExternalTab() != null + } + + private fun getExternalTab(): SessionState? { + val id = getExternalTabId() ?: return null + return components.core.store.state.findCustomTab(id) + } + + private fun getExternalTabId(): String? { + return getIntentSessionId(SafeIntent(intent)) + } } From 788a8309c2c20320b8996f7372478da76232e1e9 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Mon, 30 Nov 2020 17:23:30 +0100 Subject: [PATCH 054/202] Issue #16435: Open custom tab in Fenix: Force launch outside of and remove current task. --- .../toolbar/BrowserToolbarMenuController.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt index 90f91763f..fae8e867a 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt @@ -236,10 +236,16 @@ class DefaultBrowserToolbarMenuController( sessionManager.select(customTabSession) // Switch to the actual browser which should now display our new selected session - activity.startActivity(openInFenixIntent) + activity.startActivity(openInFenixIntent.apply { + // We never want to launch the browser in the same task as the external app + // activity. So we force a new task here. IntentReceiverActivity will do the + // right thing and take care of routing to an already existing browser and avoid + // cloning a new one. + flags = flags or Intent.FLAG_ACTIVITY_NEW_TASK + }) - // Close this activity since it is no longer displaying any session - activity.finish() + // Close this activity (and the task) since it is no longer displaying any session + activity.finishAndRemoveTask() } ToolbarMenu.Item.Quit -> { // We need to show the snackbar while the browsing data is deleting (if "Delete From 57ebc0cb999180a2b2e100f1867f40470198be33 Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Mon, 30 Nov 2020 18:51:27 +0100 Subject: [PATCH 055/202] ExternalAppBrowserActivity: Add additional test cases. --- .../customtabs/ExternalAppBrowserActivity.kt | 10 ++- ...DefaultBrowserToolbarMenuControllerTest.kt | 2 +- .../ExternalAppBrowserActivityTest.kt | 74 +++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt index 4d04e7e20..864a3692c 100644 --- a/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.customtabs import android.content.Intent +import androidx.annotation.VisibleForTesting import androidx.navigation.NavDestination import androidx.navigation.NavDirections import kotlinx.android.synthetic.main.activity_home.* @@ -111,16 +112,19 @@ open class ExternalAppBrowserActivity : HomeActivity() { } } - private fun hasExternalTab(): Boolean { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun hasExternalTab(): Boolean { return getExternalTab() != null } - private fun getExternalTab(): SessionState? { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun getExternalTab(): SessionState? { val id = getExternalTabId() ?: return null return components.core.store.state.findCustomTab(id) } - private fun getExternalTabId(): String? { + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun getExternalTabId(): String? { return getIntentSessionId(SafeIntent(intent)) } } diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarMenuControllerTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarMenuControllerTest.kt index a5c2d6c31..4082077ae 100644 --- a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarMenuControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarMenuControllerTest.kt @@ -513,7 +513,7 @@ class DefaultBrowserToolbarMenuControllerTest { verify { currentSession.customTabConfig = null } verify { sessionManager.select(currentSession) } verify { activity.startActivity(openInFenixIntent) } - verify { activity.finish() } + verify { activity.finishAndRemoveTask() } } @Test diff --git a/app/src/test/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivityTest.kt b/app/src/test/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivityTest.kt index 19d562c9e..280f40e80 100644 --- a/app/src/test/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivityTest.kt +++ b/app/src/test/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivityTest.kt @@ -12,18 +12,28 @@ import io.mockk.every import io.mockk.mockk import io.mockk.spyk import io.mockk.verify +import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.createCustomTab +import mozilla.components.browser.state.state.createTab +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.feature.intent.ext.putSessionId import mozilla.components.support.utils.toSafeIntent import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Test +import org.junit.runner.RunWith import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.utils.Settings +@RunWith(FenixRobolectricTestRunner::class) class ExternalAppBrowserActivityTest { @Test @@ -95,4 +105,68 @@ class ExternalAppBrowserActivityTest { assertNull(directions) verify { activity.finishAndRemoveTask() } } + + @Test + fun `ExternalAppBrowserActivity with matching external tab`() { + val store = BrowserStore(BrowserState( + customTabs = listOf( + createCustomTab( + url = "https://www.mozilla.org", + id = "mozilla" + ) + ) + )) + + val intent = Intent(Intent.ACTION_VIEW).apply { putSessionId("mozilla") } + + val activity = spyk(ExternalAppBrowserActivity()) + every { activity.components.core.store } returns store + every { activity.intent } returns intent + + assertTrue(activity.hasExternalTab()) + + assertEquals("mozilla", activity.getExternalTabId()) + + val tab = activity.getExternalTab() + assertNotNull(tab!!) + assertEquals("https://www.mozilla.org", tab.content.url) + } + + @Test + fun `ExternalAppBrowserActivity without matching external tab`() { + val store = BrowserStore() + + val intent = Intent(Intent.ACTION_VIEW).apply { putSessionId("mozilla") } + + val activity = spyk(ExternalAppBrowserActivity()) + every { activity.components.core.store } returns store + every { activity.intent } returns intent + + assertFalse(activity.hasExternalTab()) + assertEquals("mozilla", activity.getExternalTabId()) + assertNull(activity.getExternalTab()) + } + + @Test + fun `ExternalAppBrowserActivity with matching regular tab`() { + val store = BrowserStore(BrowserState( + tabs = listOf( + createTab( + url = "https://www.mozilla.org", + id = "mozilla" + ) + ) + )) + + val intent = Intent(Intent.ACTION_VIEW).apply { putSessionId("mozilla") } + + val activity = spyk(ExternalAppBrowserActivity()) + every { activity.components.core.store } returns store + every { activity.intent } returns intent + + // Even though we have a matching regular tab we do not care about it in ExternalAppBrowserActivity + assertFalse(activity.hasExternalTab()) + assertEquals("mozilla", activity.getExternalTabId()) + assertNull(activity.getExternalTab()) + } } From e587f814c753fa364335d1d2cd7c7336d30c1631 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Tue, 1 Dec 2020 00:06:08 +0000 Subject: [PATCH 056/202] Import l10n. --- app/src/main/res/values-br/strings.xml | 6 ++-- app/src/main/res/values-co/strings.xml | 8 +++--- app/src/main/res/values-in/strings.xml | 33 ++++++++++++++++++++++ app/src/main/res/values-nn-rNO/strings.xml | 4 +++ app/src/main/res/values-rm/strings.xml | 27 ++++++++++++++++++ 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml index 325af5759..92acaf87b 100644 --- a/app/src/main/res/values-br/strings.xml +++ b/app/src/main/res/values-br/strings.xml @@ -435,6 +435,8 @@ Roadennoù Marketing Rann ar roadennoù diwar-benn ar cʼheweriusterioù arveret ganeocʼh e %1$s gant Leanplim, hor cʼheveler marketing hezoug. + + Studioù Arnodoù @@ -746,7 +748,7 @@ Dibab un teuliad - Ha fellout a ra deocʼh dilemel an teuliad-mañ ? + Ha fellout a ra deocʼh dilemel an teuliad-mañ? %s a zilamo an elfennoù diuzet @@ -1577,7 +1579,7 @@ Pennadoù pennañ - Ha fellout a ra deocʼh dilemel ar sined-mañ ? + Ha fellout a ra deocʼh dilemel ar sined-mañ? Lakaat el lecʼhiennoù gwellañ - Accessu richiestu à l’appareghju-fotò. Accidite à e preferenze d’Android, picchichjate Permessi, è dopu Permette. + Accessu richiestu à l’apparechju-fotò. Accidite à e preferenze d’Android, picchichjate Permessi, è dopu Permette. Apre e preferenze @@ -480,7 +480,7 @@ firefox.com/pair]]> - Apre l’appareghju-fotò + Apre l’apparechju-fotò Abbandunà @@ -857,7 +857,7 @@ Leghje autumaticamente elementi multimedia - Appareghju-fotò + Apparechju-fotò Microfonu @@ -1254,7 +1254,7 @@ Prontu à numerizà - Cunnettatevi cù u vostru appareghju-fotò + Cunnettatevi cù u vostru apparechju-fotò Impiegà piuttostu un messaghju elettronicu diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 3bd10dfcc..fab2f316f 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -21,6 +21,10 @@ Tab privat Anda akan muncul di sini. + + Baidu + + JD 1 tab terbuka. Ketuk untuk beralih tab. @@ -95,6 +99,13 @@ Tutup + + Ubah tata letak tab terbuka. Buka pengaturan dan pilih kisi di bawah tampilan tab. + + Buka pengaturan + + Tutup + Tab baru @@ -442,6 +453,10 @@ Data pemasaran Bagikan data mengenai fitur yang Anda gunakan pada %1$s dengan Leanplum, vendor pemasaran seluler kami. + + Kajian + + Izinkan Mozilla untuk memasang dan menjalankan kajian Eksperimen @@ -593,6 +608,8 @@ Tab Terbuka Simpan ke koleksi + + Pilih Bagikan semua tab @@ -607,8 +624,18 @@ Buka beranda Alihkan mode tab + + Markah + + Tutup + + Bagikan tab terpilih + + Menu tab terpilih Hapus tab dari koleksi + + Pilih tab Tutup tab @@ -986,6 +1013,12 @@ Tab ditutup Tab ditutup + + Tab ditutup! + + Markah tersimpan! + + Tampilkan Ditambahkan ke situs teratas! diff --git a/app/src/main/res/values-nn-rNO/strings.xml b/app/src/main/res/values-nn-rNO/strings.xml index ab0273152..0eb9ff471 100644 --- a/app/src/main/res/values-nn-rNO/strings.xml +++ b/app/src/main/res/values-nn-rNO/strings.xml @@ -617,6 +617,8 @@ Veksle fanemodus Bokmerke + + Lat att Fjern fane frå samlinga @@ -997,6 +999,8 @@ Fane attlaten Faner attlatne + + Vis Lagt til i føretrekte netttadar! diff --git a/app/src/main/res/values-rm/strings.xml b/app/src/main/res/values-rm/strings.xml index 209fa1972..cc79a52be 100644 --- a/app/src/main/res/values-rm/strings.xml +++ b/app/src/main/res/values-rm/strings.xml @@ -97,6 +97,11 @@ Mida il layout da tabs averts. Acceda als parameters e tscherna griglia sut vista da tabs. + + Ir als parameters + + Ignorar + Nov tab @@ -434,6 +439,10 @@ Datas da marketing Transmetta datas davart las funcziunalitads che ti dovras en %1$s a Leanplum, noss partenari per il marketing sin apparats mobils. + + Studis + + Permetter a Mozilla dad installar ed exequir studis Experiments @@ -582,6 +591,8 @@ Tabs averts Memorisar en ina collecziun + + Tscherner Cundivider tut ils tabs @@ -596,8 +607,18 @@ Ir a la pagina da partenza Midar il modus da tab + + Segnapagina + + Serrar + + Cundivider ils tabs tschernids + + Menu da tabs tschernids Allontanar il tab da la collecziun + + Tscherner tabs Serrar il tab @@ -969,6 +990,12 @@ Serrà il tab Serrà ils tabs + + Serrà ils tabs! + + Memorisà ils segnapaginas! + + Vista Agiuntà a las paginas principalas! From a2b6ddf4570c2861889fea43554b9f00e73ea50c Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Wed, 2 Dec 2020 00:04:11 +0000 Subject: [PATCH 057/202] Import l10n. --- app/src/main/res/values-br/strings.xml | 2 + app/src/main/res/values-ka/strings.xml | 4 + app/src/main/res/values-pa-rIN/strings.xml | 95 +++++++++++++++++++--- 3 files changed, 91 insertions(+), 10 deletions(-) diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml index 92acaf87b..e0dbfef8b 100644 --- a/app/src/main/res/values-br/strings.xml +++ b/app/src/main/res/values-br/strings.xml @@ -437,6 +437,8 @@ Rann ar roadennoù diwar-benn ar cʼheweriusterioù arveret ganeocʼh e %1$s gant Leanplim, hor cʼheveler marketing hezoug. Studioù + + Aotren Mozilla da staliañ ha da luskañ studioù Arnodoù diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index 219e87464..e862eb7ee 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -441,6 +441,10 @@ მარკეტინგული მონაცემები გაუზიარებს მონაცემებს, თუ რომელი შესაძლებლობებით მოგწონთ %1$s ყველაზე მეტად, Leanplum-ს, მობილურის მარკეტინგის მომსახურების ჩვენს მომწოდებელს. + + კვლევები + + ნებას დართავს Mozilla-ს, ჩადგას და გაუშვას კვლევები კვლევები diff --git a/app/src/main/res/values-pa-rIN/strings.xml b/app/src/main/res/values-pa-rIN/strings.xml index d5f5160a5..7d725afba 100644 --- a/app/src/main/res/values-pa-rIN/strings.xml +++ b/app/src/main/res/values-pa-rIN/strings.xml @@ -20,6 +20,10 @@ ਤੁਹਾਡੀਆਂ ਪ੍ਰਾਈਵੇਟ ਟੈਬਾਂ ਨੂੰ ਇੱਥੇ ਵਿਖਾਇਆ ਜਾਵੇਗਾ। + + ਬਾਈਡੂ + + ਜੇਡੀ 1 ਟੈਬ ਖੁੱਲ੍ਹੀ। ਟੈਬਾਂ ਲਈ ਸਵਿੱਚ ਕਰਨ ਵਾਸਤੇ ਟੈਪ ਕਰੋ। @@ -74,14 +78,6 @@ ਨਹੀਂ, ਧੰਨਵਾਦ - - - ਫਾਇਰਫਾਕਸ ਹੋਰ ਛੇਤੀ ਲਵੋ। ਆਪਣੀ ਮੁੱਖ ਸਕਰੀਨ ਉੱਤੇ ਵਿਜੈਟ ਜੋੜੋ। - - ਵਿਜੈੱਟ ਜੋੜੋ - - ਹੁਣੇ ਨਹੀਂ - ਤੁਸੀਂ ਐਪਾਂ ਵਿਚਲੇ ਲਿੰਕਾਂ ਨੂੰ ਆਪਣੇ-ਆਪ Firefox ਨਾਲ ਖੋਲ੍ਹਣ ਲਈ ਸੈੱਟ ਕਰੋ। @@ -98,11 +94,21 @@ ਖ਼ਾਰਜ ਕਰੋ + + ਖੋਲ੍ਹੀਆਂ ਟੈਬਾਂ ਨੂੰ ਆਪਣੇ-ਆਪ ਬੰਦ ਕਰਨਾ ਨਿਯਤ ਕਰੋ, ਜੋ ਕਿ ਪਿਛਲੇ ਦਿਨ, ਹਫ਼ਤੇ ਜਾਂ ਮਹੀਨੇ ਵਿੱਚ +ਵੇਖੀਆਂ ਗਈਆਂ। ਚੋਣਾਂ ਵੇਖੋ ਖ਼ਾਰਜ ਕਰੋ + + ਖੁੱਲ੍ਹੀਆਂ ਟੈਬਾਂ ਦੇ ਖਾਤੇ ਨੂੰ ਬਦਲੋ। ਸੈਟਿੰਗਾਂ ਉੱਤੇ ਜਾਓ ਅਤੇ ਟੈਬ ਵੇਖਣ ਲਈ ਗਿੱਡ ਚੁਣੋ। + + ਸੈਟਿੰਗਾਂ ਉੱਤੇ ਜਾਓ + + ਖ਼ਾਰਜ ਕਰੋ + ਨਵੀਂ ਟੈਬ @@ -275,6 +281,8 @@ ਲਿੰਕ ਪ੍ਰਾਈਵੇਟ ਟੈਬ ਵਿੱਚ ਖੋਲ੍ਹੋ ਪ੍ਰਾਈਵੇਟ ਬ੍ਰਾਊਜ਼ਿੰਗ ਵਿੱਚ ਸਕਰੀਨਸ਼ਾਟ ਮਨਜ਼ੂਰ ਕਰੋ + + ਜੇ ਇਜਾਜ਼ਤ ਦਿੱਤੀ ਤਾਂ ਪ੍ਰਾਈਵੇਟ ਟੈਬਾਂ ਉਦੋਂ ਵੀ ਦਿਖਾਈ ਦੇਣਗੀਆਂ, ਜਦੋਂ ਕਈ ਐਪਾਂ ਖੁੱਲ੍ਹੀਆਂ ਹੁੰਦੀਆਂ ਹਨ ਪ੍ਰਾਈਵੇਟ ਬਰਾਊਜ਼ਿੰਗ ਸ਼ਾਰਟਕੱਟ ਜੋੜੋ @@ -361,6 +369,10 @@ ਭੰਡਾਰ ਮਾਲਕ (ਵਰਤੋਂਕਾਰ ID) + + ਐਡ-ਆਨ ਭੰਡਾਰ ਸੋਧਿਆ ਗਿਆ। ਤਬਦੀਲੀਆਂ ਲਾਗੂ ਕਰਨ ਲਈ ਐਪਲੀਕੇਸ਼ਨ ਨੂੰ +ਬੰਦ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ… + ਹੁਣੇ ਸਿੰਕ ਕਰੋ @@ -442,6 +454,10 @@ ਮਾਰਕੀਟਿੰਗ ਡਾਟਾ %1$s ਵਿੱਚ ਕਿਹੜੇ ਫੀਚਰ ਤੁਸੀਂ ਵਰਤਦੇ ਹੋ, ਬਾਰੇ ਡਾਟੇ ਨੂੰ Leanplum, ਸਾਡੇ ਮਾਰਕੀਟਿੰਗ ਵੇਂਡਰ ਨਾਲ ਸਾਂਝਾ ਕਰਦਾ ਹੈ। + + ਅਧਿਐਨ + + Mozilla ਨੂੰ ਅਧਿਐਨ ਇੰਸਟਾਲ ਕਰਨ ਤੇ ਚਲਾਉਣ ਦੇ ਇਜਾਜ਼ਤ ਦਿਓ ਤਜਰਬੇ @@ -569,6 +585,15 @@ ਇੱਕ ਮਹੀਨੇ ਬਾਅਦ + + ਖੁਦ ਬੰਦ ਕਰੋ + + ਇੱਕ ਦਿਨ ਬਾਅਦ ਬੰਦ ਕਰੋ + + ਇੱਕ ਹਫ਼ਤੇ ਬਾਅਦ ਬੰਦ ਕਰੋ + + ਇੱਕ ਮਹੀਨੇ ਬਾਅਦ ਬੰਦ ਕਰੋ + ਟੈਬਾਂ ਖੋਲ੍ਹੋ @@ -586,6 +611,8 @@ ਟੈਬਾਂ ਖੋਲ੍ਹੋ ਭੰਡਾਰ ਵਿੱਚ ਸੰਭਾਲੋ + + ਚੁਣੋ ਸਾਰੀਆਂ ਟੈਬਾਂ ਸਾਂਝੀਆਂ ਕਰੋ @@ -600,8 +627,18 @@ ਮੁੱਖ ਸਫ਼ੇ ਉੱਤੇ ਜਾਓ ਟੈਬ ਢੰਗ ਨੂੰ ਬਦਲੋ + + ਬੁੱਕਮਾਰਕ + + ਬੰਦ ਕਰੋ + + ਚੁਣੀਆਂ ਟੈਬਾਂ ਸਾਂਝੀਆਂ ਕਰੋ + + ਚੁਣੀਆਂ ਟੈਬਾਂ ਦਾ ਮੇਨੂ ਟੈਬ ਨੂੰ ਭੰਡਾਰ ਵਿੱਚੋਂ ਹਟਾਓ + + ਟੈਬਾਂ ਚੁਣੋ ਟੈਬ ਬੰਦ ਕਰੋ @@ -638,8 +675,10 @@ ਭੰਡਾਰ ਦਾ ਨਾਂ - - ਹਟਾਓ + + ਨਾਂ ਬਦਲੋ + + ਹਟਾਓ ਅਤੀਤ ਵਿੱਚੋਂ ਹਟਾਓ @@ -693,12 +732,24 @@ ਇ਼ੱਥੇ ਕੋਈ ਅਤੀਤ ਨਹੀਂ ਹੈ + + ਡਾਊਨਲੋਡ ਹਟਾਓ + + ਕੀ ਤੁਸੀਂ ਆਪਣੇ ਡਾਊਨਲੋਡਾਂ ਨੂੰ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ? + + ਡਾਊਨਲੋਡ ਹਟਾਏ ਗਏ ਡਾਊਨਲੋਡ ਨਹੀਂ ਹੈ %1$d ਚੁਣੇ + + ਖੋਲ੍ਹੋ + + ਹਟਾਓ + + ਅਫ਼ਸੋਸ ਹੈ। %1$s ਉਸ ਸਫ਼ੇ ਨੂੰ ਲੋਡ ਨਹੀਂ ਕਰ ਸਕਦਾ ਹੈ। @@ -855,6 +906,9 @@ ਭੰਡਾਰ ਭੰਡਾਰ ਮੇਨੂ + + ਚੀਜ਼ਾਂ, ਜੋ ਤੁਹਾਡੇ ਲਈ ਅਰਥ ਰੱਖਦੀਆਂ ਹਨ, ਨੂੰ ਇਕੱਤਰ ਕਰੋ।\nਬਾਅਦ ਚ ਫ਼ੌਰੀ ਤੌਰ ਉੱਤੇ ਲੱਭਣ +ਲਈ ਇੱਕੋ ਜੇਹੀਆਂ ਖੋਜਾਂ, ਸਾਈਟਾਂ ਤੇ ਟੈਬਾਂ ਦਾ ਗਰੁੱਪ ਬਣਾਓ। ਟੈਬਾਂ ਚੁਣੋ @@ -959,6 +1013,12 @@ ਟੈਬ ਬੰਦ ਕੀਤੀ ਟੈਬਾਂ ਬੰਦ ਕੀਤੀਆਂ + + ਟੈਬਾਂ ਬੰਦ ਕਰੋ! + + ਬੁੱਕਮਾਰਕ ਸੰਭਾਲੇ ਗਏ! + + ਵੇਖੋ ਸਿਖਰਲੀਆਂ ਸਾਈਟਾਂ ਵਿੱਚ ਜੋੜੋ! @@ -1106,6 +1166,8 @@ %s ਮੁੜ-ਡਿਜ਼ਾਈਨ ਬਾਰੇ ਕੋਈ ਸਵਾਲ ਹੈ? ਲੱਭ ਰਹੇ ਕੋ ਕਿ ਕੀ ਬਦਲਿਆ ਹੈ? ਜਵਾਬ ਇੱਥੇ ਲਵੋ + + ਆਪਣੇ Firefox ਖਾਤੇ ਨਾਲ ਬੁੱਕਮਾਰਕ, ਪਾਸਵਰਡ ਤੇ ਹੋਰਾਂ ਨੂੰ ਸਿੰਕ ਕਰਨਾ ਸ਼ੁਰੂ ਕਰੋ। ਹੋਰ ਜਾਣੋ ਸ਼ਾਰਟਕੱਟ ਨਾਂ + + ਤੁਸੀਂ ਇਸ ਵੈੱਬਸਾਈਟ ਨੂੰ ਫ਼ੌਰੀ ਤੌਰ ਉੱਤੇ ਵਰਤਣ ਤੇ ਐਪ ਵਾਂਗ ਤੇਜ਼ ਬਰਾਊਜ਼ ਕਰਨ ਲਈ ਸੌਖੀ ਤਰ੍ਹਾਂ +ਮੁੱਖ ਸਕਰੀਨ ਉੱਤੇ ਸੌਖੀ ਤਰ੍ਹਾਂ ਜੋੜ ਸਕਦੇ ਹੋ। + ਲਾਗਇਨ ਅਤੇ ਪਾਸਵਰਡ @@ -1617,6 +1683,15 @@ ਸਭ ਤੋਂ ਵੱਧ ਖੋਲ੍ਹੀਆਂ ਸਾਈਟਾਂ ਵੇਖੋ + + ਨਾਂ + + ਸਿਰਖਲੀ ਸਾਈਟ ਦਾ ਨਾਂ + + ਠੀਕ ਹੈ + + ਰੱਦ ਕਰੋ + ਹਟਾਓ From 5867d2830a9ea528962e5baf534fe3abac1dadb9 Mon Sep 17 00:00:00 2001 From: Oana Horvath Date: Wed, 2 Dec 2020 15:30:41 +0200 Subject: [PATCH 058/202] Split UI smoke tests into smaller tests --- .../java/org/mozilla/fenix/ui/SmokeTest.kt | 120 ++++++++++++++++-- .../mozilla/fenix/ui/robots/BrowserRobot.kt | 2 +- .../fenix/ui/robots/ThreeDotMenuMainRobot.kt | 2 - 3 files changed, 107 insertions(+), 17 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt index 97ca48e72..613f17b22 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt @@ -125,32 +125,71 @@ class SmokeTest { @Test fun verifyPageMainMenuItemsTest() { val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) - // Add this to check openInApp and youtube is a default app available in every Android emulator/device - val youtubeUrl = "www.youtube.com" navigationToolbar { }.enterURLAndEnterToBrowser(defaultWebPage.url) { }.openThreeDotMenu { verifyThreeDotMainMenuItems() + } + } + + // Could be removed when more smoke tests from the History category are added + @Test + fun openMainMenuHistoryItemTest() { + homeScreen { + }.openThreeDotMenu { }.openHistory { verifyHistoryMenuView() - }.goBackToBrowser { + } + } + + // Could be removed when more smoke tests from the Bookmarks category are added + @Test + fun openMainMenuBookmarksItemTest() { + homeScreen { }.openThreeDotMenu { }.openBookmarks { verifyBookmarksMenuView() - }.goBackToBrowser { + } + } + + @Test + fun openMainMenuSyncedTabsItemTest() { + homeScreen { }.openThreeDotMenu { }.openSyncedTabs { verifySyncedTabsMenuHeader() - }.goBack { + } + } + + // Could be removed when more smoke tests from the Settings category are added + @Test + fun openMainMenuSettingsItemTest() { + homeScreen { }.openThreeDotMenu { }.openSettings { verifySettingsView() - }.goBackToBrowser { + } + } + + @Test + fun openMainMenuFindInPageTest() { + val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + navigationToolbar { + }.enterURLAndEnterToBrowser(defaultWebPage.url) { }.openThreeDotMenu { }.openFindInPage { verifyFindInPageSearchBarItems() - }.closeFindInPage { + } + } + + @Test + fun openMainMenuAddTopSiteTest() { + val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + navigationToolbar { + }.enterURLAndEnterToBrowser(defaultWebPage.url) { }.openThreeDotMenu { }.addToFirefoxHome { verifySnackBarText("Added to top sites!") @@ -158,30 +197,84 @@ class SmokeTest { }.openNewTab { }.dismissSearchBar { verifyExistingTopSitesTabs(defaultWebPage.title) - }.openTabDrawer { - }.openTab(defaultWebPage.title) { + } + } + + @Test + fun mainMenuAddToHomeScreenTest() { + val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + navigationToolbar { + }.enterURLAndEnterToBrowser(defaultWebPage.url) { }.openThreeDotMenu { }.openAddToHomeScreen { verifyShortcutNameField(defaultWebPage.title) clickAddShortcutButton() clickAddAutomaticallyButton() }.openHomeScreenShortcut(defaultWebPage.title) { + verifyPageContent(defaultWebPage.content) + } + } + + @Test + fun openMainMenuAddToCollectionTest() { + val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + navigationToolbar { + }.enterURLAndEnterToBrowser(defaultWebPage.url) { }.openThreeDotMenu { }.openSaveToCollection { verifyCollectionNameTextField() - }.exitSaveCollection { + } + } + + @Test + fun mainMenuBookmarkButtonTest() { + val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + navigationToolbar { + }.enterURLAndEnterToBrowser(defaultWebPage.url) { }.openThreeDotMenu { }.bookmarkPage { verifySnackBarText("Bookmark saved!") + } + } + + @Test + fun mainMenuShareButtonTest() { + val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + + navigationToolbar { + }.enterURLAndEnterToBrowser(defaultWebPage.url) { }.openThreeDotMenu { }.sharePage { verifyShareAppsLayout() - }.closeShareDialogReturnToPage { + } + } + + @Test + fun mainMenuRefreshButtonTest() { + val refreshWebPage = TestAssetHelper.getRefreshAsset(mockWebServer) + + navigationToolbar { + }.enterURLAndEnterToBrowser(refreshWebPage.url) { + mDevice.waitForIdle() }.openThreeDotMenu { + verifyThreeDotMenuExists() + verifyRefreshButton() }.refreshPage { - verifyUrl(defaultWebPage.url.toString()) - }.openNavigationToolbar { + verifyPageContent("REFRESHED") + } + } + + @Test + fun mainMenuOpenInAppTest() { + // Using youtube as is a default app available in every Android emulator/device + val youtubeUrl = "www.youtube.com" + + navigationToolbar { }.enterURLAndEnterToBrowser(youtubeUrl.toUri()) { + verifyPageContent("YouTube") }.openThreeDotMenu { verifyOpenInAppButton() } @@ -209,7 +302,6 @@ class SmokeTest { }.goBackToBrowser { clickEnhancedTrackingProtectionPanel() verifyEnhancedTrackingProtectionSwitch() - // Turning off TP Switch results in adding the WebPage to exception list clickEnhancedTrackingProtectionSwitchOffOn() } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt index dcf84c575..5ac6bf44a 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt @@ -82,7 +82,7 @@ class BrowserRobot { Until.findObject(By.res("org.mozilla.fenix.debug:id/engineView")), waitingTime ) - assertTrue(mDevice.findObject(UiSelector().text(expectedText)).waitForExists(waitingTime)) + assertTrue(mDevice.findObject(UiSelector().textContains(expectedText)).waitForExists(waitingTime)) } fun verifyTabCounter(expectedText: String) { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt index 03e6ec5bf..869f313bd 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt @@ -519,8 +519,6 @@ private fun assertDesktopSite() { desktopSiteButton().check(matches(isDisplayed())) } -private fun openInAppButton() = - onView(allOf(withText(R.string.browser_menu_open_app_link))) private fun assertOpenInAppButton() { onView(withId(R.id.mozac_browser_menu_recyclerView)) .perform( From 0d85e50890e09f14711aafebee576d77a9d1c7d0 Mon Sep 17 00:00:00 2001 From: MickeyMoz Date: Wed, 2 Dec 2020 15:33:09 +0000 Subject: [PATCH 059/202] Update Android Components version to 68.0.20201201190117. --- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index fb5849a4a..432a88912 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201130143056" + const val VERSION = "68.0.20201201190117" } From 26051f7c0eb7bf96e3237223fe86e94e25b1aa8b Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Wed, 2 Dec 2020 11:40:44 -0500 Subject: [PATCH 060/202] Closes #16603: Disable pull down refresh when activity is in immersive mode (#16793) --- .../fenix/browser/BaseBrowserFragment.kt | 11 ++++++++-- .../fenix/browser/BrowserFragmentTest.kt | 21 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) 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 de85e69f4..1fd65872d 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -644,8 +644,8 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, .collect { tab -> pipModeChanged(tab) } } - view.swipeRefresh.isEnabled = - FeatureFlags.pullToRefreshEnabled && context.settings().isPullToRefreshEnabledInBrowser + view.swipeRefresh.isEnabled = shouldPullToRefreshBeEnabled() + if (view.swipeRefresh.isEnabled) { val primaryTextColor = ThemeManager.resolveAttribute(R.attr.primaryText, context) @@ -771,6 +771,13 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, browserToolbarView.expand() } + @VisibleForTesting + internal fun shouldPullToRefreshBeEnabled(): Boolean { + return FeatureFlags.pullToRefreshEnabled && + requireContext().settings().isPullToRefreshEnabledInBrowser && + !(requireActivity() as HomeActivity).isImmersive + } + private fun initializeEngineView(toolbarHeight: Int) { val context = requireContext() diff --git a/app/src/test/java/org/mozilla/fenix/browser/BrowserFragmentTest.kt b/app/src/test/java/org/mozilla/fenix/browser/BrowserFragmentTest.kt index 8c7ea5193..9a0fc2e4b 100644 --- a/app/src/test/java/org/mozilla/fenix/browser/BrowserFragmentTest.kt +++ b/app/src/test/java/org/mozilla/fenix/browser/BrowserFragmentTest.kt @@ -38,6 +38,7 @@ import org.mozilla.fenix.R import org.mozilla.fenix.components.toolbar.BrowserToolbarView import org.mozilla.fenix.ext.application import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.settings import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.onboarding.FenixOnboarding @@ -284,6 +285,26 @@ class BrowserFragmentTest { verify(exactly = 0) { onboarding.finish() } } + @Test + fun `WHEN isPullToRefreshEnabledInBrowser is disabled THEN pull down refresh is disabled`() { + every { homeActivity.isImmersive } returns false + every { context.settings().isPullToRefreshEnabledInBrowser } returns true + assert(browserFragment.shouldPullToRefreshBeEnabled()) + + every { context.settings().isPullToRefreshEnabledInBrowser } returns false + assert(!browserFragment.shouldPullToRefreshBeEnabled()) + } + + @Test + fun `WHEN in immersive mode THEN pull down refresh is disabled`() { + every { homeActivity.isImmersive } returns false + every { context.settings().isPullToRefreshEnabledInBrowser } returns true + assert(browserFragment.shouldPullToRefreshBeEnabled()) + + every { homeActivity.isImmersive } returns true + assert(!browserFragment.shouldPullToRefreshBeEnabled()) + } + private fun addAndSelectTab(tab: TabSessionState) { store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking() store.dispatch(TabListAction.SelectTabAction(tab.id)).joinBlocking() From 3722033a5cc7498c0d99854840f997d9d7dfd6e1 Mon Sep 17 00:00:00 2001 From: Christian Sadilek Date: Thu, 5 Nov 2020 16:25:45 -0500 Subject: [PATCH 061/202] For #16032: Support installing recommended add-ons from AMO --- .../mozilla/fenix/AppRequestInterceptor.kt | 10 +- .../mozilla/fenix/AppRequestInterceptor.kt | 56 ++++++++++- .../java/org/mozilla/fenix/HomeActivity.kt | 2 + .../fenix/addons/AddonsManagementFragment.kt | 54 ++++++++++- .../org/mozilla/fenix/addons/Extensions.kt | 4 +- .../java/org/mozilla/fenix/components/Core.kt | 11 ++- app/src/main/res/navigation/nav_graph.xml | 13 ++- app/src/main/res/values/strings.xml | 6 ++ .../fenix/AppRequestInterceptorTest.kt | 95 ++++++++++++++++++- .../addons/AddonsManagementFragmentTest.kt | 72 ++++++++++++++ 10 files changed, 314 insertions(+), 9 deletions(-) create mode 100644 app/src/test/java/org/mozilla/fenix/addons/AddonsManagementFragmentTest.kt diff --git a/app/src/androidTest/java/org/mozilla/fenix/AppRequestInterceptor.kt b/app/src/androidTest/java/org/mozilla/fenix/AppRequestInterceptor.kt index c16ee4d63..59e2de9a5 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/AppRequestInterceptor.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/AppRequestInterceptor.kt @@ -5,18 +5,26 @@ package org.mozilla.fenix import android.content.Context +import androidx.navigation.NavController import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.request.RequestInterceptor import org.mozilla.fenix.ext.components import org.mozilla.fenix.ui.robots.appContext +import java.lang.ref.WeakReference /** * This class overrides the application's request interceptor to * deactivate the FxA web channel * which is not supported on the staging servers. */ - class AppRequestInterceptor(private val context: Context) : RequestInterceptor { + + private var navController: WeakReference? = null + + fun setNavigationController(navController: NavController) { + this.navController = WeakReference(navController) + } + override fun onLoadRequest( engineSession: EngineSession, uri: String, diff --git a/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt b/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt index 915320890..e8ef423c8 100644 --- a/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt +++ b/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt @@ -7,6 +7,7 @@ package org.mozilla.fenix import android.content.Context import android.net.ConnectivityManager import androidx.core.content.getSystemService +import androidx.navigation.NavController import mozilla.components.browser.errorpages.ErrorPages import mozilla.components.browser.errorpages.ErrorType import mozilla.components.concept.engine.EngineSession @@ -14,8 +15,18 @@ import mozilla.components.concept.engine.request.RequestInterceptor import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.isOnline +import java.lang.ref.WeakReference + +class AppRequestInterceptor( + private val context: Context +) : RequestInterceptor { + + private var navController: WeakReference? = null + + fun setNavigationController(navController: NavController) { + this.navController = WeakReference(navController) + } -class AppRequestInterceptor(private val context: Context) : RequestInterceptor { override fun onLoadRequest( engineSession: EngineSession, uri: String, @@ -26,6 +37,11 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor { isDirectNavigation: Boolean, isSubframeRequest: Boolean ): RequestInterceptor.InterceptionResponse? { + + interceptAmoRequest(uri, isSameDomain, hasUserGesture)?.let { response -> + return response + } + return context.components.services.appLinksInterceptor .onLoadRequest( engineSession, @@ -59,6 +75,42 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor { return RequestInterceptor.ErrorResponse.Uri(errorPageUri) } + /** + * Checks if the provided [uri] is a request to install an add-on from addons.mozilla.org and + * redirects to Add-ons Manager to trigger installation if needed. + * + * @return [RequestInterceptor.InterceptionResponse.Deny] when installation was triggered and + * the original request can be skipped, otherwise null to continue loading the page. + */ + private fun interceptAmoRequest( + uri: String, + isSameDomain: Boolean, + hasUserGesture: Boolean + ): RequestInterceptor.InterceptionResponse? { + // First we execute a quick check to see if this is a request we're interested in i.e. a + // request triggered by the user and coming from AMO. + if (hasUserGesture && isSameDomain && uri.startsWith(AMO_BASE_URL)) { + + // Check if this is a request to install an add-on. + val matchResult = AMO_INSTALL_URL_REGEX.toRegex().matchEntire(uri) + if (matchResult != null) { + + // Navigate and trigger add-on installation. + matchResult.groupValues.getOrNull(1)?.let { addonId -> + navController?.get()?.navigate( + NavGraphDirections.actionGlobalAddonsManagementFragment(addonId) + ) + + // We've redirected to the add-ons management fragment, skip original request. + return RequestInterceptor.InterceptionResponse.Deny + } + } + } + + // In all other case we let the original request proceed. + return null + } + /** * Where possible, this will make the error type more accurate by including information not * available to AC. @@ -116,5 +168,7 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor { companion object { internal const val LOW_AND_MEDIUM_RISK_ERROR_PAGES = "low_and_medium_risk_error_pages.html" internal const val HIGH_RISK_ERROR_PAGES = "high_risk_error_pages.html" + internal const val AMO_BASE_URL = "https://addons.mozilla.org" + internal const val AMO_INSTALL_URL_REGEX = "$AMO_BASE_URL/android/downloads/file/([^\\s]+)/([^\\s]+\\.xpi)" } } diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index a8bfd89d9..a5c24db0d 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -238,6 +238,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { startupTelemetryOnCreateCalled(intent.toSafeIntent(), savedInstanceState != null) + components.core.requestInterceptor.setNavigationController(navHost.navController) + StartupTimeline.onActivityCreateEndHome(this) // DO NOT MOVE ANYTHING BELOW HERE. } diff --git a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt index 670df4130..d486fac13 100644 --- a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt @@ -9,11 +9,13 @@ import android.os.Bundle import android.view.Gravity import android.view.View import android.view.accessibility.AccessibilityEvent +import androidx.annotation.VisibleForTesting import androidx.core.content.res.ResourcesCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.android.synthetic.main.fragment_add_ons_management.* import kotlinx.android.synthetic.main.fragment_add_ons_management.view.* @@ -28,6 +30,7 @@ import mozilla.components.feature.addons.ui.AddonsManagerAdapter import mozilla.components.feature.addons.ui.PermissionsDialogFragment import mozilla.components.feature.addons.ui.translateName import org.mozilla.fenix.R +import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.getRootView @@ -44,10 +47,21 @@ import java.util.concurrent.CancellationException @Suppress("TooManyFunctions", "LargeClass") class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) { + private val args by navArgs() + /** * Whether or not an add-on installation is in progress. */ private var isInstallationInProgress = false + + private var installExternalAddonComplete: Boolean + set(value) { + arguments?.putBoolean(BUNDLE_KEY_INSTALL_EXTERNAL_ADDON_COMPLETE, value) + } + get() { + return arguments?.getBoolean(BUNDLE_KEY_INSTALL_EXTERNAL_ADDON_COMPLETE, false) ?: false + } + private var adapter: AddonsManagerAdapter? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -82,9 +96,13 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) val recyclerView = view.add_ons_list recyclerView.layoutManager = LinearLayoutManager(requireContext()) val shouldRefresh = adapter != null + + // If the fragment was launched to install an "external" add-on from AMO, we deactivate + // the cache to get the most up-to-date list of add-ons to match against. + val allowCache = args.installAddonId == null || installExternalAddonComplete lifecycleScope.launch(IO) { try { - val addons = requireContext().components.addonManager.getAddons() + val addons = requireContext().components.addonManager.getAddons(allowCache = allowCache) lifecycleScope.launch(Dispatchers.Main) { runIfFragmentIsAttached { if (!shouldRefresh) { @@ -103,6 +121,12 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) if (shouldRefresh) { adapter?.updateAddons(addons) } + + args.installAddonId?.let { addonIn -> + if (!installExternalAddonComplete) { + installExternalAddon(addons, addonIn) + } + } } } } catch (e: AddonManagerException) { @@ -121,6 +145,30 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) } } + @VisibleForTesting + internal fun installExternalAddon(supportedAddons: List, installAddonId: String) { + val addonToInstall = supportedAddons.find { it.downloadId == installAddonId } + if (addonToInstall == null) { + showErrorSnackBar(getString(R.string.addon_not_supported_error)) + } else { + if (addonToInstall.isInstalled()) { + showErrorSnackBar(getString(R.string.addon_already_installed)) + } else { + showPermissionDialog(addonToInstall) + } + } + installExternalAddonComplete = true + } + + @VisibleForTesting + internal fun showErrorSnackBar(text: String) { + runIfFragmentIsAttached { + view?.let { + showSnackBar(it, text, FenixSnackbar.LENGTH_LONG) + } + } + } + private fun createAddonStyle(context: Context): AddonsManagerAdapter.Style { return AddonsManagerAdapter.Style( sectionsTextColor = ThemeManager.resolveAttribute(R.attr.primaryText, context), @@ -144,7 +192,8 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) as? AddonInstallationDialogFragment != null } - private fun showPermissionDialog(addon: Addon) { + @VisibleForTesting + internal fun showPermissionDialog(addon: Addon) { if (!isInstallationInProgress && !hasExistingPermissionDialogFragment()) { val dialog = PermissionsDialogFragment.newInstance( addon = addon, @@ -278,5 +327,6 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) companion object { private const val PERMISSIONS_DIALOG_FRAGMENT_TAG = "ADDONS_PERMISSIONS_DIALOG_FRAGMENT" private const val INSTALLATION_DIALOG_FRAGMENT_TAG = "ADDONS_INSTALLATION_DIALOG_FRAGMENT" + private const val BUNDLE_KEY_INSTALL_EXTERNAL_ADDON_COMPLETE = "INSTALL_EXTERNAL_ADDON_COMPLETE" } } diff --git a/app/src/main/java/org/mozilla/fenix/addons/Extensions.kt b/app/src/main/java/org/mozilla/fenix/addons/Extensions.kt index 6c8a991f0..c0b135a60 100644 --- a/app/src/main/java/org/mozilla/fenix/addons/Extensions.kt +++ b/app/src/main/java/org/mozilla/fenix/addons/Extensions.kt @@ -14,10 +14,10 @@ import org.mozilla.fenix.components.FenixSnackbar * @param view A [View] used to determine a parent for the [FenixSnackbar]. * @param text The text to display in the [FenixSnackbar]. */ -internal fun showSnackBar(view: View, text: String) { +internal fun showSnackBar(view: View, text: String, duration: Int = FenixSnackbar.LENGTH_SHORT) { FenixSnackbar.make( view = view, - duration = FenixSnackbar.LENGTH_SHORT, + duration = duration, isDisplayedWithBrowserToolbar = true ) .setText(text) 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 644319e3a..a6011c217 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -100,7 +100,7 @@ class Core( */ val engine: Engine by lazyMonitored { val defaultSettings = DefaultSettings( - requestInterceptor = AppRequestInterceptor(context), + requestInterceptor = requestInterceptor, remoteDebuggingEnabled = context.settings().isRemoteDebuggingEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M, testingModeEnabled = false, @@ -141,6 +141,15 @@ class Core( } } + /** + * Passed to [engine] to intercept requests for app links, + * and various features triggered by page load requests. + * + * NB: This does not need to be lazy as it is initialized + * with the engine on startup. + */ + val requestInterceptor = AppRequestInterceptor(context) + /** * [Client] implementation to be used for code depending on `concept-fetch`` */ diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 2fca420f1..1ca254248 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -85,7 +85,13 @@ app:destination="@id/bookmarkEditFragment" /> + app:destination="@id/addons_management_graph"> + + @@ -866,6 +872,11 @@ + Add-on collection modified. Quitting the application to apply changes… + + + Add-on is not supported + + Add-on is already installed + Sync now diff --git a/app/src/test/java/org/mozilla/fenix/AppRequestInterceptorTest.kt b/app/src/test/java/org/mozilla/fenix/AppRequestInterceptorTest.kt index f843555fa..b24cbee29 100644 --- a/app/src/test/java/org/mozilla/fenix/AppRequestInterceptorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/AppRequestInterceptorTest.kt @@ -6,14 +6,17 @@ package org.mozilla.fenix import android.net.ConnectivityManager import androidx.core.content.getSystemService +import androidx.navigation.NavController import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic +import io.mockk.verify import mozilla.components.browser.errorpages.ErrorPages import mozilla.components.browser.errorpages.ErrorType import mozilla.components.concept.engine.request.RequestInterceptor import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -26,6 +29,7 @@ import org.mozilla.fenix.helpers.FenixRobolectricTestRunner class AppRequestInterceptorTest { private lateinit var interceptor: RequestInterceptor + private lateinit var navigationController: NavController @Before fun setUp() { @@ -34,7 +38,96 @@ class AppRequestInterceptorTest { every { testContext.getSystemService()!!.isOnline() } returns true - interceptor = AppRequestInterceptor(testContext) + navigationController = mockk(relaxed = true) + interceptor = AppRequestInterceptor(testContext).also { + it.setNavigationController(navigationController) + } + } + + @Test + fun `GIVEN request to install add-on WHEN on same domain and triggered by user THEN start add-on installation`() { + val addonId = "12345678" + val result = interceptor.onLoadRequest( + engineSession = mockk(), + uri = "https://addons.mozilla.org/android/downloads/file/$addonId/test.xpi", + lastUri = "https://addons.mozilla.org/en-US/firefox/", + hasUserGesture = true, + isSameDomain = true, + isDirectNavigation = false, + isRedirect = false, + isSubframeRequest = false + ) + + verify { navigationController.navigate(NavGraphDirections.actionGlobalAddonsManagementFragment(addonId)) } + assertEquals(RequestInterceptor.InterceptionResponse.Deny, result) + } + + @Test + fun `GIVEN request to install add-on WHEN on a different domain THEN no add-on installation is started`() { + val result = interceptor.onLoadRequest( + engineSession = mockk(), + uri = "https://addons.mozilla.org/android/downloads/file/12345678/test.xpi", + lastUri = "https://getpocket.com", + hasUserGesture = true, + isSameDomain = false, + isDirectNavigation = false, + isRedirect = false, + isSubframeRequest = false + ) + + verify(exactly = 0) { navigationController.navigate(NavGraphDirections.actionGlobalAddonsManagementFragment()) } + assertNull(result) + } + + @Test + fun `GIVEN invalid request to install add-on WHEN on same domain and triggered by user THEN no add-on installation is started`() { + val result = interceptor.onLoadRequest( + engineSession = mockk(), + uri = "https://addons.mozilla.org/android/downloads/file/12345678/test.invalid", + lastUri = "https://addons.mozilla.org/en-US/firefox/", + hasUserGesture = true, + isSameDomain = true, + isDirectNavigation = false, + isRedirect = false, + isSubframeRequest = false + ) + + verify(exactly = 0) { navigationController.navigate(NavGraphDirections.actionGlobalAddonsManagementFragment()) } + assertNull(result) + } + + @Test + fun `GIVEN request to install add-on WHEN not triggered by user THEN no add-on installation is started`() { + val result = interceptor.onLoadRequest( + engineSession = mockk(), + uri = "https://addons.mozilla.org/android/downloads/file/12345678/test.xpi", + lastUri = "https://addons.mozilla.org/en-US/firefox/", + hasUserGesture = false, + isSameDomain = true, + isDirectNavigation = false, + isRedirect = false, + isSubframeRequest = false + ) + + verify(exactly = 0) { navigationController.navigate(NavGraphDirections.actionGlobalAddonsManagementFragment()) } + assertNull(result) + } + + @Test + fun `GIVEN any request WHEN on same domain and triggered by user THEN no add-on installation is started`() { + val result = interceptor.onLoadRequest( + engineSession = mockk(), + uri = "https://blog.mozilla.org/blog/2020/10/20/mozilla-reaction-to-u-s-v-google/", + lastUri = "https://blog.mozilla.org", + hasUserGesture = true, + isSameDomain = true, + isDirectNavigation = false, + isRedirect = false, + isSubframeRequest = false + ) + + verify(exactly = 0) { navigationController.navigate(NavGraphDirections.actionGlobalAddonsManagementFragment()) } + assertNull(result) } @Test diff --git a/app/src/test/java/org/mozilla/fenix/addons/AddonsManagementFragmentTest.kt b/app/src/test/java/org/mozilla/fenix/addons/AddonsManagementFragmentTest.kt new file mode 100644 index 000000000..3b327fcb7 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/addons/AddonsManagementFragmentTest.kt @@ -0,0 +1,72 @@ +/* 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.addons + +import android.content.Context +import androidx.coordinatorlayout.widget.CoordinatorLayout +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import mozilla.components.feature.addons.Addon +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class AddonsManagementFragmentTest { + + private lateinit var context: Context + private lateinit var view: CoordinatorLayout + private lateinit var fragment: AddonsManagementFragment + + private val addonNotSupportedErrorMessage = "not supported" + private val addonAlreadyInstalledErrorMessage = "already installed" + + @Before + fun setup() { + context = mockk(relaxed = true) + view = mockk(relaxed = true) + fragment = spyk(AddonsManagementFragment()) + every { fragment.context } returns context + every { fragment.view } returns view + every { fragment.showErrorSnackBar(any()) } returns Unit + every { fragment.showPermissionDialog(any()) } returns Unit + every { fragment.getString(R.string.addon_not_supported_error) } returns addonNotSupportedErrorMessage + every { fragment.getString(R.string.addon_already_installed) } returns addonAlreadyInstalledErrorMessage + } + + @Test + fun `GIVEN add-on is installed from external source WHEN add-on is not supported THEN error is shown`() { + val supportedAddons = listOf( + Addon("1", downloadId = "d1"), Addon("2", downloadId = "d2") + ) + val installAddonId = "d3" + fragment.installExternalAddon(supportedAddons, installAddonId) + verify { fragment.showErrorSnackBar(addonNotSupportedErrorMessage) } + } + + @Test + fun `GIVEN add-on is installed from external source WHEN add-on is already installed THEN error is shown`() { + val addon1 = Addon("1", downloadId = "d1", installedState = mockk()) + val addon2 = Addon("2", downloadId = "d2") + val supportedAddons = listOf(addon1, addon2) + + fragment.installExternalAddon(supportedAddons, "d1") + verify { fragment.showErrorSnackBar(addonAlreadyInstalledErrorMessage) } + } + + @Test + fun `GIVEN add-on is installed from external source WHEN supported and not installed THEN start installation`() { + val addon1 = Addon("1", downloadId = "d1", installedState = mockk()) + val addon2 = Addon("2", downloadId = "d2") + val supportedAddons = listOf(addon1, addon2) + + fragment.installExternalAddon(supportedAddons, "d2") + verify { fragment.showPermissionDialog(addon2) } + } +} From eab87555dc52cefe459f269d6aaf546e745868df Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Thu, 3 Dec 2020 00:06:42 +0000 Subject: [PATCH 062/202] Import l10n. --- app/src/main/res/values-es-rMX/strings.xml | 42 +++++++++++----------- app/src/main/res/values-fr/strings.xml | 2 ++ app/src/main/res/values-gn/strings.xml | 33 +++++++++++++++++ app/src/main/res/values-nn-rNO/strings.xml | 10 ++++++ 4 files changed, 65 insertions(+), 22 deletions(-) diff --git a/app/src/main/res/values-es-rMX/strings.xml b/app/src/main/res/values-es-rMX/strings.xml index 61dbdc258..b70184656 100644 --- a/app/src/main/res/values-es-rMX/strings.xml +++ b/app/src/main/res/values-es-rMX/strings.xml @@ -68,14 +68,6 @@ No, gracias - - - Llega a Firefox más rápido. Agrega un widget a tu pantalla de inicio. - - Agregar widget - - Ahora no - Puedes configurar Firefox para que abra automáticamente enlaces en aplicaciones. @@ -143,6 +135,8 @@ Instalar Pestañas sincronizadas + + Resincronizar Encontrar en página @@ -212,6 +206,8 @@ Saber más + + Abrir una nueva pestaña de Firefox Buscar @@ -267,6 +263,8 @@ Abrir enlaces en una pestaña privada Permitir capturas de pantalla en navegación privada + + Si lo permites, las pestañas privadas también serán visibles cuando haya varias aplicaciones abiertas Agregar acceso directo a navegación privada @@ -287,6 +285,8 @@ Tema Inicio + + Gestos Personalizar @@ -322,6 +322,8 @@ Buscar historial de navegación Buscar marcadores + + Buscar pestañas sincronizadas Configuración de la cuenta @@ -505,7 +507,6 @@ No hay pestañas recientemente cerradas - Cerrar pestañas @@ -583,7 +584,7 @@ Abrir pestañas - Eliminar + Eliminar Eliminar del historial @@ -631,13 +632,13 @@ No hay ningún historial - No hay descargas aquí %1$d seleccionadas + Lo sentimos. %1$s no puede cargar esa página. @@ -1045,10 +1046,6 @@ Empieza a sincronizar marcadores, contraseñas y más con tu cuenta de Firefox. Saber más - - Ya has iniciado sesión como %s en otro navegador Firefox de este teléfono. ¿Quieres iniciar sesión con esta cuenta? Sí, iniciar sesión @@ -1292,8 +1289,6 @@ Continuar al sitio web Nombre del acceso directo - - Puedes agregar fácilmente este sitio web a la página de inicio de tu teléfono para navegar más rápido con una experiencia similar a una app. Inicios de sesión y contraseñas @@ -1519,7 +1514,7 @@ Ya existe un inicio de sesión con ese nombre de usuario - + Conectar otro dispositivo. Por favor, vuelve a autenticarte. @@ -1538,8 +1533,6 @@ Límite de sitios frecuentes alcanzado - - Para agregar un nuevo sitio frecuente, elimina uno. Mantén presionado el sitio y selecciona eliminar. Vale, entendido @@ -1549,7 +1542,7 @@ Eliminar - Saca el máximo provecho de %s. @@ -1557,4 +1550,9 @@ Colecciona las cosas que te importan Agrupa búsquedas, sitios y pestañas similares para acceder a ellos rápidamente. - + + Ya has iniciado sesión como %s en otro navegador Firefox de este teléfono. ¿Quieres iniciar sesión con esta cuenta? + + Puedes agregar fácilmente este sitio web a la página de inicio de tu teléfono para navegar más rápido con una experiencia similar a una app. + + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index d56aacbe9..a2d4137e7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -100,6 +100,8 @@ Modifiez la disposition des onglets ouverts. Allez dans les paramètres et sélectionnez « Grille » sous « Affichage des onglets ». + + Ouvrir les paramètres Ignorer diff --git a/app/src/main/res/values-gn/strings.xml b/app/src/main/res/values-gn/strings.xml index bb797e3d4..3db1d6d84 100644 --- a/app/src/main/res/values-gn/strings.xml +++ b/app/src/main/res/values-gn/strings.xml @@ -21,6 +21,10 @@ Ne rendaykekuéra ñemiguáva ojehecháta ápe. + + Baidu + + JD 1 embojuruja tendayke. Eikutu emoambue hag̃ua tendayke. @@ -95,6 +99,13 @@ Mboyke + + Emoambue tendayke ijurujáva oĩha. Eho moĩporãhápe ha eiporavo kora’ieta tendyke rechaha poravorãme. + + Eho ñembohekópe + + Mboyke + Tendayke pyahu @@ -436,6 +447,10 @@ Mba’ekuaarã jehepyme’ẽrã Emoherakuã mba’ekuaarã umi tembiapoite eipurúva %1$s rehegua Leanplum ndive, ore me’ẽhára pumbyry popeguáva. + + Ñembokatupyry + + Emoneĩ Mozilla-pe omohenda ha omongu’évo ñembokatupyry Mba’epyahu @@ -589,6 +604,8 @@ Embojuruja tendayke Eñongatu atyhápe + + Poravo Emoherakuã opaite tendayke @@ -603,8 +620,18 @@ Eho ñepyrũhápe Embojopyru tendayke ayvu + + Techaukaha + + Mboty + + Emoherakuã tendayke mbosa’ypyre + + Tendayke poravorã mbosa’ypyre Embogue tendayke mbyatypyregua + + Eiporavo tendayke Emboty tendayke @@ -987,6 +1014,12 @@ Tendayke mbotypyre Tendayke mbotypyre + + ¡Tendayke mbotypyre! + + ¡Techaukaha ñongatupyre! + + Hecha ¡Ojuajúma tenda iporãvévare! diff --git a/app/src/main/res/values-nn-rNO/strings.xml b/app/src/main/res/values-nn-rNO/strings.xml index 0eb9ff471..f07b18880 100644 --- a/app/src/main/res/values-nn-rNO/strings.xml +++ b/app/src/main/res/values-nn-rNO/strings.xml @@ -619,8 +619,14 @@ Bokmerke Lat att + + Del valde faner + + Meny for valde faner Fjern fane frå samlinga + + Vel faner Lat att fane @@ -999,6 +1005,10 @@ Fane attlaten Faner attlatne + + Faner attlatne + + Bokmerke lagra! Vis From 3cd4ff9ac8c1ae329f96ea58b49f98b589c46058 Mon Sep 17 00:00:00 2001 From: jhugman Date: Thu, 3 Dec 2020 12:24:46 +0000 Subject: [PATCH 063/202] Adding menu configuration for A/A experiment and A/B experiment (#16692) --- .../mozilla/fenix/experiments/Experiments.kt | 21 +++++++++++++ .../java/org/mozilla/fenix/home/HomeMenu.kt | 30 +++++++++++++++++-- .../main/res/drawable/ic_bookmark_list.xml | 15 ++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/experiments/Experiments.kt create mode 100644 app/src/main/res/drawable/ic_bookmark_list.xml diff --git a/app/src/main/java/org/mozilla/fenix/experiments/Experiments.kt b/app/src/main/java/org/mozilla/fenix/experiments/Experiments.kt new file mode 100644 index 000000000..5b5f67b34 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/experiments/Experiments.kt @@ -0,0 +1,21 @@ +/* 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.experiments + +class Experiments { + companion object { + const val A_A_NIMBUS_VALIDATION = "fenix-nimbus-validation" + const val BOOKMARK_ICON = "fenix-bookmark-list-icon" + } +} + +class ExperimentBranch { + companion object { + const val TREATMENT = "treatment" + const val CONTROL = "control" + const val A1 = "A1" + const val A2 = "A2" + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt b/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt index 3e172e8f8..b49795869 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt @@ -22,6 +22,8 @@ import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.OAuthAccount import mozilla.components.support.ktx.android.content.getColorFromAttr import org.mozilla.fenix.R +import org.mozilla.fenix.experiments.ExperimentBranch +import org.mozilla.fenix.experiments.Experiments import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings import org.mozilla.fenix.theme.ThemeManager @@ -96,17 +98,41 @@ class HomeMenu( onItemTapped.invoke(Item.WhatsNew) } + val experiments = context.components.analytics.experiments + val bookmarksIcon = experiments.getExperimentBranch(Experiments.BOOKMARK_ICON) + .let { + when (it) { + ExperimentBranch.TREATMENT -> R.drawable.ic_bookmark_list + else -> R.drawable.ic_bookmark_filled + } + } + val bookmarksItem = BrowserMenuImageText( context.getString(R.string.library_bookmarks), - R.drawable.ic_bookmark_filled, + bookmarksIcon, primaryTextColor ) { onItemTapped.invoke(Item.Bookmarks) } + // We want to validate that the Nimbus experiments library is working, from the android UI + // all the way back to the data science backend. We're not testing the user's preference + // or response, we're end-to-end testing the experiments platform. + // So here, we're running multiple identical branches with the same treatment, and if the + // user isn't targeted, then we get still get the same treatment. + // The `let` block is degenerate here, but left here so as to document the form of how experiments + // are implemented here. + val historyIcon = experiments.getExperimentBranch(Experiments.A_A_NIMBUS_VALIDATION) + .let { + when (it) { + ExperimentBranch.A1 -> R.drawable.ic_history + ExperimentBranch.A2 -> R.drawable.ic_history + else -> R.drawable.ic_history + } + } val historyItem = BrowserMenuImageText( context.getString(R.string.library_history), - R.drawable.ic_history, + historyIcon, primaryTextColor ) { onItemTapped.invoke(Item.History) diff --git a/app/src/main/res/drawable/ic_bookmark_list.xml b/app/src/main/res/drawable/ic_bookmark_list.xml new file mode 100644 index 000000000..eec7c8ffe --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark_list.xml @@ -0,0 +1,15 @@ + + + + + From c46552165e99c01145c1dfdbf1656b627f026615 Mon Sep 17 00:00:00 2001 From: Mark Beebe Date: Sat, 28 Nov 2020 11:02:41 -0800 Subject: [PATCH 064/202] Issue #16648: Expose Nexus logs --- taskcluster/ci/toolchain/android.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/taskcluster/ci/toolchain/android.yml b/taskcluster/ci/toolchain/android.yml index 7668a83af..2643d9724 100644 --- a/taskcluster/ci/toolchain/android.yml +++ b/taskcluster/ci/toolchain/android.yml @@ -55,4 +55,8 @@ linux64-android-gradle-dependencies: # TODO do no hardcode ANDROID_SDK_ROOT: /builds/worker/fetches/android-sdk-linux max-run-time: 14400 + artifacts: + - type: directory + name: public/logs/nexus + path: /opt/sonatype/nexus/logs worker-type: b-android-large From ea9f6920076a304edd1ab309f2a6a185669e5aa6 Mon Sep 17 00:00:00 2001 From: jhugman Date: Thu, 3 Dec 2020 16:30:20 +0000 Subject: [PATCH 065/202] Nimbus-SDK-119 Get Nimbus server endpoint at build time. (#16682) This PR builds on [AC##9024][1], and implements setting the Nimbus endpoints from [a secret set at build time][2]. For production use, this requires a secret named `nimbus_url` to be put into CI. Note: Nimbus is currently behind a feature flag. If developers wish to use a Nimbus server for local development, you can set the url by adding an entry into local.properties, e.g.: ``` nimbus.remote-settings.url=https://settings.stage.moz4ws.net ``` Without setting server, Nimbus will be able to function, except no experimental definitions will be fetched, and features under experiment will be configured as if not enrolled in the experiment. [1]: https://github.com/mozilla-mobile/android-components/pull/9024 [2]: https://groups.google.com/a/mozilla.com/g/android-components-team/c/lAGVKQy8aiA/m/rY3uGAwhBAAJ --- .gitignore | 4 ++-- README.md | 10 ++++++++ app/build.gradle | 24 +++++++++++++++++++ .../org/mozilla/fenix/components/Analytics.kt | 10 +++++++- .../fenix_taskgraph/transforms/build.py | 1 + 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8cce379dd..67abe8cc2 100644 --- a/.gitignore +++ b/.gitignore @@ -79,12 +79,12 @@ gen-external-apklibs # macOS .DS_Store -# Token files +# Secrets files, e.g. tokens .leanplum_token .adjust_token .sentry_token .mls_token - +.nimbus # Python Byte-compiled / optimized / DLL files __pycache__/ diff --git a/README.md b/README.md index 3eea0f8a1..9d87ae0c5 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ The known features that are disabled by default are: - Mozilla Location Services (also known as MLS) - Firebase Push Services - Telemetry (only disabled by default in debug builds) +- Nimbus ## Pre-push hooks To reduce review turn-around time, we'd like all pushes to run tests locally. We'd @@ -211,6 +212,15 @@ In order to build successfully, you need to check out a commit in the dependency - Run the `/tools/list_compatible_dependency_versions.py` script to output a compatible commit - Check out the latest commit from master in this repository and the dependency repository. However, this may fail if there were breaking changes added recently to the dependency. +### Using Nimbus servers during local development +If you're working with the Nimbus experiments platform, by default for local development Fenix configures Nimbus to not use a server. + +If you wish to use a Nimbus server during local development, you can add a `https://` or `file://` endpoint to the `local.properties` file. + +- `nimbus.remote-settings.url` + +Testing experimental branches should be possible without a server. + ### GeckoView Specify a relative path to your local `mozilla-central` checkout via `dependencySubstitutions.geckoviewTopsrcdir`, and optional a path to m-c object directory via `dependencySubstitutions.geckoviewTopobjdir`. diff --git a/app/build.gradle b/app/build.gradle index 99d71bc28..b149f2255 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -331,6 +331,30 @@ android.applicationVariants.all { variant -> buildConfigField 'String', 'MLS_TOKEN', '""' println("X_X") } + +// ------------------------------------------------------------------------------------------------- +// Nimbus: Read endpoint from local.properties of a local file if it exists +// ------------------------------------------------------------------------------------------------- + + print("Nimbus endpoint: ") + + if (!isDebug) { + try { + def url = new File("${rootDir}/.nimbus").text.trim() + buildConfigField 'String', 'NIMBUS_ENDPOINT', '"' + url + '"' + println "(Added from .nimbus file)" + } catch (FileNotFoundException ignored) { + buildConfigField 'String', 'NIMBUS_ENDPOINT', 'null' + println("X_X") + } + } else if (gradle.hasProperty("localProperties.nimbus.remote-settings.url")) { + def url=gradle.getProperty("localProperties.nimbus.remote-settings.url") + buildConfigField 'String', 'NIMBUS_ENDPOINT', '"' + url + '"' + println "(Added from local.properties file)" + } else { + buildConfigField 'String', 'NIMBUS_ENDPOINT', 'null' + println("--") + } } androidExtensions { diff --git a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt index 6510777aa..1505c2121 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt @@ -8,12 +8,14 @@ import android.app.Application import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.net.Uri import mozilla.components.lib.crash.CrashReporter import mozilla.components.lib.crash.service.CrashReporterService import mozilla.components.lib.crash.service.GleanCrashReporterService import mozilla.components.lib.crash.service.MozillaSocorroService import mozilla.components.lib.crash.service.SentryService import mozilla.components.service.nimbus.Nimbus +import mozilla.components.service.nimbus.NimbusServerSettings import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.Config import org.mozilla.fenix.FeatureFlags @@ -103,7 +105,13 @@ class Analytics( } val experiments by lazyMonitored { - Nimbus(context, server = null).apply { + val url: String? = BuildConfig.NIMBUS_ENDPOINT + val serverSettings = if (!url.isNullOrBlank()) { + NimbusServerSettings(url = Uri.parse(url)) + } else { + null + } + Nimbus(context, serverSettings).apply { if (FeatureFlags.nimbusExperiments) { initialize() // Global opt out state is stored in Nimbus, and shouldn't be toggled to `true` diff --git a/taskcluster/fenix_taskgraph/transforms/build.py b/taskcluster/fenix_taskgraph/transforms/build.py index 2c62117bf..37e51a7fc 100644 --- a/taskcluster/fenix_taskgraph/transforms/build.py +++ b/taskcluster/fenix_taskgraph/transforms/build.py @@ -48,6 +48,7 @@ def add_shippable_secrets(config, tasks): ('leanplum', '.leanplum_token'), ('sentry_dsn', '.sentry_token'), ('mls', '.mls_token'), + ('nimbus_url', '.nimbus'), )]) else: dummy_secrets.extend([{ From c99b2157fb7a60b7f6675bbc774c3938d335f564 Mon Sep 17 00:00:00 2001 From: Jonathan Almeida Date: Mon, 26 Oct 2020 16:40:07 -0400 Subject: [PATCH 066/202] Fix breaking changes from FCM SDK update --- app/src/main/java/org/mozilla/fenix/push/FirebasePushService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/mozilla/fenix/push/FirebasePushService.kt b/app/src/main/java/org/mozilla/fenix/push/FirebasePushService.kt index 2c060a2b4..7e03039f4 100644 --- a/app/src/main/java/org/mozilla/fenix/push/FirebasePushService.kt +++ b/app/src/main/java/org/mozilla/fenix/push/FirebasePushService.kt @@ -54,7 +54,7 @@ class FirebasePushService : LeanplumPushFirebaseMessagingService(), super.onNewToken(newToken) } - override fun onMessageReceived(remoteMessage: RemoteMessage?) { + override fun onMessageReceived(remoteMessage: RemoteMessage) { AutoPushService.onMessageReceived(remoteMessage) super.onMessageReceived(remoteMessage) } From 8a579ee62b2b980ce9c289dcd1332f8498d73a42 Mon Sep 17 00:00:00 2001 From: Oana Horvath Date: Thu, 3 Dec 2020 18:20:50 +0200 Subject: [PATCH 067/202] For #16838: deleted mainMenuOpenInAppTest --- .../java/org/mozilla/fenix/ui/SmokeTest.kt | 14 -------------- .../fenix/ui/robots/ThreeDotMenuMainRobot.kt | 10 ---------- 2 files changed, 24 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt index 613f17b22..4ef54dc4b 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt @@ -5,7 +5,6 @@ package org.mozilla.fenix.ui import android.view.View -import androidx.core.net.toUri import androidx.recyclerview.widget.RecyclerView import androidx.test.espresso.IdlingRegistry import androidx.test.platform.app.InstrumentationRegistry @@ -267,19 +266,6 @@ class SmokeTest { } } - @Test - fun mainMenuOpenInAppTest() { - // Using youtube as is a default app available in every Android emulator/device - val youtubeUrl = "www.youtube.com" - - navigationToolbar { - }.enterURLAndEnterToBrowser(youtubeUrl.toUri()) { - verifyPageContent("YouTube") - }.openThreeDotMenu { - verifyOpenInAppButton() - } - } - @Test fun verifyETPShieldNotDisplayedIfOFFGlobally() { val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt index 869f313bd..bbcd60285 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt @@ -115,7 +115,6 @@ class ThreeDotMenuMainRobot { fun verifyAddFirefoxHome() = assertAddToFirefoxHome() fun verifyAddToMobileHome() = assertAddToMobileHome() fun verifyDesktopSite() = assertDesktopSite() - fun verifyOpenInAppButton() = assertOpenInAppButton() fun verifyDownloadsButton() = assertDownloadsButton() fun verifyThreeDotMainMenuItems() { @@ -519,15 +518,6 @@ private fun assertDesktopSite() { desktopSiteButton().check(matches(isDisplayed())) } -private fun assertOpenInAppButton() { - onView(withId(R.id.mozac_browser_menu_recyclerView)) - .perform( - RecyclerViewActions.scrollTo( - hasDescendant(withText(R.string.browser_menu_open_app_link)) - ) - ).check(matches(withEffectiveVisibility(Visibility.VISIBLE))) -} - private fun downloadsButton() = onView(withText(R.string.library_downloads)) private fun assertDownloadsButton() { onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown()) From 79d1c08402274a3e96efad2929d7e0a7a9b1b647 Mon Sep 17 00:00:00 2001 From: Codrut Topliceanu <60002907+codrut-topliceanu@users.noreply.github.com> Date: Thu, 3 Dec 2020 22:30:00 +0200 Subject: [PATCH 068/202] For #16629 - Fix for collection snackbar View button (#16679) --- .../mozilla/fenix/browser/BrowserFragment.kt | 2 +- .../fenix/components/TabCollectionStorage.kt | 6 +- .../org/mozilla/fenix/home/HomeFragment.kt | 96 ++++++++++--------- .../fenix/tabtray/TabTrayDialogFragment.kt | 20 +++- app/src/main/res/navigation/nav_graph.xml | 4 + 5 files changed, 72 insertions(+), 56 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index 383c91683..562bcaa07 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -247,7 +247,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler { } private val collectionStorageObserver = object : TabCollectionStorage.Observer { - override fun onCollectionCreated(title: String, sessions: List) { + override fun onCollectionCreated(title: String, sessions: List, id: Long?) { showTabSavedToCollectionSnackbar(sessions.size, true) } diff --git a/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt b/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt index e04bac803..39da157c8 100644 --- a/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt +++ b/app/src/main/java/org/mozilla/fenix/components/TabCollectionStorage.kt @@ -40,7 +40,7 @@ class TabCollectionStorage( /** * A collection has been created */ - fun onCollectionCreated(title: String, sessions: List) = Unit + fun onCollectionCreated(title: String, sessions: List, id: Long?) = Unit /** * Tab(s) have been added to collection @@ -63,8 +63,8 @@ class TabCollectionStorage( } suspend fun createCollection(title: String, sessions: List) = ioScope.launch { - collectionStorage.createCollection(title, sessions) - notifyObservers { onCollectionCreated(title, sessions) } + val id = collectionStorage.createCollection(title, sessions) + notifyObservers { onCollectionCreated(title, sessions, id) } }.join() suspend fun addTabsToCollection(tabCollection: TabCollection, sessions: List) = ioScope.launch { diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 3f77c22a8..ec7d2e575 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -39,6 +39,7 @@ import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE +import com.google.android.material.appbar.AppBarLayout import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_home.privateBrowsingButton import kotlinx.android.synthetic.main.fragment_home.search_engine_icon @@ -62,10 +63,8 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import mozilla.appservices.places.BookmarkRoot import mozilla.components.browser.menu.view.MenuButton -import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager import mozilla.components.browser.state.selector.findTab -import mozilla.components.browser.state.selector.getNormalOrPrivateTabs import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.selector.privateTabs import mozilla.components.browser.state.state.BrowserState @@ -137,14 +136,6 @@ class HomeFragment : Fragment() { private val browsingModeManager get() = (activity as HomeActivity).browsingModeManager private val collectionStorageObserver = object : TabCollectionStorage.Observer { - override fun onCollectionCreated(title: String, sessions: List) { - scrollAndAnimateCollection() - } - - override fun onTabsAdded(tabCollection: TabCollection, sessions: List) { - scrollAndAnimateCollection(tabCollection) - } - override fun onCollectionRenamed(tabCollection: TabCollection, title: String) { lifecycleScope.launch(Main) { view?.sessionControlRecyclerView?.adapter?.notifyDataSetChanged() @@ -170,6 +161,7 @@ class HomeFragment : Fragment() { get() = _sessionControlInteractor!! private var sessionControlView: SessionControlView? = null + private var appBarLayout: AppBarLayout? = null private lateinit var currentMode: CurrentMode private val topSitesFeature = ViewBoundFeatureWrapper() @@ -267,6 +259,8 @@ class HomeFragment : Fragment() { updateSessionControlView(view) + appBarLayout = view.homeAppBar + activity.themeManager.applyStatusBarTheme(activity) return view } @@ -442,6 +436,12 @@ class HomeFragment : Fragment() { if (bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR)) { navigateToSearch() + } else if (bundleArgs.getLong(FOCUS_ON_COLLECTION, -1) >= 0) { + // No need to scroll to async'd loaded TopSites if we want to scroll to collections. + homeViewModel.shouldScrollToTopSites = false + /* Triggered when the user has added a tab to a collection and has tapped + * the View action on the [TabsTrayDialogFragment] snackbar.*/ + scrollAndAnimateCollection(bundleArgs.getLong(FOCUS_ON_COLLECTION, -1)) } } @@ -848,31 +848,23 @@ class HomeFragment : Fragment() { requireComponents.core.tabCollectionStorage.register(collectionStorageObserver, this) } + /** + * This method will find and scroll to the row of the specified collection Id. + * */ private fun scrollAndAnimateCollection( - changedCollection: TabCollection? = null + collectionIdToSelect: Long = -1 ) { - if (view != null) { + if (view != null && collectionIdToSelect >= 0) { viewLifecycleOwner.lifecycleScope.launch { val recyclerView = sessionControlView!!.view delay(ANIM_SCROLL_DELAY) - val tabsSize = store.state - .getNormalOrPrivateTabs(browsingModeManager.mode.isPrivate) - .size - - var indexOfCollection = tabsSize + NON_TAB_ITEM_NUM - changedCollection?.let { changedCollection -> - requireComponents.core.tabCollectionStorage.cachedTabCollections - .filterIndexed { index, tabCollection -> - if (tabCollection.id == changedCollection.id) { - indexOfCollection = tabsSize + NON_TAB_ITEM_NUM + index - return@filterIndexed true - } - false - } - } + val indexOfCollection = + NON_COLLECTION_ITEM_NUM + findIndexOfSpecificCollection(collectionIdToSelect) + val lastVisiblePosition = (recyclerView.layoutManager as? LinearLayoutManager)?.findLastCompletelyVisibleItemPosition() ?: 0 + if (lastVisiblePosition < indexOfCollection) { val onScrollListener = object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged( @@ -881,6 +873,7 @@ class HomeFragment : Fragment() { ) { super.onScrollStateChanged(recyclerView, newState) if (newState == SCROLL_STATE_IDLE) { + appBarLayout?.setExpanded(false) animateCollection(indexOfCollection) recyclerView.removeOnScrollListener(this) } @@ -889,12 +882,34 @@ class HomeFragment : Fragment() { recyclerView.addOnScrollListener(onScrollListener) recyclerView.smoothScrollToPosition(indexOfCollection) } else { + appBarLayout?.setExpanded(false) animateCollection(indexOfCollection) } } } } + /** + * Returns index of the collection with the specified id. + * */ + private fun findIndexOfSpecificCollection( + changedCollectionId: Long + ): Int { + var result = 0 + requireComponents.core.tabCollectionStorage.cachedTabCollections + .filterIndexed { index, tabCollection -> + if (tabCollection.id == changedCollectionId) { + result = index + return@filterIndexed true + } + false + } + return result + } + + /** + * Will highlight the border of the collection with the specified index. + * */ private fun animateCollection(indexOfCollection: Int) { viewLifecycleOwner.lifecycleScope.launch { val viewHolder = @@ -921,24 +936,6 @@ class HomeFragment : Fragment() { border?.animate()?.alpha(1.0F)?.setStartDelay(ANIM_ON_SCREEN_DELAY) ?.setDuration(FADE_ANIM_DURATION) ?.setListener(listener)?.start() - }.invokeOnCompletion { - showSavedSnackbar() - } - } - - private fun showSavedSnackbar() { - viewLifecycleOwner.lifecycleScope.launch { - delay(ANIM_SNACKBAR_DELAY) - view?.let { view -> - FenixSnackbar.make( - view = view, - duration = Snackbar.LENGTH_LONG, - isDisplayedWithBrowserToolbar = false - ) - .setText(view.context.getString(R.string.create_collection_tabs_saved_new_collection)) - .setAnchorView(snackbarAnchorView) - .show() - } } } @@ -983,13 +980,18 @@ class HomeFragment : Fragment() { const val ALL_PRIVATE_TABS = "all_private" private const val FOCUS_ON_ADDRESS_BAR = "focusOnAddressBar" + private const val FOCUS_ON_COLLECTION = "focusOnCollection" private const val ANIMATION_DELAY = 100L - private const val NON_TAB_ITEM_NUM = 3 + /** + * Represents the number of items in [sessionControlView] that are NOT part of + * the list of collections. At the moment these are topSites pager, collections header. + * */ + private const val NON_COLLECTION_ITEM_NUM = 2 + private const val ANIM_SCROLL_DELAY = 100L private const val ANIM_ON_SCREEN_DELAY = 200L private const val FADE_ANIM_DURATION = 150L - private const val ANIM_SNACKBAR_DELAY = 100L private const val CFR_WIDTH_DIVIDER = 1.7 private const val CFR_Y_OFFSET = -20 } diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt index 7ed8f6a62..88317629c 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt @@ -86,12 +86,15 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler else null private val collectionStorageObserver = object : TabCollectionStorage.Observer { - override fun onCollectionCreated(title: String, sessions: List) { - showCollectionSnackbar(sessions.size, true) + override fun onCollectionCreated(title: String, sessions: List, id: Long?) { + showCollectionSnackbar(sessions.size, true, collectionToSelect = id) } override fun onTabsAdded(tabCollection: TabCollection, sessions: List) { - showCollectionSnackbar(sessions.size) + showCollectionSnackbar( + sessions.size, + collectionToSelect = tabCollection.id + ) } } @@ -354,7 +357,11 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler requireComponents.core.tabCollectionStorage.register(collectionStorageObserver, this) } - private fun showCollectionSnackbar(tabSize: Int, isNewCollection: Boolean = false) { + private fun showCollectionSnackbar( + tabSize: Int, + isNewCollection: Boolean = false, + collectionToSelect: Long? + ) { view.let { val messageStringRes = when { isNewCollection -> { @@ -378,7 +385,10 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), UserInteractionHandler .setAction(requireContext().getString(R.string.create_collection_view)) { dismissAllowingStateLoss() findNavController().navigate( - TabTrayDialogFragmentDirections.actionGlobalHome(focusOnAddressBar = false) + TabTrayDialogFragmentDirections.actionGlobalHome( + focusOnAddressBar = false, + focusOnCollection = collectionToSelect ?: -1L + ) ) } diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 1ca254248..b58f35ec1 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -138,6 +138,10 @@ android:name="focusOnAddressBar" android:defaultValue="false" app:argType="boolean" /> + Date: Mon, 16 Nov 2020 15:45:48 -0600 Subject: [PATCH 069/202] Closes #8791: Use A-C tab counter and upgrades to A-C 69.0.20201203202830 Upgrades to A-C 69.0.20201203202830 and addresses breaking changes: - Upgrades androidx workmanager to 2.4.0 in line with A-C. - RecordingDevicesNotificationFeature was removed - SearchUseCases accept parent session ID instead of session itself --- app/build.gradle | 1 + .../fenix/browser/BaseBrowserFragment.kt | 2 +- .../org/mozilla/fenix/browser/TabPreview.kt | 2 +- .../java/org/mozilla/fenix/components/Core.kt | 14 +- .../components/toolbar/BrowserInteractor.kt | 2 + .../toolbar/BrowserToolbarController.kt | 25 +- .../components/toolbar/BrowserToolbarView.kt | 5 +- .../components/toolbar/FenixTabCounterMenu.kt | 60 ++++ .../fenix/components/toolbar/TabCounter.kt | 271 ------------------ .../components/toolbar/TabCounterMenu.kt | 121 -------- .../toolbar/TabCounterToolbarButton.kt | 85 ------ .../components/toolbar/ToolbarIntegration.kt | 37 ++- .../org/mozilla/fenix/home/HomeFragment.kt | 62 ++-- .../fenix/search/awesomebar/AwesomeBarView.kt | 4 +- .../org/mozilla/fenix/tabtray/TabTrayView.kt | 10 +- app/src/main/res/layout/fragment_home.xml | 2 +- .../res/layout/mozac_ui_tabcounter_layout.xml | 37 --- app/src/main/res/layout/tab_preview.xml | 2 +- app/src/main/res/values-night/colors.xml | 4 + app/src/main/res/values/colors.xml | 4 + app/src/main/res/values/strings.xml | 2 - .../toolbar/BrowserInteractorTest.kt | 1 + .../DefaultBrowserToolbarControllerTest.kt | 5 +- .../components/toolbar/TabCounterMenuTest.kt | 37 +-- buildSrc/src/main/java/AndroidComponents.kt | 2 +- buildSrc/src/main/java/Dependencies.kt | 3 +- 26 files changed, 197 insertions(+), 603 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/components/toolbar/FenixTabCounterMenu.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounter.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterMenu.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterToolbarButton.kt delete mode 100644 app/src/main/res/layout/mozac_ui_tabcounter_layout.xml diff --git a/app/build.gradle b/app/build.gradle index b149f2255..b1524e5ca 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -487,6 +487,7 @@ dependencies { implementation Deps.mozilla_ui_icons implementation Deps.mozilla_lib_publicsuffixlist implementation Deps.mozilla_ui_widgets + implementation Deps.mozilla_ui_tabcounter implementation Deps.mozilla_lib_crash implementation Deps.mozilla_lib_push_firebase 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 1fd65872d..2c53e2bb6 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -572,7 +572,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, useCase.invoke(request.query) requireActivity().startActivity(openInFenixIntent) } else { - useCase.invoke(request.query, parentSession = parentSession) + useCase.invoke(request.query, parentSessionId = parentSession?.id) } }, owner = this, diff --git a/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt b/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt index c72182352..346cce7e5 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt @@ -13,8 +13,8 @@ import android.widget.FrameLayout import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.doOnNextLayout import androidx.core.view.updateLayoutParams -import kotlinx.android.synthetic.main.mozac_ui_tabcounter_layout.view.* import kotlinx.android.synthetic.main.tab_preview.view.* +import kotlinx.android.synthetic.main.tabs_tray_tab_counter.view.* import mozilla.components.browser.thumbnails.loader.ThumbnailLoader import mozilla.components.concept.base.images.ImageLoadRequest import org.mozilla.fenix.R 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 a6011c217..405bf5c8d 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -23,7 +23,6 @@ import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.engine.EngineMiddleware import mozilla.components.browser.session.storage.SessionStorage import mozilla.components.browser.session.undo.UndoMiddleware -import mozilla.components.browser.state.action.RecentlyClosedAction import mozilla.components.browser.state.action.RestoreCompleteAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.store.BrowserStore @@ -40,8 +39,8 @@ 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.media.middleware.RecordingDevicesMiddleware import mozilla.components.feature.pwa.ManifestStorage import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.readerview.ReaderViewMiddleware @@ -198,11 +197,10 @@ class Core( context, additionalBundledSearchEngineIds = listOf("reddit", "youtube"), migration = SearchMigration(context) - ) + ), + RecordingDevicesMiddleware(context) ) + EngineMiddleware.create(engine, ::findSessionById) - ).also { - it.dispatch(RecentlyClosedAction.InitializeRecentlyClosedState) - } + ) } private fun lookupSessionManager(): SessionManager { @@ -242,10 +240,6 @@ class Core( // Install the "cookies" WebExtension and tracks user interaction with SERPs. searchTelemetry.install(engine, store) - // Show an ongoing notification when recording devices (camera, microphone) are used by web content - RecordingDevicesNotificationFeature(context, sessionManager) - .enable() - // Restore the previous state. GlobalScope.launch(Dispatchers.Main) { withContext(Dispatchers.IO) { diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserInteractor.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserInteractor.kt index ba8f71b35..03d7971c8 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserInteractor.kt @@ -4,6 +4,8 @@ package org.mozilla.fenix.components.toolbar +import mozilla.components.ui.tabcounter.TabCounterMenu + open class BrowserInteractor( private val browserToolbarController: BrowserToolbarController, private val menuController: BrowserToolbarMenuController diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt index 588a3a860..b52b849e7 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt @@ -9,10 +9,12 @@ import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager import mozilla.components.concept.engine.EngineView import mozilla.components.support.ktx.kotlin.isUrl +import mozilla.components.ui.tabcounter.TabCounterMenu import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions import org.mozilla.fenix.browser.BrowserFragmentDirections +import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.readermode.ReaderModeController import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.MetricController @@ -104,6 +106,9 @@ class DefaultBrowserToolbarController( override fun handleTabCounterItemInteraction(item: TabCounterMenu.Item) { when (item) { is TabCounterMenu.Item.CloseTab -> { + metrics.track( + Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.CLOSE_TAB) + ) sessionManager.selectedSession?.let { // When closing the last tab we must show the undo snackbar in the home fragment if (sessionManager.sessionsOfType(it.private).count() == 1) { @@ -120,8 +125,24 @@ class DefaultBrowserToolbarController( } } is TabCounterMenu.Item.NewTab -> { - activity.browsingModeManager.mode = item.mode - navController.navigate(BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true)) + metrics.track( + Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB) + ) + activity.browsingModeManager.mode = BrowsingMode.Normal + navController.navigate( + BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true) + ) + } + is TabCounterMenu.Item.NewPrivateTab -> { + metrics.track( + Event.TabCounterMenuItemTapped( + Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB + ) + ) + activity.browsingModeManager.mode = BrowsingMode.Private + navController.navigate( + BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true) + ) } } } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt index d79a11884..882dc3df5 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt @@ -29,6 +29,7 @@ import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.browser.toolbar.behavior.BrowserToolbarBottomBehavior import mozilla.components.browser.toolbar.display.DisplayToolbar import mozilla.components.support.utils.URLStringUtils +import mozilla.components.ui.tabcounter.TabCounterMenu import org.mozilla.fenix.R import org.mozilla.fenix.customtabs.CustomTabToolbarIntegration import org.mozilla.fenix.customtabs.CustomTabToolbarMenu @@ -267,10 +268,6 @@ class BrowserToolbarView( } } - companion object { - private const val TOOLBAR_ELEVATION = 16 - } - @Suppress("ComplexCondition") private fun ToolbarMenu.Item.performHapticIfNeeded(view: View) { if (this is ToolbarMenu.Item.Reload && this.bypassCache || diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/FenixTabCounterMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/FenixTabCounterMenu.kt new file mode 100644 index 000000000..224f19340 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/FenixTabCounterMenu.kt @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.components.toolbar + +import android.content.Context +import androidx.annotation.VisibleForTesting +import mozilla.components.concept.menu.candidate.DividerMenuCandidate +import mozilla.components.concept.menu.candidate.MenuCandidate +import mozilla.components.ui.tabcounter.TabCounterMenu +import org.mozilla.fenix.browser.browsingmode.BrowsingMode + +class FenixTabCounterMenu( + context: Context, + onItemTapped: (Item) -> Unit, + iconColor: Int? = null +) : TabCounterMenu(context, onItemTapped, iconColor) { + + @VisibleForTesting + internal fun menuItems(showOnly: BrowsingMode): List { + return when (showOnly) { + BrowsingMode.Normal -> listOf(newTabItem) + BrowsingMode.Private -> listOf(newPrivateTabItem) + } + } + + @VisibleForTesting + internal fun menuItems(toolbarPosition: ToolbarPosition): List { + val items = listOf( + newTabItem, + newPrivateTabItem, + DividerMenuCandidate(), + closeTabItem + ) + + return when (toolbarPosition) { + ToolbarPosition.BOTTOM -> items.reversed() + ToolbarPosition.TOP -> items + } + } + + /** + * Update the displayed menu items. + * @param showOnly Show only the new tab item corresponding to the given [BrowsingMode]. + */ + fun updateMenu(showOnly: BrowsingMode) { + val items = menuItems(showOnly) + + menuController.submitList(items) + } + + /** + * Update the displayed menu items. + * @param toolbarPosition Return a list that is ordered based on the given [ToolbarPosition]. + */ + fun updateMenu(toolbarPosition: ToolbarPosition) { + menuController.submitList(menuItems(toolbarPosition)) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounter.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounter.kt deleted file mode 100644 index 1585fb7c3..000000000 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounter.kt +++ /dev/null @@ -1,271 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.components.toolbar - -import android.animation.AnimatorSet -import android.animation.ObjectAnimator -import android.content.Context -import android.graphics.Typeface -import android.util.AttributeSet -import android.util.TypedValue -import android.view.LayoutInflater -import android.widget.RelativeLayout -import androidx.core.view.updatePadding -import kotlinx.android.synthetic.main.mozac_ui_tabcounter_layout.view.* -import org.mozilla.fenix.R -import java.text.NumberFormat - -class TabCounter @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyle: Int = 0 -) : RelativeLayout(context, attrs, defStyle) { - - private val animationSet: AnimatorSet - - init { - val inflater = LayoutInflater.from(context) - inflater.inflate(R.layout.mozac_ui_tabcounter_layout, this) - - // This is needed because without this counter box will be empty. - setCount(INTERNAL_COUNT) - - animationSet = createAnimatorSet() - } - - private fun updateContentDescription(count: Int) { - counter_root.contentDescription = if (count == 1) { - context?.getString(R.string.open_tab_tray_single) - } else { - String.format(context.getString(R.string.open_tab_tray_plural), count.toString()) - } - } - - fun setCountWithAnimation(count: Int) { - setCount(count) - - // No need to animate on these cases. - when { - INTERNAL_COUNT == 0 -> return // Initial state. - INTERNAL_COUNT == count -> return // There isn't any tab added or removed. - INTERNAL_COUNT > MAX_VISIBLE_TABS -> return // There are still over MAX_VISIBLE_TABS tabs open. - } - - // Cancel previous animations if necessary. - if (animationSet.isRunning) { - animationSet.cancel() - } - // Trigger animations. - animationSet.start() - } - - fun setCount(count: Int) { - updateContentDescription(count) - adjustTextSize(count) - counter_text.text = formatForDisplay(count) - INTERNAL_COUNT = count - } - - private fun createAnimatorSet(): AnimatorSet { - val animatorSet = AnimatorSet() - createBoxAnimatorSet(animatorSet) - createTextAnimatorSet(animatorSet) - return animatorSet - } - - private fun createBoxAnimatorSet(animatorSet: AnimatorSet) { - // The first animator, fadeout in 33 ms (49~51, 2 frames). - val fadeOut = ObjectAnimator.ofFloat( - counter_box, "alpha", - ANIM_BOX_FADEOUT_FROM, ANIM_BOX_FADEOUT_TO - ).setDuration(ANIM_BOX_FADEOUT_DURATION) - - // Move up on y-axis, from 0.0 to -5.3 in 50ms, with fadeOut (49~52, 3 frames). - val moveUp1 = ObjectAnimator.ofFloat( - counter_box, "translationY", - ANIM_BOX_MOVEUP1_TO, ANIM_BOX_MOVEUP1_FROM - ).setDuration(ANIM_BOX_MOVEUP1_DURATION) - - // Move down on y-axis, from -5.3 to -1.0 in 116ms, after moveUp1 (52~59, 7 frames). - val moveDown2 = ObjectAnimator.ofFloat( - counter_box, "translationY", - ANIM_BOX_MOVEDOWN2_FROM, ANIM_BOX_MOVEDOWN2_TO - ).setDuration(ANIM_BOX_MOVEDOWN2_DURATION) - - // FadeIn in 66ms, with moveDown2 (52~56, 4 frames). - val fadeIn = ObjectAnimator.ofFloat( - counter_box, "alpha", - ANIM_BOX_FADEIN_FROM, ANIM_BOX_FADEIN_TO - ).setDuration(ANIM_BOX_FADEIN_DURATION) - - // Move down on y-axis, from -1.0 to 2.7 in 116ms, after moveDown2 (59~66, 7 frames). - val moveDown3 = ObjectAnimator.ofFloat( - counter_box, "translationY", - ANIM_BOX_MOVEDOWN3_FROM, ANIM_BOX_MOVEDOWN3_TO - ).setDuration(ANIM_BOX_MOVEDOWN3_DURATION) - - // Move up on y-axis, from 2.7 to 0 in 133ms, after moveDown3 (66~74, 8 frames). - val moveUp4 = ObjectAnimator.ofFloat( - counter_box, "translationY", - ANIM_BOX_MOVEDOWN4_FROM, ANIM_BOX_MOVEDOWN4_TO - ).setDuration(ANIM_BOX_MOVEDOWN4_DURATION) - - // Scale up height from 2% to 105% in 100ms, after moveUp1 and delay 16ms (53~59, 6 frames). - val scaleUp1 = ObjectAnimator.ofFloat( - counter_box, "scaleY", - ANIM_BOX_SCALEUP1_FROM, ANIM_BOX_SCALEUP1_TO - ).setDuration(ANIM_BOX_SCALEUP1_DURATION) - scaleUp1.startDelay = ANIM_BOX_SCALEUP1_DELAY // delay 1 frame after moveUp1 - - // Scale down height from 105% to 99% in 116ms, after scaleUp1 (59~66, 7 frames). - val scaleDown2 = ObjectAnimator.ofFloat( - counter_box, "scaleY", - ANIM_BOX_SCALEDOWN2_FROM, ANIM_BOX_SCALEDOWN2_TO - ).setDuration(ANIM_BOX_SCALEDOWN2_DURATION) - - // Scale up height from 99% to 100% in 133ms, after scaleDown2 (66~74, 8 frames). - val scaleUp3 = ObjectAnimator.ofFloat( - counter_box, "scaleY", - ANIM_BOX_SCALEUP3_FROM, ANIM_BOX_SCALEUP3_TO - ).setDuration(ANIM_BOX_SCALEUP3_DURATION) - - animatorSet.play(fadeOut).with(moveUp1) - animatorSet.play(moveUp1).before(moveDown2) - animatorSet.play(moveDown2).with(fadeIn) - animatorSet.play(moveDown2).before(moveDown3) - animatorSet.play(moveDown3).before(moveUp4) - - animatorSet.play(moveUp1).before(scaleUp1) - animatorSet.play(scaleUp1).before(scaleDown2) - animatorSet.play(scaleDown2).before(scaleUp3) - } - - private fun createTextAnimatorSet(animatorSet: AnimatorSet) { - val firstAnimator = animatorSet.childAnimations[0] - - // Fadeout in 100ms, with firstAnimator (49~51, 2 frames). - val fadeOut = ObjectAnimator.ofFloat( - counter_text, "alpha", - ANIM_TEXT_FADEOUT_FROM, ANIM_TEXT_FADEOUT_TO - ).setDuration(ANIM_TEXT_FADEOUT_DURATION) - - // FadeIn in 66 ms, after fadeOut with delay 96ms (57~61, 4 frames). - val fadeIn = ObjectAnimator.ofFloat( - counter_text, "alpha", - ANIM_TEXT_FADEIN_FROM, ANIM_TEXT_FADEIN_TO - ).setDuration(ANIM_TEXT_FADEIN_DURATION) - fadeIn.startDelay = (ANIM_TEXT_FADEIN_DELAY) // delay 6 frames after fadeOut - - // Move down on y-axis, from 0 to 4.4 in 66ms, with fadeIn (57~61, 4 frames). - val moveDown = ObjectAnimator.ofFloat( - counter_text, "translationY", - ANIM_TEXT_MOVEDOWN_FROM, ANIM_TEXT_MOVEDOWN_TO - ).setDuration(ANIM_TEXT_MOVEDOWN_DURATION) - moveDown.startDelay = (ANIM_TEXT_MOVEDOWN_DELAY) // delay 6 frames after fadeOut - - // Move up on y-axis, from 0 to 4.4 in 66ms, after moveDown (61~69, 8 frames). - val moveUp = ObjectAnimator.ofFloat( - counter_text, "translationY", - ANIM_TEXT_MOVEUP_FROM, ANIM_TEXT_MOVEUP_TO - ).setDuration(ANIM_TEXT_MOVEUP_DURATION) - - animatorSet.play(firstAnimator).with(fadeOut) - animatorSet.play(fadeOut).before(fadeIn) - animatorSet.play(fadeIn).with(moveDown) - animatorSet.play(moveDown).before(moveUp) - } - - private fun formatForDisplay(count: Int): String { - return if (count > MAX_VISIBLE_TABS) { - counter_text.updatePadding(bottom = INFINITE_CHAR_PADDING_BOTTOM) - SO_MANY_TABS_OPEN - } else NumberFormat.getInstance().format(count.toLong()) - } - - private fun adjustTextSize(newCount: Int) { - val newRatio = if (newCount in TWO_DIGITS_TAB_COUNT_THRESHOLD..MAX_VISIBLE_TABS) { - TWO_DIGITS_SIZE_RATIO - } else { - ONE_DIGIT_SIZE_RATIO - } - - val counterBoxWidth = - context.resources.getDimensionPixelSize(R.dimen.tab_counter_box_width_height) - val textSize = newRatio * counterBoxWidth - counter_text.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) - counter_text.setTypeface(null, Typeface.BOLD) - counter_text.setPadding(0, 0, 0, 0) - } - - companion object { - internal var INTERNAL_COUNT = 0 - - internal const val MAX_VISIBLE_TABS = 99 - - internal const val SO_MANY_TABS_OPEN = "∞" - - internal const val INFINITE_CHAR_PADDING_BOTTOM = 6 - - internal const val ONE_DIGIT_SIZE_RATIO = 0.5f - internal const val TWO_DIGITS_SIZE_RATIO = 0.4f - internal const val TWO_DIGITS_TAB_COUNT_THRESHOLD = 10 - - // createBoxAnimatorSet - private const val ANIM_BOX_FADEOUT_FROM = 1.0f - private const val ANIM_BOX_FADEOUT_TO = 0.0f - private const val ANIM_BOX_FADEOUT_DURATION = 33L - - private const val ANIM_BOX_MOVEUP1_FROM = 0.0f - private const val ANIM_BOX_MOVEUP1_TO = -5.3f - private const val ANIM_BOX_MOVEUP1_DURATION = 50L - - private const val ANIM_BOX_MOVEDOWN2_FROM = -5.3f - private const val ANIM_BOX_MOVEDOWN2_TO = -1.0f - private const val ANIM_BOX_MOVEDOWN2_DURATION = 167L - - private const val ANIM_BOX_FADEIN_FROM = 0.01f - private const val ANIM_BOX_FADEIN_TO = 1.0f - private const val ANIM_BOX_FADEIN_DURATION = 66L - private const val ANIM_BOX_MOVEDOWN3_FROM = -1.0f - private const val ANIM_BOX_MOVEDOWN3_TO = 2.7f - private const val ANIM_BOX_MOVEDOWN3_DURATION = 116L - - private const val ANIM_BOX_MOVEDOWN4_FROM = 2.7f - private const val ANIM_BOX_MOVEDOWN4_TO = 0.0f - private const val ANIM_BOX_MOVEDOWN4_DURATION = 133L - - private const val ANIM_BOX_SCALEUP1_FROM = 0.02f - private const val ANIM_BOX_SCALEUP1_TO = 1.05f - private const val ANIM_BOX_SCALEUP1_DURATION = 100L - private const val ANIM_BOX_SCALEUP1_DELAY = 16L - - private const val ANIM_BOX_SCALEDOWN2_FROM = 1.05f - private const val ANIM_BOX_SCALEDOWN2_TO = 0.99f - private const val ANIM_BOX_SCALEDOWN2_DURATION = 116L - - private const val ANIM_BOX_SCALEUP3_FROM = 0.99f - private const val ANIM_BOX_SCALEUP3_TO = 1.00f - private const val ANIM_BOX_SCALEUP3_DURATION = 133L - - // createTextAnimatorSet - private const val ANIM_TEXT_FADEOUT_FROM = 1.0f - private const val ANIM_TEXT_FADEOUT_TO = 0.0f - private const val ANIM_TEXT_FADEOUT_DURATION = 33L - - private const val ANIM_TEXT_FADEIN_FROM = 0.01f - private const val ANIM_TEXT_FADEIN_TO = 1.0f - private const val ANIM_TEXT_FADEIN_DURATION = 66L - private const val ANIM_TEXT_FADEIN_DELAY = 16L * 6 - - private const val ANIM_TEXT_MOVEDOWN_FROM = 0.0f - private const val ANIM_TEXT_MOVEDOWN_TO = 4.4f - private const val ANIM_TEXT_MOVEDOWN_DURATION = 66L - private const val ANIM_TEXT_MOVEDOWN_DELAY = 16L * 6 - - private const val ANIM_TEXT_MOVEUP_FROM = 4.4f - private const val ANIM_TEXT_MOVEUP_TO = 0.0f - private const val ANIM_TEXT_MOVEUP_DURATION = 66L - } -} diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterMenu.kt deleted file mode 100644 index 6d6bebe5a..000000000 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterMenu.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.components.toolbar - -import android.content.Context -import androidx.annotation.VisibleForTesting -import mozilla.components.browser.menu2.BrowserMenuController -import mozilla.components.concept.menu.MenuController -import mozilla.components.concept.menu.candidate.DividerMenuCandidate -import mozilla.components.concept.menu.candidate.DrawableMenuIcon -import mozilla.components.concept.menu.candidate.MenuCandidate -import mozilla.components.concept.menu.candidate.TextMenuCandidate -import mozilla.components.concept.menu.candidate.TextStyle -import mozilla.components.support.ktx.android.content.getColorFromAttr -import org.mozilla.fenix.R -import org.mozilla.fenix.browser.browsingmode.BrowsingMode -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.metrics.MetricController - -class TabCounterMenu( - context: Context, - private val metrics: MetricController, - private val onItemTapped: (Item) -> Unit -) { - - sealed class Item { - object CloseTab : Item() - data class NewTab(val mode: BrowsingMode) : Item() - } - - val menuController: MenuController by lazy { BrowserMenuController() } - - private val newTabItem: TextMenuCandidate - private val newPrivateTabItem: TextMenuCandidate - private val closeTabItem: TextMenuCandidate - - init { - val primaryTextColor = context.getColorFromAttr(R.attr.primaryText) - val textStyle = TextStyle(color = primaryTextColor) - - newTabItem = TextMenuCandidate( - text = context.getString(R.string.browser_menu_new_tab), - start = DrawableMenuIcon( - context, - R.drawable.ic_new, - tint = primaryTextColor - ), - textStyle = textStyle - ) { - metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB)) - onItemTapped(Item.NewTab(BrowsingMode.Normal)) - } - - newPrivateTabItem = TextMenuCandidate( - text = context.getString(R.string.home_screen_shortcut_open_new_private_tab_2), - start = DrawableMenuIcon( - context, - R.drawable.ic_private_browsing, - tint = primaryTextColor - ), - textStyle = textStyle - ) { - metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB)) - onItemTapped(Item.NewTab(BrowsingMode.Private)) - } - - closeTabItem = TextMenuCandidate( - text = context.getString(R.string.close_tab), - start = DrawableMenuIcon( - context, - R.drawable.ic_close, - tint = primaryTextColor - ), - textStyle = textStyle - ) { - metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.CLOSE_TAB)) - onItemTapped(Item.CloseTab) - } - } - - @VisibleForTesting - internal fun menuItems(showOnly: BrowsingMode): List { - return when (showOnly) { - BrowsingMode.Normal -> listOf(newTabItem) - BrowsingMode.Private -> listOf(newPrivateTabItem) - } - } - - @VisibleForTesting - internal fun menuItems(toolbarPosition: ToolbarPosition): List { - val items = listOf( - newTabItem, - newPrivateTabItem, - DividerMenuCandidate(), - closeTabItem - ) - - return when (toolbarPosition) { - ToolbarPosition.BOTTOM -> items.reversed() - ToolbarPosition.TOP -> items - } - } - - /** - * Update the displayed menu items. - * @param showOnly Show only the new tab item corresponding to the given [BrowsingMode]. - */ - fun updateMenu(showOnly: BrowsingMode) { - menuController.submitList(menuItems(showOnly)) - } - - /** - * Update the displayed menu items. - * @param toolbarPosition Return a list that is ordered based on the given [ToolbarPosition]. - */ - fun updateMenu(toolbarPosition: ToolbarPosition) { - menuController.submitList(menuItems(toolbarPosition)) - } -} diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterToolbarButton.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterToolbarButton.kt deleted file mode 100644 index 6123eab56..000000000 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/TabCounterToolbarButton.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.components.toolbar - -import android.view.View -import android.view.ViewGroup -import androidx.lifecycle.LifecycleOwner -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.map -import mozilla.components.browser.state.selector.getNormalOrPrivateTabs -import mozilla.components.browser.state.selector.selectedTab -import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.concept.toolbar.Toolbar -import mozilla.components.lib.state.ext.flowScoped -import mozilla.components.support.ktx.android.content.res.resolveAttribute -import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged -import org.mozilla.fenix.ext.components -import java.lang.ref.WeakReference - -/** - * A [Toolbar.Action] implementation that shows a [TabCounter]. - */ -@OptIn(ExperimentalCoroutinesApi::class) -class TabCounterToolbarButton( - private val lifecycleOwner: LifecycleOwner, - private val onItemTapped: (TabCounterMenu.Item) -> Unit = {}, - private val showTabs: () -> Unit -) : Toolbar.Action { - - private var reference: WeakReference = WeakReference(null) - - override fun createView(parent: ViewGroup): View { - val store = parent.context.components.core.store - val metrics = parent.context.components.analytics.metrics - val settings = parent.context.components.settings - - store.flowScoped(lifecycleOwner) { flow -> - flow.map { state -> state.getNormalOrPrivateTabs(isPrivate(store)).size } - .ifChanged() - .collect { tabs -> updateCount(tabs) } - } - - val menu = TabCounterMenu(parent.context, metrics, onItemTapped) - menu.updateMenu(settings.toolbarPosition) - - val view = TabCounter(parent.context).apply { - reference = WeakReference(this) - setOnClickListener { - showTabs.invoke() - } - - setOnLongClickListener { - menu.menuController.show(anchor = it) - true - } - - addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View?) { - setCount(store.state.getNormalOrPrivateTabs(isPrivate(store)).size) - } - - override fun onViewDetachedFromWindow(v: View?) { /* no-op */ } - }) - } - - // Set selectableItemBackgroundBorderless - view.setBackgroundResource(parent.context.theme.resolveAttribute( - android.R.attr.selectableItemBackgroundBorderless - )) - return view - } - - override fun bind(view: View) = Unit - - private fun updateCount(count: Int) { - reference.get()?.setCountWithAnimation(count) - } - - private fun isPrivate(store: BrowserStore): Boolean { - return store.state.selectedTab?.content?.private ?: false - } -} diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt index 66131222c..ea8f9bb2d 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt @@ -7,13 +7,17 @@ package org.mozilla.fenix.components.toolbar import android.content.Context import android.content.res.Configuration import androidx.appcompat.content.res.AppCompatResources +import androidx.core.content.ContextCompat import androidx.lifecycle.LifecycleOwner import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider +import mozilla.components.browser.state.selector.normalTabs +import mozilla.components.browser.state.selector.privateTabs import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.browser.toolbar.display.DisplayToolbar import mozilla.components.concept.engine.Engine import mozilla.components.concept.storage.HistoryStorage +import mozilla.components.feature.tabs.toolbar.TabCounterToolbarButton import mozilla.components.feature.toolbar.ToolbarAutocompleteFeature import mozilla.components.feature.toolbar.ToolbarFeature import mozilla.components.feature.toolbar.ToolbarPresenter @@ -35,9 +39,10 @@ abstract class ToolbarIntegration( renderStyle: ToolbarFeature.RenderStyle ) : LifecycleAwareFeature { + val store = context.components.core.store private val toolbarPresenter: ToolbarPresenter = ToolbarPresenter( toolbar, - context.components.core.store, + store, sessionId, ToolbarFeature.UrlRenderConfiguration( PublicSuffixList(context), @@ -139,16 +144,40 @@ class DefaultToolbarIntegration( )!! ) - val tabsAction = TabCounterToolbarButton( - lifecycleOwner, + val tabCounterMenu = FenixTabCounterMenu( + context = context, onItemTapped = { interactor.onTabCounterMenuItemTapped(it) }, + iconColor = + if (isPrivate) { + ContextCompat.getColor(context, R.color.primary_text_private_theme) + } else { + null + } + ).also { + it.updateMenu(context.settings().toolbarPosition) + } + + val tabsAction = TabCounterToolbarButton( + lifecycleOwner = lifecycleOwner, showTabs = { toolbar.hideKeyboard() interactor.onTabCounterClicked() - } + }, + store = store, + menu = tabCounterMenu, + privateColor = ContextCompat.getColor(context, R.color.primary_text_private_theme) ) + + val tabCount = if (isPrivate) { + store.state.privateTabs.size + } else { + store.state.normalTabs.size + } + + tabsAction.updateCount(tabCount) + toolbar.addBrowserAction(tabsAction) val engineForSpeculativeConnects = if (!isPrivate) engine else null diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index ec7d2e575..58e3136a5 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -41,9 +41,7 @@ import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE import com.google.android.material.appbar.AppBarLayout import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.fragment_home.privateBrowsingButton -import kotlinx.android.synthetic.main.fragment_home.search_engine_icon -import kotlinx.android.synthetic.main.fragment_home.toolbarLayout +import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.android.synthetic.main.fragment_home.view.bottomBarShadow import kotlinx.android.synthetic.main.fragment_home.view.bottom_bar import kotlinx.android.synthetic.main.fragment_home.view.homeAppBar @@ -81,6 +79,7 @@ import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.ktx.android.content.res.resolveAttribute import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged +import mozilla.components.ui.tabcounter.TabCounterMenu import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R @@ -94,7 +93,7 @@ import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.tips.FenixTipManager import org.mozilla.fenix.components.tips.Tip import org.mozilla.fenix.components.tips.providers.MasterPasswordTipProvider -import org.mozilla.fenix.components.toolbar.TabCounterMenu +import org.mozilla.fenix.components.toolbar.FenixTabCounterMenu import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.hideToolbar @@ -351,23 +350,7 @@ class HomeFragment : Fragment() { observeSearchEngineChanges() createHomeMenu(requireContext(), WeakReference(view.menuButton)) - val tabCounterMenu = TabCounterMenu( - view.context, - metrics = view.context.components.analytics.metrics - ) { - if (it is TabCounterMenu.Item.NewTab) { - (activity as HomeActivity).browsingModeManager.mode = it.mode - } - } - val inverseBrowsingMode = when ((activity as HomeActivity).browsingModeManager.mode) { - BrowsingMode.Normal -> BrowsingMode.Private - BrowsingMode.Private -> BrowsingMode.Normal - } - tabCounterMenu.updateMenu(showOnly = inverseBrowsingMode) - view.tab_button.setOnLongClickListener { - tabCounterMenu.menuController.show(anchor = it) - true - } + createTabCounterMenu(view) view.menuButton.setColorFilter( ContextCompat.getColor( @@ -463,6 +446,40 @@ class HomeFragment : Fragment() { } } + private fun createTabCounterMenu(view: View) { + val browsingModeManager = (activity as HomeActivity).browsingModeManager + val mode = browsingModeManager.mode + + val onItemTapped: (TabCounterMenu.Item) -> Unit = { + if (it is TabCounterMenu.Item.NewTab) { + browsingModeManager.mode = BrowsingMode.Normal + } else if (it is TabCounterMenu.Item.NewPrivateTab) { + browsingModeManager.mode = BrowsingMode.Private + } + } + + val tabCounterMenu = FenixTabCounterMenu( + view.context, + onItemTapped, + iconColor = if (mode == BrowsingMode.Private) { + ContextCompat.getColor(requireContext(), R.color.primary_text_private_theme) + } else { + null + } + ) + + val inverseBrowsingMode = when (mode) { + BrowsingMode.Normal -> BrowsingMode.Private + BrowsingMode.Private -> BrowsingMode.Normal + } + + tabCounterMenu.updateMenu(showOnly = inverseBrowsingMode) + view.tab_button.setOnLongClickListener { + tabCounterMenu.menuController.show(anchor = it) + true + } + } + private fun removeAllTabsAndShowSnackbar(sessionCode: String) { if (sessionCode == ALL_PRIVATE_TABS) { sessionManager.removePrivateSessions() @@ -960,8 +977,11 @@ class HomeFragment : Fragment() { ) } + // TODO use [FenixTabCounterToolbarButton] instead of [TabCounter]: + // https://github.com/mozilla-mobile/fenix/issues/16792 private fun updateTabCounter(browserState: BrowserState) { val tabCount = if (browsingModeManager.mode.isPrivate) { + view?.tab_button?.setColor(ContextCompat.getColor(requireContext(), R.color.primary_text_private_theme)) browserState.privateTabs.size } else { browserState.normalTabs.size diff --git a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt index c24d17b6c..541711b35 100644 --- a/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt +++ b/app/src/main/java/org/mozilla/fenix/search/awesomebar/AwesomeBarView.kt @@ -68,7 +68,7 @@ class AwesomeBarView( override fun invoke( searchTerms: String, searchEngine: mozilla.components.browser.search.SearchEngine?, - parentSession: Session? + parentSessionId: String? ) { interactor.onSearchTermsTapped(searchTerms) } @@ -78,7 +78,7 @@ class AwesomeBarView( override fun invoke( searchTerms: String, searchEngine: mozilla.components.browser.search.SearchEngine?, - parentSession: Session? + parentSessionId: String? ) { interactor.onSearchTermsTapped(searchTerms) } diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt index cb1f411cd..4b26df0c7 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -42,12 +42,12 @@ import mozilla.components.browser.tabstray.TabViewHolder import mozilla.components.feature.syncedtabs.SyncedTabsFeature import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.ktx.android.util.dpToPx +import mozilla.components.ui.tabcounter.TabCounter.Companion.INFINITE_CHAR_PADDING_BOTTOM import org.mozilla.fenix.R import org.mozilla.fenix.browser.InfoBanner import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.toolbar.TabCounter.Companion.INFINITE_CHAR_PADDING_BOTTOM -import org.mozilla.fenix.components.toolbar.TabCounter.Companion.MAX_VISIBLE_TABS -import org.mozilla.fenix.components.toolbar.TabCounter.Companion.SO_MANY_TABS_OPEN +import mozilla.components.ui.tabcounter.TabCounter.Companion.MAX_VISIBLE_TABS +import mozilla.components.ui.tabcounter.TabCounter.Companion.SO_MANY_TABS_OPEN import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.updateAccessibilityCollectionInfo @@ -62,7 +62,7 @@ import mozilla.components.browser.storage.sync.Tab as SyncTab /** * View that contains and configures the BrowserAwesomeBar */ -@Suppress("LongParameterList", "TooManyFunctions", "LargeClass") +@Suppress("LongParameterList", "TooManyFunctions", "LargeClass", "ForbiddenComment") class TabTrayView( private val container: ViewGroup, private val tabsAdapter: FenixTabsAdapter, @@ -697,7 +697,7 @@ class TabTrayView( view.resources.getDimensionPixelSize(R.dimen.tab_tray_top_offset) } - behavior.setExpandedOffset(topOffset) + behavior.expandedOffset = topOffset } fun dismissMenu() { diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 23d861686..ebfe8e6cb 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -145,7 +145,7 @@ app:barrierDirection="start" app:constraint_referenced_ids="tab_button" /> - - - - - - - - - - - - diff --git a/app/src/main/res/layout/tab_preview.xml b/app/src/main/res/layout/tab_preview.xml index e7da4ce04..6d4cc1464 100644 --- a/app/src/main/res/layout/tab_preview.xml +++ b/app/src/main/res/layout/tab_preview.xml @@ -31,7 +31,7 @@ android:layout_weight="1" android:background="@drawable/home_search_background" /> - @color/destructive_dark_theme + + + @color/primary_text_dark_theme + @color/primary_text_dark_theme diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 521ef6a15..845bb44c4 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -413,4 +413,8 @@ @color/destructive_light_theme + + + @color/primary_text_light_theme + @color/primary_text_light_theme diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2c1e92a16..aafc1dfb9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -152,8 +152,6 @@ Find in page Private tab - - New tab Save to collection diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserInteractorTest.kt index 29b955e6d..5f2135aa5 100644 --- a/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserInteractorTest.kt @@ -4,6 +4,7 @@ import io.mockk.MockKAnnotations import io.mockk.impl.annotations.RelaxedMockK import io.mockk.mockk import io.mockk.verify +import mozilla.components.ui.tabcounter.TabCounterMenu import org.junit.Before import org.junit.Test diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt index a5f543f2b..ac9393d7f 100644 --- a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt @@ -22,6 +22,7 @@ import mozilla.components.concept.engine.EngineView import mozilla.components.feature.search.SearchUseCases import mozilla.components.feature.session.SessionUseCases import mozilla.components.feature.top.sites.TopSitesUseCases +import mozilla.components.ui.tabcounter.TabCounterMenu import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -240,7 +241,7 @@ class DefaultBrowserToolbarControllerTest { @Test fun handleToolbarNewTabPress() { val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Private) - val item = TabCounterMenu.Item.NewTab(BrowsingMode.Normal) + val item = TabCounterMenu.Item.NewTab every { activity.browsingModeManager } returns browsingModeManager every { navController.navigate(BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true)) } just Runs @@ -254,7 +255,7 @@ class DefaultBrowserToolbarControllerTest { @Test fun handleToolbarNewPrivateTabPress() { val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Normal) - val item = TabCounterMenu.Item.NewTab(BrowsingMode.Private) + val item = TabCounterMenu.Item.NewPrivateTab every { activity.browsingModeManager } returns browsingModeManager every { navController.navigate(BrowserFragmentDirections.actionGlobalHome(focusOnAddressBar = true)) } just Runs diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/TabCounterMenuTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/TabCounterMenuTest.kt index a85c533b2..a9316f266 100644 --- a/app/src/test/java/org/mozilla/fenix/components/toolbar/TabCounterMenuTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/TabCounterMenuTest.kt @@ -7,50 +7,31 @@ package org.mozilla.fenix.components.toolbar import android.content.Context import androidx.appcompat.view.ContextThemeWrapper import io.mockk.mockk -import io.mockk.verifyAll +import io.mockk.verify import mozilla.components.concept.menu.candidate.DividerMenuCandidate -import mozilla.components.concept.menu.candidate.DrawableMenuIcon import mozilla.components.concept.menu.candidate.TextMenuCandidate import mozilla.components.support.test.robolectric.testContext +import mozilla.components.ui.tabcounter.TabCounterMenu import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode -import org.mozilla.fenix.components.metrics.Event -import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.helpers.FenixRobolectricTestRunner @RunWith(FenixRobolectricTestRunner::class) class TabCounterMenuTest { private lateinit var context: Context - private lateinit var metrics: MetricController private lateinit var onItemTapped: (TabCounterMenu.Item) -> Unit - private lateinit var menu: TabCounterMenu + private lateinit var menu: FenixTabCounterMenu @Before fun setup() { context = ContextThemeWrapper(testContext, R.style.NormalTheme) - metrics = mockk(relaxed = true) onItemTapped = mockk(relaxed = true) - menu = TabCounterMenu(context, metrics, onItemTapped) - } - - @Test - fun `all items use primary text color styling`() { - val items = menu.menuItems(ToolbarPosition.BOTTOM) - assertEquals(4, items.size) - - val textItems = items.mapNotNull { it as? TextMenuCandidate } - assertEquals(3, textItems.size) - - val primaryTextColor = context.getColor(R.color.primary_text_normal_theme) - for (item in textItems) { - assertEquals(primaryTextColor, item.textStyle.color) - assertEquals(primaryTextColor, (item.start as DrawableMenuIcon).tint) - } + menu = FenixTabCounterMenu(context, onItemTapped) } @Test @@ -62,10 +43,7 @@ class TabCounterMenuTest { assertEquals("New tab", item.text) item.onClick() - verifyAll { - metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB)) - onItemTapped(TabCounterMenu.Item.NewTab(BrowsingMode.Normal)) - } + verify { onItemTapped(TabCounterMenu.Item.NewTab) } } @Test @@ -77,10 +55,7 @@ class TabCounterMenuTest { assertEquals("New private tab", item.text) item.onClick() - verifyAll { - metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB)) - onItemTapped(TabCounterMenu.Item.NewTab(BrowsingMode.Private)) - } + verify { onItemTapped(TabCounterMenu.Item.NewPrivateTab) } } @Test diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 432a88912..c706cedb7 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "68.0.20201201190117" + const val VERSION = "69.0.20201203202830" } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 6749a0365..8d834b226 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -32,7 +32,7 @@ object Versions { const val androidx_core = "1.3.2" const val androidx_paging = "2.1.0" const val androidx_transition = "1.3.0" - const val androidx_work = "2.2.0" + const val androidx_work = "2.4.0" const val google_material = "1.2.1" const val mozilla_android_components = AndroidComponents.VERSION @@ -139,6 +139,7 @@ object Deps { const val mozilla_ui_colors = "org.mozilla.components:ui-colors:${Versions.mozilla_android_components}" const val mozilla_ui_icons = "org.mozilla.components:ui-icons:${Versions.mozilla_android_components}" const val mozilla_ui_widgets = "org.mozilla.components:ui-widgets:${Versions.mozilla_android_components}" + const val mozilla_ui_tabcounter = "org.mozilla.components:ui-tabcounter:${Versions.mozilla_android_components}" const val mozilla_lib_crash = "org.mozilla.components:lib-crash:${Versions.mozilla_android_components}" const val mozilla_lib_push_firebase = "org.mozilla.components:lib-push-firebase:${Versions.mozilla_android_components}" From c63a14af147f0f7a1a89f3cd23d36e8246a55145 Mon Sep 17 00:00:00 2001 From: Mozilla L10n Automation Bot Date: Fri, 4 Dec 2020 00:07:24 +0000 Subject: [PATCH 070/202] Import l10n. --- app/src/main/res/values-eo/strings.xml | 142 +++++++++++++++++++++++-- app/src/main/res/values-tg/strings.xml | 6 ++ 2 files changed, 138 insertions(+), 10 deletions(-) diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 1467bb878..7ad3ef240 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -19,6 +19,10 @@ Viaj privataj langetoj aperos ĉi tie. + + Baidu + + JD 1 malfermita langeto. Tuŝetu por ŝanĝi langeton. @@ -68,14 +72,6 @@ Ne, dankon - - - Atingu Firefox pli rapide. Aldonu kromprogrameton al via hejmekrano. - - Aldoni komprogrameton - - Ne nun - Vi povas igi Firefox aŭtomate malfermi ligilojn en programoj. @@ -98,6 +94,13 @@ Ignori + + Por ŝanĝi la aranĝon de malfermitaj langetoj iru al agordoj kaj elektu kradon en la vido de langetoj. + + Iri al agordoj + + Ignori + Nova langeto @@ -142,6 +145,8 @@ Instali Spegulitaj langetoj + + Respeguli Serĉi en paĝo @@ -211,6 +216,8 @@ Pli da informo + + Malfermi novan langeton de Firefox Serĉi @@ -266,6 +273,8 @@ Malfermi ligilojn en privata langeto Permesi ekrankopiojn en privata retumo + + Se tio estas permesata, ankaŭ privataj langetoj estos videblaj kiam pluraj programoj estas malfermitaj Aldoni privatan retuman ŝparvojon @@ -339,6 +348,20 @@ Sciigoj + + + Personecigita kolekto de aldonaĵoj + + Akcepti + + Nuligi + + Nomo de kolekto + + Posedanto de kolekto (identigilo de uzanto) + + La kolekto de aldonaĵoj estis ŝanĝita. La programo nun finiĝos por apliki la ŝanĝojn… + Speguli nun @@ -418,6 +441,10 @@ Merkatikaj datumoj Divido de datumoj pri la trajtoj, kiujn vi uzas en %1$s kun Leanplum, nia poŝaparata merkatika provizanto. + + Studoj + + Permesi al Mozilla instali kaj fari studojn Eksperimentoj @@ -467,6 +494,16 @@ Uzi la etoson de la aparato + + + Tiri por refreŝigi + + Rulumi por kaŝi la ilaron + + Ŝovi la ilaron flanken por ŝanĝi langetojn + + Ŝovi la ilaron supren por malfermi langetojn + Seancoj @@ -514,6 +551,14 @@ Estas neniu ĵuse fermita langeto ĉi tie + + Langetoj + + Vido de langetoj + + Listo + + Krado Fermi langetojn @@ -525,6 +570,15 @@ Post monato + + Fermi mane + + Fermi post tago + + Fermi post semajno + + Fermi post monato + Malfermitaj langetoj @@ -542,6 +596,8 @@ Malfermi langetojn Konservi en kolekto + + Elekti Dividi ĉiujn langetojn @@ -556,8 +612,18 @@ Iri al la komenco Ŝanĝi la langetan reĝimon + + Legosigno + + Fermi + + Dividi elektitajn langetojn + + Dividi menuon de langetoj Forigi langeton el kolekto + + Elekti langetojn Fermi langeton @@ -593,8 +659,12 @@ Malfermi langetojn - - Forigi + + Nomo de kolekto + + Renomi + + Forigi Forigi el historio @@ -631,6 +701,10 @@ Forigi %1$d elementojn + + Hodiaŭ + + Hieraŭ Lastaj 24 horoj @@ -643,12 +717,25 @@ Neniu historio estas ĉi tie + + Forigi elŝutojn + + Ĉu vi certe volas forigi viajn elŝutojn? + + Elŝutoj forigitaj Neniu elŝuto ĉi tie %1$d elektitaj + + + Malfermi + + Forigi + + Bedaŭrinde %1$s ne povas ŝargi tiun paĝon. @@ -771,6 +858,8 @@ Sciigoj + + Konstanta konservejo Demandi antaŭ ol permesi @@ -913,6 +1002,12 @@ Langeto fermita Langetoj fermitaj + + Langetoj fermita! + + Legosignoj fermitaj! + + Vidi Aldonita al la plej vizititaj! @@ -1062,6 +1157,10 @@ Komenci speguli legosignojn, pasvortojn kaj aliajn aferojn per via konto de Firefox. Pli da informo + + Vi komencis seancon kiel %s en alia retumilo Firefox en tiu ĉi aparato. Ĉu vi ŝatus komenci seancon per tiu konto? Jes, komenci seancon @@ -1152,6 +1251,8 @@ en la reto kaj kion vi dividas kun ni. Komencu seancon per via filmilo Anstataŭe uzi retpoŝton + + Kreu ĝin por speguli Firefox inter aparatoj.]]> Firefox ne plu spegulos vian konton, sed ĝi ne forigos iun ajn el viaj retumaj datumoj en tiu ĉi aparato. @@ -1260,6 +1361,12 @@ en la reto kaj kion vi dividas kun ni. The first parameter is the app name --> %s | Malfermitkodaj bibliotekoj + + Redirektaj spuriloj + + + Tio ĉi forigas kuketojn kreitaj de konataj spuradaj retejoj. + Helpo @@ -1305,6 +1412,9 @@ en la reto kaj kion vi dividas kun ni. Nomo de ŝparvojo + + Vi povas facile aldoni tiun ĉi retejon al la hejmpaĝo de via aparato, por havi tujan aliron kaj retumi pli rapide per kvazaŭprograma sperto. + Legitimiloj kaj pasvortoj @@ -1564,6 +1674,15 @@ en la reto kaj kion vi dividas kun ni. Montri pli vizititajn retejojn + + Nomo + + Nomo de ofta retejo + + Akcepti + + Nuligi + Forigi @@ -1571,6 +1690,9 @@ en la reto kaj kion vi dividas kun ni. The first parameter is the name of the app (e.g. Firefox Preview) --> Eltiru la maksimumon el %s. + + Alklaku por havi pli da informo + Kolekti la aferojn kiu gravas por vi diff --git a/app/src/main/res/values-tg/strings.xml b/app/src/main/res/values-tg/strings.xml index 18a7a9444..5b060ee41 100644 --- a/app/src/main/res/values-tg/strings.xml +++ b/app/src/main/res/values-tg/strings.xml @@ -440,6 +440,10 @@ Маълумоти маркетингӣ Маълумот дар бораи хусусиятҳое, ки шумо дар %1$s истифода мебаред бо Leanplum, яъне фурӯшандаи маркетинги мобилии мо, мубодила карда шавад + + Таҳқиқҳо + + Ба Mozilla иҷозат медиҳад, ки таҳқиқҳоро насб ва иҷро кунад Таҷрибаҳо @@ -591,6 +595,8 @@ Варақаҳои кушодашуда Нигоҳ доштан дар маҷмӯа + + Интихоб кардан Мубодила кардани ҳамаи варақаҳо From 9420febe595343851985b042e02359f363140bb8 Mon Sep 17 00:00:00 2001 From: Johan Lorenzo Date: Fri, 4 Dec 2020 11:36:12 +0100 Subject: [PATCH 071/202] Create new android-test-beta build (#16814) --- taskcluster/ci/build/kind.yml | 36 ++++++++++++++++--- taskcluster/ci/signing/kind.yml | 12 +++---- .../fenix_taskgraph/transforms/build.py | 18 ++++++++++ 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/taskcluster/ci/build/kind.yml b/taskcluster/ci/build/kind.yml index bd71b6a17..335abd497 100644 --- a/taskcluster/ci/build/kind.yml +++ b/taskcluster/ci/build/kind.yml @@ -54,6 +54,15 @@ jobs: treeherder: symbol: debug(B) + beta-firebase: + disable-optimization: true + run-on-tasks-for: [github-push] # We want this on push so that we detect problem before triggering a new beta + run: + gradle-build-type: beta + test-build-type: beta + treeherder: + symbol: beta(Bf) + android-test-debug: attributes: code-review: true @@ -66,10 +75,10 @@ jobs: treeherder: symbol: debug(Bat) - # android-test-nightly, while still being a debug build, is meant to be signed with the nightly - # key. The Firebase testing infrastructure requires both the androidTest APK and the APK under - # test to be signed with the same key. Thus, the nightly APK being signed with nightly means - # we need an androidTest APK with the same signature. + # android-test-nightly and android-test-beta, while still being debug builds, are meant to be signed + # with the nightly/beta key. The Firebase testing infrastructure requires both the androidTest APK + # and the APK under test to be signed with the same key. Thus, the nightly APK being signed with + # nightly means we need an androidTest APK with the same signature. # # TODO: See if we can tweak the signing kind to make 2 signing jobs out of a single `android-test` # job. @@ -79,11 +88,28 @@ jobs: run: gradle-build-type: androidTest apk-artifact-template: - # 2 differences here: "androidTest/" is added and "{gradle_build_type}" is forced to "debug" + # 2 differences here: + # * "androidTest/" is added + # * "{gradle_build_type}" is forced to "debug" path: '/builds/worker/checkouts/src/app/build/outputs/apk/androidTest/debug/{fileName}' treeherder: symbol: nightly(Bat) + android-test-beta: + apk-artifact-template: + # 3 differences here: + # * "androidTest/" is added + # * "{gradle_build_type}" is forced to "beta" + # * "{fileName}" is forced to "app-beta-androidTest.apk" + path: '/builds/worker/checkouts/src/app/build/outputs/apk/androidTest/beta/app-beta-androidTest.apk' + disable-optimization: true + run: + gradle-build-type: androidTest + test-build-type: beta + run-on-tasks-for: [github-push] # We want this on push so that we detect problem before triggering a new beta + treeherder: + symbol: beta(Bat) + nightly-simulation: attributes: nightly: false diff --git a/taskcluster/ci/signing/kind.yml b/taskcluster/ci/signing/kind.yml index 8d6efef30..06eeff02b 100644 --- a/taskcluster/ci/signing/kind.yml +++ b/taskcluster/ci/signing/kind.yml @@ -28,16 +28,12 @@ job-template: worker: signing-type: by-build-type: - (beta|release): + (beta|release|android-test-beta): by-level: '3': fennec-production-signing default: dep-signing - android-test-nightly: - by-level: - '3': production-signing - default: dep-signing performance-test: dep-signing - nightly: + (nightly|android-test-nightly): by-level: '3': production-signing default: dep-signing @@ -52,12 +48,14 @@ job-template: by-build-type: # No test job runs on push against this build type. Although we do want nightly-simulation # signed to use it in the gecko trees. - nightly-simulation: [github-push] + # We want beta on push so that we detect problem before shipping it + (nightly-simulation|beta-firebase|android-test-beta): [github-push] default: [] treeherder: job-symbol: by-build-type: android-test.+: Bats + beta-firebase: Bfs default: Bs kind: build platform: android-all/opt diff --git a/taskcluster/fenix_taskgraph/transforms/build.py b/taskcluster/fenix_taskgraph/transforms/build.py index 37e51a7fc..a2ebdcf4f 100644 --- a/taskcluster/fenix_taskgraph/transforms/build.py +++ b/taskcluster/fenix_taskgraph/transforms/build.py @@ -77,6 +77,24 @@ def build_gradle_command(config, tasks): yield task +@transforms.add +def add_test_build_type(config, tasks): + for task in tasks: + test_build_type = task["run"].pop("test-build-type", "") + if test_build_type: + task["run"]["gradlew"].append( + "-PtestBuildType={}".format(test_build_type) + ) + yield task + + +@transforms.add +def add_disable_optimization(config, tasks): + for task in tasks: + if task.pop("disable-optimization", False): + task["run"]["gradlew"].append("-PdisableOptimization") + yield task + @transforms.add def add_nightly_version(config, tasks): From 2be3fd05f2bc604b69033f9de474735bbc4671e8 Mon Sep 17 00:00:00 2001 From: Philipp Klein Date: Fri, 4 Dec 2020 12:54:45 +0100 Subject: [PATCH 072/202] For #15362: Remove swipe to delete for bookmarks (#16646) Removed now obsolete feature flag and tests. Removed obsolete swipe refresh state from BookmarkFragmentState Also adapted tests and remove obsolete ones. --- .../java/org/mozilla/fenix/FeatureFlags.kt | 5 - .../library/bookmarks/BookmarkController.kt | 10 -- .../bookmarks/BookmarkFragmentInteractor.kt | 8 - .../bookmarks/BookmarkFragmentStore.kt | 26 +--- .../library/bookmarks/BookmarkTouchHelper.kt | 146 ------------------ .../fenix/library/bookmarks/BookmarkView.kt | 18 +-- .../bookmarks/BookmarkControllerTest.kt | 18 --- .../BookmarkFragmentInteractorTest.kt | 18 --- .../bookmarks/BookmarkFragmentStoreTest.kt | 117 ++------------ .../bookmarks/BookmarkTouchHelperTest.kt | 59 ------- 10 files changed, 25 insertions(+), 400 deletions(-) delete mode 100644 app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkTouchHelper.kt delete mode 100644 app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkTouchHelperTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt index e53b31aac..74a87fec8 100644 --- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt +++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -26,11 +26,6 @@ object FeatureFlags { */ const val externalDownloadManager = true - /** - * Enables swipe to delete in bookmarks - */ - val bookmarkSwipeToDelete = Config.channel.isNightlyOrDebug - /** * Enables ETP cookie purging */ diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt index 842d1878c..0b80684e4 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkController.kt @@ -45,8 +45,6 @@ interface BookmarkController { fun handleBookmarkFolderDeletion(nodes: Set) fun handleRequestSync() fun handleBackPressed() - fun handleStartSwipingItem() - fun handleStopSwipingItem() } @Suppress("TooManyFunctions") @@ -171,14 +169,6 @@ class DefaultBookmarkController( } } - override fun handleStartSwipingItem() { - store.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(false)) - } - - override fun handleStopSwipingItem() { - store.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(true)) - } - private fun openInNewTab( searchTermOrURL: String, newTab: Boolean, diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentInteractor.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentInteractor.kt index 3a1a4f252..4f5e757fc 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentInteractor.kt @@ -120,12 +120,4 @@ class BookmarkFragmentInteractor( override fun onRequestSync() { bookmarksController.handleRequestSync() } - - override fun onStartSwipingItem() { - bookmarksController.handleStartSwipingItem() - } - - override fun onStopSwipingItem() { - bookmarksController.handleStopSwipingItem() - } } diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentStore.kt index a14a5c023..da5a14ae2 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentStore.kt @@ -24,14 +24,12 @@ class BookmarkFragmentStore( * @property guidBackstack A set of guids for bookmark nodes we have visited. Used to traverse back * up the tree after a sync. * @property isLoading true if bookmarks are still being loaded from disk - * @property isSwipeToRefreshEnabled true if swipe to refresh should be enabled */ data class BookmarkFragmentState( val tree: BookmarkNode?, val mode: Mode = Mode.Normal(), val guidBackstack: List = emptyList(), - val isLoading: Boolean = true, - val isSwipeToRefreshEnabled: Boolean = true + val isLoading: Boolean = true ) : State { sealed class Mode : SelectionHolder { override val selectedItems = emptySet() @@ -52,7 +50,6 @@ sealed class BookmarkFragmentAction : Action { object DeselectAll : BookmarkFragmentAction() object StartSync : BookmarkFragmentAction() object FinishSync : BookmarkFragmentAction() - data class SwipeRefreshAvailabilityChanged(val enabled: Boolean) : BookmarkFragmentAction() } /** @@ -88,13 +85,11 @@ private fun bookmarkFragmentStateReducer( tree = action.tree, mode = mode, guidBackstack = backstack, - isLoading = false, - isSwipeToRefreshEnabled = mode !is BookmarkFragmentState.Mode.Selecting + isLoading = false ) } is BookmarkFragmentAction.Select -> state.copy( - mode = BookmarkFragmentState.Mode.Selecting(state.mode.selectedItems + action.item), - isSwipeToRefreshEnabled = false + mode = BookmarkFragmentState.Mode.Selecting(state.mode.selectedItems + action.item) ) is BookmarkFragmentAction.Deselect -> { val items = state.mode.selectedItems - action.item @@ -104,8 +99,7 @@ private fun bookmarkFragmentStateReducer( BookmarkFragmentState.Mode.Selecting(items) } state.copy( - mode = mode, - isSwipeToRefreshEnabled = mode !is BookmarkFragmentState.Mode.Selecting + mode = mode ) } is BookmarkFragmentAction.DeselectAll -> @@ -114,21 +108,15 @@ private fun bookmarkFragmentStateReducer( BookmarkFragmentState.Mode.Syncing } else { BookmarkFragmentState.Mode.Normal() - }, - isSwipeToRefreshEnabled = true + } ) is BookmarkFragmentAction.StartSync -> state.copy( - mode = BookmarkFragmentState.Mode.Syncing, - isSwipeToRefreshEnabled = true + mode = BookmarkFragmentState.Mode.Syncing ) is BookmarkFragmentAction.FinishSync -> state.copy( mode = BookmarkFragmentState.Mode.Normal( showMenu = shouldShowMenu(state.tree?.guid) - ), - isSwipeToRefreshEnabled = true - ) - is BookmarkFragmentAction.SwipeRefreshAvailabilityChanged -> state.copy( - isSwipeToRefreshEnabled = action.enabled && state.mode !is BookmarkFragmentState.Mode.Selecting + ) ) } } diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkTouchHelper.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkTouchHelper.kt deleted file mode 100644 index 5edf2c26f..000000000 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkTouchHelper.kt +++ /dev/null @@ -1,146 +0,0 @@ -/* 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.library.bookmarks - -import android.graphics.Canvas -import android.graphics.Rect -import android.graphics.drawable.Drawable -import androidx.appcompat.content.res.AppCompatResources -import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.RecyclerView -import mozilla.components.concept.storage.BookmarkNodeType -import mozilla.components.support.ktx.android.content.getColorFromAttr -import mozilla.components.support.ktx.android.content.getDrawableWithTint -import mozilla.components.support.ktx.android.util.dpToPx -import org.mozilla.fenix.R -import org.mozilla.fenix.home.sessioncontrol.SwipeToDeleteCallback -import org.mozilla.fenix.library.bookmarks.viewholders.BookmarkNodeViewHolder -import org.mozilla.fenix.library.bookmarks.viewholders.BookmarkSeparatorViewHolder - -class BookmarkTouchHelper(interactor: BookmarkViewInteractor) : - ItemTouchHelper(BookmarkTouchCallback(interactor)) - -class BookmarkTouchCallback( - private val interactor: BookmarkViewInteractor -) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { - - override fun getSwipeDirs( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder - ): Int { - // Swiping separators is currently not supported. - if (viewHolder is BookmarkSeparatorViewHolder) { - return 0 - } - val item = (viewHolder as BookmarkNodeViewHolder).item - return if (item?.inRoots() == true) { - 0 - } else { - super.getSwipeDirs(recyclerView, viewHolder) - } - } - - /** - * Delete the bookmark when swiped. - */ - override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { - val item = (viewHolder as BookmarkNodeViewHolder).item - item?.let { - interactor.onDelete(setOf(it)) - // We need to notify the adapter of a change if we swipe a folder to prevent - // visual bugs when cancelling deletion of a folder - if (item.type == BookmarkNodeType.FOLDER) { - viewHolder.bindingAdapter?.notifyItemChanged(viewHolder.bindingAdapterPosition) - } - } - } - - override fun onChildDraw( - c: Canvas, - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - dX: Float, - dY: Float, - actionState: Int, - isCurrentlyActive: Boolean - ) { - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) - val icon = recyclerView.context.getDrawableWithTint( - R.drawable.ic_delete, - recyclerView.context.getColorFromAttr(R.attr.destructive) - )!! - val background = AppCompatResources.getDrawable( - recyclerView.context, - R.drawable.swipe_delete_background - )!! - val margin = - SwipeToDeleteCallback.MARGIN.dpToPx(recyclerView.resources.displayMetrics) - val cellHeight = viewHolder.itemView.bottom - viewHolder.itemView.top - val iconTop = viewHolder.itemView.top + (cellHeight - icon.intrinsicHeight) / 2 - val iconBottom = iconTop + icon.intrinsicHeight - - when { - dX > 0 -> { // Swiping to the right - val backgroundBounds = Rect( - viewHolder.itemView.left, viewHolder.itemView.top, - (viewHolder.itemView.left + dX).toInt() + SwipeToDeleteCallback.BACKGROUND_CORNER_OFFSET, - viewHolder.itemView.bottom - ) - val iconLeft = viewHolder.itemView.left + margin - val iconRight = viewHolder.itemView.left + margin + icon.intrinsicWidth - val iconBounds = Rect(iconLeft, iconTop, iconRight, iconBottom) - - setBounds(background, backgroundBounds, icon, iconBounds) - draw(background, icon, c) - } - dX < 0 -> { // Swiping to the left - val backgroundBounds = Rect( - (viewHolder.itemView.right + dX).toInt() - SwipeToDeleteCallback.BACKGROUND_CORNER_OFFSET, - viewHolder.itemView.top, viewHolder.itemView.right, viewHolder.itemView.bottom - ) - val iconLeft = viewHolder.itemView.right - margin - icon.intrinsicWidth - val iconRight = viewHolder.itemView.right - margin - val iconBounds = Rect(iconLeft, iconTop, iconRight, iconBottom) - - setBounds(background, backgroundBounds, icon, iconBounds) - draw(background, icon, c) - } - else -> { // View not swiped - val bounds = Rect(0, 0, 0, 0) - setBounds(background, bounds, icon, bounds) - } - } - } - - override fun onMove( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - target: RecyclerView.ViewHolder - ): Boolean = false - - override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) { - super.onSelectedChanged(viewHolder, actionState) - if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { - interactor.onStartSwipingItem() - } else { - interactor.onStopSwipingItem() - } - } - - private fun setBounds( - background: Drawable, - backgroundBounds: Rect, - icon: Drawable, - iconBounds: Rect - ) { - background.bounds = backgroundBounds - icon.bounds = iconBounds - } - - private fun draw(background: Drawable, icon: Drawable, c: Canvas) { - background.draw(c) - icon.draw(c) - } -} diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkView.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkView.kt index 60ff69d4c..5887a871d 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkView.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkView.kt @@ -13,7 +13,6 @@ import kotlinx.android.synthetic.main.component_bookmark.view.* import mozilla.appservices.places.BookmarkRoot import mozilla.components.concept.storage.BookmarkNode import mozilla.components.support.base.feature.UserInteractionHandler -import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.NavGraphDirections import org.mozilla.fenix.R import org.mozilla.fenix.library.LibraryPageView @@ -99,16 +98,6 @@ interface BookmarkViewInteractor : SelectionInteractor { * */ fun onRequestSync() - - /** - * Handles the start of a swipe on a bookmark. - */ - fun onStartSwipingItem() - - /** - * Handles the end of a swipe on a bookmark. - */ - fun onStopSwipingItem() } class BookmarkView( @@ -135,10 +124,6 @@ class BookmarkView( view.swipe_refresh.setOnRefreshListener { interactor.onRequestSync() } - - if (FeatureFlags.bookmarkSwipeToDelete) { - BookmarkTouchHelper(interactor).attachToRecyclerView(view.bookmark_list) - } } fun update(state: BookmarkFragmentState) { @@ -166,7 +151,8 @@ class BookmarkView( } } view.bookmarks_progress_bar.isVisible = state.isLoading - view.swipe_refresh.isEnabled = state.isSwipeToRefreshEnabled + view.swipe_refresh.isEnabled = + state.mode is BookmarkFragmentState.Mode.Normal || state.mode is BookmarkFragmentState.Mode.Syncing view.swipe_refresh.isRefreshing = state.mode is BookmarkFragmentState.Mode.Syncing } diff --git a/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkControllerTest.kt b/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkControllerTest.kt index 2d0f2deb4..d55eb3818 100644 --- a/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkControllerTest.kt @@ -337,22 +337,4 @@ class BookmarkControllerTest { navController.popBackStack() } } - - @Test - fun `handleStartSwipingItem disables swipe to refresh`() { - controller.handleStartSwipingItem() - - verify { - bookmarkStore.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(false)) - } - } - - @Test - fun `handleStopSwipingItem attempts to enable swipe to refresh`() { - controller.handleStopSwipingItem() - - verify { - bookmarkStore.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(true)) - } - } } diff --git a/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentInteractorTest.kt index d640a9c78..5f0a409cc 100644 --- a/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentInteractorTest.kt @@ -210,22 +210,4 @@ class BookmarkFragmentInteractorTest { bookmarkController.handleRequestSync() } } - - @Test - fun `start swiping an item`() { - interactor.onStartSwipingItem() - - verify { - bookmarkController.handleStartSwipingItem() - } - } - - @Test - fun `stop swiping an item`() { - interactor.onStopSwipingItem() - - verify { - bookmarkController.handleStopSwipingItem() - } - } } diff --git a/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentStoreTest.kt b/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentStoreTest.kt index 321558e07..211c1f8ff 100644 --- a/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkFragmentStoreTest.kt @@ -80,63 +80,32 @@ class BookmarkFragmentStoreTest { @Test fun `ensure selected items remain selected after a tree change`() = runBlocking { - val initialState = BookmarkFragmentState( - tree, - BookmarkFragmentState.Mode.Selecting(setOf(item, subfolder)), - isLoading = false, - isSwipeToRefreshEnabled = false - ) + val initialState = BookmarkFragmentState(tree, BookmarkFragmentState.Mode.Selecting(setOf(item, subfolder))) val store = BookmarkFragmentStore(initialState) store.dispatch(BookmarkFragmentAction.Change(newTree)).join() - assertEquals( - store.state, - BookmarkFragmentState( - newTree, - BookmarkFragmentState.Mode.Selecting(setOf(subfolder)), - guidBackstack = listOf(tree.guid), - isLoading = false, - isSwipeToRefreshEnabled = false - ) - ) + assertEquals(store.state.tree, newTree) + assertEquals(store.state.mode, BookmarkFragmentState.Mode.Selecting(setOf(subfolder))) } @Test - fun `select and deselect a single bookmark changes the mode and swipe to refresh state`() = runBlocking { + fun `select and deselect a single bookmark changes the mode`() = runBlocking { val initialState = BookmarkFragmentState(tree) val store = BookmarkFragmentStore(initialState) store.dispatch(BookmarkFragmentAction.Select(childItem)).join() - assertEquals( - store.state, - BookmarkFragmentState( - tree, - BookmarkFragmentState.Mode.Selecting(setOf(childItem)), - isSwipeToRefreshEnabled = false - ) - ) + assertEquals(store.state, BookmarkFragmentState(tree, BookmarkFragmentState.Mode.Selecting(setOf(childItem)))) store.dispatch(BookmarkFragmentAction.Deselect(childItem)).join() - assertEquals( - store.state, - BookmarkFragmentState( - tree, - BookmarkFragmentState.Mode.Normal(), - isSwipeToRefreshEnabled = true - ) - ) + assertEquals(store.state, BookmarkFragmentState(tree, BookmarkFragmentState.Mode.Normal())) } @Test fun `selecting the same item twice does nothing`() = runBlocking { - val initialState = BookmarkFragmentState( - tree, - BookmarkFragmentState.Mode.Selecting(setOf(item, subfolder)), - isSwipeToRefreshEnabled = false - ) + val initialState = BookmarkFragmentState(tree, BookmarkFragmentState.Mode.Selecting(setOf(item, subfolder))) val store = BookmarkFragmentStore(initialState) store.dispatch(BookmarkFragmentAction.Select(item)).join() @@ -146,11 +115,7 @@ class BookmarkFragmentStoreTest { @Test fun `deselecting an unselected bookmark does nothing`() = runBlocking { - val initialState = BookmarkFragmentState( - tree, - BookmarkFragmentState.Mode.Selecting(setOf(childItem)), - isSwipeToRefreshEnabled = false - ) + val initialState = BookmarkFragmentState(tree, BookmarkFragmentState.Mode.Selecting(setOf(childItem))) val store = BookmarkFragmentStore(initialState) store.dispatch(BookmarkFragmentAction.Deselect(item)).join() @@ -169,25 +134,14 @@ class BookmarkFragmentStoreTest { } @Test - fun `deselect all bookmarks changes the mode and updates swipe to refresh state`() = - runBlocking { - val initialState = BookmarkFragmentState( - tree, - BookmarkFragmentState.Mode.Selecting(setOf(item, childItem)), - isSwipeToRefreshEnabled = false - ) - val store = BookmarkFragmentStore(initialState) - - store.dispatch(BookmarkFragmentAction.DeselectAll).join() - - assertEquals( - store.state, - initialState.copy( - mode = BookmarkFragmentState.Mode.Normal(), - isSwipeToRefreshEnabled = true - ) - ) - } + fun `deselect all bookmarks changes the mode`() = runBlocking { + val initialState = BookmarkFragmentState(tree, BookmarkFragmentState.Mode.Selecting(setOf(item, childItem))) + val store = BookmarkFragmentStore(initialState) + + store.dispatch(BookmarkFragmentAction.DeselectAll).join() + + assertEquals(store.state, initialState.copy(mode = BookmarkFragmentState.Mode.Normal())) + } @Test fun `deselect all bookmarks when none are selected`() = runBlocking { @@ -260,45 +214,6 @@ class BookmarkFragmentStoreTest { assertEquals(BookmarkFragmentState.Mode.Syncing, store.state.mode) } - @Test - fun `enabling swipe to refresh in Normal mode works`() = runBlocking { - val initialState = BookmarkFragmentState( - tree, - BookmarkFragmentState.Mode.Normal(), - isSwipeToRefreshEnabled = false - ) - val store = BookmarkFragmentStore(initialState) - - store.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(true)).join() - assertEquals(true, store.state.isSwipeToRefreshEnabled) - } - - @Test - fun `enabling swipe to refresh in Syncing mode works`() = runBlocking { - val initialState = BookmarkFragmentState( - tree, - BookmarkFragmentState.Mode.Syncing, - isSwipeToRefreshEnabled = false - ) - val store = BookmarkFragmentStore(initialState) - - store.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(true)).join() - assertEquals(true, store.state.isSwipeToRefreshEnabled) - } - - @Test - fun `enabling swipe to refresh in Selecting mode does not work`() = runBlocking { - val initialState = BookmarkFragmentState( - tree, - BookmarkFragmentState.Mode.Selecting(emptySet()), - isSwipeToRefreshEnabled = false - ) - val store = BookmarkFragmentStore(initialState) - - store.dispatch(BookmarkFragmentAction.SwipeRefreshAvailabilityChanged(true)).join() - assertEquals(false, store.state.isSwipeToRefreshEnabled) - } - private val item = BookmarkNode(BookmarkNodeType.ITEM, "456", "123", 0, "Mozilla", "http://mozilla.org", null) private val separator = BookmarkNode(BookmarkNodeType.SEPARATOR, "789", "123", 1, null, null, null) private val subfolder = BookmarkNode(BookmarkNodeType.FOLDER, "987", "123", 0, "Subfolder", null, listOf()) diff --git a/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkTouchHelperTest.kt b/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkTouchHelperTest.kt deleted file mode 100644 index 5e9209d88..000000000 --- a/app/src/test/java/org/mozilla/fenix/library/bookmarks/BookmarkTouchHelperTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* 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.library.bookmarks - -import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.RecyclerView -import io.mockk.MockKAnnotations -import io.mockk.every -import io.mockk.impl.annotations.RelaxedMockK -import io.mockk.mockk -import io.mockk.verify -import mozilla.components.concept.storage.BookmarkNode -import mozilla.components.concept.storage.BookmarkNodeType -import org.junit.Before -import org.junit.Test -import org.mozilla.fenix.library.bookmarks.viewholders.BookmarkNodeViewHolder - -class BookmarkTouchHelperTest { - - @RelaxedMockK private lateinit var interactor: BookmarkViewInteractor - @RelaxedMockK private lateinit var viewHolder: BookmarkNodeViewHolder - @RelaxedMockK private lateinit var item: BookmarkNode - private lateinit var touchCallback: BookmarkTouchCallback - - @Before - fun setup() { - MockKAnnotations.init(this) - touchCallback = BookmarkTouchCallback(interactor) - - every { viewHolder.item } returns item - } - - @Test - fun `swiping an item calls onDelete`() { - touchCallback.onSwiped(viewHolder, ItemTouchHelper.LEFT) - - verify { - interactor.onDelete(setOf(item)) - } - } - - @Test - fun `swiping a folder calls onDelete and notifies the adapter of the change`() { - val adapter: RecyclerView.Adapter = mockk(relaxed = true) - - every { item.type } returns BookmarkNodeType.FOLDER - every { viewHolder.bindingAdapter } returns adapter - every { viewHolder.bindingAdapterPosition } returns 0 - - touchCallback.onSwiped(viewHolder, ItemTouchHelper.LEFT) - - verify { - interactor.onDelete(setOf(item)) - adapter.notifyItemChanged(0) - } - } -} From d4ef63f8778f4458565452661bfc6e22766bcd5c Mon Sep 17 00:00:00 2001 From: Johan Lorenzo Date: Fri, 4 Dec 2020 14:07:30 +0100 Subject: [PATCH 073/202] Sign android-test-beta with the dep key Since it is used with beta-firebase which is also dep-signed --- taskcluster/ci/signing/kind.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskcluster/ci/signing/kind.yml b/taskcluster/ci/signing/kind.yml index 06eeff02b..87e55c0e7 100644 --- a/taskcluster/ci/signing/kind.yml +++ b/taskcluster/ci/signing/kind.yml @@ -28,7 +28,7 @@ job-template: worker: signing-type: by-build-type: - (beta|release|android-test-beta): + (beta|release): by-level: '3': fennec-production-signing default: dep-signing From 3c3ddb4f440bf2d4faff8d4e58d5d618d56df7af Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Fri, 4 Dec 2020 10:44:36 -0500 Subject: [PATCH 074/202] For #16133: Simplify OpenInAppOnboardingObserver condition (#16851) --- .../browser/OpenInAppOnboardingObserver.kt | 18 +-- .../OpenInAppOnboardingObserverTest.kt | 113 ++++++++++++++++++ 2 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 app/src/test/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserverTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserver.kt b/app/src/main/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserver.kt index 63b8c2635..0abb13a5d 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserver.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserver.kt @@ -6,6 +6,7 @@ package org.mozilla.fenix.browser import android.content.Context import android.view.ViewGroup +import androidx.annotation.VisibleForTesting import androidx.navigation.NavController import mozilla.components.browser.session.Session import mozilla.components.feature.app.links.AppLinksUseCases @@ -25,8 +26,10 @@ class OpenInAppOnboardingObserver( private val container: ViewGroup ) : Session.Observer { - private var sessionDomainForDisplayedBanner: String? = null - private var infoBanner: InfoBanner? = null + @VisibleForTesting + internal var sessionDomainForDisplayedBanner: String? = null + @VisibleForTesting + internal var infoBanner: InfoBanner? = null override fun onUrlChanged(session: Session, url: String) { sessionDomainForDisplayedBanner?.let { @@ -36,15 +39,14 @@ class OpenInAppOnboardingObserver( } } - @Suppress("ComplexCondition") override fun onLoadingStateChanged(session: Session, loading: Boolean) { + if (loading || settings.openLinksInExternalApp || !settings.shouldShowOpenInAppCfr) { + return + } + val appLink = appLinksUseCases.appLinkRedirect - if (!loading && - !settings.openLinksInExternalApp && - settings.shouldShowOpenInAppCfr && - appLink(session.url).hasExternalApp() - ) { + if (appLink(session.url).hasExternalApp()) { infoBanner = InfoBanner( context = context, message = context.getString(R.string.open_in_app_cfr_info_message), diff --git a/app/src/test/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserverTest.kt b/app/src/test/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserverTest.kt new file mode 100644 index 000000000..bc297d0c5 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/browser/OpenInAppOnboardingObserverTest.kt @@ -0,0 +1,113 @@ +/* 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.browser + +import android.content.Context +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import mozilla.components.browser.session.Session +import mozilla.components.feature.app.links.AppLinkRedirect +import mozilla.components.feature.app.links.AppLinksUseCases +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner +import org.mozilla.fenix.utils.Settings + +@ExperimentalCoroutinesApi +@RunWith(FenixRobolectricTestRunner::class) +class OpenInAppOnboardingObserverTest { + + @MockK(relaxed = true) private lateinit var context: Context + @MockK(relaxed = true) private lateinit var settings: Settings + @MockK(relaxed = true) private lateinit var session: Session + @MockK(relaxed = true) private lateinit var appLinksUseCases: AppLinksUseCases + @MockK(relaxed = true) private lateinit var applinksRedirect: AppLinkRedirect + @MockK(relaxed = true) private lateinit var getAppLinkRedirect: AppLinksUseCases.GetAppLinkRedirect + @MockK(relaxed = true) private lateinit var infoBanner: InfoBanner + + @Before + fun setup() { + MockKAnnotations.init(this) + } + + @Test + fun `do not show banner when openLinksInExternalApp is set to true`() { + every { settings.openLinksInExternalApp } returns true + every { settings.shouldShowOpenInAppCfr } returns true + + val observer = OpenInAppOnboardingObserver(context, mockk(), settings, appLinksUseCases, mockk()) + observer.onLoadingStateChanged(session, false) + + verify(exactly = 0) { appLinksUseCases.appLinkRedirect } + + every { settings.openLinksInExternalApp } returns false + observer.onLoadingStateChanged(session, false) + + verify(exactly = 1) { appLinksUseCases.appLinkRedirect } + } + + @Test + fun `do not show banner when shouldShowOpenInAppCfr is set to false`() { + every { settings.openLinksInExternalApp } returns false + every { settings.shouldShowOpenInAppCfr } returns false + + val observer = OpenInAppOnboardingObserver(context, mockk(), settings, appLinksUseCases, mockk()) + observer.onLoadingStateChanged(session, false) + + verify(exactly = 0) { appLinksUseCases.appLinkRedirect } + + every { settings.shouldShowOpenInAppCfr } returns true + observer.onLoadingStateChanged(session, false) + + verify(exactly = 1) { appLinksUseCases.appLinkRedirect } + } + + @Test + fun `do not show banner when URL is loading`() { + every { settings.openLinksInExternalApp } returns false + every { settings.shouldShowOpenInAppCfr } returns true + + val observer = OpenInAppOnboardingObserver(context, mockk(), settings, appLinksUseCases, mockk()) + + observer.onLoadingStateChanged(session, true) + + verify(exactly = 0) { appLinksUseCases.appLinkRedirect } + + observer.onLoadingStateChanged(session, false) + + verify(exactly = 1) { appLinksUseCases.appLinkRedirect } + } + + @Test + fun `do not show banner when external app is not found`() { + every { settings.openLinksInExternalApp } returns false + every { settings.shouldShowOpenInAppCfr } returns true + every { appLinksUseCases.appLinkRedirect } returns getAppLinkRedirect + every { getAppLinkRedirect.invoke(any()) } returns applinksRedirect + + val observer = OpenInAppOnboardingObserver(context, mockk(), settings, appLinksUseCases, mockk()) + observer.onLoadingStateChanged(session, false) + + verify(exactly = 0) { settings.shouldShowOpenInAppBanner } + } + + @Test + fun `do not dismiss banner when URL is the same`() { + val observer = OpenInAppOnboardingObserver(context, mockk(), settings, appLinksUseCases, mockk()) + observer.infoBanner = infoBanner + observer.sessionDomainForDisplayedBanner = "mozilla.com" + observer.onUrlChanged(session, "https://mozilla.com") + + verify(exactly = 0) { infoBanner.dismiss() } + + observer.onUrlChanged(session, "https://abc.com") + verify(exactly = 1) { infoBanner.dismiss() } + } +} From c89a7193f544321b1d962e1411c952c4352e5c89 Mon Sep 17 00:00:00 2001 From: Mugurell Date: Fri, 4 Dec 2020 18:47:30 +0200 Subject: [PATCH 075/202] For #16248 - Cleanup error pages of inlined code (#16834) Also streamlined the js code. The errorPageScripts.js from AC had too much or too little of what we needed. --- app/src/main/assets/highRiskErrorPages.js | 42 +++++++ .../main/assets/high_risk_error_pages.html | 10 +- app/src/main/assets/lowMediumErrorPages.js | 116 ++++++++++++++++++ .../low_and_medium_risk_error_pages.html | 8 +- .../mozilla/fenix/AppRequestInterceptor.kt | 2 +- .../fenix/AppRequestInterceptorTest.kt | 2 +- buildSrc/src/main/java/AndroidComponents.kt | 2 +- 7 files changed, 167 insertions(+), 15 deletions(-) create mode 100644 app/src/main/assets/highRiskErrorPages.js create mode 100644 app/src/main/assets/lowMediumErrorPages.js diff --git a/app/src/main/assets/highRiskErrorPages.js b/app/src/main/assets/highRiskErrorPages.js new file mode 100644 index 000000000..f4af7d3b0 --- /dev/null +++ b/app/src/main/assets/highRiskErrorPages.js @@ -0,0 +1,42 @@ +/* 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/. */ + +/** + * Handles the parsing of the ErrorPages URI and then passes them to injectValues + */ +function parseQuery(queryString) { + if (queryString[0] === '?') { + queryString = queryString.substr(1); + } + const query = Object.fromEntries(new URLSearchParams(queryString).entries()); + injectValues(query); +}; + +/** + * Updates the HTML elements based on the queryMap + */ +function injectValues(queryMap) { + // Go through each element and inject the values + document.title = queryMap.title; + document.getElementById('errorTitleText').innerHTML = queryMap.title; + document.getElementById('errorShortDesc').innerHTML = queryMap.description; + + // If no image is passed in, remove the element so as not to leave an empty iframe + const errorImage = document.getElementById('errorImage'); + if (!queryMap.image) { + errorImage.remove(); + } else { + errorImage.src = "resource://android/assets/" + queryMap.image; + } +} + +document.addEventListener('DOMContentLoaded', function () { + if (window.history.length == 1) { + document.getElementById('backButton').style.display = 'none'; + } else { + document.getElementById('backButton').addEventListener('click', () => window.history.back() ); + } +}); + +parseQuery(document.documentURI); diff --git a/app/src/main/assets/high_risk_error_pages.html b/app/src/main/assets/high_risk_error_pages.html index c74469ca7..c73012b14 100644 --- a/app/src/main/assets/high_risk_error_pages.html +++ b/app/src/main/assets/high_risk_error_pages.html @@ -8,6 +8,7 @@ + @@ -29,14 +30,9 @@ - + - - + diff --git a/app/src/main/assets/lowMediumErrorPages.js b/app/src/main/assets/lowMediumErrorPages.js new file mode 100644 index 000000000..3fc18a9ef --- /dev/null +++ b/app/src/main/assets/lowMediumErrorPages.js @@ -0,0 +1,116 @@ +/* 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/. */ + +/** + * Handles the parsing of the ErrorPages URI and then passes them to injectValues + */ +function parseQuery(queryString) { + if (queryString[0] === '?') { + queryString = queryString.substr(1); + } + const query = Object.fromEntries(new URLSearchParams(queryString).entries()); + injectValues(query); + updateShowSSL(query); +}; + +/** + * Updates the HTML elements based on the queryMap + */ +function injectValues(queryMap) { + // Go through each element and inject the values + document.title = queryMap.title; + document.getElementById('errorTitleText').innerHTML = queryMap.title; + document.getElementById('errorShortDesc').innerHTML = queryMap.description; + document.getElementById('errorTryAgain').innerHTML = queryMap.button; + document.getElementById('advancedButton').innerHTML = queryMap.badCertAdvanced; + document.getElementById('badCertTechnicalInfo').innerHTML = queryMap.badCertTechInfo; + document.getElementById('advancedPanelBackButton').innerHTML = queryMap.badCertGoBack; + document.getElementById('advancedPanelAcceptButton').innerHTML = queryMap.badCertAcceptTemporary; + + // If no image is passed in, remove the element so as not to leave an empty iframe + const errorImage = document.getElementById('errorImage'); + if (!queryMap.image) { + errorImage.remove(); + } else { + errorImage.src = "resource://android/assets/" + queryMap.image; + } +}; + +let advancedVisible = false; + +/** + * Used to show or hide the "advanced" button based on the validity of the SSL certificate + */ +function updateShowSSL(queryMap) { + /** @type {'true' | 'false'} */ + const showSSL = queryMap.showSSL; + if (typeof document.addCertException === 'undefined') { + document.getElementById('advancedButton').style.display='none'; + } else { + if (showSSL === 'true') { + document.getElementById('advancedButton').style.display='block'; + } else { + document.getElementById('advancedButton').style.display='none'; + } + } +}; + +/** + * Used to display information about the SSL certificate in `error_pages.html` + */ +function toggleAdvancedAndScroll() { + const advancedPanel = document.getElementById('badCertAdvancedPanel'); + if (advancedVisible) { + advancedPanel.style.display='none'; + } else { + advancedPanel.style.display='block'; + } + advancedVisible = !advancedVisible; + + const horizontalLine = document.getElementById("horizontalLine"); + const advancedPanelAcceptButton = document.getElementById( + "advancedPanelAcceptButton" + ); + const badCertAdvancedPanel = document.getElementById( + "badCertAdvancedPanel" + ); + + // We know that the button is being displayed + if (badCertAdvancedPanel.style.display === "block") { + horizontalLine.hidden = false; + advancedPanelAcceptButton.scrollIntoView({ + behavior: "smooth", + block: "center", + inline: "nearest", + }); + } else { + horizontalLine.hidden = true; + } +}; + +/** + * Used to bypass an SSL pages in `error_pages.html` + */ +async function acceptAndContinue(temporary) { + try { + await document.addCertException(temporary); + location.reload(); + } catch (error) { + console.error("Unexpected error: " + error); + } +}; + +document.addEventListener('DOMContentLoaded', function () { + if (window.history.length == 1) { + document.getElementById('advancedPanelBackButton').style.display = 'none'; + } else { + document.getElementById('advancedPanelBackButton').addEventListener('click', () => window.history.back()); + } + + document.getElementById('errorTryAgain').addEventListener('click', () => window.location.reload()); + document.getElementById('advancedButton').addEventListener('click', toggleAdvancedAndScroll); + document.getElementById('advancedPanelAcceptButton').addEventListener('click', () => acceptAndContinue(true)); +}); + +parseQuery(document.documentURI); diff --git a/app/src/main/assets/low_and_medium_risk_error_pages.html b/app/src/main/assets/low_and_medium_risk_error_pages.html index baa08deff..59e662987 100644 --- a/app/src/main/assets/low_and_medium_risk_error_pages.html +++ b/app/src/main/assets/low_and_medium_risk_error_pages.html @@ -8,6 +8,7 @@ + - + @@ -52,7 +52,6 @@ >
@@ -100,5 +98,5 @@ } - + diff --git a/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt b/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt index e8ef423c8..063a73cf2 100644 --- a/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt +++ b/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt @@ -72,7 +72,7 @@ class AppRequestInterceptor( htmlResource = riskLevel.htmlRes ) - return RequestInterceptor.ErrorResponse.Uri(errorPageUri) + return RequestInterceptor.ErrorResponse(errorPageUri) } /** diff --git a/app/src/test/java/org/mozilla/fenix/AppRequestInterceptorTest.kt b/app/src/test/java/org/mozilla/fenix/AppRequestInterceptorTest.kt index b24cbee29..c5be4053a 100644 --- a/app/src/test/java/org/mozilla/fenix/AppRequestInterceptorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/AppRequestInterceptorTest.kt @@ -200,7 +200,7 @@ class AppRequestInterceptorTest { private fun createActualErrorPage(error: ErrorType): String { val errorPage = interceptor.onErrorRequest(session = mockk(), errorType = error, uri = null) - as RequestInterceptor.ErrorResponse.Uri + as RequestInterceptor.ErrorResponse return errorPage.uri } diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index c706cedb7..23f4430eb 100644 --- a/buildSrc/src/main/java/AndroidComponents.kt +++ b/buildSrc/src/main/java/AndroidComponents.kt @@ -3,5 +3,5 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ object AndroidComponents { - const val VERSION = "69.0.20201203202830" + const val VERSION = "69.0.20201204143142" } From 7a08175568fe2a7e426b8542554a3a295aab5189 Mon Sep 17 00:00:00 2001 From: Oana Horvath Date: Thu, 3 Dec 2020 18:14:24 +0200 Subject: [PATCH 076/202] For #16615: Smoke UI test swipeToSwitchTabTest --- .../java/org/mozilla/fenix/ui/SmokeTest.kt | 29 +++++++++++++++---- .../mozilla/fenix/ui/robots/BrowserRobot.kt | 22 ++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt index 4ef54dc4b..1fe4cad22 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SmokeTest.kt @@ -35,6 +35,15 @@ class SmokeTest { private var awesomeBar: ViewVisibilityIdlingResource? = null private var searchSuggestionsIdlingResource: RecyclerViewIdlingResource? = null + // This finds the dialog fragment child of the homeFragment, otherwise the awesomeBar would return null + private fun getAwesomebarView(): View? { + val homeFragment = activityTestRule.activity.supportFragmentManager.primaryNavigationFragment + val searchDialogFragment = homeFragment?.childFragmentManager?.fragments?.first { + it.javaClass.simpleName == "SearchDialogFragment" + } + return searchDialogFragment?.view?.findViewById(R.id.awesome_bar) + } + @get:Rule val activityTestRule = HomeActivityTestRule() @@ -393,12 +402,20 @@ class SmokeTest { } } - // This finds the dialog fragment child of the homeFragment, otherwise the awesomeBar would return null - private fun getAwesomebarView(): View? { - val homeFragment = activityTestRule.activity.supportFragmentManager.primaryNavigationFragment - val searchDialogFragment = homeFragment?.childFragmentManager?.fragments?.first { - it.javaClass.simpleName == "SearchDialogFragment" + @Test + fun swipeToSwitchTabTest() { + val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) + val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2) + + navigationToolbar { + }.enterURLAndEnterToBrowser(firstWebPage.url) { + }.openTabDrawer { + }.openNewTab { + }.submitQuery(secondWebPage.url.toString()) { + swipeNavBarRight(secondWebPage.url.toString()) + verifyPageContent(firstWebPage.content) + swipeNavBarLeft(firstWebPage.url.toString()) + verifyPageContent(secondWebPage.content) } - return searchDialogFragment?.view?.findViewById(R.id.awesome_bar) } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt index 5ac6bf44a..723eeb7ee 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt @@ -338,6 +338,28 @@ class BrowserRobot { assertTrue(pausedStateMessage.waitForExists(waitingTime)) } + fun swipeNavBarRight(tabUrl: String) { + // failing to swipe on Firebase sometimes, so it tries again + try { + navURLBar().perform(ViewActions.swipeRight()) + assertTrue(mDevice.findObject(UiSelector().text(tabUrl)).waitUntilGone(waitingTime)) + } catch (e: AssertionError) { + navURLBar().perform(ViewActions.swipeRight()) + assertTrue(mDevice.findObject(UiSelector().text(tabUrl)).waitUntilGone(waitingTime)) + } + } + + fun swipeNavBarLeft(tabUrl: String) { + // failing to swipe on Firebase sometimes, so it tries again + try { + navURLBar().perform(ViewActions.swipeLeft()) + assertTrue(mDevice.findObject(UiSelector().text(tabUrl)).waitUntilGone(waitingTime)) + } catch (e: AssertionError) { + navURLBar().perform(ViewActions.swipeLeft()) + assertTrue(mDevice.findObject(UiSelector().text(tabUrl)).waitUntilGone(waitingTime)) + } + } + class Transition { private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) private fun threeDotButton() = onView( From e428694a51a8048dce1a730119258cc0aa16baf9 Mon Sep 17 00:00:00 2001 From: ekager Date: Wed, 2 Dec 2020 13:48:01 -0800 Subject: [PATCH 077/202] For #16397 - Fixes SignOutFragment crash and invisible button --- .../fenix/settings/account/SignOutFragment.kt | 37 +++++++++---------- app/src/main/res/layout/fragment_sign_out.xml | 23 +++++++++--- app/src/main/res/values/styles.xml | 16 -------- 3 files changed, 36 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt index 2544945f5..d927485dd 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/account/SignOutFragment.kt @@ -10,39 +10,36 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import androidx.fragment.app.DialogFragment +import com.google.android.material.bottomsheet.BottomSheetDialog +import org.mozilla.fenix.addons.runIfFragmentIsAttached import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import androidx.appcompat.app.AppCompatDialogFragment import kotlinx.android.synthetic.main.fragment_sign_out.view.* import kotlinx.coroutines.launch import mozilla.components.service.fxa.manager.FxaAccountManager import org.mozilla.fenix.R import org.mozilla.fenix.ext.requireComponents -class SignOutFragment : BottomSheetDialogFragment() { +class SignOutFragment : AppCompatDialogFragment() { private lateinit var accountManager: FxaAccountManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(DialogFragment.STYLE_NO_TITLE, R.style.FirefoxAccountsDialogStyle) + setStyle(STYLE_NO_TITLE, R.style.BottomSheet) } - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val dialog = super.onCreateDialog(savedInstanceState) - - dialog.setOnShowListener { - val bottomSheet = dialog.findViewById( - com.google.android.material.R.id.design_bottom_sheet - ) as FrameLayout - val behavior = BottomSheetBehavior.from(bottomSheet) - behavior.state = BottomSheetBehavior.STATE_EXPANDED + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = + BottomSheetDialog(requireContext(), this.theme).apply { + setOnShowListener { + val bottomSheet = + findViewById(com.google.android.material.R.id.design_bottom_sheet) as FrameLayout + val behavior = BottomSheetBehavior.from(bottomSheet) + behavior.state = BottomSheetBehavior.STATE_EXPANDED + } } - return dialog - } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -63,13 +60,15 @@ class SignOutFragment : BottomSheetDialogFragment() { super.onViewCreated(view, savedInstanceState) view.signOutDisconnect.setOnClickListener { - viewLifecycleOwner.lifecycleScope.launch { + lifecycleScope.launch { requireComponents .backgroundServices.accountAbnormalities.userRequestedLogout() accountManager.logout() }.invokeOnCompletion { - if (!findNavController().popBackStack(R.id.settingsFragment, false)) { - dismiss() + runIfFragmentIsAttached { + if (!findNavController().popBackStack(R.id.settingsFragment, false)) { + dismiss() + } } } } diff --git a/app/src/main/res/layout/fragment_sign_out.xml b/app/src/main/res/layout/fragment_sign_out.xml index 2f2ea0730..4cdd424e2 100644 --- a/app/src/main/res/layout/fragment_sign_out.xml +++ b/app/src/main/res/layout/fragment_sign_out.xml @@ -1,3 +1,4 @@ + @@ -15,24 +16,36 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="12dp" - app:drawableStartCompat="@drawable/ic_info" android:text="@string/sign_out_confirmation_message_2" android:textSize="16sp" - app:layout_constraintStart_toStartOf="parent" + app:drawableStartCompat="@drawable/ic_info" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> -