Search in history, favorites and local

master
Koitharu 2 years ago committed by Mac135135
parent 2191d9c83b
commit 720c389dbd

@ -192,7 +192,7 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name="org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity" android:name="org.koitharu.kotatsu.search.ui.multi.SearchActivity"
android:label="@string/search" /> android:label="@string/search" />
<activity <activity
android:name="org.koitharu.kotatsu.main.ui.protect.ProtectActivity" android:name="org.koitharu.kotatsu.main.ui.protect.ProtectActivity"

@ -39,6 +39,8 @@ fun MangaEntity.toManga(tags: Set<MangaTag>) = Manga(
fun MangaWithTags.toManga() = manga.toManga(tags.toMangaTags()) fun MangaWithTags.toManga() = manga.toManga(tags.toMangaTags())
fun Collection<MangaWithTags>.toMangaList() = map { it.toManga() }
// Model to entity // Model to entity
fun Manga.toEntity() = MangaEntity( fun Manga.toEntity() = MangaEntity(

@ -23,7 +23,7 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ShareHelper import org.koitharu.kotatsu.core.util.ShareHelper
import org.koitharu.kotatsu.download.ui.dialog.DownloadOption import org.koitharu.kotatsu.download.ui.dialog.DownloadOption
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity import org.koitharu.kotatsu.search.ui.multi.SearchActivity
import org.koitharu.kotatsu.stats.ui.sheet.MangaStatsSheet import org.koitharu.kotatsu.stats.ui.sheet.MangaStatsSheet
class DetailsMenuProvider( class DetailsMenuProvider(
@ -92,7 +92,7 @@ class DetailsMenuProvider(
R.id.action_related -> { R.id.action_related -> {
viewModel.manga.value?.let { viewModel.manga.value?.let {
activity.startActivity(MultiSearchActivity.newIntent(activity, it.title)) activity.startActivity(SearchActivity.newIntent(activity, it.title))
} }
} }

@ -58,9 +58,7 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>(),
RecyclerScrollKeeper(this).attach() RecyclerScrollKeeper(this).attach()
} }
addMenuProvider(DownloadsMenuProvider(this, viewModel)) addMenuProvider(DownloadsMenuProvider(this, viewModel))
viewModel.items.observe(this) { viewModel.items.observe(this, downloadsAdapter)
downloadsAdapter.items = it
}
viewModel.onActionDone.observeEvent(this, ReversibleActionObserver(viewBinding.recyclerView)) viewModel.onActionDone.observeEvent(this, ReversibleActionObserver(viewBinding.recyclerView))
val menuInvalidator = MenuInvalidator(this) val menuInvalidator = MenuInvalidator(this)
viewModel.hasActiveWorks.observe(this, menuInvalidator) viewModel.hasActiveWorks.observe(this, menuInvalidator)

@ -91,9 +91,7 @@ class ExploreFragment :
checkNotNull(sourceSelectionController).attachToRecyclerView(this) checkNotNull(sourceSelectionController).attachToRecyclerView(this)
} }
addMenuProvider(ExploreMenuProvider(binding.root.context)) addMenuProvider(ExploreMenuProvider(binding.root.context))
viewModel.content.observe(viewLifecycleOwner) { viewModel.content.observe(viewLifecycleOwner, checkNotNull(exploreAdapter))
exploreAdapter?.items = it
}
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
viewModel.onOpenManga.observeEvent(viewLifecycleOwner, ::onOpenManga) viewModel.onOpenManga.observeEvent(viewLifecycleOwner, ::onOpenManga)
viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView)) viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView))

