Closes #24307: add synced history empty fragment, feature flag and history screen showing only local files

pull/543/head
mike a 2 years ago committed by mergify[bot]
parent d9b14cfe07
commit 8e9464fee3

@ -87,6 +87,11 @@ object FeatureFlags {
*/
const val historyImprovementFeatures = true
/**
* Separates history into local and synced from other sources.
*/
val showSyncedHistory = Config.channel.isDebug
/**
* Enables the Task Continuity enhancements.
*/

@ -75,9 +75,10 @@ interface PagedHistoryProvider {
*
* @param offset How much to offset the list by
* @param numberOfItems How many items to fetch
* @param isRemote Are items local or synced from other devices
* @return list of [HistoryDB]
*/
suspend fun getHistory(offset: Int, numberOfItems: Int): List<HistoryDB>
suspend fun getHistory(offset: Int, numberOfItems: Int, isRemote: Boolean? = null): List<HistoryDB>
}
/**
@ -109,11 +110,13 @@ class DefaultPagedHistoryProvider(
it == VisitType.REDIRECT_PERMANENT || it == VisitType.REDIRECT_TEMPORARY
}
@Volatile private var historyGroups: List<HistoryDB.Group>? = null
@Volatile
private var historyGroups: List<HistoryDB.Group>? = null
override suspend fun getHistory(
offset: Int,
numberOfItems: Int
numberOfItems: Int,
isRemote: Boolean?
): List<HistoryDB> {
// We need to re-fetch all the history metadata if the offset resets back at 0
// in the case of a pull to refresh.
@ -140,7 +143,7 @@ class DefaultPagedHistoryProvider(
.toList()
}
return getHistoryAndSearchGroups(offset, numberOfItems)
return getHistoryAndSearchGroups(offset, numberOfItems, isRemote)
}
/**
@ -162,6 +165,7 @@ class DefaultPagedHistoryProvider(
private suspend fun getHistoryAndSearchGroups(
offset: Int,
numberOfItems: Int,
isRemote: Boolean?
): List<HistoryDB> {
val result = mutableListOf<HistoryDB>()
var history: List<HistoryDB.Regular> = historyStorage
@ -170,6 +174,11 @@ class DefaultPagedHistoryProvider(
numberOfItems.toLong(),
excludeTypes = excludedVisitTypes
)
.filter { item ->
isRemote?.let {
item.isRemote == it
} ?: true
}
.map { transformVisitInfoToHistoryItem(it) }
// We'll use this list to filter out redirects from metadata groups below.

@ -34,6 +34,10 @@ interface HistoryController {
fun handleDeleteSome(items: Set<History>)
fun handleRequestSync()
fun handleEnterRecentlyClosed()
/**
* Navigates to [org.mozilla.fenix.library.syncedhistory.SyncedHistoryFragment]
*/
fun handleEnterSyncedHistory()
}
@Suppress("TooManyFunctions", "LongParameterList")
@ -98,8 +102,7 @@ class DefaultHistoryController(
}
override fun handleSearch() {
val directions =
HistoryFragmentDirections.actionGlobalHistorySearchDialog()
val directions = HistoryFragmentDirections.actionGlobalHistorySearchDialog()
navController.navigateSafe(R.id.historyFragment, directions)
}
@ -158,4 +161,10 @@ class DefaultHistoryController(
)
Events.recentlyClosedTabsOpened.record(NoExtras())
}
override fun handleEnterSyncedHistory() {
navController.navigate(
HistoryFragmentDirections.actionHistoryFragmentToSyncedHistoryFragment()
)
}
}

