Move more components
This commit is contained in:
@@ -276,18 +276,16 @@ fun SearchToolbar(
|
||||
visualTransformation = visualTransformation,
|
||||
interactionSource = interactionSource,
|
||||
placeholder = {
|
||||
(placeholderText ?: stringResource(R.string.action_search_hint)).let { placeholderText ->
|
||||
Text(
|
||||
modifier = Modifier.secondaryItemAlpha(),
|
||||
text = placeholderText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.secondaryItemAlpha(),
|
||||
text = (placeholderText ?: stringResource(R.string.action_search_hint)),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
package eu.kanade.presentation.components
|
||||
|
||||
import androidx.compose.foundation.gestures.FlingBehavior
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
|
||||
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.lazy.LazyListItemInfo
|
||||
import androidx.compose.foundation.lazy.LazyListLayoutInfo
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.listSaver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.compose.ui.util.fastMaxBy
|
||||
import androidx.compose.ui.util.fastSumBy
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import tachiyomi.presentation.core.components.LazyColumn
|
||||
|
||||
@Composable
|
||||
fun HorizontalPager(
|
||||
count: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
state: PagerState = rememberPagerState(),
|
||||
key: ((page: Int) -> Any)? = null,
|
||||
contentPadding: PaddingValues = PaddingValues(),
|
||||
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
|
||||
userScrollEnabled: Boolean = true,
|
||||
content: @Composable BoxScope.(page: Int) -> Unit,
|
||||
) {
|
||||
Pager(
|
||||
count = count,
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
isVertical = false,
|
||||
key = key,
|
||||
contentPadding = contentPadding,
|
||||
verticalAlignment = verticalAlignment,
|
||||
userScrollEnabled = userScrollEnabled,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Pager(
|
||||
count: Int,
|
||||
modifier: Modifier,
|
||||
state: PagerState,
|
||||
isVertical: Boolean,
|
||||
key: ((page: Int) -> Any)?,
|
||||
contentPadding: PaddingValues,
|
||||
userScrollEnabled: Boolean,
|
||||
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
|
||||
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
|
||||
content: @Composable BoxScope.(page: Int) -> Unit,
|
||||
) {
|
||||
LaunchedEffect(count) {
|
||||
state.currentPage = minOf(count - 1, state.currentPage).coerceAtLeast(0)
|
||||
}
|
||||
|
||||
LaunchedEffect(state) {
|
||||
snapshotFlow { state.mostVisiblePageLayoutInfo?.index }
|
||||
.distinctUntilChanged()
|
||||
.collect { state.updateCurrentPageBasedOnLazyListState() }
|
||||
}
|
||||
|
||||
if (isVertical) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
state = state.lazyListState,
|
||||
contentPadding = contentPadding,
|
||||
horizontalAlignment = horizontalAlignment,
|
||||
verticalArrangement = Arrangement.aligned(verticalAlignment),
|
||||
userScrollEnabled = userScrollEnabled,
|
||||
flingBehavior = rememberLazyListSnapFlingBehavior(lazyListState = state.lazyListState),
|
||||
) {
|
||||
items(
|
||||
count = count,
|
||||
key = key,
|
||||
) { page ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillParentMaxHeight()
|
||||
.wrapContentSize(),
|
||||
) {
|
||||
content(this, page)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyRow(
|
||||
modifier = modifier,
|
||||
state = state.lazyListState,
|
||||
contentPadding = contentPadding,
|
||||
verticalAlignment = verticalAlignment,
|
||||
horizontalArrangement = Arrangement.aligned(horizontalAlignment),
|
||||
userScrollEnabled = userScrollEnabled,
|
||||
flingBehavior = rememberLazyListSnapFlingBehavior(lazyListState = state.lazyListState),
|
||||
) {
|
||||
items(
|
||||
count = count,
|
||||
key = key,
|
||||
) { page ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillParentMaxWidth()
|
||||
.wrapContentSize(),
|
||||
) {
|
||||
content(this, page)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberPagerState(
|
||||
initialPage: Int = 0,
|
||||
) = rememberSaveable(saver = PagerState.Saver) {
|
||||
PagerState(currentPage = initialPage)
|
||||
}
|
||||
|
||||
@Stable
|
||||
class PagerState(
|
||||
currentPage: Int = 0,
|
||||
) {
|
||||
init { check(currentPage >= 0) { "currentPage cannot be less than zero" } }
|
||||
|
||||
val lazyListState = LazyListState(firstVisibleItemIndex = currentPage)
|
||||
|
||||
private var _currentPage by mutableStateOf(currentPage)
|
||||
|
||||
var currentPage: Int
|
||||
get() = _currentPage
|
||||
set(value) {
|
||||
if (value != _currentPage) {
|
||||
_currentPage = value
|
||||
}
|
||||
}
|
||||
|
||||
val mostVisiblePageLayoutInfo: LazyListItemInfo?
|
||||
get() {
|
||||
val layoutInfo = lazyListState.layoutInfo
|
||||
return layoutInfo.visibleItemsInfo.fastMaxBy {
|
||||
val start = maxOf(it.offset, 0)
|
||||
val end = minOf(
|
||||
it.offset + it.size,
|
||||
layoutInfo.viewportEndOffset - layoutInfo.afterContentPadding,
|
||||
)
|
||||
end - start
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCurrentPageBasedOnLazyListState() {
|
||||
mostVisiblePageLayoutInfo?.let {
|
||||
currentPage = it.index
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun animateScrollToPage(page: Int) {
|
||||
lazyListState.animateScrollToItem(index = page)
|
||||
}
|
||||
|
||||
suspend fun scrollToPage(page: Int) {
|
||||
lazyListState.scrollToItem(index = page)
|
||||
updateCurrentPageBasedOnLazyListState()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Saver: Saver<PagerState, *> = listSaver(
|
||||
save = { listOf(it.currentPage) },
|
||||
restore = { PagerState(it[0]) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// https://android.googlesource.com/platform/frameworks/support/+/refs/changes/78/2160778/35/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyListSnapLayoutInfoProvider.kt
|
||||
private fun lazyListSnapLayoutInfoProvider(
|
||||
lazyListState: LazyListState,
|
||||
positionInLayout: (layoutSize: Float, itemSize: Float) -> Float = { layoutSize, itemSize ->
|
||||
layoutSize / 2f - itemSize / 2f
|
||||
},
|
||||
) = object : SnapLayoutInfoProvider {
|
||||
|
||||
private val layoutInfo: LazyListLayoutInfo
|
||||
get() = lazyListState.layoutInfo
|
||||
|
||||
// Single page snapping is the default
|
||||
override fun Density.calculateApproachOffset(initialVelocity: Float): Float = 0f
|
||||
|
||||
override fun Density.calculateSnappingOffsetBounds(): ClosedFloatingPointRange<Float> {
|
||||
var lowerBoundOffset = Float.NEGATIVE_INFINITY
|
||||
var upperBoundOffset = Float.POSITIVE_INFINITY
|
||||
|
||||
layoutInfo.visibleItemsInfo.fastForEach { item ->
|
||||
val offset =
|
||||
calculateDistanceToDesiredSnapPosition(layoutInfo, item, positionInLayout)
|
||||
|
||||
// Find item that is closest to the center
|
||||
if (offset <= 0 && offset > lowerBoundOffset) {
|
||||
lowerBoundOffset = offset
|
||||
}
|
||||
|
||||
// Find item that is closest to center, but after it
|
||||
if (offset >= 0 && offset < upperBoundOffset) {
|
||||
upperBoundOffset = offset
|
||||
}
|
||||
}
|
||||
|
||||
return lowerBoundOffset.rangeTo(upperBoundOffset)
|
||||
}
|
||||
|
||||
override fun Density.calculateSnapStepSize(): Float = with(layoutInfo) {
|
||||
if (visibleItemsInfo.isNotEmpty()) {
|
||||
visibleItemsInfo.fastSumBy { it.size } / visibleItemsInfo.size.toFloat()
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberLazyListSnapFlingBehavior(lazyListState: LazyListState): FlingBehavior {
|
||||
val snappingLayout = remember(lazyListState) { lazyListSnapLayoutInfoProvider(lazyListState) }
|
||||
return rememberSnapFlingBehavior(snappingLayout)
|
||||
}
|
||||
|
||||
private fun calculateDistanceToDesiredSnapPosition(
|
||||
layoutInfo: LazyListLayoutInfo,
|
||||
item: LazyListItemInfo,
|
||||
positionInLayout: (layoutSize: Float, itemSize: Float) -> Float,
|
||||
): Float {
|
||||
val containerSize =
|
||||
with(layoutInfo) { singleAxisViewportSize - beforeContentPadding - afterContentPadding }
|
||||
|
||||
val desiredDistance =
|
||||
positionInLayout(containerSize.toFloat(), item.size.toFloat())
|
||||
|
||||
val itemCurrentPosition = item.offset
|
||||
return itemCurrentPosition - desiredDistance
|
||||
}
|
||||
|
||||
private val LazyListLayoutInfo.singleAxisViewportSize: Int
|
||||
get() = if (orientation == Orientation.Vertical) viewportSize.height else viewportSize.width
|
||||
@@ -31,8 +31,10 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import eu.kanade.tachiyomi.R
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.presentation.core.components.HorizontalPager
|
||||
import tachiyomi.presentation.core.components.material.Divider
|
||||
import tachiyomi.presentation.core.components.material.TabIndicator
|
||||
import tachiyomi.presentation.core.components.rememberPagerState
|
||||
|
||||
object TabbedDialogPaddings {
|
||||
val Horizontal = 24.dp
|
||||
|
||||
@@ -21,9 +21,11 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.presentation.core.components.HorizontalPager
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.TabIndicator
|
||||
import tachiyomi.presentation.core.components.material.TabText
|
||||
import tachiyomi.presentation.core.components.rememberPagerState
|
||||
|
||||
@Composable
|
||||
fun TabbedScreen(
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
package eu.kanade.presentation.components.dialogs
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TriStateCheckbox
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.core.prefs.CheckboxState
|
||||
import eu.kanade.presentation.category.visualName
|
||||
import eu.kanade.tachiyomi.R
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.presentation.core.components.material.TextButton
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
|
||||
@Composable
|
||||
fun ChangeCategoryDialog(
|
||||
initialSelection: List<CheckboxState<Category>>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onEditCategories: () -> Unit,
|
||||
onConfirm: (List<Long>, List<Long>) -> Unit,
|
||||
) {
|
||||
if (initialSelection.isEmpty()) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
onEditCategories()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.action_edit_categories))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.action_move_category))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.information_empty_category_dialog))
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
var selection by remember { mutableStateOf(initialSelection) }
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
Row {
|
||||
TextButton(onClick = {
|
||||
onDismissRequest()
|
||||
onEditCategories()
|
||||
},) {
|
||||
Text(text = stringResource(R.string.action_edit))
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
}
|
||||
TextButton(
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
onConfirm(
|
||||
selection.filter { it is CheckboxState.State.Checked || it is CheckboxState.TriState.Include }.map { it.value.id },
|
||||
selection.filter { it is CheckboxState.State.None || it is CheckboxState.TriState.None }.map { it.value.id },
|
||||
)
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(android.R.string.ok))
|
||||
}
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.action_move_category))
|
||||
},
|
||||
text = {
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
selection.forEach { checkbox ->
|
||||
val onChange: (CheckboxState<Category>) -> Unit = {
|
||||
val index = selection.indexOf(it)
|
||||
if (index != -1) {
|
||||
val mutableList = selection.toMutableList()
|
||||
mutableList[index] = it.next()
|
||||
selection = mutableList.toList()
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onChange(checkbox) },
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
when (checkbox) {
|
||||
is CheckboxState.TriState -> {
|
||||
TriStateCheckbox(
|
||||
state = checkbox.asState(),
|
||||
onClick = { onChange(checkbox) },
|
||||
)
|
||||
}
|
||||
is CheckboxState.State -> {
|
||||
Checkbox(
|
||||
checked = checkbox.isChecked,
|
||||
onCheckedChange = { onChange(checkbox) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = checkbox.value.visualName,
|
||||
modifier = Modifier.padding(horizontal = MaterialTheme.padding.medium),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package eu.kanade.presentation.components.dialogs
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
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.Checkbox
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.core.prefs.CheckboxState
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
@Composable
|
||||
fun DeleteLibraryMangaDialog(
|
||||
containsLocalManga: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (Boolean, Boolean) -> Unit,
|
||||
) {
|
||||
var list by remember {
|
||||
mutableStateOf(
|
||||
buildList<CheckboxState.State<Int>> {
|
||||
add(CheckboxState.State.None(R.string.manga_from_library))
|
||||
if (!containsLocalManga) {
|
||||
add(CheckboxState.State.None(R.string.downloaded_chapters))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
enabled = list.any { it.isChecked },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
onConfirm(
|
||||
list[0].isChecked,
|
||||
list.getOrElse(1) { CheckboxState.State.None(0) }.isChecked,
|
||||
)
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.action_remove))
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
list.forEach { state ->
|
||||
val onCheck = {
|
||||
val index = list.indexOf(state)
|
||||
val mutableList = list.toMutableList()
|
||||
mutableList[index] = state.next() as CheckboxState.State<Int>
|
||||
list = mutableList.toList()
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onCheck() },
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Checkbox(
|
||||
checked = state.isChecked,
|
||||
onCheckedChange = { onCheck() },
|
||||
)
|
||||
Text(text = stringResource(state.value))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package eu.kanade.presentation.components.dialogs
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
@Composable
|
||||
fun DuplicateMangaDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
onOpenManga: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
Row {
|
||||
TextButton(onClick = {
|
||||
onDismissRequest()
|
||||
onOpenManga()
|
||||
},) {
|
||||
Text(text = stringResource(R.string.action_show_manga))
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(R.string.action_cancel))
|
||||
}
|
||||
TextButton(
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
onConfirm()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.action_add))
|
||||
}
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.are_you_sure))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.confirm_add_duplicate_manga))
|
||||
},
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user