Skip to content

Commit

Permalink
For mozilla-mobile#349: View Downloads
Browse files Browse the repository at this point in the history
  • Loading branch information
Kate Glazko authored and Kate Glazko committed Aug 12, 2020
1 parent c52e4fd commit 3d7b3b4
Show file tree
Hide file tree
Showing 27 changed files with 1,194 additions and 2 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ dependencies {
implementation Deps.sentry
implementation Deps.osslicenses_library

implementation 'com.google.code.gson:gson:2.8.5'

implementation Deps.leanplum_core
implementation Deps.leanplum_fcm

Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/org/mozilla/fenix/BrowserDirection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
FromEditCustomSearchEngineFragment(R.id.editCustomSearchEngineFragment),
FromAddonDetailsFragment(R.id.addonDetailsFragment),
FromAddonPermissionsDetailsFragment(R.id.addonPermissionsDetailFragment),
FromLoginDetailFragment(R.id.loginDetailFragment)
FromLoginDetailFragment(R.id.loginDetailFragment),
FromDownloads(R.id.downloadsFragment)
}
3 changes: 3 additions & 0 deletions app/src/main/java/org/mozilla/fenix/HomeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.exceptions.trackingprotection.TrackingProtectionExceptionsFragmentDirections
import org.mozilla.fenix.library.downloads.DownloadFragmentDirections
import org.mozilla.fenix.ext.alreadyOnDestination
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.metrics
Expand Down Expand Up @@ -577,6 +578,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
HistoryFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromTrackingProtectionExceptions ->
TrackingProtectionExceptionsFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromDownloads ->
DownloadFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromAbout ->
AboutFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromTrackingProtection ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* 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.components.downloads

import kotlinx.coroutines.runBlocking
import mozilla.components.browser.state.state.content.DownloadState
import mozilla.components.concept.storage.HistoryStorage
import org.mozilla.fenix.ext.requireComponents

/**
* An Interface for providing a paginated list of [VisitInfo]
*
*/
interface PagedDownloadProvider {
/**
* Gets a list of [VisitInfo]
* @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]
*/
fun getDownload(offset: Long, numberOfItems: Long, onComplete: (List<DownloadState>) -> Unit)
}

// A PagedList DataSource runs on a background thread automatically.
// If we run this in our own coroutineScope it breaks the PagedList
fun HistoryStorage.createSynchronousPagedDownloadProvider(downloadsMap: (Map<Long, DownloadState>)): PagedDownloadProvider {
return object : PagedDownloadProvider {
override fun getDownload(
offset: Long,
numberOfItems: Long,
onComplete: (List<DownloadState>) -> Unit
) {
runBlocking {
val downloads: List<DownloadState> = downloadsMap.values.toList()
onComplete(downloads)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ sealed class Event {
NEW_PRIVATE_TAB, SHARE, BACK, FORWARD, RELOAD, STOP, OPEN_IN_FENIX,
SAVE_TO_COLLECTION, ADD_TO_TOP_SITES, ADD_TO_HOMESCREEN, QUIT, READER_MODE_ON,
READER_MODE_OFF, OPEN_IN_APP, BOOKMARK, READER_MODE_APPEARANCE, ADDONS_MANAGER,
BOOKMARKS, HISTORY, SYNC_TABS
BOOKMARKS, HISTORY, SYNC_TABS, DOWNLOADS
}

override val extras: Map<Events.browserMenuActionKeys, String>?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,13 @@ class DefaultBrowserToolbarController(
BrowserFragmentDirections.actionGlobalHistoryFragment()
)
}

ToolbarMenu.Item.Downloads -> {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalDownloadsFragment()
)
}
}
}

Expand Down Expand Up @@ -419,6 +426,7 @@ class DefaultBrowserToolbarController(
ToolbarMenu.Item.AddonsManager -> Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER
ToolbarMenu.Item.Bookmarks -> Event.BrowserMenuItemTapped.Item.BOOKMARKS
ToolbarMenu.Item.History -> Event.BrowserMenuItemTapped.Item.HISTORY
ToolbarMenu.Item.Downloads -> Event.BrowserMenuItemTapped.Item.DOWNLOADS
}

