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 376751d0d..6c352d07c 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/BookmarksTest.kt @@ -443,7 +443,7 @@ class BookmarksTest { confirmFolderDeletion() verifyDeleteSnackBarText() verifyFolderTitle("3") - }.goBack { + }.closeMenu { } homeScreen { @@ -525,4 +525,14 @@ class BookmarksTest { verifySelectDefaultFolderSnackBarText() } } + + @Test + fun verifyCloseMenu() { + homeScreen { + }.openThreeDotMenu { + }.openBookmarks { + }.closeMenu { + verifyHomeScreen() + } + } } 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 dd3b45a76..0538540dc 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/HistoryTest.kt @@ -293,11 +293,11 @@ class HistoryTest { } @Test - fun verifyBackNavigation() { + fun verifyCloseMenu() { homeScreen { }.openThreeDotMenu { }.openHistory { - }.goBack { + }.closeMenu { verifyHomeScreen() } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/ThreeDotMenuMainTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/ThreeDotMenuMainTest.kt index 009f38a77..e59e2a77e 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/ThreeDotMenuMainTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/ThreeDotMenuMainTest.kt @@ -72,7 +72,7 @@ class ThreeDotMenuMainTest { }.openThreeDotMenu { }.openBookmarks { verifyBookmarksMenuView() - }.goBack { + }.closeMenu { } homeScreen { diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BookmarksRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BookmarksRobot.kt index 7ff4891c9..0255f7c0a 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BookmarksRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BookmarksRobot.kt @@ -113,7 +113,8 @@ class BookmarksRobot { .check(matches(isDisplayed())) } - fun verifySignInToSyncButton() = signInToSyncButton().check(matches(isDisplayed())) + fun verifySignInToSyncButton() = + signInToSyncButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) fun verifyDeleteFolderConfirmationMessage() = assertDeleteFolderConfirmationMessage() @@ -180,11 +181,11 @@ class BookmarksRobot { } class Transition { - fun goBack(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition { - goBackButton().click() + fun closeMenu(interact: HomeScreenRobot.() -> Unit): Transition { + closeButton().click() HomeScreenRobot().interact() - return HomeScreenRobot.Transition() + return Transition() } fun openThreeDotMenu(interact: ThreeDotMenuBookmarksRobot.() -> Unit): ThreeDotMenuBookmarksRobot.Transition { @@ -224,6 +225,8 @@ fun bookmarksMenu(interact: BookmarksRobot.() -> Unit): BookmarksRobot.Transitio return BookmarksRobot.Transition() } +private fun closeButton() = onView(withId(R.id.close_bookmarks)) + private fun goBackButton() = onView(withContentDescription("Navigate up")) private fun bookmarkFavicon(url: String) = onView( diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt index d88b6f277..5ca48ee5a 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HistoryRobot.kt @@ -10,7 +10,6 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.RootMatchers.isDialog import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.isDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withParent @@ -84,8 +83,8 @@ class HistoryRobot { } class Transition { - fun goBack(interact: HistoryRobot.() -> Unit): Transition { - goBackButton().click() + fun closeMenu(interact: HistoryRobot.() -> Unit): Transition { + closeButton().click() HistoryRobot().interact() return Transition() @@ -107,7 +106,7 @@ fun historyMenu(interact: HistoryRobot.() -> Unit): HistoryRobot.Transition { return HistoryRobot.Transition() } -private fun goBackButton() = onView(withContentDescription("Navigate up")) +private fun closeButton() = onView(withId(R.id.close_history)) private fun testPageTitle() = onView(allOf(withId(R.id.title), withText("Test_Page_1"))) diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index b23ccea97..da7c6d81a 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -128,6 +128,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity() { ) } + private lateinit var navigationToolbar: Toolbar + final override fun onCreate(savedInstanceState: Bundle?) { StrictModeManager.changeStrictModePolicies(supportFragmentManager) // There is disk read violations on some devices such as samsung and pixel for android 9/10 @@ -154,7 +156,9 @@ open class HomeActivity : LocaleAwareAppCompatActivity() { sessionObserver = UriOpenedObserver(this) - externalSourceIntentProcessors.any { it.process(intent, navHost.navController, this.intent) } + if (isActivityColdStarted(intent, savedInstanceState)) { + externalSourceIntentProcessors.any { it.process(intent, navHost.navController, this.intent) } + } Performance.processIntentIfPerformanceTest(intent, this) @@ -196,8 +200,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity() { final override fun onPause() { if (settings().lastKnownMode.isPrivate) { window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) } super.onPause() @@ -329,24 +331,29 @@ open class HomeActivity : LocaleAwareAppCompatActivity() { fun getSupportActionBarAndInflateIfNecessary(): ActionBar { // Add ids to this that we don't want to have a toolbar back button if (!isToolbarInflated) { - val navigationToolbar = navigationToolbarStub.inflate() as Toolbar + navigationToolbar = navigationToolbarStub.inflate() as Toolbar setSupportActionBar(navigationToolbar) - - NavigationUI.setupWithNavController( - navigationToolbar, - navHost.navController, - AppBarConfiguration.Builder().build() - ) - navigationToolbar.setNavigationOnClickListener { - onBackPressed() - } + setupNavigationToolbar() isToolbarInflated = true } return supportActionBar!! } + @Suppress("SpreadOperator") + fun setupNavigationToolbar(vararg topLevelDestinationIds: Int) { + NavigationUI.setupWithNavController( + navigationToolbar, + navHost.navController, + AppBarConfiguration.Builder(*topLevelDestinationIds).build() + ) + + navigationToolbar.setNavigationOnClickListener { + onBackPressed() + } + } + protected open fun getIntentSessionId(intent: SafeIntent): String? = null @Suppress("LongParameterList") @@ -478,6 +485,15 @@ open class HomeActivity : LocaleAwareAppCompatActivity() { this.visualCompletenessQueue = visualCompletenessQueue } + @VisibleForTesting + internal fun isActivityColdStarted(startingIntent: Intent, activityIcicle: Bundle?): Boolean = + // First time opening this activity in the task. + // Cold start / start from Recents after back press. + activityIcicle == null && + // Activity was restarted from Recents after it was destroyed by Android while in background + // in cases of memory pressure / "Don't keep activities". + startingIntent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY == 0 + companion object { const val OPEN_TO_BROWSER = "open_to_browser" const val OPEN_TO_BROWSER_AND_LOAD = "open_to_browser_and_load" diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt index 2359ecf60..3ad4957d3 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt @@ -302,7 +302,7 @@ class DefaultToolbarMenu( private val openInApp = BrowserMenuHighlightableItem( label = context.getString(R.string.browser_menu_open_app_link), - startImageResource = R.drawable.ic_app_links, + startImageResource = R.drawable.ic_open_in_app, iconTintColorResource = primaryTextColor(), highlight = BrowserMenuHighlight.LowPriority( label = context.getString(R.string.browser_menu_open_app_link), diff --git a/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt index b2deac1df..32a33b8d4 100644 --- a/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarMenu.kt @@ -139,7 +139,7 @@ class CustomTabToolbarMenu( private val openInApp = BrowserMenuHighlightableItem( label = context.getString(R.string.browser_menu_open_app_link), - startImageResource = R.drawable.ic_app_links, + startImageResource = R.drawable.ic_open_in_app, iconTintColorResource = primaryTextColor(), highlight = BrowserMenuHighlight.LowPriority( label = context.getString(R.string.browser_menu_open_app_link), 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 a0447c63d..6331d98f9 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/Context.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/Context.kt @@ -10,7 +10,6 @@ import android.view.ContextThemeWrapper import android.view.View import android.view.ViewGroup import androidx.annotation.StringRes -import androidx.fragment.app.FragmentActivity import mozilla.components.browser.search.SearchEngineManager import mozilla.components.support.locale.LocaleManager import org.mozilla.fenix.BuildConfig @@ -50,9 +49,6 @@ val Context.searchEngineManager: SearchEngineManager fun Context.asActivity() = (this as? ContextThemeWrapper)?.baseContext as? Activity ?: this as? Activity -fun Context.asFragmentActivity() = (this as? ContextThemeWrapper)?.baseContext as? FragmentActivity - ?: this as? FragmentActivity - fun Context.getPreferenceKey(@StringRes resourceId: Int): String = resources.getString(resourceId) diff --git a/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt b/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt index 2b0a81d86..7da50f968 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt @@ -57,14 +57,12 @@ fun Fragment.hideToolbar() { /** * Pops the backstack to force users to re-auth if they put the app in the background and return to it * while being inside the saved logins flow - * It also updates the FLAG_SECURE status for the activity's window * * Does nothing if the user is currently navigating to any of the [destinations] given as a parameter * */ fun Fragment.redirectToReAuth(destinations: List, currentDestination: Int?) { if (currentDestination !in destinations) { - activity?.let { it.checkAndUpdateScreenshotPermission(it.settings()) } findNavController().popBackStack(R.id.savedLoginsAuthFragment, false) } } 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 93991155f..c38edf498 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt @@ -115,7 +115,7 @@ class HomeMenu( val addons = BrowserMenuImageText( context.getString(R.string.browser_menu_add_ons), - R.drawable.mozac_ic_extensions, + R.drawable.ic_addons_extensions, primaryTextColor ) { onItemTapped.invoke(Item.AddonsManager) diff --git a/app/src/main/java/org/mozilla/fenix/library/LibraryPageFragment.kt b/app/src/main/java/org/mozilla/fenix/library/LibraryPageFragment.kt index 4236f2408..9c93b9c8b 100644 --- a/app/src/main/java/org/mozilla/fenix/library/LibraryPageFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/LibraryPageFragment.kt @@ -7,6 +7,7 @@ package org.mozilla.fenix.library import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment import mozilla.components.support.ktx.android.content.getColorFromAttr +import androidx.navigation.fragment.findNavController import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.browsingmode.BrowsingMode @@ -18,6 +19,12 @@ abstract class LibraryPageFragment : Fragment() { abstract val selectedItems: Set + protected fun close() { + if (!findNavController().popBackStack(R.id.browserFragment, false)) { + findNavController().popBackStack(R.id.homeFragment, false) + } + } + protected fun openItemsInNewTab(private: Boolean = false, toUrl: (T) -> String?) { context?.components?.useCases?.tabsUseCases?.let { tabsUseCases -> val addTab = if (private) tabsUseCases.addPrivateTab else tabsUseCases.addTab diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt index c6b26e770..02e434f4b 100644 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/bookmarks/BookmarkFragment.kt @@ -18,6 +18,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.navigation.NavDirections import androidx.navigation.fragment.findNavController +import kotlinx.android.synthetic.main.component_bookmark.view.* import kotlinx.android.synthetic.main.fragment_bookmark.view.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO @@ -57,7 +58,6 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan private lateinit var bookmarkStore: BookmarkFragmentStore private lateinit var bookmarkView: BookmarkView - private lateinit var signInView: SignInView private var _bookmarkInteractor: BookmarkFragmentInteractor? = null protected val bookmarkInteractor: BookmarkFragmentInteractor get() = _bookmarkInteractor!! @@ -97,9 +97,8 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan metrics = metrics!! ) - bookmarkView = BookmarkView(view.bookmarkLayout, bookmarkInteractor) - signInView = SignInView(view.bookmarkLayout, findNavController()) - signInView.view.visibility = View.GONE + bookmarkView = BookmarkView(view.bookmarkLayout, bookmarkInteractor, findNavController()) + bookmarkView.view.bookmark_folders_sign_in.visibility = View.GONE viewLifecycleOwner.lifecycle.addObserver( BookmarkDeselectNavigationListener( @@ -150,9 +149,9 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan if (currentGuid == BookmarkRoot.Root.id && requireComponents.backgroundServices.accountManager.authenticatedAccount() == null ) { - signInView.view.visibility = View.VISIBLE + bookmarkView.view.bookmark_folders_sign_in.visibility = View.VISIBLE } else { - signInView.view.visibility = View.GONE + bookmarkView.view.bookmark_folders_sign_in.visibility = View.GONE } initialJob = loadInitialBookmarkFolder(currentGuid) @@ -192,6 +191,11 @@ class BookmarkFragment : LibraryPageFragment(), UserInteractionHan override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { + R.id.close_bookmarks -> { + invokePendingDeletion() + close() + true + } R.id.add_bookmark_folder -> { navigate( BookmarkFragmentDirections 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 bccfd2a53..bd8f6ca8d 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 @@ -8,11 +8,14 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import androidx.navigation.NavController 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.HomeActivity import org.mozilla.fenix.R +import org.mozilla.fenix.NavGraphDirections import org.mozilla.fenix.library.LibraryPageView import org.mozilla.fenix.library.SelectionInteractor @@ -94,7 +97,8 @@ interface BookmarkViewInteractor : SelectionInteractor { class BookmarkView( container: ViewGroup, - val interactor: BookmarkViewInteractor + val interactor: BookmarkViewInteractor, + private val navController: NavController ) : LibraryPageView(container), UserInteractionHandler { val view: View = LayoutInflater.from(container.context) @@ -111,6 +115,9 @@ class BookmarkView( bookmarkAdapter = BookmarkAdapter(view.bookmarks_empty_view, interactor) adapter = bookmarkAdapter } + view.bookmark_folders_sign_in.setOnClickListener { + navController.navigate(NavGraphDirections.actionGlobalTurnOnSync()) + } } fun update(state: BookmarkFragmentState) { @@ -123,12 +130,25 @@ class BookmarkView( bookmarkAdapter.updateData(state.tree, mode) when (mode) { - is BookmarkFragmentState.Mode.Normal -> + is BookmarkFragmentState.Mode.Normal -> { + if (tree != null) { + if (BookmarkRoot.Mobile.id == tree?.guid) { + (activity as HomeActivity).setupNavigationToolbar(R.id.bookmarkFragment) + } else { + (activity as HomeActivity).setupNavigationToolbar() + } + } setUiForNormalMode(state.tree) - is BookmarkFragmentState.Mode.Selecting -> + } + is BookmarkFragmentState.Mode.Selecting -> { + (activity as HomeActivity).setupNavigationToolbar() setUiForSelectingMode( - context.getString(R.string.bookmarks_multi_select_title, mode.selectedItems.size) + context.getString( + R.string.bookmarks_multi_select_title, + mode.selectedItems.size + ) ) + } } view.bookmarks_progress_bar.isVisible = state.isLoading } diff --git a/app/src/main/java/org/mozilla/fenix/library/bookmarks/SignInView.kt b/app/src/main/java/org/mozilla/fenix/library/bookmarks/SignInView.kt deleted file mode 100644 index 8b0d41ebc..000000000 --- a/app/src/main/java/org/mozilla/fenix/library/bookmarks/SignInView.kt +++ /dev/null @@ -1,31 +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.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.navigation.NavController -import com.google.android.material.button.MaterialButton -import kotlinx.android.extensions.LayoutContainer -import org.mozilla.fenix.NavGraphDirections -import org.mozilla.fenix.R - -class SignInView( - private val container: ViewGroup, - private val navController: NavController -) : LayoutContainer { - - override val containerView: View? - get() = container - - val view: MaterialButton = LayoutInflater.from(container.context) - .inflate(R.layout.component_sign_in, container, true) - .findViewById(R.id.bookmark_folders_sign_in) - - init { - view.setOnClickListener { navController.navigate(NavGraphDirections.actionGlobalTurnOnSync()) } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt index ffc327cec..f5fdd9027 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryFragment.kt @@ -155,6 +155,10 @@ class HistoryFragment : LibraryPageFragment(), UserInteractionHandl } override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { + R.id.close_history -> { + close() + true + } R.id.share_history_multi_select -> { val selectedHistory = historyStore.state.mode.selectedItems val shareTabs = selectedHistory.map { ShareData(url = it.url, title = it.title) } diff --git a/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt b/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt index b26f3ad99..75c6bce67 100644 --- a/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt +++ b/app/src/main/java/org/mozilla/fenix/library/history/HistoryView.kt @@ -13,6 +13,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator import kotlinx.android.synthetic.main.component_history.* import kotlinx.android.synthetic.main.component_history.view.* import mozilla.components.support.base.feature.UserInteractionHandler +import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.library.LibraryPageView import org.mozilla.fenix.library.SelectionInteractor @@ -141,12 +142,18 @@ class HistoryView( } when (val mode = state.mode) { - is HistoryFragmentState.Mode.Normal -> + is HistoryFragmentState.Mode.Normal -> { + (activity as HomeActivity).setupNavigationToolbar(R.id.historyFragment) setUiForNormalMode( - context.getString(R.string.library_history)) - is HistoryFragmentState.Mode.Editing -> + context.getString(R.string.library_history) + ) + } + is HistoryFragmentState.Mode.Editing -> { + (activity as HomeActivity).setupNavigationToolbar() setUiForSelectingMode( - context.getString(R.string.history_multi_select_title, mode.selectedItems.size)) + context.getString(R.string.history_multi_select_title, mode.selectedItems.size) + ) + } } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/TextPercentageSeekBarPreference.kt b/app/src/main/java/org/mozilla/fenix/settings/TextPercentageSeekBarPreference.kt index 052c6ba47..f6bff9a85 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/TextPercentageSeekBarPreference.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/TextPercentageSeekBarPreference.kt @@ -29,9 +29,11 @@ import android.util.Log import android.util.TypedValue import android.view.KeyEvent import android.view.View +import android.view.accessibility.AccessibilityNodeInfo import android.widget.SeekBar import android.widget.SeekBar.OnSeekBarChangeListener import android.widget.TextView +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat.RANGE_TYPE_PERCENT import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import org.mozilla.fenix.R @@ -340,6 +342,25 @@ class TextPercentageSeekBarPreference @JvmOverloads constructor( val percentage = NumberFormat.getPercentInstance().format(decimalValue) mSeekBarValueTextView?.text = percentage } + + mSeekBar?.setAccessibilityDelegate(object : + View.AccessibilityDelegate() { + override fun onInitializeAccessibilityNodeInfo( + host: View?, + info: AccessibilityNodeInfo? + ) { + super.onInitializeAccessibilityNodeInfo(host, info) + val initialInfo = info?.rangeInfo + info?.rangeInfo = initialInfo?.let { + AccessibilityNodeInfo.RangeInfo.obtain( + RANGE_TYPE_PERCENT, + MIN_VALUE.toFloat(), + SEEK_BAR_MAX.toFloat(), + convertCurrentValue(it.current) + ) + } + } + }) } /** @@ -433,6 +454,10 @@ class TextPercentageSeekBarPreference @JvmOverloads constructor( } } + private fun convertCurrentValue(current: Float): Float { + return current * STEP_SIZE + MIN_VALUE.toFloat() + } + companion object { private const val TAG = "SeekBarPreference" private const val STEP_SIZE = 5 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 d449e8511..6cdf2235b 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 @@ -35,7 +35,6 @@ 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.increaseTapArea import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.settings.SupportUtils import java.util.Locale @@ -102,7 +101,6 @@ class AddSearchEngineFragment : Fragment(), CompoundButton.OnCheckedChangeListen toggleCustomForm(selectedIndex == CUSTOM_INDEX) - custom_search_engines_learn_more.increaseTapArea(DPS_TO_INCREASE) custom_search_engines_learn_more.setOnClickListener { (activity as HomeActivity).openToBrowserAndLoad( searchTermOrURL = SupportUtils.getSumoURLForTopic( 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 089585a55..92ed2f145 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 @@ -25,7 +25,6 @@ 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.increaseTapArea import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.settings.SupportUtils @@ -55,7 +54,6 @@ class EditCustomSearchEngineFragment : Fragment(R.layout.fragment_add_search_eng val decodedUrl = Uri.decode(searchEngine.buildSearchUrl("%s")) edit_search_string.setText(decodedUrl) - custom_search_engines_learn_more.increaseTapArea(DPS_TO_INCREASE) custom_search_engines_learn_more.setOnClickListener { (activity as HomeActivity).openToBrowserAndLoad( searchTermOrURL = SupportUtils.getSumoURLForTopic( diff --git a/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsLayout.kt b/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsLayout.kt index 90f8a3782..f3f882929 100644 --- a/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsLayout.kt +++ b/app/src/main/java/org/mozilla/fenix/sync/SyncedTabsLayout.kt @@ -39,6 +39,7 @@ class SyncedTabsLayout @JvmOverloads constructor( SyncedTabsView.ErrorType.SYNC_ENGINE_UNAVAILABLE -> R.string.synced_tabs_enable_tab_syncing SyncedTabsView.ErrorType.SYNC_UNAVAILABLE -> R.string.synced_tabs_connect_to_sync_account SyncedTabsView.ErrorType.SYNC_NEEDS_REAUTHENTICATION -> R.string.synced_tabs_reauth + SyncedTabsView.ErrorType.NO_TABS_AVAILABLE -> R.string.synced_tabs_no_tabs } sync_tabs_status.text = context.getText(stringResId) diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt new file mode 100644 index 000000000..72718208c --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayController.kt @@ -0,0 +1,124 @@ +/* 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.tabtray + +import androidx.annotation.VisibleForTesting +import androidx.navigation.NavController +import mozilla.components.browser.session.Session +import mozilla.components.browser.session.SessionManager +import mozilla.components.concept.engine.prompt.ShareData +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.R +import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.collections.SaveCollectionStep +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.sessionsOfType + +/** + * [TabTrayDialogFragment] controller. + * + * Delegated by View Interactors, handles container business logic and operates changes on it. + */ +interface TabTrayController { + fun onNewTabTapped(private: Boolean) + fun onTabTrayDismissed() + fun onShareTabsClicked(private: Boolean) + fun onSaveToCollectionClicked() + fun onCloseAllTabsClicked(private: Boolean) +} + +@Suppress("TooManyFunctions") +class DefaultTabTrayController( + private val activity: HomeActivity, + private val navController: NavController, + private val dismissTabTray: () -> Unit, + private val showUndoSnackbar: (String, SessionManager.Snapshot) -> Unit, + private val registerCollectionStorageObserver: () -> Unit +) : TabTrayController { + override fun onNewTabTapped(private: Boolean) { + activity.browsingModeManager.mode = BrowsingMode.fromBoolean(private) + navController.navigate(TabTrayDialogFragmentDirections.actionGlobalHome(focusOnAddressBar = true)) + dismissTabTray() + } + + override fun onTabTrayDismissed() { + dismissTabTray() + } + + override fun onSaveToCollectionClicked() { + val tabs = getListOfSessions(false) + val tabIds = tabs.map { it.id }.toList().toTypedArray() + val tabCollectionStorage = activity.components.core.tabCollectionStorage + + val step = when { + // Show the SelectTabs fragment if there are multiple opened tabs to select which tabs + // you want to save to a collection. + tabs.size > 1 -> SaveCollectionStep.SelectTabs + // If there is an existing tab collection, show the SelectCollection fragment to save + // the selected tab to a collection of your choice. + tabCollectionStorage.cachedTabCollections.isNotEmpty() -> SaveCollectionStep.SelectCollection + // Show the NameCollection fragment to create a new collection for the selected tab. + else -> SaveCollectionStep.NameCollection + } + + if (navController.currentDestination?.id == R.id.collectionCreationFragment) return + + // Only register the observer right before moving to collection creation + registerCollectionStorageObserver() + + val directions = TabTrayDialogFragmentDirections.actionGlobalCollectionCreationFragment( + tabIds = tabIds, + saveCollectionStep = step, + selectedTabIds = tabIds + ) + navController.navigate(directions) + } + + override fun onShareTabsClicked(private: Boolean) { + val tabs = getListOfSessions(private) + val data = tabs.map { + ShareData(url = it.url, title = it.title) + } + val directions = TabTrayDialogFragmentDirections.actionGlobalShareFragment( + data = data.toTypedArray() + ) + navController.navigate(directions) + } + + override fun onCloseAllTabsClicked(private: Boolean) { + val sessionManager = activity.components.core.sessionManager + val tabs = getListOfSessions(private) + + val selectedIndex = sessionManager + .selectedSession?.let { sessionManager.sessions.indexOf(it) } ?: 0 + + val snapshot = tabs + .map(sessionManager::createSessionSnapshot) + .map { + it.copy( + engineSession = null, + engineSessionState = it.engineSession?.saveState() + ) + } + .let { SessionManager.Snapshot(it, selectedIndex) } + + tabs.forEach { + sessionManager.remove(it) + } + + val snackbarMessage = if (private) { + activity.getString(R.string.snackbar_private_tabs_closed) + } else { + activity.getString(R.string.snackbar_tabs_closed) + } + + showUndoSnackbar(snackbarMessage, snapshot) + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + private fun getListOfSessions(private: Boolean): List { + return activity.components.core.sessionManager.sessionsOfType(private = private).toList() + } +} 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 fdc6a419d..151756e32 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayDialogFragment.kt @@ -22,25 +22,21 @@ import mozilla.components.browser.session.SessionManager import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.selector.privateTabs import mozilla.components.browser.state.state.BrowserState -import mozilla.components.concept.engine.prompt.ShareData -import mozilla.components.feature.tabs.tabstray.TabsFeature import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tabs.TabsUseCases +import mozilla.components.feature.tabs.tabstray.TabsFeature import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R -import org.mozilla.fenix.browser.browsingmode.BrowsingMode -import org.mozilla.fenix.collections.SaveCollectionStep import org.mozilla.fenix.components.FenixSnackbar +import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.requireComponents -import org.mozilla.fenix.ext.sessionsOfType import org.mozilla.fenix.utils.allowUndo -import org.mozilla.fenix.components.TabCollectionStorage @SuppressWarnings("TooManyFunctions", "LargeClass") -class TabTrayDialogFragment : AppCompatDialogFragment(), TabTrayInteractor { +class TabTrayDialogFragment : AppCompatDialogFragment() { private val tabsFeature = ViewBoundFeatureWrapper() private var _tabTrayView: TabTrayView? = null private val tabTrayView: TabTrayView @@ -108,10 +104,19 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), TabTrayInteractor { _tabTrayView = TabTrayView( view.tabLayout, - this, - isPrivate, - requireContext().resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE, - viewLifecycleOwner.lifecycleScope + interactor = TabTrayFragmentInteractor( + DefaultTabTrayController( + activity = (activity as HomeActivity), + navController = findNavController(), + dismissTabTray = ::dismissAllowingStateLoss, + showUndoSnackbar = ::showUndoSnackbar, + registerCollectionStorageObserver = ::registerCollectionStorageObserver + ) + ), + isPrivate = isPrivate, + startingInLandscape = requireContext().resources.configuration.orientation == + Configuration.ORIENTATION_LANDSCAPE, + lifecycleScope = viewLifecycleOwner.lifecycleScope ) { tabsFeature.get()?.filterTabs(it) } tabsFeature.set( @@ -195,96 +200,6 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), TabTrayInteractor { } } - override fun onNewTabTapped(private: Boolean) { - (activity as HomeActivity).browsingModeManager.mode = BrowsingMode.fromBoolean(private) - findNavController().navigate(TabTrayDialogFragmentDirections.actionGlobalHome(focusOnAddressBar = true)) - dismissAllowingStateLoss() - } - - override fun onTabTrayDismissed() { - dismissAllowingStateLoss() - } - - override fun onSaveToCollectionClicked() { - val tabs = getListOfSessions(false) - val tabIds = tabs.map { it.id }.toList().toTypedArray() - val tabCollectionStorage = (activity as HomeActivity).components.core.tabCollectionStorage - val navController = findNavController() - - val step = when { - // Show the SelectTabs fragment if there are multiple opened tabs to select which tabs - // you want to save to a collection. - tabs.size > 1 -> SaveCollectionStep.SelectTabs - // If there is an existing tab collection, show the SelectCollection fragment to save - // the selected tab to a collection of your choice. - tabCollectionStorage.cachedTabCollections.isNotEmpty() -> SaveCollectionStep.SelectCollection - // Show the NameCollection fragment to create a new collection for the selected tab. - else -> SaveCollectionStep.NameCollection - } - - if (navController.currentDestination?.id == R.id.collectionCreationFragment) return - - // Only register the observer right before moving to collection creation - registerCollectionStorageObserver() - - val directions = TabTrayDialogFragmentDirections.actionGlobalCollectionCreationFragment( - tabIds = tabIds, - saveCollectionStep = step, - selectedTabIds = tabIds - ) - navController.navigate(directions) - } - - override fun onShareTabsClicked(private: Boolean) { - val tabs = getListOfSessions(private) - val data = tabs.map { - ShareData(url = it.url, title = it.title) - } - val directions = TabTrayDialogFragmentDirections.actionGlobalShareFragment( - data = data.toTypedArray() - ) - findNavController().navigate(directions) - } - - override fun onCloseAllTabsClicked(private: Boolean) { - val sessionManager = requireContext().components.core.sessionManager - val tabs = getListOfSessions(private) - - val selectedIndex = sessionManager - .selectedSession?.let { sessionManager.sessions.indexOf(it) } ?: 0 - - val snapshot = tabs - .map(sessionManager::createSessionSnapshot) - .map { it.copy(engineSession = null, engineSessionState = it.engineSession?.saveState()) } - .let { SessionManager.Snapshot(it, selectedIndex) } - - tabs.forEach { - sessionManager.remove(it) - } - - val snackbarMessage = if (tabTrayView.isPrivateModeSelected) { - getString(R.string.snackbar_private_tabs_closed) - } else { - getString(R.string.snackbar_tabs_closed) - } - - viewLifecycleOwner.lifecycleScope.allowUndo( - requireView(), - snackbarMessage, - getString(R.string.snackbar_deleted_undo), - { - sessionManager.restore(snapshot) - }, - operation = { }, - elevation = ELEVATION - ) - } - - private fun getListOfSessions(private: Boolean): List { - return requireContext().components.core.sessionManager.sessionsOfType(private = private) - .toList() - } - private fun navigateHomeIfNeeded(state: BrowserState) { val shouldPop = if (tabTrayView.isPrivateModeSelected) { state.privateTabs.isEmpty() @@ -301,6 +216,21 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), TabTrayInteractor { requireComponents.core.tabCollectionStorage.register(collectionStorageObserver, this) } + private fun showUndoSnackbar(snackbarMessage: String, snapshot: SessionManager.Snapshot) { + view?.let { + viewLifecycleOwner.lifecycleScope.allowUndo( + it, + snackbarMessage, + getString(R.string.snackbar_deleted_undo), + { + context?.components?.core?.sessionManager?.restore(snapshot) + }, + operation = { }, + elevation = ELEVATION + ) + } + } + private fun showCollectionSnackbar() { view.let { val snackbar = FenixSnackbar @@ -328,7 +258,9 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), TabTrayInteractor { fun show(fragmentManager: FragmentManager) { // If we've killed the fragmentManager. Let's not try to show the tabs tray. - if (fragmentManager.isDestroyed) { return } + if (fragmentManager.isDestroyed) { + return + } // We want to make sure we don't accidentally show the dialog twice if // a user somehow manages to trigger `show()` twice before we present the dialog. diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt new file mode 100644 index 000000000..374292c8e --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractor.kt @@ -0,0 +1,38 @@ +/* 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.tabtray + +interface TabTrayInteractor { + fun onNewTabTapped(private: Boolean) + fun onTabTrayDismissed() + fun onShareTabsClicked(private: Boolean) + fun onSaveToCollectionClicked() + fun onCloseAllTabsClicked(private: Boolean) +} + +/** + * Interactor for the tab tray fragment. + */ +class TabTrayFragmentInteractor(private val controller: TabTrayController) : TabTrayInteractor { + override fun onNewTabTapped(private: Boolean) { + controller.onNewTabTapped(private) + } + + override fun onTabTrayDismissed() { + controller.onTabTrayDismissed() + } + + override fun onShareTabsClicked(private: Boolean) { + controller.onShareTabsClicked(private) + } + + override fun onSaveToCollectionClicked() { + controller.onSaveToCollectionClicked() + } + + override fun onCloseAllTabsClicked(private: Boolean) { + controller.onCloseAllTabsClicked(private) + } +} 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 822236f60..eaec5b455 100644 --- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt +++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayView.kt @@ -32,13 +32,6 @@ import org.mozilla.fenix.R import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings -interface TabTrayInteractor { - fun onNewTabTapped(private: Boolean) - fun onTabTrayDismissed() - fun onShareTabsClicked(private: Boolean) - fun onSaveToCollectionClicked() - fun onCloseAllTabsClicked(private: Boolean) -} /** * View that contains and configures the BrowserAwesomeBar */ @@ -219,7 +212,11 @@ class TabTrayView( } } - view.tabsTray.asView().isVisible = !hasNoTabs + view.tabsTray.asView().visibility = if (hasNoTabs) { + View.INVISIBLE + } else { + View.VISIBLE + } view.tab_tray_overflow.isVisible = !hasNoTabs } } diff --git a/app/src/main/res/drawable/ic_addons_extensions.xml b/app/src/main/res/drawable/ic_addons_extensions.xml new file mode 100644 index 000000000..c227de01c --- /dev/null +++ b/app/src/main/res/drawable/ic_addons_extensions.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_app_links.xml b/app/src/main/res/drawable/ic_app_links.xml deleted file mode 100644 index c6ee2fb0d..000000000 --- a/app/src/main/res/drawable/ic_app_links.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/ic_open_in_app.xml b/app/src/main/res/drawable/ic_open_in_app.xml new file mode 100644 index 000000000..be5f9f3d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_open_in_app.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/layout/component_bookmark.xml b/app/src/main/res/layout/component_bookmark.xml index 91620b709..c593e5e18 100644 --- a/app/src/main/res/layout/component_bookmark.xml +++ b/app/src/main/res/layout/component_bookmark.xml @@ -2,35 +2,45 @@ - + + android:id="@+id/bookmark_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:listitem="@layout/library_site_item" /> + android:id="@+id/bookmarks_empty_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:text="@string/bookmarks_empty_message" + android:textColor="?secondaryText" + android:textSize="16sp" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + - - diff --git a/app/src/main/res/layout/custom_search_engine.xml b/app/src/main/res/layout/custom_search_engine.xml index 70fff145e..ff7a262ed 100644 --- a/app/src/main/res/layout/custom_search_engine.xml +++ b/app/src/main/res/layout/custom_search_engine.xml @@ -63,6 +63,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/exceptions_empty_message_learn_more_link" + android:minHeight="@dimen/accessibility_min_height" android:textColor="?accent" android:visibility="visible" android:contentDescription="@string/search_add_custom_engine_learn_more_description" diff --git a/app/src/main/res/layout/fragment_bookmark.xml b/app/src/main/res/layout/fragment_bookmark.xml index dfbc16961..1612c6938 100644 --- a/app/src/main/res/layout/fragment_bookmark.xml +++ b/app/src/main/res/layout/fragment_bookmark.xml @@ -11,6 +11,6 @@ diff --git a/app/src/main/res/menu/bookmarks_menu.xml b/app/src/main/res/menu/bookmarks_menu.xml index 6b4104a0d..393dcb064 100644 --- a/app/src/main/res/menu/bookmarks_menu.xml +++ b/app/src/main/res/menu/bookmarks_menu.xml @@ -10,4 +10,11 @@ app:iconTint="?primaryText" android:title="@string/bookmark_add_folder" app:showAsAction="ifRoom" /> + + diff --git a/app/src/main/res/menu/library_menu.xml b/app/src/main/res/menu/library_menu.xml index 4e581c561..5bc903838 100644 --- a/app/src/main/res/menu/library_menu.xml +++ b/app/src/main/res/menu/library_menu.xml @@ -2,4 +2,13 @@ - + + + + diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 5cca57924..9850f898c 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -1,9 +1,24 @@ + + Прыватны %s + + %s (прыватна) + + + Дадаткова + + Уключыць прыватнае агляданне + + Адключыць прыватнае агляданне Увядзіце запыт або адрас + + + Вы ў прыватным сеансе + Шырокавядомыя забабоны пра прыватнае агляданне Выдаліць сеанс @@ -75,6 +90,8 @@ Рэжым чытання Закрыць Рэжым чытання + + Адкрыць у праграме Выгляд @@ -89,6 +106,12 @@ Сканаваць + + Цэтлікі + + Налады пошукавых сістэм + + Шукаць з Гэтым разам шукаць у: @@ -127,6 +150,8 @@ Адрасны радок Даведка + + Ацаніць у Google Play Даць водгук Прылады распрацоўшчыка + + Аддаленая адладка праз USB + + Пошукавыя скароты Паказваць пошукавыя прапановы @@ -209,11 +238,26 @@ Закладкі Лагіны + + Адкрытыя карткі Выйсці Назва прылады + + Назва прылады не можа быць пустой. + + Сінхранізацыя… + + Апошняя сінхранізацыя: %s + + Апошняя сінхранізацыя: ніколі + + %1$s на %2$s %3$s + Атрыманыя карткі @@ -231,25 +275,61 @@ Ахова ад сачэння Выключэнні + + Ахова ад сачэння выключана на гэтых сайтах Даведацца больш Тэлеметрыя + + Дзяліцца звесткамі пра прадукцыйнасць, выкарыстанне, апаратнае забеспячэнне і налады вашага браўзера з Mozilla, каб дапамагчы ўдасканаліць %1$s Доследы + + Паведамляльнік пра крахі + + Служба месцазнаходжання Mozilla + + + + Уключыць сінхранізацыю Увайсці + + Выдаліць уліковы запіс + Адкрыць камеру + + Адмяніць + + + + Зверху + + Знізу + + + + Светлая + + Цёмная + Сеансы + + Здымкі экрана + + Сцягванні Закладкі + + Закладкі камп’ютара Меню закладак @@ -295,6 +375,8 @@ Закрыць усе карткі Новая картка + + Дадому Выдаліць картку з калекцыі @@ -305,6 +387,8 @@ Закрыць усе карткі Падзяліцца карткамі + + Захаваць карткі у калекцыі Меню картак @@ -328,8 +412,30 @@ Выдаліць + + %1$s (Прыватны рэжым) + + + + Выдаліць гісторыю + + Вы ўпэўненыя, што хочаце выдаліць сваю гісторыю? + + Гісторыя выдалена + + %1$s выдалены + + Ачысціць + + Капіраваць Падзяліцца + + Адкрыць у новай картцы + + Адкрыць у прыватнай картцы + + Выдаліць Выбрана: %1$d @@ -343,6 +449,11 @@ Апошнія 30 дзён + + Даўней + + + Адправіць справаздачу аб краху ў Mozilla Закрыць картку @@ -357,29 +468,111 @@ Меню закладак + + Рэдагаваць закладку + + Выбраць папку + + Вы ўпэўнены, што жадаеце выдаліць гэту папку? + + %1$s выдалена Закладка створана. Закладка захавана! + + Змяніць + + Выбраць + + Капіраваць Падзяліцца + + Адкрыць у новай картцы + + Адкрыць у прыватнай картцы + + Выдаліць + + Захаваць + + Выбрана: %1$d Рэдагаваць закладку + + Рэдагаваць папку + + Увайдзіце, каб убачыць сінхранізаваныя закладкі + + URL + + ПАПКА + + НАЗВА + + Дадаць папку + + Выбраць папку + + Несапраўдны URL Няма закладак + + %1$s выдалена + + Закладкі выдалены + + АДМЯНІЦЬ + Дазволы Перайсці ў налады + + Рэкамендуецца + + Кіраваць дазволамі сайта + + Ачысціць дазволы + + Ачысціць дазвол + + Ачысціць дазволы на ўсіх сайтах + + Аўтапрайграванне Камера Мікрафон + + Месцазнаходжанне + + Абвестка + + Заблакавана Android + + Выняткі + + Уключана + + Выключана + + Дазволіць гук і відэа + + Блакаваць гук і відэа + + Уключана + + Выключана + Калекцыі @@ -393,10 +586,14 @@ Выберыце карткі Выберыце калекцыю + + Назва калекцыі Дадаць новую калекцыю Вылучыць усе + + Адмяніць выбар усіх Карткі захаваны! @@ -437,9 +634,23 @@ Па-за сеткай + + Падключыць іншую прыладу Зразумела + + Даслаць на прыладу + + Няма падключаных прылад + + Даведацца больш пра адпраўку картак… + + Злучыць іншую прыладу… + + + + Сеанс прыватнага аглядання Выдаліць прыватныя карткі @@ -449,6 +660,8 @@ Выдаліць і адкрыць + + Працуе на Калекцыя выдалена @@ -463,9 +676,97 @@ Карткі закрыты + + Прыватная картка закрыта + + Прыватныя карткі закрыты + + Прыватныя карткі выдалены + + АДМЯНІЦЬ + + Сайт выдалены + + Адмяніць + + Пацвердзіць + + ДАЗВОЛІЦЬ + + АДМОВІЦЬ + + Вы ўпэўнены, што хочаце выдаліць %1$s? + + Выдаліць + + Адмена + + Уваход у поўнаэкранны рэжым + + URL скапіраваны + + + Памер шрыфту + + + Аўтаматычны памер шрыфту + + + Адкрытыя карткі + + Карткі: %d + + Адрасы: %d + + Гісторыя + + Кукі + + Дазволы для сайтаў + + Гісторыя аглядання Выйсці + + Адмена + + Выдаліць + + + Атрымаць Firefox для Android Beta + + + Firefox Nightly пераехаў + + + Firefox Nightly пераехаў + + Атрымаць новы Nightly + + + + Вітаем у %s! + + Ужо маеце ўліковы запіс? + + Паглядзіце, што новага + + Увайсці ў Firefox + + Няўдача ўваходу + + Аўтаматычная прыватнасць + + Стандартная (прадвызначана) + + Строгая (рэкамендуецца) + + Строгая + + Аглядайце прыватна Адкрыць налады @@ -482,6 +783,94 @@ Аўтаматычна + + Цёмная тэма + + Светлая тэма + + + Карткі адпраўлены! + + Картка адпраўлена! + + Немагчыма адправіць + + ПАЎТАРЫЦЬ + + Скануйце код + + Firefox спыніць сінхранізацыю з вашым уліковым запісам, але не выдаліць дадзеныя аглядання на гэтай прыладзе. + + %s спыніць сінхранізацыю з вашым уліковым запісам, але не выдаліць дадзеныя аглядання на гэтай прыладзе. + + Адлучыцца + + Адмена + + + + Налады аховы + + Узмоцненая ахова ад сачэння + + Аглядайце без старонніх вачэй + + Захоўвайце свае дадзеныя пры сабе. %s абараняе вас ад многіх самых распаўсюджаных трэкераў, якія сочаць за тым, што вы робіце ў інтэрнэце. + + Даведацца больш + + Стандартная (прадвызначана) + + Строгая + + Адмыслова + + Выберыце, якія трэкеры і скрыпты трэба заблакаваць. + + + Кукі + + Трэкеры міжсайтавыя і сацыяльных сетак + + Кукі з ненаведаных сайтаў + + Усе кукі трэцяга боку (можа парушыць працу вэб-сайтаў) + + Усе кукі (будзе перашкаджаць працы сайтаў) + + Элементы сачэння + + Ва ўсіх картках + + Толькі ў прыватных картках + + Майнеры крыптавалют + + Збіральнікі лічбавых адбіткаў + Заблакаваны + + Дазволены + + Трэкеры сацыяльных сетак + + Міжсайтавыя кукі асочвання + + Майнеры крыптавалют + + Збіральнікі лічбавых адбіткаў + + Змест з элементамі сачэння + + Узмоцненая ахова ад сачэння выключана на гэтых сайтах + + + Перайсці назад + + Вашы правы + + Што новага ў %s + Падтрымка @@ -489,15 +878,48 @@ Паведамленне аб прыватнасці + + Ведай свае правы + + Звесткі пра ліцэнзію + + + 1 картка + + Карткі: %d + + + + Капіраваць + + Уставіць і перайсці Уставіць + + URL скапіраваны ў буфер абмену + Дадаць + + Лагіны і паролі + + Захаванне лагінаў і пароляў + + Выключэнні + Дадаць Захаваць - + + Бяспечнае злучэнне + + Не бяспечнае злучэнне + + + OK, зразумела + + diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 59f10b96a..97427024b 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -15,14 +15,17 @@ Desactiva la navegació privada Escriviu una cerca o adreça - - No hi ha cap pestanya oberta Les pestanyes obertes es mostraran aquí. Les pestanyes privades es mostraran aquí. + + 1 pestanya oberta. Toqueu per canviar de pestanya. + + %1$s pestanyes obertes. Toqueu per canviar de pestanya. + El %1$s està creat per Mozilla. @@ -296,7 +299,7 @@ Inicis de sessió - Pestanyes + Pestanyes obertes Tanca la sessió @@ -482,7 +485,7 @@ Comparteix les pestanyes - Desa a la col·lecció + Desa les pestanyes a la col·lecció Menú de pestanya @@ -695,16 +698,14 @@ Desactivat - - Recolliu allò que us importa. Per començar, deseu les pestanyes obertes en una col·lecció nova. Col·leccions Menú de col·lecció - Cap col·lecció - - Les vostres col·leccions es mostraran aquí. + Recolliu tot allò que us insteressa + + Agrupeu les cerques, els llocs i les pestanyes similars per accedir-hi ràpidament en el futur. Trieu les pestanyes @@ -913,8 +914,9 @@ El Firefox Nightly s’actualitza cada nit i té característiques noves experimentals. Tot i això, pot ser menys estable. Baixeu el navegador beta per a una experiència més estable. - - Baixeu el navegador Mozilla Firefox + + + Baixeu el Firefox per a l’Android Beta El Firefox Nightly s’ha traslladat @@ -1420,6 +1422,9 @@ Parleu ara + + Ja existeix un inici de sessió amb aquest nom d’usuari + Connecteu-vos amb un Compte del Firefox. @@ -1430,4 +1435,19 @@ Activeu la sincronització de pestanyes. + + No teniu cap pestanya oberta al Firefox dels altres dispositius. + + Vegeu una llista de les pestanyes que teniu obertes en altres dispositius. + + Inicia la sessió per sincronitzar + + + + S’ha arribat al límit de llocs principals + + Per afegir un lloc principal nou, primer cal que n’elimineu algun altre. Manteniu premut el lloc i seleccioneu eliminar-lo. + + Entesos + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 9f815c77c..17de182b0 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -16,11 +16,12 @@ 検索語またはアドレスを入力 - - 表示タブなし 開いているタブがここに表示されます。 + + プライベートタブがここに表示されます。 + %1$s は Mozilla の製品です。 @@ -36,7 +37,7 @@ セッションを削除 - + プライベートタブを開くショートカットをホーム画面に追加します。 @@ -44,6 +45,14 @@ 追加しない + + + Firefox にすばやくアクセス。ウィジェットをホーム画面に追加してください。 + + ウィジェットを追加 + + 後で + 新しいタブ @@ -86,6 +95,8 @@ ホーム画面に追加 インストール + + 同期したタブ ページ内検索 @@ -94,8 +105,6 @@ 新しいタブ コレクションに保存 - - サイトの問題を報告 共有 @@ -111,8 +120,10 @@ The first parameter is the name of the app defined in app_name (for example: Fenix) --> Powered by %1$s - + リーダービュー + + リーダービューを閉じる アプリで開く @@ -256,6 +267,8 @@ 検索ショートカットを表示 検索語句の候補を表示 + + 音声検索を表示 プライベートセッションで表示する @@ -284,6 +297,8 @@ ブックマーク ログイン情報 + + タブを開く ログアウト @@ -378,7 +393,7 @@ - firefox.com/pair のサイトにアクセスして QR コードを取得してください。]]> + firefox.com/pair で表示された QR コードをスキャンしてください]]> カメラを開く @@ -419,12 +434,12 @@ 他のブックマーク 履歴 + + 同期したタブ リーディングリスト 検索 - - ブラウジングライブラリー 設定 @@ -441,6 +456,26 @@ プライベートタブ タブを追加 + + プライベートタブを追加 + + プライベート + + 開いているタブ + + コレクションに保存 + + すべてのタブを共有 + + すべてのタブを閉じる + + 新しいタブ + + ホーム画面を開く + + タブモードを切り替え + + タブをコレクションから削除 タブを閉じる @@ -452,7 +487,7 @@ タブを共有 - コレクションに保存 + タブをコレクションに保存 タブメニュー @@ -671,16 +706,10 @@ オフ - - あなたにとって価値のあるものを収集します。開始するには、開いているタブを新しいコレクションに保存します。 コレクション コレクションメニュー - - コレクションはありません - - あなたのコレクションがここに表示されます。 タブの選択 @@ -826,14 +855,14 @@ フォントサイズ - フォントサイズを自動設定 + フォントサイズを自動調整 フォントサイズは Android の設定に従います。このフォントサイズの管理は無効化されます。 ブラウジングデータを削除 - 開いているタブ + 開いているタブ %d 個のタブ @@ -890,8 +919,6 @@ Firefox Nightly は毎晩更新され、実験的な新しい機能がテストされています。 しかし、これは安定性に欠けるため、より安定したバージョンを使用したい場合はベータ版のダウンロードをおすすめします。 - - Mozilla Firefox ブラウザーを入手しましょう Firefox Nightly は移転しました @@ -944,19 +971,8 @@ Sync が有効です ログインに失敗しました - - 自己防衛しよう - - %s がウェブサイトによるオンラインでのユーザー追跡を止めるのに役立ちます。 - - 標準 - - ブロックするトラッカーの数は少なくなりますが、ページを正常に読み込めます 厳格 (推奨) - - ユーザー保護とパフォーマンス向上のために、より多くのトラッカーをブロックしますが、一部のサイトが正常に動作しない可能性があります。 @@ -1040,32 +1056,14 @@ 個人データの収集を防止します。%s はオンラインでのあなたの行動を追跡するよく知られた数多くのトラッカーからあなたを守ります。 詳細情報 - - 標準 - - 標準 (推奨) - - 追跡防止とパフォーマンスを均等にします。 - - ページは正常に読み込まれますが、ブロックされるトラッカーが少なくなります。 標準のトラッキング防止でブロックされるもの 厳格 - - 厳格 (既定) - - トラッキング防止を強力にし、パフォーマンスを高速化します。ただし、一部のサイトが正常に機能しなくなる可能性があります。 - - 厳格 (推奨) - - 強力な追跡防止。ただし、一部のサイトまたはコンテンツが破損する可能性があります。 厳格なトラッキング防止でブロックされるもの カスタム - - ブロックするトラッカーとスクリプトを選択してください カスタム設定のトラッキング防止でブロックされるもの @@ -1114,8 +1112,6 @@ トラッキングコンテンツ 追跡コードを含む外部の広告、動画、その他のコンテンツの読み込みを停止します。一部のウェブサイトの機能に影響する場合があります。 - - 盾が紫色の時は、%s がこのサイト上のトラッカーをブロックしています。ブロックしているものを確認するには盾をタップしてください。 このサイトでは保護が有効になっています @@ -1137,6 +1133,8 @@ サポート + + クラッシュ 個人情報保護方針 @@ -1274,6 +1272,13 @@ ピンチとズームを有効にします。このジェスチャーが止められているウェブサイトでも有効です。 + + 名前 (昇順) + + 最終使用日時 + + ログイン情報メニューの並べ替え + 検索エンジンの追加 @@ -1365,8 +1370,40 @@ 認証局: %1$s 削除 + + 編集 このログイン情報を削除してもよろしいですか? 削除 - + + + ログインオプション + + ログイン情報のウェブアドレスの編集可能なテキストフィールド。 + + ログイン情報のユーザー名の編集可能なテキストフィールド。 + + ログイン情報のパスワードの編集可能なテキストフィールド。 + + 変更を保存してログインします。 + + 変更を破棄 + + 編集 + + パスワードが必要です + + 音声検索 + + 話してください + + + + Firefox アカウントで接続してください。 + + 他の端末を接続してください。 + + 再認証してください。 + + diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index 4318b8e9d..d58acebf4 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -13,14 +13,17 @@ პირადი თვალიერების გამორთვა მოძებნეთ ან შეიყვანეთ მისამართი - - გახსნილი ჩანართები არაა თქვენი გახსნილი ჩანართები გამოჩნდება აქ. თქვენი პირადი ჩანართები გამოჩნდება აქ. + + 1 გახსნილი ჩანართი. შეეხეთ ჩანართების გადასართველად. + + %1$s გახსნილი ჩანართი. შეეხეთ ჩანართების გადასართველად. + %1$s შექმნა Mozilla-მ. @@ -103,8 +106,6 @@ ახალი ჩანართი კრებულში შენახვა - - მოხსენება საიტზე გაზიარება @@ -118,8 +119,10 @@ უზრუნველყოფს %1$s - + კითხვის რეჟიმი + + კითხვის რეჟიმის დახურვა გახსნა პროგრამით @@ -293,7 +296,7 @@ ანგარიშები - ჩანართები + გახსნილი ჩანართები გამოსვლა @@ -449,6 +452,24 @@ პირადი ჩანართები ჩანართის დამატება + + პირადი ჩანართის დამატება + + პირადი + + გახსნილი ჩანართები + + კრებულში შენახვა + + ყველა ჩანართის გაზიარება + + ყველა ჩანართის დახურვა + + ახალი ჩანართი + + მთავარზე გადასვლა + + ჩანართის რეჟიმის გადართვა ჩანართის მოცილება კრებულიდან @@ -462,7 +483,7 @@ ჩანართების გაზიარება - კრებულში შენახვა + ჩანართების კრებულში შენახვა ჩანართის მენიუ @@ -675,17 +696,14 @@ გამორთ. - - შეკრიბეთ რაც მნიშვნელოვანია თქვენთვის. დასაწყებად, შეინახეთ გახსნილი ჩანართები ახალ კრებულში. - კრებულები კრებულის მენიუ - კრებულები არაა - - თქვენი კრებულები გამოჩნდება აქ. + შეაგროვეთ, რაც თქვენთვის მნიშვნელოვანია + + თავი მოუყარეთ მოძიებულ მასალებს, საიტებს, ჩანართებს, სწრაფი წვდომისთვის. ჩანართების შერჩევა @@ -715,6 +733,9 @@ შენახვა + + ჩვენება + კრებული %d @@ -889,8 +910,9 @@ Firefox Nightly ახლდება ყოველ ღამე და აქვს საცდელი შესაძლებლობები. თუმცა, შესაძლოა ნაკლებ მდგრადი იყოს. ჩამოტვირთეთ beta მეტი მდგრადობისთვის. - - გადმოწერეთ Mozilla Firefox-ბრაუზერი + + + გადმოწერეთ Firefox Android Beta Firefox Nightly ჩანაცვლებულია @@ -943,22 +965,22 @@ სინქრონიზაცია ჩართულია შესვლა ვერ მოხერხდა + - დაიცავით თავი + თავისთავადი პირადულობა - %s დაგეხმარებათ აირიდოთ თვალთვალი ვებსაიტებისგან. + პირადულობისა და უსაფრთხოების პარამეტრები უზღუდავს მეთვალყურეებს, მავნებლებს და კომპანიებს, თქვენზე თვალის დევნებას. - ჩვეულებრივი - + ჩვეულებრივი (ნაგულისხმევი) - შეიზღუდება ნაკლები მეთვალყურე, მაგრამ გვერდები ჩვეულებრივ გაიხსნება + ზღუდავს ნაკლებ მეთვალყურეს. გვერდები ჩვეულებრივ გაიხსნება. მკაცრი (სასურველი) მკაცრი - შეიზღუდება მეტი მეთვალყურე უკეთესი დაცვისა და სიჩქარისთვის, მაგრამ შესაძლოა ზოგიერთი საიტის გაუმართაობა გამოიწვიოს + ზღუდავს მეტ მეთვალყურეს, ამომხტომს, რეკლამას. გვერდები უფრო სწრაფად გაიხსნება, მაგრამ შესაძლოა, ოდნავ გაუმართავი იყოს. @@ -1046,31 +1068,21 @@ ვრცლად - ჩვეულებრივი - - ჩვეულებრივი (სასურველი) + ჩვეულებრივი (ნაგულისხმევი) - წონასწორული, უსაფრთხოებასა და წარმადობას შორის. - - გვერდები ჩაიტვირთება ჩვეულებრივ, მაგრამ ნაკლები მეთვალყურე იზღუდება. + ზღუდავს ნაკლებ მეთვალყურეს. გვერდები ჩვეულებრივ გაიხსნება. რა იზღუდება თვალთვალისგან ჩვეულებრივი დაცვით მკაცრი - - მკაცრი (ნაგულისხმევი) - მეტად მძლავრი დაცვა თვალთვალისგან და მეტი სისწრაფე, მაგრამ საიტების ნაწილმა, შესაძლოა გაუმართავად იმუშაოს. - - მკაცრი (სასურველი) - - მძლავრი დაცვა, თუმცა გამოიწვევს საიტების ან შიგთავსის ნაწილის გაუმართაობას. + ზღუდავს მეტ მეთვალყურეს, ამომხტომს, რეკლამას. გვერდები უფრო სწრაფად გაიხსნება, მაგრამ შესაძლოა, ოდნავ გაუმართავი იყოს. რა იზღუდება თვალთვალისგან მკაცრი დაცვით მორგებული - აირჩიეთ, რომელი მეთვალყურე საშუალებები შეიზღუდოს + აირჩიეთ, რომელი მეთვალყურე საშუალებები შეიზღუდოს. რა იზღუდება თვალთვალისგან მორგებული დაცვით @@ -1122,7 +1134,7 @@ აჩერებს გარეშე რეკლამების, ვიდეოებისა და სხვა მეთვალყურე კოდის შემცველი შიგთავსის ჩატვირთვას. შეიძლება გავლენა იქონიოს ვებგვერდის ზოგიერთ შესაძლებლობაზე. - %s ზღუდავს მეთვალყურეებს საიტზე, როცა ფარი იისფერია. შეეხეთ, რომ იხილოთ რა იზღუდება. + როცა ფარი იისფერია, ნიშნავს რომ %s ზღუდავს მეთვალყურეებს საიტზე. შეეხეთ ვრცლად სანახავად. დაცვა ჩართულია ამ საიტზე @@ -1408,6 +1420,9 @@ წარმოთქვით + + ანგარიში ამ სახელით უკვე არსებობს + დაკავშირება Firefox-ანგარიშით @@ -1418,4 +1433,19 @@ გთხოვთ, ჩართოთ ჩანართების სინქრონიზაცია. + + თქვენ არ გაქვთ, Firefox-ის გახსნილი ჩანართები, სხვა მოწყობილობებზე. + + იხილეთ ჩანართების სია თქვენი სხვა მოწყობილობებიდან. + + სინქრონიზაციაში შესვლა + + + + რჩეული საიტების ზღვარი მიღწეულია + + ახალი რჩეული საიტის დასამატებლად, წაშალეთ წინა. მასზე დიდხანს დაჭერით და მოცილებით. + + კარგი, გასაგებია + diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index 1f70c6347..0500d96e7 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -1451,6 +1451,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara Tewwḍeḍ ɣer talast n usmel + + I tmerna n usmel afellay amaynut, kkes yiwen. Sit mliḥ ɣef usmel syen fren kkes. IH, awi-t-id diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index a345f4cc3..ba7a4b57c 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -117,6 +117,8 @@ ขับเคลื่อนโดย %1$s มุมมองผู้อ่าน + + ปิดมุมมองผู้อ่าน เปิดในแอป @@ -444,6 +446,24 @@ แท็บส่วนตัว เพิ่มแท็บ + + เพิ่มแท็บส่วนตัว + + ส่วนตัว + + แท็บที่เปิด + + บันทึกไปยังชุดสะสม + + แบ่งปันแท็บทั้งหมด + + ปิดแท็บทั้งหมด + + แท็บใหม่ + + ไปยังหน้าแรก + + สลับโหมดแท็บ เอาแท็บออกจากชุดสะสม @@ -456,6 +476,8 @@ ปิดแท็บทั้งหมด แบ่งปันแท็บ + + บันทึกแท็บไปยังชุดสะสม เมนูแท็บ @@ -700,6 +722,9 @@ บันทึก + + ดู + ชุดสะสม %d diff --git a/app/src/main/res/values-trs/strings.xml b/app/src/main/res/values-trs/strings.xml index 9862fc1cd..bfcb1dbe3 100644 --- a/app/src/main/res/values-trs/strings.xml +++ b/app/src/main/res/values-trs/strings.xml @@ -891,6 +891,84 @@ Sa ni\'io\' nī archibô kāchê + + Gī\'iaj hīu hiūj nachrasàt + + Sa rikî nì\'iaj sîtio + + Nādure\' nej si datô riña sā nana\'uî\'t + + Nādure\' nej si datô riña sā nana\'uî\'t ngà gāhuīt + + + Nārè\' man\'an nej si datô riña sā nana\'uî\'t ngà gūru\'man ra\'ât riña tāj “Gāhuīt” riña menú ñāan + + Nārè\' man\'an nej si datô riña sā nana\'uî\'t ngà gūru\'man ra\'ât riña tāj \“Gāhuīt\” riña menú ñāan + + Nej sa gini\'iājt ngà gaché nunt + + Gāhuī + + + Gānarè\' daran\' nej si datôt ngà gaché nunt. + + %s nādure\'ej nej si datô riña gaché nunt ngà naguit. + + Dūyichin\' + + Nādūre\' + + Ngà ganare\' nej si datô sa nana\'uî\'t + + Hīaj nadure\'ej nej si datô riña sā nana\'uî\'t… + + + + Firefox Preview huin Firefox Nightly hīaj + + + Nahuin nākà Firefox Nightly daran\' nìïn nī huā sa gārahuê\' nākàa nika. + Sanī, ga\'ue sī gi\'iaj sun hue\'ej. Nādūnïnj sa nana\'ui\' gù\'nàj beta da\' gā hīa ruhuât doj. + + Nādūnïnj Firefox guendâ Android Beta + + + Ngà nasiki\' Firefox Nightly + + + Sī nahuin ra\'a aplikasiûn nan sa nahuin nākà dūgumî sò\' ngà\'. Dūnâj aplikasiûn nan nī nādūnā ngà Nightly nākàa. + \n\nDa\' gānāchīn nej sa arâj sun nīchrà\'t doj, riña ayi\'ì sēsiûn nī sa gini\'iājt riña a\'ngô aplikasiûn, da\'uît gīrīt \'ngō si kuentât Firefox. + + Nādūnā ngà Nightly nākàa + + + Ngà nasiki\' Firefox Nightly + + Sī nahuin ra\'a aplikasiûn nan sa nahuin nākà dūgumî sò\' ngà\'. Sī garâj sunt aplikasiûn nan ngà\' nī nādūnïnj Nightly nākàa. + \n\nDa\' gānāchīn nej sa arâj sun nīchrà\'t doj, riña ayi\'ì sēsiûn nī sa gini\'iājt riña a\'ngô aplikasiûn, da\'uît gīrīt \'ngō si kuentât Firefox. + + + Nādūnïnj Nightly nākàa + + + + Guruhuât gunumânt riña %s + + Ngà huā si kuendâ raj? + + Gānārì\' %s + + Gīni\'iāj sa huā nākà doj + + Huā sa gāchìnj na\'ānjt rayi\'î nùhuin saj nadunâ hua %s aj. Gīni\'înt sa nadunâ ruhuâ raj. + + Hiūj nan \'na\' nuguan\' ruhuât gīni\'înt + + Nārì\' daran\' sa nīkāj %s. Nārán diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 1a92550be..d1f2b34c1 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -17,7 +17,7 @@ Тут з’являтимуться ваші відкриті вкладки. - Тут будуть ваші приватні вкладки. + Тут зʼявлятимуться ваші приватні вкладки. 1 відкрита вкладка. Торкніться, щоб перемкнути вкладки. @@ -1137,7 +1137,7 @@ Криптомайнери - Перешкоджає доступу зловмисних скриптів до вашого пристрою для видобутку цифрової валюти. + Перешкоджає доступу зловмисних скриптів до вашого пристрою для добування криптовалют. Зчитування цифрового відбитка diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index 3608e1158..3971366bc 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -1342,6 +1342,12 @@ حذف کریں لاگ ان اختیارات + + لاگ ان کے ویب ایڈریس کے لئے یہ تدوین کر سکتے ہیں۔ + + لاگ ان کے صارف نام کے لئے یہ تدوین کر سکتے ہیں۔ + + لاگ ان کے پاس ورڈ کے لئے یہ تدوین کر سکتے ہیں۔ لاگ ان میں تبدیلیاں محفوظ کریں۔ @@ -1371,6 +1377,8 @@ sync کے لئے سائن ان کریں + + نئی ٹاپ سائٹ شامل کرنے کے لئے ، ایک سائٹ کو ہٹائیں۔ سائٹ پر دیر تک دبائیں اور ہٹانا منتخب کریں۔ ٹھیک ہے سمجھ گیا diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 65dfeb6db..08e09bdd2 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -156,5 +156,7 @@ 5dp 16dp + + 48dp diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 60471bd70..b9d8a73f4 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -122,13 +122,13 @@ android:key="@string/pref_key_advanced" android:layout="@layout/preference_cat_style"> diff --git a/app/src/test/java/org/mozilla/fenix/HomeActivityTest.kt b/app/src/test/java/org/mozilla/fenix/HomeActivityTest.kt index a919da928..1b45dc5df 100644 --- a/app/src/test/java/org/mozilla/fenix/HomeActivityTest.kt +++ b/app/src/test/java/org/mozilla/fenix/HomeActivityTest.kt @@ -5,11 +5,14 @@ package org.mozilla.fenix import android.content.Intent +import android.os.Bundle import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.utils.toSafeIntent import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith import org.mozilla.fenix.HomeActivity.Companion.PRIVATE_BROWSING_MODE @@ -58,4 +61,38 @@ class HomeActivityTest { assertNotEquals(testContext.settings().lastKnownMode, activity.getModeFromIntentOrLastKnown(intent)) assertEquals(BrowsingMode.Private, activity.getModeFromIntentOrLastKnown(intent)) } + + @Test + fun `isActivityColdStarted returns true for null savedInstanceState and not launched from history`() { + val activity = HomeActivity() + + assertTrue(activity.isActivityColdStarted(Intent(), null)) + } + + @Test + fun `isActivityColdStarted returns false for valid savedInstanceState and not launched from history`() { + val activity = HomeActivity() + + assertFalse(activity.isActivityColdStarted(Intent(), Bundle())) + } + + @Test + fun `isActivityColdStarted returns false for null savedInstanceState and launched from history`() { + val activity = HomeActivity() + val startingIntent = Intent().apply { + flags = flags or Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY + } + + assertFalse(activity.isActivityColdStarted(startingIntent, null)) + } + + @Test + fun `isActivityColdStarted returns false for null savedInstanceState and not launched from history`() { + val activity = HomeActivity() + val startingIntent = Intent().apply { + flags = flags or Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY + } + + assertFalse(activity.isActivityColdStarted(startingIntent, Bundle())) + } } diff --git a/app/src/test/java/org/mozilla/fenix/ext/ContextTest.kt b/app/src/test/java/org/mozilla/fenix/ext/ContextTest.kt index 29a990097..f0806672f 100644 --- a/app/src/test/java/org/mozilla/fenix/ext/ContextTest.kt +++ b/app/src/test/java/org/mozilla/fenix/ext/ContextTest.kt @@ -4,57 +4,165 @@ package org.mozilla.fenix.ext +import android.app.Activity import android.content.Context +import android.view.ContextThemeWrapper +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.test.core.app.ApplicationProvider import io.mockk.every import io.mockk.mockk import io.mockk.mockkObject -import io.mockk.mockkStatic +import io.mockk.unmockkObject import mozilla.components.support.locale.LocaleManager import mozilla.components.support.locale.LocaleManager.getSystemDefault +import mozilla.components.support.test.robolectric.testContext +import org.junit.After import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.FenixApplication +import org.mozilla.fenix.R +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import java.lang.String.format import java.util.Locale +@RunWith(FenixRobolectricTestRunner::class) class ContextTest { - private lateinit var context: Context + + private lateinit var mockContext: Context private val selectedLocale = Locale("ro", "RO") private val appName = "Firefox Preview" - private val correctlyFormattedString = "Incearca noul %1s" - private val incorrectlyFormattedString = "Incearca noul %1&s" - private val englishString = "Try the new %1s" private val mockId: Int = 11 @Before fun setup() { - mockkStatic("org.mozilla.fenix.settings.advanced.LocaleManagerExtensionKt") mockkObject(LocaleManager) - context = mockk(relaxed = true) - context.resources.configuration.setLocale(selectedLocale) - every { LocaleManager.getCurrentLocale(context) } returns selectedLocale + mockContext = mockk(relaxed = true) + mockContext.resources.configuration.setLocale(selectedLocale) + + every { LocaleManager.getCurrentLocale(mockContext) } returns selectedLocale + } + + @After + fun teardown() { + unmockkObject(LocaleManager) } @Test fun `getStringWithArgSafe returns selected locale for correct formatted string`() { - every { context.getString(mockId) } returns correctlyFormattedString + val correctlyFormattedString = "Incearca noul %1s" + every { mockContext.getString(mockId) } returns correctlyFormattedString - val result = context.getStringWithArgSafe(mockId, appName) + val result = mockContext.getStringWithArgSafe(mockId, appName) assertEquals("Incearca noul Firefox Preview", result) } @Test fun `getStringWithArgSafe returns English locale for incorrect formatted string`() { - + val englishString = "Try the new %1s" + val incorrectlyFormattedString = "Incearca noul %1&s" every { getSystemDefault() } returns Locale("en") - every { context.getString(mockId) } returns incorrectlyFormattedString - every { format(context.getString(mockId), appName) } returns format(englishString, appName) + every { mockContext.getString(mockId) } returns incorrectlyFormattedString + every { format(mockContext.getString(mockId), appName) } returns format(englishString, appName) - val result = context.getStringWithArgSafe(mockId, appName) + val result = mockContext.getStringWithArgSafe(mockId, appName) assertEquals("Try the new Firefox Preview", result) } + + @Test + fun `GIVEN context WHEN seeking application of context THEN send back application context`() { + val expectedAppValue = ApplicationProvider.getApplicationContext() + assertEquals(expectedAppValue, testContext.application) + } + + @Test + fun `GIVEN context WHEN requiring components THEN send back application components`() { + val expectedComponentsValue = ApplicationProvider.getApplicationContext().components + assertEquals(expectedComponentsValue, testContext.components) + } + + @Test + fun `GIVEN context WHEN getting metrics controller THEN send back metrics`() { + val expectedMetricsValue = ApplicationProvider.getApplicationContext().components.analytics.metrics + assertEquals(expectedMetricsValue, testContext.metrics) + } + + @Test + fun `GIVEN activity context WHEN make it an activity THEN return activity`() { + val mockActivity = mockk { + every { baseContext } returns null + } + val mockContext: Context = mockActivity + assertEquals(mockActivity, mockContext.asActivity()) + } + + @Test + fun `GIVEN theme wrapper context WHEN make it an activity THEN return base`() { + val mockActivity = mockk() + val mockThemeWrapper = mockk { + every { baseContext } returns mockActivity + } + val mockContext: Context = mockThemeWrapper + assertEquals(mockActivity, mockContext.asActivity()) + } + + @Test + fun `GIVEN theme wrapper context without activity base context WHEN make it an activity THEN return null`() { + val mockThemeWrapper = mockk { + every { baseContext } returns mockk() + } + val mockContext: Context = mockThemeWrapper + assertNull(mockContext.asActivity()) + } + + @Test + fun `GIVEN activity context WHEN get root view THEN return content view`() { + val rootView = mockk() + val mockActivity = mockk { + every { baseContext } returns null + every { window } returns mockk { + every { decorView } returns mockk { + every { findViewById(android.R.id.content) } returns rootView + } + } + } + assertEquals(rootView, mockActivity.getRootView()) + } + + @Test + fun `GIVEN activity context without window WHEN get root view THEN return content view`() { + val mockActivity = mockk { + every { baseContext } returns null + every { window } returns null + } + assertNull(mockActivity.getRootView()) + } + + @Test + fun `GIVEN activity context without valid content view WHEN get root view THEN return content view`() { + val mockActivity = mockk { + every { baseContext } returns null + every { window } returns mockk { + every { decorView } returns mockk { + every { findViewById(android.R.id.content) } returns mockk() + } + } + } + assertNull(mockActivity.getRootView()) + } + + @Test + fun `GIVEN context WHEN given a preference key THEN send back the right string`() { + val comparisonStr = testContext.getString(R.string.private_browsing_common_myths) + val actualStr = testContext.getPreferenceKey(R.string.private_browsing_common_myths) + assertEquals(comparisonStr, actualStr) + } } diff --git a/app/src/test/java/org/mozilla/fenix/tabtray/DefaultTabTrayControllerTest.kt b/app/src/test/java/org/mozilla/fenix/tabtray/DefaultTabTrayControllerTest.kt new file mode 100644 index 000000000..503a316eb --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabtray/DefaultTabTrayControllerTest.kt @@ -0,0 +1,165 @@ +/* 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.tabtray + +import androidx.navigation.NavController +import androidx.navigation.NavDestination +import androidx.navigation.NavDirections +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.verify +import io.mockk.verifyOrder +import mozilla.components.browser.session.Session +import mozilla.components.browser.session.SessionManager +import mozilla.components.feature.tab.collections.TabCollection +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.R +import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.components.TabCollectionStorage +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.sessionsOfType + +class DefaultTabTrayControllerTest { + + private val activity: HomeActivity = mockk(relaxed = true) + private val navController: NavController = mockk() + private val sessionManager: SessionManager = mockk(relaxed = true) + private val dismissTabTray: (() -> Unit) = mockk(relaxed = true) + private val showUndoSnackbar: ((String, SessionManager.Snapshot) -> Unit) = + mockk(relaxed = true) + private val registerCollectionStorageObserver: (() -> Unit) = mockk(relaxed = true) + private val tabCollectionStorage: TabCollectionStorage = mockk(relaxed = true) + private val tabCollection: TabCollection = mockk() + private val cachedTabCollections: List = listOf(tabCollection) + private val currentDestination: NavDestination = mockk(relaxed = true) + + private lateinit var controller: DefaultTabTrayController + + private val session = Session( + "mozilla.org", + true + ) + + private val nonPrivateSession = Session( + "mozilla.org", + false + ) + + @Before + fun setUp() { + mockkStatic("org.mozilla.fenix.ext.SessionManagerKt") + + every { activity.components.core.sessionManager } returns sessionManager + every { activity.components.core.tabCollectionStorage } returns tabCollectionStorage + every { sessionManager.sessionsOfType(private = true) } returns listOf(session).asSequence() + every { sessionManager.sessionsOfType(private = false) } returns listOf(nonPrivateSession).asSequence() + every { sessionManager.createSessionSnapshot(any()) } returns SessionManager.Snapshot.Item( + session + ) + every { sessionManager.remove(any()) } just Runs + every { tabCollectionStorage.cachedTabCollections } returns cachedTabCollections + every { sessionManager.selectedSession } returns nonPrivateSession + every { navController.navigate(any()) } just Runs + every { navController.currentDestination } returns currentDestination + every { currentDestination.id } returns R.id.browserFragment + + controller = DefaultTabTrayController( + activity = activity, + navController = navController, + dismissTabTray = dismissTabTray, + showUndoSnackbar = showUndoSnackbar, + registerCollectionStorageObserver = registerCollectionStorageObserver + ) + } + + @Test + fun onNewTabTapped() { + controller.onNewTabTapped(private = false) + + verifyOrder { + activity.browsingModeManager.mode = BrowsingMode.fromBoolean(false) + navController.navigate( + TabTrayDialogFragmentDirections.actionGlobalHome( + focusOnAddressBar = true + ) + ) + dismissTabTray() + } + + controller.onNewTabTapped(private = true) + + verifyOrder { + activity.browsingModeManager.mode = BrowsingMode.fromBoolean(true) + navController.navigate( + TabTrayDialogFragmentDirections.actionGlobalHome( + focusOnAddressBar = true + ) + ) + dismissTabTray() + } + } + + @Test + fun onTabTrayDismissed() { + controller.onTabTrayDismissed() + + verify { + dismissTabTray() + } + } + + @Test + fun onSaveToCollectionClicked() { + val navDirectionsSlot = slot() + every { navController.navigate(capture(navDirectionsSlot)) } just Runs + + controller.onSaveToCollectionClicked() + verify { + registerCollectionStorageObserver() + navController.navigate(capture(navDirectionsSlot)) + } + + assertTrue(navDirectionsSlot.isCaptured) + assertEquals( + R.id.action_global_collectionCreationFragment, + navDirectionsSlot.captured.actionId + ) + } + + @Test + fun onShareTabsClicked() { + val navDirectionsSlot = slot() + every { navController.navigate(capture(navDirectionsSlot)) } just Runs + + controller.onShareTabsClicked(private = false) + + verify { + navController.navigate(capture(navDirectionsSlot)) + } + + assertTrue(navDirectionsSlot.isCaptured) + assertEquals(R.id.action_global_shareFragment, navDirectionsSlot.captured.actionId) + } + + @Test + fun onCloseAllTabsClicked() { + controller.onCloseAllTabsClicked(private = false) + val snackbarMessage = activity.getString(R.string.snackbar_tabs_closed) + + verify { + sessionManager.createSessionSnapshot(nonPrivateSession) + sessionManager.remove(nonPrivateSession) + showUndoSnackbar(snackbarMessage, any()) + } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractorTest.kt new file mode 100644 index 000000000..4ecbefe15 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/tabtray/TabTrayFragmentInteractorTest.kt @@ -0,0 +1,53 @@ +/* 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.tabtray + +import io.mockk.mockk +import io.mockk.verify +import org.junit.Test + +class TabTrayFragmentInteractorTest { + private val controller = mockk(relaxed = true) + private val interactor = TabTrayFragmentInteractor(controller) + + @Test + fun onNewTabTapped() { + interactor.onNewTabTapped(private = true) + verify { controller.onNewTabTapped(true) } + + interactor.onNewTabTapped(private = false) + verify { controller.onNewTabTapped(false) } + } + + @Test + fun onTabTrayDismissed() { + interactor.onTabTrayDismissed() + verify { controller.onTabTrayDismissed() } + } + + @Test + fun onShareTabsClicked() { + interactor.onShareTabsClicked(private = true) + verify { controller.onShareTabsClicked(true) } + + interactor.onShareTabsClicked(private = false) + verify { controller.onShareTabsClicked(false) } + } + + @Test + fun onSaveToCollectionClicked() { + interactor.onSaveToCollectionClicked() + verify { controller.onSaveToCollectionClicked() } + } + + @Test + fun onCloseAllTabsClicked() { + interactor.onCloseAllTabsClicked(private = false) + verify { controller.onCloseAllTabsClicked(false) } + + interactor.onCloseAllTabsClicked(private = true) + verify { controller.onCloseAllTabsClicked(true) } + } +} diff --git a/buildSrc/src/main/java/AndroidComponents.kt b/buildSrc/src/main/java/AndroidComponents.kt index 8ee7a7637..1afcdc02d 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 = "47.0.20200618130101" + const val VERSION = "47.0.20200622130109" }