Review changes

This commit is contained in:
Andreas 2022-08-26 08:37:37 +02:00
parent 6d14225644
commit 96e3c60923
No known key found for this signature in database
GPG Key ID: D7D48B26482380B8
12 changed files with 113 additions and 187 deletions

@ -0,0 +1,55 @@
package eu.kanade.core.prefs
import androidx.compose.ui.state.ToggleableState
sealed class CheckboxState<T>(open val value: T) {
abstract fun next(): CheckboxState<T>
sealed class State<T>(override val value: T) : CheckboxState<T>(value) {
data class Checked<T>(override val value: T) : State<T>(value)
data class None<T>(override val value: T) : State<T>(value)
val isChecked: Boolean
get() = this is Checked
override fun next(): CheckboxState<T> {
return when (this) {
is Checked -> None(value)
is None -> Checked(value)
}
}
}
sealed class TriState<T>(override val value: T) : CheckboxState<T>(value) {
data class Include<T>(override val value: T) : TriState<T>(value)
data class Exclude<T>(override val value: T) : TriState<T>(value)
data class None<T>(override val value: T) : TriState<T>(value)
override fun next(): CheckboxState<T> {
return when (this) {
is Exclude -> None(value)
is Include -> Exclude(value)
is None -> Include(value)
}
}
fun asState(): ToggleableState {
return when (this) {
is Exclude -> ToggleableState.Indeterminate
is Include -> ToggleableState.On
is None -> ToggleableState.Off
}
}
}
}
inline fun <T> T.asCheckboxState(condition: (T) -> Boolean): CheckboxState.State<T> {
return if (condition(this)) {
CheckboxState.State.Checked(this)
} else {
CheckboxState.State.None(this)
}
}
inline fun <T> List<T>.mapAsCheckboxState(condition: (T) -> Boolean): List<CheckboxState.State<T>> {
return this.map { it.asCheckboxState(condition) }
}

