Download tab
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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) } } }
|
||||
}
|
||||
@@ -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() }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
21
app/src/main/res/xml/download_preferences.xml
Normal file
21
app/src/main/res/xml/download_preferences.xml
Normal 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>
|
||||
21
app/src/main/res/xml/history_preferences.xml
Normal file
21
app/src/main/res/xml/history_preferences.xml
Normal 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>
|
||||
Reference in New Issue
Block a user