Undo deletion from favourites
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user