activity.components.analytics.metrics.track(Event.BrowserMenuItemTapped(eventItem))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ class DefaultToolbarMenu(
.shouldDeleteBrowsingDataOnQuit

val menuItems = listOfNotNull(
downloadsItem,
historyItem,
bookmarksItem,
if (FeatureFlags.syncedTabs) syncedTabs else null,
Expand Down Expand Up @@ -333,6 +334,14 @@ class DefaultToolbarMenu(
onItemTapped.invoke(ToolbarMenu.Item.Bookmarks)
}

val downloadsItem = BrowserMenuImageText(
"Downloads",
R.drawable.ic_download,
primaryTextColor()
) {
onItemTapped.invoke(ToolbarMenu.Item.Downloads)
}

@ColorRes
private fun primaryTextColor() = ThemeManager.resolveAttribute(R.attr.primaryText, context)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface ToolbarMenu {
object ReaderModeAppearance : Item()
object Bookmarks : Item()
object History : Item()
object Downloads : Item()
}

val menuBuilder: BrowserMenuBuilder
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,15 @@ class HomeFragment : Fragment() {
HomeFragmentDirections.actionGlobalHistoryFragment()
)
}

HomeMenu.Item.Downloads -> {
hideOnboardingIfNeeded()
nav(
R.id.homeFragment,
HomeFragmentDirections.actionGlobalDownloadsFragment()
)
}

HomeMenu.Item.Help -> {
hideOnboardingIfNeeded()
(activity as HomeActivity).openToBrowserAndLoad(
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class HomeMenu(
object SyncedTabs : Item()
object History : Item()
object Bookmarks : Item()
object Downloads: Item()
object Quit : Item()
object Sync : Item()
}
Expand Down Expand Up @@ -144,6 +145,14 @@ class HomeMenu(
onItemTapped.invoke(Item.Help)
}

val downloadsItem = BrowserMenuImageText(
"Downloads",
R.drawable.ic_download,
primaryTextColor
) {
onItemTapped.invoke(Item.Downloads)
}

// Only query account manager if it has been initialized.
// We don't want to cause its initialization just for this check.
val accountAuthItem = if (context.components.backgroundServices.accountManagerAvailableQueue.isReady()) {
Expand All @@ -160,6 +169,7 @@ class HomeMenu(
BrowserMenuDivider(),
if (FeatureFlags.syncedTabs) syncedTabsItem else null,
bookmarksItem,
downloadsItem,
historyItem,
BrowserMenuDivider(),
addons,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@

/* 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.downloads

import android.content.Context
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
import org.mozilla.fenix.R
import org.mozilla.fenix.library.SelectionHolder
import org.mozilla.fenix.library.downloads.viewholders.DownloadsListItemViewHolder
import java.util.Calendar
import java.util.Date

enum class DownloadItemTimeGroup {
Today, ThisWeek, ThisMonth, Older;

fun humanReadable(context: Context): String = when (this) {
Today -> context.getString(R.string.history_24_hours)
ThisWeek -> context.getString(R.string.history_7_days)
ThisMonth -> context.getString(R.string.history_30_days)
Older -> context.getString(R.string.history_older)
}
}

class DownloadAdapter(
private val downloadInteractor: DownloadInteractor
) : PagedListAdapter<DownloadItem, DownloadsListItemViewHolder>(downloadDiffCallback), SelectionHolder<DownloadItem> {

private var mode: DownloadFragmentState.Mode = DownloadFragmentState.Mode.Normal
override val selectedItems get() = mode.selectedItems
var pendingDeletionIds = emptySet<Long>()
private val itemsWithHeaders: MutableMap<DownloadItemTimeGroup, Int> = mutableMapOf()

override fun getItemViewType(position: Int): Int = DownloadsListItemViewHolder.LAYOUT_ID

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadsListItemViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return DownloadsListItemViewHolder(view, downloadInteractor, this)
}

fun updateMode(mode: DownloadFragmentState.Mode) {
this.mode = mode
// Update the delete button alpha that the first item holds
if (itemCount > 0) notifyItemChanged(0)
}

override fun onBindViewHolder(holder: DownloadsListItemViewHolder, position: Int) {
val current = getItem(position) ?: return
val headerForCurrentItem = timeGroupForDownloadItem(current)
val isPendingDeletion = pendingDeletionIds.contains(current.visitedAt)
var timeGroup: DownloadItemTimeGroup? = null

// Add or remove the header and position to the map depending on it's deletion status
if (itemsWithHeaders.containsKey(headerForCurrentItem)) {
if (isPendingDeletion && itemsWithHeaders[headerForCurrentItem] == position) {
itemsWithHeaders.remove(headerForCurrentItem)
} else if (isPendingDeletion && itemsWithHeaders[headerForCurrentItem] != position) {
// do nothing
} else {
if (position <= itemsWithHeaders[headerForCurrentItem] as Int) {
itemsWithHeaders[headerForCurrentItem] = position
timeGroup = headerForCurrentItem
}
}
} else if (!isPendingDeletion) {
itemsWithHeaders[headerForCurrentItem] = position
timeGroup = headerForCurrentItem
}

holder.bind(current, timeGroup, position == 0)
}

fun updatePendingDeletionIds(pendingDeletionIds: Set<Long>) {
this.pendingDeletionIds = pendingDeletionIds
}

companion object {
private const val zeroDays = 0
private const val sevenDays = 7
private const val thirtyDays = 30
private val oneDayAgo = getDaysAgo(zeroDays).time
private val sevenDaysAgo = getDaysAgo(sevenDays).time
private val thirtyDaysAgo = getDaysAgo(thirtyDays).time
private val lastWeekRange = LongRange(sevenDaysAgo, oneDayAgo)
private val lastMonthRange = LongRange(thirtyDaysAgo, sevenDaysAgo)

private fun getDaysAgo(daysAgo: Int): Date {
val calendar = Calendar.getInstance()
calendar.add(Calendar.DAY_OF_YEAR, -daysAgo)

return calendar.time
}

private fun timeGroupForDownloadItem(item: DownloadItem): DownloadItemTimeGroup {
return when {
DateUtils.isToday(item.visitedAt) -> DownloadItemTimeGroup.Today
lastWeekRange.contains(item.visitedAt) -> DownloadItemTimeGroup.ThisWeek
lastMonthRange.contains(item.visitedAt) -> DownloadItemTimeGroup.ThisMonth
else -> DownloadItemTimeGroup.Older
}
}

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

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

override fun getChangePayload(oldItem: DownloadItem, newItem: DownloadItem): Any? {
return newItem
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

/* 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.downloads

import org.mozilla.fenix.browser.browsingmode.BrowsingMode

interface DownloadController {
fun handleOpen(item: DownloadItem, mode: BrowsingMode? = null)
fun handleSelect(item: DownloadItem)
fun handleDeselect(item: DownloadItem)
fun handleBackPressed(): Boolean
fun handleModeSwitched()
fun handleDeleteSome(items: Set<DownloadItem>)
}

class DefaultDownloadController(
private val store: DownloadFragmentStore,
private val openToBrowser: (item: DownloadItem, mode: BrowsingMode?) -> Unit,
private val invalidateOptionsMenu: () -> Unit,
private val deleteDownloadItems: (Set<DownloadItem>) -> Unit
) : DownloadController {
override fun handleOpen(item: DownloadItem, mode: BrowsingMode?) {
openToBrowser(item, mode)
}

override fun handleSelect(item: DownloadItem) {
store.dispatch(DownloadFragmentAction.AddItemForRemoval(item))
}

override fun handleDeselect(item: DownloadItem) {
store.dispatch(DownloadFragmentAction.RemoveItemForRemoval(item))
}

override fun handleBackPressed(): Boolean {
return if (store.state.mode is DownloadFragmentState.Mode.Editing) {
store.dispatch(DownloadFragmentAction.ExitEditMode)
true
} else {
false
}
}

override fun handleModeSwitched() {
invalidateOptionsMenu.invoke()
}

override fun handleDeleteSome(items: Set<DownloadItem>) {
deleteDownloadItems.invoke(items)
}
}
Loading

0 comments on commit 3d7b3b4

Please sign in to comment.