Fix search

pull/26/head
Koitharu 5 years ago
parent cef5d91eec
commit a215d9ebfc

@ -1,14 +1,11 @@
package org.koitharu.kotatsu.base.domain 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.model.MangaSource
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
object MangaProviderFactory : KoinComponent { object MangaProviderFactory {
fun getSources(includeHidden: Boolean): List<MangaSource> { fun getSources(settings: AppSettings, includeHidden: Boolean): List<MangaSource> {
val settings = get<AppSettings>()
val list = MangaSource.values().toList() - MangaSource.LOCAL val list = MangaSource.values().toList() - MangaSource.LOCAL
val order = settings.sourcesOrder val order = settings.sourcesOrder
val hidden = settings.hiddenSources val hidden = settings.hiddenSources

@ -1,11 +1,14 @@
package org.koitharu.kotatsu.base.ui package org.koitharu.kotatsu.base.ui
import android.os.Bundle import android.os.Bundle
import android.view.KeyEvent
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
@ -36,4 +39,12 @@ abstract class BaseActivity : AppCompatActivity() {
onBackPressed() onBackPressed()
true true
} else super.onOptionsItemSelected(item) } 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) { fun subscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
prefs.registerOnSharedPreferenceChangeListener(listener) prefs.registerOnSharedPreferenceChangeListener(listener)
} }

@ -2,9 +2,6 @@ package org.koitharu.kotatsu.core.ui
import android.content.res.Resources import android.content.res.Resources
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.ext.daysDiff
import java.util.*
import java.util.concurrent.TimeUnit
sealed class DateTimeAgo { 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() { object Yesterday : DateTimeAgo() {
override fun format(resources: Resources): String { override fun format(resources: Resources): String {
return resources.getString(R.string.yesterday) return resources.getString(R.string.yesterday)
@ -45,22 +48,4 @@ sealed class DateTimeAgo {
return resources.getString(R.string.long_ago) 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.list.ui.model.toListModel
import org.koitharu.kotatsu.utils.MangaShortcut import org.koitharu.kotatsu.utils.MangaShortcut
import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.daysDiff
import org.koitharu.kotatsu.utils.ext.onFirst import org.koitharu.kotatsu.utils.ext.onFirst
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList
class HistoryListViewModel( class HistoryListViewModel(
private val repository: HistoryRepository, private val repository: HistoryRepository,
@ -78,7 +82,7 @@ class HistoryListViewModel(
var prevDate: DateTimeAgo? = null var prevDate: DateTimeAgo? = null
for ((manga, history) in list) { for ((manga, history) in list) {
if (grouped) { if (grouped) {
val date = DateTimeAgo.from(history.updatedAt) val date = timeAgo(history.updatedAt)
if (prevDate != date) { if (prevDate != date) {
result += date result += date
} }
@ -92,4 +96,17 @@ class HistoryListViewModel(
} }
return result 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 val mainModule
get() = module { get() = module {
viewModel { MainViewModel(get()) } viewModel { MainViewModel(get(), get()) }
viewModel { ProtectViewModel(get()) } viewModel { ProtectViewModel(get()) }
} }

@ -1,7 +1,6 @@
package org.koitharu.kotatsu.main.ui package org.koitharu.kotatsu.main.ui
import android.app.ActivityOptions import android.app.ActivityOptions
import android.content.SharedPreferences
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
@ -11,20 +10,17 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.core.view.isVisible import androidx.core.view.*
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.swiperefreshlayout.widget.CircularProgressDrawable import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import org.koin.android.ext.android.inject
import org.koin.android.viewmodel.ext.android.viewModel import org.koin.android.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaProviderFactory
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.prefs.AppSection 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.favourites.ui.FavouritesContainerFragment
import org.koitharu.kotatsu.history.ui.HistoryListFragment import org.koitharu.kotatsu.history.ui.HistoryListFragment
import org.koitharu.kotatsu.local.ui.LocalListFragment import org.koitharu.kotatsu.local.ui.LocalListFragment
@ -42,11 +38,10 @@ import org.koitharu.kotatsu.utils.ext.resolveDp
import java.io.Closeable import java.io.Closeable
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener, class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener,
SharedPreferences.OnSharedPreferenceChangeListener, View.OnClickListener { View.OnClickListener {
private val viewModel by viewModel<MainViewModel>() private val viewModel by viewModel<MainViewModel>()
private val settings by inject<AppSettings>()
private lateinit var drawerToggle: ActionBarDrawerToggle private lateinit var drawerToggle: ActionBarDrawerToggle
private var closeable: Closeable? = null private var closeable: Closeable? = null
@ -59,11 +54,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
navigationView.setNavigationItemSelectedListener(this) navigationView.setNavigationItemSelectedListener(this)
settings.subscribe(this)
with(fab) { with(fab) {
imageTintList = ColorStateList.valueOf(Color.WHITE) imageTintList = ColorStateList.valueOf(Color.WHITE)
isVisible = true
setOnClickListener(this@MainActivity) setOnClickListener(this@MainActivity)
} }
@ -78,14 +71,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
TrackWorker.setup(applicationContext) TrackWorker.setup(applicationContext)
AppUpdateChecker(this).launchIfNeeded() AppUpdateChecker(this).launchIfNeeded()
viewModel.onOpenReader.observe(this, ::onOpenReader) viewModel.onOpenReader.observe(this, this::onOpenReader)
viewModel.onError.observe(this, ::onError) viewModel.onError.observe(this, this::onError)
viewModel.isLoading.observe(this, ::onLoadingStateChanged) viewModel.isLoading.observe(this, this::onLoadingStateChanged)
viewModel.remoteSources.observe(this, this::updateSideMenu)
} }
override fun onDestroy() { override fun onDestroy() {
closeable?.close() closeable?.close()
settings.unsubscribe(this)
AppProtectHelper.lock() AppProtectHelper.lock()
super.onDestroy() super.onDestroy()
} }
@ -93,7 +86,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
override fun onPostCreate(savedInstanceState: Bundle?) { override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState) super.onPostCreate(savedInstanceState)
drawerToggle.syncState() drawerToggle.syncState()
initSideMenu(MangaProviderFactory.getSources(includeHidden = false))
} }
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
@ -135,19 +127,19 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
setPrimaryFragment(RemoteListFragment.newInstance(source)) setPrimaryFragment(RemoteListFragment.newInstance(source))
} else when (item.itemId) { } else when (item.itemId) {
R.id.nav_history -> { R.id.nav_history -> {
settings.defaultSection = AppSection.HISTORY viewModel.defaultSection = AppSection.HISTORY
setPrimaryFragment(HistoryListFragment.newInstance()) setPrimaryFragment(HistoryListFragment.newInstance())
} }
R.id.nav_favourites -> { R.id.nav_favourites -> {
settings.defaultSection = AppSection.FAVOURITES viewModel.defaultSection = AppSection.FAVOURITES
setPrimaryFragment(FavouritesContainerFragment.newInstance()) setPrimaryFragment(FavouritesContainerFragment.newInstance())
} }
R.id.nav_local_storage -> { R.id.nav_local_storage -> {
settings.defaultSection = AppSection.LOCAL viewModel.defaultSection = AppSection.LOCAL
setPrimaryFragment(LocalListFragment.newInstance()) setPrimaryFragment(LocalListFragment.newInstance())
} }
R.id.nav_feed -> { R.id.nav_feed -> {
settings.defaultSection = AppSection.FEED viewModel.defaultSection = AppSection.FEED
setPrimaryFragment(FeedFragment.newInstance()) setPrimaryFragment(FeedFragment.newInstance())
} }
R.id.nav_action_settings -> { 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 val submenu = navigationView.menu.findItem(R.id.nav_remote_sources).subMenu
submenu.removeGroup(R.id.group_remote_sources) submenu.removeGroup(R.id.group_remote_sources)
remoteSources.forEachIndexed { index, source -> remoteSources.forEachIndexed { index, source ->
@ -199,17 +191,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
submenu.setGroupCheckable(R.id.group_remote_sources, true, true) 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() { private fun openDefaultSection() {
when (settings.defaultSection) { when (viewModel.defaultSection) {
AppSection.LOCAL -> { AppSection.LOCAL -> {
navigationView.setCheckedItem(R.id.nav_local_storage) navigationView.setCheckedItem(R.id.nav_local_storage)
setPrimaryFragment(LocalListFragment.newInstance()) setPrimaryFragment(LocalListFragment.newInstance())

@ -1,16 +1,34 @@
package org.koitharu.kotatsu.main.ui 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.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException 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.history.domain.HistoryRepository
import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.SingleLiveEvent
class MainViewModel( class MainViewModel(
private val historyRepository: HistoryRepository private val historyRepository: HistoryRepository,
settings: AppSettings
) : BaseViewModel() { ) : BaseViewModel() {
val onOpenReader = SingleLiveEvent<ReaderState>() 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() { fun openLastReader() {
launchLoadingJob { launchLoadingJob {

@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaFilter import org.koitharu.kotatsu.core.model.MangaFilter
@ -53,20 +52,18 @@ class RemoteListViewModel(
if (loadingJob?.isActive == true) { if (loadingJob?.isActive == true) {
return return
} }
loadingJob = launchLoadingJob { loadingJob = launchLoadingJob(Dispatchers.Default) {
withContext(Dispatchers.Default) { val list = repository.getList(
val list = repository.getList( offset = if (append) mangaList.value.size else 0,
offset = if (append) mangaList.value.size else 0, sortOrder = appliedFilter?.sortOrder,
sortOrder = appliedFilter?.sortOrder, tag = appliedFilter?.tag
tag = appliedFilter?.tag )
) if (!append) {
if (!append) { mangaList.value = list
mangaList.value = list } else if (list.isNotEmpty()) {
} else if (list.isNotEmpty()) { mangaList.value += list
mangaList.value += list
}
hasNextPage.value = list.isNotEmpty()
} }
hasNextPage.value = list.isNotEmpty()
} }
} }
@ -78,13 +75,11 @@ class RemoteListViewModel(
} }
private fun loadFilter() { private fun loadFilter() {
launchJob { launchJob(Dispatchers.Default) {
try { try {
val (sorts, tags) = withContext(Dispatchers.Default) { val sorts = repository.sortOrders.sortedBy { it.ordinal }
repository.sortOrders.sortedBy { it.ordinal } to repository.getTags() val tags = repository.getTags().sortedBy { it.title }
.sortedBy { it.title } filter.postValue(MangaFilterConfig(sorts, tags, appliedFilter))
}
filter.value = MangaFilterConfig(sorts, tags, appliedFilter)
} catch (e: Exception) { } catch (e: Exception) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
e.printStackTrace() e.printStackTrace()

@ -11,8 +11,12 @@ import org.koitharu.kotatsu.search.ui.global.GlobalSearchViewModel
val searchModule val searchModule
get() = module { get() = module {
single { MangaSearchRepository() } single { MangaSearchRepository(get()) }
viewModel { (source: MangaSource) -> SearchViewModel(get(named(source)), get()) } viewModel { (source: MangaSource, query: String) ->
viewModel { GlobalSearchViewModel(get(), get()) } 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.Manga
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.core.prefs.AppSettings
import java.util.* import java.util.*
class MangaSearchRepository { class MangaSearchRepository(private val settings: AppSettings) {
fun globalSearch(query: String, batchSize: Int = 4): Flow<List<Manga>> = flow { 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) val lists = EnumMap<MangaSource, List<Manga>>(MangaSource::class.java)
var i = 0 var i = 0
while (true) { while (true) {

@ -15,7 +15,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs
class MangaSearchSheet : MangaListSheet() { class MangaSearchSheet : MangaListSheet() {
override val viewModel by viewModel<SearchViewModel> { override val viewModel by viewModel<SearchViewModel> {
parametersOf(source) parametersOf(source, query)
} }
private val query by stringArgument(ARG_QUERY) private val query by stringArgument(ARG_QUERY)
@ -29,7 +29,7 @@ class MangaSearchSheet : MangaListSheet() {
} }
override fun onScrolledToEnd() { override fun onScrolledToEnd() {
viewModel.loadList(query.orEmpty(), append = true) viewModel.loadList(append = true)
} }
companion object { companion object {

@ -11,7 +11,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs
class SearchFragment : MangaListFragment() { class SearchFragment : MangaListFragment() {
override val viewModel by viewModel<SearchViewModel> { override val viewModel by viewModel<SearchViewModel> {
parametersOf(source) parametersOf(source, query)
} }
private val query by stringArgument(ARG_QUERY) private val query by stringArgument(ARG_QUERY)
@ -19,16 +19,14 @@ class SearchFragment : MangaListFragment() {
override fun onRefresh() { override fun onRefresh() {
super.onRefresh() super.onRefresh()
viewModel.loadList(query.orEmpty(), append = false) viewModel.loadList(append = false)
} }
override fun onScrolledToEnd() { override fun onScrolledToEnd() {
viewModel.loadList(query.orEmpty(), append = true) viewModel.loadList(append = true)
} }
override fun getTitle(): CharSequence? { override fun getTitle() = query
return query
}
companion object { companion object {

@ -1,29 +1,64 @@
package org.koitharu.kotatsu.search.ui 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.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.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings 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.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( class SearchViewModel(
private val repository: MangaRepository, private val repository: MangaRepository,
private val query: String,
settings: AppSettings settings: AppSettings
) : MangaListViewModel(settings) { ) : 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) { override val content = combine(mangaList.drop(1), createListModeFlow()) { list, mode ->
launchLoadingJob { when (mode) {
val list = withContext(Dispatchers.Default) { ListMode.LIST -> list.map { it.toListModel() }
repository.getList(TODO(), query = query) 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) { if (!append) {
content.value = list mangaList.value = list
} else { } else if (list.isNotEmpty()) {
content.value = content.value.orEmpty() + list mangaList.value += list
} }
hasNextPage.value = list.isNotEmpty()
} }
} }
} }

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.search.ui.global package org.koitharu.kotatsu.search.ui.global
import org.koin.android.viewmodel.ext.android.viewModel 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.list.ui.MangaListFragment
import org.koitharu.kotatsu.utils.ext.stringArgument import org.koitharu.kotatsu.utils.ext.stringArgument
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
@ -8,13 +9,15 @@ import org.koitharu.kotatsu.utils.ext.withArgs
class GlobalSearchFragment : MangaListFragment() { class GlobalSearchFragment : MangaListFragment() {
override val viewModel by viewModel<GlobalSearchViewModel>() override val viewModel by viewModel<GlobalSearchViewModel> {
parametersOf(query)
}
private val query by stringArgument(ARG_QUERY) private val query by stringArgument(ARG_QUERY)
override fun onRefresh() { override fun onRefresh() {
super.onRefresh() super.onRefresh()
viewModel.startSearch(query.orEmpty()) viewModel.onRefresh()
} }
override fun onScrolledToEnd() = Unit override fun onScrolledToEnd() = Unit

@ -1,43 +1,70 @@
package org.koitharu.kotatsu.search.ui.global package org.koitharu.kotatsu.search.ui.global
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.* 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.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.list.ui.MangaListViewModel 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.search.domain.MangaSearchRepository
import org.koitharu.kotatsu.utils.ext.onFirst import org.koitharu.kotatsu.utils.ext.onFirst
import java.io.IOException
class GlobalSearchViewModel( class GlobalSearchViewModel(
private val query: String,
private val repository: MangaSearchRepository, private val repository: MangaSearchRepository,
settings: AppSettings settings: AppSettings
) : MangaListViewModel(settings) { ) : 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 private var searchJob: Job? = null
fun startSearch(query: String) { override val content = combine(mangaList.drop(1), createListModeFlow()) { list, mode ->
isLoading.value = true 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?.cancel()
searchJob = repository.globalSearch(query) searchJob = repository.globalSearch(query)
.flowOn(Dispatchers.Default) .flowOn(Dispatchers.Default)
.catch { e -> .catch { e ->
if (e is IOException) { onError.postCall(e)
onError.call(e) isLoading.postValue(false)
} hasNextPage.value = false
}.filterNot { x -> x.isEmpty() } }.filterNot { x -> x.isEmpty() }
.onEmpty { .onStart {
content.value = emptyList() isLoading.postValue(true)
isLoading.value = false }.onEmpty {
mangaList.value = emptyList()
isEmptyState.postValue(true)
isLoading.postValue(false)
}.onCompletion { }.onCompletion {
// TODO isLoading.postValue(false)
hasNextPage.value = false
}.onFirst { }.onFirst {
isEmptyState.postValue(false)
hasNextPage.value = true
isLoading.value = false isLoading.value = false
}.onEach { }.onEach {
content.value = content.value.orEmpty() + it mangaList.value += it
}.launchIn(viewModelScope) }.launchIn(viewModelScope + Dispatchers.Default)
} }
} }

@ -47,14 +47,18 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
MultiSummaryProvider(R.string.gestures_only) MultiSummaryProvider(R.string.gestures_only)
findPreference<MultiSelectListPreference>(AppSettings.KEY_TRACK_SOURCES)?.summaryProvider = findPreference<MultiSelectListPreference>(AppSettings.KEY_TRACK_SOURCES)?.summaryProvider =
MultiSummaryProvider(R.string.dont_check) 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() entryValues = ZoomMode.values().names()
setDefaultValue(ZoomMode.FIT_CENTER.name) setDefaultValue(ZoomMode.FIT_CENTER.name)
} }
findPreference<ListPreference>(AppSettings.KEY_LIST_MODE)?.run { preferenceScreen?.findPreference<ListPreference>(AppSettings.KEY_LIST_MODE)?.run {
entryValues = ListMode.values().names() entryValues = ListMode.values().names()
setDefaultValue(ListMode.GRID.name) setDefaultValue(ListMode.GRID.name)
} }
super.setPreferenceScreen(preferenceScreen)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

@ -17,7 +17,7 @@ class SourcesAdapter(
private val onItemClickListener: OnListItemClickListener<MangaSource>, private val onItemClickListener: OnListItemClickListener<MangaSource>,
) : RecyclerView.Adapter<SourceViewHolder>() { ) : 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 { private val hiddenItems = settings.hiddenSources.mapNotNull {
safe { safe {
MangaSource.valueOf(it) MangaSource.valueOf(it)

@ -184,4 +184,5 @@
<string name="yesterday">Вчера</string> <string name="yesterday">Вчера</string>
<string name="long_ago">Давно</string> <string name="long_ago">Давно</string>
<string name="group">Группировать</string> <string name="group">Группировать</string>
<string name="today">Сегодня</string>
</resources> </resources>

@ -186,4 +186,5 @@
<string name="yesterday">Yesterday</string> <string name="yesterday">Yesterday</string>
<string name="long_ago">Long ago</string> <string name="long_ago">Long ago</string>
<string name="group">Group</string> <string name="group">Group</string>
<string name="today">Today</string>
</resources> </resources>
Loading…
Cancel
Save