Consistent labeled checkbox composable
This commit is contained in:
parent
8626a55fe4
commit
c53172265b
8 changed files with 151 additions and 216 deletions
|
@ -1,12 +1,8 @@
|
||||||
package eu.kanade.presentation.history.components
|
package eu.kanade.presentation.history.components
|
||||||
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.selection.toggleable
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -14,12 +10,11 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
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.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.presentation.core.util.ThemePreviews
|
import tachiyomi.presentation.core.util.ThemePreviews
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -34,28 +29,16 @@ fun HistoryDeleteDialog(
|
||||||
Text(text = stringResource(R.string.action_remove))
|
Text(text = stringResource(R.string.action_remove))
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column(
|
||||||
Text(text = stringResource(R.string.dialog_with_checkbox_remove_description))
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = 16.dp)
|
|
||||||
.toggleable(
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
indication = null,
|
|
||||||
value = removeEverything,
|
|
||||||
onValueChange = { removeEverything = it },
|
|
||||||
),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
) {
|
||||||
Checkbox(
|
Text(text = stringResource(R.string.dialog_with_checkbox_remove_description))
|
||||||
|
|
||||||
|
LabeledCheckbox(
|
||||||
|
label = stringResource(R.string.dialog_with_checkbox_reset),
|
||||||
checked = removeEverything,
|
checked = removeEverything,
|
||||||
onCheckedChange = null,
|
onCheckedChange = { removeEverything = it },
|
||||||
)
|
)
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(start = 4.dp),
|
|
||||||
text = stringResource(R.string.dialog_with_checkbox_reset),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
package eu.kanade.presentation.library
|
package eu.kanade.presentation.library
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -13,11 +9,10 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
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.res.stringResource
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.core.preference.CheckboxState
|
import tachiyomi.core.preference.CheckboxState
|
||||||
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DeleteLibraryMangaDialog(
|
fun DeleteLibraryMangaDialog(
|
||||||
|
@ -62,27 +57,18 @@ fun DeleteLibraryMangaDialog(
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column {
|
||||||
list.forEach { state ->
|
list.forEach { state ->
|
||||||
val onCheck = {
|
LabeledCheckbox(
|
||||||
|
label = stringResource(state.value),
|
||||||
|
checked = state.isChecked,
|
||||||
|
onCheckedChange = {
|
||||||
val index = list.indexOf(state)
|
val index = list.indexOf(state)
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
val mutableList = list.toMutableList()
|
val mutableList = list.toMutableList()
|
||||||
mutableList[index] = state.next() as CheckboxState.State<Int>
|
mutableList[index] = state.next() as CheckboxState.State<Int>
|
||||||
list = mutableList.toList()
|
list = mutableList.toList()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable { onCheck() },
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
checked = state.isChecked,
|
|
||||||
onCheckedChange = { onCheck() },
|
|
||||||
)
|
)
|
||||||
Text(text = stringResource(state.value))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
package eu.kanade.presentation.manga
|
package eu.kanade.presentation.manga
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
@ -19,7 +15,6 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
@ -30,6 +25,7 @@ import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.core.preference.TriState
|
import tachiyomi.core.preference.TriState
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.presentation.core.components.RadioItem
|
import tachiyomi.presentation.core.components.RadioItem
|
||||||
import tachiyomi.presentation.core.components.SortItem
|
import tachiyomi.presentation.core.components.SortItem
|
||||||
import tachiyomi.presentation.core.components.TriStateItem
|
import tachiyomi.presentation.core.components.TriStateItem
|
||||||
|
@ -172,6 +168,7 @@ private fun SetAsDefaultDialog(
|
||||||
onConfirmed: (optionalChecked: Boolean) -> Unit,
|
onConfirmed: (optionalChecked: Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
var optionalChecked by rememberSaveable { mutableStateOf(false) }
|
var optionalChecked by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
title = { Text(text = stringResource(R.string.chapter_settings)) },
|
title = { Text(text = stringResource(R.string.chapter_settings)) },
|
||||||
|
@ -181,20 +178,11 @@ private fun SetAsDefaultDialog(
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.confirm_set_chapter_settings))
|
Text(text = stringResource(R.string.confirm_set_chapter_settings))
|
||||||
|
|
||||||
Row(
|
LabeledCheckbox(
|
||||||
modifier = Modifier
|
label = stringResource(R.string.also_set_chapter_settings_for_library),
|
||||||
.clickable { optionalChecked = !optionalChecked }
|
|
||||||
.padding(vertical = 8.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
checked = optionalChecked,
|
checked = optionalChecked,
|
||||||
onCheckedChange = null,
|
onCheckedChange = { optionalChecked = it },
|
||||||
)
|
)
|
||||||
Text(text = stringResource(R.string.also_set_chapter_settings_for_library))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
|
|
|
@ -8,20 +8,13 @@ import android.widget.Toast
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.heightIn
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -38,7 +31,6 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.presentation.extensions.RequestStoragePermission
|
import eu.kanade.presentation.extensions.RequestStoragePermission
|
||||||
|
@ -55,6 +47,7 @@ import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.domain.backup.service.BackupPreferences
|
import tachiyomi.domain.backup.service.BackupPreferences
|
||||||
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.isScrolledToEnd
|
||||||
|
@ -160,22 +153,23 @@ object SettingsBackupScreen : SearchableSettings {
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
ScrollbarLazyColumn(state = state) {
|
ScrollbarLazyColumn(state = state) {
|
||||||
item {
|
item {
|
||||||
CreateBackupDialogItem(
|
LabeledCheckbox(
|
||||||
isSelected = true,
|
label = stringResource(R.string.manga),
|
||||||
title = stringResource(R.string.manga),
|
checked = true,
|
||||||
|
onCheckedChange = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
choices.forEach { (k, v) ->
|
choices.forEach { (k, v) ->
|
||||||
item {
|
item {
|
||||||
val isSelected = flags.contains(k)
|
val isSelected = flags.contains(k)
|
||||||
CreateBackupDialogItem(
|
LabeledCheckbox(
|
||||||
isSelected = isSelected,
|
label = stringResource(v),
|
||||||
title = stringResource(v),
|
checked = isSelected,
|
||||||
modifier = Modifier.clickable {
|
onCheckedChange = {
|
||||||
if (isSelected) {
|
if (it) {
|
||||||
flags.remove(k)
|
|
||||||
} else {
|
|
||||||
flags.add(k)
|
flags.add(k)
|
||||||
|
} else {
|
||||||
|
flags.remove(k)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -204,29 +198,6 @@ object SettingsBackupScreen : SearchableSettings {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun CreateBackupDialogItem(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
isSelected: Boolean,
|
|
||||||
title: String,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
modifier = Modifier.heightIn(min = 48.dp),
|
|
||||||
checked = isSelected,
|
|
||||||
onCheckedChange = null,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.bodyMedium.merge(),
|
|
||||||
modifier = Modifier.padding(start = 24.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getRestoreBackupPref(): Preference.PreferenceItem.TextPreference {
|
private fun getRestoreBackupPref(): Preference.PreferenceItem.TextPreference {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
|
@ -1,30 +1,20 @@
|
||||||
package eu.kanade.presentation.more.settings.widget
|
package eu.kanade.presentation.more.settings.widget
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.selection.selectable
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.toMutableStateList
|
import androidx.compose.runtime.toMutableStateList
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MultiSelectListPreferenceWidget(
|
fun MultiSelectListPreferenceWidget(
|
||||||
|
@ -55,33 +45,17 @@ fun MultiSelectListPreferenceWidget(
|
||||||
preference.entries.forEach { current ->
|
preference.entries.forEach { current ->
|
||||||
item {
|
item {
|
||||||
val isSelected = selected.contains(current.key)
|
val isSelected = selected.contains(current.key)
|
||||||
val onSelectionChanged = {
|
LabeledCheckbox(
|
||||||
when (!isSelected) {
|
label = current.value,
|
||||||
true -> selected.add(current.key)
|
|
||||||
false -> selected.remove(current.key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(MaterialTheme.shapes.small)
|
|
||||||
.selectable(
|
|
||||||
selected = isSelected,
|
|
||||||
onClick = { onSelectionChanged() },
|
|
||||||
)
|
|
||||||
.minimumInteractiveComponentSize()
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
checked = isSelected,
|
checked = isSelected,
|
||||||
onCheckedChange = null,
|
onCheckedChange = {
|
||||||
)
|
if (it) {
|
||||||
Text(
|
selected.add(current.key)
|
||||||
text = current.value,
|
} else {
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
selected.remove(current.key)
|
||||||
modifier = Modifier.padding(start = 24.dp),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.migration.search
|
package eu.kanade.tachiyomi.ui.browse.migration.search
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
@ -22,7 +18,6 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.toMutableStateList
|
import androidx.compose.runtime.toMutableStateList
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
@ -55,6 +50,7 @@ import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import tachiyomi.domain.track.interactor.GetTracks
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
import tachiyomi.domain.track.interactor.InsertTrack
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
@ -92,16 +88,11 @@ internal fun MigrateDialog(
|
||||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
flags.forEachIndexed { index, flag ->
|
flags.forEachIndexed { index, flag ->
|
||||||
val onChange = { selectedFlags[index] = !selectedFlags[index] }
|
LabeledCheckbox(
|
||||||
Row(
|
label = stringResource(flag.titleId),
|
||||||
modifier = Modifier
|
checked = selectedFlags[index],
|
||||||
.fillMaxWidth()
|
onCheckedChange = { selectedFlags[index] = it },
|
||||||
.clickable(onClick = onChange),
|
)
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Checkbox(checked = selectedFlags[index], onCheckedChange = { onChange() })
|
|
||||||
Text(text = context.getString(flag.titleId))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.manga.track
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
@ -13,7 +12,6 @@ import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.FilledTonalButton
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
@ -33,6 +31,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
|
@ -75,6 +74,7 @@ import tachiyomi.domain.source.service.SourceManager
|
||||||
import tachiyomi.domain.track.interactor.DeleteTrack
|
import tachiyomi.domain.track.interactor.DeleteTrack
|
||||||
import tachiyomi.domain.track.interactor.GetTracks
|
import tachiyomi.domain.track.interactor.GetTracks
|
||||||
import tachiyomi.domain.track.model.Track
|
import tachiyomi.domain.track.model.Track
|
||||||
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.presentation.core.components.material.AlertDialogContent
|
import tachiyomi.presentation.core.components.material.AlertDialogContent
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
|
@ -94,10 +94,10 @@ data class TrackInfoDialogHomeScreen(
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val sm = rememberScreenModel { Model(mangaId, sourceId) }
|
val screenModel = rememberScreenModel { Model(mangaId, sourceId) }
|
||||||
|
|
||||||
val dateFormat = remember { UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat().get()) }
|
val dateFormat = remember { UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat().get()) }
|
||||||
val state by sm.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
TrackInfoDialogHome(
|
TrackInfoDialogHome(
|
||||||
trackItems = state.trackItems,
|
trackItems = state.trackItems,
|
||||||
|
@ -146,7 +146,7 @@ data class TrackInfoDialogHomeScreen(
|
||||||
},
|
},
|
||||||
onNewSearch = {
|
onNewSearch = {
|
||||||
if (it.tracker is EnhancedTracker) {
|
if (it.tracker is EnhancedTracker) {
|
||||||
sm.registerEnhancedTracking(it)
|
screenModel.registerEnhancedTracking(it)
|
||||||
} else {
|
} else {
|
||||||
navigator.push(
|
navigator.push(
|
||||||
TrackerSearchScreen(
|
TrackerSearchScreen(
|
||||||
|
@ -261,19 +261,19 @@ private data class TrackStatusSelectorScreen(
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val sm = rememberScreenModel {
|
val screenModel = rememberScreenModel {
|
||||||
Model(
|
Model(
|
||||||
track = track,
|
track = track,
|
||||||
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val state by sm.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
TrackStatusSelector(
|
TrackStatusSelector(
|
||||||
selection = state.selection,
|
selection = state.selection,
|
||||||
onSelectionChange = sm::setSelection,
|
onSelectionChange = screenModel::setSelection,
|
||||||
selections = remember { sm.getSelections() },
|
selections = remember { screenModel.getSelections() },
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
sm.setStatus()
|
screenModel.setStatus()
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
},
|
},
|
||||||
onDismissRequest = navigator::pop,
|
onDismissRequest = navigator::pop,
|
||||||
|
@ -314,20 +314,20 @@ private data class TrackChapterSelectorScreen(
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val sm = rememberScreenModel {
|
val screenModel = rememberScreenModel {
|
||||||
Model(
|
Model(
|
||||||
track = track,
|
track = track,
|
||||||
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val state by sm.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
TrackChapterSelector(
|
TrackChapterSelector(
|
||||||
selection = state.selection,
|
selection = state.selection,
|
||||||
onSelectionChange = sm::setSelection,
|
onSelectionChange = screenModel::setSelection,
|
||||||
range = remember { sm.getRange() },
|
range = remember { screenModel.getRange() },
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
sm.setChapter()
|
screenModel.setChapter()
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
},
|
},
|
||||||
onDismissRequest = navigator::pop,
|
onDismissRequest = navigator::pop,
|
||||||
|
@ -373,20 +373,20 @@ private data class TrackScoreSelectorScreen(
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val sm = rememberScreenModel {
|
val screenModel = rememberScreenModel {
|
||||||
Model(
|
Model(
|
||||||
track = track,
|
track = track,
|
||||||
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val state by sm.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
TrackScoreSelector(
|
TrackScoreSelector(
|
||||||
selection = state.selection,
|
selection = state.selection,
|
||||||
onSelectionChange = sm::setSelection,
|
onSelectionChange = screenModel::setSelection,
|
||||||
selections = remember { sm.getSelections() },
|
selections = remember { screenModel.getSelections() },
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
sm.setScore()
|
screenModel.setScore()
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
},
|
},
|
||||||
onDismissRequest = navigator::pop,
|
onDismissRequest = navigator::pop,
|
||||||
|
@ -484,7 +484,7 @@ private data class TrackDateSelectorScreen(
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val sm = rememberScreenModel {
|
val screenModel = rememberScreenModel {
|
||||||
Model(
|
Model(
|
||||||
track = track,
|
track = track,
|
||||||
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
||||||
|
@ -503,13 +503,13 @@ private data class TrackDateSelectorScreen(
|
||||||
} else {
|
} else {
|
||||||
stringResource(R.string.track_finished_reading_date)
|
stringResource(R.string.track_finished_reading_date)
|
||||||
},
|
},
|
||||||
initialSelectedDateMillis = sm.initialSelection,
|
initialSelectedDateMillis = screenModel.initialSelection,
|
||||||
selectableDates = selectableDates,
|
selectableDates = selectableDates,
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
sm.setDate(it)
|
screenModel.setDate(it)
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
},
|
},
|
||||||
onRemove = { sm.confirmRemoveDate(navigator) }.takeIf { canRemove },
|
onRemove = { screenModel.confirmRemoveDate(navigator) }.takeIf { canRemove },
|
||||||
onDismissRequest = navigator::pop,
|
onDismissRequest = navigator::pop,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -557,7 +557,7 @@ private data class TrackDateRemoverScreen(
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val sm = rememberScreenModel {
|
val screenModel = rememberScreenModel {
|
||||||
Model(
|
Model(
|
||||||
track = track,
|
track = track,
|
||||||
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
||||||
|
@ -579,7 +579,7 @@ private data class TrackDateRemoverScreen(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
val serviceName = sm.getServiceName()
|
val serviceName = screenModel.getServiceName()
|
||||||
Text(
|
Text(
|
||||||
text = if (start) {
|
text = if (start) {
|
||||||
stringResource(R.string.track_remove_start_date_conf_text, serviceName)
|
stringResource(R.string.track_remove_start_date_conf_text, serviceName)
|
||||||
|
@ -598,7 +598,7 @@ private data class TrackDateRemoverScreen(
|
||||||
}
|
}
|
||||||
FilledTonalButton(
|
FilledTonalButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
sm.removeDate()
|
screenModel.removeDate()
|
||||||
navigator.popUntil { it is TrackInfoDialogHomeScreen }
|
navigator.popUntil { it is TrackInfoDialogHomeScreen }
|
||||||
},
|
},
|
||||||
colors = ButtonDefaults.filledTonalButtonColors(
|
colors = ButtonDefaults.filledTonalButtonColors(
|
||||||
|
@ -643,7 +643,7 @@ data class TrackerSearchScreen(
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val sm = rememberScreenModel {
|
val screenModel = rememberScreenModel {
|
||||||
Model(
|
Model(
|
||||||
mangaId = mangaId,
|
mangaId = mangaId,
|
||||||
currentUrl = currentUrl,
|
currentUrl = currentUrl,
|
||||||
|
@ -652,18 +652,18 @@ data class TrackerSearchScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val state by sm.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
var textFieldValue by remember { mutableStateOf(TextFieldValue(initialQuery)) }
|
var textFieldValue by remember { mutableStateOf(TextFieldValue(initialQuery)) }
|
||||||
TrackerSearch(
|
TrackerSearch(
|
||||||
query = textFieldValue,
|
query = textFieldValue,
|
||||||
onQueryChange = { textFieldValue = it },
|
onQueryChange = { textFieldValue = it },
|
||||||
onDispatchQuery = { sm.trackingSearch(textFieldValue.text) },
|
onDispatchQuery = { screenModel.trackingSearch(textFieldValue.text) },
|
||||||
queryResult = state.queryResult,
|
queryResult = state.queryResult,
|
||||||
selected = state.selected,
|
selected = state.selected,
|
||||||
onSelectedChange = sm::updateSelection,
|
onSelectedChange = screenModel::updateSelection,
|
||||||
onConfirmSelection = {
|
onConfirmSelection = {
|
||||||
sm.registerTracking(state.selected!!)
|
screenModel.registerTracking(state.selected!!)
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
},
|
},
|
||||||
onDismissRequest = navigator::pop,
|
onDismissRequest = navigator::pop,
|
||||||
|
@ -731,14 +731,14 @@ private data class TrackerRemoveScreen(
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val sm = rememberScreenModel {
|
val screenModel = rememberScreenModel {
|
||||||
Model(
|
Model(
|
||||||
mangaId = mangaId,
|
mangaId = mangaId,
|
||||||
track = track,
|
track = track,
|
||||||
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val serviceName = sm.getName()
|
val serviceName = screenModel.getName()
|
||||||
var removeRemoteTrack by remember { mutableStateOf(false) }
|
var removeRemoteTrack by remember { mutableStateOf(false) }
|
||||||
AlertDialogContent(
|
AlertDialogContent(
|
||||||
modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars),
|
modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars),
|
||||||
|
@ -755,21 +755,19 @@ private data class TrackerRemoveScreen(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.track_delete_text, serviceName),
|
text = stringResource(R.string.track_delete_text, serviceName),
|
||||||
)
|
)
|
||||||
if (sm.isDeletable()) {
|
|
||||||
val onChange = { removeRemoteTrack = !removeRemoteTrack }
|
if (screenModel.isDeletable()) {
|
||||||
Row(
|
LabeledCheckbox(
|
||||||
modifier = Modifier
|
label = stringResource(R.string.track_delete_remote_text, serviceName),
|
||||||
.fillMaxWidth()
|
checked = removeRemoteTrack,
|
||||||
.clickable(onClick = onChange),
|
onCheckedChange = { removeRemoteTrack = it },
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
)
|
||||||
) {
|
|
||||||
Checkbox(checked = removeRemoteTrack, onCheckedChange = { onChange() })
|
|
||||||
Text(text = stringResource(R.string.track_delete_remote_text, serviceName))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -786,8 +784,8 @@ private data class TrackerRemoveScreen(
|
||||||
}
|
}
|
||||||
FilledTonalButton(
|
FilledTonalButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
sm.unregisterTracking(serviceId)
|
screenModel.unregisterTracking(serviceId)
|
||||||
if (removeRemoteTrack) sm.deleteMangaFromService()
|
if (removeRemoteTrack) screenModel.deleteMangaFromService()
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
},
|
},
|
||||||
colors = ButtonDefaults.filledTonalButtonColors(
|
colors = ButtonDefaults.filledTonalButtonColors(
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package tachiyomi.presentation.core.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LabeledCheckbox(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
label: String,
|
||||||
|
checked: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 48.dp)
|
||||||
|
.clickable(
|
||||||
|
role = Role.Checkbox,
|
||||||
|
onClick = { onCheckedChange(!checked) },
|
||||||
|
),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = checked,
|
||||||
|
onCheckedChange = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = label)
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue