diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 43bf12789..0ca469716 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -32,6 +32,7 @@ import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.reader.domain.ReaderColorFilter import java.io.File import java.net.Proxy +import java.util.EnumSet import java.util.Locale import javax.inject.Inject import javax.inject.Singleton @@ -220,6 +221,13 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { get() = prefs.getBoolean(KEY_APP_PASSWORD_NUMERIC, false) set(value) = prefs.edit { putBoolean(KEY_APP_PASSWORD_NUMERIC, value) } + val searchSuggestionTypes: Set + get() = prefs.getStringSet(KEY_SEARCH_SUGGESTION_TYPES, null)?.let { stringSet -> + stringSet.mapNotNullTo(EnumSet.noneOf(SearchSuggestionType::class.java)) { x -> + enumValueOf(x) + } + } ?: EnumSet.allOf(SearchSuggestionType::class.java) + val isLoggingEnabled: Boolean get() = prefs.getBoolean(KEY_LOGGING_ENABLED, false) @@ -675,5 +683,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { const val KEY_APP_UPDATE = "app_update" const val KEY_APP_TRANSLATION = "about_app_translation" const val KEY_FEED_HEADER = "feed_header" + const val KEY_SEARCH_SUGGESTION_TYPES = "search_suggest_types" } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SearchSuggestionType.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SearchSuggestionType.kt new file mode 100644 index 000000000..b0229ebbd --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SearchSuggestionType.kt @@ -0,0 +1,15 @@ +package org.koitharu.kotatsu.core.prefs + +import androidx.annotation.StringRes +import org.koitharu.kotatsu.R + +enum class SearchSuggestionType( + @StringRes val titleResId: Int, +) { + + GENRES(R.string.genres), + QUERIES_RECENT(R.string.recent_queries), + QUERIES_SUGGEST(R.string.suggested_queries), + MANGA(R.string.content_type_manga), + SOURCES(R.string.remote_sources), +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeMutex.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeMutex.kt deleted file mode 100644 index 2c8920f3a..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeMutex.kt +++ /dev/null @@ -1,58 +0,0 @@ -package org.koitharu.kotatsu.core.util - -import androidx.collection.ArrayMap -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.isActive -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlin.coroutines.coroutineContext - -@Deprecated("", replaceWith = ReplaceWith("CompositeMutex2")) -class CompositeMutex : Set { - - private val state = ArrayMap>() - private val mutex = Mutex() - - override val size: Int - get() = state.size - - override fun contains(element: T): Boolean { - return state.containsKey(element) - } - - override fun containsAll(elements: Collection): Boolean { - return elements.all { x -> state.containsKey(x) } - } - - override fun isEmpty(): Boolean { - return state.isEmpty() - } - - override fun iterator(): Iterator { - return state.keys.iterator() - } - - suspend fun lock(element: T) { - while (coroutineContext.isActive) { - waitForRemoval(element) - mutex.withLock { - if (state[element] == null) { - state[element] = MutableStateFlow(false) - return - } - } - } - } - - fun unlock(element: T) { - checkNotNull(state.remove(element)) { - "CompositeMutex is not locked for $element" - }.value = true - } - - private suspend fun waitForRemoval(element: T) { - val flow = state[element] ?: return - flow.first { it } - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeRunnable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeRunnable.kt deleted file mode 100644 index fe56ffc3c..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeRunnable.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.koitharu.kotatsu.core.util - -class CompositeRunnable( - private val children: List, -) : Runnable, Collection by children { - - override fun run() { - for (child in children) { - child.run() - } - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeMutex2.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/MultiMutex.kt similarity index 95% rename from app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeMutex2.kt rename to app/src/main/kotlin/org/koitharu/kotatsu/core/util/MultiMutex.kt index 643fe2996..98d228dcc 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/CompositeMutex2.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/MultiMutex.kt @@ -3,7 +3,7 @@ package org.koitharu.kotatsu.core.util import androidx.collection.ArrayMap import kotlinx.coroutines.sync.Mutex -class CompositeMutex2 : Set { +class MultiMutex : Set { private val delegates = ArrayMap() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt index dcbecf41e..fb41830ae 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Collections.kt @@ -68,3 +68,5 @@ fun Iterable.sortedWithSafe(comparator: Comparator): List = try toList() } } + +fun Collection<*>?.sizeOrZero() = if (this == null) 0 else size diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Fragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Fragment.kt index 619211454..6b7bde9a5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Fragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Fragment.kt @@ -1,17 +1,12 @@ package org.koitharu.kotatsu.core.util.ext import android.os.Bundle -import androidx.annotation.MainThread import androidx.core.view.MenuProvider import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.Observer import androidx.lifecycle.coroutineScope -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.resume inline fun T.withArgs(size: Int, block: Bundle.() -> Unit): T { val b = Bundle(size) @@ -33,26 +28,6 @@ fun Fragment.addMenuProvider(provider: MenuProvider) { requireActivity().addMenuProvider(provider, viewLifecycleOwner, Lifecycle.State.RESUMED) } -@MainThread -suspend fun Fragment.awaitViewLifecycle(): LifecycleOwner { - val liveData = viewLifecycleOwnerLiveData - liveData.value?.let { return it } - return suspendCancellableCoroutine { cont -> - val observer = object : Observer { - override fun onChanged(value: LifecycleOwner?) { - if (value != null) { - liveData.removeObserver(this) - cont.resume(value) - } - } - } - liveData.observeForever(observer) - cont.invokeOnCancellation { - liveData.removeObserver(observer) - } - } -} - fun DialogFragment.showDistinct(fm: FragmentManager, tag: String) { val existing = fm.findFragmentByTag(tag) as? DialogFragment? if (existing != null && existing.isVisible && existing.arguments == this.arguments) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Other.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Other.kt index 046bd94ca..baf078b7f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Other.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Other.kt @@ -1,7 +1,5 @@ package org.koitharu.kotatsu.core.util.ext -import org.koitharu.kotatsu.core.util.CompositeRunnable - @Suppress("UNCHECKED_CAST") fun Class.castOrNull(obj: Any?): T? { if (obj == null || !isInstance(obj)) { @@ -9,15 +7,3 @@ fun Class.castOrNull(obj: Any?): T? { } return obj as T } - -/* CompositeRunnable */ - -operator fun Runnable.plus(other: Runnable): Runnable { - val list = ArrayList(this.size + other.size) - if (this is CompositeRunnable) list.addAll(this) else list.add(this) - if (other is CompositeRunnable) list.addAll(other) else list.add(other) - return CompositeRunnable(list) -} - -private val Runnable.size: Int - get() = if (this is CompositeRunnable) size else 1 diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/StaticLayout.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/StaticLayout.kt deleted file mode 100644 index 43e5a3d1e..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/StaticLayout.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.koitharu.kotatsu.core.util.ext - -import android.graphics.Canvas -import android.text.StaticLayout -import androidx.core.graphics.withTranslation - -fun StaticLayout.draw(canvas: Canvas, x: Float, y: Float) { - canvas.withTranslation(x, y) { - draw(this) - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/ViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/ViewModel.kt index 8fe5f8f47..27108dc26 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/ViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/ViewModel.kt @@ -1,12 +1,10 @@ package org.koitharu.kotatsu.core.util.ext -import android.annotation.SuppressLint import androidx.annotation.MainThread import androidx.fragment.app.Fragment import androidx.fragment.app.createViewModelLazy import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelStore import androidx.lifecycle.viewmodel.CreationExtras @MainThread @@ -19,7 +17,3 @@ inline fun Fragment.parentFragmentViewModels( extrasProducer = { extrasProducer?.invoke() ?: requireParentFragment().defaultViewModelCreationExtras }, factoryProducer = factoryProducer ?: { requireParentFragment().defaultViewModelProviderFactory }, ) - -val ViewModelStore.values: Collection - @SuppressLint("RestrictedApi") - get() = this.keys().mapNotNull { get(it) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/WorkManager.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/WorkManager.kt index a879f2f39..d83a4f560 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/WorkManager.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/WorkManager.kt @@ -83,7 +83,6 @@ suspend fun WorkManager.getWorkSpec(id: UUID): WorkSpec? = suspendCoroutine { co } } - @SuppressLint("RestrictedApi") suspend fun WorkManager.getWorkInputData(id: UUID): Data? = getWorkSpec(id)?.input diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/LocalMangaRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/LocalMangaRepository.kt index 9bf2ff6c5..c23006663 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/LocalMangaRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/LocalMangaRepository.kt @@ -15,7 +15,7 @@ import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.util.AlphanumComparator -import org.koitharu.kotatsu.core.util.CompositeMutex2 +import org.koitharu.kotatsu.core.util.MultiMutex import org.koitharu.kotatsu.core.util.ext.children import org.koitharu.kotatsu.core.util.ext.deleteAwait import org.koitharu.kotatsu.core.util.ext.filterWith @@ -50,7 +50,7 @@ class LocalMangaRepository @Inject constructor( ) : MangaRepository { override val source = MangaSource.LOCAL - private val locks = CompositeMutex2() + private val locks = MultiMutex() override val isMultipleTagsSupported: Boolean = true override val isTagsExclusionSupported: Boolean = true diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index b4213b2ee..5185b9bcb 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -45,6 +45,7 @@ import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.requireValue +import org.koitharu.kotatsu.core.util.ext.sizeOrZero import org.koitharu.kotatsu.details.data.MangaDetails import org.koitharu.kotatsu.details.domain.DetailsLoadUseCase import org.koitharu.kotatsu.history.data.HistoryRepository @@ -432,7 +433,7 @@ class ReaderViewModel @Inject constructor( branch = chapter.branch, chapterName = chapter.name, chapterNumber = chapterIndex + 1, - chaptersTotal = m.chapters[chapter.branch]?.size ?: 0, + chaptersTotal = m.chapters[chapter.branch].sizeOrZero(), totalPages = chaptersLoader.getPagesCount(chapter.id), currentPage = state.page, isSliderEnabled = settings.isReaderSliderEnabled, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigSheet.kt index be0c929c4..93c03a049 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderConfigSheet.kt @@ -1,12 +1,10 @@ package org.koitharu.kotatsu.reader.ui.config -import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.CompoundButton -import androidx.activity.result.ActivityResultCallback import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.FragmentManager @@ -25,12 +23,12 @@ import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.observeAsStateFlow import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.util.ScreenOrientationHelper +import org.koitharu.kotatsu.core.util.ext.findParentCallback import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.showDistinct import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope import org.koitharu.kotatsu.core.util.ext.withArgs import org.koitharu.kotatsu.databinding.SheetReaderConfigBinding -import org.koitharu.kotatsu.reader.ui.PageSaveContract import org.koitharu.kotatsu.reader.ui.ReaderViewModel import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity import org.koitharu.kotatsu.settings.SettingsActivity @@ -100,7 +98,7 @@ class ReaderConfigSheet : binding.sliderTimer.valueTo, ) } - findCallback()?.run { + findParentCallback(Callback::class.java)?.run { binding.switchScrollTimer.isChecked = isAutoScrollEnabled } } @@ -113,7 +111,7 @@ class ReaderConfigSheet : } R.id.button_save_page -> { - findCallback()?.onSavePageClick() ?: return + findParentCallback(Callback::class.java)?.onSavePageClick() ?: return dismissAllowingStateLoss() } @@ -132,7 +130,7 @@ class ReaderConfigSheet : override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { when (buttonView.id) { R.id.switch_scroll_timer -> { - findCallback()?.isAutoScrollEnabled = isChecked + findParentCallback(Callback::class.java)?.isAutoScrollEnabled = isChecked requireViewBinding().layoutTimer.isVisible = isChecked requireViewBinding().sliderTimer.isVisible = isChecked } @@ -143,7 +141,7 @@ class ReaderConfigSheet : R.id.switch_double_reader -> { settings.isReaderDoubleOnLandscape = isChecked - findCallback()?.onDoubleModeChanged(isChecked) + findParentCallback(Callback::class.java)?.onDoubleModeChanged(isChecked) } } } @@ -167,7 +165,7 @@ class ReaderConfigSheet : if (newMode == mode) { return } - findCallback()?.onReaderModeChanged(newMode) ?: return + findParentCallback(Callback::class.java)?.onReaderModeChanged(newMode) ?: return mode = newMode } @@ -196,10 +194,6 @@ class ReaderConfigSheet : switch.setOnCheckedChangeListener(this) } - private fun findCallback(): Callback? { - return (parentFragment as? Callback) ?: (activity as? Callback) - } - interface Callback { var isAutoScrollEnabled: Boolean diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt index fa80a5f1b..b316146cc 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt @@ -25,6 +25,7 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.require +import org.koitharu.kotatsu.core.util.ext.sizeOrZero import org.koitharu.kotatsu.download.ui.worker.DownloadWorker import org.koitharu.kotatsu.explore.domain.ExploreRepository import org.koitharu.kotatsu.filter.ui.FilterCoordinator @@ -134,7 +135,7 @@ open class RemoteListViewModel @Inject constructor( try { listError.value = null val list = repository.getList( - offset = if (append) mangaList.value?.size ?: 0 else 0, + offset = if (append) mangaList.value.sizeOrZero() else 0, filter = filterState, ) val prevList = mangaList.value.orEmpty() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/SearchViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/SearchViewModel.kt index a1e5688e3..57eb09333 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/SearchViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/SearchViewModel.kt @@ -17,6 +17,7 @@ import org.koitharu.kotatsu.core.model.distinctById import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.util.ext.require +import org.koitharu.kotatsu.core.util.ext.sizeOrZero import org.koitharu.kotatsu.download.ui.worker.DownloadWorker import org.koitharu.kotatsu.list.domain.ListExtraProvider import org.koitharu.kotatsu.list.ui.MangaListViewModel @@ -103,7 +104,7 @@ class SearchViewModel @Inject constructor( try { listError.value = null val list = repository.getList( - offset = if (append) mangaList.value?.size ?: 0 else 0, + offset = if (append) mangaList.value.sizeOrZero() else 0, filter = MangaListFilter.Search(query), ) val prevList = mangaList.value.orEmpty() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt index e4912614b..61a982d22 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt @@ -16,9 +16,12 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.plus import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.SearchSuggestionType +import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.prefs.observeAsStateFlow import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.widgets.ChipsView +import org.koitharu.kotatsu.core.util.ext.sizeOrZero import org.koitharu.kotatsu.core.util.ext.toEnumSet import org.koitharu.kotatsu.explore.data.MangaSourcesRepository import org.koitharu.kotatsu.parsers.model.MangaSource @@ -100,9 +103,10 @@ class SearchSuggestionViewModel @Inject constructor( suggestionJob = combine( query.debounce(DEBOUNCE_TIMEOUT), sourcesRepository.observeEnabledSources().map { it.toEnumSet() }, - ::Pair, - ).mapLatest { (searchQuery, enabledSources) -> - buildSearchSuggestion(searchQuery, enabledSources) + settings.observeAsFlow(AppSettings.KEY_SEARCH_SUGGESTION_TYPES) { searchSuggestionTypes }, + ::Triple, + ).mapLatest { (searchQuery, enabledSources, types) -> + buildSearchSuggestion(searchQuery, enabledSources, types) }.distinctUntilChanged() .onEach { suggestion.value = it @@ -112,36 +116,49 @@ class SearchSuggestionViewModel @Inject constructor( private suspend fun buildSearchSuggestion( searchQuery: String, enabledSources: Set, + types: Set, ): List = coroutineScope { - val queriesDeferred = async { - repository.getQuerySuggestion(searchQuery, MAX_QUERY_ITEMS) + val queriesDeferred = if (SearchSuggestionType.QUERIES_RECENT in types) { + async { repository.getQuerySuggestion(searchQuery, MAX_QUERY_ITEMS) } + } else { + null } - val hintsDeferred = async { - repository.getQueryHintSuggestion(searchQuery, MAX_HINTS_ITEMS) + val hintsDeferred = if (SearchSuggestionType.QUERIES_SUGGEST in types) { + async { repository.getQueryHintSuggestion(searchQuery, MAX_HINTS_ITEMS) } + } else { + null } - val tagsDeferred = async { - repository.getTagsSuggestion(searchQuery, MAX_TAGS_ITEMS, null) + val tagsDeferred = if (SearchSuggestionType.GENRES in types) { + async { repository.getTagsSuggestion(searchQuery, MAX_TAGS_ITEMS, null) } + } else { + null } - val mangaDeferred = async { - repository.getMangaSuggestion(searchQuery, MAX_MANGA_ITEMS, null) + val mangaDeferred = if (SearchSuggestionType.MANGA in types) { + async { repository.getMangaSuggestion(searchQuery, MAX_MANGA_ITEMS, null) } + } else { + null + } + val sources = if (SearchSuggestionType.SOURCES in types) { + repository.getSourcesSuggestion(searchQuery, MAX_SOURCES_ITEMS) + } else { + null } - val sources = repository.getSourcesSuggestion(searchQuery, MAX_SOURCES_ITEMS) - val tags = tagsDeferred.await() - val mangaList = mangaDeferred.await() - val queries = queriesDeferred.await() - val hints = hintsDeferred.await() + val tags = tagsDeferred?.await() + val mangaList = mangaDeferred?.await() + val queries = queriesDeferred?.await() + val hints = hintsDeferred?.await() - buildList(queries.size + sources.size + hints.size + 2) { - if (tags.isNotEmpty()) { + buildList(queries.sizeOrZero() + sources.sizeOrZero() + hints.sizeOrZero() + 2) { + if (!tags.isNullOrEmpty()) { add(SearchSuggestionItem.Tags(mapTags(tags))) } - if (mangaList.isNotEmpty()) { + if (!mangaList.isNullOrEmpty()) { add(SearchSuggestionItem.MangaList(mangaList)) } - sources.mapTo(this) { SearchSuggestionItem.Source(it, it in enabledSources) } - queries.mapTo(this) { SearchSuggestionItem.RecentQuery(it) } - hints.mapTo(this) { SearchSuggestionItem.Hint(it) } + sources?.mapTo(this) { SearchSuggestionItem.Source(it, it in enabledSources) } + queries?.mapTo(this) { SearchSuggestionItem.RecentQuery(it) } + hints?.mapTo(this) { SearchSuggestionItem.Hint(it) } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsFragment.kt index 97eafb935..96fdc2986 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsFragment.kt @@ -10,6 +10,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatDelegate import androidx.core.view.postDelayed import androidx.fragment.app.viewModels +import androidx.preference.MultiSelectListPreference import androidx.preference.Preference import androidx.preference.TwoStatePreference import androidx.preference.forEach @@ -21,6 +22,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.SearchSuggestionType import org.koitharu.kotatsu.core.ui.BasePreferenceFragment import org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver @@ -29,9 +31,12 @@ import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.tryLaunch import org.koitharu.kotatsu.local.data.CacheDir +import org.koitharu.kotatsu.parsers.util.mapToSet +import org.koitharu.kotatsu.parsers.util.names import org.koitharu.kotatsu.settings.backup.BackupDialogFragment import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity +import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider import javax.inject.Inject @AndroidEntryPoint @@ -87,6 +92,12 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac findPreference("storage_usage")?.let { pref -> viewModel.storageUsage.observe(viewLifecycleOwner, pref) } + findPreference(AppSettings.KEY_SEARCH_SUGGESTION_TYPES)?.let { pref -> + pref.entryValues = SearchSuggestionType.entries.names() + pref.entries = SearchSuggestionType.entries.map { pref.context.getString(it.titleResId) }.toTypedArray() + pref.summaryProvider = MultiSummaryProvider(R.string.none) + pref.values = settings.searchSuggestionTypes.mapToSet { it.name } + } viewModel.loadingKeys.observe(viewLifecycleOwner) { keys -> preferenceScreen.forEach { pref -> pref.isEnabled = pref.key !in keys diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt index ee52cbbd4..cedee30e0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt @@ -56,6 +56,7 @@ import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission import org.koitharu.kotatsu.core.util.ext.flatten import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.sanitize +import org.koitharu.kotatsu.core.util.ext.sizeOrZero import org.koitharu.kotatsu.core.util.ext.takeMostFrequent import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull import org.koitharu.kotatsu.core.util.ext.trySetForeground @@ -289,7 +290,7 @@ class SuggestionsWorker @AssistedInject constructor( style.bigText( buildSpannedString { append(tagsText) - val chaptersCount = manga.chapters?.size ?: 0 + val chaptersCount = manga.chapters.sizeOrZero() appendLine() bold { append( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/Tracker.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/Tracker.kt index c1d6e7aa2..01af8afda 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/Tracker.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/Tracker.kt @@ -7,7 +7,7 @@ import org.koitharu.kotatsu.core.model.getPreferredBranch import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.core.util.CompositeMutex2 +import org.koitharu.kotatsu.core.util.MultiMutex import org.koitharu.kotatsu.core.util.ext.toInstantOrNull import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.history.data.HistoryRepository @@ -139,7 +139,7 @@ class Tracker @Inject constructor( private companion object { const val NO_ID = 0L - private val mangaMutex = CompositeMutex2() + private val mangaMutex = MultiMutex() suspend inline fun withMangaLock(id: Long, action: () -> T): T { contract { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c07b49f95..1196900be 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -636,4 +636,7 @@ %1$s: %2$d Pin navigation UI Do not hide navgation bar and search view on scroll + Search suggestions + Recent queries + Suggested queries diff --git a/app/src/main/res/xml/pref_user_data.xml b/app/src/main/res/xml/pref_user_data.xml index e83c93c7a..9d3f26605 100644 --- a/app/src/main/res/xml/pref_user_data.xml +++ b/app/src/main/res/xml/pref_user_data.xml @@ -20,6 +20,10 @@ android:summary="@string/history_shortcuts_summary" android:title="@string/history_shortcuts" /> + + () + val mutex = MultiMutex() mutex.lock(1) mutex.lock(2) mutex.unlock(1) @@ -26,8 +27,9 @@ class CompositeMutexTest { } @Test + @Ignore("Cannot delay in test") fun doubleLock() = runTest { - val mutex = CompositeMutex() + val mutex = MultiMutex() repeat(2) { launch(Dispatchers.Default) { mutex.lock(1) @@ -44,7 +46,7 @@ class CompositeMutexTest { @Test fun cancellation() = runTest { - val mutex = CompositeMutex() + val mutex = MultiMutex() mutex.lock(1) val job = launch { try {