diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt index 3ac780436..a462afeb1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt @@ -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() diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt index a1cbf8cf9..bfaae8474 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt @@ -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) { + suspend fun removeFromFavourites(ids: Collection): ReversibleHandle { db.withTransaction { for (id in ids) { db.favouritesDao.delete(id) } } + return ReversibleHandle { recoverToFavourites(ids) } } - suspend fun removeFromCategory(categoryId: Long, ids: Collection) { + suspend fun removeFromCategory(categoryId: Long, ids: Collection): ReversibleHandle { db.withTransaction { for (id in ids) { db.favouritesDao.delete(categoryId, id) } } + return ReversibleHandle { recoverToCategory(categoryId, ids) } } private fun observeOrder(categoryId: Long): Flow { @@ -139,4 +142,20 @@ class FavouritesRepository( .map { x -> SortOrder(x.order, SortOrder.NEWEST) } .distinctUntilChanged() } + + private suspend fun recoverToFavourites(ids: Collection) { + db.withTransaction { + for (id in ids) { + db.favouritesDao.recover(id) + } + } + } + + private suspend fun recoverToCategory(categoryId: Long, ids: Collection) { + db.withTransaction { + for (id in ids) { + db.favouritesDao.recover(categoryId, id) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt index 8d4b9e419..6d31df17d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListFragment.kt @@ -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 diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt index c2e8c00a1..dce0a8803 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt @@ -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 = 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() + + 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) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt index cc176a057..2b11002f4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt @@ -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() diff --git a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt index 8ae569729..57e4d6b75 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt @@ -94,24 +94,14 @@ class HistoryRepository( db.historyDao.delete(manga.id) } - suspend fun delete(ids: Collection) { + suspend fun delete(ids: Collection): ReversibleHandle { db.withTransaction { for (id in ids) { db.historyDao.delete(id) } } - } - - suspend fun deleteReversible(ids: Collection): 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 { return db.historyDao.findPopularTags(limit).map { x -> x.toMangaTag() } } + + private suspend fun recover(ids: Collection) { + db.withTransaction { + for (id in ids) { + db.historyDao.recover(id) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt index 1768c2a5f..8e958baba 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt @@ -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() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b1d5f2e89..d9cf9bb12 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -304,4 +304,6 @@ Autodetect reader mode Automatically detect if manga is webtoon Enter your email to continue + Removed from favourites + Removed from \"%s\" \ No newline at end of file