@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.Flow
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import org.koitharu.kotatsu.core.db.MangaQueryBuilder import org.koitharu.kotatsu.core.db.MangaQueryBuilder
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
import org.koitharu.kotatsu.core.db.entity.MangaWithTags
import org.koitharu.kotatsu.favourites.domain.model.Cover import org.koitharu.kotatsu.favourites.domain.model.Cover
import org.koitharu.kotatsu.list.domain.ListFilterOption import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.list.domain.ListSortOrder import org.koitharu.kotatsu.list.domain.ListSortOrder
@ -32,6 +33,10 @@ abstract class FavouritesDao : MangaQueryBuilder.ConditionCallback {
@Query("SELECT * FROM favourites WHERE deleted_at = 0 GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit") @Query("SELECT * FROM favourites WHERE deleted_at = 0 GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit")
abstract suspend fun findLast(limit: Int): List<FavouriteManga> abstract suspend fun findLast(limit: Int): List<FavouriteManga>
@Transaction
@Query("SELECT manga.* FROM favourites LEFT JOIN manga ON manga.manga_id = favourites.manga_id WHERE favourites.deleted_at = 0 AND (manga.title LIKE :query OR manga.alt_title LIKE :query) LIMIT :limit")
abstract suspend fun search(query: String, limit: Int): List<MangaWithTags>
fun observeAll( fun observeAll(
order: ListSortOrder, order: ListSortOrder,
filterOptions: Set<ListFilterOption>, filterOptions: Set<ListFilterOption>,

@ -10,21 +10,21 @@ import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toEntities import org.koitharu.kotatsu.core.db.entity.toEntities
import org.koitharu.kotatsu.core.db.entity.toEntity import org.koitharu.kotatsu.core.db.entity.toEntity
import org.koitharu.kotatsu.core.db.entity.toMangaList
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.toMangaSources import org.koitharu.kotatsu.core.model.toMangaSources
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
import org.koitharu.kotatsu.core.util.ext.mapItems import org.koitharu.kotatsu.core.util.ext.mapItems
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
import org.koitharu.kotatsu.favourites.data.toManga
import org.koitharu.kotatsu.favourites.data.toMangaList import org.koitharu.kotatsu.favourites.data.toMangaList
import org.koitharu.kotatsu.favourites.domain.model.Cover import org.koitharu.kotatsu.favourites.domain.model.Cover
import org.koitharu.kotatsu.list.domain.ListFilterOption import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.list.domain.ListSortOrder import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.levenshteinDistance
import javax.inject.Inject import javax.inject.Inject
@Reusable @Reusable
@ -43,12 +43,17 @@ class FavouritesRepository @Inject constructor(
return entities.toMangaList() return entities.toMangaList()
} }
suspend fun search(query: String, limit: Int): List<Manga> {
val entities = db.getFavouritesDao().search("%$query%", limit)
return entities.toMangaList().sortedBy { it.title.levenshteinDistance(query) }
}
fun observeAll(order: ListSortOrder, filterOptions: Set<ListFilterOption>, limit: Int): Flow<List<Manga>> { fun observeAll(order: ListSortOrder, filterOptions: Set<ListFilterOption>, limit: Int): Flow<List<Manga>> {
if (ListFilterOption.Downloaded in filterOptions) { if (ListFilterOption.Downloaded in filterOptions) {
return localObserver.observeAll(order, filterOptions, limit) return localObserver.observeAll(order, filterOptions, limit)
} }
return db.getFavouritesDao().observeAll(order, filterOptions, limit) return db.getFavouritesDao().observeAll(order, filterOptions, limit)
.mapItems { it.toManga() } .map { it.toMangaList() }
} }
suspend fun getManga(categoryId: Long): List<Manga> { suspend fun getManga(categoryId: Long): List<Manga> {
@ -66,7 +71,7 @@ class FavouritesRepository @Inject constructor(
return localObserver.observeAll(categoryId, order, filterOptions, limit) return localObserver.observeAll(categoryId, order, filterOptions, limit)
} }
return db.getFavouritesDao().observeAll(categoryId, order, filterOptions, limit) return db.getFavouritesDao().observeAll(categoryId, order, filterOptions, limit)
.mapItems { it.toManga() } .map { it.toMangaList() }
} }
fun observeAll(categoryId: Long, filterOptions: Set<ListFilterOption>, limit: Int): Flow<List<Manga>> { fun observeAll(categoryId: Long, filterOptions: Set<ListFilterOption>, limit: Int): Flow<List<Manga>> {

@ -11,6 +11,7 @@ import androidx.sqlite.db.SupportSQLiteQuery
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.koitharu.kotatsu.core.db.MangaQueryBuilder import org.koitharu.kotatsu.core.db.MangaQueryBuilder
import org.koitharu.kotatsu.core.db.TABLE_HISTORY import org.koitharu.kotatsu.core.db.TABLE_HISTORY
import org.koitharu.kotatsu.core.db.entity.MangaWithTags
import org.koitharu.kotatsu.core.db.entity.TagEntity import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.list.domain.ListFilterOption import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.list.domain.ListSortOrder import org.koitharu.kotatsu.list.domain.ListSortOrder
@ -23,6 +24,10 @@ abstract class HistoryDao : MangaQueryBuilder.ConditionCallback {
@Query("SELECT * FROM history WHERE deleted_at = 0 ORDER BY updated_at DESC LIMIT :limit OFFSET :offset") @Query("SELECT * FROM history WHERE deleted_at = 0 ORDER BY updated_at DESC LIMIT :limit OFFSET :offset")
abstract suspend fun findAll(offset: Int, limit: Int): List<HistoryWithManga> abstract suspend fun findAll(offset: Int, limit: Int): List<HistoryWithManga>
@Transaction
@Query("SELECT manga.* FROM history LEFT JOIN manga ON manga.manga_id = history.manga_id WHERE history.deleted_at = 0 AND (manga.title LIKE :query OR manga.alt_title LIKE :query) LIMIT :limit")
abstract suspend fun search(query: String, limit: Int): List<MangaWithTags>
@Transaction @Transaction
@Query("SELECT * FROM history WHERE deleted_at = 0 ORDER BY updated_at DESC") @Query("SELECT * FROM history WHERE deleted_at = 0 ORDER BY updated_at DESC")
abstract fun observeAll(): Flow<List<HistoryWithManga>> abstract fun observeAll(): Flow<List<HistoryWithManga>>

@ -10,11 +10,10 @@ import kotlinx.coroutines.flow.onStart
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toEntity import org.koitharu.kotatsu.core.db.entity.toEntity
import org.koitharu.kotatsu.core.db.entity.toManga import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.core.db.entity.toMangaTag import org.koitharu.kotatsu.core.db.entity.toMangaList
import org.koitharu.kotatsu.core.db.entity.toMangaTags import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.core.db.entity.toMangaTagsList import org.koitharu.kotatsu.core.db.entity.toMangaTagsList
import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.findById import org.koitharu.kotatsu.core.model.findById
import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.model.isNsfw import org.koitharu.kotatsu.core.model.isNsfw
@ -31,6 +30,7 @@ import org.koitharu.kotatsu.list.domain.ReadingProgress
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.levenshteinDistance
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
import org.koitharu.kotatsu.scrobbling.common.domain.tryScrobble import org.koitharu.kotatsu.scrobbling.common.domain.tryScrobble
import org.koitharu.kotatsu.tracker.domain.CheckNewChaptersUseCase import org.koitharu.kotatsu.tracker.domain.CheckNewChaptersUseCase
@ -52,6 +52,11 @@ class HistoryRepository @Inject constructor(
return entities.map { it.manga.toManga(it.tags.toMangaTags()) } return entities.map { it.manga.toManga(it.tags.toMangaTags()) }
} }
suspend fun search(query: String, limit: Int): List<Manga> {
val entities = db.getHistoryDao().search("%$query%", limit)
return entities.toMangaList().sortedBy { it.title.levenshteinDistance(query) }
}
suspend fun getCount(): Int { suspend fun getCount(): Int {
return db.getHistoryDao().getCount() return db.getHistoryDao().getCount()
} }

@ -34,6 +34,7 @@ import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.levenshteinDistance
import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import java.io.File import java.io.File
@ -62,7 +63,12 @@ class LocalMangaRepository @Inject constructor(
isSearchWithFiltersSupported = true, isSearchWithFiltersSupported = true,
) )
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.RATING, SortOrder.NEWEST) override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.ALPHABETICAL,
SortOrder.RATING,
SortOrder.NEWEST,
SortOrder.RELEVANCE,
)
override var defaultSortOrder: SortOrder override var defaultSortOrder: SortOrder
get() = settings.localListOrder get() = settings.localListOrder
@ -102,6 +108,9 @@ class LocalMangaRepository @Inject constructor(
val isNsfw = contentRating == ContentRating.ADULT val isNsfw = contentRating == ContentRating.ADULT
list.retainAll { it.manga.isNsfw == isNsfw } list.retainAll { it.manga.isNsfw == isNsfw }
} }
if (!query.isNullOrEmpty() && order == SortOrder.RELEVANCE) {
list.sortBy { it.manga.title.levenshteinDistance(query) }
}
} }
when (order) { when (order) {
SortOrder.ALPHABETICAL -> list.sortWith(compareBy(AlphanumComparator()) { x -> x.manga.title }) SortOrder.ALPHABETICAL -> list.sortWith(compareBy(AlphanumComparator()) { x -> x.manga.title })

@ -66,7 +66,7 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity import org.koitharu.kotatsu.search.ui.multi.SearchActivity
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
@ -258,7 +258,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
override fun onQueryClick(query: String, submit: Boolean) { override fun onQueryClick(query: String, submit: Boolean) {
viewBinding.searchView.query = query viewBinding.searchView.query = query
if (submit && query.isNotEmpty()) { if (submit && query.isNotEmpty()) {
startActivity(MultiSearchActivity.newIntent(this, query)) startActivity(SearchActivity.newIntent(this, query))
searchSuggestionViewModel.saveQuery(query) searchSuggestionViewModel.saveQuery(query)
viewBinding.searchView.post { viewBinding.searchView.post {
closeSearchCallback.handleOnBackPressed() closeSearchCallback.handleOnBackPressed()

@ -54,7 +54,7 @@ class ScrobblerConfigActivity : BaseActivity<ActivityScrobblerConfigBinding>(),
} }
viewBinding.imageViewAvatar.setOnClickListener(this) viewBinding.imageViewAvatar.setOnClickListener(this)
viewModel.content.observe(this, listAdapter::setItems) viewModel.content.observe(this, listAdapter)
viewModel.user.observe(this, this::onUserChanged) viewModel.user.observe(this, this::onUserChanged)
viewModel.isLoading.observe(this, this::onLoadingStateChanged) viewModel.isLoading.observe(this, this::onLoadingStateChanged)
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null)) viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))

@ -23,7 +23,7 @@ import org.koitharu.kotatsu.core.util.ShareHelper
import org.koitharu.kotatsu.core.util.ext.invalidateNestedItemDecorations import org.koitharu.kotatsu.core.util.ext.invalidateNestedItemDecorations
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.ActivitySearchMultiBinding import org.koitharu.kotatsu.databinding.ActivitySearchBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
import org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteSheet import org.koitharu.kotatsu.favourites.ui.categories.select.FavoriteSheet
@ -38,12 +38,12 @@ import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.multi.adapter.MultiSearchAdapter import org.koitharu.kotatsu.search.ui.multi.adapter.SearchAdapter
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class MultiSearchActivity : class SearchActivity :
BaseActivity<ActivitySearchMultiBinding>(), BaseActivity<ActivitySearchBinding>(),
MangaListListener, MangaListListener,
ListSelectionController.Callback { ListSelectionController.Callback {
@ -53,16 +53,15 @@ class MultiSearchActivity :
@Inject @Inject
lateinit var settings: AppSettings lateinit var settings: AppSettings
private val viewModel by viewModels<MultiSearchViewModel>() private val viewModel by viewModels<SearchViewModel>()
private lateinit var adapter: MultiSearchAdapter
private lateinit var selectionController: ListSelectionController private lateinit var selectionController: ListSelectionController
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivitySearchMultiBinding.inflate(layoutInflater)) setContentView(ActivitySearchBinding.inflate(layoutInflater))
title = viewModel.query title = viewModel.query
val itemCLickListener = OnListItemClickListener<MultiSearchListModel> { item, view -> val itemCLickListener = OnListItemClickListener<SearchResultsListModel> { item, view ->
startActivity( startActivity(
MangaListActivity.newIntent( MangaListActivity.newIntent(
view.context, view.context,
@ -79,7 +78,7 @@ class MultiSearchActivity :
registryOwner = this, registryOwner = this,
callback = this, callback = this,
) )
adapter = MultiSearchAdapter( val adapter = SearchAdapter(
lifecycleOwner = this, lifecycleOwner = this,
coil = coil, coil = coil,
listener = this, listener = this,
@ -96,7 +95,7 @@ class MultiSearchActivity :
setSubtitle(R.string.search_results) setSubtitle(R.string.search_results)
} }
viewModel.list.observe(this) { adapter.items = it } viewModel.list.observe(this, adapter)
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null)) viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))
viewModel.onDownloadStarted.observeEvent(this, DownloadStartedObserver(viewBinding.recyclerView)) viewModel.onDownloadStarted.observeEvent(this, DownloadStartedObserver(viewBinding.recyclerView))
} }
@ -194,7 +193,7 @@ class MultiSearchActivity :
const val EXTRA_QUERY = "query" const val EXTRA_QUERY = "query"
fun newIntent(context: Context, query: String) = fun newIntent(context: Context, query: String) =
Intent(context, MultiSearchActivity::class.java) Intent(context, SearchActivity::class.java)
.putExtra(EXTRA_QUERY, query) .putExtra(EXTRA_QUERY, query)
} }
} }

