Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Commit

Permalink
For #1048 - Add ability to view tab history by long-pressing the back or
Browse files Browse the repository at this point in the history
forward button.
  • Loading branch information
person808 committed Jul 13, 2020
1 parent 31248b8 commit 10d07e7
Show file tree
Hide file tree
Showing 20 changed files with 436 additions and 25 deletions.
12 changes: 12 additions & 0 deletions app/src/main/java/org/mozilla/fenix/HomeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.os.Bundle
import android.os.StrictMode
import android.text.format.DateUtils
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.View
import android.view.WindowManager
import androidx.annotation.CallSuper
Expand Down Expand Up @@ -342,6 +343,17 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
super.onBackPressed()
}

final override fun onKeyLongPress(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach {
if (it is UserInteractionHandler && it.onBackLongPressed()) {
return true
}
}
}
return super.onKeyLongPress(keyCode, event)
}

final override fun onUserLeaveHint() {
supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach {
if (it is UserInteractionHandler && it.onHomePressed()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,11 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
removeSessionIfNeeded()
}

override fun onBackLongPressed(): Boolean {
findNavController().navigate(R.id.action_global_tabHistoryDialogFragment)
return true
}

/**
* Saves the external app session ID to be restored later in [onViewStateRestored].
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ open class BrowserInteractor(
}

override fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item) {
browserToolbarController.handleToolbarItemInteraction(item)
browserToolbarController.handleToolbarItemClick(item)
}

override fun onBrowserToolbarMenuItemLongClicked(item: ToolbarMenu.Item) {
browserToolbarController.handleToolbarItemLongClick(item)
}

override fun onScrolled(offset: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ interface BrowserToolbarController {
fun handleScroll(offset: Int)
fun handleToolbarPaste(text: String)
fun handleToolbarPasteAndGo(text: String)
fun handleToolbarItemInteraction(item: ToolbarMenu.Item)
fun handleToolbarItemClick(item: ToolbarMenu.Item)
fun handleToolbarItemLongClick(item: ToolbarMenu.Item)
fun handleToolbarClick()
fun handleTabCounterClick()
fun handleTabCounterItemInteraction(item: TabCounterMenuItem)
Expand Down Expand Up @@ -164,7 +165,7 @@ class DefaultBrowserToolbarController(

@ExperimentalCoroutinesApi
@Suppress("ComplexMethod", "LongMethod")
override fun handleToolbarItemInteraction(item: ToolbarMenu.Item) {
override fun handleToolbarItemClick(item: ToolbarMenu.Item) {
val sessionUseCases = activity.components.useCases.sessionUseCases
trackToolbarItemInteraction(item)

Expand Down Expand Up @@ -329,6 +330,13 @@ class DefaultBrowserToolbarController(
}
}

override fun handleToolbarItemLongClick(item: ToolbarMenu.Item) {
Do exhaustive when (item) {
is ToolbarMenu.Item.Forward -> navController.navigate(R.id.action_global_tabHistoryDialogFragment)
else -> return
}
}

@Suppress("ComplexMethod")
private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) {
val eventItem = when (item) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ interface BrowserToolbarViewInteractor {
fun onBrowserToolbarPasteAndGo(text: String)
fun onBrowserToolbarClicked()
fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item)
fun onBrowserToolbarMenuItemLongClicked(item: ToolbarMenu.Item)
fun onTabCounterClicked()
fun onTabCounterMenuItemTapped(item: TabCounterMenuItem)
fun onScrolled(offset: Int)
Expand Down Expand Up @@ -218,6 +219,7 @@ class BrowserToolbarView(
hasAccountProblem = components.backgroundServices.accountManager.accountNeedsReauth(),
shouldReverseItems = !shouldUseBottomToolbar,
onItemTapped = { interactor.onBrowserToolbarMenuItemTapped(it) },
onItemLongClicked = { interactor.onBrowserToolbarMenuItemLongClicked(it) },
lifecycleOwner = lifecycleOwner,
sessionManager = sessionManager,
store = components.core.store,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.storage.BookmarksStorage
import mozilla.components.support.ktx.android.content.getColorFromAttr
import org.mozilla.fenix.R
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.ext.asActivity
import org.mozilla.fenix.ext.components
Expand All @@ -51,6 +51,7 @@ class DefaultToolbarMenu(
hasAccountProblem: Boolean = false,
shouldReverseItems: Boolean,
private val onItemTapped: (ToolbarMenu.Item) -> Unit = {},
private val onItemLongClicked: (ToolbarMenu.Item) -> Unit = {},
private val lifecycleOwner: LifecycleOwner,
private val bookmarksStorage: BookmarksStorage
) : ToolbarMenu {
Expand Down Expand Up @@ -83,7 +84,8 @@ class DefaultToolbarMenu(
session?.canGoForward ?: true
},
secondaryImageTintResource = ThemeManager.resolveAttribute(R.attr.disabled, context),
disableInSecondaryState = true
disableInSecondaryState = true,
longClickListener = { onItemLongClicked.invoke(ToolbarMenu.Item.Forward) }
) {
onItemTapped.invoke(ToolbarMenu.Item.Forward)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* 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.tabhistory

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.R

data class TabHistoryItem(
val title: String,
val url: String,
val index: Int,
val isSelected: Boolean
)

class TabHistoryAdapter(
private val interactor: TabHistoryViewInteractor
) : RecyclerView.Adapter<TabHistoryViewHolder>() {

var historyList: List<TabHistoryItem> = emptyList()
set(value) {
field = value
notifyDataSetChanged()
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TabHistoryViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.history_list_item, parent, false)
return TabHistoryViewHolder(view, interactor)
}

override fun onBindViewHolder(holder: TabHistoryViewHolder, position: Int) {
holder.bind(historyList[position])
}

override fun getItemCount(): Int = historyList.size
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.tabhistory

import androidx.navigation.NavController
import mozilla.components.feature.session.SessionUseCases
import org.mozilla.fenix.R

interface TabHistoryController {
fun handleGoToHistoryItem(item: TabHistoryItem)
}

class DefaultTabHistoryController(
private val navController: NavController,
private val goToHistoryIndexUseCase: SessionUseCases.GoToHistoryIndexUseCase
) : TabHistoryController {

override fun handleGoToHistoryItem(item: TabHistoryItem) {
navController.popBackStack(R.id.browserFragment, false)
goToHistoryIndexUseCase.invoke(item.index)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* 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.tabhistory

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.android.synthetic.main.fragment_tab_history_dialog.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents

class TabHistoryDialogFragment : BottomSheetDialogFragment() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.TabHistoryDialogStyle)
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_tab_history_dialog, container, false)

@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

val controller = DefaultTabHistoryController(
navController = findNavController(),
goToHistoryIndexUseCase = requireComponents.useCases.sessionUseCases.goToHistoryIndex
)
val tabHistoryView = TabHistoryView(
container = tabHistoryLayout,
expandDialog = ::expand,
interactor = TabHistoryInteractor(controller)
)

consumeFrom(requireComponents.core.store) {
tabHistoryView.updateState(it)
}
}

private fun expand() {
(dialog as BottomSheetDialog).behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* 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.tabhistory

class TabHistoryInteractor(
private val controller: TabHistoryController
) : TabHistoryViewInteractor {

override fun goToHistoryItem(item: TabHistoryItem) {
controller.handleGoToHistoryItem(item)
}
}
79 changes: 79 additions & 0 deletions app/src/main/java/org/mozilla/fenix/tabhistory/TabHistoryView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* 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.tabhistory

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_tabhistory.*
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.BrowserState
import org.mozilla.fenix.R

interface TabHistoryViewInteractor {

/**
* Jump to a specific index in the tab's history.
*/
fun goToHistoryItem(item: TabHistoryItem)
}

class TabHistoryView(
private val container: ViewGroup,
private val expandDialog: () -> Unit,
interactor: TabHistoryViewInteractor
) : LayoutContainer {

override val containerView: View?
get() = container

val view: View = LayoutInflater.from(container.context)
.inflate(R.layout.component_tabhistory, container, true)

private val adapter = TabHistoryAdapter(interactor)
private val layoutManager = object : LinearLayoutManager(view.context) {
override fun onLayoutCompleted(state: RecyclerView.State?) {
super.onLayoutCompleted(state)
currentIndex?.let { index ->
// Force expansion of the dialog, otherwise scrolling to the current history item
// won't work when its position is near the bottom of the recyclerview.
expandDialog.invoke()
// Also, attempt to center the current history item.
val itemView = tabHistoryRecyclerView.findViewHolderForLayoutPosition(
findFirstCompletelyVisibleItemPosition()
)?.itemView
val offset = tabHistoryRecyclerView.height / 2 - (itemView?.height ?: 0) / 2
scrollToPositionWithOffset(index, offset)
}
}
}.apply {
reverseLayout = true
}

private var currentIndex: Int? = null

init {
tabHistoryRecyclerView.adapter = adapter
tabHistoryRecyclerView.layoutManager = layoutManager
}

fun updateState(state: BrowserState) {
state.selectedTab?.content?.history?.let { historyState ->
currentIndex = historyState.currentIndex
val items = historyState.items.mapIndexed { index, historyItem ->
TabHistoryItem(
title = historyItem.title,
url = historyItem.uri,
index = index,
isSelected = index == historyState.currentIndex
)
}
adapter.historyList = items
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* 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.tabhistory

import android.view.View
import androidx.core.text.bold
import androidx.core.text.buildSpannedString
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.history_list_item.view.*

class TabHistoryViewHolder(
private val view: View,
private val interactor: TabHistoryViewInteractor
) : RecyclerView.ViewHolder(view) {

fun bind(item: TabHistoryItem) {
view.history_layout.overflowView.isVisible = false
view.history_layout.urlView.text = item.url
view.history_layout.loadFavicon(item.url)

view.history_layout.titleView.text = if (item.isSelected) {
buildSpannedString {
bold { append(item.title) }
}
} else {
item.title
}

view.setOnClickListener { interactor.goToHistoryItem(item) }
}
}
Loading

0 comments on commit 10d07e7

Please sign in to comment.