Undo deletion from favourites

This commit is contained in:
Koitharu
2022-05-15 20:10:56 +03:00
parent f5db5c39c3
commit 318486d62b
8 changed files with 76 additions and 17 deletions

View File

@@ -87,6 +87,10 @@ abstract class FavouritesDao {
@Query("UPDATE favourites SET deleted_at = :now WHERE manga_id = :mangaId AND category_id = :categoryId")
abstract suspend fun delete(categoryId: Long, mangaId: Long, now: Long = System.currentTimeMillis())
suspend fun recover(mangaId: Long) = delete(mangaId, 0L)
suspend fun recover(categoryId: Long, mangaId: Long) = delete(categoryId, mangaId, 0L)
@Query("DELETE FROM favourites WHERE deleted_at != 0")
abstract suspend fun gc()

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.favourites.domain
import androidx.room.withTransaction
import kotlinx.coroutines.flow.*
import org.koitharu.kotatsu.base.domain.ReversibleHandle
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.*
import org.koitharu.kotatsu.core.model.FavouriteCategory
@@ -117,20 +118,22 @@ class FavouritesRepository(
}
}
suspend fun removeFromFavourites(ids: Collection<Long>) {
suspend fun removeFromFavourites(ids: Collection<Long>): ReversibleHandle {
db.withTransaction {
for (id in ids) {
db.favouritesDao.delete(id)
}
}
return ReversibleHandle { recoverToFavourites(ids) }
}
suspend fun removeFromCategory(categoryId: Long, ids: Collection<Long>) {
suspend fun removeFromCategory(categoryId: Long, ids: Collection<Long>): ReversibleHandle {
db.withTransaction {
for (id in ids) {
db.favouritesDao.delete(categoryId, id)
}
}
return ReversibleHandle { recoverToCategory(categoryId, ids) }
}
private fun observeOrder(categoryId: Long): Flow<SortOrder> {
@@ -139,4 +142,20 @@ class FavouritesRepository(
.map { x -> SortOrder(x.order, SortOrder.NEWEST) }
.distinctUntilChanged()
}
private suspend fun recoverToFavourites(ids: Collection<Long>) {
db.withTransaction {
for (id in ids) {
db.favouritesDao.recover(id)
}
}
}
private suspend fun recoverToCategory(categoryId: Long, ids: Collection<Long>) {
db.withTransaction {
for (id in ids) {
db.favouritesDao.recover(categoryId, id)
}
}
}
}

View File

@@ -7,9 +7,12 @@ import android.view.MenuItem
import android.view.View
import androidx.appcompat.view.ActionMode
import androidx.core.view.iterator
import com.google.android.material.snackbar.Snackbar
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.ReversibleHandle
import org.koitharu.kotatsu.base.domain.reverseAsync
import org.koitharu.kotatsu.core.ui.titleRes
import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
import org.koitharu.kotatsu.list.ui.MangaListFragment
@@ -30,6 +33,7 @@ class FavouritesListFragment : MangaListFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.sortOrder.observe(viewLifecycleOwner) { activity?.invalidateOptionsMenu() }
viewModel.onItemsRemoved.observe(viewLifecycleOwner, ::onItemsRemoved)
}
override fun onScrolledToEnd() = Unit
@@ -94,6 +98,15 @@ class FavouritesListFragment : MangaListFragment() {
}
}
private fun onItemsRemoved(reversibleHandle: ReversibleHandle) {
val message = viewModel.categoryName?.let {
getString(R.string.removed_from_s, it)
} ?: getString(R.string.removed_from_favourites)
Snackbar.make(binding.recyclerView, message, Snackbar.LENGTH_LONG)
.setAction(R.string.undo) { reversibleHandle.reverseAsync() }
.show()
}
companion object {
const val NO_ID = 0L

View File

@@ -7,7 +7,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.ReversibleHandle
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
@@ -19,6 +21,7 @@ import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
class FavouritesListViewModel(
@@ -28,6 +31,9 @@ class FavouritesListViewModel(
settings: AppSettings,
) : MangaListViewModel(settings), CountersProvider {
var categoryName: String? = null
private set
var sortOrder: LiveData<SortOrder?> = if (categoryId == NO_ID) {
MutableLiveData(null)
} else {
@@ -63,6 +69,20 @@ class FavouritesListViewModel(
emit(listOf(it.toErrorState(canRetry = false)))
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
val onItemsRemoved = SingleLiveEvent<ReversibleHandle>()
init {
if (categoryId != NO_ID) {
launchJob {
categoryName = withContext(Dispatchers.Default) {
runCatching {
repository.getCategory(categoryId).title
}.getOrNull()
}
}
}
}
override fun onRefresh() = Unit
override fun onRetry() = Unit
@@ -71,12 +91,13 @@ class FavouritesListViewModel(
if (ids.isEmpty()) {
return
}
launchJob {
if (categoryId == NO_ID) {
launchJob(Dispatchers.Default) {
val handle = if (categoryId == NO_ID) {
repository.removeFromFavourites(ids)
} else {
repository.removeFromCategory(categoryId, ids)
}
onItemsRemoved.postCall(handle)
}
}

View File

@@ -64,6 +64,8 @@ abstract class HistoryDao {
@Query("UPDATE history SET deleted_at = :now WHERE manga_id = :mangaId")
abstract suspend fun delete(mangaId: Long, now: Long = System.currentTimeMillis())
suspend fun recover(mangaId: Long) = delete(mangaId, 0L)
@Query("DELETE FROM history WHERE deleted_at != 0")
abstract suspend fun gc()

View File

@@ -94,24 +94,14 @@ class HistoryRepository(
db.historyDao.delete(manga.id)
}
suspend fun delete(ids: Collection<Long>) {
suspend fun delete(ids: Collection<Long>): ReversibleHandle {
db.withTransaction {
for (id in ids) {
db.historyDao.delete(id)
}
}
}
suspend fun deleteReversible(ids: Collection<Long>): ReversibleHandle {
val entities = db.withTransaction {
val entities = db.historyDao.findAll(ids.toList()).filterNotNull()
for (id in ids) {
db.historyDao.delete(id)
}
entities
}
return ReversibleHandle {
db.historyDao.upsert(entities)
recover(ids)
}
}
@@ -128,4 +118,12 @@ class HistoryRepository(
suspend fun getPopularTags(limit: Int): List<MangaTag> {
return db.historyDao.findPopularTags(limit).map { x -> x.toMangaTag() }
}
private suspend fun recover(ids: Collection<Long>) {
db.withTransaction {
for (id in ids) {
db.historyDao.recover(id)
}
}
}
}

View File

@@ -80,7 +80,7 @@ class HistoryListViewModel(
return
}
launchJob(Dispatchers.Default) {
val handle = repository.deleteReversible(ids) + ReversibleHandle {
val handle = repository.delete(ids) + ReversibleHandle {
shortcutsRepository.updateShortcuts()
}
shortcutsRepository.updateShortcuts()

View File

@@ -304,4 +304,6 @@
<string name="detect_reader_mode">Autodetect reader mode</string>
<string name="detect_reader_mode_summary">Automatically detect if manga is webtoon</string>
<string name="enter_email_text">Enter your email to continue</string>
<string name="removed_from_favourites">Removed from favourites</string>
<string name="removed_from_s">Removed from \"%s\"</string>
</resources>