@ -1,23 +1,33 @@
package org.koitharu.kotatsu.search.ui.multi package org.koitharu.kotatsu.search.ui.multi
import android.content.Context
import androidx.annotation.StringRes
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
data class MultiSearchListModel( data class SearchResultsListModel(
@StringRes val titleResId: Int,
val source: MangaSource, val source: MangaSource,
val hasMore: Boolean, val hasMore: Boolean,
val list: List<MangaListModel>, val list: List<MangaListModel>,
val error: Throwable?, val error: Throwable?,
) : ListModel { ) : ListModel {
fun getTitle(context: Context): String = if (titleResId != 0) {
context.getString(titleResId)
} else {
source.getTitle(context)
}
override fun areItemsTheSame(other: ListModel): Boolean { override fun areItemsTheSame(other: ListModel): Boolean {
return other is MultiSearchListModel && source == other.source return other is SearchResultsListModel && source == other.source && titleResId == other.titleResId
} }
override fun getChangePayload(previousState: ListModel): Any? { override fun getChangePayload(previousState: ListModel): Any? {
return if (previousState is MultiSearchListModel && previousState.list != list) { return if (previousState is SearchResultsListModel && previousState.list != list) {
ListModelDiffCallback.PAYLOAD_NESTED_LIST_CHANGED ListModelDiffCallback.PAYLOAD_NESTED_LIST_CHANGED
} else { } else {
super.getChangePayload(previousState) super.getChangePayload(previousState)

@ -23,6 +23,8 @@ import kotlinx.coroutines.plus
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.sync.withPermit
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.LocalMangaSource
import org.koitharu.kotatsu.core.model.UnknownMangaSource
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
@ -31,13 +33,17 @@ import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.list.domain.MangaListMapper import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.model.EmptyState import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingFooter import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import javax.inject.Inject import javax.inject.Inject
@ -45,16 +51,19 @@ private const val MAX_PARALLELISM = 4
private const val MIN_HAS_MORE_ITEMS = 8 private const val MIN_HAS_MORE_ITEMS = 8
@HiltViewModel @HiltViewModel
class MultiSearchViewModel @Inject constructor( class SearchViewModel @Inject constructor(
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
private val mangaListMapper: MangaListMapper, private val mangaListMapper: MangaListMapper,
private val mangaRepositoryFactory: MangaRepository.Factory, private val mangaRepositoryFactory: MangaRepository.Factory,
private val downloadScheduler: DownloadWorker.Scheduler, private val downloadScheduler: DownloadWorker.Scheduler,
private val sourcesRepository: MangaSourcesRepository, private val sourcesRepository: MangaSourcesRepository,
private val historyRepository: HistoryRepository,
private val localMangaRepository: LocalMangaRepository,
private val favouritesRepository: FavouritesRepository,
) : BaseViewModel() { ) : BaseViewModel() {
val onDownloadStarted = MutableEventFlow<Unit>() val onDownloadStarted = MutableEventFlow<Unit>()
val query = savedStateHandle.get<String>(MultiSearchActivity.EXTRA_QUERY).orEmpty() val query = savedStateHandle.get<String>(SearchActivity.EXTRA_QUERY).orEmpty()
private val retryCounter = MutableStateFlow(0) private val retryCounter = MutableStateFlow(0)
private val listData = retryCounter.flatMapLatest { private val listData = retryCounter.flatMapLatest {
@ -108,7 +117,10 @@ class MultiSearchViewModel @Inject constructor(
} }
@CheckResult @CheckResult
private fun searchImpl(q: String): Flow<List<MultiSearchListModel>> = channelFlow { private fun searchImpl(q: String): Flow<List<SearchResultsListModel>> = channelFlow {
searchHistory(q)?.let { send(it) }
searchFavorites(q)?.let { send(it) }
searchLocal(q)?.let { send(it) }
val sources = sourcesRepository.getEnabledSources() val sources = sourcesRepository.getEnabledSources()
if (sources.isEmpty()) { if (sources.isEmpty()) {
return@channelFlow return@channelFlow
@ -132,12 +144,12 @@ class MultiSearchViewModel @Inject constructor(
if (list.isEmpty()) { if (list.isEmpty()) {
null null
} else { } else {
MultiSearchListModel(source, list.size > MIN_HAS_MORE_ITEMS, list, null) SearchResultsListModel(0, source, list.size > MIN_HAS_MORE_ITEMS, list, null)
} }
}, },
onFailure = { error -> onFailure = { error ->
error.printStackTraceDebug() error.printStackTraceDebug()
MultiSearchListModel(source, true, emptyList(), error) SearchResultsListModel(0, source, true, emptyList(), error)
}, },
) )
if (item != null) { if (item != null) {
@ -146,7 +158,94 @@ class MultiSearchViewModel @Inject constructor(
} }
} }
}.joinAll() }.joinAll()
}.runningFold<MultiSearchListModel, List<MultiSearchListModel>?>(null) { list, item -> list.orEmpty() + item } }.runningFold<SearchResultsListModel, List<SearchResultsListModel>?>(null) { list, item -> list.orEmpty() + item }
.filterNotNull() .filterNotNull()
.onEmpty { emit(emptyList()) } .onEmpty { emit(emptyList()) }
private suspend fun searchHistory(q: String): SearchResultsListModel? {
return runCatchingCancellable {
historyRepository.search(q, Int.MAX_VALUE)
}.fold(
onSuccess = { result ->
if (result.isNotEmpty()) {
SearchResultsListModel(
titleResId = R.string.history,
source = UnknownMangaSource,
hasMore = false,
list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID),
error = null,
)
} else {
null
}
},
onFailure = { error ->
SearchResultsListModel(
titleResId = R.string.history,
source = UnknownMangaSource,
hasMore = false,
list = emptyList(),
error = error,
)
},
)
}
private suspend fun searchFavorites(q: String): SearchResultsListModel? {
return runCatchingCancellable {
favouritesRepository.search(q, Int.MAX_VALUE)
}.fold(
onSuccess = { result ->
if (result.isNotEmpty()) {
SearchResultsListModel(
titleResId = R.string.favourites,
source = UnknownMangaSource,
hasMore = false,
list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID),
error = null,
)
} else {
null
}
},
onFailure = { error ->
SearchResultsListModel(
titleResId = R.string.favourites,
source = UnknownMangaSource,
hasMore = false,
list = emptyList(),
error = error,
)
},
)
}
private suspend fun searchLocal(q: String): SearchResultsListModel? {
return runCatchingCancellable {
localMangaRepository.getList(0, SortOrder.RELEVANCE, MangaListFilter(query = q))
}.fold(
onSuccess = { result ->
if (result.isNotEmpty()) {
SearchResultsListModel(
titleResId = 0,
source = LocalMangaSource,
hasMore = result.size > MIN_HAS_MORE_ITEMS,
list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID),
error = null,
)
} else {
null
}
},
onFailure = { error ->
SearchResultsListModel(
titleResId = 0,
source = LocalMangaSource,
hasMore = true,
list = emptyList(),
error = error,
)
},
)
}
} }