@ -16,78 +16,20 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.state.ToggleableState
import eu.kanade.core.prefs.CheckboxState
import eu.kanade.domain.category.model.Category
import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.util.horizontalPadding
import eu.kanade.tachiyomi.R
sealed class Checkbox<T>(open val value: T) {
abstract fun next(): Checkbox<T>
sealed class State<T>(override val value: T) : Checkbox<T>(value) {
data class Checked<T>(override val value: T) : State<T>(value)
data class None<T>(override val value: T) : State<T>(value)
override fun next(): Checkbox<T> {
return when (this) {
is Checked -> None(value)
is None -> Checked(value)
}
}
}
sealed class TriState<T>(override val value: T) : Checkbox<T>(value) {
data class Include<T>(override val value: T) : TriState<T>(value)
data class Exclude<T>(override val value: T) : TriState<T>(value)
data class None<T>(override val value: T) : TriState<T>(value)
override fun next(): Checkbox<T> {
return when (this) {
is Exclude -> None(value)
is Include -> Exclude(value)
is None -> Include(value)
}
}
fun asState(): ToggleableState {
return when (this) {
is Exclude -> ToggleableState.Indeterminate
is Include -> ToggleableState.On
is None -> ToggleableState.Off
}
}
}
}
@JvmName("ChangeCategoryDialogHelper")
@Composable
fun ChangeCategoryDialog(
categories: List<Category>,
initialSelection: List<Long>,
onDismissRequest: () -> Unit,
onEditCategories: () -> Unit,
onConfirm: (List<Long>) -> Unit,
) {
ChangeCategoryDialog(
categories = categories,
initialSelection = categories.map {
if (it.id in initialSelection) Checkbox.State.Checked(it) else Checkbox.State.None(it)
},
onDismissRequest = onDismissRequest,
onEditCategories = onEditCategories,
onConfirm = { include, _ -> onConfirm(include.map { it }) },
)
}
@Composable
fun ChangeCategoryDialog(
categories: List<Category>,
initialSelection: List<Checkbox<Category>>,
initialSelection: List<CheckboxState<Category>>,
onDismissRequest: () -> Unit,
onEditCategories: () -> Unit,
onConfirm: (List<Long>, List<Long>) -> Unit,
) {
if (categories.isEmpty()) {
if (initialSelection.isEmpty()) {
AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
@ -128,12 +70,12 @@ fun ChangeCategoryDialog(
onClick = {
onDismissRequest()
onConfirm(
selection.filter { it is Checkbox.State.Checked || it is Checkbox.TriState.Include }.map { it.value.id },
selection.filter { it is Checkbox.TriState.Exclude }.map { it.value.id },
selection.filter { it is CheckboxState.State.Checked || it is CheckboxState.TriState.Include }.map { it.value.id },
selection.filter { it is CheckboxState.TriState.Exclude }.map { it.value.id },
)
},
) {
Text(text = "Add")
Text(text = stringResource(id = R.string.action_add))
}
}
},
@ -146,7 +88,7 @@ fun ChangeCategoryDialog(
Row(
verticalAlignment = Alignment.CenterVertically,
) {
val onCheckboxChange: (Checkbox<Category>) -> Unit = {
val onCheckboxChange: (CheckboxState<Category>) -> Unit = {
val index = selection.indexOf(it)
val mutableList = selection.toMutableList()
mutableList.removeAt(index)
@ -154,15 +96,15 @@ fun ChangeCategoryDialog(
selection = mutableList.toList()
}
when (checkbox) {
is Checkbox.TriState -> {
is CheckboxState.TriState -> {
TriStateCheckbox(
state = checkbox.asState(),
onClick = { onCheckboxChange(checkbox) },
)
}
is Checkbox.State -> {
is CheckboxState.State -> {
Checkbox(
checked = checkbox is Checkbox.State.Checked,
checked = checkbox.isChecked,
onCheckedChange = { onCheckboxChange(checkbox) },
)
}

@ -13,7 +13,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.Checkbox
import eu.kanade.core.prefs.CheckboxState
import eu.kanade.tachiyomi.R
@Composable
@ -24,10 +24,10 @@ fun DeleteLibraryMangaDialog(
) {
var list by remember {
mutableStateOf(
buildList<Checkbox.State<Int>> {
add(Checkbox.State.None(R.string.manga_from_library))
if (containsLocalManga) {
add(Checkbox.State.None(R.string.downloaded_chapters))
buildList<CheckboxState.State<Int>> {
add(CheckboxState.State.None(R.string.manga_from_library))
if (!containsLocalManga) {
add(CheckboxState.State.None(R.string.downloaded_chapters))
}
},
)
@ -44,8 +44,8 @@ fun DeleteLibraryMangaDialog(
onClick = {
onDismissRequest()
onConfirm(
list[0] is Checkbox.State.Checked,
list.getOrElse(1) { Checkbox.State.None(0) } is Checkbox.State.Checked,
list[0].isChecked,
list.getOrElse(1) { CheckboxState.State.None(0) }.isChecked,
)
},
) {
@ -60,12 +60,12 @@ fun DeleteLibraryMangaDialog(
list.forEach { state ->
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = state is Checkbox.State.Checked,
checked = state.isChecked,
onCheckedChange = {
val index = list.indexOf(state)
val mutableList = list.toMutableList()
mutableList.removeAt(index)
mutableList.add(index, state.next() as Checkbox.State<Int>)
mutableList.add(index, state.next() as CheckboxState.State<Int>)
list = mutableList.toList()
},
)

@ -8,10 +8,11 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import eu.kanade.core.prefs.CheckboxState
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.isLocal
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.presentation.components.ChangeCategoryDialog
import eu.kanade.presentation.components.Checkbox
import eu.kanade.presentation.library.LibraryScreen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.toDomainManga
@ -67,22 +68,21 @@ class LibraryController(
when (val dialog = presenter.dialog) {
is LibraryPresenter.Dialog.ChangeCategory -> {
ChangeCategoryDialog(
categories = dialog.categories,
initialSelection = dialog.initialSelection,
onDismissRequest = onDismissRequest,
onEditCategories = {
router.popCurrentController()
presenter.clearSelection()
router.pushController(CategoryController())
},
onConfirm = { include, exclude ->
presenter.setMangaCategories(dialog.manga, include, exclude)
presenter.clearSelection()
presenter.setMangaCategories(dialog.manga, include, exclude)
},
)
}
is LibraryPresenter.Dialog.DeleteManga -> {
DeleteLibraryMangaDialog(
containsLocalManga = dialog.manga.any { !it.isLocal() },
containsLocalManga = dialog.manga.any(Manga::isLocal),
onDismissRequest = onDismissRequest,
onConfirm = { deleteManga, deleteChapter ->
presenter.removeMangas(dialog.manga.map { it.toDbManga() }, deleteManga, deleteChapter)
@ -197,40 +197,40 @@ class LibraryController(
private fun showMangaCategoriesDialog() {
viewScope.launchIO {
// Create a copy of selected manga
val manga = presenter.selection.mapNotNull { it.toDomainManga() }.toList()
val mangaList = presenter.selection.mapNotNull { it.toDomainManga() }.toList()
// Hide the default category because it has a different behavior than the ones from db.
val categories = presenter.categories.filter { it.id != 0L }
// Get indexes of the common categories to preselect.
val common = presenter.getCommonCategories(manga)
val common = presenter.getCommonCategories(mangaList)
// Get indexes of the mix categories to preselect.
val mix = presenter.getMixCategories(manga)
val mix = presenter.getMixCategories(mangaList)
val preselected = categories.map {
when (it) {
in common -> Checkbox.State.Checked(it)
in mix -> Checkbox.TriState.Exclude(it)
else -> Checkbox.State.None(it)
in common -> CheckboxState.State.Checked(it)
in mix -> CheckboxState.TriState.Exclude(it)
else -> CheckboxState.State.None(it)
}
}
presenter.dialog = LibraryPresenter.Dialog.ChangeCategory(manga, categories, preselected)
presenter.dialog = LibraryPresenter.Dialog.ChangeCategory(mangaList, preselected)
}
}
private fun downloadUnreadChapters() {
val manga = presenter.selection.toList()
presenter.downloadUnreadChapters(manga.mapNotNull { it.toDomainManga() })
val mangaList = presenter.selection.toList()
presenter.downloadUnreadChapters(mangaList.mapNotNull { it.toDomainManga() })
presenter.clearSelection()
}
private fun markReadStatus(read: Boolean) {
val manga = presenter.selection.toList()
presenter.markReadStatus(manga.mapNotNull { it.toDomainManga() }, read)
val mangaList = presenter.selection.toList()
presenter.markReadStatus(mangaList.mapNotNull { it.toDomainManga() }, read)
presenter.clearSelection()
}
private fun showDeleteMangaDialog() {
val manga = presenter.selection.mapNotNull { it.toDomainManga() }.toList()
presenter.dialog = LibraryPresenter.Dialog.DeleteManga(manga)
val mangaList = presenter.selection.mapNotNull { it.toDomainManga() }.toList()
presenter.dialog = LibraryPresenter.Dialog.DeleteManga(mangaList)
}
}

@ -11,6 +11,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.util.fastAny
import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.core.prefs.CheckboxState
import eu.kanade.core.prefs.PreferenceMutableState
import eu.kanade.core.util.asFlow
import eu.kanade.core.util.asObservable
@ -28,7 +29,6 @@ import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.manga.model.isLocal
import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.components.Checkbox
import eu.kanade.presentation.library.LibraryState
import eu.kanade.presentation.library.LibraryStateImpl
import eu.kanade.presentation.library.components.LibraryToolbarTitle
@ -720,7 +720,7 @@ class LibraryPresenter(
}
sealed class Dialog {
data class ChangeCategory(val manga: List<Manga>, val categories: List<Category>, val initialSelection: List<Checkbox<Category>>) : Dialog()
data class ChangeCategory(val manga: List<Manga>, val initialSelection: List<CheckboxState<Category>>) : Dialog()
data class DeleteManga(val manga: List<Manga>) : Dialog()
}
}

@ -151,15 +151,13 @@ class MangaController : FullComposeController<MangaPresenter> {
when (val dialog = dialog) {
is Dialog.ChangeCategory -> {
ChangeCategoryDialog(
categories = dialog.categories,
initialSelection = dialog.initialSelection,
onDismissRequest = onDismissRequest,
onEditCategories = {
router.popCurrentController()
router.pushController(CategoryController())
},
onConfirm = {
presenter.moveMangaToCategoriesAndAddToLibrary(dialog.manga, it)
onConfirm = { include, _ ->
presenter.moveMangaToCategoriesAndAddToLibrary(dialog.manga, include)
},
)
}
@ -186,7 +184,13 @@ class MangaController : FullComposeController<MangaPresenter> {
is Dialog.DuplicateManga -> {
DuplicateDialog(
onDismissRequest = onDismissRequest,
onConfirm = { presenter.toggleFavorite({}, {}, false) },
onConfirm = {
presenter.toggleFavorite(
onRemoved = {},
onAdded = {},
checkDuplicate = false,
)
},
onOpenManga = { router.pushController(MangaController(dialog.duplicate.id)) },
duplicateFrom = presenter.getSourceOrStub(dialog.duplicate),
)

@ -4,6 +4,8 @@ import android.app.Application
import android.content.Context
import android.os.Bundle
import androidx.compose.runtime.Immutable
import eu.kanade.core.prefs.CheckboxState
import eu.kanade.core.prefs.mapAsCheckboxState
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.SetMangaCategories
import eu.kanade.domain.category.model.Category
@ -347,10 +349,16 @@ class MangaPresenter(
val manga = state.manga
presenterScope.launch {
val categories = getCategories()
val selection = getMangaCategoryIds(manga)
_state.update { state ->
when (state) {
MangaScreenState.Loading -> state
is MangaScreenState.Success -> state.copy(dialog = Dialog.ChangeCategory(manga, categories, getMangaCategoryIds(manga)))
is MangaScreenState.Success -> state.copy(
dialog = Dialog.ChangeCategory(
manga = manga,
initialSelection = categories.mapAsCheckboxState { it.id in selection },
),
)
}
}
}
@ -1026,7 +1034,7 @@ class MangaPresenter(
}
sealed class Dialog {
data class ChangeCategory(val manga: DomainManga, val categories: List<Category>, val initialSelection: List<Long>) : Dialog()
data class ChangeCategory(val manga: DomainManga, val initialSelection: List<CheckboxState<Category>>) : Dialog()
data class DeleteChapters(val chapters: List<DomainChapter>) : Dialog()
data class DuplicateManga(val manga: DomainManga, val duplicate: DomainManga) : Dialog()
data class DownloadCustomAmount(val max: Int) : Dialog()

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z" />
</vector>

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M18.41,7.41L17,6L11,12L17,18L18.41,16.59L13.83,12L18.41,7.41M12.41,7.41L11,6L5,12L11,18L12.41,16.59L7.83,12L12.41,7.41Z" />
</vector>

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z" />
</vector>

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M5.59,7.41L7,6L13,12L7,18L5.59,16.59L10.17,12L5.59,7.41M11.59,7.41L13,6L19,12L13,18L11.59,16.59L16.17,12L11.59,7.41Z" />
</vector>

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:paddingVertical="8dp">
<com.google.android.material.button.MaterialButton
style="@style/Widget.Tachiyomi.Button.IconButton"
android:id="@+id/btn_decrease_10"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:icon="@drawable/ic_chevron_left_double_black_24dp" />
<com.google.android.material.button.MaterialButton
style="@style/Widget.Tachiyomi.Button.IconButton"
android:id="@+id/btn_decrease"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:icon="@drawable/ic_chevron_left_black_24dp" />
<eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText
android:id="@+id/myNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:digits="0123456789"
android:inputType="number"
android:padding="8dp"
android:textStyle="bold" />
<com.google.android.material.button.MaterialButton
style="@style/Widget.Tachiyomi.Button.IconButton"
android:id="@+id/btn_increase"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:icon="@drawable/ic_chevron_right_black_24dp" />
<com.google.android.material.button.MaterialButton
style="@style/Widget.Tachiyomi.Button.IconButton"
android:id="@+id/btn_increase_10"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:icon="@drawable/ic_chevron_right_double_black_24dp" />
</LinearLayout>