From a215d9ebfc73d29d8e89985bf6b9f598080eb85b Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 28 Nov 2020 17:57:12 +0200 Subject: [PATCH] Fix search --- .../base/domain/MangaProviderFactory.kt | 7 +-- .../koitharu/kotatsu/base/ui/BaseActivity.kt | 11 ++++ .../kotatsu/core/prefs/AppSettings.kt | 1 + .../koitharu/kotatsu/core/ui/DateTimeAgo.kt | 27 ++------- .../history/ui/HistoryListViewModel.kt | 19 ++++++- .../org/koitharu/kotatsu/main/MainModule.kt | 2 +- .../koitharu/kotatsu/main/ui/MainActivity.kt | 41 ++++--------- .../koitharu/kotatsu/main/ui/MainViewModel.kt | 20 ++++++- .../remotelist/ui/RemoteListViewModel.kt | 35 +++++------- .../koitharu/kotatsu/search/SearchModule.kt | 10 +++- .../search/domain/MangaSearchRepository.kt | 5 +- .../kotatsu/search/ui/MangaSearchSheet.kt | 4 +- .../kotatsu/search/ui/SearchFragment.kt | 10 ++-- .../kotatsu/search/ui/SearchViewModel.kt | 57 +++++++++++++++---- .../search/ui/global/GlobalSearchFragment.kt | 7 ++- .../search/ui/global/GlobalSearchViewModel.kt | 55 +++++++++++++----- .../kotatsu/settings/MainSettingsFragment.kt | 8 ++- .../settings/sources/SourcesAdapter.kt | 2 +- app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 20 files changed, 202 insertions(+), 121 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaProviderFactory.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaProviderFactory.kt index 8498cc190..12771e5ea 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaProviderFactory.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaProviderFactory.kt @@ -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 { - val settings = get() + fun getSources(settings: AppSettings, includeHidden: Boolean): List { val list = MangaSource.values().toList() - MangaSource.LOCAL val order = settings.sourcesOrder val hidden = settings.hiddenSources diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt index 16710be54..535cfaf86 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 6a2967f3e..80d4b9b28 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -114,6 +114,7 @@ class AppSettings private constructor(private val prefs: SharedPreferences) : } } + @Deprecated("Use observe()") fun subscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) { prefs.registerOnSharedPreferenceChangeListener(listener) } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/DateTimeAgo.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/DateTimeAgo.kt index 1ed9772b0..d398cd1ca 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/ui/DateTimeAgo.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/DateTimeAgo.kt @@ -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 - } - } - } } diff --git a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt index 8ab56809c..13a2ad852 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt @@ -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 + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/main/MainModule.kt b/app/src/main/java/org/koitharu/kotatsu/main/MainModule.kt index 53f0204ad..c7b1b2785 100644 --- a/app/src/main/java/org/koitharu/kotatsu/main/MainModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/MainModule.kt @@ -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()) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt index 1b0e62717..322fa8fba 100644 --- a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt @@ -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() - private val settings by inject() 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) { + private fun updateSideMenu(remoteSources: List) { 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()) diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainViewModel.kt index 6b4e8d799..bcd53035e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainViewModel.kt @@ -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() + 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 { diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt index 07469a896..10bfb9347 100644 --- a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt @@ -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() diff --git a/app/src/main/java/org/koitharu/kotatsu/search/SearchModule.kt b/app/src/main/java/org/koitharu/kotatsu/search/SearchModule.kt index b1fb6f120..c15e72132 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/SearchModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/SearchModule.kt @@ -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()) + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt index ad41053bb..7e46ae4d1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt @@ -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> = flow { - val sources = MangaProviderFactory.getSources(false) + val sources = MangaProviderFactory.getSources(settings, includeHidden = false) val lists = EnumMap>(MangaSource::class.java) var i = 0 while (true) { diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSearchSheet.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSearchSheet.kt index bb3941707..c6c95b9ea 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSearchSheet.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaSearchSheet.kt @@ -15,7 +15,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs class MangaSearchSheet : MangaListSheet() { override val viewModel by viewModel { - 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 { diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt index ac6e22c25..bf7e5307a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt @@ -11,7 +11,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs class SearchFragment : MangaListFragment() { override val viewModel by viewModel { - 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 { diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt index 6d6b8b783..a52dd9f62 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchViewModel.kt @@ -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>() + private val mangaList = MutableStateFlow>(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() } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt index 63a116b4e..515748775 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt @@ -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() + override val viewModel by viewModel { + parametersOf(query) + } private val query by stringArgument(ARG_QUERY) override fun onRefresh() { super.onRefresh() - viewModel.startSearch(query.orEmpty()) + viewModel.onRefresh() } override fun onScrolledToEnd() = Unit diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchViewModel.kt index d4b965628..679c0bbce 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchViewModel.kt @@ -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>() + private val mangaList = MutableStateFlow>(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) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt index 4306615e7..9d42faa80 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt @@ -47,14 +47,18 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), MultiSummaryProvider(R.string.gestures_only) findPreference(AppSettings.KEY_TRACK_SOURCES)?.summaryProvider = MultiSummaryProvider(R.string.dont_check) - findPreference(AppSettings.KEY_ZOOM_MODE)?.run { + } + + override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) { + preferenceScreen?.findPreference(AppSettings.KEY_ZOOM_MODE)?.run { entryValues = ZoomMode.values().names() setDefaultValue(ZoomMode.FIT_CENTER.name) } - findPreference(AppSettings.KEY_LIST_MODE)?.run { + preferenceScreen?.findPreference(AppSettings.KEY_LIST_MODE)?.run { entryValues = ListMode.values().names() setDefaultValue(ListMode.GRID.name) } + super.setPreferenceScreen(preferenceScreen) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesAdapter.kt index 3b12c1798..a4e4ddd60 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesAdapter.kt @@ -17,7 +17,7 @@ class SourcesAdapter( private val onItemClickListener: OnListItemClickListener, ) : RecyclerView.Adapter() { - 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) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 65f946c7c..daee82b79 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -184,4 +184,5 @@ Вчера Давно Группировать + Сегодня \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3e9dd2e88..d540e0088 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -186,4 +186,5 @@ Yesterday Long ago Group + Today \ No newline at end of file