@ -1,10 +1,12 @@
package org.koitharu.kotatsu.search.ui.multi.adapter package org.koitharu.kotatsu.search.ui.multi.adapter
import android.content.Context
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
import coil.ImageLoader import coil.ImageLoader
import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.list.ui.adapter.ListItemType import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
@ -14,16 +16,16 @@ import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.size.ItemSizeResolver import org.koitharu.kotatsu.list.ui.size.ItemSizeResolver
import org.koitharu.kotatsu.search.ui.multi.MultiSearchListModel import org.koitharu.kotatsu.search.ui.multi.SearchResultsListModel
class MultiSearchAdapter( class SearchAdapter(
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
coil: ImageLoader, coil: ImageLoader,
listener: MangaListListener, listener: MangaListListener,
itemClickListener: OnListItemClickListener<MultiSearchListModel>, itemClickListener: OnListItemClickListener<SearchResultsListModel>,
sizeResolver: ItemSizeResolver, sizeResolver: ItemSizeResolver,
selectionDecoration: MangaSelectionDecoration, selectionDecoration: MangaSelectionDecoration,
) : BaseListAdapter<ListModel>() { ) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {
init { init {
val pool = RecycledViewPool() val pool = RecycledViewPool()
@ -44,4 +46,8 @@ class MultiSearchAdapter(
addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listener)) addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listener))
addDelegate(ListItemType.STATE_ERROR, errorStateListAD(listener)) addDelegate(ListItemType.STATE_ERROR, errorStateListAD(listener))
} }
override fun getSectionText(context: Context, position: Int): CharSequence? {
return (items.getOrNull(position) as? SearchResultsListModel)?.getTitle(context)
}
} }

