feat: Collapse ExtendedFAB on scroll (#1630)

This commit is contained in:
Ushie 2024-02-03 22:12:12 +03:00 committed by GitHub
parent 607d8b67c9
commit 39536c0e18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 79 additions and 29 deletions

View file

@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -69,6 +70,7 @@ import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_UNSUPPORTED import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_UNSUPPORTED
import app.revanced.manager.util.Options import app.revanced.manager.util.Options
import app.revanced.manager.util.PatchSelection import app.revanced.manager.util.PatchSelection
import app.revanced.manager.util.isScrollingUp
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.compose.rememberKoinInject import org.koin.compose.rememberKoinInject
@ -273,7 +275,7 @@ fun PatchesSelectorScreen(
} }
} }
val patchLazyListState = rememberLazyListState()
Scaffold( Scaffold(
topBar = { topBar = {
AppTopBar( AppTopBar(
@ -302,6 +304,7 @@ fun PatchesSelectorScreen(
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.save)) }, text = { Text(stringResource(R.string.save)) },
icon = { Icon(Icons.Outlined.Save, null) }, icon = { Icon(Icons.Outlined.Save, null) },
expanded = patchLazyListState.isScrollingUp,
onClick = { onClick = {
// TODO: only allow this if all required options have been set. // TODO: only allow this if all required options have been set.
onSave(vm.getCustomSelection(), vm.getOptions()) onSave(vm.getCustomSelection(), vm.getOptions())
@ -344,7 +347,8 @@ fun PatchesSelectorScreen(
val bundle = bundles[index] val bundle = bundles[index]
LazyColumnWithScrollbar( LazyColumnWithScrollbar(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize(),
state = patchLazyListState
) { ) {
patchList( patchList(
uid = bundle.uid, uid = bundle.uid,

View file

@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@ -32,11 +34,12 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.data.room.apps.installed.InstallType import app.revanced.manager.data.room.apps.installed.InstallType
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.GroupHeader import app.revanced.manager.ui.component.GroupHeader
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
import app.revanced.manager.ui.component.LoadingIndicator import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.viewmodel.VersionSelectorViewModel import app.revanced.manager.ui.viewmodel.VersionSelectorViewModel
import app.revanced.manager.util.isScrollingUp
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -63,6 +66,7 @@ fun VersionSelectorScreen(
var selectedVersion: SelectedApp? by rememberSaveable { mutableStateOf(null) } var selectedVersion: SelectedApp? by rememberSaveable { mutableStateOf(null) }
val lazyListState = rememberLazyListState()
Scaffold( Scaffold(
topBar = { topBar = {
AppTopBar( AppTopBar(
@ -74,38 +78,47 @@ fun VersionSelectorScreen(
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.select_version)) }, text = { Text(stringResource(R.string.select_version)) },
icon = { Icon(Icons.Default.Check, null) }, icon = { Icon(Icons.Default.Check, null) },
expanded = lazyListState.isScrollingUp,
onClick = { selectedVersion?.let(onAppClick) } onClick = { selectedVersion?.let(onAppClick) }
) )
} }
) { paddingValues -> ) { paddingValues ->
ColumnWithScrollbar( LazyColumnWithScrollbar(
modifier = Modifier modifier = Modifier
.padding(paddingValues) .padding(paddingValues)
.fillMaxSize(), .fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally,
state = lazyListState
) { ) {
viewModel.installedApp?.let { (packageInfo, installedApp) -> viewModel.installedApp?.let { (packageInfo, installedApp) ->
SelectedApp.Installed( SelectedApp.Installed(
packageName = viewModel.packageName, packageName = viewModel.packageName,
version = packageInfo.versionName version = packageInfo.versionName
).let { ).let {
SelectedAppItem( item {
selectedApp = it, SelectedAppItem(
selected = selectedVersion == it, selectedApp = it,
onClick = { selectedVersion = it }, selected = selectedVersion == it,
patchCount = supportedVersions[it.version], onClick = { selectedVersion = it },
enabled = patchCount = supportedVersions[it.version],
enabled =
!(installedApp?.installType == InstallType.ROOT && !viewModel.rootInstaller.hasRootAccess()), !(installedApp?.installType == InstallType.ROOT && !viewModel.rootInstaller.hasRootAccess()),
alreadyPatched = installedApp != null && installedApp.installType != InstallType.ROOT alreadyPatched = installedApp != null && installedApp.installType != InstallType.ROOT
) )
}
} }
} }
Row(Modifier.fillMaxWidth()) { item {
GroupHeader(stringResource(R.string.downloadable_versions)) Row(Modifier.fillMaxWidth()) {
GroupHeader(stringResource(R.string.downloadable_versions))
}
} }
list.forEach { items(
items = list,
key = { it.packageName }
) {
SelectedAppItem( SelectedAppItem(
selectedApp = it, selectedApp = it,
selected = selectedVersion == it, selected = selectedVersion == it,
@ -115,19 +128,23 @@ fun VersionSelectorScreen(
} }
if (viewModel.errorMessage != null) { if (viewModel.errorMessage != null) {
Column( item {
modifier = Modifier.fillMaxWidth(), Column(
horizontalAlignment = Alignment.CenterHorizontally modifier = Modifier.fillMaxWidth(),
) { horizontalAlignment = Alignment.CenterHorizontally
Text(stringResource(R.string.error_occurred)) ) {
Text( Text(stringResource(R.string.error_occurred))
text = viewModel.errorMessage!!, Text(
modifier = Modifier.padding(horizontal = 15.dp) text = viewModel.errorMessage!!,
) modifier = Modifier.padding(horizontal = 15.dp)
)
}
} }
} else if (viewModel.isLoading) } else if (viewModel.isLoading) {
LoadingIndicator() item {
LoadingIndicator()
}
}
} }
} }
} }

View file

@ -11,6 +11,14 @@ import android.os.Build
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -165,4 +173,25 @@ fun String.relativeTime(context: Context): String {
} catch (e: DateTimeParseException) { } catch (e: DateTimeParseException) {
return context.getString(R.string.invalid_date) return context.getString(R.string.invalid_date)
} }
} }
@Composable
fun LazyListState.isScrollingUp(): State<Boolean> {
return remember(this) {
var previousIndex by mutableIntStateOf(firstVisibleItemIndex)
var previousScrollOffset by mutableIntStateOf(firstVisibleItemScrollOffset)
derivedStateOf {
if (previousIndex != firstVisibleItemIndex) {
previousIndex > firstVisibleItemIndex
} else {
previousScrollOffset >= firstVisibleItemScrollOffset
}.also {
previousIndex = firstVisibleItemIndex
previousScrollOffset = firstVisibleItemScrollOffset
}
}
}
}
val LazyListState.isScrollingUp: Boolean @Composable get() = this.isScrollingUp().value