Improve ui

This commit is contained in:
Koitharu
2023-07-26 15:27:43 +03:00
parent 01c23bc3b8
commit 61a7f1c830
28 changed files with 238 additions and 76 deletions

View File

@@ -135,8 +135,7 @@
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name="org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity" android:name="org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity"
android:label="@string/favourites" android:label="@string/manage_categories" />
android:windowSoftInputMode="stateAlwaysHidden" />
<activity <activity
android:name="org.koitharu.kotatsu.widget.shelf.ShelfConfigActivity" android:name="org.koitharu.kotatsu.widget.shelf.ShelfConfigActivity"
android:exported="true" android:exported="true"

View File

@@ -12,6 +12,7 @@ import androidx.core.view.children
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import com.google.android.material.tabs.TabLayout
import kotlin.math.roundToInt import kotlin.math.roundToInt
fun View.hideKeyboard() { fun View.hideKeyboard() {
@@ -147,3 +148,9 @@ var View.isRtl: Boolean
set(value) { set(value) {
layoutDirection = if (value) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR layoutDirection = if (value) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR
} }
fun TabLayout.setTabsEnabled(enabled: Boolean) {
for (i in 0 until tabCount) {
getTabAt(i)?.view?.isEnabled = enabled
}
}

View File

@@ -143,14 +143,6 @@ class FavouritesRepository @Inject constructor(
db.favouriteCategoriesDao.updateTracking(id, isTrackingEnabled) db.favouriteCategoriesDao.updateTracking(id, isTrackingEnabled)
} }
suspend fun removeCategory(id: Long) {
db.withTransaction {
db.favouriteCategoriesDao.delete(id)
db.favouritesDao.deleteAll(id)
}
channels.deleteChannel(id)
}
suspend fun removeCategories(ids: Collection<Long>) { suspend fun removeCategories(ids: Collection<Long>) {
db.withTransaction { db.withTransaction {
for (id in ids) { for (id in ids) {

View File

@@ -7,7 +7,6 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
class CategoriesSelectionCallback( class CategoriesSelectionCallback(
@@ -25,21 +24,44 @@ class CategoriesSelectionCallback(
} }
override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
val isOneItem = controller.count == 1 val categories = viewModel.getCategories(controller.peekCheckedIds())
menu.findItem(R.id.action_edit)?.isVisible = isOneItem var canShow = categories.isNotEmpty()
var canHide = canShow
for (cat in categories) {
if (cat.isVisibleInLibrary) {
canShow = false
} else {
canHide = false
}
}
menu.findItem(R.id.action_show)?.isVisible = canShow
menu.findItem(R.id.action_hide)?.isVisible = canHide
mode.title = controller.count.toString() mode.title = controller.count.toString()
return true return true
} }
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean { override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.action_edit -> { /*R.id.action_view -> {
val id = controller.peekCheckedIds().singleOrNull() ?: return false val id = controller.peekCheckedIds().singleOrNull() ?: return false
val context = recyclerView.context val context = recyclerView.context
val intent = FavouritesCategoryEditActivity.newIntent(context, id) val category = viewModel.getCategory(id) ?: return false
val intent = FavouritesActivity.newIntent(context, category)
context.startActivity(intent) context.startActivity(intent)
mode.finish() mode.finish()
true true
}*/
R.id.action_show -> {
viewModel.setIsVisible(controller.snapshot(), true)
mode.finish()
true
}
R.id.action_hide -> {
viewModel.setIsVisible(controller.snapshot(), false)
mode.finish()
true
} }
R.id.action_remove -> { R.id.action_remove -> {

View File

@@ -20,9 +20,7 @@ import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
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.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
import org.koitharu.kotatsu.favourites.ui.FavouritesActivity
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoriesAdapter import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoriesAdapter
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
@@ -66,7 +64,7 @@ class FavouriteCategoriesActivity :
attachToRecyclerView(viewBinding.recyclerView) attachToRecyclerView(viewBinding.recyclerView)
} }
viewModel.detalizedCategories.observe(this, ::onCategoriesChanged) viewModel.categories.observe(this, ::onCategoriesChanged)
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null)) viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))
} }
@@ -80,9 +78,8 @@ class FavouriteCategoriesActivity :
if (selectionController.onItemClick(item.id)) { if (selectionController.onItemClick(item.id)) {
return return
} }
val intent = FavouritesActivity.newIntent(this, item) val intent = FavouritesCategoryEditActivity.newIntent(view.context, item.id)
val options = scaleUpActivityOptionsOf(view) startActivity(intent)
startActivity(intent, options)
} }
override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean { override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean {

View File

@@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.requireValue import org.koitharu.kotatsu.core.util.ext.requireValue
@@ -27,7 +28,7 @@ class FavouritesCategoriesViewModel @Inject constructor(
private var reorderJob: Job? = null private var reorderJob: Job? = null
val detalizedCategories = repository.observeCategoriesWithCovers() val categories = repository.observeCategoriesWithCovers()
.map { list -> .map { list ->
list.map { (category, covers) -> list.map { (category, covers) ->
CategoryListModel( CategoryListModel(
@@ -47,14 +48,8 @@ class FavouritesCategoriesViewModel @Inject constructor(
} }
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState)) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
fun deleteCategory(id: Long) {
launchJob {
repository.removeCategory(id)
}
}
fun deleteCategories(ids: Set<Long>) { fun deleteCategories(ids: Set<Long>) {
launchJob { launchJob(Dispatchers.Default) {
repository.removeCategories(ids) repository.removeCategories(ids)
} }
} }
@@ -63,13 +58,13 @@ class FavouritesCategoriesViewModel @Inject constructor(
settings.isAllFavouritesVisible = isVisible settings.isAllFavouritesVisible = isVisible
} }
fun isEmpty(): Boolean = detalizedCategories.value.none { it is CategoryListModel } fun isEmpty(): Boolean = categories.value.none { it is CategoryListModel }
fun reorderCategories(oldPos: Int, newPos: Int) { fun reorderCategories(oldPos: Int, newPos: Int) {
val prevJob = reorderJob val prevJob = reorderJob
reorderJob = launchJob(Dispatchers.Default) { reorderJob = launchJob(Dispatchers.Default) {
prevJob?.join() prevJob?.join()
val items = detalizedCategories.requireValue() val items = categories.requireValue()
val ids = items.mapNotNullTo(ArrayList(items.size)) { val ids = items.mapNotNullTo(ArrayList(items.size)) {
(it as? CategoryListModel)?.category?.id (it as? CategoryListModel)?.category?.id
} }
@@ -78,4 +73,19 @@ class FavouritesCategoriesViewModel @Inject constructor(
repository.reorderCategories(ids) repository.reorderCategories(ids)
} }
} }
fun setIsVisible(ids: Set<Long>, isVisible: Boolean) {
launchJob(Dispatchers.Default) {
for (id in ids) {
repository.updateCategory(id, isVisible)
}
}
}
fun getCategories(ids: Set<Long>): ArrayList<FavouriteCategory> {
val items = categories.requireValue()
return items.mapNotNullTo(ArrayList(ids.size)) { item ->
(item as? CategoryListModel)?.category?.takeIf { it.id in ids }
}
}
} }

View File

@@ -35,8 +35,8 @@ fun categoryAD(
{ inflater, parent -> ItemCategoryBinding.inflate(inflater, parent, false) }, { inflater, parent -> ItemCategoryBinding.inflate(inflater, parent, false) },
) { ) {
val eventListener = object : OnClickListener, OnLongClickListener, OnTouchListener { val eventListener = object : OnClickListener, OnLongClickListener, OnTouchListener {
override fun onClick(v: View) = clickListener.onItemClick(item.category, binding.imageViewCover1) override fun onClick(v: View) = clickListener.onItemClick(item.category, itemView)
override fun onLongClick(v: View) = clickListener.onItemLongClick(item.category, binding.imageViewCover1) override fun onLongClick(v: View) = clickListener.onItemLongClick(item.category, itemView)
override fun onTouch(v: View?, event: MotionEvent): Boolean = event.actionMasked == MotionEvent.ACTION_DOWN && override fun onTouch(v: View?, event: MotionEvent): Boolean = event.actionMasked == MotionEvent.ACTION_DOWN &&
clickListener.onDragHandleTouch(this@adapterDelegateViewBinding) clickListener.onDragHandleTouch(this@adapterDelegateViewBinding)
} }

View File

@@ -1,22 +1,38 @@
package org.koitharu.kotatsu.favourites.ui.container package org.koitharu.kotatsu.favourites.ui.container
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewStub
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import coil.ImageLoader
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.util.ActionModeListener import org.koitharu.kotatsu.core.ui.util.ActionModeListener
import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.setTabsEnabled
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
import org.koitharu.kotatsu.databinding.FragmentFavouritesContainerBinding import org.koitharu.kotatsu.databinding.FragmentFavouritesContainerBinding
import org.koitharu.kotatsu.databinding.ItemEmptyStateBinding
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBinding>(), ActionModeListener { class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBinding>(), ActionModeListener,
ViewStub.OnInflateListener, View.OnClickListener {
@Inject
lateinit var coil: ImageLoader
private val viewModel: FavouritesContainerViewModel by viewModels() private val viewModel: FavouritesContainerViewModel by viewModels()
@@ -31,8 +47,10 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
binding.pager.adapter = adapter binding.pager.adapter = adapter
binding.tabs.setupWithViewPager(binding.pager) binding.tabs.setupWithViewPager(binding.pager)
binding.pager.offscreenPageLimit = 1 binding.pager.offscreenPageLimit = 1
binding.stubEmpty.setOnInflateListener(this)
actionModeDelegate.addListener(this) actionModeDelegate.addListener(this)
viewModel.categories.observe(viewLifecycleOwner, adapter) viewModel.categories.observe(viewLifecycleOwner, adapter)
viewModel.isEmpty.observe(viewLifecycleOwner, ::onEmptyStateChanged)
addMenuProvider(FavouritesContainerMenuProvider(binding.root.context)) addMenuProvider(FavouritesContainerMenuProvider(binding.root.context))
} }
@@ -48,19 +66,42 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
) )
} }
@SuppressLint("ClickableViewAccessibility")
override fun onActionModeStarted(mode: ActionMode) { override fun onActionModeStarted(mode: ActionMode) {
viewBinding?.run { viewBinding?.run {
pager.isUserInputEnabled = false pager.isUserInputEnabled = false
tabs.isEnabled = false tabs.setTabsEnabled(false)
} }
} }
@SuppressLint("ClickableViewAccessibility")
override fun onActionModeFinished(mode: ActionMode) { override fun onActionModeFinished(mode: ActionMode) {
viewBinding?.run { viewBinding?.run {
pager.isUserInputEnabled = true pager.isUserInputEnabled = true
tabs.isEnabled = true tabs.setTabsEnabled(true)
}
}
override fun onInflate(stub: ViewStub?, inflated: View) {
val stubBinding = ItemEmptyStateBinding.bind(inflated)
stubBinding.icon.newImageRequest(viewLifecycleOwner, R.drawable.ic_empty_favourites)?.enqueueWith(coil)
stubBinding.textPrimary.setText(R.string.text_empty_holder_primary)
stubBinding.textSecondary.setTextAndVisible(R.string.empty_favourite_categories)
stubBinding.buttonRetry.setTextAndVisible(R.string.manage)
stubBinding.buttonRetry.setOnClickListener(this)
}
override fun onClick(v: View) {
when (v.id) {
R.id.button_retry -> startActivity(
FavouriteCategoriesActivity.newIntent(v.context),
)
}
}
private fun onEmptyStateChanged(isEmpty: Boolean) {
viewBinding?.run {
pager.isGone = isEmpty
tabs.isGone = isEmpty
stubEmpty.isVisible = isEmpty
} }
} }
} }