@ -8,7 +8,6 @@ import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
@ -20,7 +19,7 @@ import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.size.ItemSizeResolver import org.koitharu.kotatsu.list.ui.size.ItemSizeResolver
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.search.ui.multi.MultiSearchListModel import org.koitharu.kotatsu.search.ui.multi.SearchResultsListModel
fun searchResultsAD( fun searchResultsAD(
sharedPool: RecycledViewPool, sharedPool: RecycledViewPool,
@ -29,8 +28,8 @@ fun searchResultsAD(
sizeResolver: ItemSizeResolver, sizeResolver: ItemSizeResolver,
selectionDecoration: MangaSelectionDecoration, selectionDecoration: MangaSelectionDecoration,
listener: OnListItemClickListener<Manga>, listener: OnListItemClickListener<Manga>,
itemClickListener: OnListItemClickListener<MultiSearchListModel>, itemClickListener: OnListItemClickListener<SearchResultsListModel>,
) = adapterDelegateViewBinding<MultiSearchListModel, ListModel, ItemListGroupBinding>( ) = adapterDelegateViewBinding<SearchResultsListModel, ListModel, ItemListGroupBinding>(
{ layoutInflater, parent -> ItemListGroupBinding.inflate(layoutInflater, parent, false) }, { layoutInflater, parent -> ItemListGroupBinding.inflate(layoutInflater, parent, false) },
) { ) {
@ -40,13 +39,13 @@ fun searchResultsAD(
) )
binding.recyclerView.addItemDecoration(selectionDecoration) binding.recyclerView.addItemDecoration(selectionDecoration)
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
val spacing = context.resources.getDimensionPixelOffset(R.dimen.grid_spacing) val spacing = context.resources.getDimensionPixelOffset(R.dimen.grid_spacing_outer)
binding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing)) binding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing))
val eventListener = AdapterDelegateClickListenerAdapter(this, itemClickListener) val eventListener = AdapterDelegateClickListenerAdapter(this, itemClickListener)
binding.buttonMore.setOnClickListener(eventListener) binding.buttonMore.setOnClickListener(eventListener)
bind { bind {
binding.textViewTitle.text = item.source.getTitle(context) binding.textViewTitle.text = item.getTitle(context)
binding.buttonMore.isVisible = item.hasMore binding.buttonMore.isVisible = item.hasMore
adapter.items = item.list adapter.items = item.list
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()

@ -31,7 +31,7 @@ class TrackerCategoriesConfigSheet :
val adapter = TrackerCategoriesConfigAdapter(this) val adapter = TrackerCategoriesConfigAdapter(this)
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
viewModel.content.observe(viewLifecycleOwner) { adapter.items = it } viewModel.content.observe(viewLifecycleOwner, adapter)
} }
override fun onItemClick(item: FavouriteCategory, view: View) { override fun onItemClick(item: FavouriteCategory, view: View) {

@ -7,6 +7,8 @@ import android.view.ViewGroup
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import coil.ImageLoader import coil.ImageLoader
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@ -28,7 +30,6 @@ import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
@ -48,8 +49,6 @@ class FeedFragment :
private val viewModel by viewModels<FeedViewModel>() private val viewModel by viewModels<FeedViewModel>()
private var feedAdapter: FeedAdapter? = null
override fun onCreateViewBinding( override fun onCreateViewBinding(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -58,11 +57,12 @@ class FeedFragment :
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState) super.onViewBindingCreated(binding, savedInstanceState)
val sizeResolver = StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)) val sizeResolver = StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width))
feedAdapter = FeedAdapter(coil, viewLifecycleOwner, this, sizeResolver) { item, v -> val feedAdapter = FeedAdapter(coil, viewLifecycleOwner, this, sizeResolver) { item, v ->
viewModel.onItemClick(item) viewModel.onItemClick(item)
onItemClick(item.manga, v) onItemClick(item.manga, v)
} }
with(binding.recyclerView) { with(binding.recyclerView) {
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
adapter = feedAdapter adapter = feedAdapter
setHasFixedSize(true) setHasFixedSize(true)
addOnScrollListener(PaginationScrollListener(4, this@FeedFragment)) addOnScrollListener(PaginationScrollListener(4, this@FeedFragment))
@ -73,17 +73,12 @@ class FeedFragment :
addMenuProvider(FeedMenuProvider(binding.recyclerView, viewModel)) addMenuProvider(FeedMenuProvider(binding.recyclerView, viewModel))
viewModel.isHeaderEnabled.drop(1).observe(viewLifecycleOwner, MenuInvalidator(requireActivity())) viewModel.isHeaderEnabled.drop(1).observe(viewLifecycleOwner, MenuInvalidator(requireActivity()))
viewModel.content.observe(viewLifecycleOwner, this::onListChanged) viewModel.content.observe(viewLifecycleOwner, feedAdapter)
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
viewModel.onFeedCleared.observeEvent(viewLifecycleOwner) { onFeedCleared() } viewModel.onFeedCleared.observeEvent(viewLifecycleOwner) { onFeedCleared() }
viewModel.isRunning.observe(viewLifecycleOwner, this::onIsTrackerRunningChanged) viewModel.isRunning.observe(viewLifecycleOwner, this::onIsTrackerRunningChanged)
} }
override fun onDestroyView() {
feedAdapter = null
super.onDestroyView()
}
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
val rv = requireViewBinding().recyclerView val rv = requireViewBinding().recyclerView
rv.updatePadding( rv.updatePadding(
@ -112,10 +107,6 @@ class FeedFragment :
context.startActivity(UpdatesActivity.newIntent(context)) context.startActivity(UpdatesActivity.newIntent(context))
} }
private fun onListChanged(list: List<ListModel>) {
feedAdapter?.items = list
}
private fun onFeedCleared() { private fun onFeedCleared() {
val snackbar = Snackbar.make( val snackbar = Snackbar.make(
requireViewBinding().recyclerView, requireViewBinding().recyclerView,

@ -56,7 +56,7 @@ class ShelfWidgetConfigActivity :
viewModel.checkedId = config.categoryId viewModel.checkedId = config.categoryId
viewBinding.switchBackground.isChecked = config.hasBackground viewBinding.switchBackground.isChecked = config.hasBackground
viewModel.content.observe(this, this::onContentChanged) viewModel.content.observe(this, adapter)
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null)) viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))
} }
@ -96,10 +96,6 @@ class ShelfWidgetConfigActivity :
} }
} }
private fun onContentChanged(categories: List<CategoryItem>) {
adapter.items = categories
}
private fun updateWidget() { private fun updateWidget() {
val intent = Intent(this, ShelfWidgetProvider::class.java) val intent = Intent(this, ShelfWidgetProvider::class.java)
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE

@ -27,7 +27,7 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView <org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView
android:id="@id/recyclerView" android:id="@id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

@ -19,7 +19,6 @@
android:clipToPadding="false" android:clipToPadding="false"
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/list_spacing_normal" android:padding="@dimen/list_spacing_normal"
app:bubbleSize="small"
tools:layoutManager="org.koitharu.kotatsu.core.ui.list.FitHeightLinearLayoutManager" tools:layoutManager="org.koitharu.kotatsu.core.ui.list.FitHeightLinearLayoutManager"
tools:listitem="@layout/item_manga_list" /> tools:listitem="@layout/item_manga_list" />

@ -153,7 +153,7 @@
<item name="bubbleColor">?colorTertiary</item> <item name="bubbleColor">?colorTertiary</item>
<item name="bubbleTextColor">?colorOnTertiary</item> <item name="bubbleTextColor">?colorOnTertiary</item>
<item name="trackColor">?colorOutline</item> <item name="trackColor">?colorOutline</item>
<item name="bubbleSize">normal</item> <item name="bubbleSize">small</item>
<item name="scrollerOffset">@dimen/grid_spacing_outer</item> <item name="scrollerOffset">@dimen/grid_spacing_outer</item>
</style> </style>

Loading…
Cancel
Save