Action to mark manga as completed
This commit is contained in:
@@ -141,7 +141,7 @@ dependencies {
|
|||||||
compileOnly 'com.google.auto.service:auto-service-annotations:1.1.1'
|
compileOnly 'com.google.auto.service:auto-service-annotations:1.1.1'
|
||||||
ksp 'dev.zacsweers.autoservice:auto-service-ksp:1.1.0'
|
ksp 'dev.zacsweers.autoservice:auto-service-ksp:1.1.0'
|
||||||
|
|
||||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.13'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
testImplementation 'org.json:json:20231013'
|
testImplementation 'org.json:json:20231013'
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.view.View
|
|||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
||||||
@@ -73,6 +74,18 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.action_mark_current -> {
|
||||||
|
MaterialAlertDialogBuilder(context ?: return false)
|
||||||
|
.setTitle(item.title)
|
||||||
|
.setMessage(R.string.mark_as_completed_prompt)
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
viewModel.markAsRead(selectedItems)
|
||||||
|
mode.finish()
|
||||||
|
}.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
else -> super.onActionItemClicked(controller, mode, item)
|
else -> super.onActionItemClicked(controller, mode, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
|||||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.ARG_CATEGORY_ID
|
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.ARG_CATEGORY_ID
|
||||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
|
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
|
||||||
|
import org.koitharu.kotatsu.history.domain.MarkAsReadUseCase
|
||||||
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
||||||
import org.koitharu.kotatsu.list.domain.ListSortOrder
|
import org.koitharu.kotatsu.list.domain.ListSortOrder
|
||||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||||
@@ -28,6 +29,7 @@ import org.koitharu.kotatsu.list.ui.model.EmptyState
|
|||||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||||
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
||||||
import org.koitharu.kotatsu.list.ui.model.toUi
|
import org.koitharu.kotatsu.list.ui.model.toUi
|
||||||
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@@ -35,11 +37,13 @@ class FavouritesListViewModel @Inject constructor(
|
|||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
private val repository: FavouritesRepository,
|
private val repository: FavouritesRepository,
|
||||||
private val listExtraProvider: ListExtraProvider,
|
private val listExtraProvider: ListExtraProvider,
|
||||||
|
private val markAsReadUseCase: MarkAsReadUseCase,
|
||||||
settings: AppSettings,
|
settings: AppSettings,
|
||||||
downloadScheduler: DownloadWorker.Scheduler,
|
downloadScheduler: DownloadWorker.Scheduler,
|
||||||
) : MangaListViewModel(settings, downloadScheduler) {
|
) : MangaListViewModel(settings, downloadScheduler) {
|
||||||
|
|
||||||
val categoryId: Long = savedStateHandle[ARG_CATEGORY_ID] ?: NO_ID
|
val categoryId: Long = savedStateHandle[ARG_CATEGORY_ID] ?: NO_ID
|
||||||
|
private val refreshTrigger = MutableStateFlow(Any())
|
||||||
|
|
||||||
override val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_FAVORITES) { favoritesListMode }
|
override val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_FAVORITES) { favoritesListMode }
|
||||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.favoritesListMode)
|
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.favoritesListMode)
|
||||||
@@ -59,7 +63,8 @@ class FavouritesListViewModel @Inject constructor(
|
|||||||
repository.observeAll(categoryId)
|
repository.observeAll(categoryId)
|
||||||
},
|
},
|
||||||
listMode,
|
listMode,
|
||||||
) { list, mode ->
|
refreshTrigger,
|
||||||
|
) { list, mode, _ ->
|
||||||
when {
|
when {
|
||||||
list.isEmpty() -> listOf(
|
list.isEmpty() -> listOf(
|
||||||
EmptyState(
|
EmptyState(
|
||||||
@@ -80,10 +85,19 @@ class FavouritesListViewModel @Inject constructor(
|
|||||||
emit(listOf(it.toErrorState(canRetry = false)))
|
emit(listOf(it.toErrorState(canRetry = false)))
|
||||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
|
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
|
||||||
|
|
||||||
override fun onRefresh() = Unit
|
override fun onRefresh() {
|
||||||
|
refreshTrigger.value = Any()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onRetry() = Unit
|
override fun onRetry() = Unit
|
||||||
|
|
||||||
|
fun markAsRead(items: Set<Manga>) {
|
||||||
|
launchLoadingJob(Dispatchers.Default) {
|
||||||
|
markAsReadUseCase(items)
|
||||||
|
onRefresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun removeFromFavourites(ids: Set<Long>) {
|
fun removeFromFavourites(ids: Set<Long>) {
|
||||||
if (ids.isEmpty()) {
|
if (ids.isEmpty()) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.koitharu.kotatsu.history.domain
|
||||||
|
|
||||||
|
import dagger.Reusable
|
||||||
|
import kotlinx.coroutines.joinAll
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.supervisorScope
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
|
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||||
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Reusable
|
||||||
|
class MarkAsReadUseCase @Inject constructor(
|
||||||
|
private val historyRepository: HistoryRepository,
|
||||||
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(manga: Manga) {
|
||||||
|
val repo = mangaRepositoryFactory.create(manga.source)
|
||||||
|
val details = if (manga.chapters.isNullOrEmpty()) {
|
||||||
|
repo.getDetails(manga)
|
||||||
|
} else {
|
||||||
|
manga
|
||||||
|
}
|
||||||
|
val lastChapter = checkNotNull(details.chapters).last()
|
||||||
|
val pages = repo.getPages(lastChapter)
|
||||||
|
historyRepository.addOrUpdate(
|
||||||
|
manga = details,
|
||||||
|
chapterId = lastChapter.id,
|
||||||
|
page = pages.lastIndex,
|
||||||
|
scroll = 0,
|
||||||
|
percent = 1f,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend operator fun invoke(manga: Collection<Manga>) {
|
||||||
|
when (manga.size) {
|
||||||
|
0 -> Unit
|
||||||
|
1 -> invoke(manga.first())
|
||||||
|
else -> supervisorScope {
|
||||||
|
manga.map {
|
||||||
|
launch {
|
||||||
|
invoke(it)
|
||||||
|
}
|
||||||
|
}.joinAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,12 @@
|
|||||||
android:title="@string/categories"
|
android:title="@string/categories"
|
||||||
app:showAsAction="ifRoom|withText" />
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_mark_current"
|
||||||
|
android:icon="@drawable/ic_eye_check"
|
||||||
|
android:title="@string/mark_as_completed"
|
||||||
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_select_all"
|
android:id="@+id/action_select_all"
|
||||||
android:icon="?actionModeSelectAllDrawable"
|
android:icon="?actionModeSelectAllDrawable"
|
||||||
|
|||||||
@@ -556,4 +556,6 @@
|
|||||||
<string name="rating_suggestive">Suggestive</string>
|
<string name="rating_suggestive">Suggestive</string>
|
||||||
<string name="rating_adult">Adult</string>
|
<string name="rating_adult">Adult</string>
|
||||||
<string name="default_tab">Default tab</string>
|
<string name="default_tab">Default tab</string>
|
||||||
|
<string name="mark_as_completed">Mark as completed</string>
|
||||||
|
<string name="mark_as_completed_prompt">Mark selected manga as completely read?\n\nWarning: current reading progress will be lost.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user