Merge pull request #1690 from kiwix/feature/macgills/716-merge-download-library

Feature/macgills/716 merge download library
This commit is contained in:
Seán Mac Gillicuddy 2020-01-13 10:24:12 +00:00 committed by GitHub
commit 08b4e20e2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 195 additions and 438 deletions

View File

@ -81,12 +81,4 @@ public class MainActivityTest {
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
clickOn(R.string.remote_zims);
}
@Test
public void navigateDownloadingContent() {
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
BaristaMenuClickInteractions.clickMenu(getResourceString(R.string.menu_zim_manager));
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
clickOn(R.string.zim_downloads);
}
}

View File

@ -63,20 +63,13 @@ class ZimManageActivityTest : BaseActivityTest<ZimManageActivity>() {
forceResponse("012345678901234567890123456789012345678901234567890123456789012345678")
attempt(10) {
clickOn(book)
waitForEmptyView()
}
}
clickOnDownloading {
clickStop()
clickNegativeDialogButton()
clickStop()
clickPositiveDialogButton()
}
clickOnOnline {
forceResponse("01234")
clickOn(book)
}
clickOnDownloading {
waitForEmptyView()
}
clickOnDevice {
@ -90,9 +83,10 @@ class ZimManageActivityTest : BaseActivityTest<ZimManageActivity>() {
clickPositiveDialogButton()
waitForEmptyView()
}
clickOnOnline { }
clickOnOnline {
} clickOnLanguageIcon { }
}
}
private fun forceResponse(body: String) {
mockServer.forceResponse(

View File

@ -42,22 +42,11 @@ class ZimManageRobot : BaseRobot() {
return library(func)
}
fun clickOnDownloading(func: DownloadRobot.() -> Unit): DownloadRobot {
clickOnTab(R.string.zim_downloads)
return download(func)
}
fun clickOnDevice(func: DeviceRobot.() -> Unit): DeviceRobot {
clickOnTab(R.string.local_zims)
return device(func)
}
infix fun clickOnLanguageIcon(function: LanguageRobot.() -> Unit): LanguageRobot {
TextId(R.string.remote_zims)
clickOn(ViewId(R.id.select_language))
return language(function)
}
private fun library(func: LibraryRobot.() -> Unit) = LibraryRobot().apply(func)
inner class LibraryRobot : BaseRobot() {
init {
@ -79,20 +68,15 @@ class ZimManageRobot : BaseRobot() {
fun waitForEmptyView() {
isVisible(ViewId(R.id.libraryErrorText), VERY_LONG_WAIT)
}
}
private fun download(func: DownloadRobot.() -> Unit) = DownloadRobot().apply(func)
inner class DownloadRobot : BaseRobot() {
init {
isVisible(ViewId(R.id.zim_download_root))
}
fun clickStop() {
clickOn(ViewId(R.id.stop), LONG_WAIT)
}
fun waitForEmptyView() {
isVisible(ViewId(R.id.download_management_no_downloads), 11000L)
infix fun clickOnLanguageIcon(function: LanguageRobot.() -> Unit): LanguageRobot {
TextId(R.string.remote_zims)
clickOn(ViewId(R.id.select_language))
return language(function)
}
}

View File

@ -32,7 +32,6 @@ import org.kiwix.kiwixmobile.splash.KiwixSplashActivity
import org.kiwix.kiwixmobile.webserver.ZimHostActivity
import org.kiwix.kiwixmobile.webserver.ZimHostModule
import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity
import org.kiwix.kiwixmobile.zim_manager.download_view.DownloadFragment
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.ZimFileSelectFragment
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.DeleteFiles
import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment
@ -46,7 +45,6 @@ import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment
]
)
interface KiwixActivityComponent {
fun inject(downloadFragment: DownloadFragment)
fun inject(libraryFragment: LibraryFragment)
fun inject(zimFileSelectFragment: ZimFileSelectFragment)
fun inject(deleteFiles: DeleteFiles)

View File

@ -20,31 +20,30 @@ package org.kiwix.kiwixmobile.zim_manager
import android.content.Context
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.zim_manager.download_view.DownloadFragment
import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.core.base.BaseFragment
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.ZimFileSelectFragment
import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment
import kotlin.reflect.KFunction0
class SectionsPagerAdapter(
private val context: Context,
private val pagerData: Array<PagerData> =
arrayOf(
PagerData(::ZimFileSelectFragment, string.local_zims),
PagerData(::LibraryFragment, string.remote_zims)
),
fm: FragmentManager
) : FragmentPagerAdapter(fm) {
override fun getItem(position: Int) = when (position) {
0 -> ZimFileSelectFragment()
1 -> LibraryFragment()
2 -> DownloadFragment()
else -> throw RuntimeException("No matching fragment for position: $position")
override fun getItem(position: Int) = pagerData[position].fragmentConstructor.invoke()
override fun getCount() = pagerData.size
override fun getPageTitle(position: Int): String = context.getString(pagerData[position].title)
}
override fun getCount() = 3
override fun getPageTitle(position: Int): String = context.getString(
when (position) {
0 -> R.string.local_zims
1 -> R.string.remote_zims
2 -> R.string.zim_downloads
else -> throw RuntimeException("No matching title for position: $position")
}
data class PagerData(
val fragmentConstructor: KFunction0<BaseFragment>,
val title: Int
)
}

View File

@ -19,7 +19,6 @@ package org.kiwix.kiwixmobile.zim_manager
import android.content.Intent
import android.os.Bundle
import android.provider.Settings.System
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.widget.SearchView
@ -31,9 +30,7 @@ import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.start
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.startWithActionFrom
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.viewModel
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.utils.LanguageUtils
import org.kiwix.kiwixmobile.kiwixActivityComponent
import org.kiwix.kiwixmobile.language.LanguageActivity
@ -45,8 +42,8 @@ class ZimManageActivity : BaseActivity() {
val cachedComponent by lazy { kiwixActivityComponent }
private val zimManageViewModel by lazy { viewModel<ZimManageViewModel>(viewModelFactory) }
private val mSectionsPagerAdapter: SectionsPagerAdapter by lazy {
SectionsPagerAdapter(this, supportFragmentManager)
private val sectionsPagerAdapter: SectionsPagerAdapter by lazy {
SectionsPagerAdapter(this, fm = supportFragmentManager)
}
private var searchItem: MenuItem? = null
@ -67,8 +64,8 @@ class ZimManageActivity : BaseActivity() {
setUpToolbar()
manageViewPager.run {
adapter = mSectionsPagerAdapter
offscreenPageLimit = 2
adapter = sectionsPagerAdapter
offscreenPageLimit = sectionsPagerAdapter.count - 1
tabs.setupWithViewPager(this)
addOnPageChangeListener(SimplePageChangeListener(::updateMenu))
}
@ -105,24 +102,14 @@ class ZimManageActivity : BaseActivity() {
}
}
override fun onBackPressed() {
val value = System.getInt(contentResolver, System.ALWAYS_FINISH_ACTIVITIES, 0)
if (value == 1) {
startWithActionFrom<CoreMainActivity>()
} else {
super.onBackPressed() // optional depending on your needs
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_zim_manager, menu)
searchItem = menu.findItem(R.id.action_search)
languageItem = menu.findItem(R.id.select_language)
getZimItem = menu.findItem(R.id.get_zim_nearby_device)
val searchView = searchItem!!.actionView as SearchView
updateMenu(manageViewPager.currentItem)
searchView.setOnQueryTextListener(
(searchItem?.actionView as? SearchView)?.setOnQueryTextListener(
SimpleTextListener(zimManageViewModel.requestFiltering::onNext)
)
return true

View File

@ -38,7 +38,6 @@ import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.data.DataSource
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
@ -67,6 +66,7 @@ import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.StartMultiSelec
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.DividerItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.LibraryDownloadItem
import java.util.LinkedList
import java.util.Locale
import java.util.concurrent.TimeUnit.MILLISECONDS
@ -97,7 +97,6 @@ class ZimManageViewModel @Inject constructor(
val sideEffects = PublishProcessor.create<SideEffect<out Any?>>()
val libraryItems: MutableLiveData<List<LibraryListItem>> = MutableLiveData()
val downloadItems: MutableLiveData<List<DownloadItem>> = MutableLiveData()
val fileSelectListStates: MutableLiveData<FileSelectListState> = MutableLiveData()
val deviceListIsRefreshing = MutableLiveData<Boolean>()
val libraryListIsRefreshing = MutableLiveData<Boolean>()
@ -132,7 +131,6 @@ class ZimManageViewModel @Inject constructor(
val networkLibrary = PublishProcessor.create<LibraryNetworkEntity>()
val languages = languageDao.languages()
return arrayOf(
updateDownloadItems(downloads),
updateBookItems(),
checkFileSystemForBooksOnRequest(booksFromDao),
updateLibraryItems(booksFromDao, downloads, networkLibrary, languages),
@ -343,64 +341,65 @@ class ZimManageViewModel @Inject constructor(
filter: String,
fileSystemState: FileSystemState
): List<LibraryListItem> {
val downloadedBooksIds = booksOnFileSystem.map { it.book.id }
val downloadingBookIds = activeDownloads.map { it.book.id }
val activeLanguageCodes = allLanguages.filter(Language::active)
.map(Language::languageCode)
val booksUnfilteredByLanguage =
applyUserFilter(
libraryNetworkEntity.books
.filterNot { downloadedBooksIds.contains(it.id) }
.filterNot { downloadingBookIds.contains(it.id) },
applySearchFilter(
libraryNetworkEntity.books - booksOnFileSystem.map(BookOnDisk::book),
filter
)
return listOf(
*createLibrarySection(
booksUnfilteredByLanguage.filter { activeLanguageCodes.contains(it.language) },
val booksWithActiveLanguages =
booksUnfilteredByLanguage.filter { activeLanguageCodes.contains(it.language) }
return createLibrarySection(
booksWithActiveLanguages,
activeDownloads,
fileSystemState,
R.string.your_languages,
Long.MAX_VALUE
),
*createLibrarySection(
booksUnfilteredByLanguage.filterNot { activeLanguageCodes.contains(it.language) },
) +
createLibrarySection(
booksUnfilteredByLanguage - booksWithActiveLanguages,
activeDownloads,
fileSystemState,
R.string.other_languages,
Long.MIN_VALUE
)
)
}
private fun createLibrarySection(
books: List<Book>,
activeDownloads: List<DownloadModel>,
fileSystemState: FileSystemState,
sectionStringId: Int,
sectionId: Long
) =
if (books.isNotEmpty())
arrayOf(
DividerItem(sectionId, context.getString(sectionStringId)),
*toBookItems(books, fileSystemState)
)
else emptyArray()
listOf(DividerItem(sectionId, context.getString(sectionStringId))) +
books.asLibraryItems(activeDownloads, fileSystemState)
else emptyList()
private fun applyUserFilter(
booksUnfilteredByLanguage: List<Book>,
private fun applySearchFilter(
unDownloadedBooks: List<Book>,
filter: String
) = if (filter.isEmpty()) {
booksUnfilteredByLanguage
unDownloadedBooks
} else {
booksUnfilteredByLanguage.forEach { it.calculateSearchMatches(filter, bookUtils) }
booksUnfilteredByLanguage.filter { it.searchMatches > 0 }
unDownloadedBooks.forEach { it.calculateSearchMatches(filter, bookUtils) }
unDownloadedBooks.filter { it.searchMatches > 0 }
}
private fun toBookItems(
books: List<Book>,
private fun List<Book>.asLibraryItems(
activeDownloads: List<DownloadModel>,
fileSystemState: FileSystemState
) =
books.map { BookItem(it, fileSystemState) }.toTypedArray()
) = map { book ->
activeDownloads.firstOrNull { download -> download.book == book }
?.let(::LibraryDownloadItem)
?: BookItem(book, fileSystemState)
}
private fun checkFileSystemForBooksOnRequest(booksFromDao: Flowable<List<BookOnDisk>>) =
private fun checkFileSystemForBooksOnRequest(booksFromDao: Flowable<List<BookOnDisk>>):
Disposable =
requestFileSystemCheck
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
@ -462,12 +461,4 @@ class ZimManageViewModel @Inject constructor(
newBookOnDisk.apply { isSelected = firstOrNull?.isSelected ?: false }
})
}
private fun updateDownloadItems(downloadModels: Flowable<List<DownloadModel>>) =
downloadModels
.map { it.map(::DownloadItem) }
.subscribe(
downloadItems::postValue,
Throwable::printStackTrace
)
}

View File

@ -1,59 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.zim_manager.download_view
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.core.extensions.ViewGroupExtensions.inflate
class DownloadAdapter(private val itemClickListener: (DownloadItem) -> Unit) :
RecyclerView.Adapter<DownloadViewHolder>() {
init {
setHasStableIds(true)
}
var itemList: List<DownloadItem> = mutableListOf()
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemId(position: Int) = itemList[position].downloadId
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
) = DownloadViewHolder(
parent.inflate(
R.layout.download_item,
false
)
)
override fun getItemCount() = itemList.size
override fun onBindViewHolder(
holder: DownloadViewHolder,
position: Int
) {
holder.bind(itemList[position], itemClickListener)
}
}

View File

@ -1,94 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.zim_manager.download_view
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.layout_download_management.download_management_no_downloads
import kotlinx.android.synthetic.main.layout_download_management.zim_downloader_list
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.base.BaseFragment
import org.kiwix.kiwixmobile.core.downloader.Downloader
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.viewModel
import org.kiwix.kiwixmobile.core.utils.DialogShower
import org.kiwix.kiwixmobile.core.utils.KiwixDialog.YesNoDialog.StopDownload
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel
import javax.inject.Inject
class DownloadFragment : BaseFragment() {
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
@Inject lateinit var dialogShower: DialogShower
@Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil
@Inject lateinit var downloader: Downloader
private val zimManageViewModel by lazy {
activity!!.viewModel<ZimManageViewModel>(viewModelFactory)
}
private val downloadAdapter = DownloadAdapter {
dialogShower.show(StopDownload, { downloader.cancelDownload(it) })
}
override fun inject(baseActivity: BaseActivity) {
(baseActivity as ZimManageActivity).cachedComponent.inject(this)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View =
inflater.inflate(R.layout.layout_download_management, container, false)
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
zim_downloader_list.run {
adapter = downloadAdapter
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
setHasFixedSize(true)
}
zimManageViewModel.downloadItems.observe(this, Observer {
onDownloadItemsUpdate(it!!)
})
}
private fun onDownloadItemsUpdate(items: List<DownloadItem>) {
downloadAdapter.itemList = items
updateNoDownloads(items)
}
private fun updateNoDownloads(downloadItems: List<DownloadItem>) {
download_management_no_downloads.visibility =
if (downloadItems.isEmpty()) View.VISIBLE
else View.GONE
}
}

View File

@ -1,49 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.zim_manager.download_view
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.download_item.description
import kotlinx.android.synthetic.main.download_item.downloadProgress
import kotlinx.android.synthetic.main.download_item.downloadState
import kotlinx.android.synthetic.main.download_item.eta
import kotlinx.android.synthetic.main.download_item.favicon
import kotlinx.android.synthetic.main.download_item.stop
import kotlinx.android.synthetic.main.download_item.title
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.core.extensions.setBitmap
class DownloadViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView),
LayoutContainer {
fun bind(
downloadItem: DownloadItem,
itemClickListener: (DownloadItem) -> Unit
) {
favicon.setBitmap(downloadItem.favIcon)
title.text = downloadItem.title
description.text = downloadItem.description
downloadProgress.progress = downloadItem.progress
stop.setOnClickListener {
itemClickListener.invoke(downloadItem)
}
downloadState.text = downloadItem.downloadState.toReadableState(containerView.context)
eta.text = downloadItem.readableEta
}
}

View File

@ -45,10 +45,10 @@ import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.settings.StorageCalculator
import org.kiwix.kiwixmobile.core.utils.BookUtils
import org.kiwix.kiwixmobile.core.utils.DialogShower
import org.kiwix.kiwixmobile.core.utils.KiwixDialog.YesNoDialog.StopDownload
import org.kiwix.kiwixmobile.core.utils.KiwixDialog.YesNoDialog.WifiOnly
import org.kiwix.kiwixmobile.core.utils.NetworkUtils
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.utils.TestingUtils
import org.kiwix.kiwixmobile.zim_manager.NetworkState
import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED
import org.kiwix.kiwixmobile.zim_manager.NetworkState.NOT_CONNECTED
@ -57,6 +57,7 @@ import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryAdapter
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryDelegate.BookDelegate
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryDelegate.DividerDelegate
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryDelegate.DownloadDelegate
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem
import java.io.File
@ -78,7 +79,11 @@ class LibraryFragment : BaseFragment() {
private val libraryAdapter: LibraryAdapter by lazy {
LibraryAdapter(
BookDelegate(bookUtils, ::onBookItemClick), DividerDelegate
BookDelegate(bookUtils, ::onBookItemClick),
DownloadDelegate {
dialogShower.show(StopDownload, { downloader.cancelDownload(it.downloadId) })
},
DividerDelegate
)
}
@ -98,10 +103,7 @@ class LibraryFragment : BaseFragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
TestingUtils.bindResource(LibraryFragment::class.java)
return inflater.inflate(R.layout.activity_library, container, false)
}
): View = inflater.inflate(R.layout.activity_library, container, false)
override fun onViewCreated(
view: View,
@ -148,7 +150,6 @@ class LibraryFragment : BaseFragment() {
else string.no_items_msg
)
libraryErrorText.visibility = VISIBLE
TestingUtils.unbindResource(LibraryFragment::class.java)
} else {
libraryErrorText.visibility = GONE
}

View File

@ -19,11 +19,13 @@ package org.kiwix.kiwixmobile.zim_manager.library_view.adapter
import android.view.ViewGroup
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.base.adapter.AbsDelegateAdapter
import org.kiwix.kiwixmobile.core.extensions.ViewGroupExtensions.inflate
import org.kiwix.kiwixmobile.core.utils.BookUtils
import org.kiwix.kiwixmobile.core.base.adapter.AbsDelegateAdapter
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.DividerItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.LibraryDownloadItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryViewHolder.DownloadViewHolder
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryViewHolder.LibraryBookViewHolder
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryViewHolder.LibraryDividerViewHolder
@ -44,6 +46,14 @@ sealed class LibraryDelegate<I : LibraryListItem, out VH : LibraryViewHolder<I>>
)
}
class DownloadDelegate(private val clickAction: (LibraryDownloadItem) -> Unit) :
LibraryDelegate<LibraryDownloadItem, DownloadViewHolder>() {
override val itemClass = LibraryDownloadItem::class.java
override fun createViewHolder(parent: ViewGroup) =
DownloadViewHolder(parent.inflate(R.layout.item_download, false), clickAction)
}
object DividerDelegate : LibraryDelegate<DividerItem, LibraryDividerViewHolder>() {
override val itemClass = DividerItem::class.java

View File

@ -18,6 +18,10 @@
package org.kiwix.kiwixmobile.zim_manager.library_view.adapter
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState
import org.kiwix.kiwixmobile.core.downloader.model.Seconds
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.core.zim_manager.KiwixTag
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker
@ -53,4 +57,33 @@ sealed class LibraryListItem {
size.toLongOrNull() ?: 0L < Fat32Checker.FOUR_GIGABYTES_IN_KILOBYTES
}
}
data class LibraryDownloadItem(
val downloadId: Long,
val favIcon: Base64String,
val title: String,
val description: String,
val bytesDownloaded: Long,
val totalSizeBytes: Long,
val progress: Int,
val eta: Seconds,
val downloadState: DownloadState,
override val id: Long
) : LibraryListItem() {
val readableEta: CharSequence = eta.takeIf { it.seconds > 0L }?.toHumanReadableTime() ?: ""
constructor(downloadModel: DownloadModel) : this(
downloadModel.downloadId,
Base64String(downloadModel.book.favicon),
downloadModel.book.title,
downloadModel.book.description,
downloadModel.bytesDownloaded,
downloadModel.totalSizeOfDownload,
downloadModel.progress,
Seconds(downloadModel.etaInMilliSeconds / 1000L),
DownloadState.from(downloadModel.state, downloadModel.error),
downloadModel.book.id.hashCode().toLong()
)
}
}

View File

@ -23,16 +23,23 @@ import android.view.View
import android.view.View.MeasureSpec
import android.widget.Toast
import androidx.annotation.StringRes
import kotlinx.android.synthetic.main.item_library.creator
import kotlinx.android.synthetic.main.item_library.date
import kotlinx.android.synthetic.main.item_library.description
import kotlinx.android.synthetic.main.item_library.favicon
import kotlinx.android.synthetic.main.item_library.fileName
import kotlinx.android.synthetic.main.item_library.language
import kotlinx.android.synthetic.main.item_library.publisher
import kotlinx.android.synthetic.main.item_library.size
import kotlinx.android.synthetic.main.item_download.downloadProgress
import kotlinx.android.synthetic.main.item_download.downloadState
import kotlinx.android.synthetic.main.item_download.eta
import kotlinx.android.synthetic.main.item_download.libraryDownloadDescription
import kotlinx.android.synthetic.main.item_download.libraryDownloadFavicon
import kotlinx.android.synthetic.main.item_download.libraryDownloadTitle
import kotlinx.android.synthetic.main.item_download.stop
import kotlinx.android.synthetic.main.item_library.libraryBookCreator
import kotlinx.android.synthetic.main.item_library.libraryBookDate
import kotlinx.android.synthetic.main.item_library.libraryBookDescription
import kotlinx.android.synthetic.main.item_library.libraryBookFavicon
import kotlinx.android.synthetic.main.item_library.libraryBookFileName
import kotlinx.android.synthetic.main.item_library.libraryBookLanguage
import kotlinx.android.synthetic.main.item_library.libraryBookPublisher
import kotlinx.android.synthetic.main.item_library.libraryBookSize
import kotlinx.android.synthetic.main.item_library.libraryBookTitle
import kotlinx.android.synthetic.main.item_library.tags
import kotlinx.android.synthetic.main.item_library.title
import kotlinx.android.synthetic.main.item_library.unableToDownload
import kotlinx.android.synthetic.main.library_divider.divider_text
import org.kiwix.kiwixmobile.R
@ -48,6 +55,7 @@ import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CannotWrit
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.Unknown
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.DividerItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.LibraryDownloadItem
sealed class LibraryViewHolder<in T : LibraryListItem>(containerView: View) :
BaseViewHolder<T>(containerView) {
@ -58,15 +66,15 @@ sealed class LibraryViewHolder<in T : LibraryListItem>(containerView: View) :
private val clickAction: (BookItem) -> Unit
) : LibraryViewHolder<BookItem>(view) {
override fun bind(item: BookItem) {
title.setTextAndVisibility(item.book.title)
description.setTextAndVisibility(item.book.description)
creator.setTextAndVisibility(item.book.creator)
publisher.setTextAndVisibility(item.book.publisher)
date.setTextAndVisibility(item.book.date)
size.setTextAndVisibility(KiloByte(item.book.size).humanReadable)
language.text = bookUtils.getLanguage(item.book.getLanguage())
fileName.text = NetworkUtils.parseURL(CoreApp.getInstance(), item.book.url)
favicon.setBitmap(Base64String(item.book.favicon))
libraryBookTitle.setTextAndVisibility(item.book.title)
libraryBookDescription.setTextAndVisibility(item.book.description)
libraryBookCreator.setTextAndVisibility(item.book.creator)
libraryBookPublisher.setTextAndVisibility(item.book.publisher)
libraryBookDate.setTextAndVisibility(item.book.date)
libraryBookSize.setTextAndVisibility(KiloByte(item.book.size).humanReadable)
libraryBookLanguage.text = bookUtils.getLanguage(item.book.getLanguage())
libraryBookFileName.text = NetworkUtils.parseURL(CoreApp.getInstance(), item.book.url)
libraryBookFavicon.setBitmap(Base64String(item.book.favicon))
containerView.setOnClickListener { clickAction.invoke(item) }
containerView.isClickable = item.canBeDownloaded
@ -87,6 +95,20 @@ sealed class LibraryViewHolder<in T : LibraryListItem>(containerView: View) :
}
}
class DownloadViewHolder(view: View, private val clickAction: (LibraryDownloadItem) -> Unit) :
LibraryViewHolder<LibraryDownloadItem>(view) {
override fun bind(item: LibraryDownloadItem) {
libraryDownloadFavicon.setBitmap(item.favIcon)
libraryDownloadTitle.text = item.title
libraryDownloadDescription.text = item.description
downloadProgress.progress = item.progress
stop.setOnClickListener { clickAction.invoke(item) }
downloadState.text = item.downloadState.toReadableState(containerView.context)
eta.text = item.readableEta
}
}
class LibraryDividerViewHolder(view: View) : LibraryViewHolder<DividerItem>(view) {
override fun bind(item: DividerItem) {
divider_text.text = item.text

View File

@ -11,7 +11,7 @@
android:paddingRight="@dimen/activity_horizontal_margin">
<ImageView
android:id="@+id/favicon"
android:id="@+id/libraryDownloadFavicon"
android:layout_width="@dimen/favicon_width"
android:layout_height="wrap_content"
android:layout_gravity="center"
@ -19,7 +19,8 @@
android:adjustViewBounds="true"
android:minHeight="?android:attr/listPreferredItemHeight"
android:scaleType="fitCenter"
android:src="@mipmap/ic_launcher" />
android:src="@mipmap/ic_launcher"
tools:ignore="RtlHardcoded" />
<LinearLayout
android:layout_width="0dp"
@ -29,7 +30,7 @@
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:id="@+id/libraryDownloadTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
@ -37,7 +38,7 @@
tools:text="Title" />
<TextView
android:id="@+id/description"
android:id="@+id/libraryDownloadDescription"
style="@style/list_item_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -10,7 +10,7 @@
tools:ignore="Overdraw, RTLHardcoded">
<ImageView
android:id="@+id/favicon"
android:id="@+id/libraryBookFavicon"
android:layout_width="@dimen/favicon_width"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
@ -24,82 +24,82 @@
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/title"
android:id="@+id/libraryBookTitle"
style="@style/list_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/favicon_margin_right"
android:layout_marginTop="@dimen/activity_horizontal_margin"
app:layout_constraintStart_toEndOf="@+id/favicon"
app:layout_constraintStart_toEndOf="@+id/libraryBookFavicon"
app:layout_constraintTop_toTopOf="parent"
tools:text="Title" />
<TextView
android:id="@+id/description"
android:id="@+id/libraryBookDescription"
style="@style/list_item_body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/activity_horizontal_margin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/title"
app:layout_constraintTop_toBottomOf="@+id/title"
app:layout_constraintStart_toStartOf="@+id/libraryBookTitle"
app:layout_constraintTop_toBottomOf="@+id/libraryBookTitle"
tools:text="Description a really really really really really really really really really really long descricption" />
<TextView
android:id="@+id/size"
android:id="@+id/libraryBookSize"
style="@style/list_item_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@+id/description"
app:layout_constraintTop_toBottomOf="@+id/description"
app:layout_constraintStart_toStartOf="@+id/libraryBookDescription"
app:layout_constraintTop_toBottomOf="@+id/libraryBookDescription"
tools:text="File Size" />
<TextView
android:id="@+id/creator"
android:id="@+id/libraryBookCreator"
style="@style/list_item_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@+id/size"
app:layout_constraintTop_toBottomOf="@+id/size"
app:layout_constraintStart_toStartOf="@+id/libraryBookSize"
app:layout_constraintTop_toBottomOf="@+id/libraryBookSize"
tools:text="Author" />
<TextView
android:id="@+id/publisher"
android:id="@+id/libraryBookPublisher"
style="@style/list_item_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/activity_horizontal_margin"
app:layout_constraintStart_toStartOf="@+id/creator"
app:layout_constraintTop_toBottomOf="@+id/creator"
app:layout_constraintStart_toStartOf="@+id/libraryBookCreator"
app:layout_constraintTop_toBottomOf="@+id/libraryBookCreator"
tools:text="Publisher" />
<TextView
android:id="@+id/date"
android:id="@+id/libraryBookDate"
style="@style/list_item_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/activity_horizontal_margin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/size"
app:layout_constraintTop_toTopOf="@+id/libraryBookSize"
tools:text="Date" />
<TextView
android:id="@+id/language"
android:id="@+id/libraryBookLanguage"
style="@style/list_item_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="@+id/date"
app:layout_constraintTop_toBottomOf="@+id/date"
app:layout_constraintEnd_toEndOf="@+id/libraryBookDate"
app:layout_constraintTop_toBottomOf="@+id/libraryBookDate"
tools:text="Language" />
<TextView
android:id="@+id/fileName"
android:id="@+id/libraryBookFileName"
style="@style/list_item_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="@+id/language"
app:layout_constraintTop_toBottomOf="@+id/language"
app:layout_constraintEnd_toEndOf="@+id/libraryBookLanguage"
app:layout_constraintTop_toBottomOf="@+id/libraryBookLanguage"
tools:text="File Name" />
<org.kiwix.kiwixmobile.core.zim_manager.TagsView
@ -107,8 +107,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/title"
app:layout_constraintTop_toBottomOf="@id/fileName" />
app:layout_constraintStart_toStartOf="@+id/libraryBookTitle"
app:layout_constraintTop_toBottomOf="@id/libraryBookFileName" />
<View

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<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/zim_download_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical">
<TextView
android:id="@+id/download_management_no_downloads"
style="@style/no_list_content_text"
android:text="@string/no_downloads_here"
tools:ignore="MissingConstraints" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/zim_downloader_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -56,7 +56,6 @@ import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem
import org.kiwix.sharedFunctions.InstantExecutorExtension
import org.kiwix.sharedFunctions.book
import org.kiwix.sharedFunctions.bookOnDisk
import org.kiwix.sharedFunctions.downloadItem
import org.kiwix.sharedFunctions.downloadModel
import org.kiwix.sharedFunctions.language
import org.kiwix.sharedFunctions.libraryNetworkEntity
@ -64,7 +63,6 @@ import org.kiwix.sharedFunctions.resetSchedulers
import org.kiwix.sharedFunctions.setScheduler
import java.util.Locale
import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.SECONDS
@ExtendWith(InstantExecutorExtension::class)
class ZimManageViewModelTest {
@ -149,29 +147,6 @@ class ZimManageViewModelTest {
}
}
@Nested
inner class Downloads {
@Test
fun `on emission from database render downloads`() {
expectDownloads()
viewModel.downloadItems
.test()
.assertValue(listOf(downloadItem()))
}
private fun expectDownloads(
expectedDownloads: List<DownloadModel> = listOf(
downloadModel()
)
) {
every { application.getString(any()) } returns ""
downloads.offer(expectedDownloads)
testScheduler.triggerActions()
testScheduler.advanceTimeBy(1, SECONDS)
testScheduler.triggerActions()
}
}
@Nested
inner class Books {
@Test
@ -344,7 +319,7 @@ class ZimManageViewModelTest {
}
@Test
fun `library update removes from sources`() {
fun `library update removes from sources and maps to list items`() {
every { application.getString(R.string.your_languages) } returns "1"
every { application.getString(R.string.other_languages) } returns "2"
val bookAlreadyOnDisk = book(
@ -394,7 +369,7 @@ class ZimManageViewModelTest {
LibraryListItem.DividerItem(Long.MAX_VALUE, "1"),
LibraryListItem.BookItem(bookWithActiveLanguage, CanWrite4GbFile),
LibraryListItem.DividerItem(Long.MIN_VALUE, "2"),
LibraryListItem.BookItem(bookWithInactiveLanguage, CanWrite4GbFile)
LibraryListItem.LibraryDownloadItem(downloadModel(book = bookDownloading))
)
)
}

View File

@ -17,10 +17,9 @@
*/
package org.kiwix.kiwixmobile.core.downloader
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest
interface DownloadRequester {
fun enqueue(downloadRequest: DownloadRequest): Long
fun cancel(downloadItem: DownloadItem)
fun cancel(downloadId: Long)
}

View File

@ -18,9 +18,8 @@
package org.kiwix.kiwixmobile.core.downloader
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
interface Downloader {
fun download(book: LibraryNetworkEntity.Book)
fun cancelDownload(downloadItem: DownloadItem)
fun cancelDownload(downloadId: Long)
}

View File

@ -21,7 +21,6 @@ package org.kiwix.kiwixmobile.core.downloader
import io.reactivex.Observable
import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import javax.inject.Inject
@ -47,7 +46,7 @@ class DownloaderImpl @Inject constructor(
if (book.url.endsWith("meta4")) kiwixService.getMetaLinks(book.url).map { it.relevantUrl.value }
else Observable.just(book.url)
override fun cancelDownload(downloadItem: DownloadItem) {
downloadRequester.cancel(downloadItem)
override fun cancelDownload(downloadId: Long) {
downloadRequester.cancel(downloadId)
}
}

View File

@ -22,7 +22,6 @@ import com.tonyodev.fetch2.NetworkType.ALL
import com.tonyodev.fetch2.NetworkType.WIFI_ONLY
import com.tonyodev.fetch2.Request
import org.kiwix.kiwixmobile.core.downloader.DownloadRequester
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import javax.inject.Inject
@ -38,8 +37,8 @@ class FetchDownloadRequester @Inject constructor(
return request.id.toLong()
}
override fun cancel(downloadItem: DownloadItem) {
fetch.delete(downloadItem.downloadId.toInt())
override fun cancel(downloadId: Long) {
fetch.delete(downloadId.toInt())
}
}