From ff4eac82697ead5ef09ad4d1a90cab53efa94688 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 13 Apr 2024 08:52:53 +0300 Subject: [PATCH] Improve updated manga screen --- .../kotatsu/core/prefs/AppSettings.kt | 10 +++++ .../koitharu/kotatsu/core/util/ext/Android.kt | 3 +- .../kotatsu/list/ui/MangaListMenuProvider.kt | 2 + .../list/ui/config/ListConfigBottomSheet.kt | 18 +++------ .../list/ui/config/ListConfigSection.kt | 3 ++ .../list/ui/config/ListConfigViewModel.kt | 30 +++++++++++++-- .../kotatsu/tracker/data/MangaWithTrack.kt | 23 +++++++++++ .../kotatsu/tracker/data/TracksDao.kt | 9 ++--- .../tracker/domain/TrackingRepository.kt | 18 +++++++-- .../tracker/domain/model/MangaTracking.kt | 3 ++ .../kotatsu/tracker/ui/feed/FeedFragment.kt | 17 ++++----- .../tracker/ui/feed/FeedMenuProvider.kt | 10 +++++ .../kotatsu/tracker/ui/feed/FeedViewModel.kt | 36 ++++++++++++++---- .../tracker/ui/updates/UpdatesViewModel.kt | 38 +++++++++++++++++-- app/src/main/res/menu/opt_feed.xml | 7 ++++ app/src/main/res/values/strings.xml | 1 + 16 files changed, 185 insertions(+), 43 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/MangaWithTrack.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 466e38788..f81c4f9c8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -172,6 +172,14 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { get() = prefs.getBoolean(KEY_HISTORY_GROUPING, true) set(value) = prefs.edit { putBoolean(KEY_HISTORY_GROUPING, value) } + var isUpdatedGroupingEnabled: Boolean + get() = prefs.getBoolean(KEY_UPDATED_GROUPING, true) + set(value) = prefs.edit { putBoolean(KEY_UPDATED_GROUPING, value) } + + var isFeedHeaderVisible: Boolean + get() = prefs.getBoolean(KEY_FEED_HEADER, true) + set(value) = prefs.edit { putBoolean(KEY_FEED_HEADER, value) } + val isReadingIndicatorsEnabled: Boolean get() = prefs.getBoolean(KEY_READING_INDICATORS, true) @@ -575,6 +583,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { const val KEY_BACKUP_PERIODICAL_OUTPUT = "backup_periodic_output" const val KEY_BACKUP_PERIODICAL_LAST = "backup_periodic_last" const val KEY_HISTORY_GROUPING = "history_grouping" + const val KEY_UPDATED_GROUPING = "updated_grouping" const val KEY_READING_INDICATORS = "reading_indicators" const val KEY_REVERSE_CHAPTERS = "reverse_chapters" const val KEY_GRID_VIEW_CHAPTERS = "grid_view_chapters" @@ -652,5 +661,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { const val KEY_STATS_ENABLED = "stats_on" const val KEY_APP_UPDATE = "app_update" const val KEY_APP_TRANSLATION = "about_app_translation" + const val KEY_FEED_HEADER = "feed_header" } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt index a15142b7b..a51c3e81b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Android.kt @@ -65,6 +65,7 @@ import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserException import java.io.File import kotlin.math.roundToLong +import com.google.android.material.R as materialR val Context.activityManager: ActivityManager? get() = getSystemService(ACTIVITY_SERVICE) as? ActivityManager @@ -141,7 +142,7 @@ fun Window.setNavigationBarTransparentCompat(context: Context, elevation: Float, } else { // Set navbar scrim 70% of navigationBarColor ElevationOverlayProvider(context).compositeOverlayIfNeeded( - context.getThemeColor(com.google.android.material.R.attr.colorSurfaceContainer, alphaFactor), + context.getThemeColor(materialR.attr.colorSurfaceContainer, alphaFactor), elevation, ) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListMenuProvider.kt index fd5e16aab..055e79227 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListMenuProvider.kt @@ -11,6 +11,7 @@ import org.koitharu.kotatsu.history.ui.HistoryListFragment import org.koitharu.kotatsu.list.ui.config.ListConfigBottomSheet import org.koitharu.kotatsu.list.ui.config.ListConfigSection import org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment +import org.koitharu.kotatsu.tracker.ui.updates.UpdatesFragment class MangaListMenuProvider( private val fragment: Fragment, @@ -26,6 +27,7 @@ class MangaListMenuProvider( is HistoryListFragment -> ListConfigSection.History is SuggestionsFragment -> ListConfigSection.Suggestions is FavouritesListFragment -> ListConfigSection.Favorites(fragment.categoryId) + is UpdatesFragment -> ListConfigSection.Updated else -> ListConfigSection.General } ListConfigBottomSheet.show(fragment.childFragmentManager, section) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigBottomSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigBottomSheet.kt index ef3cac052..bc7c45a5b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigBottomSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigBottomSheet.kt @@ -14,7 +14,6 @@ import com.google.android.material.button.MaterialButtonToggleGroup import com.google.android.material.slider.Slider import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.util.ext.setValueRounded @@ -22,7 +21,6 @@ import org.koitharu.kotatsu.core.util.ext.showDistinct import org.koitharu.kotatsu.core.util.ext.withArgs import org.koitharu.kotatsu.core.util.progress.IntPercentLabelFormatter import org.koitharu.kotatsu.databinding.SheetListModeBinding -import javax.inject.Inject @AndroidEntryPoint class ListConfigBottomSheet : @@ -31,10 +29,6 @@ class ListConfigBottomSheet : MaterialButtonToggleGroup.OnButtonCheckedListener, CompoundButton.OnCheckedChangeListener, AdapterView.OnItemSelectedListener { - @Inject - @Deprecated("") - lateinit var settings: AppSettings - private val viewModel by viewModels() override fun onCreateViewBinding( @@ -57,11 +51,11 @@ class ListConfigBottomSheet : binding.checkableGroup.addOnButtonCheckedListener(this) - binding.switchGrouping.isVisible = viewModel.isGroupingAvailable - if (viewModel.isGroupingAvailable) { - binding.switchGrouping.isEnabled = settings.historySortOrder.isGroupingSupported() + binding.switchGrouping.isVisible = viewModel.isGroupingSupported + if (viewModel.isGroupingSupported) { + binding.switchGrouping.isEnabled = viewModel.isGroupingAvailable } - binding.switchGrouping.isChecked = settings.isHistoryGroupingEnabled + binding.switchGrouping.isChecked = viewModel.isGroupingEnabled binding.switchGrouping.setOnCheckedChangeListener(this) val sortOrders = viewModel.getSortOrders() @@ -99,7 +93,7 @@ class ListConfigBottomSheet : override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { when (buttonView.id) { - R.id.switch_grouping -> settings.isHistoryGroupingEnabled = isChecked + R.id.switch_grouping -> viewModel.isGroupingEnabled = isChecked } } @@ -113,7 +107,7 @@ class ListConfigBottomSheet : when (parent.id) { R.id.spinner_order -> { viewModel.setSortOrder(position) - viewBinding?.switchGrouping?.isEnabled = settings.historySortOrder.isGroupingSupported() + viewBinding?.switchGrouping?.isEnabled = viewModel.isGroupingAvailable } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigSection.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigSection.kt index 14d8bdbe7..1908f080e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigSection.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigSection.kt @@ -18,4 +18,7 @@ sealed interface ListConfigSection : Parcelable { @Parcelize data object Suggestions : ListConfigSection + + @Parcelize + data object Updated : ListConfigSection } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigViewModel.kt index e1a39d634..b2a2c3155 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigViewModel.kt @@ -26,16 +26,18 @@ class ListConfigViewModel @Inject constructor( var listMode: ListMode get() = when (section) { is ListConfigSection.Favorites -> settings.favoritesListMode - ListConfigSection.General -> settings.listMode ListConfigSection.History -> settings.historyListMode ListConfigSection.Suggestions -> settings.suggestionsListMode + ListConfigSection.General, + ListConfigSection.Updated -> settings.listMode } set(value) { when (section) { is ListConfigSection.Favorites -> settings.favoritesListMode = value - ListConfigSection.General -> settings.listMode = value ListConfigSection.History -> settings.historyListMode = value ListConfigSection.Suggestions -> settings.suggestionsListMode = value + ListConfigSection.Updated, + ListConfigSection.General -> settings.listMode = value } } @@ -45,19 +47,40 @@ class ListConfigViewModel @Inject constructor( settings.gridSize = value } + val isGroupingSupported: Boolean + get() = section == ListConfigSection.History || section == ListConfigSection.Updated + val isGroupingAvailable: Boolean - get() = section == ListConfigSection.History + get() = when (section) { + ListConfigSection.History -> settings.historySortOrder.isGroupingSupported() + ListConfigSection.Updated -> true + else -> false + } + + var isGroupingEnabled: Boolean + get() = when (section) { + ListConfigSection.History -> settings.isHistoryGroupingEnabled + ListConfigSection.Updated -> settings.isUpdatedGroupingEnabled + else -> false + } + set(value) = when (section) { + ListConfigSection.History -> settings.isHistoryGroupingEnabled = value + ListConfigSection.Updated -> settings.isUpdatedGroupingEnabled = value + else -> Unit + } fun getSortOrders(): List? = when (section) { is ListConfigSection.Favorites -> ListSortOrder.FAVORITES ListConfigSection.General -> null ListConfigSection.History -> ListSortOrder.HISTORY ListConfigSection.Suggestions -> ListSortOrder.SUGGESTIONS + ListConfigSection.Updated -> null }?.sortedByOrdinal() fun getSelectedSortOrder(): ListSortOrder? = when (section) { is ListConfigSection.Favorites -> getCategorySortOrder(section.categoryId) ListConfigSection.General -> null + ListConfigSection.Updated -> null ListConfigSection.History -> settings.historySortOrder ListConfigSection.Suggestions -> ListSortOrder.RELEVANCE // TODO } @@ -77,6 +100,7 @@ class ListConfigViewModel @Inject constructor( ListConfigSection.History -> settings.historySortOrder = value ListConfigSection.Suggestions -> Unit + ListConfigSection.Updated -> Unit } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/MangaWithTrack.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/MangaWithTrack.kt new file mode 100644 index 000000000..c2a4eed2e --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/MangaWithTrack.kt @@ -0,0 +1,23 @@ +package org.koitharu.kotatsu.tracker.data + +import androidx.room.Embedded +import androidx.room.Junction +import androidx.room.Relation +import org.koitharu.kotatsu.core.db.entity.MangaEntity +import org.koitharu.kotatsu.core.db.entity.MangaTagsEntity +import org.koitharu.kotatsu.core.db.entity.TagEntity + +class MangaWithTrack( + @Embedded val track: TrackEntity, + @Relation( + parentColumn = "manga_id", + entityColumn = "manga_id", + ) + val manga: MangaEntity, + @Relation( + parentColumn = "manga_id", + entityColumn = "tag_id", + associateBy = Junction(MangaTagsEntity::class), + ) + val tags: List, +) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TracksDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TracksDao.kt index 31b58b86d..02ca68098 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TracksDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TracksDao.kt @@ -6,7 +6,6 @@ import androidx.room.Query import androidx.room.Transaction import androidx.room.Upsert import kotlinx.coroutines.flow.Flow -import org.koitharu.kotatsu.core.db.entity.MangaWithTags @Dao abstract class TracksDao { @@ -47,12 +46,12 @@ abstract class TracksDao { abstract fun observeNewChapters(mangaId: Long): Flow @Transaction - @Query("SELECT manga.* FROM tracks LEFT JOIN manga ON manga.manga_id = tracks.manga_id WHERE chapters_new > 0 ORDER BY last_chapter_date DESC") - abstract fun observeUpdatedManga(): Flow> + @Query("SELECT * FROM tracks WHERE chapters_new > 0 ORDER BY last_chapter_date DESC") + abstract fun observeUpdatedManga(): Flow> @Transaction - @Query("SELECT manga.* FROM tracks LEFT JOIN manga ON manga.manga_id = tracks.manga_id WHERE chapters_new > 0 ORDER BY last_chapter_date DESC LIMIT :limit") - abstract fun observeUpdatedManga(limit: Int): Flow> + @Query("SELECT * FROM tracks WHERE chapters_new > 0 ORDER BY last_chapter_date DESC LIMIT :limit") + abstract fun observeUpdatedManga(limit: Int): Flow> @Query("DELETE FROM tracks") abstract suspend fun clear() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt index 4ca432c0a..06a4999c9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.onStart import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.entity.MangaEntity 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.core.prefs.AppSettings import org.koitharu.kotatsu.core.util.ext.ifZero @@ -59,13 +60,20 @@ class TrackingRepository @Inject constructor( .onStart { gcIfNotCalled() } } - fun observeUpdatedManga(limit: Int = 0): Flow> { + fun observeUpdatedManga(limit: Int = 0): Flow> { return if (limit == 0) { db.getTracksDao().observeUpdatedManga() } else { db.getTracksDao().observeUpdatedManga(limit) - }.mapItems { it.toManga() } - .distinctUntilChanged() + }.mapItems { + MangaTracking( + manga = it.manga.toManga(it.tags.toMangaTags()), + lastChapterId = it.track.lastChapterId, + lastCheck = it.track.lastCheckTime.toInstantOrNull(), + lastChapterDate = it.track.lastChapterDate.toInstantOrNull(), + newChapters = it.track.newChapters, + ) + }.distinctUntilChanged() .onStart { gcIfNotCalled() } } @@ -79,6 +87,8 @@ class TrackingRepository @Inject constructor( manga = it.manga.toManga(emptySet()), lastChapterId = it.track.lastChapterId, lastCheck = it.track.lastCheckTime.toInstantOrNull(), + lastChapterDate = it.track.lastChapterDate.toInstantOrNull(), + newChapters = it.track.newChapters, ) } } @@ -90,6 +100,8 @@ class TrackingRepository @Inject constructor( manga = manga, lastChapterId = track?.lastChapterId ?: NO_ID, lastCheck = track?.lastCheckTime?.toInstantOrNull(), + lastChapterDate = track?.lastChapterDate?.toInstantOrNull(), + newChapters = track?.newChapters ?: 0, ) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/model/MangaTracking.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/model/MangaTracking.kt index 9f11991b8..9f67e3629 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/model/MangaTracking.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/model/MangaTracking.kt @@ -7,7 +7,10 @@ data class MangaTracking( val manga: Manga, val lastChapterId: Long, val lastCheck: Instant?, + val lastChapterDate: Instant?, + val newChapters: Int, ) { + fun isEmpty(): Boolean { return lastChapterId == 0L } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt index 2bdd92dc8..11f0d0aa5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt @@ -11,11 +11,15 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import coil.ImageLoader import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.drop import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.list.PaginationScrollListener +import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper +import org.koitharu.kotatsu.core.ui.util.MenuInvalidator import org.koitharu.kotatsu.core.ui.widgets.TipView +import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent @@ -60,20 +64,15 @@ class FeedFragment : setHasFixedSize(true) addOnScrollListener(PaginationScrollListener(4, this@FeedFragment)) addItemDecoration(TypedListSpacingDecoration(context, true)) + RecyclerScrollKeeper(this).attach() } binding.swipeRefreshLayout.setOnRefreshListener(this) - addMenuProvider( - FeedMenuProvider( - binding.recyclerView, - viewModel, - ), - ) + addMenuProvider(FeedMenuProvider(binding.recyclerView, viewModel)) + viewModel.isHeaderEnabled.drop(1).observe(viewLifecycleOwner, MenuInvalidator(requireActivity())) viewModel.content.observe(viewLifecycleOwner, this::onListChanged) viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) - viewModel.onFeedCleared.observeEvent(viewLifecycleOwner) { - onFeedCleared() - } + viewModel.onFeedCleared.observeEvent(viewLifecycleOwner) { onFeedCleared() } viewModel.isRunning.observe(viewLifecycleOwner, this::onIsTrackerRunningChanged) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedMenuProvider.kt index 9ec9c545b..26f4444de 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedMenuProvider.kt @@ -22,12 +22,22 @@ class FeedMenuProvider( menuInflater.inflate(R.menu.opt_feed, menu) } + override fun onPrepareMenu(menu: Menu) { + super.onPrepareMenu(menu) + menu.findItem(R.id.action_show_updated)?.isChecked = viewModel.isHeaderEnabled.value + } + override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) { R.id.action_update -> { viewModel.update() true } + R.id.action_show_updated -> { + viewModel.setHeaderEnabled(!menuItem.isChecked) + true + } + R.id.action_clear_feed -> { CheckBoxAlertDialog.Builder(context) .setTitle(R.string.clear_updates_feed) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedViewModel.kt index 48ac6dd48..5d4687f91 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedViewModel.kt @@ -6,22 +6,25 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.plus import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.prefs.ListMode +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.observeAsStateFlow import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.model.DateTimeAgo import org.koitharu.kotatsu.core.util.ext.MutableEventFlow -import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.calculateTimeAgo +import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.list.domain.ListExtraProvider import org.koitharu.kotatsu.list.ui.model.EmptyState import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.LoadingState -import org.koitharu.kotatsu.list.ui.model.toUi +import org.koitharu.kotatsu.list.ui.model.toGridModel import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem import org.koitharu.kotatsu.tracker.ui.feed.model.UpdatedMangaHeader @@ -34,6 +37,7 @@ private const val PAGE_SIZE = 20 @HiltViewModel class FeedViewModel @Inject constructor( + private val settings: AppSettings, private val repository: TrackingRepository, private val scheduler: TrackWorker.Scheduler, private val listExtraProvider: ListExtraProvider, @@ -45,6 +49,12 @@ class FeedViewModel @Inject constructor( val isRunning = scheduler.observeIsRunning() .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false) + val isHeaderEnabled = settings.observeAsStateFlow( + scope = viewModelScope + Dispatchers.Default, + key = AppSettings.KEY_FEED_HEADER, + valueProducer = { isFeedHeaderVisible }, + ) + val onFeedCleared = MutableEventFlow() val content = combine( observeHeader(), @@ -94,6 +104,10 @@ class FeedViewModel @Inject constructor( scheduler.startNow() } + fun setHeaderEnabled(value: Boolean) { + settings.isFeedHeaderVisible = value + } + private fun List.mapListTo(destination: MutableList) { var prevDate: DateTimeAgo? = null for (item in this) { @@ -106,11 +120,19 @@ class FeedViewModel @Inject constructor( } } - private fun observeHeader() = repository.observeUpdatedManga(10).map { mangaList -> - if (mangaList.isEmpty()) { - null + private fun observeHeader() = isHeaderEnabled.flatMapLatest { hasHeader -> + if (hasHeader) { + repository.observeUpdatedManga(10).map { mangaList -> + if (mangaList.isEmpty()) { + null + } else { + UpdatedMangaHeader( + mangaList.map { it.manga.toGridModel(listExtraProvider) }, + ) + } + } } else { - UpdatedMangaHeader(mangaList.toUi(ListMode.GRID, listExtraProvider)) + flowOf(null) } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesViewModel.kt index c6d9faf98..8bd8290be 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesViewModel.kt @@ -11,15 +11,24 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.plus import org.koitharu.kotatsu.R 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.ui.model.DateTimeAgo +import org.koitharu.kotatsu.core.util.ext.calculateTimeAgo import org.koitharu.kotatsu.core.util.ext.onFirst import org.koitharu.kotatsu.download.ui.worker.DownloadWorker import org.koitharu.kotatsu.list.domain.ListExtraProvider import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.model.EmptyState +import org.koitharu.kotatsu.list.ui.model.ListHeader +import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.list.ui.model.toErrorState -import org.koitharu.kotatsu.list.ui.model.toUi +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.tracker.domain.TrackingRepository +import org.koitharu.kotatsu.tracker.domain.model.MangaTracking import javax.inject.Inject @HiltViewModel @@ -32,8 +41,9 @@ class UpdatesViewModel @Inject constructor( override val content = combine( repository.observeUpdatedManga(), + settings.observeAsFlow(AppSettings.KEY_UPDATED_GROUPING) { isUpdatedGroupingEnabled }, listMode, - ) { mangaList, mode -> + ) { mangaList, grouping, mode -> when { mangaList.isEmpty() -> listOf( EmptyState( @@ -44,7 +54,7 @@ class UpdatesViewModel @Inject constructor( ), ) - else -> mangaList.toUi(mode, extraProvider) + else -> mangaList.toUi(mode, grouping) } }.onStart { loadingCounter.increment() @@ -69,4 +79,26 @@ class UpdatesViewModel @Inject constructor( repository.clearUpdates(ids) } } + + private suspend fun List.toUi(mode: ListMode, grouped: Boolean): List { + val result = ArrayList(if (grouped) (size * 1.4).toInt() else size) + var prevHeader: DateTimeAgo? = null + for (item in this) { + if (grouped) { + val header = item.lastChapterDate?.let { calculateTimeAgo(it) } + if (header != prevHeader) { + if (header != null) { + result += ListHeader(header) + } + prevHeader = header + } + } + result += when (mode) { + ListMode.LIST -> item.manga.toListModel(extraProvider) + ListMode.DETAILED_LIST -> item.manga.toListDetailedModel(extraProvider) + ListMode.GRID -> item.manga.toGridModel(extraProvider) + } + } + return result + } } diff --git a/app/src/main/res/menu/opt_feed.xml b/app/src/main/res/menu/opt_feed.xml index aff41be01..783741741 100644 --- a/app/src/main/res/menu/opt_feed.xml +++ b/app/src/main/res/menu/opt_feed.xml @@ -15,6 +15,13 @@ android:title="@string/update" app:showAsAction="never" /> + + Fix There is no permission to access manga on external storage Last used + Show updated