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

View File

@ -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) }
}

View File

@ -16,78 +16,20 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource 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.domain.category.model.Category
import eu.kanade.presentation.category.visualName import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.util.horizontalPadding import eu.kanade.presentation.util.horizontalPadding
import eu.kanade.tachiyomi.R 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 @Composable
fun ChangeCategoryDialog( fun ChangeCategoryDialog(
categories: List<Category>, initialSelection: List<CheckboxState<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>>,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onEditCategories: () -> Unit, onEditCategories: () -> Unit,
onConfirm: (List<Long>, List<Long>) -> Unit, onConfirm: (List<Long>, List<Long>) -> Unit,
) { ) {
if (categories.isEmpty()) { if (initialSelection.isEmpty()) {
AlertDialog( AlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
confirmButton = { confirmButton = {
@ -128,12 +70,12 @@ fun ChangeCategoryDialog(
onClick = { onClick = {
onDismissRequest() onDismissRequest()
onConfirm( onConfirm(
selection.filter { it is Checkbox.State.Checked || it is Checkbox.TriState.Include }.map { it.value.id }, selection.filter { it is CheckboxState.State.Checked || it is CheckboxState.TriState.Include }.map { it.value.id },
selection.filter { it is Checkbox.TriState.Exclude }.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( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
val onCheckboxChange: (Checkbox<Category>) -> Unit = { val onCheckboxChange: (CheckboxState<Category>) -> Unit = {
val index = selection.indexOf(it) val index = selection.indexOf(it)
val mutableList = selection.toMutableList() val mutableList = selection.toMutableList()
mutableList.removeAt(index) mutableList.removeAt(index)
@ -154,15 +96,15 @@ fun ChangeCategoryDialog(
selection = mutableList.toList() selection = mutableList.toList()
} }
when (checkbox) { when (checkbox) {
is Checkbox.TriState -> { is CheckboxState.TriState -> {
TriStateCheckbox( TriStateCheckbox(
state = checkbox.asState(), state = checkbox.asState(),
onClick = { onCheckboxChange(checkbox) }, onClick = { onCheckboxChange(checkbox) },
) )
} }
is Checkbox.State -> { is CheckboxState.State -> {
Checkbox( Checkbox(
checked = checkbox is Checkbox.State.Checked, checked = checkbox.isChecked,
onCheckedChange = { onCheckboxChange(checkbox) }, onCheckedChange = { onCheckboxChange(checkbox) },
) )
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,8 @@ import android.app.Application
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.compose.runtime.Immutable 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.GetCategories
import eu.kanade.domain.category.interactor.SetMangaCategories import eu.kanade.domain.category.interactor.SetMangaCategories
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
@ -347,10 +349,16 @@ class MangaPresenter(
val manga = state.manga val manga = state.manga
presenterScope.launch { presenterScope.launch {
val categories = getCategories() val categories = getCategories()
val selection = getMangaCategoryIds(manga)
_state.update { state -> _state.update { state ->
when (state) { when (state) {
MangaScreenState.Loading -> 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 { 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 DeleteChapters(val chapters: List<DomainChapter>) : Dialog()
data class DuplicateManga(val manga: DomainManga, val duplicate: DomainManga) : Dialog() data class DuplicateManga(val manga: DomainManga, val duplicate: DomainManga) : Dialog()
data class DownloadCustomAmount(val max: Int) : Dialog() data class DownloadCustomAmount(val max: Int) : Dialog()

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>