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 Sep 30, 2020
1 parent 19b377c commit 1635ed9
Show file tree
Hide file tree
Showing 11 changed files with 373 additions and 26 deletions.
1 change: 1 addition & 0 deletions app/src/main/java/org/mozilla/fenix/home/HomeMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ class HomeMenu(
BrowserMenuDivider(),
if (settings.syncedTabsInTabsTray) null else syncedTabsItem,
bookmarksItem,
downloadsItem,
historyItem,
downloadsItem,
BrowserMenuDivider(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ package org.mozilla.fenix.library.downloads

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.ext.logDebug
import org.mozilla.fenix.library.SelectionHolder
import org.mozilla.fenix.library.downloads.viewholders.DownloadsListItemViewHolder

Expand Down Expand Up @@ -37,4 +39,25 @@ class DownloadAdapter(
this.downloads = downloads
notifyDataSetChanged()
}

fun updatePendingDeletionIds(pendingDeletionIds: Set<Long>) {
logDebug("boek", pendingDeletionIds.toString())
// this.pendingDeletionIds = pendingDeletionIds
}

companion object {
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
Expand Up @@ -8,15 +8,29 @@ 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 openToFileManager: (item: DownloadItem, mode: BrowsingMode?) -> Unit
private val openToFileManager: (item: DownloadItem, mode: BrowsingMode?) -> Unit,
private val invalidateOptionsMenu: () -> Unit,
private val deleteDownloadItems: (Set<DownloadItem>) -> Unit
) : DownloadController {
override fun handleOpen(item: DownloadItem, mode: BrowsingMode?) {
openToFileManager(item, mode)
openToFileManager(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 {
Expand All @@ -27,4 +41,12 @@ class DefaultDownloadController(
false
}
}

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

override fun handleDeleteSome(items: Set<DownloadItem>) {
deleteDownloadItems.invoke(items)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ package org.mozilla.fenix.library.downloads

import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.fragment_downloads.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import mozilla.components.browser.state.state.content.DownloadState
import mozilla.components.feature.downloads.AbstractFetchDownloadService
import mozilla.components.lib.state.ext.consumeFrom
Expand All @@ -19,16 +26,17 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.filterNotExistsOnDisk
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.ext.*
import org.mozilla.fenix.library.LibraryPageFragment
import org.mozilla.fenix.utils.allowUndo

@SuppressWarnings("TooManyFunctions", "LargeClass")
class DownloadFragment : LibraryPageFragment<DownloadItem>(), UserInteractionHandler {
private lateinit var downloadStore: DownloadFragmentStore
private lateinit var downloadView: DownloadView
private lateinit var downloadInteractor: DownloadInteractor
private var undoScope: CoroutineScope? = null
private var pendingHistoryDeletionJob: (suspend () -> Unit)? = null

override fun onCreateView(
inflater: LayoutInflater,
Expand All @@ -54,14 +62,17 @@ class DownloadFragment : LibraryPageFragment<DownloadItem>(), UserInteractionHan
DownloadFragmentStore(
DownloadFragmentState(
items = items,
mode = DownloadFragmentState.Mode.Normal
mode = DownloadFragmentState.Mode.Normal,
pendingDeletionIds = emptySet(),
isDeletingItems = false
)
)
}

val downloadController: DownloadController = DefaultDownloadController(
downloadStore,
::openItem
::openItem,
::invalidateOptionsMenu,
::deleteHistoryItems
)
downloadInteractor = DownloadInteractor(
downloadController
Expand All @@ -73,12 +84,31 @@ class DownloadFragment : LibraryPageFragment<DownloadItem>(), UserInteractionHan

override val selectedItems get() = downloadStore.state.mode.selectedItems

private fun invalidateOptionsMenu() {
activity?.invalidateOptionsMenu()
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

requireComponents.analytics.metrics.track(Event.HistoryOpened)

setHasOptionsMenu(false)
setHasOptionsMenu(true)
}

private fun deleteHistoryItems(items: Set<DownloadItem>) {

updatePendingHistoryToDelete(items)
undoScope = CoroutineScope(IO)
undoScope?.allowUndo(
requireView(),
getMultiSelectSnackBarMessage(items),
getString(R.string.bookmark_undo_deletion),
{
undoPendingDeletion(items)
},
getDeleteHistoryItemsOperation(items)
)
}

@ExperimentalCoroutinesApi
Expand All @@ -95,7 +125,60 @@ class DownloadFragment : LibraryPageFragment<DownloadItem>(), UserInteractionHan
showToolbar(getString(R.string.library_downloads))
}

override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
val menuRes = when (downloadStore.state.mode) {
DownloadFragmentState.Mode.Normal -> R.menu.library_menu
is DownloadFragmentState.Mode.Editing -> R.menu.download_select_multi
}

inflater.inflate(menuRes, menu)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
R.id.close_history -> {
close()
true
}

R.id.delete_downloads -> {
deleteHistoryItems(downloadStore.state.mode.selectedItems)
downloadStore.dispatch(DownloadFragmentAction.ExitEditMode)
true
}
R.id.open_downloads -> {
val selectedDownloads = downloadStore.state.mode.selectedItems
val listOfDownload = selectedDownloads.toList()
context?.let {
AbstractFetchDownloadService.openFile(
context = it,
contentType = listOfDownload[0].contentType,
filePath = listOfDownload[0].filePath
)
}
true
}
else -> super.onOptionsItemSelected(item)
}

private fun getMultiSelectSnackBarMessage(downloadItems: Set<DownloadItem>): String {
return if (downloadItems.size > 1) {
getString(R.string.history_delete_multiple_items_snackbar)
} else {
String.format(
requireContext().getString(
R.string.history_delete_single_item_snackbar
), downloadItems.first().filePath.toShortUrl(requireComponents.publicSuffixList)
)
}
}

override fun onPause() {
invokePendingDeletion()
super.onPause()
}

override fun onBackPressed(): Boolean {
invokePendingDeletion()
return downloadView.onBackPressed()
}

Expand All @@ -110,4 +193,44 @@ class DownloadFragment : LibraryPageFragment<DownloadItem>(), UserInteractionHan
)
}
}

private fun getDeleteHistoryItemsOperation(items: Set<DownloadItem>): (suspend () -> Unit) {
return {
CoroutineScope(IO).launch {
downloadStore.dispatch(DownloadFragmentAction.EnterDeletionMode)
context?.components?.run {
for (item in items) {
analytics.metrics.track(Event.HistoryItemRemoved)
// core.historyStorage.deleteVisit(item.url, item.visitedAt)
}
}
downloadStore.dispatch(DownloadFragmentAction.ExitDeletionMode)
pendingHistoryDeletionJob = null
}
}
}

private fun updatePendingHistoryToDelete(items: Set<DownloadItem>) {
logDebug("boek", items.toString())
// pendingHistoryDeletionJob = getDeleteHistoryItemsOperation(items)
// val ids = items.map { item -> item.visitedAt }.toSet()
// downloadStore.dispatch(DownloadFragmentAction.AddPendingDeletionSet(ids))
}

private fun undoPendingDeletion(items: Set<DownloadItem>) {
logDebug("boek", items.toString())
// pendingHistoryDeletionJob = null
// val ids = items.map { item -> item.visitedAt }.toSet()
// downloadStore.dispatch(DownloadFragmentAction.UndoPendingDeletionSet(ids))
}

private fun invokePendingDeletion() {
pendingHistoryDeletionJob?.let {
viewLifecycleOwner.lifecycleScope.launch {
it.invoke()
}.invokeOnCompletion {
pendingHistoryDeletionJob = null
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,27 @@ class DownloadFragmentStore(initialState: DownloadFragmentState) :
/**
* Actions to dispatch through the `DownloadStore` to modify `DownloadState` through the reducer.
*/

sealed class DownloadFragmentAction : Action {
object ExitEditMode : DownloadFragmentAction()
data class AddItemForRemoval(val item: DownloadItem) : DownloadFragmentAction()
data class RemoveItemForRemoval(val item: DownloadItem) : DownloadFragmentAction()
data class AddPendingDeletionSet(val itemIds: Set<Long>) : DownloadFragmentAction()
data class UndoPendingDeletionSet(val itemIds: Set<Long>) : DownloadFragmentAction()
object EnterDeletionMode : DownloadFragmentAction()
object ExitDeletionMode : DownloadFragmentAction()
}

/**
* The state for the Download Screen
* @property items List of DownloadItem to display
* @property mode Current Mode of Download
* The state for the History Screen
* @property items List of HistoryItem to display
* @property mode Current Mode of History
*/
data class DownloadFragmentState(
val items: List<DownloadItem>,
val mode: Mode
val mode: Mode,
val pendingDeletionIds: Set<Long>,
val isDeletingItems: Boolean
) : State {
sealed class Mode {
open val selectedItems = emptySet<DownloadItem>()
Expand All @@ -64,6 +73,28 @@ private fun downloadStateReducer(
action: DownloadFragmentAction
): DownloadFragmentState {
return when (action) {
is DownloadFragmentAction.AddItemForRemoval ->
state.copy(mode = DownloadFragmentState.Mode.Editing(state.mode.selectedItems + action.item))
is DownloadFragmentAction.RemoveItemForRemoval -> {
val selected = state.mode.selectedItems - action.item
state.copy(
mode = if (selected.isEmpty()) {
DownloadFragmentState.Mode.Normal
} else {
DownloadFragmentState.Mode.Editing(selected)
}
)
}
is DownloadFragmentAction.ExitEditMode -> state.copy(mode = DownloadFragmentState.Mode.Normal)
is DownloadFragmentAction.EnterDeletionMode -> state.copy(isDeletingItems = true)
is DownloadFragmentAction.ExitDeletionMode -> state.copy(isDeletingItems = false)
is DownloadFragmentAction.AddPendingDeletionSet ->
state.copy(
pendingDeletionIds = state.pendingDeletionIds + action.itemIds
)
is DownloadFragmentAction.UndoPendingDeletionSet ->
state.copy(
pendingDeletionIds = state.pendingDeletionIds - action.itemIds
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,23 @@ class DownloadInteractor(
downloadController.handleOpen(item)
}

override fun select(item: DownloadItem) { /* noop */ }
override fun select(item: DownloadItem) {
downloadController.handleSelect(item)
}

override fun deselect(item: DownloadItem) { /* noop */ }
override fun deselect(item: DownloadItem) {
downloadController.handleDeselect(item)
}

override fun onBackPressed(): Boolean {
return downloadController.handleBackPressed()
}

override fun onModeSwitched() {
downloadController.handleModeSwitched()
}

override fun onDeleteSome(items: Set<DownloadItem>) {
downloadController.handleDeleteSome(items)
}
}
Loading

0 comments on commit 1635ed9

Please sign in to comment.