View File

@@ -5,6 +5,8 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
@@ -17,8 +19,15 @@ class FavouritesContainerViewModel @Inject constructor(
favouritesRepository: FavouritesRepository, favouritesRepository: FavouritesRepository,
) : BaseViewModel() { ) : BaseViewModel() {
val categories = favouritesRepository.observeCategoriesForLibrary() private val categoriesStateFlow = favouritesRepository.observeCategoriesForLibrary()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
val categories = categoriesStateFlow.filterNotNull()
.mapItems { FavouriteTabModel(it.id, it.title) } .mapItems { FavouriteTabModel(it.id, it.title) }
.distinctUntilChanged() .distinctUntilChanged()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList()) .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
val isEmpty = categoriesStateFlow.map {
it?.isEmpty() == true
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
} }

View File

@@ -16,8 +16,12 @@ class FilterAdapter(
) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer { ) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {
init { init {
delegatesManager.addDelegate(filterSortDelegate(listener)).addDelegate(filterTagDelegate(listener)) delegatesManager
.addDelegate(listHeaderAD(null)).addDelegate(loadingStateAD()).addDelegate(loadingFooterAD()) .addDelegate(ITEM_TYPE_SORT, filterSortDelegate(listener))
.addDelegate(ITEM_TYPE_TAG, filterTagDelegate(listener))
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD(listener))
.addDelegate(loadingStateAD())
.addDelegate(loadingFooterAD())
.addDelegate(filterErrorDelegate()) .addDelegate(filterErrorDelegate())
differ.addListListener(listListener) differ.addListListener(listListener)
} }
@@ -32,4 +36,11 @@ class FilterAdapter(
} }
return null return null
} }
companion object {
const val ITEM_TYPE_SORT = 0
const val ITEM_TYPE_TAG = 1
const val ITEM_TYPE_HEADER = 2
}
} }

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.filter.ui package org.koitharu.kotatsu.filter.ui
import android.view.View
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import dagger.hilt.android.ViewModelLifecycle import dagger.hilt.android.ViewModelLifecycle
@@ -95,6 +96,10 @@ class FilterCoordinator @Inject constructor(
} }
} }
override fun onListHeaderClick(item: ListHeader, view: View) {
reset()
}
fun observeAvailableTags(): Flow<Set<MangaTag>?> = flow { fun observeAvailableTags(): Flow<Set<MangaTag>?> = flow {
if (!availableTagsDeferred.isCompleted) { if (!availableTagsDeferred.isCompleted) {
emit(emptySet()) emit(emptySet())
@@ -208,7 +213,7 @@ class FilterCoordinator @Inject constructor(
} }
} }
if (allTags.isLoading || allTags.isError || tags.isNotEmpty()) { if (allTags.isLoading || allTags.isError || tags.isNotEmpty()) {
list.add(ListHeader(R.string.genres, 0, null)) list.add(ListHeader(R.string.genres, if (state.tags.isEmpty()) 0 else R.string.reset, null))
tags.mapTo(list) { tags.mapTo(list) {
FilterItem.Tag(it, isChecked = it in state.tags) FilterItem.Tag(it, isChecked = it in state.tags)
} }

View File

@@ -0,0 +1,28 @@
package org.koitharu.kotatsu.filter.ui
import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.R
class FilterItemDecoration(
context: Context,
) : RecyclerView.ItemDecoration() {
private val spacing = context.resources.getDimensionPixelOffset(R.dimen.list_spacing)
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val itemType = parent.getChildViewHolder(view)?.itemViewType ?: -1
if (itemType == FilterAdapter.ITEM_TYPE_HEADER) {
outRect.set(spacing, 0, spacing, 0)
} else {
outRect.set(0, 0, 0, 0)
}
}
}

View File

@@ -32,6 +32,7 @@ class FilterSheetFragment :
val adapter = FilterAdapter(filter, this) val adapter = FilterAdapter(filter, this)
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
filter.filterItems.observe(viewLifecycleOwner, adapter) filter.filterItems.observe(viewLifecycleOwner, adapter)
binding.recyclerView.addItemDecoration(FilterItemDecoration(binding.root.context))
if (dialog == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (dialog == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
binding.recyclerView.scrollIndicators = 0 binding.recyclerView.scrollIndicators = 0

View File

@@ -1,8 +1,9 @@
package org.koitharu.kotatsu.filter.ui package org.koitharu.kotatsu.filter.ui
import org.koitharu.kotatsu.filter.ui.model.FilterItem import org.koitharu.kotatsu.filter.ui.model.FilterItem
import org.koitharu.kotatsu.list.ui.adapter.ListHeaderClickListener
interface OnFilterChangedListener { interface OnFilterChangedListener : ListHeaderClickListener {
fun onSortItemClick(item: FilterItem.Sort) fun onSortItemClick(item: FilterItem.Sort)

View File

@@ -309,7 +309,7 @@ abstract class MangaListFragment :
} }
override fun onSelectionChanged(controller: ListSelectionController, count: Int) { override fun onSelectionChanged(controller: ListSelectionController, count: Int) {
requireViewBinding().recyclerView.invalidateItemDecorations() viewBinding?.recyclerView?.invalidateItemDecorations()
} }
override fun onFastScrollStart(fastScroller: FastScroller) { override fun onFastScrollStart(fastScroller: FastScroller) {

View File

@@ -1,7 +1,8 @@
package org.koitharu.kotatsu.list.ui.adapter package org.koitharu.kotatsu.list.ui.adapter
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
import org.koitharu.kotatsu.databinding.ItemHeaderButtonBinding import org.koitharu.kotatsu.databinding.ItemHeaderButtonBinding
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.model.ListModel
@@ -19,6 +20,12 @@ fun listHeaderAD(
bind { bind {
binding.textViewTitle.text = item.getText(context) binding.textViewTitle.text = item.getText(context)
binding.buttonMore.setTextAndVisible(item.buttonTextRes) if (item.buttonTextRes == 0) {
binding.buttonMore.isInvisible = true
binding.buttonMore.text = null
} else {
binding.buttonMore.setText(item.buttonTextRes)
binding.buttonMore.isVisible = true
}
} }
} }

View File

@@ -5,6 +5,7 @@ import android.text.method.LinkMovementMethod
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
@@ -120,11 +121,11 @@ class PreviewFragment : BaseFragment<FragmentPreviewBinding>(), View.OnClickList
} }
private fun onDescriptionChanged(description: CharSequence?) { private fun onDescriptionChanged(description: CharSequence?) {
val tv = requireViewBinding().textViewDescription val tv = viewBinding?.textViewDescription ?: return
if (description.isNullOrBlank()) { when {
tv.setText(R.string.no_description) description == null -> tv.setText(R.string.loading_)
} else { description.isBlank() -> tv.setText(R.string.no_description)
tv.text = description else -> tv.setText(description, TextView.BufferType.NORMAL)
} }
} }

View File

@@ -12,6 +12,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
@@ -49,6 +50,8 @@ class PreviewViewModel @Inject constructor(
emit(description.parseAsHtml().filterSpans().sanitize()) emit(description.parseAsHtml().filterSpans().sanitize())
emit(description.parseAsHtml(imageGetter = imageGetter).filterSpans()) emit(description.parseAsHtml(imageGetter = imageGetter).filterSpans())
} }
}.combine(isLoading) { desc, loading ->
if (loading) null else desc ?: ""
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(5000), null) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(5000), null)
val tagsChips = manga.map { val tagsChips = manga.map {

View File

@@ -62,7 +62,7 @@ class MangaListActivity :
finishAfterTransition() finishAfterTransition()
return return
} }
viewBinding.chipSort?.setOnClickListener(this) viewBinding.buttonOrder?.setOnClickListener(this)
title = if (source == MangaSource.LOCAL) getString(R.string.local_storage) else source.title title = if (source == MangaSource.LOCAL) getString(R.string.local_storage) else source.title
initList(source, tags) initList(source, tags)
} }
@@ -80,7 +80,7 @@ class MangaListActivity :
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.chip_sort -> FilterSheetFragment.show(supportFragmentManager) R.id.button_order -> FilterSheetFragment.show(supportFragmentManager)
} }
} }
@@ -127,7 +127,7 @@ class MangaListActivity :
} }
} }
val filter = filterOwner.filter val filter = filterOwner.filter
val chipSort = viewBinding.chipSort val chipSort = viewBinding.buttonOrder
if (chipSort != null) { if (chipSort != null) {
filter.header.observe(this) { filter.header.observe(this) {
chipSort.setTextAndVisible(it.sortOrder?.titleRes ?: 0) chipSort.setTextAndVisible(it.sortOrder?.titleRes ?: 0)

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M2,5.27L3.28,4L20,20.72L18.73,22L15.65,18.92C14.5,19.3 13.28,19.5 12,19.5C7,19.5 2.73,16.39 1,12C1.69,10.24 2.79,8.69 4.19,7.46L2,5.27M12,9A3,3 0 0,1 15,12C15,12.35 14.94,12.69 14.83,13L11,9.17C11.31,9.06 11.65,9 12,9M12,4.5C17,4.5 21.27,7.61 23,12C22.18,14.08 20.79,15.88 19,17.19L17.58,15.76C18.94,14.82 20.06,13.54 20.82,12C19.17,8.64 15.76,6.5 12,6.5C10.91,6.5 9.84,6.68 8.84,7L7.3,5.47C8.74,4.85 10.33,4.5 12,4.5M3.18,12C4.83,15.36 8.24,17.5 12,17.5C12.69,17.5 13.37,17.43 14,17.29L11.72,15C10.29,14.85 9.15,13.71 9,12.28L5.6,8.87C4.61,9.72 3.78,10.78 3.18,12Z" />
</vector>

View File

@@ -2,6 +2,7 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@@ -50,7 +51,8 @@
android:paddingVertical="@dimen/list_spacing" android:paddingVertical="@dimen/list_spacing"
android:scrollbars="vertical" android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:listitem="@layout/item_category" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/fab_add" android:id="@+id/fab_add"

View File

@@ -29,19 +29,14 @@
app:layout_collapseMode="parallax" app:layout_collapseMode="parallax"
tools:ignore="RtlSymmetry"> tools:ignore="RtlSymmetry">
<com.google.android.material.chip.Chip <com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Chip.Assist" android:id="@+id/button_order"
android:id="@+id/chip_sort" style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone" android:visibility="invisible"
app:chipBackgroundColor="?attr/colorSurfaceContainerHigh" app:icon="@drawable/ic_reorder"
app:chipIcon="@drawable/ic_sort" tools:text="@string/manage"
app:chipIconEnabled="true"
app:closeIcon="@drawable/ic_expand_more"
app:closeIconEnabled="true"
app:layout_collapseMode="pin"
tools:text="@string/popular"
tools:visibility="visible" /> tools:visibility="visible" />
</LinearLayout> </LinearLayout>

View File

@@ -2,6 +2,7 @@
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
@@ -16,6 +17,14 @@
<org.koitharu.kotatsu.core.ui.widgets.EnhancedViewPager <org.koitharu.kotatsu.core.ui.widgets.EnhancedViewPager
android:id="@+id/pager" android:id="@+id/pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
tools:visibility="gone" />
<ViewStub
android:id="@+id/stub_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/item_empty_state"
tools:visibility="visible" />
</LinearLayout> </LinearLayout>

View File

@@ -124,7 +124,6 @@
android:id="@+id/imageView_handle" android:id="@+id/imageView_handle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/reorder" android:contentDescription="@string/reorder"
android:padding="@dimen/margin_normal" android:padding="@dimen/margin_normal"
android:src="@drawable/ic_reorder_handle" android:src="@drawable/ic_reorder_handle"

View File

@@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:baselineAligned="true"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
@@ -22,6 +23,8 @@
style="@style/Widget.Kotatsu.Button.More" style="@style/Widget.Kotatsu.Button.More"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/manage" /> android:visibility="invisible"
tools:text="@string/manage"
tools:visibility="visible" />
</LinearLayout> </LinearLayout>

View File

@@ -4,9 +4,15 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/action_edit" android:id="@+id/action_show"
android:icon="@drawable/ic_edit" android:icon="@drawable/ic_eye"
android:title="@string/edit" android:title="@string/show"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_hide"
android:icon="@drawable/ic_eye_off"
android:title="@string/hide"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item
@@ -15,4 +21,4 @@
android:title="@string/remove" android:title="@string/remove"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
</menu> </menu>

View File

@@ -5,7 +5,7 @@
<item <item
android:id="@+id/action_manage" android:id="@+id/action_manage"
android:orderInCategory="48" android:orderInCategory="48"
android:title="@string/manage_favourites" android:title="@string/manage_categories"
android:titleCondensed="@string/manage" /> android:titleCondensed="@string/manage" />
</menu> </menu>

View File

@@ -460,10 +460,12 @@
<string name="background">Background</string> <string name="background">Background</string>
<string name="data_not_restored">Data was not restored</string> <string name="data_not_restored">Data was not restored</string>
<string name="data_not_restored_text">Make sure you have selected the correct backup file</string> <string name="data_not_restored_text">Make sure you have selected the correct backup file</string>
<string name="manage_favourites">Manage favourites</string> <string name="manage_categories">Manage categories</string>
<string name="suggestions_wifi_only_summary">Do not update suggestions using metered network connections</string> <string name="suggestions_wifi_only_summary">Do not update suggestions using metered network connections</string>
<string name="tracker_wifi_only_summary">Do not check for new chapters using metered network connections</string> <string name="tracker_wifi_only_summary">Do not check for new chapters using metered network connections</string>
<string name="search_hint">Enter manga title, genre or source name</string> <string name="search_hint">Enter manga title, genre or source name</string>
<string name="progress">Progress</string> <string name="progress">Progress</string>
<string name="order_added">Added</string> <string name="order_added">Added</string>
<string name="view_list">View list</string>
<string name="show">Show</string>
</resources> </resources>