fix: screens not scrollable in landscape

Fixes not being able to scroll when in landscape mode if content is off screen.

Fixes FAB being in the way of controlling tunnels by making it disappear on scroll down.

Bump targetSdk to 34
This commit is contained in:
Zane Schepke 2023-07-19 09:02:31 -04:00
parent 509d22a98c
commit 7ca5de1836
6 changed files with 192 additions and 115 deletions

View File

@ -5,25 +5,25 @@ plugins {
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
kotlin("kapt") kotlin("kapt")
id("com.google.dagger.hilt.android") id("com.google.dagger.hilt.android")
id("io.objectbox")
id("com.google.gms.google-services") id("com.google.gms.google-services")
id("com.google.firebase.crashlytics") id("com.google.firebase.crashlytics")
id("org.jetbrains.kotlin.plugin.serialization") id("org.jetbrains.kotlin.plugin.serialization")
id("io.objectbox")
} }
android { android {
namespace = "com.zaneschepke.wireguardautotunnel" namespace = "com.zaneschepke.wireguardautotunnel"
compileSdk = 33 compileSdk = 34
val versionMajor = 2 val versionMajor = 2
val versionMinor = 1 val versionMinor = 1
val versionPatch = 2 val versionPatch = 3
val versionBuild = 0 val versionBuild = 0
defaultConfig { defaultConfig {
applicationId = "com.zaneschepke.wireguardautotunnel" applicationId = "com.zaneschepke.wireguardautotunnel"
minSdk = 29 minSdk = 29
targetSdk = 33 targetSdk = 34
versionCode = versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild versionCode = versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
versionName = "${versionMajor}.${versionMinor}.${versionPatch}" versionName = "${versionMajor}.${versionMinor}.${versionPatch}"

View File

@ -3,10 +3,8 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.config
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
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
@ -72,13 +70,14 @@ fun ConfigScreen(
} }
if(tunnel != null) { if(tunnel != null) {
Column( LazyColumn(
horizontalAlignment = Alignment.Start, horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.Top,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(padding) .padding(padding)
) { ) {
item {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -106,6 +105,8 @@ fun ConfigScreen(
), ),
) )
} }
}
item {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -121,14 +122,20 @@ fun ConfigScreen(
} }
) )
} }
}
if (!allApplications) { if (!allApplications) {
Row(modifier = Modifier item {
Row(
modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 7.dp), .padding(horizontal = 20.dp, vertical = 7.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween) { horizontalArrangement = Arrangement.SpaceBetween
Row(verticalAlignment = Alignment.CenterVertically, ) {
horizontalArrangement = Arrangement.SpaceBetween){ Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(stringResource(id = R.string.include)) Text(stringResource(id = R.string.include))
Checkbox( Checkbox(
checked = include, checked = include,
@ -137,8 +144,10 @@ fun ConfigScreen(
} }
) )
} }
Row(verticalAlignment = Alignment.CenterVertically, Row(
horizontalArrangement = Arrangement.SpaceBetween){ verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(stringResource(id = R.string.exclude)) Text(stringResource(id = R.string.exclude))
Checkbox( Checkbox(
checked = !include, checked = !include,
@ -148,38 +157,57 @@ fun ConfigScreen(
) )
} }
} }
LazyColumn(modifier = Modifier }
.fillMaxWidth() // LazyColumn(
.fillMaxHeight(.75f) // modifier = Modifier
.padding(horizontal = 14.dp, vertical = 7.dp), // .fillMaxWidth()
verticalArrangement = Arrangement.Center, // .fillMaxHeight(.75f)
horizontalAlignment = Alignment.Start) { // .padding(horizontal = 14.dp, vertical = 7.dp),
// verticalArrangement = Arrangement.Center,
// horizontalAlignment = Alignment.Start
// ) {
items(packages) { pack -> items(packages) { pack ->
Row(verticalAlignment = Alignment.CenterVertically, Row(
horizontalArrangement = Arrangement.SpaceBetween) { verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row( Row(
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(5.dp) modifier = Modifier.padding(5.dp)
) { ) {
val drawable = pack.applicationInfo?.loadIcon(context.packageManager) val drawable =
pack.applicationInfo?.loadIcon(context.packageManager)
if (drawable != null) { if (drawable != null) {
Image(painter = DrawablePainter(drawable), stringResource(id = R.string.icon), modifier = Modifier.size(50.dp, 50.dp)) Image(
painter = DrawablePainter(drawable),
stringResource(id = R.string.icon),
modifier = Modifier.size(50.dp, 50.dp)
)
} else { } else {
Icon(Icons.Rounded.Android, stringResource(id = R.string.edit), modifier = Modifier.size(50.dp, 50.dp)) Icon(
Icons.Rounded.Android,
stringResource(id = R.string.edit),
modifier = Modifier.size(50.dp, 50.dp)
)
} }
Text(pack.applicationInfo.loadLabel(context.packageManager).toString(), modifier = Modifier.padding(5.dp)) Text(
pack.applicationInfo.loadLabel(context.packageManager)
.toString(), modifier = Modifier.padding(5.dp)
)
} }
Checkbox( Checkbox(
checked = (checkedPackages.contains(pack.packageName)), checked = (checkedPackages.contains(pack.packageName)),
onCheckedChange = { onCheckedChange = {
if(it) viewModel.onAddCheckedPackage(pack.packageName) else viewModel.onRemoveCheckedPackage(pack.packageName) if (it) viewModel.onAddCheckedPackage(pack.packageName) else viewModel.onRemoveCheckedPackage(
pack.packageName
)
} }
) )
} }
} }
} }
} item {
Row( Row(
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@ -188,7 +216,11 @@ fun ConfigScreen(
Button(onClick = { Button(onClick = {
scope.launch { scope.launch {
viewModel.onSaveAllChanges() viewModel.onSaveAllChanges()
Toast.makeText(context, context.resources.getString(R.string.config_changes_saved), Toast.LENGTH_LONG).show() Toast.makeText(
context,
context.resources.getString(R.string.config_changes_saved),
Toast.LENGTH_LONG
).show()
navController.navigate(Routes.Main.name) navController.navigate(Routes.Main.name)
} }
}, Modifier.padding(25.dp)) { }, Modifier.padding(25.dp)) {
@ -198,3 +230,4 @@ fun ConfigScreen(
} }
} }
} }
}

View File

@ -9,6 +9,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.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -43,6 +45,7 @@ fun DetailScreen(
val tunnelName by viewModel.tunnelName.collectAsStateWithLifecycle() val tunnelName by viewModel.tunnelName.collectAsStateWithLifecycle()
val lastHandshake by viewModel.lastHandshake.collectAsStateWithLifecycle(emptyMap()) val lastHandshake by viewModel.lastHandshake.collectAsStateWithLifecycle(emptyMap())
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.getTunnelById(id) viewModel.getTunnelById(id)
} }
@ -58,6 +61,7 @@ fun DetailScreen(
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.Top,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(padding) .padding(padding)
) { ) {
Row( Row(

View File

@ -3,6 +3,9 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.main
import android.annotation.SuppressLint 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.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -43,11 +46,16 @@ import androidx.compose.runtime.getValue
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.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue 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.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
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.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
@ -65,7 +73,6 @@ import com.zaneschepke.wireguardautotunnel.ui.Routes
import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem
import com.zaneschepke.wireguardautotunnel.ui.theme.brickRed import com.zaneschepke.wireguardautotunnel.ui.theme.brickRed
import com.zaneschepke.wireguardautotunnel.ui.theme.mint import com.zaneschepke.wireguardautotunnel.ui.theme.mint
import com.zaneschepke.wireguardautotunnel.ui.theme.pinkRed
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@ -78,6 +85,7 @@ fun MainScreen(
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val context = LocalContext.current val context = LocalContext.current
val isVisible = rememberSaveable { mutableStateOf(true) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState() val sheetState = rememberModalBottomSheetState()
@ -89,6 +97,23 @@ fun MainScreen(
val state by viewModel.state.collectAsStateWithLifecycle(Tunnel.State.DOWN) val state by viewModel.state.collectAsStateWithLifecycle(Tunnel.State.DOWN)
val tunnelName by viewModel.tunnelName.collectAsStateWithLifecycle("") val tunnelName by viewModel.tunnelName.collectAsStateWithLifecycle("")
// Nested scroll for control FAB
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
// Hide FAB
if (available.y < -1) {
isVisible.value = false
}
// Show FAB
if (available.y > 1) {
isVisible.value = true
}
return Offset.Zero
}
}
}
LaunchedEffect(viewState.value) { LaunchedEffect(viewState.value) {
if (viewState.value.showSnackbarMessage) { if (viewState.value.showSnackbarMessage) {
val result = snackbarHostState.showSnackbar( val result = snackbarHostState.showSnackbar(
@ -119,6 +144,11 @@ fun MainScreen(
}, },
floatingActionButtonPosition = FabPosition.End, floatingActionButtonPosition = FabPosition.End,
floatingActionButton = { floatingActionButton = {
AnimatedVisibility(
visible = isVisible.value,
enter = slideInVertically(initialOffsetY = { it * 2 }),
exit = slideOutVertically(targetOffsetY = { it * 2 }),
) {
FloatingActionButton( FloatingActionButton(
modifier = Modifier.padding(bottom = 90.dp), modifier = Modifier.padding(bottom = 90.dp),
onClick = { onClick = {
@ -134,6 +164,7 @@ fun MainScreen(
) )
} }
} }
}
) { ) {
if (tunnels.isEmpty()) { if (tunnels.isEmpty()) {
Column( Column(
@ -204,7 +235,8 @@ fun MainScreen(
.padding(padding) .padding(padding)
) { ) {
LazyColumn(modifier = Modifier.fillMaxSize()) { LazyColumn(modifier = Modifier.fillMaxSize()
.nestedScroll(nestedScrollConnection),) {
items(tunnels.toList()) { tunnel -> items(tunnels.toList()) { tunnel ->
RowListItem(leadingIcon = Icons.Rounded.Circle, RowListItem(leadingIcon = Icons.Rounded.Circle,
leadingIconColor = when (handshakeStatus) { leadingIconColor = when (handshakeStatus) {

View File

@ -16,8 +16,10 @@ 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.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Add
@ -91,6 +93,7 @@ fun SettingsScreen(
val backgroundLocationState = val backgroundLocationState =
rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION) rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
var currentText by remember { mutableStateOf("") } var currentText by remember { mutableStateOf("") }
val scrollState = rememberScrollState()
LaunchedEffect(viewState) { LaunchedEffect(viewState) {
if (viewState.showSnackbarMessage) { if (viewState.showSnackbarMessage) {
@ -120,6 +123,7 @@ fun SettingsScreen(
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.Top,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(scrollState)
.padding(padding)) { .padding(padding)) {
Icon(Icons.Rounded.LocationOff, contentDescription = stringResource(id = R.string.map), modifier = Modifier Icon(Icons.Rounded.LocationOff, contentDescription = stringResource(id = R.string.map), modifier = Modifier
.padding(30.dp) .padding(30.dp)
@ -178,6 +182,7 @@ fun SettingsScreen(
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.Top,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(scrollState)
.clickable(indication = null, interactionSource = interactionSource) { .clickable(indication = null, interactionSource = interactionSource) {
focusManager.clearFocus() focusManager.clearFocus()
} }

View File

@ -11,6 +11,8 @@ import androidx.compose.foundation.layout.Spacer
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.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -43,6 +45,7 @@ fun SupportScreen(padding : PaddingValues) {
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.Top,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(padding)) { .padding(padding)) {
Text(stringResource(R.string.support_text), textAlign = TextAlign.Center, modifier = Modifier.padding(30.dp), fontSize = 15.sp) Text(stringResource(R.string.support_text), textAlign = TextAlign.Center, modifier = Modifier.padding(30.dp), fontSize = 15.sp)
Row( Row(