Skip to content

Commit

Permalink
For mozilla-mobile#20893 - Search term groups in history
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielluong committed Sep 13, 2021
1 parent 64f5e85 commit 447e6b2
Show file tree
Hide file tree
Showing 42 changed files with 1,127 additions and 724 deletions.
1 change: 1 addition & 0 deletions app/src/main/java/org/mozilla/fenix/BrowserDirection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
FromSettings(R.id.settingsFragment),
FromBookmarks(R.id.bookmarkFragment),
FromHistory(R.id.historyFragment),
FromHistoryMetadataGroup(R.id.historyMetadataGroupFragment),
FromTrackingProtectionExceptions(R.id.trackingProtectionExceptionsFragment),
FromAbout(R.id.aboutFragment),
FromTrackingProtection(R.id.trackingProtectionFragment),
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,9 @@ object FeatureFlags {
* Enables showing the home screen behind the search dialog
*/
val showHomeBehindSearch = Config.channel.isNightlyOrDebug

/**
* Enables showing search groupings in the History.
*/
val showHistorySearchGroups = Config.channel.isDebug
}
9 changes: 6 additions & 3 deletions app/src/main/java/org/mozilla/fenix/HomeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import android.os.StrictMode
import android.os.SystemClock
import android.text.format.DateUtils
import android.util.AttributeSet
import android.view.ActionMode
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ActionMode
import android.view.ViewConfiguration
import android.view.WindowManager.LayoutParams.FLAG_SECURE
import androidx.annotation.CallSuper
Expand All @@ -34,12 +34,12 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.Dispatchers.IO
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.search.SearchEngine
Expand Down Expand Up @@ -93,6 +93,7 @@ import org.mozilla.fenix.home.intent.StartSearchIntentProcessor
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentDirections
import org.mozilla.fenix.library.bookmarks.DesktopFolders
import org.mozilla.fenix.library.history.HistoryFragmentDirections
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentDirections
import org.mozilla.fenix.library.recentlyclosed.RecentlyClosedFragmentDirections
import org.mozilla.fenix.onboarding.DefaultBrowserNotificationWorker
import org.mozilla.fenix.perf.Performance
Expand Down Expand Up @@ -740,6 +741,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
BookmarkFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromHistory ->
HistoryFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromHistoryMetadataGroup ->
HistoryMetadataGroupFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromTrackingProtectionExceptions ->
TrackingProtectionExceptionsFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromAbout ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,164 @@

package org.mozilla.fenix.components.history

import mozilla.components.concept.storage.HistoryStorage
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import mozilla.components.concept.storage.HistoryMetadata
import mozilla.components.concept.storage.VisitInfo
import mozilla.components.concept.storage.VisitType
import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.library.history.History
import org.mozilla.fenix.perf.runBlockingIncrement

/**
* An Interface for providing a paginated list of [VisitInfo]
* An Interface for providing a paginated list of [History].
*/
interface PagedHistoryProvider {
/**
* Gets a list of [VisitInfo]
* Gets a list of [History].
*
* @param offset How much to offset the list by
* @param numberOfItems How many items to fetch
* @param onComplete A callback that returns the list of [VisitInfo]
* @param onComplete A callback that returns the list of [History]
*/
fun getHistory(offset: Long, numberOfItems: Long, onComplete: (List<VisitInfo>) -> Unit)
fun getHistory(offset: Long, numberOfItems: Long, onComplete: (List<History>) -> Unit)
}

// A PagedList DataSource runs on a background thread automatically.
// If we run this in our own coroutineScope it breaks the PagedList
fun HistoryStorage.createSynchronousPagedHistoryProvider(): PagedHistoryProvider {
return object : PagedHistoryProvider {

override fun getHistory(
offset: Long,
numberOfItems: Long,
onComplete: (List<VisitInfo>) -> Unit
) {
runBlockingIncrement {
val history = getVisitsPaginated(
offset,
numberOfItems,
excludeTypes = listOf(
VisitType.NOT_A_VISIT,
VisitType.DOWNLOAD,
VisitType.REDIRECT_TEMPORARY,
VisitType.RELOAD,
VisitType.EMBED,
VisitType.FRAMED_LINK,
VisitType.REDIRECT_PERMANENT
/**
* @param historyStorage
*/
class DefaultPagedHistoryProvider(
private val historyStorage: PlacesHistoryStorage
) : PagedHistoryProvider {

@Suppress("LongMethod")
override fun getHistory(
offset: Long,
numberOfItems: Long,
onComplete: (List<History>) -> Unit
) {
// A PagedList DataSource runs on a background thread automatically.
// If we run this in our own coroutineScope it breaks the PagedList
runBlockingIncrement {
val history: List<History>

if (FeatureFlags.showHistorySearchGroups) {
history = getHistoryAndSearchGroups(offset, numberOfItems)
} else {
history = historyStorage
.getVisitsPaginated(
offset,
numberOfItems,
excludeTypes = listOf(
VisitType.NOT_A_VISIT,
VisitType.DOWNLOAD,
VisitType.REDIRECT_TEMPORARY,
VisitType.RELOAD,
VisitType.EMBED,
VisitType.FRAMED_LINK,
VisitType.REDIRECT_PERMANENT
)
)
.mapIndexed(transformVisitInfoToHistoryItem(offset.toInt()))
}

onComplete(history)
}
}

private suspend fun getHistoryAndSearchGroups(
offset: Long,
numberOfItems: Long
): List<History> {
val historyMetadata: MutableList<HistoryMetadata> =
historyStorage.getHistoryMetadataSince(Long.MIN_VALUE)
.filter { it.totalViewTime > 0 && it.key.searchTerm != null }
.toMutableList()
val historyGroups: MutableMap<String, List<HistoryMetadata>> = historyMetadata
.groupBy { it.key.searchTerm!! }
.toMutableMap()

val history: List<History.Regular> = historyStorage
.getVisitsPaginated(
offset,
numberOfItems,
excludeTypes = listOf(
VisitType.NOT_A_VISIT,
VisitType.DOWNLOAD,
VisitType.REDIRECT_TEMPORARY,
VisitType.RELOAD,
VisitType.EMBED,
VisitType.FRAMED_LINK,
VisitType.REDIRECT_PERMANENT
)
)
.mapIndexed(transformVisitInfoToHistoryItem(offset.toInt()))

val result = mutableListOf<History>()

onComplete(history)
// Iterate through the existing list of visited history item and filter out items
// that belong to a search group, replacing the first seen item that matches a
// history metadata item with its corresponding search group.
for (item in history) {
val matchingHistoryMetadataItem =
historyMetadata.find { it.key.url == item.url }

if (matchingHistoryMetadataItem != null) {
// Found a visited history item matching an existing history metadata item

val searchTerm = matchingHistoryMetadataItem.key.searchTerm!!
val historyMetadataItems = historyGroups[searchTerm]

historyMetadata.remove(matchingHistoryMetadataItem)

// Replace the visited history item with a history group along with all the
// matching history metadata item with the same search term.
if (historyMetadataItems != null && historyMetadataItems.isNotEmpty()) {
result.add(
History.Group(
id = item.id,
title = searchTerm,
visitedAt = item.visitedAt,
items = historyMetadataItems.map {
History.Metadata(
id = 0,
title = it.title?.takeIf(String::isNotEmpty)
?: it.key.url.tryGetHostFromUrl(),
url = it.key.url,
visitedAt = it.updatedAt
)
}
)
)

// Since a history group was added for the existing `searchTerm`,
// remove the `searchTerm` from `historyGroups`. Subsequent matches
// of the visited history item and an existing history metadata with
// a search group that has already been added will not be appended
// to the `result`.
historyGroups.remove(searchTerm)
}
} else {
// Visited history item does not belong in a search group. Add to `result`.
result.add(item)
}
}

return result
}

private fun transformVisitInfoToHistoryItem(offset: Int): (id: Int, visit: VisitInfo) -> History.Regular {
return { id, visit ->
val title = visit.title
?.takeIf(String::isNotEmpty)
?: visit.url.tryGetHostFromUrl()

History.Regular(
id = offset + id,
title = title,
url = visit.url,
visitedAt = visit.visitTime
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ enum class HistoryItemTimeGroup {

class HistoryAdapter(
private val historyInteractor: HistoryInteractor,
) : PagedListAdapter<HistoryItem, HistoryListItemViewHolder>(historyDiffCallback),
SelectionHolder<HistoryItem> {
) : PagedListAdapter<History, HistoryListItemViewHolder>(historyDiffCallback),
SelectionHolder<History> {

private var mode: HistoryFragmentState.Mode = HistoryFragmentState.Mode.Normal
override val selectedItems get() = mode.selectedItems
Expand Down Expand Up @@ -102,7 +102,7 @@ class HistoryAdapter(
return calendar.time
}

private fun timeGroupForHistoryItem(item: HistoryItem): HistoryItemTimeGroup {
private fun timeGroupForHistoryItem(item: History): HistoryItemTimeGroup {
return when {
DateUtils.isToday(item.visitedAt) -> HistoryItemTimeGroup.Today
yesterdayRange.contains(item.visitedAt) -> HistoryItemTimeGroup.Yesterday
Expand All @@ -112,16 +112,16 @@ class HistoryAdapter(
}
}

private val historyDiffCallback = object : DiffUtil.ItemCallback<HistoryItem>() {
override fun areItemsTheSame(oldItem: HistoryItem, newItem: HistoryItem): Boolean {
private val historyDiffCallback = object : DiffUtil.ItemCallback<History>() {
override fun areItemsTheSame(oldItem: History, newItem: History): Boolean {
return oldItem == newItem
}

override fun areContentsTheSame(oldItem: HistoryItem, newItem: HistoryItem): Boolean {
override fun areContentsTheSame(oldItem: History, newItem: History): Boolean {
return oldItem == newItem
}

override fun getChangePayload(oldItem: HistoryItem, newItem: HistoryItem): Any? {
override fun getChangePayload(oldItem: History, newItem: History): Any? {
return newItem
}
}
Expand Down
Loading

0 comments on commit 447e6b2

Please sign in to comment.