@@ -15,8 +15,8 @@ android {
|
|||||||
applicationId 'org.koitharu.kotatsu'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 494
|
versionCode 495
|
||||||
versionName '4.0-a5'
|
versionName '4.0-a6'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation('com.github.KotatsuApp:kotatsu-parsers:f112a06ab6') {
|
implementation('com.github.KotatsuApp:kotatsu-parsers:551a1d70ae') {
|
||||||
exclude group: 'org.json', module: 'json'
|
exclude group: 'org.json', module: 'json'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ dependencies {
|
|||||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||||
implementation 'androidx.work:work-runtime-ktx:2.7.1'
|
implementation 'androidx.work:work-runtime-ktx:2.7.1'
|
||||||
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04'
|
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04'
|
||||||
implementation 'com.google.android.material:material:1.7.0-beta01'
|
implementation 'com.google.android.material:material:1.7.0-rc01'
|
||||||
//noinspection LifecycleAnnotationProcessorWithJava8
|
//noinspection LifecycleAnnotationProcessorWithJava8
|
||||||
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.1'
|
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.1'
|
||||||
|
|
||||||
@@ -124,7 +124,6 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'io.coil-kt:coil-base:2.2.0'
|
implementation 'io.coil-kt:coil-base:2.2.0'
|
||||||
implementation 'io.coil-kt:coil-svg:2.2.0'
|
implementation 'io.coil-kt:coil-svg:2.2.0'
|
||||||
// implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
|
|
||||||
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:2942b797a2'
|
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:2942b797a2'
|
||||||
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.BuildConfig
|
|||||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||||
import org.koitharu.kotatsu.core.network.DoHProvider
|
import org.koitharu.kotatsu.core.network.DoHProvider
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||||
import org.koitharu.kotatsu.utils.ext.getEnumValue
|
import org.koitharu.kotatsu.utils.ext.getEnumValue
|
||||||
import org.koitharu.kotatsu.utils.ext.observe
|
import org.koitharu.kotatsu.utils.ext.observe
|
||||||
import org.koitharu.kotatsu.utils.ext.putEnumValue
|
import org.koitharu.kotatsu.utils.ext.putEnumValue
|
||||||
@@ -208,6 +209,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
val dnsOverHttps: DoHProvider
|
val dnsOverHttps: DoHProvider
|
||||||
get() = prefs.getEnumValue(KEY_DOH, DoHProvider.NONE)
|
get() = prefs.getEnumValue(KEY_DOH, DoHProvider.NONE)
|
||||||
|
|
||||||
|
var localListOrder: SortOrder
|
||||||
|
get() = prefs.getEnumValue(KEY_LOCAL_LIST_ORDER, SortOrder.NEWEST)
|
||||||
|
set(value) = prefs.edit { putEnumValue(KEY_LOCAL_LIST_ORDER, value) }
|
||||||
|
|
||||||
fun isPagesPreloadAllowed(cm: ConnectivityManager): Boolean {
|
fun isPagesPreloadAllowed(cm: ConnectivityManager): Boolean {
|
||||||
return when (prefs.getString(KEY_PAGES_PRELOAD, null)?.toIntOrNull()) {
|
return when (prefs.getString(KEY_PAGES_PRELOAD, null)?.toIntOrNull()) {
|
||||||
NETWORK_ALWAYS -> true
|
NETWORK_ALWAYS -> true
|
||||||
@@ -325,6 +330,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
const val KEY_READER_BAR = "reader_bar"
|
const val KEY_READER_BAR = "reader_bar"
|
||||||
const val KEY_SHORTCUTS = "dynamic_shortcuts"
|
const val KEY_SHORTCUTS = "dynamic_shortcuts"
|
||||||
const val KEY_READER_TAPS_LTR = "reader_taps_ltr"
|
const val KEY_READER_TAPS_LTR = "reader_taps_ltr"
|
||||||
|
const val KEY_LOCAL_LIST_ORDER = "local_order"
|
||||||
|
|
||||||
// About
|
// About
|
||||||
const val KEY_APP_UPDATE = "app_update"
|
const val KEY_APP_UPDATE = "app_update"
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.koitharu.kotatsu.local.domain
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||||
|
|
||||||
|
class LocalManga(
|
||||||
|
val manga: Manga,
|
||||||
|
val file: File,
|
||||||
|
) {
|
||||||
|
|
||||||
|
var createdAt: Long = -1L
|
||||||
|
get() {
|
||||||
|
if (field == -1L) {
|
||||||
|
field = file.lastModified()
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as LocalManga
|
||||||
|
|
||||||
|
if (manga != other.manga) return false
|
||||||
|
if (file != other.file) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = manga.hashCode()
|
||||||
|
result = 31 * result + file.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Collection<LocalManga>.unwrap(): List<Manga> = map { it.manga }
|
||||||
|
|
||||||
|
fun LocalManga.isMatchesQuery(query: String): Boolean {
|
||||||
|
return manga.title.contains(query, ignoreCase = true) ||
|
||||||
|
manga.altTitle?.contains(query, ignoreCase = true) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LocalManga.containsTags(tags: Set<MangaTag>): Boolean {
|
||||||
|
return manga.tags.containsAll(tags)
|
||||||
|
}
|
||||||
@@ -45,12 +45,9 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
|
|||||||
}
|
}
|
||||||
val list = getRawList()
|
val list = getRawList()
|
||||||
if (query.isNotEmpty()) {
|
if (query.isNotEmpty()) {
|
||||||
list.retainAll { x ->
|
list.retainAll { x -> x.isMatchesQuery(query) }
|
||||||
x.title.contains(query, ignoreCase = true) ||
|
|
||||||
x.altTitle?.contains(query, ignoreCase = true) == true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return list
|
return list.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> {
|
override suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> {
|
||||||
@@ -59,15 +56,17 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
|
|||||||
}
|
}
|
||||||
val list = getRawList()
|
val list = getRawList()
|
||||||
if (!tags.isNullOrEmpty()) {
|
if (!tags.isNullOrEmpty()) {
|
||||||
list.retainAll { x ->
|
list.retainAll { x -> x.containsTags(tags) }
|
||||||
x.tags.containsAll(tags)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@Suppress("NON_EXHAUSTIVE_WHEN_STATEMENT")
|
||||||
when (sortOrder) {
|
when (sortOrder) {
|
||||||
SortOrder.ALPHABETICAL -> list.sortBy { it.title }
|
SortOrder.ALPHABETICAL -> list.sortWith(compareBy(AlphanumComparator()) { x -> x.manga.title })
|
||||||
SortOrder.RATING -> list.sortBy { it.rating }
|
SortOrder.RATING -> list.sortByDescending { it.manga.rating }
|
||||||
|
SortOrder.NEWEST,
|
||||||
|
SortOrder.UPDATED,
|
||||||
|
-> list.sortByDescending { it.createdAt }
|
||||||
}
|
}
|
||||||
return list
|
return list.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getDetails(manga: Manga) = when {
|
override suspend fun getDetails(manga: Manga) = when {
|
||||||
@@ -235,9 +234,9 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
|
|||||||
private fun CoroutineScope.getFromFileAsync(
|
private fun CoroutineScope.getFromFileAsync(
|
||||||
file: File,
|
file: File,
|
||||||
context: CoroutineContext,
|
context: CoroutineContext,
|
||||||
): Deferred<Manga?> = async(context) {
|
): Deferred<LocalManga?> = async(context) {
|
||||||
runInterruptible {
|
runInterruptible {
|
||||||
runCatching { getFromFile(file) }.getOrNull()
|
runCatching { LocalManga(getFromFile(file), file) }.getOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +282,7 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
|
|||||||
locks.unlock(id)
|
locks.unlock(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getRawList(): ArrayList<Manga> {
|
private suspend fun getRawList(): ArrayList<LocalManga> {
|
||||||
val files = getAllFiles()
|
val files = getAllFiles()
|
||||||
return coroutineScope {
|
return coroutineScope {
|
||||||
val dispatcher = Dispatchers.IO.limitedParallelism(MAX_PARALLELISM)
|
val dispatcher = Dispatchers.IO.limitedParallelism(MAX_PARALLELISM)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.view.Menu
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
@@ -13,10 +14,11 @@ import com.google.android.material.snackbar.Snackbar
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
|
import org.koitharu.kotatsu.base.ui.list.ListSelectionController
|
||||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||||
|
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||||
import org.koitharu.kotatsu.utils.ShareHelper
|
import org.koitharu.kotatsu.utils.ShareHelper
|
||||||
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
||||||
|
|
||||||
class LocalListFragment : MangaListFragment() {
|
class LocalListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickListener {
|
||||||
|
|
||||||
override val viewModel by viewModels<LocalListViewModel>()
|
override val viewModel by viewModels<LocalListViewModel>()
|
||||||
|
|
||||||
@@ -30,6 +32,14 @@ class LocalListFragment : MangaListFragment() {
|
|||||||
ImportDialogFragment.show(childFragmentManager)
|
ImportDialogFragment.show(childFragmentManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onFilterClick(view: View?) {
|
||||||
|
super.onFilterClick(view)
|
||||||
|
val menu = PopupMenu(requireContext(), view ?: binding.recyclerView)
|
||||||
|
menu.inflate(R.menu.popup_order)
|
||||||
|
menu.setOnMenuItemClickListener(this)
|
||||||
|
menu.show()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onScrolledToEnd() = Unit
|
override fun onScrolledToEnd() = Unit
|
||||||
|
|
||||||
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
||||||
@@ -53,6 +63,17 @@ class LocalListFragment : MangaListFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||||
|
val order = when (item.itemId) {
|
||||||
|
R.id.action_order_new -> SortOrder.NEWEST
|
||||||
|
R.id.action_order_abs -> SortOrder.ALPHABETICAL
|
||||||
|
R.id.action_order_rating -> SortOrder.RATING
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
viewModel.setSortOrder(order)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun showDeletionConfirm(ids: Set<Long>, mode: ActionMode) {
|
private fun showDeletionConfirm(ids: Set<Long>, mode: ActionMode) {
|
||||||
MaterialAlertDialogBuilder(context ?: return)
|
MaterialAlertDialogBuilder(context ?: return)
|
||||||
.setTitle(R.string.delete_manga)
|
.setTitle(R.string.delete_manga)
|
||||||
|
|||||||
@@ -23,4 +23,4 @@ class LocalListMenuProvider(
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,31 @@
|
|||||||
package org.koitharu.kotatsu.local.ui
|
package org.koitharu.kotatsu.local.ui
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.asFlow
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||||
|
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
|
||||||
|
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
||||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
import org.koitharu.kotatsu.list.ui.model.*
|
||||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
|
||||||
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
|
||||||
import org.koitharu.kotatsu.list.ui.model.toUi
|
|
||||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||||
|
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||||
|
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||||
@@ -30,18 +34,24 @@ import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
|||||||
class LocalListViewModel @Inject constructor(
|
class LocalListViewModel @Inject constructor(
|
||||||
private val repository: LocalMangaRepository,
|
private val repository: LocalMangaRepository,
|
||||||
private val historyRepository: HistoryRepository,
|
private val historyRepository: HistoryRepository,
|
||||||
settings: AppSettings,
|
private val trackingRepository: TrackingRepository,
|
||||||
) : MangaListViewModel(settings) {
|
private val settings: AppSettings,
|
||||||
|
) : MangaListViewModel(settings), ListExtraProvider {
|
||||||
|
|
||||||
val onMangaRemoved = SingleLiveEvent<Unit>()
|
val onMangaRemoved = SingleLiveEvent<Unit>()
|
||||||
|
val sortOrder = MutableLiveData(settings.localListOrder)
|
||||||
private val listError = MutableStateFlow<Throwable?>(null)
|
private val listError = MutableStateFlow<Throwable?>(null)
|
||||||
private val mangaList = MutableStateFlow<List<Manga>?>(null)
|
private val mangaList = MutableStateFlow<List<Manga>?>(null)
|
||||||
|
private val selectedTags = MutableStateFlow<Set<MangaTag>>(emptySet())
|
||||||
|
private var refreshJob: Job? = null
|
||||||
|
|
||||||
override val content = combine(
|
override val content = combine(
|
||||||
mangaList,
|
mangaList,
|
||||||
createListModeFlow(),
|
createListModeFlow(),
|
||||||
|
sortOrder.asFlow(),
|
||||||
|
selectedTags,
|
||||||
listError,
|
listError,
|
||||||
) { list, mode, error ->
|
) { list, mode, order, tags, error ->
|
||||||
when {
|
when {
|
||||||
error != null -> listOf(error.toErrorState(canRetry = true))
|
error != null -> listOf(error.toErrorState(canRetry = true))
|
||||||
list == null -> listOf(LoadingState)
|
list == null -> listOf(LoadingState)
|
||||||
@@ -53,7 +63,10 @@ class LocalListViewModel @Inject constructor(
|
|||||||
actionStringRes = R.string._import,
|
actionStringRes = R.string._import,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else -> list.toUi(mode)
|
else -> buildList(list.size + 1) {
|
||||||
|
add(createHeader(list, tags, order))
|
||||||
|
list.toUi(this, mode, this@LocalListViewModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
||||||
|
|
||||||
@@ -63,14 +76,27 @@ class LocalListViewModel @Inject constructor(
|
|||||||
watchDirectories()
|
watchDirectories()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onUpdateFilter(tags: Set<MangaTag>) {
|
||||||
|
selectedTags.value = tags
|
||||||
|
onRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onRefresh() {
|
override fun onRefresh() {
|
||||||
launchLoadingJob(Dispatchers.Default) {
|
val prevJob = refreshJob
|
||||||
|
refreshJob = launchLoadingJob(Dispatchers.Default) {
|
||||||
|
prevJob?.cancelAndJoin()
|
||||||
doRefresh()
|
doRefresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRetry() = onRefresh()
|
override fun onRetry() = onRefresh()
|
||||||
|
|
||||||
|
fun setSortOrder(value: SortOrder) {
|
||||||
|
sortOrder.value = value
|
||||||
|
settings.localListOrder = value
|
||||||
|
onRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
fun delete(ids: Set<Long>) {
|
fun delete(ids: Set<Long>) {
|
||||||
launchLoadingJob {
|
launchLoadingJob {
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
@@ -93,7 +119,7 @@ class LocalListViewModel @Inject constructor(
|
|||||||
private suspend fun doRefresh() {
|
private suspend fun doRefresh() {
|
||||||
try {
|
try {
|
||||||
listError.value = null
|
listError.value = null
|
||||||
mangaList.value = repository.getList(0, null, null)
|
mangaList.value = repository.getList(0, selectedTags.value, sortOrder.value)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
listError.value = e
|
listError.value = e
|
||||||
}
|
}
|
||||||
@@ -119,4 +145,46 @@ class LocalListViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createHeader(mangaList: List<Manga>, selectedTags: Set<MangaTag>, order: SortOrder): ListHeader2 {
|
||||||
|
val tags = HashMap<MangaTag, Int>()
|
||||||
|
for (item in mangaList) {
|
||||||
|
for (tag in item.tags) {
|
||||||
|
tags[tag] = tags[tag]?.plus(1) ?: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val topTags = tags.entries.sortedByDescending { it.value }.take(6)
|
||||||
|
val chips = LinkedList<ChipsView.ChipModel>()
|
||||||
|
for ((tag, _) in topTags) {
|
||||||
|
val model = ChipsView.ChipModel(
|
||||||
|
icon = 0,
|
||||||
|
title = tag.title,
|
||||||
|
isCheckable = true,
|
||||||
|
isChecked = tag in selectedTags,
|
||||||
|
data = tag,
|
||||||
|
)
|
||||||
|
if (model.isChecked) {
|
||||||
|
chips.addFirst(model)
|
||||||
|
} else {
|
||||||
|
chips.addLast(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ListHeader2(
|
||||||
|
chips = chips,
|
||||||
|
sortOrder = order,
|
||||||
|
hasSelectedTags = selectedTags.isNotEmpty(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getCounter(mangaId: Long): Int {
|
||||||
|
return trackingRepository.getNewChaptersCount(mangaId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getProgress(mangaId: Long): Float {
|
||||||
|
return if (settings.isReadingIndicatorsEnabled) {
|
||||||
|
historyRepository.getProgress(mangaId)
|
||||||
|
} else {
|
||||||
|
PROGRESS_NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
app/src/main/res/menu/popup_order.xml
Normal file
17
app/src/main/res/menu/popup_order.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_order_new"
|
||||||
|
android:title="@string/newest" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_order_abs"
|
||||||
|
android:title="@string/by_name" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_order_rating"
|
||||||
|
android:title="@string/by_rating" />
|
||||||
|
|
||||||
|
</menu>
|
||||||
Reference in New Issue
Block a user