diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt index 06f22090a..e51857f55 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt @@ -6,7 +6,10 @@ import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity import org.koitharu.kotatsu.core.db.entity.TagEntity import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.core.prefs.ReaderMode +import org.koitharu.kotatsu.utils.ext.mapToSet class MangaDataRepository(private val db: MangaDatabase) { @@ -45,4 +48,10 @@ class MangaDataRepository(private val db: MangaDatabase) { db.mangaDao.upsert(MangaEntity.from(manga), tags) } } + + suspend fun findTags(source: MangaSource): Set { + return db.tagsDao.findTags(source.name).mapToSet { + it.toMangaTag() + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/AnimatedToolbar.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/AnimatedToolbar.kt deleted file mode 100644 index f17b31f84..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/AnimatedToolbar.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.koitharu.kotatsu.base.ui.widgets - -import android.content.Context -import android.graphics.drawable.Drawable -import android.util.AttributeSet -import android.view.View -import androidx.appcompat.widget.Toolbar -import androidx.core.view.isGone -import com.google.android.material.R -import com.google.android.material.appbar.MaterialToolbar -import java.lang.reflect.Field - -class AnimatedToolbar @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = R.attr.toolbarStyle, -) : MaterialToolbar(context, attrs, defStyleAttr) { - - private var navButtonView: View? = null - get() { - if (field == null) { - runCatching { - field = navButtonViewField?.get(this) as? View - } - } - return field - } - - override fun setNavigationIcon(icon: Drawable?) { - super.setNavigationIcon(icon) - navButtonView?.isGone = (icon == null) - } - - private companion object { - - val navButtonViewField: Field? = runCatching { - Toolbar::class.java.getDeclaredField("mNavButtonView") - .also { it.isAccessible = true } - }.getOrNull() - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt index 0cd94ba37..7f9655d19 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt @@ -6,8 +6,8 @@ import org.koitharu.kotatsu.core.db.entity.TagEntity @Dao abstract class TagsDao { - @Query("SELECT * FROM tags") - abstract suspend fun getAllTags(): List + @Query("SELECT * FROM tags WHERE source = :source") + abstract suspend fun findTags(source: String): List @Insert(onConflict = OnConflictStrategy.IGNORE) abstract suspend fun insert(tag: TagEntity): Long diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt index 4a2c2be82..f66e4b14f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt @@ -4,7 +4,6 @@ import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaTag -import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.prefs.SourceSettings abstract class RemoteMangaRepository( @@ -20,8 +19,6 @@ abstract class RemoteMangaRepository( val title: String get() = source.title - override val sortOrders: Set get() = emptySet() - override suspend fun getPageUrl(page: MangaPage): String = page.url.withDomain() override suspend fun getTags(): Set = emptySet() diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ExHentaiRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ExHentaiRepository.kt index 41b86750e..0077f3d8c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ExHentaiRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ExHentaiRepository.kt @@ -17,6 +17,8 @@ class ExHentaiRepository( override val source = MangaSource.EXHENTAI + override val sortOrders: Set = emptySet() + override val defaultDomain: String get() = if (isAuthorized()) DOMAIN_AUTHORIZED else DOMAIN_UNAUTHORIZED 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 97fa18c4c..97664dd2f 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 @@ -85,7 +85,7 @@ class HistoryListViewModel( val result = ArrayList(if (grouped) (list.size * 1.4).toInt() else list.size + 1) var prevDate: DateTimeAgo? = null if (!grouped) { - result += ListHeader(null, R.string.history) + result += ListHeader(null, R.string.history, null) } for ((manga, history) in list) { if (grouped) { diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt index 01f40436b..00bd769fd 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt @@ -64,7 +64,8 @@ abstract class MangaListFragment : BaseFragment(), lifecycleOwner = viewLifecycleOwner, clickListener = this, onRetryClick = ::resolveException, - onTagRemoveClick = viewModel::onRemoveFilterTag + onTagRemoveClick = viewModel::onRemoveFilterTag, + onFilterClickListener = this::onFilterClick, ) paginationListener = PaginationScrollListener(4, this) with(binding.recyclerView) { @@ -191,6 +192,8 @@ abstract class MangaListFragment : BaseFragment(), } } + protected open fun onFilterClick() = Unit + private fun onGridScaleChanged(scale: Float) { spanSizeLookup.invalidateCache() spanResolver.setGridSize(scale, binding.recyclerView) diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/ListHeaderAD.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/ListHeaderAD.kt index 4d25060ac..53ac01484 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/ListHeaderAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/ListHeaderAD.kt @@ -2,11 +2,16 @@ package org.koitharu.kotatsu.list.ui.adapter import android.widget.TextView import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.databinding.ItemHeaderWithFilterBinding import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListModel -fun listHeaderAD() = adapterDelegate(R.layout.item_header) { +fun listHeaderAD() = adapterDelegate( + layout = R.layout.item_header, + on = { item, _, _ -> item is ListHeader && item.sortOrder == null }, +) { bind { val textView = (itemView as TextView) @@ -16,4 +21,25 @@ fun listHeaderAD() = adapterDelegate(R.layout.item_header textView.setText(item.textRes) } } +} + +fun listHeaderWithFilterAD( + onFilterClickListener: () -> Unit, +) = adapterDelegateViewBinding( + viewBinding = { inflater, parent -> ItemHeaderWithFilterBinding.inflate(inflater, parent, false) }, + on = { item, _, _ -> item is ListHeader && item.sortOrder != null }, +) { + + binding.textViewFilter.setOnClickListener { + onFilterClickListener() + } + + bind { + if (item.text != null) { + binding.textViewTitle.text = item.text + } else { + binding.textViewTitle.setText(item.textRes) + } + binding.textViewFilter.setText(requireNotNull(item.sortOrder).titleRes) + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt index 61cd60c03..714f04473 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt @@ -20,6 +20,7 @@ class MangaListAdapter( clickListener: OnListItemClickListener, onRetryClick: (Throwable) -> Unit, onTagRemoveClick: (MangaTag) -> Unit, + onFilterClickListener: () -> Unit, ) : AsyncListDifferDelegationAdapter(DiffCallback()) { init { @@ -41,6 +42,7 @@ class MangaListAdapter( .addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD()) .addDelegate(ITEM_TYPE_HEADER, listHeaderAD()) .addDelegate(ITEM_TYPE_FILTER, currentFilterAD(onTagRemoveClick)) + .addDelegate(ITEM_TYPE_HEADER_FILTER, listHeaderWithFilterAD(onFilterClickListener)) } private class DiffCallback : DiffUtil.ItemCallback() { @@ -79,5 +81,6 @@ class MangaListAdapter( const val ITEM_TYPE_EMPTY = 8 const val ITEM_TYPE_HEADER = 9 const val ITEM_TYPE_FILTER = 10 + const val ITEM_TYPE_HEADER_FILTER = 11 } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterBottomSheet.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterBottomSheet.kt index c47d7ac00..70aef4326 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterBottomSheet.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterBottomSheet.kt @@ -8,7 +8,8 @@ import androidx.core.os.bundleOf import androidx.fragment.app.FragmentManager import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog -import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.androidx.viewmodel.ViewModelOwner.Companion.from +import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.core.parameter.parametersOf import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseBottomSheet @@ -18,7 +19,9 @@ import org.koitharu.kotatsu.utils.ext.withArgs class FilterBottomSheet : BaseBottomSheet() { - private val viewModel by viewModel { + private val viewModel by sharedViewModel( + owner = { from(requireParentFragment(), requireParentFragment()) } + ) { parametersOf( requireArguments().getParcelable(ARG_SOURCE), requireArguments().getParcelable(ARG_STATE), diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterViewModel.kt index 7341e836a..0942ef35d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterViewModel.kt @@ -4,13 +4,15 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import kotlinx.coroutines.* import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.MangaDataRepository import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.model.SortOrder -import org.koitharu.kotatsu.core.parser.MangaRepository +import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import java.util.* class FilterViewModel( - private val repository: MangaRepository, + private val repository: RemoteMangaRepository, + dataRepository: MangaDataRepository, state: FilterState, ) : BaseViewModel(), OnFilterChangedListener { @@ -22,6 +24,9 @@ class FilterViewModel( private val availableTagsDeferred = viewModelScope.async(Dispatchers.Default + createErrorHandler()) { repository.getTags() } + private val localTagsDeferred = viewModelScope.async(Dispatchers.Default + createErrorHandler()) { + dataRepository.findTags(repository.source) + } init { showFilter() @@ -48,6 +53,7 @@ class FilterViewModel( job = launchJob(Dispatchers.Default) { previousJob?.cancelAndJoin() val tags = availableTagsDeferred.await() + val localTags = localTagsDeferred.await() val sortOrders = repository.sortOrders val list = ArrayList(sortOrders.size + tags.size + 2) list.add(FilterItem.Header(R.string.sort_order)) @@ -57,6 +63,7 @@ class FilterViewModel( if (tags.isNotEmpty() || selectedTags.isNotEmpty()) { list.add(FilterItem.Header(R.string.genres)) val mappedTags = TreeSet(compareBy({ !it.isChecked }, { it.tag.title })) + localTags.mapTo(mappedTags) { FilterItem.Tag(it, isChecked = it in selectedTags) } tags.mapTo(mappedTags) { FilterItem.Tag(it, isChecked = it in selectedTags) } selectedTags.mapTo(mappedTags) { FilterItem.Tag(it, isChecked = true) } list.addAll(mappedTags) diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListHeader.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListHeader.kt index 209c7227f..a14db0f3a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListHeader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListHeader.kt @@ -1,8 +1,10 @@ package org.koitharu.kotatsu.list.ui.model import androidx.annotation.StringRes +import org.koitharu.kotatsu.core.model.SortOrder data class ListHeader( val text: CharSequence?, @StringRes val textRes: Int, + val sortOrder: SortOrder?, ) : ListModel \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt index 3f721355a..b68af3a0a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt @@ -32,7 +32,7 @@ class LocalListViewModel( val importProgress = MutableLiveData(null) private val listError = MutableStateFlow(null) private val mangaList = MutableStateFlow?>(null) - private val headerModel = ListHeader(null, R.string.local_storage) + private val headerModel = ListHeader(null, R.string.local_storage, null) private var importJob: Job? = null override val content = combine( diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/RemoteListModule.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/RemoteListModule.kt index b856248ba..4d35a857f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/remotelist/RemoteListModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/RemoteListModule.kt @@ -4,6 +4,8 @@ import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.qualifier.named import org.koin.dsl.module import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.parser.MangaRepository +import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.list.ui.filter.FilterViewModel import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel @@ -11,10 +13,17 @@ val remoteListModule get() = module { viewModel { params -> - RemoteListViewModel(get(named(params.get())), get()) + RemoteListViewModel( + repository = get(named(params.get())) as RemoteMangaRepository, + settings = get(), + ) } viewModel { params -> - FilterViewModel(get(named(params.get())), params.get()) + FilterViewModel( + repository = get(named(params.get())) as RemoteMangaRepository, + dataRepository = get(), + state = params.get(), + ) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt index ff951de7b..04a1ffefe 100644 --- a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt @@ -54,13 +54,17 @@ class RemoteListFragment : MangaListFragment(), FragmentResultListener { true } R.id.action_filter -> { - FilterBottomSheet.show(childFragmentManager, source, viewModel.filter) + onFilterClick() true } else -> super.onOptionsItemSelected(item) } } + override fun onFilterClick() { + FilterBottomSheet.show(childFragmentManager, source, viewModel.filter) + } + override fun onFragmentResult(requestKey: String, result: Bundle) { when (requestKey) { FilterBottomSheet.REQUEST_KEY -> viewModel.applyFilter( 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 fc5acd438..c914e709f 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 @@ -10,7 +10,6 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.widgets.ChipsView import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaTag -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.list.ui.MangaListViewModel @@ -19,7 +18,7 @@ import org.koitharu.kotatsu.list.ui.model.* import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct class RemoteListViewModel( - private val repository: MangaRepository, + private val repository: RemoteMangaRepository, settings: AppSettings ) : MangaListViewModel(settings) { @@ -29,21 +28,24 @@ class RemoteListViewModel( private val hasNextPage = MutableStateFlow(false) private val listError = MutableStateFlow(null) private var loadingJob: Job? = null - private val headerModel = ListHeader((repository as RemoteMangaRepository).title, 0) + private val headerModel = MutableStateFlow( + ListHeader(repository.title, 0, filter.sortOrder) + ) override val content = combine( mangaList, createListModeFlow(), + headerModel, listError, hasNextPage - ) { list, mode, error, hasNext -> + ) { list, mode, header, error, hasNext -> when { list.isNullOrEmpty() && error != null -> listOf(error.toErrorState(canRetry = true)) list == null -> listOf(LoadingState) list.isEmpty() -> listOf(EmptyState(R.drawable.ic_book_cross, R.string.nothing_found, R.string.empty)) else -> { val result = ArrayList(list.size + 3) - result += headerModel + result += header createFilterModel()?.let { result.add(it) } list.toUi(result, mode) when { @@ -83,6 +85,7 @@ class RemoteListViewModel( fun applyFilter(newFilter: FilterState) { filter = newFilter + headerModel.value = ListHeader(repository.title, 0, newFilter.sortOrder) mangaList.value = null hasNextPage.value = false loadList(false) diff --git a/app/src/main/res/layout/item_header_with_filter.xml b/app/src/main/res/layout/item_header_with_filter.xml new file mode 100644 index 000000000..05c7793ba --- /dev/null +++ b/app/src/main/res/layout/item_header_with_filter.xml @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file