1
0
Fork 0

Copione merged onto master
continuous-integration/drone/push Build is passing Details

master
blallo 2020-06-14 00:00:33 +02:00
commit 3e5e5865a5
26 changed files with 301 additions and 116 deletions

View File

@ -509,6 +509,7 @@ dependencies {
implementation Deps.mozilla_service_location
implementation Deps.mozilla_support_base
implementation Deps.mozilla_support_images
implementation Deps.mozilla_support_ktx
implementation Deps.mozilla_support_rustlog
implementation Deps.mozilla_support_utils

View File

@ -0,0 +1,64 @@
/* 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.ui
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
/**
* Test Suite that contains tests defined as part of the Smoke and Sanity check defined in Test rail.
* These tests will verify different functionalities of the app as a way to quickly detect regressions in main areas
*/
class SmokeTest {
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityTestRule = HomeActivityTestRule()
@Before
fun setUp() {
mockWebServer = MockWebServer().apply {
setDispatcher(AndroidAssetDispatcher())
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@Test
fun verifyBasicNavigationToolbarFunctionality() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
verifyPageContent(defaultWebPage.content)
verifyNavURLBarItems()
}.openNavigationToolbar {
}.goBackToWebsite {
// Check disabled due to intermittent failures
// verifyPageContent(defaultWebPage.content)
}.openTabDrawer {
verifyExistingTabList()
}.openHomeScreen {
verifyHomeScreen()
}
}
}
}

View File

@ -10,6 +10,7 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
@ -17,6 +18,7 @@ import androidx.test.espresso.intent.matcher.BundleMatchers
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
@ -134,6 +136,32 @@ class BrowserRobot {
)
}
fun verifyNavURLBar() = assertNavURLBar()
fun verifySecureConnectionLockIcon() = assertSecureConnectionLockIcon()
fun verifyEnhancedTrackingProtectionSwitch() = assertEnhancedTrackingProtectionSwitch()
fun verifyProtectionSettingsButton() = assertProtectionSettingsButton()
fun verifyEnhancedTrackingOptions() {
clickEnhancedTrackingProtectionPanel()
verifyEnhancedTrackingProtectionSwitch()
verifyProtectionSettingsButton()
}
fun verifyMenuButton() = assertMenuButton()
fun verifyNavURLBarItems() {
verifyEnhancedTrackingOptions()
pressBack()
waitingTime
verifySecureConnectionLockIcon()
verifyTabCounter("1")
verifyNavURLBar()
verifyMenuButton()
}
fun verifyNoLinkImageContextMenuItems(containsTitle: String) {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mDevice.waitNotNull(Until.findObject(By.textContains(containsTitle)))
@ -147,6 +175,8 @@ class BrowserRobot {
)
}
fun clickEnhancedTrackingProtectionPanel() = enhancedTrackingProtectionPanel().click()
fun clickContextOpenLinkInNewTab() {
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mDevice.waitNotNull(
@ -336,8 +366,7 @@ class BrowserRobot {
}
fun openTabDrawer(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
mDevice.waitForIdle()
mDevice.waitForIdle(waitingTime)
tabsCounter().click()
mDevice.waitNotNull(
@ -372,7 +401,34 @@ fun dismissTrackingOnboarding() {
fun navURLBar() = onView(withId(R.id.mozac_browser_toolbar_url_view))
private fun tabsCounter() = onView(withId(R.id.mozac_browser_toolbar_browser_actions))
private fun assertNavURLBar() = navURLBar()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
fun enhancedTrackingProtectionPanel() = onView(withId(R.id.mozac_browser_toolbar_tracking_protection_indicator))
private fun assertEnhancedTrackingProtectionSwitch() {
withText(R.id.trackingProtectionSwitch)
.matches(withEffectiveVisibility(Visibility.VISIBLE))
}
private fun assertProtectionSettingsButton() {
onView(withId(R.id.protection_settings))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertSecureConnectionLockIcon() {
onView(withId(R.id.mozac_browser_toolbar_security_indicator))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun menuButton() = onView(withId(R.id.icon))
private fun assertMenuButton() {
menuButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun tabsCounter() = onView(withId(R.id.counter_box))
private fun mediaPlayerPlayButton() =
mDevice.findObject(

View File

@ -514,13 +514,13 @@ private fun assertCollectionsHeader() =
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertNoCollectionsHeader() =
onView(allOf(withText("No collections")))
onView(allOf(withText("Collect the things that matter to you")))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertNoCollectionsText() =
onView(
allOf(
withText("Collect the things that matter to you. To start, save open tabs to a new collection.")
withText("Group together similar searches, sites, and tabs for quick access later.")
)
)
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))

View File

@ -24,6 +24,7 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import org.hamcrest.CoreMatchers.anyOf
import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.CoreMatchers.not
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.SessionLoadedIdlingResource
@ -51,6 +52,24 @@ class NavigationToolbarRobot {
private lateinit var sessionLoadedIdlingResource: SessionLoadedIdlingResource
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
fun goBackToWebsite(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitNotNull(
Until.findObject(By.res("org.mozilla.fenix.debug:id/toolbar")),
waitingTime
)
urlBar().click()
mDevice.waitNotNull(
Until.findObject(By.res("org.mozilla.fenix.debug:id/mozac_browser_toolbar_edit_url_view")),
waitingTime
)
clearAddressBar().click()
awesomeBar().check((matches(withText(containsString("")))))
goBackButton()
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun enterURLAndEnterToBrowser(
url: Uri,
interact: BrowserRobot.() -> Unit

View File

@ -34,6 +34,7 @@ import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.state.WebExtensionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.tabstray.BrowserTabsTray
import mozilla.components.browser.thumbnails.loader.ThumbnailLoader
import mozilla.components.concept.engine.EngineView
import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.feature.contextmenu.DefaultSelectionActionDelegate
@ -242,13 +243,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
stackFromEnd = true
}
val adapter = FenixTabsAdapter(context)
BrowserTabsTray(
context,
attrs,
tabsAdapter = adapter,
layout = layout
)
val thumbnailLoader = ThumbnailLoader(components.core.thumbnailStorage)
val adapter = FenixTabsAdapter(context, thumbnailLoader)
BrowserTabsTray(context, attrs, 0, adapter, layout)
}
else -> super.onCreateView(parent, name, context, attrs)
}

View File

@ -15,6 +15,7 @@ import android.view.ViewGroup
import androidx.annotation.CallSuper
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
@ -24,15 +25,19 @@ import kotlinx.android.synthetic.main.fragment_browser.*
import kotlinx.android.synthetic.main.fragment_browser.view.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.session.runWithSessionIdOrSelected
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.selector.findTabOrCustomTabOrSelectedTab
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.content.DownloadState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.thumbnails.BrowserThumbnails
@ -59,12 +64,14 @@ import mozilla.components.feature.session.behavior.EngineViewBottomBehavior
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissionsFeature
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.service.sync.logins.DefaultLoginValidationDelegate
import mozilla.components.support.base.feature.PermissionsFeature
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.view.exitImmersiveModeIfNeeded
import mozilla.components.support.ktx.android.view.hideKeyboard
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.IntentReceiverActivity
@ -105,6 +112,7 @@ import java.lang.ref.WeakReference
* This class only contains shared code focused on the main browsing content.
* UI code specific to the app or to custom tabs can be found in the subclasses.
*/
@ExperimentalCoroutinesApi
@Suppress("TooManyFunctions", "LargeClass")
abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, SessionManager.Observer {
protected lateinit var browserFragmentStore: BrowserFragmentStore
@ -143,7 +151,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
private var browserInitialized: Boolean = false
private var initUIJob: Job? = null
private var enteredPip = false
protected var webAppToolbarShouldBeVisible = true
private val sharedViewModel: SharedViewModel by activityViewModels()
@ -383,11 +391,10 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
resumeDownloadDialogState(session, store, view, context, toolbarHeight)
pipFeature = PictureInPictureFeature(
requireComponents.core.sessionManager,
requireActivity(),
requireComponents.analytics.crashReporter,
customTabSessionId,
::pipModeChanged
store = store,
activity = requireActivity(),
crashReporting = context.components.analytics.crashReporter,
tabId = customTabSessionId
)
appLinksFeature.set(
@ -528,6 +535,12 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
}
}, owner = viewLifecycleOwner)
store.flowScoped(viewLifecycleOwner) { flow ->
flow.mapNotNull { state -> state.findTabOrCustomTabOrSelectedTab(customTabSessionId) }
.ifChanged { tab -> tab.content.pictureInPictureEnabled }
.collect { tab -> pipModeChanged(tab) }
}
@Suppress("ConstantConditionIf")
if (FeatureFlags.pullToRefreshEnabled) {
val primaryTextColor =
@ -694,11 +707,11 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
@CallSuper
final override fun onPause() {
super.onPause()
val session = requireComponents.core.store.state.findTabOrCustomTabOrSelectedTab(customTabSessionId)
// If we didn't enter PiP, exit full screen on pause
if (!enteredPip && fullScreenFeature.onBackPressed()) {
if (session?.content?.pictureInPictureEnabled == false && fullScreenFeature.onBackPressed()) {
fullScreenChanged(false)
}
enteredPip = false
if (findNavController().currentDestination?.id != R.id.searchFragment) {
view?.hideKeyboard()
}
@ -894,21 +907,13 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
}
}
override fun onHomePressed(): Boolean {
if (pipFeature?.onHomePressed() == true) {
enteredPip = true
return true
}
return false
}
override fun onHomePressed() = pipFeature?.onHomePressed() ?: false
private fun pipModeChanged(enabled: Boolean) {
val fullScreenMode =
requireComponents.core.sessionManager.runWithSessionIdOrSelected(customTabSessionId) { session ->
session.fullScreenMode
}
// If we're exiting PIP mode and we're in fullscreen mode, then we should exit fullscreen mode as well.
if (!enabled && fullScreenMode) {
/**
* Exit fullscreen mode when exiting PIP mode
*/
private fun pipModeChanged(session: SessionState) {
if (!session.content.pictureInPictureEnabled && session.content.fullScreen) {
onBackPressed()
fullScreenChanged(false)
}
@ -938,7 +943,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
.setText(getString(R.string.full_screen_notification))
.show()
activity?.enterToImmersiveMode()
browserToolbarView.view.visibility = View.GONE
browserToolbarView.view.isVisible = false
engineView.setDynamicToolbarMaxHeight(0)
browserToolbarView.expand()
@ -949,19 +954,14 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
(activity as? HomeActivity)?.let { activity ->
activity.themeManager.applyStatusBarTheme(activity)
}
browserToolbarView.view.visibility = View.VISIBLE
val toolbarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height)
engineView.setDynamicToolbarMaxHeight(toolbarHeight)
if (webAppToolbarShouldBeVisible) {
browserToolbarView.view.isVisible = true
val toolbarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height)
engineView.setDynamicToolbarMaxHeight(toolbarHeight)
}
}
}
private fun getListOfSessions(
private: Boolean = (activity as HomeActivity).browsingModeManager.mode.isPrivate
): List<Session> {
return requireComponents.core.sessionManager.sessionsOfType(private = private)
.toList()
}
/*
* Dereference these views when the fragment view is destroyed to prevent memory leaks
*/

View File

@ -14,7 +14,6 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_browser.*
import kotlinx.android.synthetic.main.fragment_browser.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.session.Session

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix.components
import android.content.Context
import android.os.StrictMode
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import androidx.paging.DataSource
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
@ -71,7 +72,7 @@ class TabCollectionStorage(
}
fun getCollections(limit: Int = 20): LiveData<List<TabCollection>> {
return collectionStorage.getCollections(limit)
return collectionStorage.getCollections(limit).asLiveData()
}
fun getCollectionsPaged(): DataSource.Factory<Int, TabCollection> {

View File

@ -6,6 +6,7 @@ package org.mozilla.fenix.components
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -41,7 +42,7 @@ class TopSiteStorage(private val context: Context) {
* Returns a [LiveData] list of all the [TopSite] instances.
*/
fun getTopSites(): LiveData<List<TopSite>> {
return storage.getTopSites()
return storage.getTopSites().asLiveData()
}
/**

View File

@ -147,7 +147,7 @@ class DefaultBrowserToolbarController(
if (sessionManager.sessionsOfType(it.private).count() == 1) {
// The tab tray always returns to normal mode so do that here too
(activity as HomeActivity).browsingModeManager.mode = BrowsingMode.Normal
navController.navigate(BrowserFragmentDirections.actionGlobalHome(it.id))
navController.navigate(BrowserFragmentDirections.actionGlobalHome(sessionToDelete = it.id))
} else {
onCloseTab.invoke(it)
activity.components.useCases.tabsUseCases.removeTab.invoke(it)

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix.customtabs
import android.content.Context
import android.content.Intent
import android.view.View
import androidx.core.view.isVisible
import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.component_browser_top_toolbar.*
import kotlinx.android.synthetic.main.fragment_browser.*
@ -17,15 +18,12 @@ import mozilla.components.concept.engine.manifest.WebAppManifestParser
import mozilla.components.concept.engine.manifest.getOrNull
import mozilla.components.feature.contextmenu.ContextMenuCandidate
import mozilla.components.feature.customtabs.CustomTabWindowFeature
import mozilla.components.feature.pwa.ext.getTrustedScope
import mozilla.components.feature.pwa.ext.trustedOrigins
import mozilla.components.feature.pwa.feature.ManifestUpdateFeature
import mozilla.components.feature.pwa.feature.WebAppActivityFeature
import mozilla.components.feature.pwa.feature.WebAppHideToolbarFeature
import mozilla.components.feature.pwa.feature.WebAppSiteControlsFeature
import mozilla.components.feature.session.TrackingProtectionUseCases
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.arch.lifecycle.addObservers
@ -60,7 +58,6 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
val manifest = args.webAppManifest?.let { json ->
WebAppManifestParser().parse(json).getOrNull()
}
val trustedScopes = listOfNotNull(manifest?.getTrustedScope())
customTabSessionId?.let { customTabSessionId ->
customTabsIntegration.set(
@ -99,11 +96,13 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
hideToolbarFeature.set(
feature = WebAppHideToolbarFeature(
requireComponents.core.sessionManager,
toolbar,
customTabSessionId,
trustedScopes
store = requireComponents.core.store,
customTabsStore = requireComponents.core.customTabsStore,
tabId = customTabSessionId,
manifest = manifest
) { toolbarVisible ->
browserToolbarView.view.isVisible = toolbarVisible
webAppToolbarShouldBeVisible = toolbarVisible
if (!toolbarVisible) { engineView.setDynamicToolbarMaxHeight(0) }
},
owner = this,
@ -151,17 +150,6 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
)
}
}
consumeFrom(components.core.customTabsStore) { state ->
getSessionById()
?.let { session -> session.customTabConfig?.sessionToken }
?.let { token -> state.tabs[token] }
?.let { tabState ->
hideToolbarFeature.withFeature {
it.onTrustedScopesChange(tabState.trustedOrigins)
}
}
}
}
}

View File

@ -15,6 +15,7 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.widget.Button
import android.widget.LinearLayout
import android.widget.PopupWindow
@ -107,12 +108,12 @@ import kotlin.math.min
@SuppressWarnings("TooManyFunctions", "LargeClass")
class HomeFragment : Fragment() {
private val args by navArgs<HomeFragmentArgs>()
private val homeViewModel: HomeScreenViewModel by viewModels {
ViewModelProvider.AndroidViewModelFactory(requireActivity().application)
}
private val args by navArgs<HomeFragmentArgs>()
private val snackbarAnchorView: View?
get() {
return if (requireContext().settings().shouldUseBottomToolbar) {
@ -359,6 +360,15 @@ class HomeFragment : Fragment() {
SearchWidgetCFR(view.context) { view.toolbar_wrapper }.displayIfNecessary()
}
}
if (view.context.settings().accessibilityServicesEnabled && args.focusOnAddressBar) {
// We cannot put this in the fragment_home.xml file as it breaks tests
view.toolbar_wrapper.isFocusableInTouchMode = true
viewLifecycleOwner.lifecycleScope.launch {
view.toolbar_wrapper?.requestFocus()
view.toolbar_wrapper?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
}
}
}
override fun onDestroyView() {

View File

@ -21,8 +21,8 @@ import org.mozilla.fenix.home.OnboardingState
import org.mozilla.fenix.components.tips.Tip
val noCollectionMessage = AdapterItem.NoContentMessage(
R.string.no_collections_header,
R.string.collections_description
R.string.no_collections_header1,
R.string.no_collections_description1
)
// This method got a little complex with the addition of the tab tray feature flag

View File

@ -8,17 +8,20 @@ import android.content.Context
import android.view.LayoutInflater
import mozilla.components.browser.tabstray.TabsAdapter
import mozilla.components.concept.tabstray.Tabs
import mozilla.components.support.images.loader.ImageLoader
import org.mozilla.fenix.R
class FenixTabsAdapter(
context: Context
context: Context,
imageLoader: ImageLoader
) : TabsAdapter(
viewHolderProvider = { parentView, _ ->
TabTrayViewHolder(
LayoutInflater.from(context).inflate(
R.layout.tab_tray_item,
parentView,
false)
false),
imageLoader
)
}
) {

View File

@ -110,7 +110,8 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), TabTrayInteractor {
view.tabLayout,
this,
isPrivate,
requireContext().resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
requireContext().resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE,
viewLifecycleOwner.lifecycleScope
) { tabsFeature.get()?.filterTabs(it) }
tabsFeature.set(
@ -196,7 +197,7 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), TabTrayInteractor {
override fun onNewTabTapped(private: Boolean) {
(activity as HomeActivity).browsingModeManager.mode = BrowsingMode.fromBoolean(private)
findNavController().popBackStack(R.id.homeFragment, false)
findNavController().navigate(TabTrayDialogFragmentDirections.actionGlobalHome(focusOnAddressBar = true))
dismissAllowingStateLoss()
}
@ -311,7 +312,9 @@ class TabTrayDialogFragment : AppCompatDialogFragment(), TabTrayInteractor {
.setText(requireContext().getString(R.string.create_collection_tabs_saved))
.setAction(requireContext().getString(R.string.create_collection_view)) {
dismissAllowingStateLoss()
findNavController().navigate(TabTrayDialogFragmentDirections.actionGlobalHome())
findNavController().navigate(
TabTrayDialogFragmentDirections.actionGlobalHome(focusOnAddressBar = false)
)
}
snackbar.view.elevation = ELEVATION

View File

@ -8,14 +8,19 @@ import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleCoroutineScope
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.tabs.TabLayout
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_tabstray.view.*
import kotlinx.android.synthetic.main.component_tabstray_fab.view.*
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import mozilla.components.browser.menu.BrowserMenuBuilder
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
import mozilla.components.browser.state.selector.normalTabs
@ -25,6 +30,7 @@ import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.tabstray.BrowserTabsTray
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
interface TabTrayInteractor {
fun onNewTabTapped(private: Boolean)
@ -36,11 +42,13 @@ interface TabTrayInteractor {
/**
* View that contains and configures the BrowserAwesomeBar
*/
@Suppress("LongParameterList")
class TabTrayView(
private val container: ViewGroup,
private val interactor: TabTrayInteractor,
isPrivate: Boolean,
startingInLandscape: Boolean,
lifecycleScope: LifecycleCoroutineScope,
private val filterTabs: ((TabSessionState) -> Boolean) -> Unit
) : LayoutContainer, TabLayout.OnTabSelectedListener {
val fabView = LayoutInflater.from(container.context)
@ -61,14 +69,18 @@ class TabTrayView(
get() = container
init {
val hasAccessibilityEnabled = view.context.settings().accessibilityServicesEnabled
toggleFabText(isPrivate)
behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
if (slideOffset >= SLIDE_OFFSET) {
fabView.new_tab_button.show()
} else {
fabView.new_tab_button.hide()
if (!hasAccessibilityEnabled) {
if (slideOffset >= SLIDE_OFFSET) {
fabView.new_tab_button.show()
} else {
fabView.new_tab_button.hide()
}
}
}
@ -113,6 +125,19 @@ class TabTrayView(
if (!hasLoaded) {
hasLoaded = true
tray.layoutManager?.scrollToPosition(selectedBrowserTabIndex)
if (view.context.settings().accessibilityServicesEnabled) {
lifecycleScope.launch {
delay(SELECTION_DELAY.toLong())
lifecycleScope.launch(Main) {
tray.layoutManager?.findViewByPosition(selectedBrowserTabIndex)
?.requestFocus()
tray.layoutManager?.findViewByPosition(selectedBrowserTabIndex)
?.sendAccessibilityEvent(
AccessibilityEvent.TYPE_VIEW_FOCUSED
)
}
}
}
}
}
}
@ -142,8 +167,18 @@ class TabTrayView(
}
}
fabView.new_tab_button.setOnClickListener {
interactor.onNewTabTapped(isPrivateModeSelected)
view.tab_tray_new_tab.apply {
isVisible = hasAccessibilityEnabled
setOnClickListener {
interactor.onNewTabTapped(isPrivateModeSelected)
}
}
fabView.new_tab_button.apply {
isVisible = !hasAccessibilityEnabled
setOnClickListener {
interactor.onNewTabTapped(isPrivateModeSelected)
}
}
}
@ -214,6 +249,7 @@ class TabTrayView(
private const val PRIVATE_TAB_ID = 1
private const val EXPAND_AT_SIZE = 3
private const val SLIDE_OFFSET = 0
private const val SELECTION_DELAY = 500
}
}

View File

@ -6,7 +6,6 @@ package org.mozilla.fenix.tabtray
import android.view.View
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.appcompat.widget.AppCompatImageButton
@ -20,6 +19,7 @@ import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.feature.media.ext.pauseIfPlaying
import mozilla.components.feature.media.ext.playIfPaused
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.images.loader.ImageLoader
import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
@ -35,9 +35,9 @@ import org.mozilla.fenix.ext.toTab
*/
class TabTrayViewHolder(
itemView: View,
private val imageLoader: ImageLoader,
val getSelectedTabId: () -> String? = { itemView.context.components.core.store.state.selectedTabId }
) : TabViewHolder(itemView) {
private val iconView: ImageView? = itemView.findViewById(R.id.mozac_browser_tabstray_icon)
private val titleView: TextView = itemView.findViewById(R.id.mozac_browser_tabstray_title)
private val closeView: AppCompatImageButton =
itemView.findViewById(R.id.mozac_browser_tabstray_close)
@ -69,13 +69,8 @@ class TabTrayViewHolder(
if (tab.thumbnail != null) {
thumbnailView.setImageBitmap(tab.thumbnail)
thumbnailView.visibility = View.VISIBLE
iconView?.visibility = View.INVISIBLE
} else {
thumbnailView.setImageBitmap(null)
iconView?.setImageBitmap(tab.icon)
thumbnailView.visibility = View.INVISIBLE
iconView?.visibility = View.VISIBLE
imageLoader.loadIntoView(thumbnailView, tab.id)
}
// Media state

View File

@ -70,6 +70,18 @@
</com.google.android.material.tabs.TabLayout>
<ImageButton
android:id="@+id/tab_tray_new_tab"
android:layout_width="48dp"
android:layout_height="48dp"
android:visibility="gone"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/add_tab"
app:srcCompat="@drawable/ic_new"
app:layout_constraintTop_toTopOf="@id/tab_layout"
app:layout_constraintEnd_toStartOf="@id/tab_tray_overflow"
app:layout_constraintBottom_toBottomOf="@id/tab_layout" />
<ImageButton
android:id="@+id/tab_tray_overflow"
android:layout_width="48dp"

View File

@ -7,7 +7,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/tab_item"
android:layout_width="match_parent"
android:layout_height="88dp">
android:layout_height="88dp"
android:focusable="true"
android:focusableInTouchMode="true">
<ImageButton
android:id="@+id/play_pause_button"
@ -33,15 +35,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/mozac_browser_tabstray_icon"
android:layout_width="50dp"
android:layout_height="28dp"
android:layout_marginStart="21dp"
android:layout_marginTop="20dp"
android:importantForAccessibility="no"
android:visibility="invisible" />
<mozilla.components.browser.tabstray.thumbnail.TabThumbnailView
android:id="@+id/mozac_browser_tabstray_thumbnail"
android:layout_width="match_parent"

View File

@ -66,6 +66,11 @@
app:destination="@id/browserFragment"
app:exitAnim="@anim/zoom_in_fade"
app:popEnterAnim="@anim/zoom_out_fade" />
<argument
android:name="focusOnAddressBar"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="session_to_delete"
app:argType="string"

View File

@ -15,8 +15,6 @@
<string name="content_description_disable_private_browsing_button">Disable private browsing</string>
<!-- Placeholder text shown in the search bar before a user enters text -->
<string name="search_hint">Search or enter address</string>
<!-- No Open Tabs Message Header -->
<string name="no_open_tabs_header_2">No open tabs</string>
<!-- No Open Tabs Message Description -->
<string name="no_open_tabs_description">Your open tabs will be shown here.</string>
<!-- No Private Tabs Message Description -->
@ -477,7 +475,7 @@
<!-- Open tabs menu item to share all tabs -->
<string name="tabs_menu_share_tabs">Share tabs</string>
<!-- Open tabs menu item to save tabs to collection -->
<string name="tabs_menu_save_to_collection">Save to collection</string>
<string name="tabs_menu_save_to_collection1">Save tabs to collection</string>
<!-- Content description (not visible, for screen readers etc.): Opens the tab menu when pressed -->
<string name="tab_menu">Tab menu</string>
<!-- Tab menu item to share the tab -->
@ -686,16 +684,14 @@
<string name="delete_browsing_data_quit_off">Off</string>
<!-- Collections -->
<!-- Label to describe what collections are to a new user without any collections -->
<string name="collections_description">Collect the things that matter to you. To start, save open tabs to a new collection.</string>
<!-- Collections header on home fragment -->
<string name="collections_header">Collections</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">Collection menu</string>
<!-- No Open Tabs Message Header -->
<string name="no_collections_header">No collections</string>
<!-- No Open Tabs Message Description -->
<string name="no_collections_description">Your collections will be shown here.</string>
<string name="no_collections_header1">Collect the things that matter to you</string>
<!-- Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Group together similar searches, sites, and tabs for quick access later.</string>
<!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Select Tabs</string>
<!-- Title for the "select collection" step of the collection creator -->

View File

@ -569,7 +569,7 @@ class DefaultBrowserToolbarControllerTest {
every { activity.browsingModeManager } returns browsingModeManager
controller.handleTabCounterItemInteraction(item)
verify { navController.navigate(BrowserFragmentDirections.actionGlobalHome("1")) }
verify { navController.navigate(BrowserFragmentDirections.actionGlobalHome(sessionToDelete = "1")) }
assertEquals(BrowsingMode.Normal, browsingModeManager.mode)
}

View File

@ -13,6 +13,7 @@ import io.mockk.mockk
import io.mockk.spyk
import mozilla.components.browser.toolbar.MAX_URI_LENGTH
import mozilla.components.concept.tabstray.Tab
import mozilla.components.support.images.loader.ImageLoader
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@ -25,15 +26,18 @@ class TabTrayViewHolderTest {
@Test
fun `extremely long URLs are truncated to prevent slowing down the UI`() {
val view = LayoutInflater.from(ApplicationProvider.getApplicationContext()).inflate(
R.layout.tab_tray_item, null, false)
val tabViewHolder = spyk(TabTrayViewHolder(view) { null })
R.layout.tab_tray_item, null, false
)
val imageLoader: ImageLoader = mockk()
every { imageLoader.loadIntoView(any(), any(), any(), any()) } just Runs
val tabViewHolder = spyk(TabTrayViewHolder(view, imageLoader) { null })
every { tabViewHolder.updateBackgroundColor(false) } just Runs
val extremelyLongUrl = "m".repeat(MAX_URI_LENGTH + 1)
val tab = Tab(
id = "123",
url = extremelyLongUrl)
url = extremelyLongUrl
)
tabViewHolder.bind(tab, false, mockk())
assertEquals("m".repeat(MAX_URI_LENGTH), tabViewHolder.urlView?.text)

View File

@ -3,5 +3,5 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
object AndroidComponents {
const val VERSION = "45.0.20200610130052"
const val VERSION = "45.0.20200613130120"
}

View File

@ -133,6 +133,7 @@ object Deps {
const val mozilla_ui_publicsuffixlist = "org.mozilla.components:lib-publicsuffixlist:${Versions.mozilla_android_components}"
const val mozilla_support_base = "org.mozilla.components:support-base:${Versions.mozilla_android_components}"
const val mozilla_support_images = "org.mozilla.components:support-images:${Versions.mozilla_android_components}"
const val mozilla_support_ktx = "org.mozilla.components:support-ktx:${Versions.mozilla_android_components}"
const val mozilla_support_rusthttp = "org.mozilla.components:support-rusthttp:${Versions.mozilla_android_components}"
const val mozilla_support_rustlog = "org.mozilla.components:support-rustlog:${Versions.mozilla_android_components}"