Fix search
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
package org.koitharu.kotatsu.base.domain
|
||||
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
|
||||
object MangaProviderFactory : KoinComponent {
|
||||
object MangaProviderFactory {
|
||||
|
||||
fun getSources(includeHidden: Boolean): List<MangaSource> {
|
||||
val settings = get<AppSettings>()
|
||||
fun getSources(settings: AppSettings, includeHidden: Boolean): List<MangaSource> {
|
||||
val list = MangaSource.values().toList() - MangaSource.LOCAL
|
||||
val order = settings.sourcesOrder
|
||||
val hidden = settings.hiddenSources
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package org.koitharu.kotatsu.base.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.app.ActivityCompat
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
|
||||
@@ -36,4 +39,12 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||
onBackPressed()
|
||||
true
|
||||
} else super.onOptionsItemSelected(item)
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) { // TODO remove
|
||||
ActivityCompat.recreate(this)
|
||||
return true
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,7 @@ class AppSettings private constructor(private val prefs: SharedPreferences) :
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use observe()")
|
||||
fun subscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
|
||||
prefs.registerOnSharedPreferenceChangeListener(listener)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@ package org.koitharu.kotatsu.core.ui
|
||||
|
||||
import android.content.res.Resources
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.utils.ext.daysDiff
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
sealed class DateTimeAgo {
|
||||
|
||||
@@ -28,6 +25,12 @@ sealed class DateTimeAgo {
|
||||
}
|
||||
}
|
||||
|
||||
object Today : DateTimeAgo() {
|
||||
override fun format(resources: Resources): String {
|
||||
return resources.getString(R.string.today)
|
||||
}
|
||||
}
|
||||
|
||||
object Yesterday : DateTimeAgo() {
|
||||
override fun format(resources: Resources): String {
|
||||
return resources.getString(R.string.yesterday)
|
||||
@@ -45,22 +48,4 @@ sealed class DateTimeAgo {
|
||||
return resources.getString(R.string.long_ago)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun from(date: Date): DateTimeAgo {
|
||||
val diff = (System.currentTimeMillis() - date.time).coerceAtLeast(0L)
|
||||
val diffMinutes = TimeUnit.MILLISECONDS.toMinutes(diff).toInt()
|
||||
val diffHours = TimeUnit.MILLISECONDS.toHours(diff).toInt()
|
||||
val diffDays = -date.daysDiff(System.currentTimeMillis())
|
||||
return when {
|
||||
diffMinutes < 1 -> JustNow
|
||||
diffMinutes < 60 -> MinutesAgo(diffMinutes)
|
||||
diffDays < 1 -> HoursAgo(diffHours)
|
||||
diffDays == 1 -> Yesterday
|
||||
diffDays < 16 -> DaysAgo(diffDays)
|
||||
else -> LongAgo
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,11 @@ import org.koitharu.kotatsu.list.ui.model.toListDetailedModel
|
||||
import org.koitharu.kotatsu.list.ui.model.toListModel
|
||||
import org.koitharu.kotatsu.utils.MangaShortcut
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.daysDiff
|
||||
import org.koitharu.kotatsu.utils.ext.onFirst
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class HistoryListViewModel(
|
||||
private val repository: HistoryRepository,
|
||||
@@ -78,7 +82,7 @@ class HistoryListViewModel(
|
||||
var prevDate: DateTimeAgo? = null
|
||||
for ((manga, history) in list) {
|
||||
if (grouped) {
|
||||
val date = DateTimeAgo.from(history.updatedAt)
|
||||
val date = timeAgo(history.updatedAt)
|
||||
if (prevDate != date) {
|
||||
result += date
|
||||
}
|
||||
@@ -92,4 +96,17 @@ class HistoryListViewModel(
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun timeAgo(date: Date): DateTimeAgo {
|
||||
val diff = (System.currentTimeMillis() - date.time).coerceAtLeast(0L)
|
||||
val diffMinutes = TimeUnit.MILLISECONDS.toMinutes(diff).toInt()
|
||||
val diffDays = -date.daysDiff(System.currentTimeMillis())
|
||||
return when {
|
||||
diffMinutes < 3 -> DateTimeAgo.JustNow
|
||||
diffDays < 1 -> DateTimeAgo.Today
|
||||
diffDays == 1 -> DateTimeAgo.Yesterday
|
||||
diffDays < 6 -> DateTimeAgo.DaysAgo(diffDays)
|
||||
else -> DateTimeAgo.LongAgo
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,6 @@ import org.koitharu.kotatsu.main.ui.protect.ProtectViewModel
|
||||
|
||||
val mainModule
|
||||
get() = module {
|
||||
viewModel { MainViewModel(get()) }
|
||||
viewModel { MainViewModel(get(), get()) }
|
||||
viewModel { ProtectViewModel(get()) }
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.koitharu.kotatsu.main.ui
|
||||
|
||||
import android.app.ActivityOptions
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
@@ -11,20 +10,17 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.*
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.android.viewmodel.ext.android.viewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaProviderFactory
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.prefs.AppSection
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.favourites.ui.FavouritesContainerFragment
|
||||
import org.koitharu.kotatsu.history.ui.HistoryListFragment
|
||||
import org.koitharu.kotatsu.local.ui.LocalListFragment
|
||||
@@ -42,11 +38,10 @@ import org.koitharu.kotatsu.utils.ext.resolveDp
|
||||
import java.io.Closeable
|
||||
|
||||
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener,
|
||||
SharedPreferences.OnSharedPreferenceChangeListener, View.OnClickListener {
|
||||
View.OnClickListener {
|
||||
|
||||
private val viewModel by viewModel<MainViewModel>()
|
||||
|
||||
private val settings by inject<AppSettings>()
|
||||
private lateinit var drawerToggle: ActionBarDrawerToggle
|
||||
private var closeable: Closeable? = null
|
||||
|
||||
@@ -59,11 +54,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
navigationView.setNavigationItemSelectedListener(this)
|
||||
settings.subscribe(this)
|
||||
|
||||
with(fab) {
|
||||
imageTintList = ColorStateList.valueOf(Color.WHITE)
|
||||
isVisible = true
|
||||
setOnClickListener(this@MainActivity)
|
||||
}
|
||||
|
||||
@@ -78,14 +71,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
TrackWorker.setup(applicationContext)
|
||||
AppUpdateChecker(this).launchIfNeeded()
|
||||
|
||||
viewModel.onOpenReader.observe(this, ::onOpenReader)
|
||||
viewModel.onError.observe(this, ::onError)
|
||||
viewModel.isLoading.observe(this, ::onLoadingStateChanged)
|
||||
viewModel.onOpenReader.observe(this, this::onOpenReader)
|
||||
viewModel.onError.observe(this, this::onError)
|
||||
viewModel.isLoading.observe(this, this::onLoadingStateChanged)
|
||||
viewModel.remoteSources.observe(this, this::updateSideMenu)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
closeable?.close()
|
||||
settings.unsubscribe(this)
|
||||
AppProtectHelper.lock()
|
||||
super.onDestroy()
|
||||
}
|
||||
@@ -93,7 +86,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
override fun onPostCreate(savedInstanceState: Bundle?) {
|
||||
super.onPostCreate(savedInstanceState)
|
||||
drawerToggle.syncState()
|
||||
initSideMenu(MangaProviderFactory.getSources(includeHidden = false))
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
@@ -135,19 +127,19 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
setPrimaryFragment(RemoteListFragment.newInstance(source))
|
||||
} else when (item.itemId) {
|
||||
R.id.nav_history -> {
|
||||
settings.defaultSection = AppSection.HISTORY
|
||||
viewModel.defaultSection = AppSection.HISTORY
|
||||
setPrimaryFragment(HistoryListFragment.newInstance())
|
||||
}
|
||||
R.id.nav_favourites -> {
|
||||
settings.defaultSection = AppSection.FAVOURITES
|
||||
viewModel.defaultSection = AppSection.FAVOURITES
|
||||
setPrimaryFragment(FavouritesContainerFragment.newInstance())
|
||||
}
|
||||
R.id.nav_local_storage -> {
|
||||
settings.defaultSection = AppSection.LOCAL
|
||||
viewModel.defaultSection = AppSection.LOCAL
|
||||
setPrimaryFragment(LocalListFragment.newInstance())
|
||||
}
|
||||
R.id.nav_feed -> {
|
||||
settings.defaultSection = AppSection.FEED
|
||||
viewModel.defaultSection = AppSection.FEED
|
||||
setPrimaryFragment(FeedFragment.newInstance())
|
||||
}
|
||||
R.id.nav_action_settings -> {
|
||||
@@ -190,7 +182,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
}
|
||||
|
||||
private fun initSideMenu(remoteSources: List<MangaSource>) {
|
||||
private fun updateSideMenu(remoteSources: List<MangaSource>) {
|
||||
val submenu = navigationView.menu.findItem(R.id.nav_remote_sources).subMenu
|
||||
submenu.removeGroup(R.id.group_remote_sources)
|
||||
remoteSources.forEachIndexed { index, source ->
|
||||
@@ -199,17 +191,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
submenu.setGroupCheckable(R.id.group_remote_sources, true, true)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
when (key) {
|
||||
AppSettings.KEY_SOURCES_HIDDEN,
|
||||
AppSettings.KEY_SOURCES_ORDER -> {
|
||||
initSideMenu(MangaProviderFactory.getSources(includeHidden = false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openDefaultSection() {
|
||||
when (settings.defaultSection) {
|
||||
when (viewModel.defaultSection) {
|
||||
AppSection.LOCAL -> {
|
||||
navigationView.setCheckedItem(R.id.nav_local_storage)
|
||||
setPrimaryFragment(LocalListFragment.newInstance())
|
||||
|
||||
@@ -1,16 +1,34 @@
|
||||
package org.koitharu.kotatsu.main.ui
|
||||
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import org.koitharu.kotatsu.base.domain.MangaProviderFactory
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
|
||||
class MainViewModel(
|
||||
private val historyRepository: HistoryRepository
|
||||
private val historyRepository: HistoryRepository,
|
||||
settings: AppSettings
|
||||
) : BaseViewModel() {
|
||||
|
||||
val onOpenReader = SingleLiveEvent<ReaderState>()
|
||||
var defaultSection by settings::defaultSection
|
||||
|
||||
val remoteSources = settings.observe()
|
||||
.filter { it == AppSettings.KEY_SOURCES_ORDER || it == AppSettings.KEY_SOURCES_HIDDEN }
|
||||
.onStart { emit("") }
|
||||
.map { MangaProviderFactory.getSources(settings, includeHidden = false) }
|
||||
.distinctUntilChanged()
|
||||
.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
fun openLastReader() {
|
||||
launchLoadingJob {
|
||||
|
||||
@@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||
@@ -53,20 +52,18 @@ class RemoteListViewModel(
|
||||
if (loadingJob?.isActive == true) {
|
||||
return
|
||||
}
|
||||
loadingJob = launchLoadingJob {
|
||||
withContext(Dispatchers.Default) {
|
||||
val list = repository.getList(
|
||||
offset = if (append) mangaList.value.size else 0,
|
||||
sortOrder = appliedFilter?.sortOrder,
|
||||
tag = appliedFilter?.tag
|
||||
)
|
||||
if (!append) {
|
||||
mangaList.value = list
|
||||
} else if (list.isNotEmpty()) {
|
||||
mangaList.value += list
|
||||
}
|
||||
hasNextPage.value = list.isNotEmpty()
|
||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||
val list = repository.getList(
|
||||
offset = if (append) mangaList.value.size else 0,
|
||||
sortOrder = appliedFilter?.sortOrder,
|
||||
tag = appliedFilter?.tag
|
||||
)
|
||||
if (!append) {
|
||||
mangaList.value = list
|
||||
} else if (list.isNotEmpty()) {
|
||||
mangaList.value += list
|
||||
}
|
||||
hasNextPage.value = list.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,13 +75,11 @@ class RemoteListViewModel(
|
||||
}
|
||||
|
||||
private fun loadFilter() {
|
||||
launchJob {
|
||||
launchJob(Dispatchers.Default) {
|
||||
try {
|
||||
val (sorts, tags) = withContext(Dispatchers.Default) {
|
||||
repository.sortOrders.sortedBy { it.ordinal } to repository.getTags()
|
||||
.sortedBy { it.title }
|
||||
}
|
||||
filter.value = MangaFilterConfig(sorts, tags, appliedFilter)
|
||||
val sorts = repository.sortOrders.sortedBy { it.ordinal }
|
||||
val tags = repository.getTags().sortedBy { it.title }
|
||||
filter.postValue(MangaFilterConfig(sorts, tags, appliedFilter))
|
||||
} catch (e: Exception) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
|
||||
@@ -11,8 +11,12 @@ import org.koitharu.kotatsu.search.ui.global.GlobalSearchViewModel
|
||||
val searchModule
|
||||
get() = module {
|
||||
|
||||
single { MangaSearchRepository() }
|
||||
single { MangaSearchRepository(get()) }
|
||||
|
||||
viewModel { (source: MangaSource) -> SearchViewModel(get(named(source)), get()) }
|
||||
viewModel { GlobalSearchViewModel(get(), get()) }
|
||||
viewModel { (source: MangaSource, query: String) ->
|
||||
SearchViewModel(get(named(source)), query, get())
|
||||
}
|
||||
viewModel { (query: String) ->
|
||||
GlobalSearchViewModel(query, get(), get())
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,13 @@ import org.koitharu.kotatsu.base.domain.MangaProviderFactory
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.model.SortOrder
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import java.util.*
|
||||
|
||||
class MangaSearchRepository {
|
||||
class MangaSearchRepository(private val settings: AppSettings) {
|
||||
|
||||
fun globalSearch(query: String, batchSize: Int = 4): Flow<List<Manga>> = flow {
|
||||
val sources = MangaProviderFactory.getSources(false)
|
||||
val sources = MangaProviderFactory.getSources(settings, includeHidden = false)
|
||||
val lists = EnumMap<MangaSource, List<Manga>>(MangaSource::class.java)
|
||||
var i = 0
|
||||
while (true) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
class MangaSearchSheet : MangaListSheet() {
|
||||
|
||||
override val viewModel by viewModel<SearchViewModel> {
|
||||
parametersOf(source)
|
||||
parametersOf(source, query)
|
||||
}
|
||||
|
||||
private val query by stringArgument(ARG_QUERY)
|
||||
@@ -29,7 +29,7 @@ class MangaSearchSheet : MangaListSheet() {
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() {
|
||||
viewModel.loadList(query.orEmpty(), append = true)
|
||||
viewModel.loadList(append = true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
class SearchFragment : MangaListFragment() {
|
||||
|
||||
override val viewModel by viewModel<SearchViewModel> {
|
||||
parametersOf(source)
|
||||
parametersOf(source, query)
|
||||
}
|
||||
|
||||
private val query by stringArgument(ARG_QUERY)
|
||||
@@ -19,16 +19,14 @@ class SearchFragment : MangaListFragment() {
|
||||
|
||||
override fun onRefresh() {
|
||||
super.onRefresh()
|
||||
viewModel.loadList(query.orEmpty(), append = false)
|
||||
viewModel.loadList(append = false)
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() {
|
||||
viewModel.loadList(query.orEmpty(), append = true)
|
||||
viewModel.loadList(append = true)
|
||||
}
|
||||
|
||||
override fun getTitle(): CharSequence? {
|
||||
return query
|
||||
}
|
||||
override fun getTitle() = query
|
||||
|
||||
companion object {
|
||||
|
||||
|
||||
@@ -1,29 +1,64 @@
|
||||
package org.koitharu.kotatsu.search.ui
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
import org.koitharu.kotatsu.list.ui.model.IndeterminateProgress
|
||||
import org.koitharu.kotatsu.list.ui.model.toGridModel
|
||||
import org.koitharu.kotatsu.list.ui.model.toListDetailedModel
|
||||
import org.koitharu.kotatsu.list.ui.model.toListModel
|
||||
|
||||
class SearchViewModel(
|
||||
private val repository: MangaRepository,
|
||||
private val query: String,
|
||||
settings: AppSettings
|
||||
) : MangaListViewModel(settings) {
|
||||
|
||||
override val content = MutableLiveData<List<Any>>()
|
||||
private val mangaList = MutableStateFlow<List<Manga>>(emptyList())
|
||||
private val hasNextPage = MutableStateFlow(false)
|
||||
private var loadingJob: Job? = null
|
||||
|
||||
fun loadList(query: String, append: Boolean) {
|
||||
launchLoadingJob {
|
||||
val list = withContext(Dispatchers.Default) {
|
||||
repository.getList(TODO(), query = query)
|
||||
}
|
||||
override val content = combine(mangaList.drop(1), createListModeFlow()) { list, mode ->
|
||||
when (mode) {
|
||||
ListMode.LIST -> list.map { it.toListModel() }
|
||||
ListMode.DETAILED_LIST -> list.map { it.toListDetailedModel() }
|
||||
ListMode.GRID -> list.map { it.toGridModel() }
|
||||
}
|
||||
}.onEach {
|
||||
isEmptyState.postValue(it.isEmpty())
|
||||
}.combine(hasNextPage) { list, isHasNextPage ->
|
||||
if (isHasNextPage && list.isNotEmpty()) list + IndeterminateProgress else list
|
||||
}.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
init {
|
||||
loadList(append = false)
|
||||
}
|
||||
|
||||
fun loadList(append: Boolean) {
|
||||
if (loadingJob?.isActive == true) {
|
||||
return
|
||||
}
|
||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||
val list = repository.getList(
|
||||
offset = if (append) mangaList.value.size else 0,
|
||||
query = query
|
||||
)
|
||||
if (!append) {
|
||||
content.value = list
|
||||
} else {
|
||||
content.value = content.value.orEmpty() + list
|
||||
mangaList.value = list
|
||||
} else if (list.isNotEmpty()) {
|
||||
mangaList.value += list
|
||||
}
|
||||
hasNextPage.value = list.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.search.ui.global
|
||||
|
||||
import org.koin.android.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||
import org.koitharu.kotatsu.utils.ext.stringArgument
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
@@ -8,13 +9,15 @@ import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
class GlobalSearchFragment : MangaListFragment() {
|
||||
|
||||
override val viewModel by viewModel<GlobalSearchViewModel>()
|
||||
override val viewModel by viewModel<GlobalSearchViewModel> {
|
||||
parametersOf(query)
|
||||
}
|
||||
|
||||
private val query by stringArgument(ARG_QUERY)
|
||||
|
||||
override fun onRefresh() {
|
||||
super.onRefresh()
|
||||
viewModel.startSearch(query.orEmpty())
|
||||
viewModel.onRefresh()
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() = Unit
|
||||
|
||||
@@ -1,43 +1,70 @@
|
||||
package org.koitharu.kotatsu.search.ui.global
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
import org.koitharu.kotatsu.list.ui.model.IndeterminateProgress
|
||||
import org.koitharu.kotatsu.list.ui.model.toGridModel
|
||||
import org.koitharu.kotatsu.list.ui.model.toListDetailedModel
|
||||
import org.koitharu.kotatsu.list.ui.model.toListModel
|
||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||
import org.koitharu.kotatsu.utils.ext.onFirst
|
||||
import java.io.IOException
|
||||
|
||||
class GlobalSearchViewModel(
|
||||
private val query: String,
|
||||
private val repository: MangaSearchRepository,
|
||||
settings: AppSettings
|
||||
) : MangaListViewModel(settings) {
|
||||
|
||||
override val content = MutableLiveData<List<Any>>()
|
||||
private val mangaList = MutableStateFlow<List<Manga>>(emptyList())
|
||||
private val hasNextPage = MutableStateFlow(false)
|
||||
private var searchJob: Job? = null
|
||||
|
||||
fun startSearch(query: String) {
|
||||
isLoading.value = true
|
||||
override val content = combine(mangaList.drop(1), createListModeFlow()) { list, mode ->
|
||||
when (mode) {
|
||||
ListMode.LIST -> list.map { it.toListModel() }
|
||||
ListMode.DETAILED_LIST -> list.map { it.toListDetailedModel() }
|
||||
ListMode.GRID -> list.map { it.toGridModel() }
|
||||
}
|
||||
}.combine(hasNextPage) { list, isHasNextPage ->
|
||||
if (isHasNextPage && list.isNotEmpty()) list + IndeterminateProgress else list
|
||||
}.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
init {
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
fun onRefresh() {
|
||||
searchJob?.cancel()
|
||||
searchJob = repository.globalSearch(query)
|
||||
.flowOn(Dispatchers.Default)
|
||||
.catch { e ->
|
||||
if (e is IOException) {
|
||||
onError.call(e)
|
||||
}
|
||||
onError.postCall(e)
|
||||
isLoading.postValue(false)
|
||||
hasNextPage.value = false
|
||||
}.filterNot { x -> x.isEmpty() }
|
||||
.onEmpty {
|
||||
content.value = emptyList()
|
||||
isLoading.value = false
|
||||
.onStart {
|
||||
isLoading.postValue(true)
|
||||
}.onEmpty {
|
||||
mangaList.value = emptyList()
|
||||
isEmptyState.postValue(true)
|
||||
isLoading.postValue(false)
|
||||
}.onCompletion {
|
||||
// TODO
|
||||
isLoading.postValue(false)
|
||||
hasNextPage.value = false
|
||||
}.onFirst {
|
||||
isEmptyState.postValue(false)
|
||||
hasNextPage.value = true
|
||||
isLoading.value = false
|
||||
}.onEach {
|
||||
content.value = content.value.orEmpty() + it
|
||||
}.launchIn(viewModelScope)
|
||||
mangaList.value += it
|
||||
}.launchIn(viewModelScope + Dispatchers.Default)
|
||||
}
|
||||
}
|
||||
@@ -47,14 +47,18 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
||||
MultiSummaryProvider(R.string.gestures_only)
|
||||
findPreference<MultiSelectListPreference>(AppSettings.KEY_TRACK_SOURCES)?.summaryProvider =
|
||||
MultiSummaryProvider(R.string.dont_check)
|
||||
findPreference<ListPreference>(AppSettings.KEY_ZOOM_MODE)?.run {
|
||||
}
|
||||
|
||||
override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
|
||||
preferenceScreen?.findPreference<ListPreference>(AppSettings.KEY_ZOOM_MODE)?.run {
|
||||
entryValues = ZoomMode.values().names()
|
||||
setDefaultValue(ZoomMode.FIT_CENTER.name)
|
||||
}
|
||||
findPreference<ListPreference>(AppSettings.KEY_LIST_MODE)?.run {
|
||||
preferenceScreen?.findPreference<ListPreference>(AppSettings.KEY_LIST_MODE)?.run {
|
||||
entryValues = ListMode.values().names()
|
||||
setDefaultValue(ListMode.GRID.name)
|
||||
}
|
||||
super.setPreferenceScreen(preferenceScreen)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
||||
@@ -17,7 +17,7 @@ class SourcesAdapter(
|
||||
private val onItemClickListener: OnListItemClickListener<MangaSource>,
|
||||
) : RecyclerView.Adapter<SourceViewHolder>() {
|
||||
|
||||
private val dataSet = MangaProviderFactory.getSources(includeHidden = true).toMutableList()
|
||||
private val dataSet = MangaProviderFactory.getSources(settings, includeHidden = true).toMutableList()
|
||||
private val hiddenItems = settings.hiddenSources.mapNotNull {
|
||||
safe {
|
||||
MangaSource.valueOf(it)
|
||||
|
||||
@@ -184,4 +184,5 @@
|
||||
<string name="yesterday">Вчера</string>
|
||||
<string name="long_ago">Давно</string>
|
||||
<string name="group">Группировать</string>
|
||||
<string name="today">Сегодня</string>
|
||||
</resources>
|
||||
@@ -186,4 +186,5 @@
|
||||
<string name="yesterday">Yesterday</string>
|
||||
<string name="long_ago">Long ago</string>
|
||||
<string name="group">Group</string>
|
||||
<string name="today">Today</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user