@ -15,7 +15,8 @@ import org.mozilla.fenix.components.history.PagedHistoryProvider
* Flow<PagingData>, that provides HistoryAdapter with items to display.
*/
class HistoryDataSource(
private val historyProvider: PagedHistoryProvider
private val historyProvider: PagedHistoryProvider,
private val isRemote: Boolean? = null,
) : PagingSource<Int, History>() {
// The refresh key is set to null so that it will always reload the entire list for any data
@ -26,7 +27,7 @@ class HistoryDataSource(
// Get the offset of the last loaded page or default to 0 when it is null on the initial
// load or a refresh.
val offset = params.key ?: 0
val historyItems = historyProvider.getHistory(offset, params.loadSize).run {
val historyItems = historyProvider.getHistory(offset, params.loadSize, isRemote).run {
positionWithOffset(offset)
}
val nextOffset = if (historyItems.isEmpty()) {

@ -68,7 +68,8 @@ class HistoryFragment : LibraryPageFragment<History>(), UserInteractionHandler {
null
) {
HistoryDataSource(
historyProvider = historyProvider
historyProvider = historyProvider,
isRemote = if (FeatureFlags.showSyncedHistory) false else null
)
}.flow

@ -47,6 +47,11 @@ interface HistoryInteractor : SelectionInteractor<History> {
* Called when the user clicks on recently closed tab button.
*/
fun onRecentlyClosedClicked()
/**
* Called when the user clicks on synced history button.
*/
fun onSyncedHistoryClicked()
}
/**
@ -96,4 +101,8 @@ class DefaultHistoryInteractor(
override fun onRecentlyClosedClicked() {
historyController.handleEnterRecentlyClosed()
}
override fun onSyncedHistoryClicked() {
historyController.handleEnterSyncedHistory()
}
}

@ -12,6 +12,7 @@ import androidx.paging.LoadState
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
import mozilla.components.support.base.feature.UserInteractionHandler
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.ComponentHistoryBinding
import org.mozilla.fenix.ext.components
@ -114,6 +115,8 @@ class HistoryView(
private fun updateEmptyState(userHasHistory: Boolean) {
binding.historyList.isInvisible = !userHasHistory
binding.historyEmptyView.isVisible = !userHasHistory
binding.topSpacer.isVisible = !userHasHistory
with(binding.recentlyClosedNavEmpty) {
recentlyClosedNav.setOnClickListener {
interactor.onRecentlyClosedClicked()
@ -131,6 +134,14 @@ class HistoryView(
)
recentlyClosedNav.isVisible = !userHasHistory
}
with(binding.syncedHistoryNavEmpty) {
syncedHistoryNav.setOnClickListener {
interactor.onSyncedHistoryClicked()
}
syncedHistoryNav.isVisible = FeatureFlags.showSyncedHistory && !userHasHistory
}
if (!userHasHistory) {
binding.historyEmptyView.announceForAccessibility(context.getString(R.string.history_empty_message))
}

@ -7,6 +7,7 @@ package org.mozilla.fenix.library.history.viewholders
import android.view.View
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.HistoryListItemBinding
import org.mozilla.fenix.ext.components
@ -33,6 +34,10 @@ class HistoryListItemViewHolder(
historyInteractor.onRecentlyClosedClicked()
}
binding.syncedHistoryNavEmpty.syncedHistoryNav.setOnClickListener {
historyInteractor.onSyncedHistoryClicked()
}
binding.historyLayout.overflowView.apply {
setImageResource(R.drawable.ic_close)
contentDescription = view.context.getString(R.string.history_delete_item)
@ -113,11 +118,15 @@ class HistoryListItemViewHolder(
}
}
@Suppress("NestedBlockDepth")
private fun toggleTopContent(
showTopContent: Boolean,
isNormalMode: Boolean,
) {
binding.recentlyClosedNavEmpty.recentlyClosedNav.isVisible = showTopContent
binding.topSpacer.isVisible = showTopContent
binding.bottomSpacer.isVisible = showTopContent
binding.syncedHistoryNavEmpty.syncedHistoryNav.isVisible = showTopContent && FeatureFlags.showSyncedHistory
if (showTopContent) {
val numRecentTabs = itemView.context.components.core.store.state.closedTabs.size
@ -131,6 +140,7 @@ class HistoryListItemViewHolder(
),
numRecentTabs
)
binding.recentlyClosedNavEmpty.recentlyClosedNav.run {
if (isNormalMode) {
isEnabled = true
@ -140,6 +150,18 @@ class HistoryListItemViewHolder(
alpha = DISABLED_BUTTON_ALPHA
}
}
if (FeatureFlags.showSyncedHistory) {
binding.syncedHistoryNavEmpty.syncedHistoryNav.run {
if (isNormalMode) {
isEnabled = true
alpha = 1f
} else {
isEnabled = false
alpha = DISABLED_BUTTON_ALPHA
}
}
}
}
}

@ -0,0 +1,22 @@
/* 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.syncedhistory
import mozilla.components.support.base.feature.UserInteractionHandler
import org.mozilla.fenix.library.LibraryPageFragment
import org.mozilla.fenix.library.history.History
/**
* A screen displaying history items that were opened on other devices, not local.
*/
class SyncedHistoryFragment : LibraryPageFragment<History>(), UserInteractionHandler {
override fun onBackPressed(): Boolean {
return false
}
override val selectedItems: Set<History>
get() = setOf()
}

@ -10,7 +10,7 @@
android:orientation="vertical">
<View
android:id="@+id/top_divider"
android:id="@+id/top_spacer"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="16dp"
@ -29,7 +29,7 @@
</ScrollView>
<View
android:id="@+id/bottom_divider"
android:id="@+id/bottom_spacer"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="8dp"

@ -20,12 +20,26 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/top_spacer"
android:layout_width="match_parent"
android:layout_height="8dp"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"/>
<include
android:id="@+id/recently_closed_nav_empty"
layout="@layout/recently_closed_nav_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@+id/top_spacer" />
<include
android:id="@+id/synced_history_nav_empty"
layout="@layout/synced_history_nav_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/recently_closed_nav_empty" />
<TextView
android:id="@+id/history_empty_view"
@ -39,14 +53,14 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/recently_closed_nav_empty" />
app:layout_constraintTop_toBottomOf="@id/synced_history_nav_empty" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/recently_closed_nav_empty">
app:layout_constraintTop_toBottomOf="@id/synced_history_nav_empty">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/history_list"

@ -0,0 +1,12 @@
<?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/. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/historyLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.mozilla.fenix.library.syncedhistory.SyncedHistoryFragment" />

@ -9,10 +9,26 @@
android:importantForAccessibility="no"
android:orientation="vertical">
<View
android:id="@+id/top_spacer"
android:layout_width="match_parent"
android:layout_height="8dp"
android:visibility="gone"/>
<include
android:id="@+id/recently_closed_nav_empty"
layout="@layout/recently_closed_nav_item" />
<include
android:id="@+id/synced_history_nav_empty"
layout="@layout/synced_history_nav_item" />
<View
android:id="@+id/bottom_spacer"
android:layout_width="match_parent"
android:layout_height="32dp"
android:visibility="gone"/>
<TextView
android:id="@+id/header_title"
android:layout_width="wrap_content"

@ -8,8 +8,6 @@
android:id="@+id/recently_closed_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"

@ -0,0 +1,47 @@
<?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/. -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/synced_history_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:minHeight="@dimen/library_item_height"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="@dimen/history_favicon_width_height"
android:layout_height="@dimen/history_favicon_width_height"
android:layout_marginStart="20dp"
android:importantForAccessibility="no"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_synced_tabs" />
<TextView
android:id="@+id/synced_history_header"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:ellipsize="end"
android:singleLine="true"
android:text="@string/history_synced_from_other_devices"
android:textAlignment="viewStart"
android:textColor="?attr/textPrimary"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:layout_goneMarginEnd="@dimen/library_item_icon_margin_horizontal" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -276,6 +276,10 @@
app:destination="@id/historySearchDialogFragment"
app:popUpTo="@id/historySearchDialogFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/action_historyFragment_to_syncedHistoryFragment"
app:destination="@id/syncedHistoryFragment" />
</fragment>
<dialog
@ -295,6 +299,18 @@
app:argType="org.mozilla.fenix.library.history.History[]" />
</fragment>
<fragment
android:id="@+id/syncedHistoryFragment"
android:name="org.mozilla.fenix.library.syncedhistory.SyncedHistoryFragment"
android:label="@string/history_from_other_devices"
tools:layout="@layout/fragment_synced_history">
<action
android:id="@+id/action_global_history_search_dialog"
app:destination="@id/historySearchDialogFragment"
app:popUpTo="@id/historySearchDialogFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/downloadsFragment"
android:name="org.mozilla.fenix.library.downloads.DownloadFragment"

@ -766,6 +766,10 @@
<string name="history_older">Older</string>
<!-- Text shown when no history exists -->
<string name="history_empty_message">No history here</string>
<!-- Text for the button inside History screen that opens Synced History screen. History that is coming from other devices -->
<string name="history_synced_from_other_devices">Synced from other devices</string>
<!-- The page title for browsing history coming from other devices. -->
<string name="history_from_other_devices">From other devices</string>
<!-- Downloads -->
<!-- Text for the snackbar to confirm that multiple downloads items have been removed -->

Loading…
Cancel
Save