Add updated manga to shelf
This commit is contained in:
@@ -68,6 +68,9 @@
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.history.ui.HistoryActivity"
|
||||
android:label="@string/history" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity"
|
||||
android:label="@string/updates" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.favourites.ui.FavouritesActivity"
|
||||
android:label="@string/favourites" />
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.list.domain.ListExtraProvider
|
||||
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.list.domain.ListExtraProvider
|
||||
import org.koitharu.kotatsu.list.ui.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
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<List<ListModel>> = 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<MangaWithHistory>,
|
||||
favourites: Map<FavouriteCategory, List<Manga>>,
|
||||
updated: Map<Manga, Int>,
|
||||
): List<ListModel> {
|
||||
val result = ArrayList<ListModel>(favourites.keys.size + 1)
|
||||
val result = ArrayList<ListModel>(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<MangaWithHistory>,
|
||||
) {
|
||||
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<in ShelfSectionModel.Updated>,
|
||||
updated: Map<Manga, Int>,
|
||||
) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MangaItemModel>,
|
||||
@StringRes val showAllButtonText: Int,
|
||||
) : ListModel {
|
||||
sealed interface ShelfSectionModel : ListModel {
|
||||
|
||||
abstract val key: Any
|
||||
abstract fun getTitle(resources: Resources): CharSequence
|
||||
val items: List<MangaItemModel>
|
||||
|
||||
@get:StringRes
|
||||
val showAllButtonText: Int
|
||||
|
||||
val key: String
|
||||
fun getTitle(resources: Resources): CharSequence
|
||||
|
||||
override fun toString(): String
|
||||
|
||||
class History(
|
||||
items: List<MangaItemModel>,
|
||||
val timeAgo: DateTimeAgo?,
|
||||
showAllButtonText: Int,
|
||||
) : ShelfSectionModel(items, showAllButtonText) {
|
||||
override val items: List<MangaItemModel>,
|
||||
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<MangaItemModel>,
|
||||
override val items: List<MangaItemModel>,
|
||||
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<MangaItemModel>,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Int?>
|
||||
|
||||
@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<Map<MangaWithTags, Int>>
|
||||
|
||||
@Query("DELETE FROM tracks")
|
||||
abstract suspend fun clear()
|
||||
|
||||
|
||||
@@ -2,10 +2,9 @@ package org.koitharu.kotatsu.tracker.domain
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.room.withTransaction
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
@@ -22,6 +21,8 @@ import org.koitharu.kotatsu.tracker.data.toTrackingLogItem
|
||||
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
|
||||
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
|
||||
import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val NO_ID = 0L
|
||||
|
||||
@@ -41,6 +42,12 @@ class TrackingRepository @Inject constructor(
|
||||
return db.tracksDao.observeNewChapters().map { list -> list.count { it > 0 } }
|
||||
}
|
||||
|
||||
fun observeUpdatedManga(): Flow<Map<Manga, Int>> {
|
||||
return db.tracksDao.observeUpdatedManga()
|
||||
.map { x -> x.mapKeys { it.key.toManga() } }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
suspend fun getTracks(mangaList: Collection<Manga>): List<MangaTracking> {
|
||||
val ids = mangaList.mapToSet { it.id }
|
||||
val tracks = db.tracksDao.findAll(ids).groupBy { it.mangaId }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.tracker.ui
|
||||
package org.koitharu.kotatsu.tracker.ui.feed
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
@@ -10,7 +10,6 @@ import androidx.fragment.app.viewModels
|
||||
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.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener
|
||||
@@ -23,11 +22,12 @@ 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.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.tracker.ui.adapter.FeedAdapter
|
||||
import org.koitharu.kotatsu.tracker.ui.feed.adapter.FeedAdapter
|
||||
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
||||
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.getThemeColor
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class FeedFragment :
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.tracker.ui
|
||||
package org.koitharu.kotatsu.tracker.ui.feed
|
||||
|
||||
import android.content.Context
|
||||
import android.view.Menu
|
||||
@@ -37,6 +37,7 @@ class FeedMenuProvider(
|
||||
snackbar.show()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_clear_feed -> {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.clear_updates_feed)
|
||||
@@ -47,11 +48,13 @@ class FeedMenuProvider(
|
||||
}.show()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_settings -> {
|
||||
val intent = SettingsActivity.newTrackerSettingsIntent(context)
|
||||
context.startActivity(intent)
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
package org.koitharu.kotatsu.tracker.ui
|
||||
package org.koitharu.kotatsu.tracker.ui.feed
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -17,10 +13,14 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
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.tracker.ui.feed.model.toFeedItem
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.asFlowLiveData
|
||||
import org.koitharu.kotatsu.utils.ext.daysDiff
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val PAGE_SIZE = 20
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
package org.koitharu.kotatsu.tracker.ui.adapter
|
||||
package org.koitharu.kotatsu.tracker.ui.feed.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
import org.koitharu.kotatsu.core.ui.DateTimeAgo
|
||||
import org.koitharu.kotatsu.list.ui.adapter.*
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
|
||||
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
|
||||
import org.koitharu.kotatsu.list.ui.adapter.errorFooterAD
|
||||
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.adapter.relatedDateItemAD
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.tracker.ui.model.FeedItem
|
||||
import org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
|
||||
class FeedAdapter(
|
||||
coil: ImageLoader,
|
||||
@@ -33,9 +39,11 @@ class FeedAdapter(
|
||||
oldItem is FeedItem && newItem is FeedItem -> {
|
||||
oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
oldItem is DateTimeAgo && newItem is DateTimeAgo -> {
|
||||
oldItem == newItem
|
||||
}
|
||||
|
||||
else -> oldItem.javaClass == newItem.javaClass
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.tracker.ui.adapter
|
||||
package org.koitharu.kotatsu.tracker.ui.feed.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.databinding.ItemFeedBinding
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.tracker.ui.model.FeedItem
|
||||
import org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem
|
||||
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.utils.ext.isBold
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.tracker.ui.model
|
||||
package org.koitharu.kotatsu.tracker.ui.feed.model
|
||||
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.tracker.ui.model
|
||||
package org.koitharu.kotatsu.tracker.ui.feed.model
|
||||
|
||||
import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.koitharu.kotatsu.tracker.ui.updates
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.commit
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
|
||||
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
|
||||
|
||||
@AndroidEntryPoint
|
||||
class UpdatesActivity :
|
||||
BaseActivity<ActivityContainerBinding>(),
|
||||
AppBarOwner {
|
||||
|
||||
override val appBar: AppBarLayout
|
||||
get() = binding.appbar
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityContainerBinding.inflate(layoutInflater))
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
val fm = supportFragmentManager
|
||||
if (fm.findFragmentById(R.id.container) == null) {
|
||||
fm.commit {
|
||||
setReorderingAllowed(true)
|
||||
val fragment = UpdatesFragment.newInstance()
|
||||
replace(R.id.container, fragment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
binding.root.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newIntent(context: Context) = Intent(context, UpdatesActivity::class.java)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.koitharu.kotatsu.tracker.ui.updates
|
||||
|
||||
import androidx.fragment.app.viewModels
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||
|
||||
@AndroidEntryPoint
|
||||
class UpdatesFragment : MangaListFragment() {
|
||||
|
||||
override val viewModel by viewModels<UpdatesViewModel>()
|
||||
override val isSwipeRefreshEnabled = false
|
||||
|
||||
override fun onScrolledToEnd() = Unit
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance() = UpdatesFragment()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.koitharu.kotatsu.tracker.ui.updates
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
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.toListDetailedModel
|
||||
import org.koitharu.kotatsu.list.ui.model.toListModel
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.asFlowLiveData
|
||||
import org.koitharu.kotatsu.utils.ext.onFirst
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class UpdatesViewModel @Inject constructor(
|
||||
private val repository: TrackingRepository,
|
||||
private val settings: AppSettings,
|
||||
private val historyRepository: HistoryRepository,
|
||||
) : MangaListViewModel(settings) {
|
||||
|
||||
override val content = combine(
|
||||
repository.observeUpdatedManga(),
|
||||
createListModeFlow(),
|
||||
) { mangaMap, mode ->
|
||||
when {
|
||||
mangaMap.isEmpty() -> listOf(
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_empty_history,
|
||||
textPrimary = R.string.text_history_holder_primary,
|
||||
textSecondary = R.string.text_history_holder_secondary,
|
||||
actionStringRes = 0,
|
||||
),
|
||||
)
|
||||
|
||||
else -> mapList(mangaMap, mode)
|
||||
}
|
||||
}.onStart {
|
||||
loadingCounter.increment()
|
||||
}.onFirst {
|
||||
loadingCounter.decrement()
|
||||
}.catch {
|
||||
emit(listOf(it.toErrorState(canRetry = false)))
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
||||
|
||||
override fun onRefresh() = Unit
|
||||
|
||||
override fun onRetry() = Unit
|
||||
|
||||
private suspend fun mapList(
|
||||
mangaMap: Map<Manga, Int>,
|
||||
mode: ListMode,
|
||||
): List<ListModel> {
|
||||
val showPercent = settings.isReadingIndicatorsEnabled
|
||||
return mangaMap.map { (manga, counter) ->
|
||||
val percent = if (showPercent) historyRepository.getProgress(manga.id) else PROGRESS_NONE
|
||||
when (mode) {
|
||||
ListMode.LIST -> manga.toListModel(counter, percent)
|
||||
ListMode.DETAILED_LIST -> manga.toListDetailedModel(counter, percent)
|
||||
ListMode.GRID -> manga.toGridModel(counter, percent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user