mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor(UI): Use animateFloatingActionButton for FAB animations (#2844)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
b2a8d7a934
commit
1c9e14e87c
3 changed files with 87 additions and 83 deletions
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package com.geeksville.mesh.ui.contact
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -37,6 +36,7 @@ import androidx.compose.material.icons.rounded.QrCode2
|
|||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
|
|
@ -44,6 +44,7 @@ import androidx.compose.material3.RadioButton
|
|||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.animateFloatingActionButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -67,6 +68,7 @@ import com.geeksville.mesh.model.Contact
|
|||
import com.geeksville.mesh.model.UIViewModel
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun ContactsScreen(
|
||||
|
|
@ -153,13 +155,11 @@ fun ContactsScreen(
|
|||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
AnimatedVisibility(isConnected && !isSelectionModeActive) {
|
||||
FloatingActionButton(onClick = onNavigateToShare) {
|
||||
Icon(
|
||||
Icons.Rounded.QrCode2,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
FloatingActionButton(
|
||||
modifier = Modifier.animateFloatingActionButton(visible = isConnected, alignment = Alignment.BottomEnd),
|
||||
onClick = onNavigateToShare,
|
||||
) {
|
||||
Icon(Icons.Rounded.QrCode2, contentDescription = null)
|
||||
}
|
||||
},
|
||||
) { paddingValues ->
|
||||
|
|
@ -227,11 +227,8 @@ fun MuteNotificationsDialog(
|
|||
val text = stringResource(stringRes)
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.selectable(
|
||||
selected = isSelected,
|
||||
onClick = { selectedOptionIndex = index })
|
||||
Modifier.fillMaxWidth()
|
||||
.selectable(selected = isSelected, onClick = { selectedOptionIndex = index })
|
||||
.padding(vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package com.geeksville.mesh.ui.node
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -28,7 +27,10 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.animateFloatingActionButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -54,7 +56,7 @@ import com.geeksville.mesh.ui.sharing.AddContactFAB
|
|||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
import com.geeksville.mesh.ui.sharing.supportsQrCodeSharing
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun NodeScreen(
|
||||
|
|
@ -80,77 +82,82 @@ fun NodeScreen(
|
|||
}
|
||||
|
||||
val isScrollInProgress by remember { derivedStateOf { listState.isScrollInProgress } }
|
||||
Scaffold(
|
||||
floatingActionButton = {
|
||||
val firmwareVersion = DeviceVersion(ourNode?.metadata?.firmwareVersion ?: "0.0.0")
|
||||
val shareCapable = firmwareVersion.supportsQrCodeSharing()
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
LazyColumn(state = listState, modifier = Modifier.fillMaxSize()) {
|
||||
stickyHeader {
|
||||
val animatedAlpha by
|
||||
animateFloatAsState(targetValue = if (!isScrollInProgress) 1.0f else 0f, label = "alpha")
|
||||
NodeFilterTextField(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.graphicsLayer(alpha = animatedAlpha)
|
||||
.background(MaterialTheme.colorScheme.surfaceDim)
|
||||
.padding(8.dp),
|
||||
filterText = state.filter,
|
||||
onTextChange = model::setNodeFilterText,
|
||||
currentSortOption = state.sort,
|
||||
onSortSelect = model::setSortOption,
|
||||
includeUnknown = state.includeUnknown,
|
||||
onToggleIncludeUnknown = model::toggleIncludeUnknown,
|
||||
onlyOnline = state.onlyOnline,
|
||||
onToggleOnlyOnline = model::toggleOnlyOnline,
|
||||
onlyDirect = state.onlyDirect,
|
||||
onToggleOnlyDirect = model::toggleOnlyDirect,
|
||||
showDetails = state.showDetails,
|
||||
onToggleShowDetails = model::toggleShowDetails,
|
||||
showIgnored = state.showIgnored,
|
||||
onToggleShowIgnored = model::toggleShowIgnored,
|
||||
ignoredNodeCount = ignoredNodeCount,
|
||||
)
|
||||
}
|
||||
AddContactFAB(
|
||||
modifier =
|
||||
Modifier.animateFloatingActionButton(
|
||||
visible = !isScrollInProgress && connectionState == ConnectionState.CONNECTED && shareCapable,
|
||||
alignment = Alignment.BottomEnd,
|
||||
),
|
||||
model = model,
|
||||
onSharedContactImport = { contact -> model.addSharedContact(contact) },
|
||||
)
|
||||
},
|
||||
) { contentPadding ->
|
||||
Box(modifier = Modifier.fillMaxSize().padding(contentPadding)) {
|
||||
LazyColumn(state = listState, modifier = Modifier.fillMaxSize()) {
|
||||
stickyHeader {
|
||||
val animatedAlpha by
|
||||
animateFloatAsState(targetValue = if (!isScrollInProgress) 1.0f else 0f, label = "alpha")
|
||||
NodeFilterTextField(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.graphicsLayer(alpha = animatedAlpha)
|
||||
.background(MaterialTheme.colorScheme.surfaceDim)
|
||||
.padding(8.dp),
|
||||
filterText = state.filter,
|
||||
onTextChange = model::setNodeFilterText,
|
||||
currentSortOption = state.sort,
|
||||
onSortSelect = model::setSortOption,
|
||||
includeUnknown = state.includeUnknown,
|
||||
onToggleIncludeUnknown = model::toggleIncludeUnknown,
|
||||
onlyOnline = state.onlyOnline,
|
||||
onToggleOnlyOnline = model::toggleOnlyOnline,
|
||||
onlyDirect = state.onlyDirect,
|
||||
onToggleOnlyDirect = model::toggleOnlyDirect,
|
||||
showDetails = state.showDetails,
|
||||
onToggleShowDetails = model::toggleShowDetails,
|
||||
showIgnored = state.showIgnored,
|
||||
onToggleShowIgnored = model::toggleShowIgnored,
|
||||
ignoredNodeCount = ignoredNodeCount,
|
||||
)
|
||||
}
|
||||
|
||||
items(nodes, key = { it.num }) { node ->
|
||||
NodeItem(
|
||||
modifier = Modifier.animateItem(),
|
||||
thisNode = ourNode,
|
||||
thatNode = node,
|
||||
distanceUnits = state.distanceUnits,
|
||||
tempInFahrenheit = state.tempInFahrenheit,
|
||||
onAction = { menuItem ->
|
||||
when (menuItem) {
|
||||
is NodeMenuAction.Remove -> model.removeNode(node.num)
|
||||
is NodeMenuAction.Ignore -> model.ignoreNode(node)
|
||||
is NodeMenuAction.Favorite -> model.favoriteNode(node)
|
||||
is NodeMenuAction.DirectMessage -> {
|
||||
val hasPKC = model.ourNodeInfo.value?.hasPKC == true && node.hasPKC
|
||||
val channel = if (hasPKC) DataPacket.PKC_CHANNEL_INDEX else node.channel
|
||||
navigateToMessages("$channel${node.user.id}")
|
||||
items(nodes, key = { it.num }) { node ->
|
||||
NodeItem(
|
||||
modifier = Modifier.animateItem(),
|
||||
thisNode = ourNode,
|
||||
thatNode = node,
|
||||
distanceUnits = state.distanceUnits,
|
||||
tempInFahrenheit = state.tempInFahrenheit,
|
||||
onAction = { menuItem ->
|
||||
when (menuItem) {
|
||||
is NodeMenuAction.Remove -> model.removeNode(node.num)
|
||||
is NodeMenuAction.Ignore -> model.ignoreNode(node)
|
||||
is NodeMenuAction.Favorite -> model.favoriteNode(node)
|
||||
is NodeMenuAction.DirectMessage -> {
|
||||
val hasPKC = model.ourNodeInfo.value?.hasPKC == true && node.hasPKC
|
||||
val channel = if (hasPKC) DataPacket.PKC_CHANNEL_INDEX else node.channel
|
||||
navigateToMessages("$channel${node.user.id}")
|
||||
}
|
||||
|
||||
is NodeMenuAction.RequestUserInfo -> model.requestUserInfo(node.num)
|
||||
is NodeMenuAction.RequestPosition -> model.requestPosition(node.num)
|
||||
is NodeMenuAction.TraceRoute -> model.requestTraceroute(node.num)
|
||||
is NodeMenuAction.MoreDetails -> navigateToNodeDetails(node.num)
|
||||
is NodeMenuAction.Share -> showSharedContact = node
|
||||
}
|
||||
|
||||
is NodeMenuAction.RequestUserInfo -> model.requestUserInfo(node.num)
|
||||
is NodeMenuAction.RequestPosition -> model.requestPosition(node.num)
|
||||
is NodeMenuAction.TraceRoute -> model.requestTraceroute(node.num)
|
||||
is NodeMenuAction.MoreDetails -> navigateToNodeDetails(node.num)
|
||||
is NodeMenuAction.Share -> showSharedContact = node
|
||||
}
|
||||
},
|
||||
expanded = state.showDetails,
|
||||
currentTimeMillis = currentTimeMillis,
|
||||
isConnected = connectionState.isConnected(),
|
||||
)
|
||||
},
|
||||
expanded = state.showDetails,
|
||||
currentTimeMillis = currentTimeMillis,
|
||||
isConnected = connectionState.isConnected(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val firmwareVersion = DeviceVersion(ourNode?.metadata?.firmwareVersion ?: "0.0.0")
|
||||
val shareCapable = firmwareVersion.supportsQrCodeSharing()
|
||||
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.align(Alignment.BottomEnd),
|
||||
visible = !isScrollInProgress && connectionState == ConnectionState.CONNECTED && shareCapable,
|
||||
) {
|
||||
@Suppress("NewApi")
|
||||
(AddContactFAB(model = model, onSharedContactImport = { contact -> model.addSharedContact(contact) }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ fun AddContactFAB(
|
|||
}
|
||||
|
||||
FloatingActionButton(
|
||||
modifier = modifier,
|
||||
onClick = {
|
||||
if (cameraPermissionState.status.isGranted) {
|
||||
zxingScan()
|
||||
|
|
@ -170,7 +171,6 @@ fun AddContactFAB(
|
|||
cameraPermissionState.launchPermissionRequest()
|
||||
}
|
||||
},
|
||||
modifier = modifier.padding(16.dp),
|
||||
) {
|
||||
Icon(imageVector = Icons.TwoTone.QrCodeScanner, contentDescription = stringResource(R.string.scan_qr_code))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue