Add unread field to feed items
This commit is contained in:
@@ -16,9 +16,15 @@ interface TrackLogsDao {
|
|||||||
@Query("SELECT * FROM track_logs ORDER BY created_at DESC LIMIT :limit OFFSET 0")
|
@Query("SELECT * FROM track_logs ORDER BY created_at DESC LIMIT :limit OFFSET 0")
|
||||||
fun observeAll(limit: Int): Flow<List<TrackLogWithManga>>
|
fun observeAll(limit: Int): Flow<List<TrackLogWithManga>>
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(*) FROM track_logs WHERE unread = 1")
|
||||||
|
fun observeUnreadCount(): Flow<Int>
|
||||||
|
|
||||||
@Query("DELETE FROM track_logs")
|
@Query("DELETE FROM track_logs")
|
||||||
suspend fun clear()
|
suspend fun clear()
|
||||||
|
|
||||||
|
@Query("UPDATE track_logs SET unread = 0 WHERE id = :id")
|
||||||
|
suspend fun markAsRead(id: Long)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insert(entity: TrackLogEntity): Long
|
suspend fun insert(entity: TrackLogEntity): Long
|
||||||
|
|
||||||
|
|||||||
@@ -12,5 +12,7 @@ class Migration19To20 : Migration(19, 20) {
|
|||||||
db.execSQL("CREATE TABLE tracks (`manga_id` INTEGER NOT NULL, `last_chapter_id` INTEGER NOT NULL, `chapters_new` INTEGER NOT NULL, `last_check_time` INTEGER NOT NULL, `last_chapter_date` INTEGER NOT NULL, `last_result` INTEGER NOT NULL, PRIMARY KEY(`manga_id`), FOREIGN KEY(`manga_id`) REFERENCES `manga`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
db.execSQL("CREATE TABLE tracks (`manga_id` INTEGER NOT NULL, `last_chapter_id` INTEGER NOT NULL, `chapters_new` INTEGER NOT NULL, `last_check_time` INTEGER NOT NULL, `last_chapter_date` INTEGER NOT NULL, `last_result` INTEGER NOT NULL, PRIMARY KEY(`manga_id`), FOREIGN KEY(`manga_id`) REFERENCES `manga`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
|
||||||
db.execSQL("INSERT INTO tracks SELECT manga_id, last_chapter_id, chapters_new, last_check AS last_check_time, 0 AS last_chapter_date, 0 AS last_result FROM tracks_bk")
|
db.execSQL("INSERT INTO tracks SELECT manga_id, last_chapter_id, chapters_new, last_check AS last_check_time, 0 AS last_chapter_date, 0 AS last_result FROM tracks_bk")
|
||||||
db.execSQL("DROP TABLE tracks_bk")
|
db.execSQL("DROP TABLE tracks_bk")
|
||||||
|
|
||||||
|
db.execSQL("ALTER TABLE track_logs ADD COLUMN `unread` INTEGER NOT NULL DEFAULT 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.koitharu.kotatsu.core.util
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.report
|
||||||
|
import kotlin.coroutines.AbstractCoroutineContextElement
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
class AcraCoroutineErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler),
|
||||||
|
CoroutineExceptionHandler {
|
||||||
|
|
||||||
|
override fun handleException(context: CoroutineContext, exception: Throwable) {
|
||||||
|
exception.printStackTraceDebug()
|
||||||
|
exception.report()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.core.util.ext
|
package org.koitharu.kotatsu.core.util.ext
|
||||||
|
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
|
||||||
import androidx.lifecycle.LifecycleDestroyedException
|
import androidx.lifecycle.LifecycleDestroyedException
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.LifecycleObserver
|
import androidx.lifecycle.LifecycleObserver
|
||||||
@@ -10,17 +9,20 @@ import androidx.lifecycle.ProcessLifecycleOwner
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import dagger.hilt.android.lifecycle.RetainedLifecycle
|
import dagger.hilt.android.lifecycle.RetainedLifecycle
|
||||||
import kotlinx.coroutines.CancellableContinuation
|
import kotlinx.coroutines.CancellableContinuation
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.plus
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import org.koitharu.kotatsu.core.util.AcraCoroutineErrorHandler
|
||||||
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
val processLifecycleScope: LifecycleCoroutineScope
|
val processLifecycleScope: CoroutineScope
|
||||||
inline get() = ProcessLifecycleOwner.get().lifecycleScope
|
get() = ProcessLifecycleOwner.get().lifecycleScope + AcraCoroutineErrorHandler()
|
||||||
|
|
||||||
val RetainedLifecycle.lifecycleScope: RetainedLifecycleCoroutineScope
|
val RetainedLifecycle.lifecycleScope: RetainedLifecycleCoroutineScope
|
||||||
inline get() = RetainedLifecycleCoroutineScope(this)
|
inline get() = RetainedLifecycleCoroutineScope(this)
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class ExploreViewModel @Inject constructor(
|
|||||||
sourcesRepository.observeNewSources(),
|
sourcesRepository.observeNewSources(),
|
||||||
) { content, suggestions, grid, randomLoading, newSources ->
|
) { content, suggestions, grid, randomLoading, newSources ->
|
||||||
buildList(content, suggestions, grid, randomLoading, newSources)
|
buildList(content, suggestions, grid, randomLoading, newSources)
|
||||||
}
|
}.withErrorHandling()
|
||||||
|
|
||||||
private fun buildList(
|
private fun buildList(
|
||||||
sources: List<MangaSource>,
|
sources: List<MangaSource>,
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ class FavouritesCategoriesViewModel @Inject constructor(
|
|||||||
settings.observeAsFlow(AppSettings.KEY_ALL_FAVOURITES_VISIBLE) { isAllFavouritesVisible },
|
settings.observeAsFlow(AppSettings.KEY_ALL_FAVOURITES_VISIBLE) { isAllFavouritesVisible },
|
||||||
) { cats, all, showAll ->
|
) { cats, all, showAll ->
|
||||||
cats.toUiList(all, showAll)
|
cats.toUiList(all, showAll)
|
||||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
|
}.withErrorHandling()
|
||||||
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
|
||||||
|
|
||||||
fun deleteCategories(ids: Set<Long>) {
|
fun deleteCategories(ids: Set<Long>) {
|
||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class FavouritesListViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
repository.observeCategory(categoryId)
|
repository.observeCategory(categoryId)
|
||||||
|
.withErrorHandling()
|
||||||
.map { it?.order }
|
.map { it?.order }
|
||||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||||
|
|
||||||
|
|||||||
@@ -277,7 +277,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
|||||||
|
|
||||||
private fun onFeedCounterChanged(counter: Int) {
|
private fun onFeedCounterChanged(counter: Int) {
|
||||||
navigationDelegate.setCounter(NavItem.FEED, counter)
|
navigationDelegate.setCounter(NavItem.FEED, counter)
|
||||||
navigationDelegate.setCounter(NavItem.UPDATED, counter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onIncognitoModeChanged(isIncognito: Boolean) {
|
private fun onIncognitoModeChanged(isIncognito: Boolean) {
|
||||||
|
|||||||
@@ -32,15 +32,18 @@ class MainViewModel @Inject constructor(
|
|||||||
val onOpenReader = MutableEventFlow<Manga>()
|
val onOpenReader = MutableEventFlow<Manga>()
|
||||||
val onFirstStart = MutableEventFlow<Unit>()
|
val onFirstStart = MutableEventFlow<Unit>()
|
||||||
|
|
||||||
val isResumeEnabled = readingResumeEnabledUseCase().stateIn(
|
val isResumeEnabled = readingResumeEnabledUseCase()
|
||||||
scope = viewModelScope + Dispatchers.Default,
|
.withErrorHandling()
|
||||||
started = SharingStarted.WhileSubscribed(5000),
|
.stateIn(
|
||||||
initialValue = false,
|
scope = viewModelScope + Dispatchers.Default,
|
||||||
)
|
started = SharingStarted.WhileSubscribed(5000),
|
||||||
|
initialValue = false,
|
||||||
|
)
|
||||||
|
|
||||||
val appUpdate = appUpdateRepository.observeAvailableUpdate()
|
val appUpdate = appUpdateRepository.observeAvailableUpdate()
|
||||||
|
|
||||||
val feedCounter = trackingRepository.observeUpdatedMangaCount()
|
val feedCounter = trackingRepository.observeUnreadUpdatesCount()
|
||||||
|
.withErrorHandling()
|
||||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, 0)
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, 0)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class SearchSuggestionViewModel @Inject constructor(
|
|||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
.onEach {
|
.onEach {
|
||||||
suggestion.value = it
|
suggestion.value = it
|
||||||
}.launchIn(viewModelScope + Dispatchers.Default)
|
}.withErrorHandling().launchIn(viewModelScope + Dispatchers.Default)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun buildSearchSuggestion(
|
private suspend fun buildSearchSuggestion(
|
||||||
|
|||||||
@@ -18,5 +18,6 @@ class RootSettingsViewModel @Inject constructor(
|
|||||||
val totalSourcesCount = sourcesRepository.allMangaSources.size
|
val totalSourcesCount = sourcesRepository.allMangaSources.size
|
||||||
|
|
||||||
val enabledSourcesCount = sourcesRepository.observeEnabledSourcesCount()
|
val enabledSourcesCount = sourcesRepository.observeEnabledSourcesCount()
|
||||||
|
.withErrorHandling()
|
||||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ class SourcesSettingsViewModel @Inject constructor(
|
|||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val enabledSourcesCount = sourcesRepository.observeEnabledSourcesCount()
|
val enabledSourcesCount = sourcesRepository.observeEnabledSourcesCount()
|
||||||
|
.withErrorHandling()
|
||||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)
|
||||||
|
|
||||||
val availableSourcesCount = sourcesRepository.observeAvailableSourcesCount()
|
val availableSourcesCount = sourcesRepository.observeAvailableSourcesCount()
|
||||||
|
.withErrorHandling()
|
||||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, -1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,26 +5,13 @@ import org.koitharu.kotatsu.core.db.entity.toMangaTags
|
|||||||
import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem
|
import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
fun TrackLogWithManga.toTrackingLogItem(counters: MutableMap<Long, Int>): TrackingLogItem {
|
fun TrackLogWithManga.toTrackingLogItem(): TrackingLogItem {
|
||||||
val chaptersList = trackLog.chapters.split('\n').filterNot { x -> x.isEmpty() }
|
val chaptersList = trackLog.chapters.split('\n').filterNot { x -> x.isEmpty() }
|
||||||
return TrackingLogItem(
|
return TrackingLogItem(
|
||||||
id = trackLog.id,
|
id = trackLog.id,
|
||||||
chapters = chaptersList,
|
chapters = chaptersList,
|
||||||
manga = manga.toManga(tags.toMangaTags()),
|
manga = manga.toManga(tags.toMangaTags()),
|
||||||
createdAt = Instant.ofEpochMilli(trackLog.createdAt),
|
createdAt = Instant.ofEpochMilli(trackLog.createdAt),
|
||||||
isNew = counters.decrement(trackLog.mangaId, chaptersList.size),
|
isNew = trackLog.isUnread,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableMap<Long, Int>.decrement(key: Long, count: Int): Boolean = synchronized(this) {
|
|
||||||
val counter = get(key)
|
|
||||||
if (counter == null || counter <= 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (counter < count) {
|
|
||||||
remove(key)
|
|
||||||
} else {
|
|
||||||
put(key, counter - count)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,4 +23,5 @@ class TrackLogEntity(
|
|||||||
@ColumnInfo(name = "manga_id", index = true) val mangaId: Long,
|
@ColumnInfo(name = "manga_id", index = true) val mangaId: Long,
|
||||||
@ColumnInfo(name = "chapters") val chapters: String,
|
@ColumnInfo(name = "chapters") val chapters: String,
|
||||||
@ColumnInfo(name = "created_at") val createdAt: Long,
|
@ColumnInfo(name = "created_at") val createdAt: Long,
|
||||||
|
@ColumnInfo(name = "unread") val isUnread: Boolean,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import androidx.annotation.VisibleForTesting
|
|||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import dagger.Reusable
|
import dagger.Reusable
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -55,11 +54,16 @@ class TrackingRepository @Inject constructor(
|
|||||||
return db.getTracksDao().observeNewChapters(mangaId).map { it ?: 0 }
|
return db.getTracksDao().observeNewChapters(mangaId).map { it ?: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("")
|
||||||
fun observeUpdatedMangaCount(): Flow<Int> {
|
fun observeUpdatedMangaCount(): Flow<Int> {
|
||||||
return db.getTracksDao().observeNewChapters().map { list -> list.count { it > 0 } }
|
return db.getTracksDao().observeNewChapters().map { list -> list.count { it > 0 } }
|
||||||
.onStart { gcIfNotCalled() }
|
.onStart { gcIfNotCalled() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun observeUnreadUpdatesCount(): Flow<Int> {
|
||||||
|
return db.getTrackLogsDao().observeUnreadCount()
|
||||||
|
}
|
||||||
|
|
||||||
fun observeUpdatedManga(limit: Int = 0): Flow<List<MangaTracking>> {
|
fun observeUpdatedManga(limit: Int = 0): Flow<List<MangaTracking>> {
|
||||||
return if (limit == 0) {
|
return if (limit == 0) {
|
||||||
db.getTracksDao().observeUpdatedManga()
|
db.getTracksDao().observeUpdatedManga()
|
||||||
@@ -112,13 +116,8 @@ class TrackingRepository @Inject constructor(
|
|||||||
|
|
||||||
fun observeTrackingLog(limit: Flow<Int>): Flow<List<TrackingLogItem>> {
|
fun observeTrackingLog(limit: Flow<Int>): Flow<List<TrackingLogItem>> {
|
||||||
return limit.flatMapLatest { limitValue ->
|
return limit.flatMapLatest { limitValue ->
|
||||||
combine(
|
db.getTrackLogsDao().observeAll(limitValue)
|
||||||
db.getTracksDao().observeNewChaptersMap(),
|
.mapItems { it.toTrackingLogItem() }
|
||||||
db.getTrackLogsDao().observeAll(limitValue),
|
|
||||||
) { counters, entities ->
|
|
||||||
val countersMap = counters.toMutableMap()
|
|
||||||
entities.map { x -> x.toTrackingLogItem(countersMap) }
|
|
||||||
}
|
|
||||||
}.onStart {
|
}.onStart {
|
||||||
gcIfNotCalled()
|
gcIfNotCalled()
|
||||||
}
|
}
|
||||||
@@ -130,6 +129,8 @@ class TrackingRepository @Inject constructor(
|
|||||||
|
|
||||||
suspend fun clearCounters() = db.getTracksDao().clearCounters()
|
suspend fun clearCounters() = db.getTracksDao().clearCounters()
|
||||||
|
|
||||||
|
suspend fun markAsRead(trackLogId: Long) = db.getTrackLogsDao().markAsRead(trackLogId)
|
||||||
|
|
||||||
suspend fun gc() = db.withTransaction {
|
suspend fun gc() = db.withTransaction {
|
||||||
db.getTracksDao().gc()
|
db.getTracksDao().gc()
|
||||||
db.getTrackLogsDao().run {
|
db.getTrackLogsDao().run {
|
||||||
@@ -148,6 +149,7 @@ class TrackingRepository @Inject constructor(
|
|||||||
mangaId = updates.manga.id,
|
mangaId = updates.manga.id,
|
||||||
chapters = updates.newChapters.joinToString("\n") { x -> x.name },
|
chapters = updates.newChapters.joinToString("\n") { x -> x.name },
|
||||||
createdAt = System.currentTimeMillis(),
|
createdAt = System.currentTimeMillis(),
|
||||||
|
isUnread = true,
|
||||||
)
|
)
|
||||||
db.getTrackLogsDao().insert(logEntity)
|
db.getTrackLogsDao().insert(logEntity)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ class FeedFragment :
|
|||||||
override fun onViewBindingCreated(binding: FragmentFeedBinding, savedInstanceState: Bundle?) {
|
override fun onViewBindingCreated(binding: FragmentFeedBinding, savedInstanceState: Bundle?) {
|
||||||
super.onViewBindingCreated(binding, savedInstanceState)
|
super.onViewBindingCreated(binding, savedInstanceState)
|
||||||
val sizeResolver = StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width))
|
val sizeResolver = StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width))
|
||||||
feedAdapter = FeedAdapter(coil, viewLifecycleOwner, this, sizeResolver)
|
feedAdapter = FeedAdapter(coil, viewLifecycleOwner, this, sizeResolver) { item, v ->
|
||||||
|
viewModel.onItemClick(item)
|
||||||
|
onItemClick(item.manga, v)
|
||||||
|
}
|
||||||
with(binding.recyclerView) {
|
with(binding.recyclerView) {
|
||||||
adapter = feedAdapter
|
adapter = feedAdapter
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.tracker.ui.feed
|
|||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.CoroutineStart
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
@@ -27,6 +28,7 @@ import org.koitharu.kotatsu.list.ui.model.LoadingState
|
|||||||
import org.koitharu.kotatsu.list.ui.model.toGridModel
|
import org.koitharu.kotatsu.list.ui.model.toGridModel
|
||||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||||
import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem
|
import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem
|
||||||
|
import org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem
|
||||||
import org.koitharu.kotatsu.tracker.ui.feed.model.UpdatedMangaHeader
|
import org.koitharu.kotatsu.tracker.ui.feed.model.UpdatedMangaHeader
|
||||||
import org.koitharu.kotatsu.tracker.ui.feed.model.toFeedItem
|
import org.koitharu.kotatsu.tracker.ui.feed.model.toFeedItem
|
||||||
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
||||||
@@ -108,6 +110,12 @@ class FeedViewModel @Inject constructor(
|
|||||||
settings.isFeedHeaderVisible = value
|
settings.isFeedHeaderVisible = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onItemClick(item: FeedItem) {
|
||||||
|
launchJob(Dispatchers.Default, CoroutineStart.ATOMIC) {
|
||||||
|
repository.markAsRead(item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun List<TrackingLogItem>.mapListTo(destination: MutableList<ListModel>) {
|
private fun List<TrackingLogItem>.mapListTo(destination: MutableList<ListModel>) {
|
||||||
var prevDate: DateTimeAgo? = null
|
var prevDate: DateTimeAgo? = null
|
||||||
for (item in this) {
|
for (item in this) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
|
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
||||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
|
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
|
||||||
@@ -15,16 +16,18 @@ import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
|
|||||||
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
|
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.list.ui.size.ItemSizeResolver
|
import org.koitharu.kotatsu.list.ui.size.ItemSizeResolver
|
||||||
|
import org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem
|
||||||
|
|
||||||
class FeedAdapter(
|
class FeedAdapter(
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
lifecycleOwner: LifecycleOwner,
|
lifecycleOwner: LifecycleOwner,
|
||||||
listener: MangaListListener,
|
listener: MangaListListener,
|
||||||
sizeResolver: ItemSizeResolver,
|
sizeResolver: ItemSizeResolver,
|
||||||
|
feedClickListener: OnListItemClickListener<FeedItem>,
|
||||||
) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {
|
) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
addDelegate(ListItemType.FEED, feedItemAD(coil, lifecycleOwner, listener))
|
addDelegate(ListItemType.FEED, feedItemAD(coil, lifecycleOwner, feedClickListener))
|
||||||
addDelegate(
|
addDelegate(
|
||||||
ListItemType.MANGA_NESTED_GROUP,
|
ListItemType.MANGA_NESTED_GROUP,
|
||||||
updatedMangaAD(
|
updatedMangaAD(
|
||||||
|
|||||||
@@ -12,20 +12,19 @@ import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
|||||||
import org.koitharu.kotatsu.core.util.ext.source
|
import org.koitharu.kotatsu.core.util.ext.source
|
||||||
import org.koitharu.kotatsu.databinding.ItemFeedBinding
|
import org.koitharu.kotatsu.databinding.ItemFeedBinding
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
|
||||||
import org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem
|
import org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem
|
||||||
|
|
||||||
fun feedItemAD(
|
fun feedItemAD(
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
lifecycleOwner: LifecycleOwner,
|
lifecycleOwner: LifecycleOwner,
|
||||||
clickListener: OnListItemClickListener<Manga>,
|
clickListener: OnListItemClickListener<FeedItem>,
|
||||||
) = adapterDelegateViewBinding<FeedItem, ListModel, ItemFeedBinding>(
|
) = adapterDelegateViewBinding<FeedItem, ListModel, ItemFeedBinding>(
|
||||||
{ inflater, parent -> ItemFeedBinding.inflate(inflater, parent, false) },
|
{ inflater, parent -> ItemFeedBinding.inflate(inflater, parent, false) },
|
||||||
) {
|
) {
|
||||||
val indicatorNew = ContextCompat.getDrawable(context, R.drawable.ic_new)
|
val indicatorNew = ContextCompat.getDrawable(context, R.drawable.ic_new)
|
||||||
|
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
clickListener.onItemClick(item.manga, it)
|
clickListener.onItemClick(item, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
bind {
|
bind {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.tracker.ui.feed.model
|
package org.koitharu.kotatsu.tracker.ui.feed.model
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
|
||||||
@@ -11,7 +12,14 @@ data class FeedItem(
|
|||||||
val count: Int,
|
val count: Int,
|
||||||
val isNew: Boolean,
|
val isNew: Boolean,
|
||||||
) : ListModel {
|
) : ListModel {
|
||||||
|
|
||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is FeedItem && other.id == id
|
return other is FeedItem && other.id == id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getChangePayload(previousState: ListModel): Any? = when {
|
||||||
|
previousState !is FeedItem -> null
|
||||||
|
isNew != previousState.isNew -> ListModelDiffCallback.PAYLOAD_ANYTHING_CHANGED
|
||||||
|
else -> super.getChangePayload(previousState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user