Add action for empty list state
This commit is contained in:
@@ -36,13 +36,14 @@ class FavouritesListViewModel(
|
||||
when {
|
||||
list.isEmpty() -> listOf(
|
||||
EmptyState(
|
||||
R.drawable.ic_heart_outline,
|
||||
R.string.text_empty_holder_primary,
|
||||
if (categoryId == 0L) {
|
||||
icon = R.drawable.ic_heart_outline,
|
||||
textPrimary = R.string.text_empty_holder_primary,
|
||||
textSecondary = if (categoryId == 0L) {
|
||||
R.string.you_have_not_favourites_yet
|
||||
} else {
|
||||
R.string.favourites_category_empty
|
||||
}
|
||||
},
|
||||
actionStringRes = 0,
|
||||
)
|
||||
)
|
||||
else -> list.toUi(mode, this)
|
||||
|
||||
@@ -45,7 +45,14 @@ class HistoryListViewModel(
|
||||
createListModeFlow()
|
||||
) { list, grouped, mode ->
|
||||
when {
|
||||
list.isEmpty() -> listOf(EmptyState(R.drawable.ic_history, R.string.text_history_holder_primary, R.string.text_history_holder_secondary))
|
||||
list.isEmpty() -> listOf(
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_history,
|
||||
textPrimary = R.string.text_history_holder_primary,
|
||||
textSecondary = R.string.text_history_holder_secondary,
|
||||
actionStringRes = 0,
|
||||
)
|
||||
)
|
||||
else -> mapList(list, grouped, mode)
|
||||
}
|
||||
}.onFirst {
|
||||
|
||||
@@ -15,17 +15,18 @@ import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
|
||||
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.databinding.FragmentListBinding
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.main.ui.AppBarOwner
|
||||
import org.koitharu.kotatsu.main.ui.MainActivity
|
||||
@@ -33,7 +34,7 @@ import org.koitharu.kotatsu.utils.RecycledViewPoolHolder
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
|
||||
abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
PaginationScrollListener.Callback, OnListItemClickListener<Manga>,
|
||||
PaginationScrollListener.Callback, MangaListListener,
|
||||
SwipeRefreshLayout.OnRefreshListener {
|
||||
|
||||
private var listAdapter: MangaListAdapter? = null
|
||||
@@ -62,10 +63,7 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
listAdapter = MangaListAdapter(
|
||||
coil = get(),
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
clickListener = this,
|
||||
onRetryClick = ::resolveException,
|
||||
onTagRemoveClick = viewModel::onRemoveFilterTag,
|
||||
onFilterClickListener = this::onFilterClick,
|
||||
listener = this,
|
||||
)
|
||||
paginationListener = PaginationScrollListener(4, this)
|
||||
with(binding.recyclerView) {
|
||||
@@ -192,7 +190,17 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onFilterClick() = Unit
|
||||
override fun onFilterClick() = Unit
|
||||
|
||||
override fun onEmptyActionClick() = Unit
|
||||
|
||||
override fun onRetryClick(error: Throwable) {
|
||||
resolveException(error)
|
||||
}
|
||||
|
||||
override fun onTagRemoveClick(tag: MangaTag) {
|
||||
viewModel.onRemoveFilterTag(tag)
|
||||
}
|
||||
|
||||
private fun onGridScaleChanged(scale: Float) {
|
||||
spanSizeLookup.invalidateCache()
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
import org.koitharu.kotatsu.databinding.ItemCurrentFilterBinding
|
||||
import org.koitharu.kotatsu.list.ui.model.CurrentFilterModel
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
|
||||
fun currentFilterAD(
|
||||
onTagRemoveClick: (MangaTag) -> Unit,
|
||||
) = adapterDelegateViewBinding<CurrentFilterModel, ListModel, ItemCurrentFilterBinding>(
|
||||
{ inflater, parent -> ItemCurrentFilterBinding.inflate(inflater, parent, false) }
|
||||
) {
|
||||
listener: MangaListListener,
|
||||
) = adapterDelegate<CurrentFilterModel, ListModel>(R.layout.item_current_filter) {
|
||||
|
||||
binding.chipsTags.onChipCloseClickListener = ChipsView.OnChipCloseClickListener { chip, data ->
|
||||
onTagRemoveClick(data as? MangaTag ?: return@OnChipCloseClickListener)
|
||||
val chipGroup = itemView as ChipsView
|
||||
|
||||
chipGroup.onChipCloseClickListener = ChipsView.OnChipCloseClickListener { chip, data ->
|
||||
listener.onTagRemoveClick(data as? MangaTag ?: return@OnChipCloseClickListener)
|
||||
}
|
||||
|
||||
bind {
|
||||
binding.chipsTags.setChips(item.chips)
|
||||
chipGroup.setChips(item.chips)
|
||||
}
|
||||
}
|
||||
@@ -4,20 +4,20 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemEmptyStateBinding
|
||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.utils.ext.setTextAndVisible
|
||||
|
||||
fun emptyStateListAD() = adapterDelegateViewBinding<EmptyState, ListModel, ItemEmptyStateBinding>(
|
||||
fun emptyStateListAD(
|
||||
listener: MangaListListener,
|
||||
) = adapterDelegateViewBinding<EmptyState, ListModel, ItemEmptyStateBinding>(
|
||||
{ inflater, parent -> ItemEmptyStateBinding.inflate(inflater, parent, false) }
|
||||
) {
|
||||
|
||||
binding.buttonRetry.setOnClickListener { listener.onEmptyActionClick() }
|
||||
|
||||
bind {
|
||||
with(binding.icon) {
|
||||
setImageResource(item.icon)
|
||||
}
|
||||
with(binding.textPrimary) {
|
||||
setText(item.textPrimary)
|
||||
}
|
||||
with(binding.textSecondary) {
|
||||
setText(item.textSecondary)
|
||||
}
|
||||
binding.icon.setImageResource(item.icon)
|
||||
binding.textPrimary.setText(item.textPrimary)
|
||||
binding.textSecondary.setTextAndVisible(item.textSecondary)
|
||||
binding.buttonRetry.setTextAndVisible(item.actionStringRes)
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,13 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
|
||||
fun errorFooterAD(
|
||||
onRetryClick: (Throwable) -> Unit
|
||||
listener: MangaListListener,
|
||||
) = adapterDelegateViewBinding<ErrorFooter, ListModel, ItemErrorFooterBinding>(
|
||||
{ inflater, parent -> ItemErrorFooterBinding.inflate(inflater, parent, false) }
|
||||
) {
|
||||
|
||||
binding.root.setOnClickListener {
|
||||
onRetryClick(item.exception)
|
||||
listener.onRetryClick(item.exception)
|
||||
}
|
||||
|
||||
bind {
|
||||
|
||||
@@ -8,13 +8,13 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
|
||||
fun errorStateListAD(
|
||||
onRetryClick: (Throwable) -> Unit
|
||||
listener: MangaListListener,
|
||||
) = adapterDelegateViewBinding<ErrorState, ListModel, ItemErrorStateBinding>(
|
||||
{ inflater, parent -> ItemErrorStateBinding.inflate(inflater, parent, false) }
|
||||
) {
|
||||
|
||||
binding.buttonRetry.setOnClickListener {
|
||||
onRetryClick(item.exception)
|
||||
listener.onRetryClick(item.exception)
|
||||
}
|
||||
|
||||
bind {
|
||||
|
||||
@@ -24,14 +24,14 @@ fun listHeaderAD() = adapterDelegate<ListHeader, ListModel>(
|
||||
}
|
||||
|
||||
fun listHeaderWithFilterAD(
|
||||
onFilterClickListener: () -> Unit,
|
||||
listener: MangaListListener,
|
||||
) = adapterDelegateViewBinding<ListHeader, ListModel, ItemHeaderWithFilterBinding>(
|
||||
viewBinding = { inflater, parent -> ItemHeaderWithFilterBinding.inflate(inflater, parent, false) },
|
||||
on = { item, _, _ -> item is ListHeader && item.sortOrder != null },
|
||||
) {
|
||||
|
||||
binding.textViewFilter.setOnClickListener {
|
||||
onFilterClickListener()
|
||||
listener.onFilterClick()
|
||||
}
|
||||
|
||||
bind {
|
||||
|
||||
@@ -4,9 +4,6 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
|
||||
@@ -17,32 +14,29 @@ import kotlin.jvm.internal.Intrinsics
|
||||
class MangaListAdapter(
|
||||
coil: ImageLoader,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
clickListener: OnListItemClickListener<Manga>,
|
||||
onRetryClick: (Throwable) -> Unit,
|
||||
onTagRemoveClick: (MangaTag) -> Unit,
|
||||
onFilterClickListener: () -> Unit,
|
||||
listener: MangaListListener,
|
||||
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
|
||||
|
||||
init {
|
||||
delegatesManager
|
||||
.addDelegate(
|
||||
ITEM_TYPE_MANGA_LIST,
|
||||
mangaListItemAD(coil, lifecycleOwner, clickListener)
|
||||
mangaListItemAD(coil, lifecycleOwner, listener)
|
||||
)
|
||||
.addDelegate(
|
||||
ITEM_TYPE_MANGA_LIST_DETAILED,
|
||||
mangaListDetailedItemAD(coil, lifecycleOwner, clickListener)
|
||||
mangaListDetailedItemAD(coil, lifecycleOwner, listener)
|
||||
)
|
||||
.addDelegate(ITEM_TYPE_MANGA_GRID, mangaGridItemAD(coil, lifecycleOwner, clickListener))
|
||||
.addDelegate(ITEM_TYPE_MANGA_GRID, mangaGridItemAD(coil, lifecycleOwner, listener))
|
||||
.addDelegate(ITEM_TYPE_LOADING_FOOTER, loadingFooterAD())
|
||||
.addDelegate(ITEM_TYPE_LOADING_STATE, loadingStateAD())
|
||||
.addDelegate(ITEM_TYPE_DATE, relatedDateItemAD())
|
||||
.addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD(onRetryClick))
|
||||
.addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD(onRetryClick))
|
||||
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD())
|
||||
.addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD(listener))
|
||||
.addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD(listener))
|
||||
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(listener))
|
||||
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD())
|
||||
.addDelegate(ITEM_TYPE_FILTER, currentFilterAD(onTagRemoveClick))
|
||||
.addDelegate(ITEM_TYPE_HEADER_FILTER, listHeaderWithFilterAD(onFilterClickListener))
|
||||
.addDelegate(ITEM_TYPE_FILTER, currentFilterAD(listener))
|
||||
.addDelegate(ITEM_TYPE_HEADER_FILTER, listHeaderWithFilterAD(listener))
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
|
||||
interface MangaListListener : OnListItemClickListener<Manga> {
|
||||
|
||||
fun onRetryClick(error: Throwable)
|
||||
fun onTagRemoveClick(tag: MangaTag)
|
||||
fun onFilterClick()
|
||||
fun onEmptyActionClick()
|
||||
}
|
||||
@@ -24,10 +24,15 @@ class FilterBottomSheet : BaseBottomSheet<SheetFilterBinding>() {
|
||||
) {
|
||||
parametersOf(
|
||||
requireArguments().getParcelable<MangaSource>(ARG_SOURCE),
|
||||
requireArguments().getParcelable<FilterState>(ARG_STATE),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val state = requireArguments().getParcelable<FilterState>(ARG_STATE)
|
||||
viewModel.updateState(state)
|
||||
}
|
||||
|
||||
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetFilterBinding {
|
||||
return SheetFilterBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
@@ -11,28 +11,24 @@ import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
import org.koitharu.kotatsu.core.model.SortOrder
|
||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.utils.ext.replaceWith
|
||||
import java.util.*
|
||||
|
||||
class FilterViewModel(
|
||||
private val repository: RemoteMangaRepository,
|
||||
dataRepository: MangaDataRepository,
|
||||
state: FilterState,
|
||||
) : BaseViewModel(), OnFilterChangedListener {
|
||||
|
||||
val filter = MutableLiveData<List<FilterItem>>()
|
||||
val result = MutableLiveData<FilterState>()
|
||||
private var job: Job? = null
|
||||
private var selectedSortOrder: SortOrder? = state.sortOrder
|
||||
private val selectedTags = HashSet(state.tags)
|
||||
private var selectedSortOrder: SortOrder? = repository.sortOrders.firstOrNull()
|
||||
private val selectedTags = HashSet<MangaTag>()
|
||||
private val localTagsDeferred = viewModelScope.async(Dispatchers.Default) {
|
||||
dataRepository.findTags(repository.source)
|
||||
}
|
||||
private var availableTagsDeferred = loadTagsAsync()
|
||||
|
||||
init {
|
||||
showFilter()
|
||||
}
|
||||
|
||||
override fun onSortItemClick(item: FilterItem.Sort) {
|
||||
selectedSortOrder = item.order
|
||||
updateFilters()
|
||||
@@ -49,6 +45,18 @@ class FilterViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateState(state: FilterState?) {
|
||||
if (state != null) {
|
||||
selectedSortOrder = state.sortOrder
|
||||
selectedTags.replaceWith(state.tags)
|
||||
}
|
||||
if (job == null) {
|
||||
showFilter()
|
||||
} else {
|
||||
updateFilters()
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
private fun updateFilters() {
|
||||
val previousJob = job
|
||||
|
||||
@@ -6,5 +6,6 @@ import androidx.annotation.StringRes
|
||||
data class EmptyState(
|
||||
@DrawableRes val icon: Int,
|
||||
@StringRes val textPrimary: Int,
|
||||
@StringRes val textSecondary: Int
|
||||
@StringRes val textSecondary: Int,
|
||||
@StringRes val actionStringRes: Int,
|
||||
) : ListModel
|
||||
@@ -62,6 +62,21 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback<List<@JvmS
|
||||
|
||||
override fun onScrolledToEnd() = Unit
|
||||
|
||||
override fun onEmptyActionClick() {
|
||||
try {
|
||||
importCall.launch(arrayOf("*/*"))
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
Snackbar.make(
|
||||
binding.recyclerView,
|
||||
R.string.operation_not_supported,
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.opt_local, menu)
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
@@ -70,18 +85,7 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback<List<@JvmS
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_import -> {
|
||||
try {
|
||||
importCall.launch(arrayOf("*/*"))
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
Snackbar.make(
|
||||
binding.recyclerView,
|
||||
R.string.operation_not_supported,
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
onEmptyActionClick()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
||||
@@ -45,9 +45,10 @@ class LocalListViewModel(
|
||||
list == null -> listOf(LoadingState)
|
||||
list.isEmpty() -> listOf(
|
||||
EmptyState(
|
||||
R.drawable.ic_storage,
|
||||
R.string.text_local_holder_primary,
|
||||
R.string.text_local_holder_secondary
|
||||
icon = R.drawable.ic_storage,
|
||||
textPrimary = R.string.text_local_holder_primary,
|
||||
textSecondary = R.string.text_local_holder_secondary,
|
||||
actionStringRes = R.string._import,
|
||||
)
|
||||
)
|
||||
else -> ArrayList<ListModel>(list.size + 1).apply {
|
||||
|
||||
@@ -23,7 +23,6 @@ val remoteListModule
|
||||
FilterViewModel(
|
||||
repository = get<MangaRepository>(named(params.get<MangaSource>())) as RemoteMangaRepository,
|
||||
dataRepository = get(),
|
||||
state = params.get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||
import org.koitharu.kotatsu.list.ui.filter.FilterBottomSheet
|
||||
import org.koitharu.kotatsu.list.ui.filter.FilterState
|
||||
import org.koitharu.kotatsu.reader.ui.SimpleSettingsActivity
|
||||
import org.koitharu.kotatsu.utils.ext.parcelableArgument
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
@@ -65,6 +66,10 @@ class RemoteListFragment : MangaListFragment(), FragmentResultListener {
|
||||
FilterBottomSheet.show(childFragmentManager, source, viewModel.filter)
|
||||
}
|
||||
|
||||
override fun onEmptyActionClick() {
|
||||
viewModel.applyFilter(FilterState(viewModel.filter.sortOrder, emptySet()))
|
||||
}
|
||||
|
||||
override fun onFragmentResult(requestKey: String, result: Bundle) {
|
||||
when (requestKey) {
|
||||
FilterBottomSheet.REQUEST_KEY -> viewModel.applyFilter(
|
||||
|
||||
@@ -42,7 +42,7 @@ class RemoteListViewModel(
|
||||
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))
|
||||
list.isEmpty() -> createEmptyState()
|
||||
else -> {
|
||||
val result = ArrayList<ListModel>(list.size + 3)
|
||||
result += header
|
||||
@@ -126,4 +126,13 @@ class RemoteListViewModel(
|
||||
CurrentFilterModel(tags.map { ChipsView.ChipModel(0, it.title, it) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEmptyState() = listOf(
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_book_cross,
|
||||
textPrimary = R.string.nothing_found,
|
||||
textSecondary = 0,
|
||||
actionStringRes = if (filter.tags.isEmpty()) 0 else R.string.reset_filter,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,14 @@ class SearchViewModel(
|
||||
when {
|
||||
list.isNullOrEmpty() && error != null -> listOf(error.toErrorState(canRetry = true))
|
||||
list == null -> listOf(LoadingState)
|
||||
list.isEmpty() -> listOf(EmptyState(R.drawable.ic_book_search, R.string.nothing_found, R.string.text_search_holder_secondary))
|
||||
list.isEmpty() -> listOf(
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_book_search,
|
||||
textPrimary = R.string.nothing_found,
|
||||
textSecondary = R.string.text_search_holder_secondary,
|
||||
actionStringRes = 0,
|
||||
)
|
||||
)
|
||||
else -> {
|
||||
val result = ArrayList<ListModel>(list.size + 1)
|
||||
list.toUi(result, mode)
|
||||
|
||||
@@ -13,7 +13,6 @@ import org.koitharu.kotatsu.list.ui.model.*
|
||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.onFirst
|
||||
import java.util.*
|
||||
|
||||
class GlobalSearchViewModel(
|
||||
private val query: String,
|
||||
@@ -35,7 +34,14 @@ class GlobalSearchViewModel(
|
||||
when {
|
||||
list.isNullOrEmpty() && error != null -> listOf(error.toErrorState(canRetry = true))
|
||||
list == null -> listOf(LoadingState)
|
||||
list.isEmpty() -> listOf(EmptyState(R.drawable.ic_book_search, R.string.nothing_found, R.string.text_search_holder_secondary))
|
||||
list.isEmpty() -> listOf(
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_book_search,
|
||||
textPrimary = R.string.nothing_found,
|
||||
textSecondary = R.string.text_search_holder_secondary,
|
||||
actionStringRes = 0,
|
||||
)
|
||||
)
|
||||
else -> {
|
||||
val result = ArrayList<ListModel>(list.size + 1)
|
||||
list.toUi(result, mode)
|
||||
|
||||
@@ -24,11 +24,14 @@ class SuggestionsViewModel(
|
||||
createListModeFlow()
|
||||
) { list, mode ->
|
||||
when {
|
||||
list.isEmpty() -> listOf(EmptyState(
|
||||
icon = R.drawable.ic_book_cross,
|
||||
textPrimary = R.string.nothing_found,
|
||||
textSecondary = R.string.text_suggestion_holder,
|
||||
))
|
||||
list.isEmpty() -> listOf(
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_book_cross,
|
||||
textPrimary = R.string.nothing_found,
|
||||
textSecondary = R.string.text_suggestion_holder,
|
||||
actionStringRes = 0,
|
||||
)
|
||||
)
|
||||
else -> buildList<ListModel>(list.size + 1) {
|
||||
add(headerModel)
|
||||
list.toUi(this, mode)
|
||||
|
||||
@@ -10,12 +10,13 @@ import org.koin.android.ext.android.get
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaTag
|
||||
import org.koitharu.kotatsu.databinding.FragmentFeedBinding
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.main.ui.AppBarOwner
|
||||
import org.koitharu.kotatsu.tracker.ui.adapter.FeedAdapter
|
||||
@@ -25,7 +26,7 @@ import org.koitharu.kotatsu.utils.ext.measureHeight
|
||||
import org.koitharu.kotatsu.utils.progress.Progress
|
||||
|
||||
class FeedFragment : BaseFragment<FragmentFeedBinding>(), PaginationScrollListener.Callback,
|
||||
OnListItemClickListener<Manga> {
|
||||
MangaListListener {
|
||||
|
||||
private val viewModel by viewModel<FeedViewModel>()
|
||||
|
||||
@@ -114,6 +115,14 @@ class FeedFragment : BaseFragment<FragmentFeedBinding>(), PaginationScrollListen
|
||||
)
|
||||
}
|
||||
|
||||
override fun onRetryClick(error: Throwable) = Unit
|
||||
|
||||
override fun onTagRemoveClick(tag: MangaTag) = Unit
|
||||
|
||||
override fun onFilterClick() = Unit
|
||||
|
||||
override fun onEmptyActionClick() = Unit
|
||||
|
||||
private fun onListChanged(list: List<ListModel>) {
|
||||
feedAdapter?.items = list
|
||||
}
|
||||
|
||||
@@ -37,7 +37,14 @@ class FeedViewModel(
|
||||
hasNextPage
|
||||
) { list, isHasNextPage ->
|
||||
when {
|
||||
list.isEmpty() -> listOf(EmptyState(R.drawable.ic_feed, R.string.text_empty_holder_primary, R.string.text_feed_holder))
|
||||
list.isEmpty() -> listOf(
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_feed,
|
||||
textPrimary = R.string.text_empty_holder_primary,
|
||||
textSecondary = R.string.text_feed_holder,
|
||||
actionStringRes = 0,
|
||||
)
|
||||
)
|
||||
isHasNextPage -> list + LoadingFooter
|
||||
else -> list
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.list.ui.adapter.*
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
||||
@@ -15,17 +13,17 @@ import kotlin.jvm.internal.Intrinsics
|
||||
class FeedAdapter(
|
||||
coil: ImageLoader,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
clickListener: OnListItemClickListener<Manga>
|
||||
listener: MangaListListener,
|
||||
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
|
||||
|
||||
init {
|
||||
delegatesManager
|
||||
.addDelegate(ITEM_TYPE_FEED, feedItemAD(coil, lifecycleOwner, clickListener))
|
||||
.addDelegate(ITEM_TYPE_FEED, feedItemAD(coil, lifecycleOwner, listener))
|
||||
.addDelegate(ITEM_TYPE_LOADING_FOOTER, loadingFooterAD())
|
||||
.addDelegate(ITEM_TYPE_LOADING_STATE, loadingStateAD())
|
||||
.addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD {})
|
||||
.addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD {})
|
||||
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD())
|
||||
.addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD(listener))
|
||||
.addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD(listener))
|
||||
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(listener))
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.utils.ext
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.isGone
|
||||
|
||||
var TextView.textAndVisible: CharSequence?
|
||||
@@ -17,4 +18,14 @@ var TextView.drawableStart: Drawable?
|
||||
set(value) {
|
||||
val dr = compoundDrawablesRelative
|
||||
setCompoundDrawablesRelativeWithIntrinsicBounds(value, dr[1], dr[2], dr[3])
|
||||
}
|
||||
}
|
||||
|
||||
fun TextView.setTextAndVisible(@StringRes textResId: Int) {
|
||||
if (textResId == 0) {
|
||||
text = null
|
||||
isGone = true
|
||||
} else {
|
||||
setText(textResId)
|
||||
isGone = text.isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<HorizontalScrollView
|
||||
<org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/chips_tags"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||
android:id="@+id/chips_tags"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:closeIconEnabled="true"
|
||||
app:singleLine="true" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
android:layout_height="wrap_content"
|
||||
app:closeIconEnabled="true" />
|
||||
@@ -34,4 +34,14 @@
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
tools:text="@tools:sample/lorem[15]" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_retry"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_normal"
|
||||
android:visibility="gone"
|
||||
tools:text="@string/try_again"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -264,4 +264,5 @@
|
||||
<string name="enabled">Enabled</string>
|
||||
<string name="disabled">Disabled</string>
|
||||
<string name="filter_load_error">Unable to load genres list</string>
|
||||
<string name="reset_filter">Reset filter</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user