Range-selection for Migration (#466)
- Select a range of entries for migration - Better bottom bar - Show obsolete sources --------- Co-authored-by: ImaginaryDesignation <108343184+ImaginaryDesignation@users.noreply.github.com>
This commit is contained in:
parent
6b6e6a7fba
commit
467c39b899
10 changed files with 668 additions and 187 deletions
|
|
@ -1,35 +1,124 @@
|
||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
|
import androidx.compose.foundation.layout.only
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.FindReplace
|
||||||
|
import androidx.compose.material.icons.outlined.FlipToBack
|
||||||
|
import androidx.compose.material.icons.outlined.SelectAll
|
||||||
|
import androidx.compose.material.icons.outlined.VerticalAlignBottom
|
||||||
|
import androidx.compose.material.icons.outlined.VerticalAlignTop
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
||||||
|
import eu.kanade.presentation.manga.components.Button
|
||||||
|
import eu.kanade.presentation.util.animateItemFastScroll
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaItem
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreenModel
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreenModel
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.i18n.kmk.KMR
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateMangaScreen(
|
fun MigrateMangaScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
title: String?,
|
title: String,
|
||||||
state: MigrateMangaScreenModel.State,
|
state: MigrateMangaScreenModel.State,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (MigrateMangaItem) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
|
// KMK -->
|
||||||
|
onMultiMigrateClicked: (() -> Unit),
|
||||||
|
onSelectAll: (Boolean) -> Unit,
|
||||||
|
onInvertSelection: () -> Unit,
|
||||||
|
onMangaSelected: (MigrateMangaItem, Boolean, Boolean, Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) })
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
val enableScrollToTop by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
listState.firstVisibleItemIndex > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val enableScrollToBottom by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
listState.canScrollForward
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
// KMK -->
|
||||||
|
MigrateMangaAppBar(
|
||||||
title = title,
|
title = title,
|
||||||
|
itemCnt = state.titles.size,
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
|
selectedCount = state.selected.size,
|
||||||
|
onClickUnselectAll = { onSelectAll(false) },
|
||||||
|
onClickSelectAll = { onSelectAll(true) },
|
||||||
|
onClickInvertSelection = onInvertSelection,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
bottomBar = {
|
||||||
|
MigrateMangaBottomBar(
|
||||||
|
selected = state.selected,
|
||||||
|
onMultiMigrateClicked = onMultiMigrateClicked,
|
||||||
|
enableScrollToTop = enableScrollToTop,
|
||||||
|
enableScrollToBottom = enableScrollToBottom,
|
||||||
|
scrollToTop = {
|
||||||
|
scope.launch {
|
||||||
|
listState.scrollToItem(0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollToBottom = {
|
||||||
|
scope.launch {
|
||||||
|
listState.scrollToItem(state.titles.size - 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// KMK <--
|
||||||
|
)
|
||||||
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
if (state.isEmpty) {
|
if (state.isEmpty) {
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
|
|
@ -44,6 +133,10 @@ fun MigrateMangaScreen(
|
||||||
state = state,
|
state = state,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onClickCover = onClickCover,
|
onClickCover = onClickCover,
|
||||||
|
// KMK -->
|
||||||
|
onMangaSelected = onMangaSelected,
|
||||||
|
listState = listState,
|
||||||
|
// KMK <--
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,17 +145,36 @@ fun MigrateMangaScreen(
|
||||||
private fun MigrateMangaContent(
|
private fun MigrateMangaContent(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
state: MigrateMangaScreenModel.State,
|
state: MigrateMangaScreenModel.State,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (MigrateMangaItem) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
|
// KMK -->
|
||||||
|
onMangaSelected: (MigrateMangaItem, Boolean, Boolean, Boolean) -> Unit,
|
||||||
|
listState: LazyListState,
|
||||||
) {
|
) {
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
|
state = listState,
|
||||||
) {
|
) {
|
||||||
items(state.titles) { manga ->
|
// KMK <--
|
||||||
|
items(items = state.titles) {
|
||||||
MigrateMangaItem(
|
MigrateMangaItem(
|
||||||
manga = manga,
|
manga = it.manga,
|
||||||
onClickItem = onClickItem,
|
onClickItem = {
|
||||||
onClickCover = onClickCover,
|
// KMK -->
|
||||||
|
when {
|
||||||
|
state.selectionMode -> onMangaSelected(it, !it.selected, true, false)
|
||||||
|
// KMK <--
|
||||||
|
else -> onClickItem(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClickCover = {
|
||||||
|
onClickCover(it.manga)
|
||||||
|
// KMK -->
|
||||||
|
}.takeIf { !state.selectionMode },
|
||||||
|
onLongClick = { onMangaSelected(it, !it.selected, true, true) },
|
||||||
|
selected = it.selected,
|
||||||
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
|
// KMK <--
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -71,14 +183,159 @@ private fun MigrateMangaContent(
|
||||||
@Composable
|
@Composable
|
||||||
private fun MigrateMangaItem(
|
private fun MigrateMangaItem(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: () -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (() -> Unit)?,
|
||||||
|
// KMK -->
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
selected: Boolean,
|
||||||
|
// KMK <--
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseMangaListItem(
|
BaseMangaListItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
manga = manga,
|
manga = manga,
|
||||||
onClickItem = { onClickItem(manga) },
|
onClickItem = onClickItem,
|
||||||
onClickCover = { onClickCover(manga) },
|
onClickCover = { onClickCover?.invoke() },
|
||||||
|
// KMK -->
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
selected = selected,
|
||||||
|
// KMK <--
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KMK -->
|
||||||
|
@Composable
|
||||||
|
private fun MigrateMangaAppBar(
|
||||||
|
title: String,
|
||||||
|
itemCnt: Int,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
selectedCount: Int,
|
||||||
|
onClickUnselectAll: () -> Unit,
|
||||||
|
onClickSelectAll: () -> Unit,
|
||||||
|
onClickInvertSelection: () -> Unit,
|
||||||
|
scrollBehavior: TopAppBarScrollBehavior,
|
||||||
|
) {
|
||||||
|
AppBar(
|
||||||
|
title = title,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
actions = {
|
||||||
|
if (itemCnt > 0) {
|
||||||
|
AppBarActions(
|
||||||
|
persistentListOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.action_select_all),
|
||||||
|
icon = Icons.Outlined.SelectAll,
|
||||||
|
onClick = onClickSelectAll,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actionModeCounter = selectedCount,
|
||||||
|
onCancelActionMode = onClickUnselectAll,
|
||||||
|
actionModeActions = {
|
||||||
|
AppBarActions(
|
||||||
|
persistentListOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.action_select_all),
|
||||||
|
icon = Icons.Outlined.SelectAll,
|
||||||
|
onClick = onClickSelectAll,
|
||||||
|
),
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(MR.strings.action_select_inverse),
|
||||||
|
icon = Icons.Outlined.FlipToBack,
|
||||||
|
onClick = onClickInvertSelection,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MigrateMangaBottomBar(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
selected: List<MigrateMangaItem>,
|
||||||
|
onMultiMigrateClicked: (() -> Unit),
|
||||||
|
enableScrollToTop: Boolean,
|
||||||
|
enableScrollToBottom: Boolean,
|
||||||
|
scrollToTop: () -> Unit,
|
||||||
|
scrollToBottom: () -> Unit,
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val animatedElevation by animateDpAsState(
|
||||||
|
targetValue = if (selected.isNotEmpty()) 3.dp else 0.dp,
|
||||||
|
label = "elevation",
|
||||||
|
)
|
||||||
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = MaterialTheme.shapes.large.copy(
|
||||||
|
bottomEnd = ZeroCornerSize,
|
||||||
|
bottomStart = ZeroCornerSize,
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||||
|
elevation = animatedElevation,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
val confirm = remember { mutableStateListOf(false, false, false) }
|
||||||
|
var resetJob: Job? = remember { null }
|
||||||
|
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
(0 until 3).forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||||
|
resetJob?.cancel()
|
||||||
|
resetJob = scope.launch {
|
||||||
|
delay(1.seconds)
|
||||||
|
if (isActive) confirm[toConfirmIndex] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
WindowInsets.navigationBars
|
||||||
|
.only(WindowInsetsSides.Bottom)
|
||||||
|
.asPaddingValues(),
|
||||||
|
)
|
||||||
|
.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
title = stringResource(KMR.strings.action_scroll_to_top),
|
||||||
|
icon = Icons.Outlined.VerticalAlignTop,
|
||||||
|
toConfirm = confirm[0],
|
||||||
|
onLongClick = { onLongClickItem(0) },
|
||||||
|
onClick = if (enableScrollToTop) {
|
||||||
|
scrollToTop
|
||||||
|
} else {
|
||||||
|
{}
|
||||||
|
},
|
||||||
|
enabled = enableScrollToTop,
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
title = stringResource(MR.strings.migrate),
|
||||||
|
icon = Icons.Outlined.FindReplace,
|
||||||
|
toConfirm = confirm[1],
|
||||||
|
onLongClick = { onLongClickItem(1) },
|
||||||
|
onClick = if (selected.isNotEmpty()) {
|
||||||
|
onMultiMigrateClicked
|
||||||
|
} else {
|
||||||
|
{}
|
||||||
|
},
|
||||||
|
enabled = selected.isNotEmpty(),
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
title = stringResource(KMR.strings.action_scroll_to_bottom),
|
||||||
|
icon = Icons.Outlined.VerticalAlignBottom,
|
||||||
|
toConfirm = confirm[2],
|
||||||
|
onLongClick = { onLongClickItem(2) },
|
||||||
|
onClick = if (enableScrollToBottom) {
|
||||||
|
scrollToBottom
|
||||||
|
} else {
|
||||||
|
{}
|
||||||
|
},
|
||||||
|
enabled = enableScrollToBottom,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// KMK <--
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,15 @@ import androidx.compose.material.icons.outlined.Numbers
|
||||||
import androidx.compose.material.icons.outlined.SortByAlpha
|
import androidx.compose.material.icons.outlined.SortByAlpha
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalTextStyle
|
|
||||||
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.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
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.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
|
import eu.kanade.domain.source.model.installedExtension
|
||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.presentation.browse.components.SourceIcon
|
import eu.kanade.presentation.browse.components.SourceIcon
|
||||||
import eu.kanade.presentation.components.AnimatedFloatingSearchBox
|
import eu.kanade.presentation.components.AnimatedFloatingSearchBox
|
||||||
|
|
@ -57,9 +56,6 @@ fun MigrateSourceScreen(
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
onToggleSortingDirection: () -> Unit,
|
onToggleSortingDirection: () -> Unit,
|
||||||
onToggleSortingMode: () -> Unit,
|
onToggleSortingMode: () -> Unit,
|
||||||
// SY -->
|
|
||||||
onClickAll: (Source) -> Unit,
|
|
||||||
// SY <--
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
@ -87,9 +83,6 @@ fun MigrateSourceScreen(
|
||||||
onToggleSortingMode = onToggleSortingMode,
|
onToggleSortingMode = onToggleSortingMode,
|
||||||
sortingDirection = state.sortingDirection,
|
sortingDirection = state.sortingDirection,
|
||||||
onToggleSortingDirection = onToggleSortingDirection,
|
onToggleSortingDirection = onToggleSortingDirection,
|
||||||
// SY -->
|
|
||||||
onClickAll = onClickAll,
|
|
||||||
// SY <--
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
state = state,
|
state = state,
|
||||||
onChangeSearchQuery = onChangeSearchQuery,
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
|
|
@ -108,9 +101,6 @@ private fun MigrateSourceList(
|
||||||
onToggleSortingMode: () -> Unit,
|
onToggleSortingMode: () -> Unit,
|
||||||
sortingDirection: SetMigrateSorting.Direction,
|
sortingDirection: SetMigrateSorting.Direction,
|
||||||
onToggleSortingDirection: () -> Unit,
|
onToggleSortingDirection: () -> Unit,
|
||||||
// SY -->
|
|
||||||
onClickAll: (Source) -> Unit,
|
|
||||||
// SY <--
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
state: MigrateSourceScreenModel.State,
|
state: MigrateSourceScreenModel.State,
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
|
|
@ -199,9 +189,6 @@ private fun MigrateSourceList(
|
||||||
count = count,
|
count = count,
|
||||||
onClickItem = { onClickItem(source) },
|
onClickItem = { onClickItem(source) },
|
||||||
onLongClickItem = { onLongClickItem(source) },
|
onLongClickItem = { onLongClickItem(source) },
|
||||||
// SY -->
|
|
||||||
onClickAll = { onClickAll(source) },
|
|
||||||
// SY <--
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -214,9 +201,6 @@ private fun MigrateSourceItem(
|
||||||
count: Long,
|
count: Long,
|
||||||
onClickItem: () -> Unit,
|
onClickItem: () -> Unit,
|
||||||
onLongClickItem: () -> Unit,
|
onLongClickItem: () -> Unit,
|
||||||
// SY -->
|
|
||||||
onClickAll: () -> Unit,
|
|
||||||
// SY <--
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseSourceItem(
|
BaseSourceItem(
|
||||||
|
|
@ -230,16 +214,6 @@ private fun MigrateSourceItem(
|
||||||
BadgeGroup {
|
BadgeGroup {
|
||||||
Badge(text = "$count")
|
Badge(text = "$count")
|
||||||
}
|
}
|
||||||
// SY -->
|
|
||||||
TextButton(onClick = onClickAll) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(MR.strings.all),
|
|
||||||
style = LocalTextStyle.current.copy(
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
},
|
},
|
||||||
content = { _, sourceLangString, /* KMK --> */ lang /* KMK <-- */ ->
|
content = { _, sourceLangString, /* KMK --> */ lang /* KMK <-- */ ->
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -276,6 +250,17 @@ private fun MigrateSourceItem(
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
)
|
)
|
||||||
|
// KMK -->
|
||||||
|
} else if (source.installedExtension?.isObsolete == true) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
text = stringResource(MR.strings.ext_obsolete),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
)
|
||||||
|
// KMK <--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,14 @@
|
||||||
package eu.kanade.presentation.libraryUpdateError
|
package eu.kanade.presentation.libraryUpdateError
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.animation.animateColorAsState
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
|
||||||
import androidx.compose.animation.expandVertically
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.animation.shrinkVertically
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.only
|
import androidx.compose.foundation.layout.only
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.ZeroCornerSize
|
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
|
@ -31,12 +19,10 @@ import androidx.compose.material.icons.outlined.FlipToBack
|
||||||
import androidx.compose.material.icons.outlined.SelectAll
|
import androidx.compose.material.icons.outlined.SelectAll
|
||||||
import androidx.compose.material.icons.outlined.VerticalAlignBottom
|
import androidx.compose.material.icons.outlined.VerticalAlignBottom
|
||||||
import androidx.compose.material.icons.outlined.VerticalAlignTop
|
import androidx.compose.material.icons.outlined.VerticalAlignTop
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.material3.ripple
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
|
@ -45,16 +31,14 @@ import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.libraryUpdateError.components.libraryUpdateErrorUiItems
|
import eu.kanade.presentation.libraryUpdateError.components.libraryUpdateErrorUiItems
|
||||||
|
import eu.kanade.presentation.manga.components.Button
|
||||||
import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorItem
|
import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorItem
|
||||||
import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorScreenState
|
import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorScreenState
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -74,7 +58,6 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
@Composable
|
@Composable
|
||||||
fun LibraryUpdateErrorScreen(
|
fun LibraryUpdateErrorScreen(
|
||||||
state: LibraryUpdateErrorScreenState,
|
state: LibraryUpdateErrorScreenState,
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onClick: (LibraryUpdateErrorItem) -> Unit,
|
onClick: (LibraryUpdateErrorItem) -> Unit,
|
||||||
onClickCover: (LibraryUpdateErrorItem) -> Unit,
|
onClickCover: (LibraryUpdateErrorItem) -> Unit,
|
||||||
onMultiMigrateClicked: (() -> Unit),
|
onMultiMigrateClicked: (() -> Unit),
|
||||||
|
|
@ -83,8 +66,15 @@ fun LibraryUpdateErrorScreen(
|
||||||
onErrorSelected: (LibraryUpdateErrorItem, Boolean, Boolean, Boolean) -> Unit,
|
onErrorSelected: (LibraryUpdateErrorItem, Boolean, Boolean, Boolean) -> Unit,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val listState = rememberLazyListState()
|
BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) })
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
val headerIndexes = remember { mutableStateOf<List<Int>>(emptyList()) }
|
||||||
|
LaunchedEffect(state) {
|
||||||
|
headerIndexes.value = state.getHeaderIndexes()
|
||||||
|
}
|
||||||
|
|
||||||
val enableScrollToTop by remember {
|
val enableScrollToTop by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
|
|
@ -98,10 +88,6 @@ fun LibraryUpdateErrorScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val headerIndexes = remember { mutableStateOf<List<Int>>(emptyList()) }
|
|
||||||
LaunchedEffect(state) {
|
|
||||||
headerIndexes.value = state.getHeaderIndexes()
|
|
||||||
}
|
|
||||||
val enableScrollToPrevious by remember {
|
val enableScrollToPrevious by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
headerIndexes.value.any { it < listState.firstVisibleItemIndex }
|
headerIndexes.value.any { it < listState.firstVisibleItemIndex }
|
||||||
|
|
@ -113,11 +99,9 @@ fun LibraryUpdateErrorScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) })
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
LibraryUpdateErrorsAppBar(
|
LibraryUpdateErrorAppBar(
|
||||||
title = stringResource(
|
title = stringResource(
|
||||||
KMR.strings.label_library_update_errors,
|
KMR.strings.label_library_update_errors,
|
||||||
state.items.size,
|
state.items.size,
|
||||||
|
|
@ -132,8 +116,7 @@ fun LibraryUpdateErrorScreen(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
LibraryUpdateErrorsBottomBar(
|
LibraryUpdateErrorBottomBar(
|
||||||
modifier = modifier,
|
|
||||||
selected = state.selected,
|
selected = state.selected,
|
||||||
onMultiMigrateClicked = onMultiMigrateClicked,
|
onMultiMigrateClicked = onMultiMigrateClicked,
|
||||||
enableScrollToTop = enableScrollToTop,
|
enableScrollToTop = enableScrollToTop,
|
||||||
|
|
@ -198,7 +181,7 @@ fun LibraryUpdateErrorScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LibraryUpdateErrorsBottomBar(
|
private fun LibraryUpdateErrorBottomBar(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
selected: List<LibraryUpdateErrorItem>,
|
selected: List<LibraryUpdateErrorItem>,
|
||||||
onMultiMigrateClicked: (() -> Unit),
|
onMultiMigrateClicked: (() -> Unit),
|
||||||
|
|
@ -212,13 +195,19 @@ private fun LibraryUpdateErrorsBottomBar(
|
||||||
scrollToNext: () -> Unit,
|
scrollToNext: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val animatedElevation by animateDpAsState(
|
||||||
|
targetValue = if (selected.isNotEmpty()) 3.dp else 0.dp,
|
||||||
|
label = "elevation",
|
||||||
|
)
|
||||||
Surface(
|
Surface(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
shape = MaterialTheme.shapes.large.copy(
|
shape = MaterialTheme.shapes.large.copy(
|
||||||
bottomEnd = ZeroCornerSize,
|
bottomEnd = ZeroCornerSize,
|
||||||
bottomStart = ZeroCornerSize,
|
bottomStart = ZeroCornerSize,
|
||||||
),
|
),
|
||||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
color = MaterialTheme.colorScheme.surfaceColorAtElevation(
|
||||||
|
elevation = animatedElevation,
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val confirm = remember { mutableStateListOf(false, false, false, false, false) }
|
val confirm = remember { mutableStateListOf(false, false, false, false, false) }
|
||||||
|
|
@ -306,62 +295,7 @@ private fun LibraryUpdateErrorsBottomBar(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RowScope.Button(
|
private fun LibraryUpdateErrorAppBar(
|
||||||
title: String,
|
|
||||||
icon: ImageVector,
|
|
||||||
toConfirm: Boolean,
|
|
||||||
enabled: Boolean,
|
|
||||||
onLongClick: () -> Unit,
|
|
||||||
onClick: (() -> Unit),
|
|
||||||
content: (@Composable () -> Unit)? = null,
|
|
||||||
) {
|
|
||||||
val animatedWeight by animateFloatAsState(if (toConfirm) 2f else 1f)
|
|
||||||
val animatedColor by animateColorAsState(
|
|
||||||
if (enabled) {
|
|
||||||
MaterialTheme.colorScheme.onSurface
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.onSurface.copy(
|
|
||||||
alpha = 0.38f,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(48.dp)
|
|
||||||
.weight(animatedWeight)
|
|
||||||
.combinedClickable(
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
indication = ripple(bounded = false),
|
|
||||||
onLongClick = onLongClick,
|
|
||||||
onClick = onClick,
|
|
||||||
),
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = icon,
|
|
||||||
contentDescription = title,
|
|
||||||
tint = animatedColor,
|
|
||||||
)
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = toConfirm,
|
|
||||||
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
|
|
||||||
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
overflow = TextOverflow.Visible,
|
|
||||||
maxLines = 1,
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
color = animatedColor,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
content?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun LibraryUpdateErrorsAppBar(
|
|
||||||
title: String,
|
title: String,
|
||||||
itemCnt: Int,
|
itemCnt: Int,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package eu.kanade.presentation.manga.components
|
package eu.kanade.presentation.manga.components
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
|
@ -12,10 +12,13 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
import tachiyomi.presentation.core.util.selectedBackground
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BaseMangaListItem(
|
fun BaseMangaListItem(
|
||||||
|
|
@ -23,13 +26,31 @@ fun BaseMangaListItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClickItem: () -> Unit = {},
|
onClickItem: () -> Unit = {},
|
||||||
onClickCover: () -> Unit = onClickItem,
|
onClickCover: () -> Unit = onClickItem,
|
||||||
|
// KMK -->
|
||||||
|
onLongClick: () -> Unit = onClickItem,
|
||||||
|
selected: Boolean,
|
||||||
|
// KMK <--
|
||||||
cover: @Composable RowScope.() -> Unit = { defaultCover(manga, onClickCover) },
|
cover: @Composable RowScope.() -> Unit = { defaultCover(manga, onClickCover) },
|
||||||
actions: @Composable RowScope.() -> Unit = {},
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
content: @Composable RowScope.() -> Unit = { defaultContent(manga) },
|
content: @Composable RowScope.() -> Unit = { defaultContent(manga) },
|
||||||
) {
|
) {
|
||||||
|
// KMK -->
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
// KMK <--
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clickable(onClick = onClickItem)
|
// KMK -->
|
||||||
|
.selectedBackground(selected)
|
||||||
|
.combinedClickable(
|
||||||
|
// KMK <--
|
||||||
|
onClick = onClickItem,
|
||||||
|
// KMK -->
|
||||||
|
onLongClick = {
|
||||||
|
onLongClick()
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
},
|
||||||
|
// KMK <--
|
||||||
|
)
|
||||||
.height(56.dp)
|
.height(56.dp)
|
||||||
.padding(horizontal = MaterialTheme.padding.medium),
|
.padding(horizontal = MaterialTheme.padding.medium),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.kanade.presentation.manga.components
|
package eu.kanade.presentation.manga.components
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.expandVertically
|
import androidx.compose.animation.expandVertically
|
||||||
|
|
@ -182,18 +183,33 @@ fun MangaBottomActionMenu(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RowScope.Button(
|
internal fun RowScope.Button(
|
||||||
title: String,
|
title: String,
|
||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
toConfirm: Boolean,
|
toConfirm: Boolean,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
|
// KMK -->
|
||||||
|
enabled: Boolean = true,
|
||||||
|
// KMK <--
|
||||||
content: (@Composable () -> Unit)? = null,
|
content: (@Composable () -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val animatedWeight by animateFloatAsState(
|
val animatedWeight by animateFloatAsState(
|
||||||
targetValue = if (toConfirm) 2f else 1f,
|
targetValue = if (toConfirm) 2f else 1f,
|
||||||
label = "weight",
|
label = "weight",
|
||||||
)
|
)
|
||||||
|
// KMK -->
|
||||||
|
val animatedColor by animateColorAsState(
|
||||||
|
if (enabled) {
|
||||||
|
MaterialTheme.colorScheme.onSurface
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(
|
||||||
|
alpha = 0.38f,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = "color",
|
||||||
|
)
|
||||||
|
// KMK <--
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
|
|
@ -210,6 +226,9 @@ private fun RowScope.Button(
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = title,
|
contentDescription = title,
|
||||||
|
// KMK -->
|
||||||
|
tint = animatedColor,
|
||||||
|
// KMK <--
|
||||||
)
|
)
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = toConfirm,
|
visible = toConfirm,
|
||||||
|
|
@ -221,6 +240,9 @@ private fun RowScope.Button(
|
||||||
overflow = TextOverflow.Visible,
|
overflow = TextOverflow.Visible,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
// KMK -->
|
||||||
|
color = animatedColor,
|
||||||
|
// KMK <--
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
content?.invoke()
|
content?.invoke()
|
||||||
|
|
|
||||||
|
|
@ -2,36 +2,69 @@ package eu.kanade.tachiyomi.ui.browse.migration.advanced.design
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
|
import androidx.compose.foundation.layout.only
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.ArrowForward
|
import androidx.compose.material.icons.automirrored.outlined.ArrowForward
|
||||||
import androidx.compose.material.icons.outlined.Deselect
|
import androidx.compose.material.icons.outlined.Deselect
|
||||||
|
import androidx.compose.material.icons.outlined.PushPin
|
||||||
import androidx.compose.material.icons.outlined.SelectAll
|
import androidx.compose.material.icons.outlined.SelectAll
|
||||||
|
import androidx.compose.material.icons.outlined.ToggleOn
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
|
import androidx.compose.material3.ripple
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
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.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Velocity
|
import androidx.compose.ui.unit.Velocity
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
|
|
@ -41,13 +74,15 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
|
||||||
import eu.kanade.presentation.components.SourcesSearchBox
|
import eu.kanade.presentation.components.SourcesSearchBox
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.databinding.PreMigrationListBinding
|
import eu.kanade.tachiyomi.databinding.PreMigrationListBinding
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen
|
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationProcedureConfig
|
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationProcedureConfig
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.i18n.kmk.KMR
|
import tachiyomi.i18n.kmk.KMR
|
||||||
import tachiyomi.i18n.sy.SYMR
|
import tachiyomi.i18n.sy.SYMR
|
||||||
|
|
@ -57,6 +92,7 @@ import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
sealed class MigrationType : Serializable {
|
sealed class MigrationType : Serializable {
|
||||||
data class MangaList(val mangaIds: List<Long>) : MigrationType()
|
data class MangaList(val mangaIds: List<Long>) : MigrationType()
|
||||||
|
|
@ -125,32 +161,19 @@ class PreMigrationScreen(val migration: MigrationType) : Screen() {
|
||||||
title = stringResource(SYMR.strings.select_sources),
|
title = stringResource(SYMR.strings.select_sources),
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
actions = {
|
|
||||||
AppBarActions(
|
|
||||||
persistentListOf(
|
|
||||||
AppBar.Action(
|
|
||||||
title = stringResource(SYMR.strings.select_none),
|
|
||||||
icon = Icons.Outlined.Deselect,
|
|
||||||
onClick = { screenModel.massSelect(false) },
|
|
||||||
),
|
|
||||||
AppBar.Action(
|
|
||||||
title = stringResource(MR.strings.action_select_all),
|
|
||||||
icon = Icons.Outlined.SelectAll,
|
|
||||||
onClick = { screenModel.massSelect(true) },
|
|
||||||
),
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(SYMR.strings.match_enabled_sources),
|
|
||||||
onClick = { screenModel.matchSelection(true) },
|
|
||||||
),
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(SYMR.strings.match_pinned_sources),
|
|
||||||
onClick = { screenModel.matchSelection(false) },
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
// KMK -->
|
||||||
|
bottomBar = {
|
||||||
|
PreMigrationScreenBottomBar(
|
||||||
|
modifier = Modifier,
|
||||||
|
onSelectAll = { screenModel.massSelect(true) },
|
||||||
|
onSelectNone = { screenModel.massSelect(false) },
|
||||||
|
onSelectPinned = { screenModel.matchSelection(false) },
|
||||||
|
onSelectEnabled = { screenModel.matchSelection(true) },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// KMK <--
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
text = { Text(text = stringResource(MR.strings.action_migrate)) },
|
text = { Text(text = stringResource(MR.strings.action_migrate)) },
|
||||||
|
|
@ -234,6 +257,124 @@ class PreMigrationScreen(val migration: MigrationType) : Screen() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KMK -->
|
||||||
|
@Composable
|
||||||
|
fun PreMigrationScreenBottomBar(
|
||||||
|
modifier: Modifier,
|
||||||
|
onSelectAll: () -> Unit,
|
||||||
|
onSelectNone: () -> Unit,
|
||||||
|
onSelectPinned: () -> Unit,
|
||||||
|
onSelectEnabled: () -> Unit,
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = MaterialTheme.shapes.large.copy(
|
||||||
|
bottomEnd = ZeroCornerSize,
|
||||||
|
bottomStart = ZeroCornerSize,
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceColorAtElevation(elevation = 0.dp),
|
||||||
|
) {
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
val confirm = remember { mutableStateListOf(false, false, false, false) }
|
||||||
|
var resetJob: Job? = remember { null }
|
||||||
|
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
(0 until 4).forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||||
|
resetJob?.cancel()
|
||||||
|
resetJob = scope.launch {
|
||||||
|
delay(1.seconds)
|
||||||
|
if (isActive) confirm[toConfirmIndex] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
WindowInsets.navigationBars
|
||||||
|
.only(WindowInsetsSides.Bottom)
|
||||||
|
.asPaddingValues(),
|
||||||
|
)
|
||||||
|
.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
title = stringResource(MR.strings.action_select_all),
|
||||||
|
icon = Icons.Outlined.SelectAll,
|
||||||
|
onClick = onSelectAll,
|
||||||
|
toConfirm = confirm[0],
|
||||||
|
onLongClick = { onLongClickItem(0) },
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
title = stringResource(SYMR.strings.select_none),
|
||||||
|
icon = Icons.Outlined.Deselect,
|
||||||
|
onClick = onSelectNone,
|
||||||
|
toConfirm = confirm[1],
|
||||||
|
onLongClick = { onLongClickItem(1) },
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
title = stringResource(SYMR.strings.match_enabled_sources),
|
||||||
|
icon = Icons.Outlined.ToggleOn,
|
||||||
|
onClick = onSelectEnabled,
|
||||||
|
toConfirm = confirm[2],
|
||||||
|
onLongClick = { onLongClickItem(2) },
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
title = stringResource(SYMR.strings.match_pinned_sources),
|
||||||
|
icon = Icons.Outlined.PushPin,
|
||||||
|
onClick = onSelectPinned,
|
||||||
|
toConfirm = confirm[3],
|
||||||
|
onLongClick = { onLongClickItem(3) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RowScope.Button(
|
||||||
|
title: String,
|
||||||
|
icon: ImageVector,
|
||||||
|
toConfirm: Boolean,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
onClick: (() -> Unit),
|
||||||
|
content: (@Composable () -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
val animatedWeight by animateFloatAsState(
|
||||||
|
targetValue = if (toConfirm) 2f else 1f,
|
||||||
|
label = "weight",
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.weight(animatedWeight)
|
||||||
|
.combinedClickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = ripple(bounded = false),
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
onClick = onClick,
|
||||||
|
),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = title,
|
||||||
|
)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = toConfirm,
|
||||||
|
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
|
||||||
|
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
overflow = TextOverflow.Visible,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
content?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun navigateToMigration(skipPre: Boolean, navigator: Navigator, mangaIds: List<Long>) {
|
fun navigateToMigration(skipPre: Boolean, navigator: Navigator, mangaIds: List<Long>) {
|
||||||
navigator.push(
|
navigator.push(
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import tachiyomi.domain.UnsortedPreferences
|
import tachiyomi.domain.UnsortedPreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.i18n.kmk.KMR
|
||||||
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
|
||||||
|
|
@ -39,18 +40,39 @@ data class MigrateMangaScreen(
|
||||||
|
|
||||||
MigrateMangaScreen(
|
MigrateMangaScreen(
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
title = state.source!!.name,
|
title = state.source?.name ?: "???",
|
||||||
state = state,
|
state = state,
|
||||||
onClickItem = {
|
onClickItem = {
|
||||||
// SY -->
|
// SY -->
|
||||||
PreMigrationScreen.navigateToMigration(
|
PreMigrationScreen.navigateToMigration(
|
||||||
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
||||||
navigator,
|
navigator,
|
||||||
listOf(it.id),
|
listOf(it.manga.id),
|
||||||
)
|
)
|
||||||
// SY <--
|
// SY <--
|
||||||
},
|
},
|
||||||
onClickCover = { navigator.push(MangaScreen(it.id)) },
|
onClickCover = { navigator.push(MangaScreen(it.id)) },
|
||||||
|
// KMK -->
|
||||||
|
onMultiMigrateClicked = {
|
||||||
|
if (state.selectionMode) {
|
||||||
|
PreMigrationScreen.navigateToMigration(
|
||||||
|
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
||||||
|
navigator,
|
||||||
|
state.selected.map { it.manga.id },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
context.toast(KMR.strings.migrating_all_entries)
|
||||||
|
PreMigrationScreen.navigateToMigration(
|
||||||
|
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
||||||
|
navigator,
|
||||||
|
state.titles.map { it.manga.id },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSelectAll = screenModel::toggleAllSelection,
|
||||||
|
onInvertSelection = screenModel::invertSelection,
|
||||||
|
onMangaSelected = screenModel::toggleSelection,
|
||||||
|
// KMK <--
|
||||||
)
|
)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.browse.migration.manga
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
|
import eu.kanade.core.util.addOrRemove
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -32,6 +33,12 @@ class MigrateMangaScreenModel(
|
||||||
private val _events: Channel<MigrationMangaEvent> = Channel()
|
private val _events: Channel<MigrationMangaEvent> = Channel()
|
||||||
val events: Flow<MigrationMangaEvent> = _events.receiveAsFlow()
|
val events: Flow<MigrationMangaEvent> = _events.receiveAsFlow()
|
||||||
|
|
||||||
|
// KMK -->
|
||||||
|
// First and last selected index in list
|
||||||
|
private val selectedPositions: Array<Int> = arrayOf(-1, -1)
|
||||||
|
private val selectedMangaIds: HashSet<Long> = HashSet()
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
screenModelScope.launch {
|
||||||
mutableState.update { state ->
|
mutableState.update { state ->
|
||||||
|
|
@ -46,9 +53,14 @@ class MigrateMangaScreenModel(
|
||||||
state.copy(titleList = persistentListOf())
|
state.copy(titleList = persistentListOf())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// KMK -->
|
||||||
|
.map { manga ->
|
||||||
|
toMigrationMangaScreenItems(manga)
|
||||||
|
}
|
||||||
|
// KMK <--
|
||||||
.map { manga ->
|
.map { manga ->
|
||||||
manga
|
manga
|
||||||
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.title })
|
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.manga.title })
|
||||||
.toImmutableList()
|
.toImmutableList()
|
||||||
}
|
}
|
||||||
.collectLatest { list ->
|
.collectLatest { list ->
|
||||||
|
|
@ -57,13 +69,117 @@ class MigrateMangaScreenModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KMK -->
|
||||||
|
private fun toMigrationMangaScreenItems(mangas: List<Manga>): List<MigrateMangaItem> {
|
||||||
|
return mangas.map { manga ->
|
||||||
|
MigrateMangaItem(
|
||||||
|
manga = manga,
|
||||||
|
selected = manga.id in selectedMangaIds,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleSelection(
|
||||||
|
item: MigrateMangaItem,
|
||||||
|
selected: Boolean,
|
||||||
|
userSelected: Boolean = false,
|
||||||
|
fromLongPress: Boolean = false,
|
||||||
|
) {
|
||||||
|
mutableState.update { state ->
|
||||||
|
val newItems = state.titles.toMutableList().apply {
|
||||||
|
val selectedIndex = indexOfFirst { it.manga.id == item.manga.id }
|
||||||
|
if (selectedIndex < 0) return@apply
|
||||||
|
|
||||||
|
val selectedItem = get(selectedIndex)
|
||||||
|
if (selectedItem.selected == selected) return@apply
|
||||||
|
|
||||||
|
val firstSelection = none { it.selected }
|
||||||
|
set(selectedIndex, selectedItem.copy(selected = selected))
|
||||||
|
selectedMangaIds.addOrRemove(item.manga.id, selected)
|
||||||
|
|
||||||
|
if (selected && userSelected && fromLongPress) {
|
||||||
|
if (firstSelection) {
|
||||||
|
selectedPositions[0] = selectedIndex
|
||||||
|
selectedPositions[1] = selectedIndex
|
||||||
|
} else {
|
||||||
|
// Try to select the items in-between when possible
|
||||||
|
val range: IntRange
|
||||||
|
if (selectedIndex < selectedPositions[0]) {
|
||||||
|
range = selectedIndex + 1 until selectedPositions[0]
|
||||||
|
selectedPositions[0] = selectedIndex
|
||||||
|
} else if (selectedIndex > selectedPositions[1]) {
|
||||||
|
range = (selectedPositions[1] + 1) until selectedIndex
|
||||||
|
selectedPositions[1] = selectedIndex
|
||||||
|
} else {
|
||||||
|
// Just select itself
|
||||||
|
range = IntRange.EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
range.forEach {
|
||||||
|
val inBetweenItem = get(it)
|
||||||
|
if (!inBetweenItem.selected) {
|
||||||
|
selectedMangaIds.add(inBetweenItem.manga.id)
|
||||||
|
set(it, inBetweenItem.copy(selected = true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (userSelected && !fromLongPress) {
|
||||||
|
if (!selected) {
|
||||||
|
if (selectedIndex == selectedPositions[0]) {
|
||||||
|
selectedPositions[0] = indexOfFirst { it.selected }
|
||||||
|
} else if (selectedIndex == selectedPositions[1]) {
|
||||||
|
selectedPositions[1] = indexOfLast { it.selected }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (selectedIndex < selectedPositions[0]) {
|
||||||
|
selectedPositions[0] = selectedIndex
|
||||||
|
} else if (selectedIndex > selectedPositions[1]) {
|
||||||
|
selectedPositions[1] = selectedIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.copy(titleList = newItems.toImmutableList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleAllSelection(selected: Boolean) {
|
||||||
|
mutableState.update { state ->
|
||||||
|
val newItems = state.titles.map {
|
||||||
|
selectedMangaIds.addOrRemove(it.manga.id, selected)
|
||||||
|
it.copy(selected = selected)
|
||||||
|
}
|
||||||
|
state.copy(titleList = newItems.toImmutableList())
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedPositions[0] = -1
|
||||||
|
selectedPositions[1] = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invertSelection() {
|
||||||
|
mutableState.update { state ->
|
||||||
|
val newItems = state.titles.map {
|
||||||
|
selectedMangaIds.addOrRemove(it.manga.id, !it.selected)
|
||||||
|
it.copy(selected = !it.selected)
|
||||||
|
}
|
||||||
|
state.copy(titleList = newItems.toImmutableList())
|
||||||
|
}
|
||||||
|
selectedPositions[0] = -1
|
||||||
|
selectedPositions[1] = -1
|
||||||
|
}
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val source: Source? = null,
|
val source: Source? = null,
|
||||||
private val titleList: ImmutableList<Manga>? = null,
|
private val titleList: ImmutableList<MigrateMangaItem>? = null,
|
||||||
) {
|
) {
|
||||||
|
// KMK -->
|
||||||
|
val selected = titles.filter { it.selected }
|
||||||
|
val selectionMode = selected.isNotEmpty()
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
val titles: ImmutableList<Manga>
|
val titles: ImmutableList<MigrateMangaItem>
|
||||||
get() = titleList ?: persistentListOf()
|
get() = titleList ?: persistentListOf()
|
||||||
|
|
||||||
val isLoading: Boolean
|
val isLoading: Boolean
|
||||||
|
|
@ -77,3 +193,11 @@ class MigrateMangaScreenModel(
|
||||||
sealed interface MigrationMangaEvent {
|
sealed interface MigrationMangaEvent {
|
||||||
data object FailedFetchingFavorites : MigrationMangaEvent
|
data object FailedFetchingFavorites : MigrationMangaEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KMK -->
|
||||||
|
@Immutable
|
||||||
|
data class MigrateMangaItem(
|
||||||
|
val manga: Manga,
|
||||||
|
val selected: Boolean,
|
||||||
|
)
|
||||||
|
// KMK <--
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,10 @@ import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.MigrateSourceScreen
|
import eu.kanade.presentation.browse.MigrateSourceScreen
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.TabContent
|
import eu.kanade.presentation.components.TabContent
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreen
|
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreen
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
|
||||||
import tachiyomi.core.common.util.lang.withUIContext
|
|
||||||
import tachiyomi.domain.UnsortedPreferences
|
|
||||||
import tachiyomi.domain.manga.interactor.GetFavorites
|
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Screen.migrateSourceTab(): TabContent {
|
fun Screen.migrateSourceTab(): TabContent {
|
||||||
|
|
@ -53,24 +45,6 @@ fun Screen.migrateSourceTab(): TabContent {
|
||||||
},
|
},
|
||||||
onToggleSortingDirection = screenModel::toggleSortingDirection,
|
onToggleSortingDirection = screenModel::toggleSortingDirection,
|
||||||
onToggleSortingMode = screenModel::toggleSortingMode,
|
onToggleSortingMode = screenModel::toggleSortingMode,
|
||||||
// SY -->
|
|
||||||
onClickAll = { source ->
|
|
||||||
// TODO: Jay wtf, need to clean this up sometime
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
|
||||||
launchIO {
|
|
||||||
val manga = Injekt.get<GetFavorites>().await()
|
|
||||||
val sourceMangas =
|
|
||||||
manga.asSequence().filter { it.source == source.id }.map { it.id }.toList()
|
|
||||||
withUIContext {
|
|
||||||
PreMigrationScreen.navigateToMigration(
|
|
||||||
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
|
||||||
navigator,
|
|
||||||
sourceMangas,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// SY <--
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
onChangeSearchQuery = screenModel::search,
|
onChangeSearchQuery = screenModel::search,
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,7 @@
|
||||||
|
|
||||||
<!-- Migration -->
|
<!-- Migration -->
|
||||||
<string name="current_">Current: %1$s</string>
|
<string name="current_">Current: %1$s</string>
|
||||||
|
<string name="migrating_all_entries">Migrating all entries from source</string>
|
||||||
|
|
||||||
<!-- Misc -->
|
<!-- Misc -->
|
||||||
<string name="label_to_be_updated">To be updated</string>
|
<string name="label_to_be_updated">To be updated</string>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue