Download tab

This commit is contained in:
tom5079
2021-06-25 14:45:10 +09:00
parent 975b98e4dc
commit 2a92d287af
10 changed files with 123 additions and 59 deletions

View File

@@ -48,7 +48,7 @@ import kotlin.math.roundToInt
data class ReaderItem(
val progress: Float,
val image: File?
val image: Uri?
)
class ReaderAdapter : ListAdapter<ReaderItem, ReaderAdapter.ViewHolder>(ReaderItemDiffCallback()) {
@@ -107,7 +107,7 @@ class ReaderAdapter : ListAdapter<ReaderItem, ReaderAdapter.ViewHolder>(ReaderIt
if (image != null) {
binding.root.background = null
binding.image.showImage(Uri.fromFile(image))
binding.image.showImage(image)
} else {
binding.root.setBackgroundResource(R.drawable.reader_item_boundary)

View File

@@ -136,12 +136,12 @@ abstract class Source<Query_SortMode: Enum<Query_SortMode>, Suggestion: SearchSu
typealias SourceEntry = Pair<String, AnySource>
typealias SourceEntries = Set<SourceEntry>
typealias PreferenceID = Pair<String, Int>
typealias PreferenceIDs = Set<PreferenceID>
typealias SourcePreferenceID = Pair<String, Int>
typealias SourcePreferenceIDs = Set<SourcePreferenceID>
@Suppress("UNCHECKED_CAST")
val sourceModule = DI.Module(name = "source") {
bindSet<SourceEntry>()
bindSet<PreferenceID>()
bindSet<SourcePreferenceID>()
listOf(
Hitomi()
@@ -151,5 +151,5 @@ val sourceModule = DI.Module(name = "source") {
}
bind { factory { source: String -> History(di, source) } }
bind { singleton { Downloads(di) } }
inSet { singleton { Downloads(di).let { it.name to (it as AnySource) } } }
}

View File

@@ -18,11 +18,8 @@
package xyz.quaver.pupil.sources
import android.app.Application
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.kodein.di.DI
@@ -39,17 +36,15 @@ import kotlin.math.min
class Downloads(override val di: DI) : Source<DefaultSortMode, SearchSuggestion>(), DIAware {
override val name: String
get() = "Downloads"
get() = "downloads"
override val iconResID: Int
get() = R.drawable.ic_download
override val preferenceID: Int
get() = -1
get() = R.xml.download_preferences
override val availableSortMode: Array<DefaultSortMode> = DefaultSortMode.values()
private val downloadManager: DownloadManager by instance()
private val applicationContext: Application by instance()
override suspend fun search(query: String, range: IntRange, sortMode: Enum<*>): Pair<Channel<ItemInfo>, Int> {
val downloads = downloadManager.downloads.toList()
@@ -74,30 +69,38 @@ class Downloads(override val di: DI) : Source<DefaultSortMode, SearchSuggestion>
}
override suspend fun images(itemID: String): List<String> {
TODO("Not yet implemented")
return downloadManager.downloadFolder.getChild(itemID).let {
if (!it.exists()) null else images(it)
}!!
}
override suspend fun info(itemID: String): ItemInfo {
TODO("Not yet implemented")
return transform(downloadManager.downloadFolder.getChild(itemID))
}
companion object {
private fun firstImage(folder: FileX): String? =
private fun images(folder: FileX): List<String>? =
folder.list { _, name ->
name.takeLastWhile { it != '.' } !in listOf("jpg", "png", "gif", "webp")
}?.firstOrNull()
name.takeLastWhile { it != '.' } in listOf("jpg", "png", "gif", "webp")
}?.toList()
fun transform(folder: FileX): ItemInfo =
suspend fun transform(folder: FileX): ItemInfo = withContext(Dispatchers.Unconfined) {
kotlin.runCatching {
Json.decodeFromString<ItemInfo>(folder.getChild(".metadata").readText())
}.getOrNull() ?:
ItemInfo(
"Downloads",
"",
folder.name,
firstImage(folder) ?: "",
""
)
}.getOrNull() ?: run {
val images = images(folder)
ItemInfo(
"Downloads",
folder.name,
folder.name,
images?.firstOrNull() ?: "",
"",
mapOf(
ItemInfo.ExtraType.PAGECOUNT to async { images?.size?.toString() }
)
)
}
}
}
}

View File

@@ -27,14 +27,8 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.kodein.di.*
import org.kodein.di.android.x.closestDI
import org.kodein.di.android.x.di
import org.kodein.type.jvmType
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.pupil.adapters.SourceAdapter
import xyz.quaver.pupil.sources.AnySource
import xyz.quaver.pupil.sources.Source
import xyz.quaver.pupil.sources.SourceEntries
import xyz.quaver.pupil.util.ItemClickSupport
import xyz.quaver.pupil.sources.*
class SourceSelectDialog : DialogFragment(), DIAware {
@@ -48,9 +42,14 @@ class SourceSelectDialog : DialogFragment(), DIAware {
window?.requestFeature(Window.FEATURE_NO_TITLE)
window?.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
val sourcesWithPreferenceID = direct.instance<SourcePreferenceIDs>().map { it.first }
val preferences = direct.instance<SourceEntries>().filter {
it.first in sourcesWithPreferenceID
}.toSet()
setContentView(RecyclerView(context).apply {
layoutManager = LinearLayoutManager(context)
adapter = SourceAdapter(direct.instance()).apply {
adapter = SourceAdapter(preferences).apply {
onSourceSelectedListener = this@SourceSelectDialog.onSourceSelectedListener
onSourceSettingsSelectedListener = this@SourceSelectDialog.onSourceSettingsSelectedListener
}

View File

@@ -30,7 +30,7 @@ import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI
import org.kodein.di.direct
import org.kodein.di.instance
import xyz.quaver.pupil.sources.PreferenceIDs
import xyz.quaver.pupil.sources.SourcePreferenceIDs
import xyz.quaver.pupil.ui.dialog.DefaultQueryDialogFragment
import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.getAvailableLanguages
@@ -45,7 +45,7 @@ class SourceSettingsFragment(private val source: String) :
override val di by closestDI()
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(direct.instance<PreferenceIDs>().toMap()[source]!!, rootKey)
setPreferencesFromResource(direct.instance<SourcePreferenceIDs>().toMap()[source]!!, rootKey)
initPreferences()
}

View File

@@ -105,13 +105,18 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
fun setModeAndReset(mode: MainMode) {
sourceFactory = when (mode) {
MainMode.SEARCH -> defaultSourceFactory
MainMode.SEARCH, MainMode.DOWNLOADS -> defaultSourceFactory
MainMode.HISTORY -> { { direct.instance<String, History>(arg = it) } }
MainMode.DOWNLOADS -> { { direct.instance<Downloads>() } }
else -> return
}
setSourceAndReset(source.value!!.name)
setSourceAndReset(
when {
mode == MainMode.DOWNLOADS -> "downloads"
source.value is Downloads -> "hitomi.la"
else -> source.value!!.name
}
)
}
fun query() {

View File

@@ -20,6 +20,7 @@
package xyz.quaver.pupil.ui.viewmodel
import android.app.Application
import android.net.Uri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
@@ -29,8 +30,10 @@ import okhttp3.Headers
import okhttp3.Headers.Companion.toHeaders
import okhttp3.Request
import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI
import org.kodein.di.android.x.di
import org.kodein.di.instance
import xyz.quaver.io.FileX
import xyz.quaver.pupil.adapters.ReaderItem
import xyz.quaver.pupil.sources.AnySource
import xyz.quaver.pupil.util.ImageCache
@@ -40,7 +43,7 @@ import xyz.quaver.pupil.util.source
@Suppress("UNCHECKED_CAST")
class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware {
override val di by di()
override val di by closestDI()
private val cache: ImageCache by instance()
@@ -71,29 +74,38 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware {
_images.value = images
images.forEachIndexed { index, image ->
val file = cache.load(
Request.Builder()
.url(image)
.headers(source.getHeadersForImage(itemID, image).toHeaders())
.build()
)
when (val scheme = image.takeWhile { it != ':' }) {
"http", "https" -> {
val file = cache.load(
Request.Builder()
.url(image)
.headers(source.getHeadersForImage(itemID, image).toHeaders())
.build()
)
val channel = cache.channels[image] ?: error("Channel is null")
val channel = cache.channels[image] ?: error("Channel is null")
channel.invokeOnClose { e ->
viewModelScope.launch {
if (e == null) {
_readerItems.value!![index] = ReaderItem(_readerItems.value!![index].progress, file)
_readerItems.notify()
channel.invokeOnClose { e ->
viewModelScope.launch {
if (e == null) {
_readerItems.value!![index] = ReaderItem(_readerItems.value!![index].progress, Uri.fromFile(file))
_readerItems.notify()
}
}
}
launch {
for (progress in channel) {
_readerItems.value!![index] = ReaderItem(progress, _readerItems.value!![index].image)
_readerItems.notify()
}
}
}
}
launch {
for (progress in channel) {
_readerItems.value!![index] = ReaderItem(progress, _readerItems.value!![index].image)
"content" -> {
_readerItems.value!![index] = ReaderItem(100f, Uri.parse(image))
_readerItems.notify()
}
else -> throw IllegalArgumentException("Expected URL scheme 'http(s)' or 'content' but was '$scheme'")
}
}
}

View File

@@ -104,6 +104,9 @@
android:id="@+id/id_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="150dp"
android:ellipsize="end"
android:maxLines="1"
android:layout_margin="8dp"
app:layout_constraintTop_toBottomOf="@id/divider"
app:layout_constraintBottom_toBottomOf="parent"

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2021 tom5079
~
~ 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/>.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
</PreferenceScreen>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2021 tom5079
~
~ 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/>.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
</PreferenceScreen>