feat: scaffold and scrolling improvements (#21)

* feat: scaffold and scrolling improvements

* fix: apply scaffoldPadding before verticalScroll
This commit is contained in:
aliernfrog 2022-12-04 11:18:24 +03:00 committed by GitHub
parent 58c016bfb9
commit d70e750c36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 191 additions and 151 deletions

View File

@ -0,0 +1,109 @@
package app.revanced.manager.ui.component
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import app.revanced.manager.ui.navigation.AppDestination
import app.revanced.manager.ui.navigation.DashboardDestination
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppScaffold(
topBar: @Composable (TopAppBarScrollBehavior) -> Unit = {},
bottomBar: @Composable () -> Unit = {},
floatingActionButton: @Composable () -> Unit = {},
content: @Composable (PaddingValues) -> Unit
) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar(scrollBehavior) },
bottomBar = bottomBar,
floatingActionButton = floatingActionButton,
content = content
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppLargeTopBar(
topBarTitle: String,
scrollBehavior: TopAppBarScrollBehavior,
actions: @Composable (RowScope.() -> Unit) = {},
onBackClick: (() -> Unit)? = null
) {
LargeTopAppBar(
title = { Text(topBarTitle) },
scrollBehavior = scrollBehavior,
navigationIcon = {
if (onBackClick != null) {
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = null
)
}
}
},
actions = actions
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppMediumTopBar(
topBarTitle: String,
scrollBehavior: TopAppBarScrollBehavior,
actions: @Composable (RowScope.() -> Unit) = {},
onBackClick: (() -> Unit)? = null
) {
MediumTopAppBar(
title = { Text(topBarTitle) },
scrollBehavior = scrollBehavior,
navigationIcon = {
if (onBackClick != null) {
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = null
)
}
}
},
actions = actions
)
}
@Composable
fun AppBottomNavBar(
navItems: List<DashboardDestination>,
currentDestination: DashboardDestination,
onNavChanged: (AppDestination) -> Unit
) {
NavigationBar {
navItems.forEach { destination ->
NavigationBarItem(
selected = currentDestination == destination,
icon = {
Icon(
if (currentDestination == destination) destination.icon else destination.outlinedIcon,
stringResource(destination.label)
)
},
label = { Text(stringResource(destination.label)) },
onClick = {
if (destination != currentDestination) {
onNavChanged(destination)
}
}
)
}
}
}

View File

