diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e662f0327..3faa30c69 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -68,6 +68,9 @@
+
diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt
index 5a86ecdc4..5b2789d6f 100644
--- a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainNavigationDelegate.kt
@@ -10,10 +10,10 @@ import com.google.android.material.navigation.NavigationBarView
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.explore.ui.ExploreFragment
-import org.koitharu.kotatsu.shelf.ui.ShelfFragment
import org.koitharu.kotatsu.settings.tools.ToolsFragment
-import org.koitharu.kotatsu.tracker.ui.FeedFragment
-import java.util.*
+import org.koitharu.kotatsu.shelf.ui.ShelfFragment
+import org.koitharu.kotatsu.tracker.ui.feed.FeedFragment
+import java.util.LinkedList
private const val TAG_PRIMARY = "primary"
diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfFragment.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfFragment.kt
index a59b7bfb5..c61f866f4 100644
--- a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfFragment.kt
@@ -11,7 +11,6 @@ import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
-import javax.inject.Inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.reverseAsync
import org.koitharu.kotatsu.base.ui.BaseFragment
@@ -23,15 +22,17 @@ import org.koitharu.kotatsu.databinding.FragmentShelfBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.favourites.ui.FavouritesActivity
import org.koitharu.kotatsu.history.ui.HistoryActivity
-import org.koitharu.kotatsu.shelf.ui.adapter.ShelfAdapter
-import org.koitharu.kotatsu.shelf.ui.adapter.ShelfListEventListener
-import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.shelf.ui.adapter.ShelfAdapter
+import org.koitharu.kotatsu.shelf.ui.adapter.ShelfListEventListener
+import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
+import org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity
import org.koitharu.kotatsu.utils.ext.addMenuProvider
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
+import javax.inject.Inject
@AndroidEntryPoint
class ShelfFragment :
@@ -102,6 +103,7 @@ class ShelfFragment :
val intent = when (section) {
is ShelfSectionModel.History -> HistoryActivity.newIntent(view.context)
is ShelfSectionModel.Favourites -> FavouritesActivity.newIntent(view.context, section.category)
+ is ShelfSectionModel.Updated -> UpdatesActivity.newIntent(view.context)
}
startActivity(intent)
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfSelectionCallback.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfSelectionCallback.kt
index f343ad825..6f167897e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfSelectionCallback.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfSelectionCallback.kt
@@ -11,10 +11,10 @@ import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet
-import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.flattenTo
+import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.invalidateNestedItemDecorations
@@ -41,8 +41,9 @@ class ShelfSelectionCallback(
mode: ActionMode,
menu: Menu,
): Boolean {
- menu.findItem(R.id.action_remove).isVisible =
- controller.peekCheckedIds().count { (_, v) -> v.isNotEmpty() } == 1
+ val checkedIds = controller.peekCheckedIds()
+ menu.findItem(R.id.action_remove).isVisible = checkedIds.none { (key, _) -> key is ShelfSectionModel.Updated }
+ && checkedIds.count { (_, v) -> v.isNotEmpty() } == 1
return super.onPrepareActionMode(controller, mode, menu)
}
@@ -57,25 +58,30 @@ class ShelfSelectionCallback(
mode.finish()
true
}
+
R.id.action_favourite -> {
FavouriteCategoriesBottomSheet.show(fragmentManager, collectSelectedItems(controller))
mode.finish()
true
}
+
R.id.action_save -> {
DownloadService.confirmAndStart(context, collectSelectedItems(controller))
mode.finish()
true
}
+
R.id.action_remove -> {
val (group, ids) = controller.snapshot().entries.singleOrNull { it.value.isNotEmpty() } ?: return false
when (group) {
is ShelfSectionModel.Favourites -> viewModel.removeFromFavourites(group.category, ids)
is ShelfSectionModel.History -> viewModel.removeFromHistory(ids)
+ is ShelfSectionModel.Updated -> return false
}
mode.finish()
true
}
+
else -> false
}
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfViewModel.kt
index 99164a2ab..3708d089b 100644
--- a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfViewModel.kt
@@ -4,8 +4,6 @@ import androidx.collection.ArraySet
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
-import java.util.*
-import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
@@ -15,22 +13,25 @@ import org.koitharu.kotatsu.base.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
-import org.koitharu.kotatsu.core.ui.DateTimeAgo
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.domain.MangaWithHistory
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
-import org.koitharu.kotatsu.shelf.domain.ShelfRepository
-import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.list.domain.ListExtraProvider
-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.LoadingState
+import org.koitharu.kotatsu.list.ui.model.toErrorState
+import org.koitharu.kotatsu.list.ui.model.toGridModel
+import org.koitharu.kotatsu.list.ui.model.toUi
+import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.shelf.domain.ShelfRepository
+import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.asFlowLiveData
-import org.koitharu.kotatsu.utils.ext.daysDiff
-
-private const val HISTORY_MAX_SEGMENTS = 2
+import javax.inject.Inject
@HiltViewModel
class ShelfViewModel @Inject constructor(
@@ -38,6 +39,7 @@ class ShelfViewModel @Inject constructor(
private val historyRepository: HistoryRepository,
private val favouritesRepository: FavouritesRepository,
private val trackingRepository: TrackingRepository,
+ private val localMangaRepository: LocalMangaRepository,
private val settings: AppSettings,
) : BaseViewModel(), ListExtraProvider {
@@ -46,8 +48,9 @@ class ShelfViewModel @Inject constructor(
val content: LiveData> = combine(
historyRepository.observeAllWithHistory(),
repository.observeFavourites(),
- ) { history, favourites ->
- mapList(history, favourites)
+ trackingRepository.observeUpdatedManga(),
+ ) { history, favourites, updated ->
+ mapList(history, favourites, updated)
}.catch { e ->
emit(listOf(e.toErrorState(canRetry = false)))
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
@@ -119,11 +122,15 @@ class ShelfViewModel @Inject constructor(
private suspend fun mapList(
history: List,
favourites: Map>,
+ updated: Map,
): List {
- val result = ArrayList(favourites.keys.size + 1)
+ val result = ArrayList(favourites.keys.size + 2)
if (history.isNotEmpty()) {
mapHistory(result, history)
}
+ if (updated.isNotEmpty()) {
+ mapUpdated(result, updated)
+ }
if (favourites.isNotEmpty()) {
mapFavourites(result, favourites)
}
@@ -135,7 +142,6 @@ class ShelfViewModel @Inject constructor(
actionStringRes = 0,
)
}
- result.trimToSize()
return result
}
@@ -144,23 +150,28 @@ class ShelfViewModel @Inject constructor(
list: List,
) {
val showPercent = settings.isReadingIndicatorsEnabled
- 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)
- }
- for ((timeAgo, subList) in groups) {
- destination += ShelfSectionModel.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,
- )
- }
+ destination += ShelfSectionModel.History(
+ items = list.map { (manga, history) ->
+ val counter = trackingRepository.getNewChaptersCount(manga.id)
+ val percent = if (showPercent) history.percent else PROGRESS_NONE
+ manga.toGridModel(counter, percent)
+ },
+ showAllButtonText = R.string.show_all,
+ )
+ }
+
+ private suspend fun mapUpdated(
+ destination: MutableList,
+ updated: Map,
+ ) {
+ val showPercent = settings.isReadingIndicatorsEnabled
+ destination += ShelfSectionModel.Updated(
+ items = updated.map { (manga, counter) ->
+ val percent = if (showPercent) getProgress(manga.id) else PROGRESS_NONE
+ manga.toGridModel(counter, percent)
+ },
+ showAllButtonText = R.string.show_all,
+ )
}
private suspend fun mapFavourites(
@@ -177,14 +188,4 @@ class ShelfViewModel @Inject constructor(
}
}
}
-
- private fun timeAgo(date: Date): DateTimeAgo {
- val diffDays = -date.daysDiff(System.currentTimeMillis())
- return when {
- diffDays < 1 -> DateTimeAgo.Today
- diffDays == 1 -> DateTimeAgo.Yesterday
- diffDays <= 3 -> DateTimeAgo.DaysAgo(diffDays)
- else -> DateTimeAgo.LongAgo
- }
- }
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/model/ShelfSectionModel.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/model/ShelfSectionModel.kt
index e9df8971c..a72bf0b47 100644
--- a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/model/ShelfSectionModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/model/ShelfSectionModel.kt
@@ -4,30 +4,29 @@ import android.content.res.Resources
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory
-import org.koitharu.kotatsu.core.ui.DateTimeAgo
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
-sealed class ShelfSectionModel(
- val items: List,
- @StringRes val showAllButtonText: Int,
-) : ListModel {
+sealed interface ShelfSectionModel : ListModel {
- abstract val key: Any
- abstract fun getTitle(resources: Resources): CharSequence
+ val items: List
+
+ @get:StringRes
+ val showAllButtonText: Int
+
+ val key: String
+ fun getTitle(resources: Resources): CharSequence
+
+ override fun toString(): String
class History(
- items: List,
- val timeAgo: DateTimeAgo?,
- showAllButtonText: Int,
- ) : ShelfSectionModel(items, showAllButtonText) {
+ override val items: List,
+ override val showAllButtonText: Int,
+ ) : ShelfSectionModel {
- override val key: Any
- get() = timeAgo?.javaClass ?: this::class.java
+ override val key = "history"
- override fun getTitle(resources: Resources): CharSequence {
- return timeAgo?.format(resources) ?: resources.getString(R.string.history)
- }
+ override fun getTitle(resources: Resources) = resources.getString(R.string.history)
override fun equals(other: Any?): Boolean {
if (this === other) return true
@@ -35,7 +34,6 @@ sealed class ShelfSectionModel(
other as History
- if (timeAgo != other.timeAgo) return false
if (showAllButtonText != other.showAllButtonText) return false
if (items != other.items) return false
@@ -44,28 +42,22 @@ sealed class ShelfSectionModel(
override fun hashCode(): Int {
var result = items.hashCode()
- result = 31 * result + (timeAgo?.hashCode() ?: 0)
result = 31 * result + showAllButtonText.hashCode()
return result
}
- override fun toString(): String {
- return "hist_$timeAgo"
- }
+ override fun toString(): String = key
}
class Favourites(
- items: List,
+ override val items: List,
val category: FavouriteCategory,
- showAllButtonText: Int,
- ) : ShelfSectionModel(items, showAllButtonText) {
+ override val showAllButtonText: Int,
+ ) : ShelfSectionModel {
- override val key: Any
- get() = category.id
+ override val key = "fav_${category.id}"
- override fun getTitle(resources: Resources): CharSequence {
- return category.title
- }
+ override fun getTitle(resources: Resources) = category.title
override fun equals(other: Any?): Boolean {
if (this === other) return true
@@ -87,8 +79,36 @@ sealed class ShelfSectionModel(
return result
}
- override fun toString(): String {
- return "fav_${category.id}"
+ override fun toString(): String = key
+ }
+
+ class Updated(
+ override val items: List,
+ override val showAllButtonText: Int,
+ ) : ShelfSectionModel {
+
+ override val key = "upd"
+
+ override fun getTitle(resources: Resources) = resources.getString(R.string.updated)
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as Updated
+
+ if (items != other.items) return false
+ if (showAllButtonText != other.showAllButtonText) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = items.hashCode()
+ result = 31 * result + showAllButtonText
+ return result
}
+
+ override fun toString(): String = key
}
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/data/TracksDao.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/data/TracksDao.kt
index 0eb482202..d248de40e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/tracker/data/TracksDao.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/tracker/data/TracksDao.kt
@@ -1,7 +1,14 @@
package org.koitharu.kotatsu.tracker.data
-import androidx.room.*
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.MapInfo
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Transaction
+import androidx.room.Update
import kotlinx.coroutines.flow.Flow
+import org.koitharu.kotatsu.core.db.entity.MangaWithTags
@Dao
abstract class TracksDao {
@@ -28,6 +35,11 @@ abstract class TracksDao {
@Query("SELECT chapters_new FROM tracks WHERE manga_id = :mangaId")
abstract fun observeNewChapters(mangaId: Long): Flow
+ @Transaction
+ @MapInfo(valueColumn = "chapters_new")
+ @Query("SELECT manga.*, chapters_new FROM tracks LEFT JOIN manga ON manga.manga_id = tracks.manga_id WHERE chapters_new > 0 ORDER BY chapters_new DESC")
+ abstract fun observeUpdatedManga(): Flow