From 300d365d8bb48a05ed0a45cf04f1f77a839ca1f8 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 21 Jul 2022 17:09:20 +0300 Subject: [PATCH] Update library fragment --- .editorconfig | 3 +- .../ui/adapter/ExploreAdapterDelegates.kt | 8 +- .../kotatsu/favourites/data/FavouritesDao.kt | 24 ++-- .../favourites/domain/FavouritesRepository.kt | 3 +- .../ui/list/FavouritesListFragment.kt | 13 --- .../ui/list/FavouritesListViewModel.kt | 7 +- .../kotatsu/history/ui/HistoryListFragment.kt | 12 +- .../history/ui/HistoryListViewModel.kt | 6 +- .../library/domain/LibraryRepository.kt | 36 +++--- .../kotatsu/library/ui/LibraryFragment.kt | 8 +- .../kotatsu/library/ui/LibraryViewModel.kt | 110 +++++++++--------- .../library/ui/adapter/LibraryAdapter.kt | 15 ++- .../library/ui/model/LibrarySectionModel.kt | 2 +- .../kotatsu/list/ui/MangaListFragment.kt | 27 ++++- .../kotatsu/list/ui/MangaListViewModel.kt | 5 +- .../kotatsu/list/ui/adapter/ListHeaderAD.kt | 46 ++------ .../ui/adapter/ListHeaderClickListener.kt | 9 ++ .../list/ui/adapter/MangaListAdapter.kt | 12 +- .../list/ui/adapter/MangaListListener.kt | 4 +- .../kotatsu/list/ui/model/ListHeader.kt | 61 +++++++++- .../search/ui/multi/MultiSearchActivity.kt | 5 +- .../kotatsu/tracker/ui/FeedFragment.kt | 5 +- .../kotatsu/tracker/ui/FeedViewModel.kt | 18 ++- .../kotatsu/tracker/ui/adapter/FeedAdapter.kt | 3 +- app/src/main/res/layout/fragment_library.xml | 10 +- .../main/res/layout/item_explore_source.xml | 3 +- ...lore_header.xml => item_header_button.xml} | 4 +- .../res/layout/item_header_with_filter.xml | 36 ------ app/src/main/res/menu/mode_library.xml | 30 +++++ 29 files changed, 282 insertions(+), 243 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/ListHeaderClickListener.kt rename app/src/main/res/layout/{item_explore_header.xml => item_header_button.xml} (98%) delete mode 100644 app/src/main/res/layout/item_header_with_filter.xml create mode 100644 app/src/main/res/menu/mode_library.xml diff --git a/.editorconfig b/.editorconfig index afb2723bf..999845632 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,11 +9,12 @@ insert_final_newline = true max_line_length = 120 tab_width = 4 # noinspection EditorConfigKeyCorrectness -disabled_rules=no-wildcard-imports,no-unused-imports +disabled_rules = no-wildcard-imports, no-unused-imports [{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] ij_continuation_indent_size = 4 [{*.kt,*.kts}] +ij_kotlin_allow_trailing_comma_on_call_site = true ij_kotlin_allow_trailing_comma = true ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL diff --git a/app/src/main/java/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt b/app/src/main/java/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt index aa9e6d84f..6712ab46e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt +++ b/app/src/main/java/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt @@ -12,8 +12,8 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.parser.favicon.faviconUri import org.koitharu.kotatsu.databinding.ItemEmptyCardBinding import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding -import org.koitharu.kotatsu.databinding.ItemExploreHeaderBinding import org.koitharu.kotatsu.databinding.ItemExploreSourceBinding +import org.koitharu.kotatsu.databinding.ItemHeaderButtonBinding import org.koitharu.kotatsu.explore.ui.model.ExploreItem import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener import org.koitharu.kotatsu.utils.ext.disposeImageRequest @@ -42,8 +42,8 @@ fun exploreButtonsAD( fun exploreSourcesHeaderAD( listener: ExploreListEventListener, -) = adapterDelegateViewBinding( - { layoutInflater, parent -> ItemExploreHeaderBinding.inflate(layoutInflater, parent, false) } +) = adapterDelegateViewBinding( + { layoutInflater, parent -> ItemHeaderButtonBinding.inflate(layoutInflater, parent, false) } ) { val listenerAdapter = View.OnClickListener { @@ -105,4 +105,4 @@ fun exploreEmptyHintListAD( } } -fun exploreLoadingAD() = adapterDelegate(R.layout.item_loading_state) {} \ No newline at end of file +fun exploreLoadingAD() = adapterDelegate(R.layout.item_loading_state) {} diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt index e6fdd5a8c..562d289a5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt @@ -19,7 +19,9 @@ abstract class FavouritesDao { fun observeAll(order: SortOrder): Flow> { val orderBy = getOrderBy(order) - @Language("RoomSql") val query = SimpleSQLiteQuery( + + @Language("RoomSql") + val query = SimpleSQLiteQuery( "SELECT * FROM favourites LEFT JOIN manga ON favourites.manga_id = manga.manga_id " + "WHERE favourites.deleted_at = 0 GROUP BY favourites.manga_id ORDER BY $orderBy", ) @@ -29,20 +31,22 @@ abstract class FavouritesDao { @Transaction @Query( "SELECT * FROM favourites WHERE deleted_at = 0 " + - "GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit OFFSET :offset" + "GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit OFFSET :offset", ) abstract suspend fun findAll(offset: Int, limit: Int): List @Transaction @Query( "SELECT * FROM favourites WHERE category_id = :categoryId AND deleted_at = 0 " + - "GROUP BY manga_id ORDER BY created_at DESC" + "GROUP BY manga_id ORDER BY created_at DESC", ) abstract suspend fun findAll(categoryId: Long): List fun observeAll(categoryId: Long, order: SortOrder): Flow> { val orderBy = getOrderBy(order) - @Language("RoomSql") val query = SimpleSQLiteQuery( + + @Language("RoomSql") + val query = SimpleSQLiteQuery( "SELECT * FROM favourites LEFT JOIN manga ON favourites.manga_id = manga.manga_id " + "WHERE category_id = ? AND deleted_at = 0 GROUP BY favourites.manga_id ORDER BY $orderBy", arrayOf(categoryId), @@ -53,19 +57,21 @@ abstract class FavouritesDao { @Transaction @Query( "SELECT * FROM favourites WHERE category_id = :categoryId AND deleted_at = 0 " + - "GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit OFFSET :offset" + "GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit OFFSET :offset", ) abstract suspend fun findAll(categoryId: Long, offset: Int, limit: Int): List @Query( "SELECT * FROM manga WHERE manga_id IN " + - "(SELECT manga_id FROM favourites WHERE category_id = :categoryId AND deleted_at = 0)" + "(SELECT manga_id FROM favourites WHERE category_id = :categoryId AND deleted_at = 0)", ) abstract suspend fun findAllManga(categoryId: Int): List suspend fun findCovers(categoryId: Long, order: SortOrder): List { val orderBy = getOrderBy(order) - @Language("RoomSql") val query = SimpleSQLiteQuery( + + @Language("RoomSql") + val query = SimpleSQLiteQuery( "SELECT m.cover_url FROM favourites AS f LEFT JOIN manga AS m ON f.manga_id = m.manga_id " + "WHERE f.category_id = ? AND deleted_at = 0 ORDER BY $orderBy", arrayOf(categoryId), @@ -81,6 +87,7 @@ abstract class FavouritesDao { abstract suspend fun find(id: Long): FavouriteManga? @Transaction + @Deprecated("Ignores order") @Query("SELECT * FROM favourites WHERE manga_id = :id AND deleted_at = 0 GROUP BY manga_id") abstract fun observe(id: Long): Flow @@ -140,7 +147,8 @@ abstract class FavouritesDao { private fun getOrderBy(sortOrder: SortOrder) = when (sortOrder) { SortOrder.RATING -> "rating DESC" SortOrder.NEWEST, - SortOrder.UPDATED -> "created_at DESC" + SortOrder.UPDATED, + -> "created_at DESC" SortOrder.ALPHABETICAL -> "title ASC" else -> throw IllegalArgumentException("Sort order $sortOrder is not supported") } diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt index 871ae5329..def0b272f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.favourites.domain -import android.util.ArrayMap import androidx.room.withTransaction import kotlinx.coroutines.flow.* import org.koitharu.kotatsu.base.domain.ReversibleHandle @@ -55,7 +54,7 @@ class FavouritesRepository( return db.favouriteCategoriesDao.observeAll() .map { db.withTransaction { - val res = ArrayMap>() + val res = LinkedHashMap>() for (entity in it) { val cat = entity.toFavouriteCategory() res[cat] = db.favouritesDao.findCovers( diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt index cbb569de8..edacd269f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt @@ -6,12 +6,9 @@ import android.view.MenuItem import android.view.View import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.PopupMenu -import com.google.android.material.snackbar.Snackbar import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.base.domain.ReversibleHandle -import org.koitharu.kotatsu.base.domain.reverseAsync import org.koitharu.kotatsu.core.ui.titleRes import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity import org.koitharu.kotatsu.list.ui.MangaListFragment @@ -32,7 +29,6 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.sortOrder.observe(viewLifecycleOwner) { activity?.invalidateOptionsMenu() } - viewModel.onItemsRemoved.observe(viewLifecycleOwner, ::onItemsRemoved) } override fun onScrolledToEnd() = Unit @@ -75,15 +71,6 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis } } - private fun onItemsRemoved(reversibleHandle: ReversibleHandle) { - val message = viewModel.categoryName?.let { - getString(R.string.removed_from_s, it) - } ?: getString(R.string.removed_from_favourites) - Snackbar.make(binding.recyclerView, message, Snackbar.LENGTH_LONG) - .setAction(R.string.undo) { reversibleHandle.reverseAsync() } - .show() - } - companion object { const val NO_ID = 0L 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 c444cfa09..adbd00678 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 @@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.base.domain.ReversibleHandle +import org.koitharu.kotatsu.base.ui.util.ReversibleAction import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID @@ -23,7 +23,6 @@ import org.koitharu.kotatsu.list.ui.model.toErrorState import org.koitharu.kotatsu.list.ui.model.toUi import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.tracker.domain.TrackingRepository -import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct class FavouritesListViewModel( @@ -72,8 +71,6 @@ class FavouritesListViewModel( emit(listOf(it.toErrorState(canRetry = false))) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState)) - val onItemsRemoved = SingleLiveEvent() - init { if (categoryId != NO_ID) { launchJob { @@ -100,7 +97,7 @@ class FavouritesListViewModel( } else { repository.removeFromCategory(categoryId, ids) } - onItemsRemoved.postCall(handle) + onActionDone.postCall(ReversibleAction(R.string.removed_from_favourites, handle)) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt index bdd424098..11d3f75ba 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt @@ -5,12 +5,9 @@ import android.view.Menu import android.view.MenuItem import android.view.View import androidx.appcompat.view.ActionMode -import com.google.android.material.snackbar.Snackbar 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.domain.ReversibleHandle -import org.koitharu.kotatsu.base.domain.reverseAsync import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.utils.ext.addMenuProvider @@ -26,7 +23,6 @@ class HistoryListFragment : MangaListFragment() { viewModel.isGroupingEnabled.observe(viewLifecycleOwner) { activity?.invalidateOptionsMenu() } - viewModel.onItemsRemoved.observe(viewLifecycleOwner, ::onItemsRemoved) } override fun onScrolledToEnd() = Unit @@ -56,14 +52,8 @@ class HistoryListFragment : MangaListFragment() { override fun onCreateAdapter() = HistoryListAdapter(get(), viewLifecycleOwner, this) - private fun onItemsRemoved(reversibleHandle: ReversibleHandle) { - Snackbar.make(binding.recyclerView, R.string.removed_from_history, Snackbar.LENGTH_LONG) - .setAction(R.string.undo) { reversibleHandle.reverseAsync() } - .show() - } - companion object { fun newInstance() = HistoryListFragment() } -} \ No newline at end of file +} 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 468d4ae91..c2c03311f 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 @@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.base.domain.ReversibleHandle +import org.koitharu.kotatsu.base.ui.util.ReversibleAction import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.observeAsFlow @@ -19,7 +19,6 @@ import org.koitharu.kotatsu.history.domain.PROGRESS_NONE import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.model.* import org.koitharu.kotatsu.tracker.domain.TrackingRepository -import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.daysDiff import org.koitharu.kotatsu.utils.ext.onFirst @@ -33,7 +32,6 @@ class HistoryListViewModel( ) : MangaListViewModel(settings) { val isGroupingEnabled = MutableLiveData() - val onItemsRemoved = SingleLiveEvent() private val historyGrouping = settings.observeAsFlow(AppSettings.KEY_HISTORY_GROUPING) { isHistoryGroupingEnabled } .onEach { isGroupingEnabled.postValue(it) } @@ -78,7 +76,7 @@ class HistoryListViewModel( } launchJob(Dispatchers.Default) { val handle = repository.delete(ids) - onItemsRemoved.postCall(handle) + onActionDone.postCall(ReversibleAction(R.string.removed_from_history, handle)) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/library/domain/LibraryRepository.kt b/app/src/main/java/org/koitharu/kotatsu/library/domain/LibraryRepository.kt index c698c36a3..84dfd214d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/domain/LibraryRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/domain/LibraryRepository.kt @@ -1,40 +1,30 @@ package org.koitharu.kotatsu.library.domain import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.entity.toManga import org.koitharu.kotatsu.core.db.entity.toMangaTags import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.favourites.data.FavouriteManga import org.koitharu.kotatsu.favourites.data.toFavouriteCategory -import org.koitharu.kotatsu.favourites.domain.FavouritesRepository -import org.koitharu.kotatsu.history.domain.HistoryRepository import org.koitharu.kotatsu.parsers.model.Manga -import org.koitharu.kotatsu.parsers.model.SortOrder class LibraryRepository( private val db: MangaDatabase, ) { - - fun observeFavourites(order: SortOrder): Flow>> { - return db.favouritesDao.observeAll(order) - .map { list -> groupByCategory(list) } - } - - private fun groupByCategory(list: List): Map> { - val map = HashMap>() - for (item in list) { - val manga = item.manga.toManga(item.tags.toMangaTags()) - for (category in item.categories) { - if (!category.isVisibleInLibrary) { - continue - } - map.getOrPut(category.toFavouriteCategory()) { ArrayList() } - .add(manga) + fun observeFavourites(): Flow>> { + return db.favouriteCategoriesDao.observeAll() + .flatMapLatest { categories -> + combine( + categories.map { cat -> + val category = cat.toFavouriteCategory() + db.favouritesDao.observeAll(category.id, category.order) + .map { category to it.map { x -> x.manga.toManga(x.tags.toMangaTags()) } } + }, + ) { array -> array.toMap() } } - } - return map } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryFragment.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryFragment.kt index b5cc8dd7d..558a7e982 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryFragment.kt @@ -33,7 +33,9 @@ import org.koitharu.kotatsu.utils.ext.addMenuProvider import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.invalidateNestedItemDecorations -class LibraryFragment : BaseFragment(), LibraryListEventListener, +class LibraryFragment : + BaseFragment(), + LibraryListEventListener, SectionedSelectionController.Callback { private val viewModel by viewModel() @@ -109,7 +111,7 @@ class LibraryFragment : BaseFragment(), LibraryListEvent } override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { - mode.menuInflater.inflate(R.menu.mode_remote, menu) + mode.menuInflater.inflate(R.menu.mode_library, menu) return true } @@ -172,7 +174,7 @@ class LibraryFragment : BaseFragment(), LibraryListEvent Snackbar.make( binding.recyclerView, e.getDisplayMessage(resources), - Snackbar.LENGTH_SHORT + Snackbar.LENGTH_SHORT, ).show() } diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt index 27d21046a..0a6aa24db 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.library.ui import androidx.collection.ArraySet import androidx.lifecycle.LiveData import androidx.lifecycle.viewModelScope +import java.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine @@ -21,17 +22,15 @@ import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel import org.koitharu.kotatsu.list.domain.ListExtraProvider import org.koitharu.kotatsu.list.ui.model.* import org.koitharu.kotatsu.parsers.model.Manga -import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.daysDiff -import java.util.* private const val HISTORY_MAX_SEGMENTS = 2 class LibraryViewModel( - private val repository: LibraryRepository, + repository: LibraryRepository, private val historyRepository: HistoryRepository, private val trackingRepository: TrackingRepository, private val settings: AppSettings, @@ -41,7 +40,7 @@ class LibraryViewModel( val content: LiveData> = combine( historyRepository.observeAllWithHistory(), - repository.observeFavourites(SortOrder.NEWEST), + repository.observeFavourites(), ) { history, favourites -> mapList(history, favourites) }.catch { e -> @@ -60,25 +59,6 @@ class LibraryViewModel( } } - fun getManga(ids: Set): Set { - val snapshot = content.value ?: return emptySet() - val result = ArraySet(ids.size) - for (section in snapshot) { - if (section !is LibrarySectionModel) { - continue - } - for (item in section.items) { - if (item.id in ids) { - result.add(item.manga) - if (result.size == ids.size) { - return result - } - } - } - } - return result - } - fun removeFromHistory(ids: Set) { if (ids.isEmpty()) { return @@ -102,16 +82,35 @@ class LibraryViewModel( } } + fun getManga(ids: Set): Set { + val snapshot = content.value ?: return emptySet() + val result = ArraySet(ids.size) + for (section in snapshot) { + if (section !is LibrarySectionModel) { + continue + } + for (item in section.items) { + if (item.id in ids) { + result.add(item.manga) + if (result.size == ids.size) { + return result + } + } + } + } + return result + } + private suspend fun mapList( history: List, favourites: Map>, ): List { val result = ArrayList(favourites.keys.size + 1) if (history.isNotEmpty()) { - result += mapHistory(history) + mapHistory(result, history) } - for ((category, list) in favourites) { - result += LibrarySectionModel.Favourites(list.toUi(ListMode.GRID, this), category, R.string.show_all) + if (favourites.isNotEmpty()) { + mapFavourites(result, favourites) } if (result.isEmpty()) { result += EmptyState( @@ -121,40 +120,45 @@ class LibraryViewModel( actionStringRes = 0, ) } + result.trimToSize() return result } - private suspend fun mapHistory(list: List): List { + private suspend fun mapHistory( + destination: MutableList, + list: List, + ) { val showPercent = settings.isReadingIndicatorsEnabled - val groups = ArrayList() - val map = HashMap>() - for ((manga, history) in list) { - val date = timeAgo(history.updatedAt) - val counter = trackingRepository.getNewChaptersCount(manga.id) - val percent = if (showPercent) history.percent else PROGRESS_NONE - if (groups.lastOrNull() != date) { - groups.add(date) - } - map.getOrPut(date) { ArrayList() }.add(manga.toGridModel(counter, percent)) + val groups = list.groupByTo(LinkedHashMap()) { timeAgo(it.history.updatedAt) } + while (groups.size > HISTORY_MAX_SEGMENTS) { + val lastKey = groups.keys.last() + val subList = groups.remove(lastKey) ?: continue + groups[groups.keys.last()]?.addAll(subList) } - val result = ArrayList(HISTORY_MAX_SEGMENTS) - repeat(minOf(HISTORY_MAX_SEGMENTS - 1, groups.size - 1)) { i -> - val key = groups[i] - val values = map.remove(key) - if (!values.isNullOrEmpty()) { - result.add(LibrarySectionModel.History(values, key, 0)) - } + for ((timeAgo, subList) in groups) { + destination += LibrarySectionModel.History( + items = subList.map { (manga, history) -> + val counter = trackingRepository.getNewChaptersCount(manga.id) + val percent = if (showPercent) history.percent else PROGRESS_NONE + manga.toGridModel(counter, percent) + }, + timeAgo = timeAgo, + showAllButtonText = R.string.show_all, + ) } - val values = map.values.flatten() - if (values.isNotEmpty()) { - val key = if (result.isEmpty()) { - map.keys.singleOrNull()?.takeUnless { it == DateTimeAgo.LongAgo } - } else { - map.keys.singleOrNull() ?: DateTimeAgo.LongAgo - } - result.add(LibrarySectionModel.History(values, key, R.string.show_all)) + } + + private suspend fun mapFavourites( + destination: MutableList, + favourites: Map>, + ) { + for ((category, list) in favourites) { + destination += LibrarySectionModel.Favourites( + items = list.toUi(ListMode.GRID, this), + category = category, + showAllButtonText = R.string.show_all, + ) } - return result } private fun timeAgo(date: Date): DateTimeAgo { diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryAdapter.kt index 72b0d132f..bdc2bcc7c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryAdapter.kt @@ -1,11 +1,14 @@ package org.koitharu.kotatsu.library.ui.adapter +import android.content.Context import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter +import kotlin.jvm.internal.Intrinsics import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController +import org.koitharu.kotatsu.base.ui.list.fastscroll.FastScroller import org.koitharu.kotatsu.library.ui.model.LibrarySectionModel import org.koitharu.kotatsu.list.ui.ItemSizeResolver import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD @@ -13,7 +16,6 @@ import org.koitharu.kotatsu.list.ui.adapter.errorStateListAD import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD import org.koitharu.kotatsu.list.ui.model.ListModel -import kotlin.jvm.internal.Intrinsics class LibraryAdapter( lifecycleOwner: LifecycleOwner, @@ -21,7 +23,7 @@ class LibraryAdapter( listener: LibraryListEventListener, sizeResolver: ItemSizeResolver, selectionController: SectionedSelectionController, -) : AsyncListDifferDelegationAdapter(DiffCallback()) { +) : AsyncListDifferDelegationAdapter(DiffCallback()), FastScroller.SectionIndexer { init { val pool = RecyclerView.RecycledViewPool() @@ -34,7 +36,7 @@ class LibraryAdapter( sizeResolver = sizeResolver, selectionController = selectionController, listener = listener, - ) + ), ) .addDelegate(loadingStateAD()) .addDelegate(loadingFooterAD()) @@ -42,6 +44,11 @@ class LibraryAdapter( .addDelegate(errorStateListAD(listener)) } + override fun getSectionText(context: Context, position: Int): CharSequence { + val item = items.getOrNull(position) as? LibrarySectionModel + return item?.getTitle(context.resources) ?: "" + } + private class DiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean { @@ -64,4 +71,4 @@ class LibraryAdapter( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/model/LibrarySectionModel.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/model/LibrarySectionModel.kt index a374f463b..4b53017e1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/ui/model/LibrarySectionModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/model/LibrarySectionModel.kt @@ -91,4 +91,4 @@ sealed class LibrarySectionModel( return "fav_${category.id}" } } -} \ No newline at end of file +} 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 e765f1c14..952708a02 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 @@ -16,6 +16,7 @@ import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.launch import org.koin.android.ext.android.get import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.reverseAsync import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.list.FitHeightGridLayoutManager import org.koitharu.kotatsu.base.ui.list.FitHeightLinearLayoutManager @@ -24,6 +25,7 @@ import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.base.ui.list.decor.TypedSpacingItemDecoration import org.koitharu.kotatsu.base.ui.list.fastscroll.FastScroller +import org.koitharu.kotatsu.base.ui.util.ReversibleAction import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver @@ -35,6 +37,7 @@ import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesB import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter.Companion.ITEM_TYPE_MANGA_GRID import org.koitharu.kotatsu.list.ui.adapter.MangaListListener +import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.MangaItemModel import org.koitharu.kotatsu.main.ui.AppBarOwner @@ -49,7 +52,8 @@ abstract class MangaListFragment : PaginationScrollListener.Callback, MangaListListener, SwipeRefreshLayout.OnRefreshListener, - ListSelectionController.Callback, FastScroller.FastScrollListener { + ListSelectionController.Callback, + FastScroller.FastScrollListener { private var listAdapter: MangaListAdapter? = null private var paginationListener: PaginationScrollListener? = null @@ -71,7 +75,7 @@ abstract class MangaListFragment : override fun onInflateView( inflater: LayoutInflater, - container: ViewGroup? + container: ViewGroup?, ) = FragmentListBinding.inflate(inflater, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -104,6 +108,7 @@ abstract class MangaListFragment : viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged) viewModel.content.observe(viewLifecycleOwner, ::onListChanged) viewModel.onError.observe(viewLifecycleOwner, ::onError) + viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone) } override fun onDestroyView() { @@ -141,11 +146,21 @@ abstract class MangaListFragment : Snackbar.make( binding.recyclerView, e.getDisplayMessage(resources), - Snackbar.LENGTH_SHORT + Snackbar.LENGTH_SHORT, ).show() } } + private fun onActionDone(action: ReversibleAction) { + val handle = action.handle + val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG + val snackbar = Snackbar.make(binding.recyclerView, action.stringResId, length) + if (handle != null) { + snackbar.setAction(R.string.undo) { handle.reverseAsync() } + } + snackbar.show() + } + private fun resolveException(e: Throwable) { if (ExceptionResolver.canResolve(e)) { viewLifecycleScope.launch { @@ -201,6 +216,8 @@ abstract class MangaListFragment : override fun onEmptyActionClick() = Unit + override fun onListHeaderClick(item: ListHeader, view: View) = Unit + override fun onRetryClick(error: Throwable) { resolveException(error) } @@ -225,7 +242,7 @@ abstract class MangaListFragment : val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing) val decoration = TypedSpacingItemDecoration( MangaListAdapter.ITEM_TYPE_MANGA_LIST to 0, - fallbackSpacing = spacing + fallbackSpacing = spacing, ) addItemDecoration(decoration) } @@ -332,4 +349,4 @@ abstract class MangaListFragment : invalidateSpanIndexCache() } } -} \ 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 a254efc44..f71ccac22 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 @@ -6,12 +6,14 @@ import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.onEach import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.base.ui.util.ReversibleAction import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.prefs.observeAsLiveData import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.MangaTag +import org.koitharu.kotatsu.utils.SingleLiveEvent abstract class MangaListViewModel( private val settings: AppSettings, @@ -19,6 +21,7 @@ abstract class MangaListViewModel( abstract val content: LiveData> val listMode = MutableLiveData() + val onActionDone = SingleLiveEvent() val gridScale = settings.observeAsLiveData( context = viewModelScope.coroutineContext + Dispatchers.Default, key = AppSettings.KEY_GRID_SIZE, @@ -37,4 +40,4 @@ abstract class MangaListViewModel( abstract fun onRefresh() abstract fun onRetry() -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/ListHeaderAD.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/ListHeaderAD.kt index 3e5faa413..855c70c1f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/ListHeaderAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/ListHeaderAD.kt @@ -1,46 +1,22 @@ package org.koitharu.kotatsu.list.ui.adapter -import android.widget.TextView -import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.ui.titleRes -import org.koitharu.kotatsu.databinding.ItemHeaderWithFilterBinding +import org.koitharu.kotatsu.databinding.ItemHeaderButtonBinding import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.utils.ext.setTextAndVisible -fun listHeaderAD() = adapterDelegate( - layout = R.layout.item_header, - on = { item, _, _ -> item is ListHeader && item.sortOrder == null }, +fun listHeaderAD( + listener: ListHeaderClickListener, +) = adapterDelegateViewBinding( + { inflater, parent -> ItemHeaderButtonBinding.inflate(inflater, parent, false) }, ) { + binding.buttonMore.setOnClickListener { + listener.onListHeaderClick(item, it) + } bind { - val textView = (itemView as TextView) - if (item.text != null) { - textView.text = item.text - } else { - textView.setText(item.textRes) - } + binding.textViewTitle.text = item.getText(context) + binding.buttonMore.setTextAndVisible(item.buttonTextRes) } } - -fun listHeaderWithFilterAD( - listener: MangaListListener, -) = adapterDelegateViewBinding( - viewBinding = { inflater, parent -> ItemHeaderWithFilterBinding.inflate(inflater, parent, false) }, - on = { item, _, _ -> item is ListHeader && item.sortOrder != null }, -) { - - binding.textViewFilter.setOnClickListener { - listener.onFilterClick(it) - } - - bind { - if (item.text != null) { - binding.textViewTitle.text = item.text - } else { - binding.textViewTitle.setText(item.textRes) - } - binding.textViewFilter.setText(requireNotNull(item.sortOrder).titleRes) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/ListHeaderClickListener.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/ListHeaderClickListener.kt new file mode 100644 index 000000000..67a242759 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/ListHeaderClickListener.kt @@ -0,0 +1,9 @@ +package org.koitharu.kotatsu.list.ui.adapter + +import android.view.View +import org.koitharu.kotatsu.list.ui.model.ListHeader + +interface ListHeaderClickListener { + + fun onListHeaderClick(item: ListHeader, view: View) +} 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 ca4be8e13..1dfe213b0 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 @@ -25,9 +25,8 @@ open class MangaListAdapter( .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_HEADER, listHeaderAD(listener)) .addDelegate(ITEM_TYPE_HEADER_2, listHeader2AD(listener)) - .addDelegate(ITEM_TYPE_HEADER_FILTER, listHeaderWithFilterAD(listener)) } private class DiffCallback : DiffUtil.ItemCallback() { @@ -45,6 +44,11 @@ open class MangaListAdapter( oldItem is DateTimeAgo && newItem is DateTimeAgo -> { oldItem == newItem } + oldItem is ListHeader && newItem is ListHeader -> { + oldItem.textRes == newItem.textRes && + oldItem.text == newItem.text && + oldItem.dateTimeAgo == newItem.dateTimeAgo + } else -> oldItem.javaClass == newItem.javaClass } @@ -59,7 +63,6 @@ open class MangaListAdapter( if (oldItem.progress != newItem.progress) { PAYLOAD_PROGRESS } else { - Unit } } is ListHeader2 -> Unit @@ -81,8 +84,7 @@ open class MangaListAdapter( const val ITEM_TYPE_EMPTY = 8 const val ITEM_TYPE_HEADER = 9 const val ITEM_TYPE_HEADER_2 = 10 - const val ITEM_TYPE_HEADER_FILTER = 11 val PAYLOAD_PROGRESS = Any() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListListener.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListListener.kt index 89553be7f..516455c06 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListListener.kt @@ -5,9 +5,9 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaTag -interface MangaListListener : OnListItemClickListener, ListStateHolderListener { +interface MangaListListener : OnListItemClickListener, ListStateHolderListener, ListHeaderClickListener { fun onUpdateFilter(tags: Set) fun onFilterClick(view: View?) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListHeader.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListHeader.kt index 15f184160..5d9682d75 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListHeader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/model/ListHeader.kt @@ -1,11 +1,62 @@ package org.koitharu.kotatsu.list.ui.model +import android.content.Context import androidx.annotation.StringRes -import org.koitharu.kotatsu.parsers.model.SortOrder +import org.koitharu.kotatsu.core.ui.DateTimeAgo -@Deprecated("") -data class ListHeader( +class ListHeader private constructor( val text: CharSequence?, @StringRes val textRes: Int, - val sortOrder: SortOrder?, -) : ListModel \ No newline at end of file + val dateTimeAgo: DateTimeAgo?, + @StringRes val buttonTextRes: Int, + val payload: Any?, +) : ListModel { + + constructor( + text: CharSequence, + @StringRes buttonTextRes: Int, + payload: Any?, + ) : this(text, 0, null, buttonTextRes, payload) + + constructor( + @StringRes textRes: Int, + @StringRes buttonTextRes: Int, + payload: Any?, + ) : this(null, textRes, null, buttonTextRes, payload) + + constructor( + dateTimeAgo: DateTimeAgo, + @StringRes buttonTextRes: Int, + payload: Any?, + ) : this(null, 0, dateTimeAgo, buttonTextRes, payload) + + fun getText(context: Context): CharSequence? = when { + text != null -> text + textRes != 0 -> context.getString(textRes) + else -> dateTimeAgo?.format(context.resources) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ListHeader + + if (text != other.text) return false + if (textRes != other.textRes) return false + if (dateTimeAgo != other.dateTimeAgo) return false + if (buttonTextRes != other.buttonTextRes) return false + if (payload != other.payload) return false + + return true + } + + override fun hashCode(): Int { + var result = text?.hashCode() ?: 0 + result = 31 * result + textRes + result = 31 * result + (dateTimeAgo?.hashCode() ?: 0) + result = 31 * result + buttonTextRes + result = 31 * result + (payload?.hashCode() ?: 0) + return result + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt index 34c8600dd..a6f2937ea 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt @@ -23,6 +23,7 @@ import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesB import org.koitharu.kotatsu.list.ui.ItemSizeResolver import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration import org.koitharu.kotatsu.list.ui.adapter.MangaListListener +import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.search.ui.SearchActivity @@ -109,6 +110,8 @@ class MultiSearchActivity : BaseActivity(), MangaLis override fun onEmptyActionClick() = Unit + override fun onListHeaderClick(item: ListHeader, view: View) = Unit + override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { mode.menuInflater.inflate(R.menu.mode_remote, menu) return true @@ -156,4 +159,4 @@ class MultiSearchActivity : BaseActivity(), MangaLis Intent(context, MultiSearchActivity::class.java) .putExtra(EXTRA_QUERY, query) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt index 7a7004e6e..51413c691 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt @@ -16,6 +16,7 @@ import org.koitharu.kotatsu.base.ui.list.decor.TypedSpacingItemDecoration 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.ListHeader import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaTag @@ -93,6 +94,8 @@ class FeedFragment : override fun onEmptyActionClick() = Unit + override fun onListHeaderClick(item: ListHeader, view: View) = Unit + private fun onListChanged(list: List) { feedAdapter?.items = list } @@ -129,4 +132,4 @@ class FeedFragment : fun newInstance() = FeedFragment() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt index 11ae7f6fa..bf89c42ab 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedViewModel.kt @@ -1,8 +1,6 @@ package org.koitharu.kotatsu.tracker.ui import androidx.lifecycle.viewModelScope -import java.util.* -import java.util.concurrent.TimeUnit import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancelAndJoin @@ -12,13 +10,18 @@ import kotlinx.coroutines.flow.filterNotNull import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.DateTimeAgo -import org.koitharu.kotatsu.list.ui.model.* +import org.koitharu.kotatsu.list.ui.model.EmptyState +import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.list.ui.model.LoadingFooter +import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem import org.koitharu.kotatsu.tracker.ui.model.toFeedItem import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.daysDiff +import java.util.* +import java.util.concurrent.TimeUnit class FeedViewModel( private val repository: TrackingRepository @@ -27,7 +30,6 @@ class FeedViewModel( private val logList = MutableStateFlow?>(null) private val hasNextPage = MutableStateFlow(false) private var loadingJob: Job? = null - private val header = ListHeader(null, R.string.updates, null) val onFeedCleared = SingleLiveEvent() val content = combine( @@ -36,7 +38,6 @@ class FeedViewModel( ) { list, isHasNextPage -> buildList(list.size + 2) { if (list.isEmpty()) { - add(header) add( EmptyState( icon = R.drawable.ic_empty_feed, @@ -52,10 +53,7 @@ class FeedViewModel( } } } - }.asLiveDataDistinct( - viewModelScope.coroutineContext + Dispatchers.Default, - listOf(header, LoadingState) - ) + }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState)) init { loadList(append = false) @@ -114,4 +112,4 @@ class FeedViewModel( else -> DateTimeAgo.Absolute(date) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedAdapter.kt index bebf7baae..ac066b946 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/adapter/FeedAdapter.kt @@ -24,7 +24,6 @@ class FeedAdapter( .addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD(listener)) .addDelegate(ITEM_TYPE_ERROR_STATE, errorStateListAD(listener)) .addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(listener)) - .addDelegate(ITEM_TYPE_HEADER, listHeaderAD()) .addDelegate(ITEM_TYPE_DATE_HEADER, relatedDateItemAD()) } @@ -56,4 +55,4 @@ class FeedAdapter( const val ITEM_TYPE_HEADER = 6 const val ITEM_TYPE_DATE_HEADER = 7 } -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index e520c6868..2ec03caee 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -4,16 +4,16 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:clipToPadding="false" - android:paddingHorizontal="@dimen/list_spacing"> + android:layout_height="match_parent"> - - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_explore_source.xml b/app/src/main/res/layout/item_explore_source.xml index 48b1a178b..69ee7706e 100644 --- a/app/src/main/res/layout/item_explore_source.xml +++ b/app/src/main/res/layout/item_explore_source.xml @@ -15,6 +15,7 @@ android:id="@+id/imageView_icon" android:layout_width="32dp" android:layout_height="32dp" + android:background="?colorControlHighlight" android:labelFor="@id/textView_title" android:scaleType="fitCenter" app:shapeAppearance="?shapeAppearanceCornerSmall" @@ -30,4 +31,4 @@ android:textAppearance="?attr/textAppearanceBodyMedium" tools:text="@tools:sample/lorem[2]" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_explore_header.xml b/app/src/main/res/layout/item_header_button.xml similarity index 98% rename from app/src/main/res/layout/item_explore_header.xml rename to app/src/main/res/layout/item_header_button.xml index 509869242..5b478fef9 100644 --- a/app/src/main/res/layout/item_explore_header.xml +++ b/app/src/main/res/layout/item_header_button.xml @@ -9,8 +9,8 @@ - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_header_with_filter.xml b/app/src/main/res/layout/item_header_with_filter.xml deleted file mode 100644 index 4737f9478..000000000 --- a/app/src/main/res/layout/item_header_with_filter.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/mode_library.xml b/app/src/main/res/menu/mode_library.xml new file mode 100644 index 000000000..72a14323c --- /dev/null +++ b/app/src/main/res/menu/mode_library.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + +