For #349: View Downloads
parent
929ec541b0
commit
f83372b67c
|
@ -48,4 +48,9 @@ object FeatureFlags {
|
|||
* Enables downloads with external download managers.
|
||||
*/
|
||||
val externalDownloadManager = Config.channel.isNightlyOrDebug
|
||||
|
||||
/**
|
||||
* Enables viewing downloads in browser.
|
||||
*/
|
||||
val viewDownloads = Config.channel.isNightlyOrDebug
|
||||
}
|
||||
|
|
|
@ -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>?
|
||||
|
|
|
@ -380,6 +380,13 @@ class DefaultBrowserToolbarController(
|
|||
BrowserFragmentDirections.actionGlobalHistoryFragment()
|
||||
)
|
||||
}
|
||||
|
||||
ToolbarMenu.Item.Downloads -> browserAnimator.captureEngineViewAndDrawStatically {
|
||||
navController.nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections.actionGlobalDownloadsFragment()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,6 +421,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))
|
||||
|
|
|
@ -179,6 +179,7 @@ class DefaultToolbarMenu(
|
|||
.shouldDeleteBrowsingDataOnQuit
|
||||
|
||||
val menuItems = listOfNotNull(
|
||||
if (FeatureFlags.viewDownloads) downloadsItem else null,
|
||||
historyItem,
|
||||
bookmarksItem,
|
||||
if (FeatureFlags.syncedTabs) syncedTabs else null,
|
||||
|
@ -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)
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ interface ToolbarMenu {
|
|||
object ReaderModeAppearance : Item()
|
||||
object Bookmarks : Item()
|
||||
object History : Item()
|
||||
object Downloads : Item()
|
||||
}
|
||||
|
||||
val menuBuilder: BrowserMenuBuilder
|
||||
|
|
|
@ -771,6 +771,15 @@ class HomeFragment : Fragment() {
|
|||
HomeFragmentDirections.actionGlobalHistoryFragment()
|
||||
)
|
||||
}
|
||||
|
||||
HomeMenu.Item.Downloads -> {
|
||||
hideOnboardingIfNeeded()
|
||||
nav(
|
||||
R.id.homeFragment,
|
||||
HomeFragmentDirections.actionGlobalDownloadsFragment()
|
||||
)
|
||||
}
|
||||
|
||||
HomeMenu.Item.Help -> {
|
||||
hideOnboardingIfNeeded()
|
||||
(activity as HomeActivity).openToBrowserAndLoad(
|
||||
|
|
|
@ -43,6 +43,7 @@ class HomeMenu(
|
|||
object SyncedTabs : Item()
|
||||
object History : Item()
|
||||
object Bookmarks : Item()
|
||||
object Downloads : Item()
|
||||
object Quit : Item()
|
||||
object Sync : Item()
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -161,6 +170,7 @@ class HomeMenu(
|
|||
if (FeatureFlags.syncedTabs) syncedTabsItem else null,
|
||||
bookmarksItem,
|
||||
historyItem,
|
||||
if (FeatureFlags.viewDownloads) downloadsItem else null,
|
||||
BrowserMenuDivider(),
|
||||
addons,
|
||||
BrowserMenuDivider(),
|
||||
|
|
|
@ -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.library.downloads
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.mozilla.fenix.library.SelectionHolder
|
||||
import org.mozilla.fenix.library.downloads.viewholders.DownloadsListItemViewHolder
|
||||
|
||||
class DownloadAdapter(
|
||||
private val downloadInteractor: DownloadInteractor
|
||||
) : RecyclerView.Adapter<DownloadsListItemViewHolder>(), SelectionHolder<DownloadItem> {
|
||||
private var downloads: List<DownloadItem> = listOf()
|
||||
private var mode: DownloadFragmentState.Mode = DownloadFragmentState.Mode.Normal
|
||||
override val selectedItems get() = mode.selectedItems
|
||||
|
||||
override fun getItemCount(): Int = downloads.size
|
||||
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
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: DownloadsListItemViewHolder, position: Int) {
|
||||
holder.bind(downloads[position])
|
||||
}
|
||||
|
||||
fun updateDownloads(downloads: List<DownloadItem>) {
|
||||
this.downloads = downloads
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
/* 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 handleBackPressed(): Boolean
|
||||
}
|
||||
|
||||
class DefaultDownloadController(
|
||||
private val store: DownloadFragmentStore,
|
||||
private val openToFileManager: (item: DownloadItem, mode: BrowsingMode?) -> Unit
|
||||
) : DownloadController {
|
||||
override fun handleOpen(item: DownloadItem, mode: BrowsingMode?) {
|
||||
openToFileManager(item, mode)
|
||||
}
|
||||
|
||||
override fun handleBackPressed(): Boolean {
|
||||
return if (store.state.mode is DownloadFragmentState.Mode.Editing) {
|
||||
store.dispatch(DownloadFragmentAction.ExitEditMode)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/* 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.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.fragment_downloads.view.*
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import mozilla.components.feature.downloads.AbstractFetchDownloadService
|
||||
import mozilla.components.lib.state.ext.consumeFrom
|
||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
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.requireComponents
|
||||
import org.mozilla.fenix.ext.showToolbar
|
||||
import org.mozilla.fenix.library.LibraryPageFragment
|
||||
|
||||
@SuppressWarnings("TooManyFunctions", "LargeClass")
|
||||
class DownloadFragment : LibraryPageFragment<DownloadItem>(), UserInteractionHandler {
|
||||
private lateinit var downloadStore: DownloadFragmentStore
|
||||
private lateinit var downloadView: DownloadView
|
||||
private lateinit var downloadInteractor: DownloadInteractor
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_downloads, container, false)
|
||||
|
||||
val items = requireComponents.core.store.state.downloads.map {
|
||||
DownloadItem(
|
||||
it.value.id,
|
||||
it.value.fileName,
|
||||
it.value.filePath,
|
||||
it.value.contentLength.toString(),
|
||||
it.value.contentType
|
||||
)
|
||||
}
|
||||
|
||||
downloadStore = StoreProvider.get(this) {
|
||||
DownloadFragmentStore(
|
||||
DownloadFragmentState(
|
||||
items = items,
|
||||
mode = DownloadFragmentState.Mode.Normal
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val downloadController: DownloadController = DefaultDownloadController(
|
||||
downloadStore,
|
||||
::openItem
|
||||
)
|
||||
downloadInteractor = DownloadInteractor(
|
||||
downloadController
|
||||
)
|
||||
downloadView = DownloadView(view.downloadsLayout, downloadInteractor)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override val selectedItems get() = downloadStore.state.mode.selectedItems
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
requireComponents.analytics.metrics.track(Event.HistoryOpened)
|
||||
|
||||
setHasOptionsMenu(false)
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
consumeFrom(downloadStore) {
|
||||
downloadView.update(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
showToolbar(getString(R.string.library_downloads))
|
||||
}
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
return downloadView.onBackPressed()
|
||||
}
|
||||
|
||||
private fun openItem(item: DownloadItem, mode: BrowsingMode? = null) {
|
||||
|
||||
mode?.let { (activity as HomeActivity).browsingModeManager.mode = it }
|
||||
context?.let {
|
||||
AbstractFetchDownloadService.openFile(
|
||||
context = it,
|
||||
contentType = item.contentType,
|
||||
filePath = item.filePath
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/* 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 mozilla.components.lib.state.Action
|
||||
import mozilla.components.lib.state.State
|
||||
import mozilla.components.lib.state.Store
|
||||
|
||||
/**
|
||||
* Class representing a history entry
|
||||
* @property id Unique id of the download item
|
||||
* @property fileName File name of the download item
|
||||
* @property filePath Full path of the download item
|
||||
* @property size The size in bytes of the download item
|
||||
* @property contentType The type of file the download is
|
||||
*/
|
||||
data class DownloadItem(val id: Long, val fileName: String?, val filePath: String, val size: String, val contentType: String?)
|
||||
|
||||
/**
|
||||
* The [Store] for holding the [DownloadFragmentState] and applying [DownloadFragmentAction]s.
|
||||
*/
|
||||
class DownloadFragmentStore(initialState: DownloadFragmentState) :
|
||||
Store<DownloadFragmentState, DownloadFragmentAction>(initialState, ::downloadStateReducer)
|
||||
|
||||
/**
|
||||
* Actions to dispatch through the `DownloadStore` to modify `DownloadState` through the reducer.
|
||||
*/
|
||||
sealed class DownloadFragmentAction : Action {
|
||||
object ExitEditMode : DownloadFragmentAction()
|
||||
}
|
||||
|
||||
/**
|
||||
* The state for the Download Screen
|
||||
* @property items List of DownloadItem to display
|
||||
* @property mode Current Mode of Download
|
||||
*/
|
||||
data class DownloadFragmentState(
|
||||
val items: List<DownloadItem>,
|
||||
val mode: Mode
|
||||
) : State {
|
||||
sealed class Mode {
|
||||
open val selectedItems = emptySet<DownloadItem>()
|
||||
|
||||
object Normal : Mode()
|
||||
data class Editing(override val selectedItems: Set<DownloadItem>) : DownloadFragmentState.Mode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The DownloadState Reducer.
|
||||
*/
|
||||
private fun downloadStateReducer(
|
||||
state: DownloadFragmentState,
|
||||
action: DownloadFragmentAction
|
||||
): DownloadFragmentState {
|
||||
return when (action) {
|
||||
is DownloadFragmentAction.ExitEditMode -> state.copy(mode = DownloadFragmentState.Mode.Normal)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/* 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
|
||||
/**
|
||||
* Interactor for the download screen
|
||||
* Provides implementations for the DownloadViewInteractor
|
||||
*/
|
||||
@SuppressWarnings("TooManyFunctions")
|
||||
class DownloadInteractor(
|
||||
private val downloadController: DownloadController
|
||||
) : DownloadViewInteractor {
|
||||
override fun open(item: DownloadItem) {
|
||||
downloadController.handleOpen(item)
|
||||
}
|
||||
|
||||
override fun select(item: DownloadItem) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun deselect(item: DownloadItem) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
return downloadController.handleBackPressed()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
|
||||
|
||||
/* 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.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import kotlinx.android.synthetic.main.component_downloads.view.*
|
||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.library.LibraryPageView
|
||||
import org.mozilla.fenix.library.SelectionInteractor
|
||||
|
||||
/**
|
||||
* Interface for the DownloadViewInteractor. This interface is implemented by objects that want
|
||||
* to respond to user interaction on the DownloadView
|
||||
*/
|
||||
interface DownloadViewInteractor : SelectionInteractor<DownloadItem> {
|
||||
|
||||
/**
|
||||
* Called on backpressed to exit edit mode
|
||||
*/
|
||||
fun onBackPressed(): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* View that contains and configures the Downloads List
|
||||
*/
|
||||
class DownloadView(
|
||||
container: ViewGroup,
|
||||
val interactor: DownloadInteractor
|
||||
) : LibraryPageView(container), UserInteractionHandler {
|
||||
|
||||
val view: View = LayoutInflater.from(container.context)
|
||||
.inflate(R.layout.component_downloads, container, true)
|
||||
|
||||
var mode: DownloadFragmentState.Mode = DownloadFragmentState.Mode.Normal
|
||||
private set
|
||||
|
||||
val downloadAdapter = DownloadAdapter(interactor)
|
||||
private val layoutManager = LinearLayoutManager(container.context)
|
||||
|
||||
init {
|
||||
view.download_list.apply {
|
||||
layoutManager = this@DownloadView.layoutManager
|
||||
adapter = downloadAdapter
|
||||
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
}
|
||||
}
|
||||
|
||||
fun update(state: DownloadFragmentState) {
|
||||
|
||||
view.swipe_refresh.isEnabled =
|
||||
state.mode === DownloadFragmentState.Mode.Normal
|
||||
mode = state.mode
|
||||
|
||||
downloadAdapter.updateMode(state.mode)
|
||||
downloadAdapter.updateDownloads(state.items)
|
||||
|
||||
setUiForNormalMode(
|
||||
context.getString(R.string.library_downloads)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
return interactor.onBackPressed()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/* 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.viewholders
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.download_list_item.view.*
|
||||
import kotlinx.android.synthetic.main.library_site_item.view.*
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.hideAndDisable
|
||||
import org.mozilla.fenix.library.SelectionHolder
|
||||
import org.mozilla.fenix.library.downloads.DownloadInteractor
|
||||
import org.mozilla.fenix.library.downloads.DownloadItem
|
||||
import mozilla.components.feature.downloads.toMegabyteString
|
||||
|
||||
class DownloadsListItemViewHolder(
|
||||
view: View,
|
||||
private val downloadInteractor: DownloadInteractor,
|
||||
private val selectionHolder: SelectionHolder<DownloadItem>
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
private var item: DownloadItem? = null
|
||||
|
||||
fun bind(
|
||||
item: DownloadItem
|
||||
) {
|
||||
itemView.download_layout.visibility = View.VISIBLE
|
||||
itemView.download_layout.titleView.text = item.fileName
|
||||
itemView.download_layout.urlView.text = item.size.toLong().toMegabyteString()
|
||||
|
||||
itemView.download_layout.setSelectionInteractor(item, selectionHolder, downloadInteractor)
|
||||
itemView.download_layout.changeSelected(item in selectionHolder.selectedItems)
|
||||
|
||||
itemView.overflow_menu.hideAndDisable()
|
||||
itemView.favicon.setImageResource(R.drawable.ic_download_default)
|
||||
itemView.favicon.isClickable = false
|
||||
|
||||
this.item = item
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.download_list_item
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,49 @@
|
|||
<?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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/download_wrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:translationY="-3dp"
|
||||
android:visibility="gone"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/download_empty_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/download_empty_message"
|
||||
android:textColor="?secondaryText"
|
||||
android:textSize="16sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipe_refresh"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/download_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
tools:listitem="@layout/download_list_item"/>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,17 @@
|
|||
<?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/. -->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent" xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.mozilla.fenix.library.LibrarySiteItemView
|
||||
android:id="@+id/download_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="@dimen/library_item_height" />
|
||||
</LinearLayout>
|
|
@ -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/downloadsLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="org.mozilla.fenix.library.downloads.DownloadFragment" />
|
|
@ -70,6 +70,9 @@
|
|||
<action
|
||||
android:id="@+id/action_global_historyFragment"
|
||||
app:destination="@id/historyFragment" />
|
||||
|
||||
<action android:id="@+id/action_global_downloadsFragment"
|
||||
app:destination="@id/downloadsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_global_accountProblemFragment"
|
||||
app:destination="@id/accountProblemFragment" />
|
||||
|
@ -239,6 +242,12 @@
|
|||
android:label="@string/library_history"
|
||||
tools:layout="@layout/fragment_history" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/downloadsFragment"
|
||||
android:name="org.mozilla.fenix.library.downloads.DownloadFragment"
|
||||
android:label="Downloads"
|
||||
tools:layout="@layout/fragment_downloads" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/bookmarkFragment"
|
||||
android:name="org.mozilla.fenix.library.bookmarks.BookmarkFragment"
|
||||
|
|
|
@ -563,6 +563,13 @@
|
|||
<!-- Text shown when no history exists -->
|
||||
<string name="history_empty_message">No history here</string>
|
||||
|
||||
<!-- Downloads -->
|
||||
<!-- Text shown when no download exists -->
|
||||
<string name="download_empty_message">No downloads here</string>
|
||||
<!-- History multi select title in app bar
|
||||
The first parameter is the number of downloads selected -->
|
||||
<string name="download_multi_select_title">%1$d selected</string>
|
||||
|
||||
<!-- Crashes -->
|
||||
<!-- Title text displayed on the tab crash page. This first parameter is the name of the application (For example: Fenix) -->
|
||||
<string name="tab_crash_title_2">Sorry. %1$s can’t load that page.</string>
|
||||
|
|
|
@ -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.library.downloads
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class DownloadAdapterTest {
|
||||
|
||||
private lateinit var interactor: DownloadInteractor
|
||||
private lateinit var adapter: DownloadAdapter
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
interactor = mockk()
|
||||
adapter = DownloadAdapter(interactor)
|
||||
|
||||
every { interactor.select(any()) } just Runs
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getItemCount should return the number of tab collections`() {
|
||||
val download = mockk<DownloadItem>()
|
||||
|
||||
assertEquals(0, adapter.itemCount)
|
||||
|
||||
adapter.updateDownloads(
|
||||
downloads = listOf(download)
|
||||
)
|
||||
assertEquals(1, adapter.itemCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `updateData inserts item`() {
|
||||
val download = mockk<DownloadItem> {
|
||||
}
|
||||
val observer = mockk<RecyclerView.AdapterDataObserver>(relaxed = true)
|
||||
adapter.registerAdapterDataObserver(observer)
|
||||
adapter.updateDownloads(
|
||||
downloads = listOf(download)
|
||||
)
|
||||
verify { observer.onChanged() }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/* 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 io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestCoroutineScope
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class DownloadControllerTest {
|
||||
private val downloadItem = DownloadItem(0, "title", "url", "77", "jpg")
|
||||
private val scope: CoroutineScope = TestCoroutineScope()
|
||||
private val store: DownloadFragmentStore = mockk(relaxed = true)
|
||||
private val state: DownloadFragmentState = mockk(relaxed = true)
|
||||
private val openToFileManager: (DownloadItem, BrowsingMode?) -> Unit = mockk(relaxed = true)
|
||||
private val invalidateOptionsMenu: () -> Unit = mockk(relaxed = true)
|
||||
private val controller = DefaultDownloadController(
|
||||
store,
|
||||
openToFileManager
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
every { store.state } returns state
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onPressDownloadItemInNormalMode() {
|
||||
controller.handleOpen(downloadItem)
|
||||
|
||||
verify {
|
||||
openToFileManager(downloadItem, null)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onOpenItemInNormalMode() {
|
||||
controller.handleOpen(downloadItem, BrowsingMode.Normal)
|
||||
|
||||
verify {
|
||||
openToFileManager(downloadItem, BrowsingMode.Normal)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onBackPressedInNormalMode() {
|
||||
every { state.mode } returns DownloadFragmentState.Mode.Normal
|
||||
|
||||
assertFalse(controller.handleBackPressed())
|
||||
}
|
||||
}
|
|
@ -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.library.downloads
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verifyAll
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class DownloadInteractorTest {
|
||||
private val downloadItem = DownloadItem(0, "title", "url", "5.6 mb", "png")
|
||||
val controller: DownloadController = mockk(relaxed = true)
|
||||
val interactor = DownloadInteractor(controller)
|
||||
|
||||
@Test
|
||||
fun onOpen() {
|
||||
interactor.open(downloadItem)
|
||||
|
||||
verifyAll {
|
||||
controller.handleOpen(downloadItem)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onBackPressed() {
|
||||
every {
|
||||
controller.handleBackPressed()
|
||||
} returns true
|
||||
|
||||
val backpressHandled = interactor.onBackPressed()
|
||||
|
||||
verifyAll {
|
||||
controller.handleBackPressed()
|
||||
}
|
||||
assertTrue(backpressHandled)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue