mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-21 20:47:03 -05:00
Bump compose version
Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>
This commit is contained in:
parent
263e467cde
commit
e473c7f09f
25 changed files with 112 additions and 411 deletions
|
@ -190,7 +190,7 @@ private fun ExtensionDetails(
|
||||||
key = { it.source.id },
|
key = { it.source.id },
|
||||||
) { source ->
|
) { source ->
|
||||||
SourceSwitchPreference(
|
SourceSwitchPreference(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
source = source,
|
source = source,
|
||||||
onClickSourcePreferences = onClickSourcePreferences,
|
onClickSourcePreferences = onClickSourcePreferences,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
|
|
|
@ -58,7 +58,7 @@ private fun ExtensionFilterContent(
|
||||||
) {
|
) {
|
||||||
items(state.languages) { language ->
|
items(state.languages) { language ->
|
||||||
SwitchPreferenceWidget(
|
SwitchPreferenceWidget(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
title = LocaleHelper.getSourceDisplayName(language, context),
|
title = LocaleHelper.getSourceDisplayName(language, context),
|
||||||
checked = language in state.enabledLanguages,
|
checked = language in state.enabledLanguages,
|
||||||
onCheckedChanged = { onClickLang(language) },
|
onCheckedChanged = { onClickLang(language) },
|
||||||
|
|
|
@ -187,14 +187,14 @@ private fun ExtensionContent(
|
||||||
}
|
}
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
textRes = header.textRes,
|
textRes = header.textRes,
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
action = action,
|
action = action,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is ExtensionUiModel.Header.Text -> {
|
is ExtensionUiModel.Header.Text -> {
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
text = header.text,
|
text = header.text,
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,7 +212,7 @@ private fun ExtensionContent(
|
||||||
},
|
},
|
||||||
) { item ->
|
) { item ->
|
||||||
ExtensionItem(
|
ExtensionItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
item = item,
|
item = item,
|
||||||
onClickItem = {
|
onClickItem = {
|
||||||
when (it) {
|
when (it) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
||||||
|
@ -79,6 +80,7 @@ internal fun GlobalSearchContent(
|
||||||
} ?: source.name,
|
} ?: source.name,
|
||||||
subtitle = LocaleHelper.getLocalizedDisplayName(source.lang),
|
subtitle = LocaleHelper.getLocalizedDisplayName(source.lang),
|
||||||
onClick = { onClickSource(source) },
|
onClick = { onClickSource(source) },
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
) {
|
) {
|
||||||
when (result) {
|
when (result) {
|
||||||
SearchItemResult.Loading -> {
|
SearchItemResult.Loading -> {
|
||||||
|
|
|
@ -133,7 +133,7 @@ private fun MigrateSourceList(
|
||||||
key = { (source, _) -> "migrate-${source.id}" },
|
key = { (source, _) -> "migrate-${source.id}" },
|
||||||
) { (source, count) ->
|
) { (source, count) ->
|
||||||
MigrateSourceItem(
|
MigrateSourceItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
source = source,
|
source = source,
|
||||||
count = count,
|
count = count,
|
||||||
onClickItem = { onClickItem(source) },
|
onClickItem = { onClickItem(source) },
|
||||||
|
|
|
@ -68,7 +68,7 @@ private fun SourcesFilterContent(
|
||||||
contentType = "source-filter-header",
|
contentType = "source-filter-header",
|
||||||
) {
|
) {
|
||||||
SourcesFilterHeader(
|
SourcesFilterHeader(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
language = language,
|
language = language,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
onClickItem = onClickLanguage,
|
onClickItem = onClickLanguage,
|
||||||
|
@ -81,7 +81,7 @@ private fun SourcesFilterContent(
|
||||||
contentType = { "source-filter-item" },
|
contentType = { "source-filter-item" },
|
||||||
) { source ->
|
) { source ->
|
||||||
SourcesFilterItem(
|
SourcesFilterItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
source = source,
|
source = source,
|
||||||
enabled = "${source.id}" !in state.disabledSources,
|
enabled = "${source.id}" !in state.disabledSources,
|
||||||
onClickItem = onClickSource,
|
onClickItem = onClickSource,
|
||||||
|
|
|
@ -74,12 +74,12 @@ fun SourcesScreen(
|
||||||
when (model) {
|
when (model) {
|
||||||
is SourceUiModel.Header -> {
|
is SourceUiModel.Header -> {
|
||||||
SourceHeader(
|
SourceHeader(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
language = model.language,
|
language = model.language,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is SourceUiModel.Item -> SourceItem(
|
is SourceUiModel.Item -> SourceItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
source = model.source,
|
source = model.source,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
|
|
|
@ -32,9 +32,10 @@ fun GlobalSearchResultItem(
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: String,
|
subtitle: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
Column {
|
Column(modifier = modifier) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(
|
.padding(
|
||||||
|
|
|
@ -107,7 +107,7 @@ private fun CategoryContent(
|
||||||
key = { _, category -> "category-${category.id}" },
|
key = { _, category -> "category-${category.id}" },
|
||||||
) { index, category ->
|
) { index, category ->
|
||||||
CategoryListItem(
|
CategoryListItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
category = category,
|
category = category,
|
||||||
canMoveUp = index != 0,
|
canMoveUp = index != 0,
|
||||||
canMoveDown = index != categories.lastIndex,
|
canMoveDown = index != categories.lastIndex,
|
||||||
|
|
|
@ -10,8 +10,7 @@ import androidx.compose.ui.Modifier
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||||
import tachiyomi.presentation.core.util.isScrollingUp
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CategoryFloatingActionButton(
|
fun CategoryFloatingActionButton(
|
||||||
|
@ -23,7 +22,7 @@ fun CategoryFloatingActionButton(
|
||||||
text = { Text(text = stringResource(MR.strings.action_add)) },
|
text = { Text(text = stringResource(MR.strings.action_add)) },
|
||||||
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
|
||||||
onClick = onCreate,
|
onClick = onCreate,
|
||||||
expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(),
|
expanded = lazyListState.shouldExpandFAB(),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,14 +113,14 @@ private fun HistoryScreenContent(
|
||||||
when (item) {
|
when (item) {
|
||||||
is HistoryUiModel.Header -> {
|
is HistoryUiModel.Header -> {
|
||||||
ListGroupHeader(
|
ListGroupHeader(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
text = relativeDateText(item.date),
|
text = relativeDateText(item.date),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is HistoryUiModel.Item -> {
|
is HistoryUiModel.Item -> {
|
||||||
val value = item.item
|
val value = item.item
|
||||||
HistoryItem(
|
HistoryItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
history = value,
|
history = value,
|
||||||
onClickCover = { onClickCover(value) },
|
onClickCover = { onClickCover(value) },
|
||||||
onClickResume = { onClickResume(value) },
|
onClickResume = { onClickResume(value) },
|
||||||
|
|
|
@ -75,8 +75,7 @@ import tachiyomi.presentation.core.components.material.ExtendedFloatingActionBut
|
||||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||||
import tachiyomi.presentation.core.util.isScrollingUp
|
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
|
@ -346,7 +345,7 @@ private fun MangaScreenSmallImpl(
|
||||||
},
|
},
|
||||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
onClick = onContinueReading,
|
onClick = onContinueReading,
|
||||||
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
expanded = chapterListState.shouldExpandFAB(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -594,7 +593,7 @@ fun MangaScreenLargeImpl(
|
||||||
},
|
},
|
||||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
onClick = onContinueReading,
|
onClick = onContinueReading,
|
||||||
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
expanded = chapterListState.shouldExpandFAB(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -42,7 +42,7 @@ import androidx.compose.material.icons.outlined.Sync
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
|
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ProvideTextStyle
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
|
@ -649,7 +649,7 @@ private fun TagsChip(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
|
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||||
SuggestionChip(
|
SuggestionChip(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
|
|
@ -31,8 +31,6 @@ import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.TextButton
|
import tachiyomi.presentation.core.components.material.TextButton
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
|
||||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ScanlatorFilterDialog(
|
fun ScanlatorFilterDialog(
|
||||||
|
@ -96,8 +94,8 @@ fun ScanlatorFilterDialog(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||||
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
properties = DialogProperties(
|
properties = DialogProperties(
|
||||||
|
|
|
@ -28,11 +28,11 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
||||||
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
|
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
|
|
@ -46,7 +46,7 @@ fun ExtensionReposContent(
|
||||||
repos.forEach {
|
repos.forEach {
|
||||||
item {
|
item {
|
||||||
ExtensionRepoListItem(
|
ExtensionRepoListItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
repo = it,
|
repo = it,
|
||||||
onOpenWebsite = { onOpenWebsite(it) },
|
onOpenWebsite = { onOpenWebsite(it) },
|
||||||
onDelete = { onClickDelete(it.baseUrl) },
|
onDelete = { onClickDelete(it.baseUrl) },
|
||||||
|
|
|
@ -26,8 +26,6 @@ import androidx.compose.ui.unit.dp
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
|
||||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> ListPreferenceWidget(
|
fun <T> ListPreferenceWidget(
|
||||||
|
@ -69,8 +67,8 @@ fun <T> ListPreferenceWidget(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||||
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
|
|
@ -30,8 +30,6 @@ import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
|
||||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
|
||||||
|
|
||||||
private enum class State {
|
private enum class State {
|
||||||
CHECKED, INVERSED, UNCHECKED
|
CHECKED, INVERSED, UNCHECKED
|
||||||
|
@ -115,16 +113,8 @@ fun <T> TriStateListDialog(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!listState.isScrolledToStart()) {
|
if (listState.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||||
HorizontalDivider(
|
if (listState.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||||
modifier = Modifier.align(Alignment.TopCenter),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (!listState.isScrolledToEnd()) {
|
|
||||||
HorizontalDivider(
|
|
||||||
modifier = Modifier.align(Alignment.BottomCenter),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -43,8 +43,6 @@ import tachiyomi.presentation.core.components.WheelTextPicker
|
||||||
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 tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
|
||||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TrackStatusSelector(
|
fun TrackStatusSelector(
|
||||||
|
@ -86,8 +84,8 @@ fun TrackStatusSelector(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||||
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||||
},
|
},
|
||||||
onConfirm = onConfirm,
|
onConfirm = onConfirm,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
|
|
|
@ -54,7 +54,7 @@ internal fun LazyListScope.updatesLastUpdatedItem(
|
||||||
item(key = "updates-lastUpdated") {
|
item(key = "updates-lastUpdated") {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateItemPlacement()
|
.animateItem()
|
||||||
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -91,14 +91,14 @@ internal fun LazyListScope.updatesUiItems(
|
||||||
when (item) {
|
when (item) {
|
||||||
is UpdatesUiModel.Header -> {
|
is UpdatesUiModel.Header -> {
|
||||||
ListGroupHeader(
|
ListGroupHeader(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
text = relativeDateText(item.date),
|
text = relativeDateText(item.date),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is UpdatesUiModel.Item -> {
|
is UpdatesUiModel.Item -> {
|
||||||
val updatesItem = item.item
|
val updatesItem = item.item
|
||||||
UpdatesUiItem(
|
UpdatesUiItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
update = updatesItem.update,
|
update = updatesItem.update,
|
||||||
selected = updatesItem.selected,
|
selected = updatesItem.selected,
|
||||||
readProgress = updatesItem.update.lastPageRead
|
readProgress = updatesItem.update.lastPageRead
|
||||||
|
|
|
@ -7,9 +7,9 @@ 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.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberRequestPackageInstallsPermissionState(initialValue: Boolean = false): Boolean {
|
fun rememberRequestPackageInstallsPermissionState(initialValue: Boolean = false): Boolean {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
[versions]
|
[versions]
|
||||||
compiler = "1.5.12"
|
compiler = "1.5.12"
|
||||||
# 2024.04.00-alpha01 has several bugs with the new animateItem() modifier
|
compose-bom = "2024.05.00-alpha01"
|
||||||
compose-bom = "2024.03.00-alpha02"
|
|
||||||
accompanist = "0.35.0-alpha"
|
accompanist = "0.35.0-alpha"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|
|
@ -153,7 +153,9 @@ fun AdaptiveSheet(
|
||||||
if (enableSwipeDismiss) {
|
if (enableSwipeDismiss) {
|
||||||
Modifier.nestedScroll(
|
Modifier.nestedScroll(
|
||||||
remember(anchoredDraggableState) {
|
remember(anchoredDraggableState) {
|
||||||
anchoredDraggableState.preUpPostDownNestedScrollConnection()
|
anchoredDraggableState.preUpPostDownNestedScrollConnection(
|
||||||
|
onFling = { scope.launch { anchoredDraggableState.settle(it) } }
|
||||||
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -201,55 +203,51 @@ fun AdaptiveSheet(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T> AnchoredDraggableState<T>.preUpPostDownNestedScrollConnection() =
|
private fun <T> AnchoredDraggableState<T>.preUpPostDownNestedScrollConnection(
|
||||||
object : NestedScrollConnection {
|
onFling: (velocity: Float) -> Unit
|
||||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
) = object : NestedScrollConnection {
|
||||||
val delta = available.toFloat()
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||||
return if (delta < 0 && source == NestedScrollSource.Drag) {
|
val delta = available.toFloat()
|
||||||
dispatchRawDelta(delta).toOffset()
|
return if (delta < 0 && source == NestedScrollSource.UserInput) {
|
||||||
} else {
|
dispatchRawDelta(delta).toOffset()
|
||||||
Offset.Zero
|
} else {
|
||||||
}
|
Offset.Zero
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPostScroll(
|
|
||||||
consumed: Offset,
|
|
||||||
available: Offset,
|
|
||||||
source: NestedScrollSource,
|
|
||||||
): Offset {
|
|
||||||
return if (source == NestedScrollSource.Drag) {
|
|
||||||
dispatchRawDelta(available.toFloat()).toOffset()
|
|
||||||
} else {
|
|
||||||
Offset.Zero
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
|
||||||
val toFling = available.toFloat()
|
|
||||||
return if (toFling < 0 && offset > anchors.minAnchor()) {
|
|
||||||
settle(toFling)
|
|
||||||
// since we go to the anchor with tween settling, consume all for the best UX
|
|
||||||
available
|
|
||||||
} else {
|
|
||||||
Velocity.Zero
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
|
|
||||||
val toFling = available.toFloat()
|
|
||||||
return if (toFling > 0) {
|
|
||||||
settle(toFling)
|
|
||||||
available
|
|
||||||
} else {
|
|
||||||
Velocity.Zero
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Float.toOffset(): Offset = Offset(0f, this)
|
|
||||||
|
|
||||||
@JvmName("velocityToFloat")
|
|
||||||
private fun Velocity.toFloat() = this.y
|
|
||||||
|
|
||||||
@JvmName("offsetToFloat")
|
|
||||||
private fun Offset.toFloat(): Float = this.y
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPostScroll(
|
||||||
|
consumed: Offset,
|
||||||
|
available: Offset,
|
||||||
|
source: NestedScrollSource,
|
||||||
|
): Offset {
|
||||||
|
return if (source == NestedScrollSource.UserInput) {
|
||||||
|
dispatchRawDelta(available.toFloat()).toOffset()
|
||||||
|
} else {
|
||||||
|
Offset.Zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||||
|
val toFling = available.toFloat()
|
||||||
|
return if (toFling < 0 && offset > anchors.minAnchor()) {
|
||||||
|
onFling(toFling)
|
||||||
|
// since we go to the anchor with tween settling, consume all for the best UX
|
||||||
|
available
|
||||||
|
} else {
|
||||||
|
Velocity.Zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
|
||||||
|
onFling(available.toFloat())
|
||||||
|
return available
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Float.toOffset(): Offset = Offset(0f, this)
|
||||||
|
|
||||||
|
@JvmName("velocityToFloat")
|
||||||
|
private fun Velocity.toFloat() = this.y
|
||||||
|
|
||||||
|
@JvmName("offsetToFloat")
|
||||||
|
private fun Offset.toFloat(): Float = this.y
|
||||||
|
}
|
||||||
|
|
|
@ -1,34 +1,16 @@
|
||||||
package tachiyomi.presentation.core.components.material
|
package tachiyomi.presentation.core.components.material
|
||||||
|
|
||||||
import androidx.compose.animation.core.animate
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshContainer
|
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshState
|
import androidx.compose.material3.pulltorefresh.pullToRefresh
|
||||||
|
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.saveable.Saver
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.unit.Velocity
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.pow
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param refreshing Whether the layout is currently refreshing
|
* @param refreshing Whether the layout is currently refreshing
|
||||||
|
@ -46,242 +28,26 @@ fun PullRefresh(
|
||||||
indicatorPadding: PaddingValues = PaddingValues(0.dp),
|
indicatorPadding: PaddingValues = PaddingValues(0.dp),
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val state = rememberPullToRefreshState(
|
val state = rememberPullToRefreshState()
|
||||||
isRefreshing = refreshing,
|
Box(
|
||||||
extraVerticalOffset = indicatorPadding.calculateTopPadding(),
|
modifier = modifier
|
||||||
enabled = enabled,
|
.pullToRefresh(
|
||||||
onRefresh = onRefresh,
|
isRefreshing = refreshing,
|
||||||
)
|
state = state,
|
||||||
|
enabled = enabled,
|
||||||
Box(modifier.nestedScroll(state.nestedScrollConnection)) {
|
onRefresh = onRefresh,
|
||||||
|
)
|
||||||
|
) {
|
||||||
content()
|
content()
|
||||||
|
|
||||||
val contentPadding = remember(indicatorPadding) {
|
PullToRefreshDefaults.Indicator(
|
||||||
object : PaddingValues {
|
|
||||||
override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp =
|
|
||||||
indicatorPadding.calculateLeftPadding(layoutDirection)
|
|
||||||
|
|
||||||
override fun calculateTopPadding(): Dp = 0.dp
|
|
||||||
|
|
||||||
override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp =
|
|
||||||
indicatorPadding.calculateRightPadding(layoutDirection)
|
|
||||||
|
|
||||||
override fun calculateBottomPadding(): Dp =
|
|
||||||
indicatorPadding.calculateBottomPadding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PullToRefreshContainer(
|
|
||||||
state = state,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
.padding(contentPadding),
|
.padding(indicatorPadding),
|
||||||
|
isRefreshing = refreshing,
|
||||||
|
state = state,
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun rememberPullToRefreshState(
|
|
||||||
isRefreshing: Boolean,
|
|
||||||
extraVerticalOffset: Dp,
|
|
||||||
positionalThreshold: Dp = 64.dp,
|
|
||||||
enabled: () -> Boolean = { true },
|
|
||||||
onRefresh: () -> Unit,
|
|
||||||
): PullToRefreshStateImpl {
|
|
||||||
val density = LocalDensity.current
|
|
||||||
val extraVerticalOffsetPx = with(density) { extraVerticalOffset.toPx() }
|
|
||||||
val positionalThresholdPx = with(density) { positionalThreshold.toPx() }
|
|
||||||
return rememberSaveable(
|
|
||||||
extraVerticalOffset,
|
|
||||||
positionalThresholdPx,
|
|
||||||
enabled,
|
|
||||||
onRefresh,
|
|
||||||
saver = PullToRefreshStateImpl.Saver(
|
|
||||||
extraVerticalOffset = extraVerticalOffsetPx,
|
|
||||||
positionalThreshold = positionalThresholdPx,
|
|
||||||
enabled = enabled,
|
|
||||||
onRefresh = onRefresh,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
PullToRefreshStateImpl(
|
|
||||||
initialRefreshing = isRefreshing,
|
|
||||||
extraVerticalOffset = extraVerticalOffsetPx,
|
|
||||||
positionalThreshold = positionalThresholdPx,
|
|
||||||
enabled = enabled,
|
|
||||||
onRefresh = onRefresh,
|
|
||||||
)
|
|
||||||
}.also {
|
|
||||||
LaunchedEffect(isRefreshing) {
|
|
||||||
if (isRefreshing && !it.isRefreshing) {
|
|
||||||
it.startRefreshAnimated()
|
|
||||||
} else if (!isRefreshing && it.isRefreshing) {
|
|
||||||
it.endRefreshAnimated()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a [PullToRefreshState].
|
|
||||||
*
|
|
||||||
* @param positionalThreshold The positional threshold, in pixels, in which a refresh is triggered
|
|
||||||
* @param extraVerticalOffset Extra vertical offset, in pixels, for the "refreshing" state
|
|
||||||
* @param initialRefreshing The initial refreshing value of [PullToRefreshState]
|
|
||||||
* @param enabled a callback used to determine whether scroll events are to be handled by this
|
|
||||||
* @param onRefresh a callback to run when pull-to-refresh action is triggered by user
|
|
||||||
* [PullToRefreshState]
|
|
||||||
*/
|
|
||||||
private class PullToRefreshStateImpl(
|
|
||||||
initialRefreshing: Boolean,
|
|
||||||
private val extraVerticalOffset: Float,
|
|
||||||
override val positionalThreshold: Float,
|
|
||||||
enabled: () -> Boolean,
|
|
||||||
private val onRefresh: () -> Unit,
|
|
||||||
) : PullToRefreshState {
|
|
||||||
|
|
||||||
override val progress get() = adjustedDistancePulled / positionalThreshold
|
|
||||||
override var verticalOffset by mutableFloatStateOf(if (initialRefreshing) refreshingVerticalOffset else 0f)
|
|
||||||
|
|
||||||
override var isRefreshing by mutableStateOf(initialRefreshing)
|
|
||||||
|
|
||||||
private val refreshingVerticalOffset: Float
|
|
||||||
get() = positionalThreshold + extraVerticalOffset
|
|
||||||
|
|
||||||
override fun startRefresh() {
|
|
||||||
isRefreshing = true
|
|
||||||
verticalOffset = refreshingVerticalOffset
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun startRefreshAnimated() {
|
|
||||||
isRefreshing = true
|
|
||||||
animateTo(refreshingVerticalOffset)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun endRefresh() {
|
|
||||||
verticalOffset = 0f
|
|
||||||
isRefreshing = false
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun endRefreshAnimated() {
|
|
||||||
animateTo(0f)
|
|
||||||
isRefreshing = false
|
|
||||||
}
|
|
||||||
|
|
||||||
override var nestedScrollConnection = object : NestedScrollConnection {
|
|
||||||
override fun onPreScroll(
|
|
||||||
available: Offset,
|
|
||||||
source: NestedScrollSource,
|
|
||||||
): Offset = when {
|
|
||||||
!enabled() -> Offset.Zero
|
|
||||||
// Swiping up
|
|
||||||
source == NestedScrollSource.Drag && available.y < 0 -> {
|
|
||||||
consumeAvailableOffset(available)
|
|
||||||
}
|
|
||||||
else -> Offset.Zero
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPostScroll(
|
|
||||||
consumed: Offset,
|
|
||||||
available: Offset,
|
|
||||||
source: NestedScrollSource,
|
|
||||||
): Offset = when {
|
|
||||||
!enabled() -> Offset.Zero
|
|
||||||
// Swiping down
|
|
||||||
source == NestedScrollSource.Drag && available.y > 0 -> {
|
|
||||||
consumeAvailableOffset(available)
|
|
||||||
}
|
|
||||||
else -> Offset.Zero
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
|
||||||
return Velocity(0f, onRelease(available.y))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Helper method for nested scroll connection */
|
|
||||||
fun consumeAvailableOffset(available: Offset): Offset {
|
|
||||||
val y = if (isRefreshing) {
|
|
||||||
0f
|
|
||||||
} else {
|
|
||||||
val newOffset = (distancePulled + available.y).coerceAtLeast(0f)
|
|
||||||
val dragConsumed = newOffset - distancePulled
|
|
||||||
distancePulled = newOffset
|
|
||||||
verticalOffset = calculateVerticalOffset() + (extraVerticalOffset * progress.coerceIn(0f, 1f))
|
|
||||||
dragConsumed
|
|
||||||
}
|
|
||||||
return Offset(0f, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Helper method for nested scroll connection. Calls onRefresh callback when triggered */
|
|
||||||
suspend fun onRelease(velocity: Float): Float {
|
|
||||||
if (isRefreshing) return 0f // Already refreshing, do nothing
|
|
||||||
// Trigger refresh
|
|
||||||
if (adjustedDistancePulled > positionalThreshold) {
|
|
||||||
onRefresh()
|
|
||||||
startRefreshAnimated()
|
|
||||||
} else {
|
|
||||||
animateTo(0f)
|
|
||||||
}
|
|
||||||
|
|
||||||
val consumed = when {
|
|
||||||
// We are flinging without having dragged the pull refresh (for example a fling inside
|
|
||||||
// a list) - don't consume
|
|
||||||
distancePulled == 0f -> 0f
|
|
||||||
// If the velocity is negative, the fling is upwards, and we don't want to prevent the
|
|
||||||
// the list from scrolling
|
|
||||||
velocity < 0f -> 0f
|
|
||||||
// We are showing the indicator, and the fling is downwards - consume everything
|
|
||||||
else -> velocity
|
|
||||||
}
|
|
||||||
distancePulled = 0f
|
|
||||||
return consumed
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun animateTo(offset: Float) {
|
|
||||||
animate(initialValue = verticalOffset, targetValue = offset) { value, _ ->
|
|
||||||
verticalOffset = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Provides custom vertical offset behavior for [PullToRefreshContainer] */
|
|
||||||
fun calculateVerticalOffset(): Float = when {
|
|
||||||
// If drag hasn't gone past the threshold, the position is the adjustedDistancePulled.
|
|
||||||
adjustedDistancePulled <= positionalThreshold -> adjustedDistancePulled
|
|
||||||
else -> {
|
|
||||||
// How far beyond the threshold pull has gone, as a percentage of the threshold.
|
|
||||||
val overshootPercent = abs(progress) - 1.0f
|
|
||||||
// Limit the overshoot to 200%. Linear between 0 and 200.
|
|
||||||
val linearTension = overshootPercent.coerceIn(0f, 2f)
|
|
||||||
// Non-linear tension. Increases with linearTension, but at a decreasing rate.
|
|
||||||
val tensionPercent = linearTension - linearTension.pow(2) / 4
|
|
||||||
// The additional offset beyond the threshold.
|
|
||||||
val extraOffset = positionalThreshold * tensionPercent
|
|
||||||
positionalThreshold + extraOffset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/** The default [Saver] for [PullToRefreshStateImpl]. */
|
|
||||||
fun Saver(
|
|
||||||
extraVerticalOffset: Float,
|
|
||||||
positionalThreshold: Float,
|
|
||||||
enabled: () -> Boolean,
|
|
||||||
onRefresh: () -> Unit,
|
|
||||||
) = Saver<PullToRefreshStateImpl, Boolean>(
|
|
||||||
save = { it.isRefreshing },
|
|
||||||
restore = { isRefreshing ->
|
|
||||||
PullToRefreshStateImpl(
|
|
||||||
initialRefreshing = isRefreshing,
|
|
||||||
extraVerticalOffset = extraVerticalOffset,
|
|
||||||
positionalThreshold = positionalThreshold,
|
|
||||||
enabled = enabled,
|
|
||||||
onRefresh = onRefresh,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var distancePulled by mutableFloatStateOf(0f)
|
|
||||||
private val adjustedDistancePulled: Float get() = distancePulled * 0.5f
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,63 +3,16 @@ package tachiyomi.presentation.core.util
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LazyListState.isScrolledToStart(): Boolean {
|
fun LazyListState.shouldExpandFAB(): Boolean {
|
||||||
return remember {
|
return remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
val firstItem = layoutInfo.visibleItemsInfo.firstOrNull()
|
(firstVisibleItemIndex == 0 && firstVisibleItemScrollOffset == 0) ||
|
||||||
firstItem == null || firstItem.offset == layoutInfo.viewportStartOffset
|
lastScrolledBackward ||
|
||||||
|
!canScrollForward
|
||||||
}
|
}
|
||||||
}.value
|
}
|
||||||
}
|
.value
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LazyListState.isScrolledToEnd(): Boolean {
|
|
||||||
return remember {
|
|
||||||
derivedStateOf {
|
|
||||||
val lastItem = layoutInfo.visibleItemsInfo.lastOrNull()
|
|
||||||
lastItem == null || lastItem.size + lastItem.offset <= layoutInfo.viewportEndOffset
|
|
||||||
}
|
|
||||||
}.value
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LazyListState.isScrollingUp(): Boolean {
|
|
||||||
var previousIndex by remember { mutableIntStateOf(firstVisibleItemIndex) }
|
|
||||||
var previousScrollOffset by remember { mutableIntStateOf(firstVisibleItemScrollOffset) }
|
|
||||||
return remember {
|
|
||||||
derivedStateOf {
|
|
||||||
if (previousIndex != firstVisibleItemIndex) {
|
|
||||||
previousIndex > firstVisibleItemIndex
|
|
||||||
} else {
|
|
||||||
previousScrollOffset >= firstVisibleItemScrollOffset
|
|
||||||
}.also {
|
|
||||||
previousIndex = firstVisibleItemIndex
|
|
||||||
previousScrollOffset = firstVisibleItemScrollOffset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.value
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LazyListState.isScrollingDown(): Boolean {
|
|
||||||
var previousIndex by remember { mutableIntStateOf(firstVisibleItemIndex) }
|
|
||||||
var previousScrollOffset by remember { mutableIntStateOf(firstVisibleItemScrollOffset) }
|
|
||||||
return remember {
|
|
||||||
derivedStateOf {
|
|
||||||
if (previousIndex != firstVisibleItemIndex) {
|
|
||||||
previousIndex < firstVisibleItemIndex
|
|
||||||
} else {
|
|
||||||
previousScrollOffset <= firstVisibleItemScrollOffset
|
|
||||||
}.also {
|
|
||||||
previousIndex = firstVisibleItemIndex
|
|
||||||
previousScrollOffset = firstVisibleItemScrollOffset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.value
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue