1
0
Fork 0

For #11045: Add reader mode to urlView

master
Sawyer Blatz 2020-06-04 13:21:20 -07:00
parent 1a19b06227
commit e9189dc089
12 changed files with 81 additions and 62 deletions

View File

@ -10,6 +10,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
@ -17,6 +18,8 @@ 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
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.feature.app.links.AppLinksUseCases
import mozilla.components.feature.contextmenu.ContextMenuCandidate
import mozilla.components.feature.readerview.ReaderViewFeature
@ -52,6 +55,8 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
private val windowFeature = ViewBoundFeatureWrapper<WindowFeature>()
private val searchFeature = ViewBoundFeatureWrapper<SearchFeature>()
private var readerModeAvailable = false
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -68,17 +73,39 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
val components = context.components
return super.initializeUI(view)?.also {
val readerModeAction =
BrowserToolbar.ToggleButton(
image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_readermode)!!,
imageSelected = ContextCompat.getDrawable(requireContext(), R.drawable.ic_readermode_selected)!!,
contentDescription = requireContext().getString(R.string.browser_menu_read),
contentDescriptionSelected = requireContext().getString(R.string.browser_menu_read_close),
visible = {
readerModeAvailable
},
selected = getSessionById()?.let {
activity?.components?.core?.store?.state?.findTab(it.id)?.readerState?.active
} ?: false,
listener = browserInteractor::onReaderModePressed
)
browserToolbarView.view.addPageAction(readerModeAction)
readerViewFeature.set(
feature = ReaderViewFeature(
context,
components.core.engine,
components.core.store,
view.readerViewControlsBar
) { available, _ ->
) { available, active ->
if (available) {
components.analytics.metrics.track(Event.ReaderModeAvailable)
}
readerModeAvailable = available
readerModeAction.setSelected(active)
runIfFragmentIsAttached {
browserToolbarView.view.invalidateActions()
browserToolbarView.toolbarIntegration.invalidateMenu()
}
},

View File

@ -35,4 +35,8 @@ open class BrowserInteractor(
override fun onScrolled(offset: Int) {
browserToolbarController.handleScroll(offset)
}
override fun onReaderModePressed(enabled: Boolean) {
browserToolbarController.handleReaderModePressed(enabled)
}
}

View File

@ -53,9 +53,10 @@ interface BrowserToolbarController {
fun handleToolbarClick()
fun handleTabCounterClick()
fun handleBrowserMenuDismissed(lowPrioHighlightItems: List<ToolbarMenu.Item>)
fun handleReaderModePressed(enabled: Boolean)
}
@Suppress("LargeClass")
@Suppress("LargeClass", "TooManyFunctions")
class DefaultBrowserToolbarController(
private val activity: Activity,
private val navController: NavController,
@ -124,6 +125,14 @@ class DefaultBrowserToolbarController(
onTabCounterClicked.invoke()
}
override fun handleReaderModePressed(enabled: Boolean) {
if (enabled) {
readerModeController.showReaderView()
} else {
readerModeController.hideReaderView()
}
}
override fun handleBrowserMenuDismissed(lowPrioHighlightItems: List<ToolbarMenu.Item>) {
lowPrioHighlightItems.forEach {
when (it) {

View File

@ -52,6 +52,7 @@ interface BrowserToolbarViewInteractor {
fun onTabCounterClicked()
fun onBrowserMenuDismissed(lowPrioHighlightItems: List<ToolbarMenu.Item>)
fun onScrolled(offset: Int)
fun onReaderModePressed(enabled: Boolean)
}
@SuppressWarnings("LargeClass")
class BrowserToolbarView(

View File

@ -15,7 +15,6 @@ import mozilla.components.browser.menu.BrowserMenuHighlight
import mozilla.components.browser.menu.WebExtensionBrowserMenuBuilder
import mozilla.components.browser.menu.item.BrowserMenuDivider
import mozilla.components.browser.menu.item.BrowserMenuHighlightableItem
import mozilla.components.browser.menu.item.BrowserMenuHighlightableSwitch
import mozilla.components.browser.menu.item.BrowserMenuImageSwitch
import mozilla.components.browser.menu.item.BrowserMenuImageText
import mozilla.components.browser.menu.item.BrowserMenuItemToolbar
@ -143,9 +142,6 @@ class DefaultToolbarMenu(
if (canInstall() && installToHomescreen.isHighlighted()) {
lowPrioHighlightItems.add(ToolbarMenu.Item.InstallToHomeScreen)
}
if (shouldShowReaderMode() && readerMode.isHighlighted()) {
lowPrioHighlightItems.add(ToolbarMenu.Item.ReaderMode(false))
}
if (shouldShowOpenInApp() && openInApp.isHighlighted()) {
lowPrioHighlightItems.add(ToolbarMenu.Item.OpenInApp)
}
@ -161,10 +157,6 @@ class DefaultToolbarMenu(
session != null && context.components.useCases.webAppUseCases.isPinningSupported() &&
context.components.useCases.webAppUseCases.isInstallable()
private fun shouldShowReaderMode(): Boolean = session?.let {
store.state.findTab(it.id)?.readerState?.readerable
} ?: false
private fun shouldShowOpenInApp(): Boolean = session?.let { session ->
val appLink = context.components.useCases.appLinksUseCases.appLinkRedirect
appLink(session.url).hasExternalApp()
@ -196,7 +188,6 @@ class DefaultToolbarMenu(
if (shouldShowSaveToCollection) saveToCollection else null,
desktopMode,
openInApp.apply { visible = ::shouldShowOpenInApp },
readerMode.apply { visible = ::shouldShowReaderMode },
readerAppearance.apply { visible = ::shouldShowReaderAppearance },
BrowserMenuDivider(),
menuToolbar
@ -301,23 +292,6 @@ class DefaultToolbarMenu(
onItemTapped.invoke(ToolbarMenu.Item.Quit)
}
private val readerMode = BrowserMenuHighlightableSwitch(
label = context.getString(R.string.browser_menu_read),
startImageResource = R.drawable.ic_readermode,
initialState = {
session?.let {
store.state.findTab(it.id)?.readerState?.active
} ?: false
},
highlight = BrowserMenuHighlight.LowPriority(
label = context.getString(R.string.browser_menu_read),
notificationTint = getColor(context, R.color.whats_new_notification_color)
),
isHighlighted = { !context.settings().readerModeOpened }
) { checked ->
onItemTapped.invoke(ToolbarMenu.Item.ReaderMode(checked))
}
private val readerAppearance = BrowserMenuImageText(
label = context.getString(R.string.browser_menu_read_appearance),
imageResource = R.drawable.ic_readermode_appearance,

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M16.7 21H7.3A3.3 3.3 0 0 1 4 17.7V6.3C4 4.5 5.5 3 7.3 3h9.4C18.5 3 20 4.5 20 6.3v11.4c0 1.8-1.5 3.3-3.3 3.3zM7.3 5C6.6 5 6 5.6 6 6.3v11.4c0 0.7 0.6 1.3 1.3 1.3h9.4c0.7 0 1.3-0.6 1.3-1.3V6.3c0-0.7-0.6-1.3-1.3-1.3H7.3zM15 8H9a0.5 0.5 0 0 1 0-1h6a0.5 0.5 0 0 1 0 1zm0 3H9a0.5 0.5 0 0 1 0-1h6a0.5 0.5 0 0 1 0 1zm0 3H9a0.5 0.5 0 0 1 0-1h6a0.5 0.5 0 0 1 0 1zm-3.4 3H9a0.5 0.5 0 0 1 0-1h2.6a0.5 0.5 0 0 1 0 1z">
<aapt:attr name="android:fillColor">
<gradient
android:type="linear"
android:startX="24"
android:startY="0"
android:endX="0"
android:endY="24"
android:startColor="?readerModeStartGradient"
android:endColor="?readerModeEndGradient"/>
</aapt:attr>
</path>
</vector>

View File

@ -45,6 +45,8 @@
<color name="add_on_private_browsing_exterior_circle_background_normal_theme">@color/add_on_private_browsing_exterior_circle_background_dark_theme</color>
<color name="add_on_private_browsing_interior_icon_background_normal_theme">@color/add_on_private_browsing_interior_icon_background_dark_theme</color>
<color name="prompt_login_edit_text_cursor_color_normal_theme">@color/prompt_login_edit_text_cursor_color_dark_theme</color>
<color name="readermode_start_gradient_normal_theme">#C689FF</color>
<color name="readermode_end_gradient_normal_theme">#00B3F4</color>
<!-- Tab tray -->
<color name="tab_tray_item_text_normal_theme">@color/tab_tray_item_text_dark_theme</color>

View File

@ -49,6 +49,8 @@
<attr name="onboardingDeselected" format="reference"/>
<attr name="addOnPrivateBrowsingExteriorCircleBackground" format="reference"/>
<attr name="addOnPrivateBrowsingInteriorIconBackground" format="reference"/>
<attr name="readerModeStartGradient" format="reference"/>
<attr name="readerModeEndGradient" format="reference"/>
<!-- Tab tray -->
<attr name="tabTrayItemBackground" format="reference" />

View File

@ -63,6 +63,8 @@
<color name="add_on_private_browsing_exterior_circle_background_light_theme">@color/accent_bright_light_theme</color>
<color name="add_on_private_browsing_interior_icon_background_light_theme">#FFFFFF</color>
<color name="prompt_login_edit_text_cursor_color_light_theme">#312a64</color>
<color name="readermode_start_gradient_normal_theme">#FF9059ff</color>
<color name="readermode_end_gradient_normal_theme">#FF0250bb</color>
<!-- Tab Tray -->
<color name="tab_tray_item_text_light_theme">@color/ink_80</color>

View File

@ -118,8 +118,10 @@
<!-- Browser menu text shown in custom tabs to indicate this is a Fenix tab
The first parameter is the name of the app defined in app_name (for example: Fenix) -->
<string name="browser_menu_powered_by2">Powered by %1$s</string>
<!-- Browser menu button to put the the current page in reader mode -->
<!-- Browser menu button to put the current page in reader mode -->
<string name="browser_menu_read">Reader view</string>
<!-- Browser menu button content description to close reader mode and return the user to the regular browser -->
<string name="browser_menu_read_close">Close reader view</string>
<!-- Browser menu button to open the current page in an external app -->
<string name="browser_menu_open_app_link">Open in app</string>
<!-- Browser menu button to configure reader mode appearance e.g. the used font type and size -->

View File

@ -73,6 +73,9 @@
<item name="addOnPrivateBrowsingExteriorCircleBackground">@color/add_on_private_browsing_exterior_circle_background_normal_theme</item>
<item name="addOnPrivateBrowsingInteriorIconBackground">@color/add_on_private_browsing_interior_icon_background_normal_theme</item>
<item name="mozacPromptLoginEditTextCursorColor">@color/prompt_login_edit_text_cursor_color_normal_theme</item>
<item name="readerModeStartGradient">@color/readermode_start_gradient_normal_theme</item>
<item name="readerModeEndGradient">@color/readermode_end_gradient_normal_theme</item>
<item name="tabTrayItemBackground">@color/tab_tray_item_background_normal_theme</item>
<item name="tabTrayItemSelectedBackground">@color/tab_tray_item_selected_background_normal_theme</item>

View File

@ -6,7 +6,6 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.spyk
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.ReaderState
@ -69,15 +68,10 @@ class DefaultToolbarMenuTest {
every { appLinkRedirect.hasExternalApp() } returns true
every { getAppLinkRedirect(any()) } returns appLinkRedirect
val session: Session = mockk(relaxed = true)
every { session.id } returns "readerable-tab"
every { sessionManager.selectedSession } returns session
val list = defaultToolbarMenu.getLowPrioHighlightItems()
assertEquals(ToolbarMenu.Item.InstallToHomeScreen, list[0])
assertEquals(ToolbarMenu.Item.ReaderMode(false), list[1])
assertEquals(ToolbarMenu.Item.OpenInApp, list[2])
assertEquals(ToolbarMenu.Item.OpenInApp, list[1])
}
@Test
@ -94,29 +88,9 @@ class DefaultToolbarMenuTest {
every { appLinkRedirect.hasExternalApp() } returns true
every { getAppLinkRedirect(any()) } returns appLinkRedirect
val session: Session = mockk(relaxed = true)
every { session.id } returns "readerable-tab"
every { sessionManager.selectedSession } returns session
val list = defaultToolbarMenu.getLowPrioHighlightItems()
assertEquals(ToolbarMenu.Item.ReaderMode(false), list[0])
assertEquals(ToolbarMenu.Item.OpenInApp, list[1])
}
@Test
fun `get all low prio highlight items without ReaderMode`() {
every { context.components.useCases.webAppUseCases.isPinningSupported() } returns true
every { context.components.useCases.webAppUseCases.isInstallable() } returns true
val getAppLinkRedirect: AppLinksUseCases.GetAppLinkRedirect = mockk(relaxed = true)
every { context.components.useCases.appLinksUseCases.appLinkRedirect } returns getAppLinkRedirect
every { getAppLinkRedirect(any()).hasExternalApp() } returns true
val list = defaultToolbarMenu.getLowPrioHighlightItems()
assertEquals(ToolbarMenu.Item.InstallToHomeScreen, list[0])
assertEquals(ToolbarMenu.Item.OpenInApp, list[1])
assertEquals(ToolbarMenu.Item.OpenInApp, list[0])
}
@Test
@ -124,10 +98,6 @@ class DefaultToolbarMenuTest {
every { context.components.useCases.webAppUseCases.isPinningSupported() } returns true
every { context.components.useCases.webAppUseCases.isInstallable() } returns true
val session: Session = mockk(relaxed = true)
every { session.id } returns "readerable-tab"
every { sessionManager.selectedSession } returns session
val getAppLinkRedirect: AppLinksUseCases.GetAppLinkRedirect = mockk(relaxed = true)
every { context.components.useCases.appLinksUseCases.appLinkRedirect } returns getAppLinkRedirect
every { getAppLinkRedirect(any()).hasExternalApp() } returns false
@ -135,6 +105,5 @@ class DefaultToolbarMenuTest {
val list = defaultToolbarMenu.getLowPrioHighlightItems()
assertEquals(ToolbarMenu.Item.InstallToHomeScreen, list[0])
assertEquals(ToolbarMenu.Item.ReaderMode(false), list[1])
}
}