diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt index 816110a09..d9f43632d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/PaginationScrollListener.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.base.ui.list +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView class PaginationScrollListener(offset: Int, private val callback: Callback) : @@ -10,7 +11,7 @@ class PaginationScrollListener(offset: Int, private val callback: Callback) : override fun onScrolledToStart(recyclerView: RecyclerView) = Unit override fun onScrolledToEnd(recyclerView: RecyclerView) { - val total = callback.getItemsCount() + val total = (recyclerView.layoutManager as? LinearLayoutManager)?.itemCount ?: return if (total > lastTotalCount) { lastTotalCount = total callback.onRequestMoreItems(total) @@ -27,6 +28,7 @@ class PaginationScrollListener(offset: Int, private val callback: Callback) : fun onRequestMoreItems(offset: Int) - fun getItemsCount(): Int + @Deprecated("Not in use") + fun getItemsCount(): Int = 0 } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 05485b754..9cc5460bb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -22,7 +22,7 @@ class AppSettings private constructor(private val prefs: SharedPreferences) : PreferenceManager.getDefaultSharedPreferences(context) ) - var listMode by IntEnumPreferenceDelegate( + var listMode by EnumPreferenceDelegate( ListMode::class.java, KEY_LIST_MODE, ListMode.DETAILED_LIST @@ -41,7 +41,7 @@ class AppSettings private constructor(private val prefs: SharedPreferences) : val isAmoledTheme by BoolPreferenceDelegate(KEY_THEME_AMOLED, defaultValue = false) - val gridSize by IntPreferenceDelegate(KEY_GRID_SIZE, defaultValue = 100) + var gridSize by IntPreferenceDelegate(KEY_GRID_SIZE, defaultValue = 100) val readerPageSwitch by StringSetPreferenceDelegate( KEY_READER_SWITCHERS, @@ -138,7 +138,7 @@ class AppSettings private constructor(private val prefs: SharedPreferences) : const val TRACK_HISTORY = "history" const val TRACK_FAVOURITES = "favourites" - const val KEY_LIST_MODE = "list_mode" + const val KEY_LIST_MODE = "list_mode_2" const val KEY_APP_SECTION = "app_section" const val KEY_THEME = "theme" const val KEY_THEME_AMOLED = "amoled_theme" diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt index 21ff731e6..e6daa5a75 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt @@ -4,6 +4,8 @@ import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ListMode @@ -12,6 +14,7 @@ import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.model.toGridModel import org.koitharu.kotatsu.list.ui.model.toListDetailedModel import org.koitharu.kotatsu.list.ui.model.toListModel +import org.koitharu.kotatsu.utils.ext.onFirst class FavouritesListViewModel( private val categoryId: Long, @@ -28,6 +31,12 @@ class FavouritesListViewModel( ListMode.DETAILED_LIST -> list.map { it.toListDetailedModel() } ListMode.GRID -> list.map { it.toGridModel() } } + }.onEach { + isEmptyState.postValue(it.isEmpty()) + }.onStart { + isLoading.postValue(true) + }.onFirst { + isLoading.postValue(false) }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) fun removeFromFavourites(manga: Manga) { 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 51172facd..9916acce8 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 @@ -6,6 +6,8 @@ import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ListMode @@ -16,6 +18,7 @@ import org.koitharu.kotatsu.list.ui.model.toListDetailedModel import org.koitharu.kotatsu.list.ui.model.toListModel import org.koitharu.kotatsu.utils.MangaShortcut import org.koitharu.kotatsu.utils.SingleLiveEvent +import org.koitharu.kotatsu.utils.ext.onFirst class HistoryListViewModel( private val repository: HistoryRepository, @@ -34,6 +37,12 @@ class HistoryListViewModel( ListMode.DETAILED_LIST -> list.map { it.toListDetailedModel() } ListMode.GRID -> list.map { it.toGridModel() } } + }.onEach { + isEmptyState.postValue(it.isEmpty()) + }.onStart { + isLoading.postValue(true) + }.onFirst { + isLoading.postValue(false) }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) fun clearHistory() { diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/ListModeSelectDialog.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/ListModeSelectDialog.kt index 9c204bda2..78491def3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/ListModeSelectDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/ListModeSelectDialog.kt @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.list.ui import android.os.Bundle import android.view.View +import android.widget.SeekBar import androidx.appcompat.app.AlertDialog import androidx.fragment.app.FragmentManager import kotlinx.android.synthetic.main.dialog_list_mode.* @@ -11,19 +12,23 @@ import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ListMode -class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), View.OnClickListener { +class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), View.OnClickListener, + SeekBar.OnSeekBarChangeListener { private val settings by inject() - private lateinit var mode: ListMode + private var mode: ListMode = ListMode.GRID + private var pendingGridSize: Int = 100 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mode = settings.listMode + pendingGridSize = settings.gridSize } override fun onBuildDialog(builder: AlertDialog.Builder) { builder.setTitle(R.string.list_mode) + .setPositiveButton(R.string.done, null) .setCancelable(true) } @@ -33,22 +38,33 @@ class ListModeSelectDialog : AlertDialogFragment(R.layout.dialog_list_mode), Vie button_list_detailed.isChecked = mode == ListMode.DETAILED_LIST button_grid.isChecked = mode == ListMode.GRID - button_ok.setOnClickListener(this) + with(seekbar_grid) { + progress = pendingGridSize - 50 + setOnSeekBarChangeListener(this@ListModeSelectDialog) + } + button_list.setOnClickListener(this) button_grid.setOnClickListener(this) button_list_detailed.setOnClickListener(this) } + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + pendingGridSize = progress + 50 + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit + + override fun onStopTrackingTouch(seekBar: SeekBar?) { + settings.gridSize = pendingGridSize + } + override fun onClick(v: View) { when (v.id) { - R.id.button_ok -> { - settings.listMode = mode - dismiss() - } R.id.button_list -> mode = ListMode.LIST R.id.button_list_detailed -> mode = ListMode.DETAILED_LIST R.id.button_grid -> mode = ListMode.GRID } + settings.listMode = mode } companion object { 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 9b16cb126..d1a64ccfa 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 @@ -32,8 +32,10 @@ import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter import org.koitharu.kotatsu.list.ui.filter.FilterAdapter import org.koitharu.kotatsu.list.ui.filter.OnFilterChangedListener -import org.koitharu.kotatsu.utils.UiUtils -import org.koitharu.kotatsu.utils.ext.* +import org.koitharu.kotatsu.utils.ext.clearItemDecorations +import org.koitharu.kotatsu.utils.ext.getDisplayMessage +import org.koitharu.kotatsu.utils.ext.hasItems +import org.koitharu.kotatsu.utils.ext.toggleDrawer abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), PaginationScrollListener.Callback, OnListItemClickListener, OnFilterChangedListener, @@ -41,7 +43,8 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), private var adapter: MangaListAdapter? = null private var paginationListener: PaginationScrollListener? = null - private val spanResolver: MangaListSpanResolver? = null + private val spanResolver = MangaListSpanResolver() + private val spanSizeLookup = SpanSizeLookup() protected var isSwipeRefreshEnabled = true protected abstract val viewModel: MangaListViewModel @@ -70,11 +73,13 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged) viewModel.listMode.observe(viewLifecycleOwner, ::onListModeChanged) viewModel.gridScale.observe(viewLifecycleOwner, ::onGridScaleChanged) + viewModel.isEmptyState.observe(viewLifecycleOwner, ::onEmptyStateChanged) } override fun onDestroyView() { adapter = null paginationListener = null + spanSizeLookup.invalidateCache() super.onDestroyView() } @@ -126,24 +131,15 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), } private fun onListChanged(list: List) { + spanSizeLookup.invalidateCache() adapter?.items = list - if (list.isEmpty()) { - setUpEmptyListHolder() - layout_holder.isVisible = true - } else { - layout_holder.isVisible = false - } - recyclerView.callOnScrollListeners() } private fun onError(e: Throwable) { if (e is CloudFlareProtectedException) { CloudFlareDialog.newInstance(e.url).show(childFragmentManager, CloudFlareDialog.TAG) } - if (recyclerView.hasItems) { - Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT) - .show() - } else { + if (viewModel.isEmptyState.value == true) { textView_holder.text = e.getDisplayMessage(resources) textView_holder.setCompoundDrawablesRelativeWithIntrinsicBounds( 0, @@ -152,21 +148,29 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), 0 ) layout_holder.isVisible = true + } else { + Snackbar.make(recyclerView, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT) + .show() } } @CallSuper protected open fun onLoadingStateChanged(isLoading: Boolean) { val hasItems = recyclerView.hasItems - progressBar.isVisible = isLoading && !hasItems + progressBar.isVisible = isLoading && !hasItems && viewModel.isEmptyState.value != true swipeRefreshLayout.isEnabled = isSwipeRefreshEnabled && !progressBar.isVisible - if (isLoading) { - layout_holder.isVisible = false - } else { + if (!isLoading) { swipeRefreshLayout.isRefreshing = false } } + private fun onEmptyStateChanged(isEmpty: Boolean) { + if (isEmpty) { + setUpEmptyListHolder() + } + layout_holder.isVisible = isEmpty + } + protected fun onInitFilter(config: MangaFilterConfig) { recyclerView_filter.adapter = FilterAdapter( sortOrders = config.sortOrders, @@ -198,12 +202,15 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), } private fun onGridScaleChanged(scale: Float) { - UiUtils.SpanCountResolver.update(recyclerView) + spanSizeLookup.invalidateCache() + spanResolver.setGridSize(scale, recyclerView) } private fun onListModeChanged(mode: ListMode) { + spanSizeLookup.invalidateCache() with(recyclerView) { clearItemDecorations() + removeOnLayoutChangeListener(spanResolver) when (mode) { ListMode.LIST -> { layoutManager = LinearLayoutManager(context) @@ -216,52 +223,27 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), } ListMode.DETAILED_LIST -> { layoutManager = LinearLayoutManager(context) - } - ListMode.GRID -> { - layoutManager = GridLayoutManager(context, 3) addItemDecoration( SpacingItemDecoration( resources.getDimensionPixelOffset(R.dimen.grid_spacing) ) ) } - } - } - } - - private fun initListMode(mode: ListMode) { - val ctx = context ?: return - recyclerView.layoutManager = null - recyclerView.clearItemDecorations() - recyclerView.removeOnLayoutChangeListener(UiUtils.SpanCountResolver) - recyclerView.layoutManager = when (mode) { - ListMode.GRID -> { - GridLayoutManager(ctx, UiUtils.resolveGridSpanCount(ctx)).apply { - spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { - override fun getSpanSize(position: Int) = if (position < getItemsCount()) - 1 else this@apply.spanCount + ListMode.GRID -> { + layoutManager = GridLayoutManager(context, spanResolver.spanCount).also { + it.spanSizeLookup = spanSizeLookup } + addItemDecoration( + SpacingItemDecoration( + resources.getDimensionPixelOffset(R.dimen.grid_spacing) + ) + ) + addOnLayoutChangeListener(spanResolver) } } - else -> LinearLayoutManager(ctx) } - recyclerView.addItemDecoration( - when (mode) { - ListMode.LIST -> DividerItemDecoration(ctx, RecyclerView.VERTICAL) - ListMode.DETAILED_LIST, - ListMode.GRID -> SpacingItemDecoration( - resources.getDimensionPixelOffset(R.dimen.grid_spacing) - ) - } - ) - if (mode == ListMode.GRID) { - recyclerView.addOnLayoutChangeListener(UiUtils.SpanCountResolver) - } - adapter?.notifyDataSetChanged() } - override fun getItemsCount() = adapter?.itemCount ?: 0 - final override fun isSection(position: Int): Boolean { return position == 0 || recyclerView_filter.adapter?.run { getItemViewType(position) != getItemViewType(position - 1) @@ -279,4 +261,25 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list), protected open fun onCreatePopupMenu(inflater: MenuInflater, menu: Menu, data: Manga) = Unit protected open fun onPopupMenuItemSelected(item: MenuItem, data: Manga) = false + + private inner class SpanSizeLookup : GridLayoutManager.SpanSizeLookup() { + + init { + isSpanIndexCacheEnabled = true + isSpanGroupIndexCacheEnabled = true + } + + override fun getSpanSize(position: Int): Int { + val total = (recyclerView.layoutManager as? GridLayoutManager)?.spanCount ?: return 1 + return when(adapter?.getItemViewType(position)) { + MangaListAdapter.ITEM_TYPE_PROGRESS -> total + else -> 1 + } + } + + fun invalidateCache() { + invalidateSpanGroupIndexCache() + invalidateSpanIndexCache() + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSpanResolver.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSpanResolver.kt index 0bd2beb43..1e58b2065 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSpanResolver.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListSpanResolver.kt @@ -1,26 +1,20 @@ package org.koitharu.kotatsu.list.ui -import android.content.Context import android.view.View import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter import kotlin.math.abs import kotlin.math.roundToInt -class MangaListSpanResolver( - context: Context, - private val adapter: MangaListAdapter -) : GridLayoutManager.SpanSizeLookup(), View.OnLayoutChangeListener { +class MangaListSpanResolver : View.OnLayoutChangeListener { - private val gridWidth = context.resources.getDimension(R.dimen.preferred_grid_width) + var spanCount = 3 + private set + + private var gridWidth = -1f private var cellWidth = -1f - override fun getSpanSize(position: Int) = when(adapter.getItemViewType(position)) { - else -> 1 - } - override fun onLayoutChange( v: View?, left: Int, @@ -36,20 +30,33 @@ class MangaListSpanResolver( return } val rv = v as? RecyclerView ?: return + if (gridWidth < 0f) { + gridWidth = rv.resources.getDimension(R.dimen.preferred_grid_width) + } val width = abs(right - left) if (width == 0) { return } - (rv.layoutManager as? GridLayoutManager)?.spanCount = resolveGridSpanCount(width) + resolveGridSpanCount(width) + (rv.layoutManager as? GridLayoutManager)?.spanCount = spanCount } - fun setGridSize(gridSize: Int) { - val scaleFactor = gridSize / 100f + fun setGridSize(scaleFactor: Float, rv: RecyclerView?) { + if (gridWidth < 0f) { + gridWidth = (rv ?: return).resources.getDimension(R.dimen.preferred_grid_width) + } cellWidth = gridWidth * scaleFactor + if (rv != null) { + val width = rv.width + if (width != 0) { + resolveGridSpanCount(width) + (rv.layoutManager as? GridLayoutManager)?.spanCount = spanCount + } + } } - private fun resolveGridSpanCount(width: Int): Int { + private fun resolveGridSpanCount(width: Int) { val estimatedCount = (width / cellWidth).roundToInt() - return estimatedCount.coerceAtLeast(2) + spanCount = estimatedCount.coerceAtLeast(2) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt index 9f8afb6a7..4b9b1603f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt @@ -15,11 +15,13 @@ abstract class MangaListViewModel( ) : BaseViewModel() { abstract val content: LiveData> + val isEmptyState = MutableLiveData(false) val filter = MutableLiveData() val listMode = MutableLiveData() val gridScale = settings.observe() .filter { it == AppSettings.KEY_GRID_SIZE } .map { settings.gridSize / 100f } + .onStart { emit(settings.gridSize / 100f) } .asLiveData(viewModelScope.coroutineContext + Dispatchers.IO) protected fun createListModeFlow() = settings.observe() 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 9bbdc6d16..33133d2c8 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 @@ -14,16 +14,16 @@ import kotlin.jvm.internal.Intrinsics class MangaListAdapter( coil: ImageLoader, clickListener: OnListItemClickListener -) : AsyncListDifferDelegationAdapter(DiffCallback) { +) : AsyncListDifferDelegationAdapter(DiffCallback()) { init { - delegatesManager.addDelegate(mangaListItemAD(coil, clickListener)) - .addDelegate(mangaListDetailedItemAD(coil, clickListener)) - .addDelegate(mangaGridItemAD(coil, clickListener)) - .addDelegate(indeterminateProgressAD()) + delegatesManager.addDelegate(ITEM_TYPE_MANGA_LIST, mangaListItemAD(coil, clickListener)) + .addDelegate(ITEM_TYPE_MANGA_LIST_DETAILED, mangaListDetailedItemAD(coil, clickListener)) + .addDelegate(ITEM_TYPE_MANGA_GRID, mangaGridItemAD(coil, clickListener)) + .addDelegate(ITEM_TYPE_PROGRESS, indeterminateProgressAD()) } - private companion object DiffCallback : DiffUtil.ItemCallback() { + private class DiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Any, newItem: Any) = when { oldItem is MangaListModel && newItem is MangaListModel -> { @@ -44,6 +44,13 @@ class MangaListAdapter( override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean { return Intrinsics.areEqual(oldItem, newItem) } + } + companion object { + + const val ITEM_TYPE_MANGA_LIST = 0 + const val ITEM_TYPE_MANGA_LIST_DETAILED = 1 + const val ITEM_TYPE_MANGA_GRID = 2 + const val ITEM_TYPE_PROGRESS = 3 } } \ 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 71afe9327..909e20a14 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 @@ -55,7 +55,7 @@ class LocalListViewModel( fun importFile(uri: Uri) { launchLoadingJob { val contentResolver = context.contentResolver - val list = withContext(Dispatchers.Default) { + withContext(Dispatchers.Default) { val name = MediaStoreCompat.getName(contentResolver, uri) ?: throw IOException("Cannot fetch name from uri: $uri") if (!LocalMangaRepository.isFileSupported(name)) { @@ -92,7 +92,9 @@ class LocalListViewModel( private fun loadList() { launchLoadingJob { withContext(Dispatchers.Default) { - mangaList.value = repository.getList(0) + val list = repository.getList(0) + mangaList.value = list + isEmptyState.postValue(list.isEmpty()) } } } diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderConfigDialog.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderConfigDialog.kt index 2afd7ae54..9902293ac 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderConfigDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderConfigDialog.kt @@ -4,7 +4,6 @@ import android.os.Bundle import android.view.View import androidx.appcompat.app.AlertDialog import androidx.fragment.app.FragmentManager -import kotlinx.android.synthetic.main.dialog_list_mode.button_ok import kotlinx.android.synthetic.main.dialog_reader_config.* import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.AlertDialogFragment 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 aaef4176b..08ab7978b 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 @@ -3,8 +3,11 @@ package org.koitharu.kotatsu.remotelist.ui import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.core.model.Manga @@ -27,13 +30,16 @@ class RemoteListViewModel( private val mangaList = MutableStateFlow>(emptyList()) private val hasNextPage = MutableStateFlow(false) private var appliedFilter: MangaFilter? = null + private var loadingJob: Job? = null - override val content = combine(mangaList, createListModeFlow()) { list, mode -> + override val content = combine(mangaList.drop(1), createListModeFlow()) { list, mode -> when(mode) { ListMode.LIST -> list.map { it.toListModel() } ListMode.DETAILED_LIST -> list.map { it.toListDetailedModel() } ListMode.GRID -> list.map { it.toGridModel() } } + }.onEach { + isEmptyState.postValue(it.isEmpty()) }.combine(hasNextPage) { list, isHasNextPage -> if (isHasNextPage && list.isNotEmpty()) list + IndeterminateProgress else list }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) @@ -44,7 +50,10 @@ class RemoteListViewModel( } fun loadList(offset: Int) { - launchLoadingJob { + if (loadingJob?.isActive == true) { + return + } + loadingJob = launchLoadingJob { withContext(Dispatchers.Default) { val list = repository.getList( offset = offset, diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt index a6c9ff146..4306615e7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt @@ -9,7 +9,6 @@ import android.provider.Settings import android.text.InputType import android.view.View import androidx.appcompat.app.AppCompatDelegate -import androidx.collection.arrayMapOf import androidx.preference.* import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.launch @@ -22,7 +21,6 @@ import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ListMode -import org.koitharu.kotatsu.list.ui.ListModeSelectDialog import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider import org.koitharu.kotatsu.tracker.work.TrackWorker import org.koitharu.kotatsu.utils.ext.getStorageName @@ -38,12 +36,6 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_main) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - findPreference(AppSettings.KEY_LIST_MODE)?.summary = - LIST_MODES[settings.listMode]?.let(::getString) findPreference(AppSettings.KEY_GRID_SIZE)?.run { summary = "%d%%".format(value) setOnPreferenceChangeListener { preference, newValue -> @@ -55,6 +47,18 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), MultiSummaryProvider(R.string.gestures_only) findPreference(AppSettings.KEY_TRACK_SOURCES)?.summaryProvider = MultiSummaryProvider(R.string.dont_check) + findPreference(AppSettings.KEY_ZOOM_MODE)?.run { + entryValues = ZoomMode.values().names() + setDefaultValue(ZoomMode.FIT_CENTER.name) + } + findPreference(AppSettings.KEY_LIST_MODE)?.run { + entryValues = ListMode.values().names() + setDefaultValue(ListMode.GRID.name) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) findPreference(AppSettings.KEY_APP_UPDATE_AUTO)?.run { isVisible = AppUpdateChecker.isUpdateSupported(context) } @@ -62,10 +66,6 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), summary = settings.getStorageDir(context)?.getStorageName(context) ?: getString(R.string.not_available) } - findPreference(AppSettings.KEY_ZOOM_MODE)?.let { - it.entryValues = ZoomMode.values().names() - it.setDefaultValue(ZoomMode.FIT_CENTER.name) - } findPreference(AppSettings.KEY_PROTECT_APP)?.isChecked = !settings.appPassword.isNullOrEmpty() findPreference(AppSettings.KEY_APP_VERSION)?.run { @@ -82,8 +82,6 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { when (key) { - AppSettings.KEY_LIST_MODE -> findPreference(key)?.summary = - LIST_MODES[settings.listMode]?.let(::getString) AppSettings.KEY_THEME -> { AppCompatDelegate.setDefaultNightMode(settings.theme) } @@ -111,10 +109,6 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), override fun onPreferenceTreeClick(preference: Preference?): Boolean { return when (preference?.key) { - AppSettings.KEY_LIST_MODE -> { - ListModeSelectDialog.show(childFragmentManager) - true - } AppSettings.KEY_NOTIFICATIONS_SETTINGS -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) @@ -224,13 +218,4 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings), } } } - - private companion object { - - val LIST_MODES = arrayMapOf( - ListMode.DETAILED_LIST to R.string.detailed_list, - ListMode.GRID to R.string.grid, - ListMode.LIST to R.string.list - ) - } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt index 48d112540..486b7d177 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt @@ -1,7 +1,6 @@ package org.koitharu.kotatsu.settings import android.os.Bundle -import android.view.View import androidx.preference.ListPreference import androidx.preference.MultiSelectListPreference import org.koitharu.kotatsu.R @@ -15,10 +14,6 @@ class ReaderSettingsFragment : BasePreferenceFragment(R.string.reader_settings) override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_reader) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) findPreference(AppSettings.KEY_READER_SWITCHERS)?.let { it.summaryProvider = MultiSummaryProvider(R.string.gestures_only) } diff --git a/app/src/main/res/layout/dialog_list_mode.xml b/app/src/main/res/layout/dialog_list_mode.xml index 3266beffa..d11c45009 100644 --- a/app/src/main/res/layout/dialog_list_mode.xml +++ b/app/src/main/res/layout/dialog_list_mode.xml @@ -2,14 +2,15 @@ @@ -40,12 +41,21 @@ - + android:paddingLeft="?attr/dialogPreferredPadding" + android:paddingRight="?attr/dialogPreferredPadding" + android:singleLine="true" + android:text="@string/grid_size" /> + + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 5cb3fd8b2..9119e2901 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -19,4 +19,9 @@ @string/favourites @string/history + + @string/list + @string/detailed_list + @string/grid + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index c67cfeafb..15caea8b4 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -8,6 +8,13 @@ 10dp + +