@ -32,8 +32,8 @@ fun DashboardScreen(viewModel: DashboardViewModel = getViewModel()) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(horizontal = 18.dp) .verticalScroll(state = rememberScrollState())
.verticalScroll(state = rememberScrollState()), .padding(horizontal = 18.dp),
horizontalAlignment = Alignment.Start, horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(18.dp) verticalArrangement = Arrangement.spacedBy(18.dp)
) { ) {

View File

@ -3,11 +3,13 @@ package app.revanced.manager.ui.screen
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.* import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import app.revanced.manager.ui.component.AppBottomNavBar
import app.revanced.manager.ui.component.AppLargeTopBar
import app.revanced.manager.ui.component.AppScaffold
import app.revanced.manager.ui.navigation.AppDestination import app.revanced.manager.ui.navigation.AppDestination
import app.revanced.manager.ui.navigation.DashboardDestination import app.revanced.manager.ui.navigation.DashboardDestination
@ -19,53 +21,22 @@ fun MainDashboardScreen(
onNavChanged: (AppDestination) -> Unit, onNavChanged: (AppDestination) -> Unit,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( AppScaffold(
state = rememberTopAppBarState(), topBar = { scrollBehavior ->
canScroll = { true } AppLargeTopBar(
) topBarTitle = stringResource(currentDestination.label),
Scaffold(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
title = {
Text(
text = stringResource(currentDestination.label),
style = MaterialTheme.typography.headlineLarge
)
},
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
}, },
bottomBar = { bottomBar = {
NavigationBar { AppBottomNavBar(
bottomNavItems.forEach { destination -> navItems = bottomNavItems,
NavigationBarItem( currentDestination = currentDestination,
selected = currentDestination == destination, onNavChanged = onNavChanged
icon = { )
Icon(
if (currentDestination == destination) destination.icon else destination.outlinedIcon,
stringResource(destination.label)
)
},
label = { Text(stringResource(destination.label)) },
onClick = {
if (destination != currentDestination) {
onNavChanged(destination)
}
}
)
}
}
} }
) { paddingValues -> ) { paddingValues ->
Box( Box(Modifier.padding(paddingValues).fillMaxSize()) {
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
) {
content() content()
} }
} }

View File

@ -1,6 +1,8 @@
package app.revanced.manager.ui.screen package app.revanced.manager.ui.screen
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Build import androidx.compose.material.icons.filled.Build
import androidx.compose.material3.* import androidx.compose.material3.*
@ -44,6 +46,7 @@ fun PatcherScreen(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(paddingValues) .padding(paddingValues)
.verticalScroll(rememberScrollState())
.padding(16.dp), .padding(16.dp),
) { ) {
ElevatedCard( ElevatedCard(

View File

@ -46,8 +46,8 @@ fun SettingsScreen(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(top = 48.dp, start = 18.dp, end = 18.dp) .verticalScroll(rememberScrollState())
.verticalScroll(state = rememberScrollState()), .padding(top = 48.dp, start = 18.dp, end = 18.dp),
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
if (vm.showThemePicker) { if (vm.showThemePicker) {

View File

@ -4,10 +4,10 @@ import android.annotation.SuppressLint
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.SdStorage import androidx.compose.material.icons.filled.SdStorage
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -15,6 +15,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.AppIcon import app.revanced.manager.ui.component.AppIcon
import app.revanced.manager.ui.component.AppMediumTopBar
import app.revanced.manager.ui.component.AppScaffold
import app.revanced.manager.ui.component.LoadingIndicator import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.viewmodel.AppSelectorViewModel import app.revanced.manager.ui.viewmodel.AppSelectorViewModel
import org.koin.androidx.compose.getViewModel import org.koin.androidx.compose.getViewModel
@ -26,7 +28,6 @@ fun AppSelectorSubscreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
vm: AppSelectorViewModel = getViewModel(), vm: AppSelectorViewModel = getViewModel(),
) { ) {
val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
it?.let { uri -> it?.let { uri ->
vm.setSelectedAppPackageFromFile(uri) vm.setSelectedAppPackageFromFile(uri)
@ -34,14 +35,13 @@ fun AppSelectorSubscreen(
} }
} }
Scaffold( AppScaffold(
topBar = { topBar = { scrollBehavior ->
MediumTopAppBar(title = { Text(stringResource(R.string.app_selector_title)) }, AppMediumTopBar(
navigationIcon = { topBarTitle = stringResource(R.string.app_selector_title),
IconButton(onClick = onBackClick) { scrollBehavior = scrollBehavior,
Icon(Icons.Default.ArrowBack, contentDescription = null) onBackClick = onBackClick
} )
})
}, },
floatingActionButton = { floatingActionButton = {
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
@ -51,10 +51,10 @@ fun AppSelectorSubscreen(
icon = { Icon(Icons.Default.SdStorage, contentDescription = null) }, icon = { Icon(Icons.Default.SdStorage, contentDescription = null) },
text = { Text(stringResource(R.string.storage)) }, text = { Text(stringResource(R.string.storage)) },
) )
}, }
) { paddingValues -> ) { paddingValues ->
if (vm.filteredApps.isNotEmpty()) { if (vm.filteredApps.isNotEmpty()) {
LazyColumn(modifier = Modifier.padding(paddingValues)) { LazyColumn(modifier = Modifier.fillMaxSize().padding(paddingValues)) {
items(count = vm.filteredApps.size) { int -> items(count = vm.filteredApps.size) { int ->
val app = vm.filteredApps[int] val app = vm.filteredApps[int]
val label = vm.applicationLabel(app) val label = vm.applicationLabel(app)

View File

@ -4,17 +4,16 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.AppLargeTopBar
import app.revanced.manager.ui.component.AppScaffold
import app.revanced.manager.ui.component.ContributorsCard import app.revanced.manager.ui.component.ContributorsCard
import app.revanced.manager.ui.viewmodel.ContributorsViewModel import app.revanced.manager.ui.viewmodel.ContributorsViewModel
import app.revanced.manager.util.ghOrganization import app.revanced.manager.util.ghOrganization
@ -27,31 +26,14 @@ fun ContributorsSubscreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
vm: ContributorsViewModel = getViewModel() vm: ContributorsViewModel = getViewModel()
) { ) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
state = rememberTopAppBarState(),
canScroll = { true }
)
val ctx = LocalContext.current.applicationContext val ctx = LocalContext.current.applicationContext
Scaffold( AppScaffold(
modifier = Modifier topBar = { scrollBehavior ->
.nestedScroll(scrollBehavior.nestedScrollConnection), AppLargeTopBar(
topBar = { topBarTitle = stringResource(R.string.screen_contributors_title),
LargeTopAppBar( scrollBehavior = scrollBehavior,
title = { onBackClick = onBackClick
Text( )
text = stringResource(R.string.screen_contributors_title)
)
},
navigationIcon = {
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = null
)
}
},
scrollBehavior = scrollBehavior
)
}, },
floatingActionButton = { floatingActionButton = {
FloatingActionButton(onClick = { ctx.openUrl(ghOrganization) }) { FloatingActionButton(onClick = { ctx.openUrl(ghOrganization) }) {
@ -62,8 +44,8 @@ fun ContributorsSubscreen(
Column( Column(
Modifier Modifier
.padding(paddingValues) .padding(paddingValues)
.padding(bottom = 8.dp)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(bottom = 8.dp)
) { ) {
ContributorsCard( ContributorsCard(
stringResource(R.string.cli_contributors), stringResource(R.string.cli_contributors),

View File

@ -1,14 +1,13 @@
package app.revanced.manager.ui.screen.subscreens package app.revanced.manager.ui.screen.subscreens
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.AppLargeTopBar
import app.revanced.manager.ui.component.AppScaffold
import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
@ -17,25 +16,12 @@ import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
fun LicensesSubscreen( fun LicensesSubscreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
) { ) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( AppScaffold(
state = rememberTopAppBarState(), topBar = { scrollBehavior ->
canScroll = { true } AppLargeTopBar(
) topBarTitle = stringResource(R.string.opensource_licenses),
Scaffold( scrollBehavior = scrollBehavior,
modifier = Modifier onBackClick = onBackClick
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
title = { Text(stringResource(R.string.opensource_licenses)) },
navigationIcon = {
IconButton(onClick = onBackClick) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = null
)
}
},
scrollBehavior = scrollBehavior
) )
} }
) { paddingValues -> ) { paddingValues ->

View File

@ -13,6 +13,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.AppMediumTopBar
import app.revanced.manager.ui.component.AppScaffold
import app.revanced.manager.ui.component.LoadingIndicator import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.component.PatchCard import app.revanced.manager.ui.component.PatchCard
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
@ -36,28 +38,24 @@ fun PatchesSelectorSubscreen(
vm.filterPatches() vm.filterPatches()
} }
} }
Scaffold(topBar = { AppScaffold(
MediumTopAppBar(title = { topBar = { scrollBehavior ->
Text( AppMediumTopBar(
text = stringResource(R.string.card_patches_header), topBarTitle = stringResource(id = R.string.card_patches_header),
style = MaterialTheme.typography.headlineLarge scrollBehavior = scrollBehavior,
) actions = {
}, navigationIcon = { IconButton(onClick = {
IconButton(onClick = onBackClick) { vm.selectAllPatches(patches, vm.selectedPatches.isEmpty())
Icon( }) {
imageVector = Icons.Default.ArrowBack, contentDescription = null if (vm.selectedPatches.isEmpty()) Icon(
) Icons.Default.SelectAll, contentDescription = null
} ) else Icon(Icons.Default.Deselect, contentDescription = null)
}, actions = { }
IconButton(onClick = { },
vm.selectAllPatches(patches, vm.selectedPatches.isEmpty()) onBackClick = onBackClick
}) { )
if (vm.selectedPatches.isEmpty()) Icon( }
Icons.Default.SelectAll, contentDescription = null ) { paddingValues ->
) else Icon(Icons.Default.Deselect, contentDescription = null)
}
})
}) { paddingValues ->
Column( Column(
modifier = Modifier.padding(paddingValues) modifier = Modifier.padding(paddingValues)
) { ) {

View File

@ -5,21 +5,22 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.AppLargeTopBar
import app.revanced.manager.ui.component.AppScaffold
import app.revanced.manager.ui.component.SourceItem import app.revanced.manager.ui.component.SourceItem
import app.revanced.manager.ui.viewmodel.SourceSelectorViewModel import app.revanced.manager.ui.viewmodel.SourceSelectorViewModel
import org.koin.androidx.compose.getViewModel import org.koin.androidx.compose.getViewModel
@ -30,10 +31,6 @@ fun SourceSelectorSubscreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
viewModel: SourceSelectorViewModel = getViewModel() viewModel: SourceSelectorViewModel = getViewModel()
) { ) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
state = rememberTopAppBarState(),
canScroll = { true }
)
val context = LocalContext.current val context = LocalContext.current
val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
@ -44,19 +41,12 @@ fun SourceSelectorSubscreen(
} }
Toast.makeText(context, "Couldn't load local patch bundle.", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Couldn't load local patch bundle.", Toast.LENGTH_SHORT).show()
} }
AppScaffold(
Scaffold( topBar = { scrollBehavior ->
modifier = Modifier AppLargeTopBar(
.nestedScroll(scrollBehavior.nestedScrollConnection), topBarTitle = stringResource(R.string.select_sources),
topBar = { scrollBehavior = scrollBehavior,
LargeTopAppBar( onBackClick = onBackClick
title = { Text(stringResource(R.string.select_sources)) },
navigationIcon = {
IconButton(onClick = onBackClick) {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
}
},
scrollBehavior = scrollBehavior
) )
}, },
floatingActionButton = { floatingActionButton = {
@ -67,9 +57,10 @@ fun SourceSelectorSubscreen(
) { paddingValues -> ) { paddingValues ->
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize()
.padding(paddingValues) .padding(paddingValues)
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(horizontal = 16.dp)
) { ) {
ListItem( ListItem(
modifier = Modifier modifier = Modifier