feat(ui): align Android empty states

This commit is contained in:
2026-07-22 12:26:35 +02:00
parent cf7734fb42
commit a0d352895e
9 changed files with 25 additions and 108 deletions

View File

@@ -11,7 +11,6 @@ androidx-lifecycle = "2.11.0-beta01"
androidx-datastore = "1.2.1"
androidx-testExt = "1.3.0"
composeMultiplatform = "1.11.1"
compottie = "2.2.4"
filekit = "0.14.2"
gobley = "0.3.7"
junit = "4.13.2"
@@ -42,7 +41,6 @@ compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "composeMul
compose-uiTest = { module = "org.jetbrains.compose.ui:ui-test", version.ref = "composeMultiplatform" }
compose-components-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "composeMultiplatform" }
compose-uiToolingPreview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "composeMultiplatform" }
compottie-lite = { module = "io.github.alexzhirkevich:compottie-lite", version.ref = "compottie" }
filekit-dialogs = { module = "io.github.vinceglb:filekit-dialogs", version.ref = "filekit" }
kotlinx-coroutinesCore = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
kotlinx-coroutinesTest = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }

View File

@@ -139,7 +139,6 @@ kotlin {
implementation(libs.compose.ui)
implementation(libs.compose.components.resources)
implementation(libs.compose.uiToolingPreview)
implementation(libs.compottie.lite)
implementation(libs.androidx.lifecycle.viewmodelCompose)
implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.datastore)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -47,11 +47,9 @@ import com.vnidrop.app.core.FolderAccessStatus
import com.vnidrop.app.core.Transfer
import com.vnidrop.app.core.TransferDirection
import com.vnidrop.app.core.TransferStatus
import com.vnidrop.app.isDesktop
import com.vnidrop.app.ui.components.AdaptiveDrawer
import com.vnidrop.app.ui.components.DestructiveButton
import com.vnidrop.app.ui.components.DestructiveQuietButton
import com.vnidrop.app.ui.components.EmptyStateAnimation
import com.vnidrop.app.ui.components.Field
import com.vnidrop.app.ui.components.PrimaryButton
import com.vnidrop.app.ui.components.ProgressRow
@@ -166,9 +164,6 @@ private fun ReceiveHeader(showAction: Boolean, onOpen: () -> Unit) {
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(stringResource(Res.string.receive_title), style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold)
if (!LocalUiPlatform.current.isDesktop) {
Text(stringResource(Res.string.receive_new_subtitle), color = LocalVniDropColors.current.foregroundLighter)
}
}
if (showAction) {
Spacer(Modifier.width(16.dp))
@@ -180,27 +175,19 @@ private fun ReceiveHeader(showAction: Boolean, onOpen: () -> Unit) {
@Composable
private fun ReceiveEmptyState(onOpen: () -> Unit) {
val colors = LocalVniDropColors.current
val desktop = LocalUiPlatform.current.isDesktop
Column(
Modifier.fillMaxWidth().heightIn(min = 430.dp).padding(horizontal = 20.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
if (desktop) {
Icon(
imageVector = VniDropIcons.Receive,
contentDescription = null,
tint = colors.brandLink,
modifier = Modifier
.size(88.dp)
.testTag("receive-empty-icon"),
)
} else {
EmptyStateAnimation(
assetPath = "files/animations/receive_empty_state.json",
modifier = Modifier.size(168.dp),
)
}
Icon(
imageVector = VniDropIcons.Receive,
contentDescription = null,
tint = colors.brandLink,
modifier = Modifier
.size(88.dp)
.testTag("receive-empty-icon"),
)
Text(stringResource(Res.string.receive_empty_title), modifier = Modifier.padding(top = 12.dp), style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(
stringResource(Res.string.receive_empty_body),

View File

@@ -38,8 +38,6 @@ import androidx.compose.ui.unit.dp
import com.vnidrop.app.core.CoreEventModel
import com.vnidrop.app.core.Transfer
import com.vnidrop.app.core.TransferStatus
import com.vnidrop.app.isDesktop
import com.vnidrop.app.ui.components.EmptyStateAnimation
import com.vnidrop.app.ui.components.PillTone
import com.vnidrop.app.ui.components.PrimaryButton
import com.vnidrop.app.ui.components.ProgressRow
@@ -62,7 +60,6 @@ import vnidrop.shared.generated.resources.send_empty_body
import vnidrop.shared.generated.resources.send_empty_title
import vnidrop.shared.generated.resources.send_new_transfer_description
import vnidrop.shared.generated.resources.send_new_transfer_title
import vnidrop.shared.generated.resources.send_subtitle
import vnidrop.shared.generated.resources.send_title
import vnidrop.shared.generated.resources.send_transfers_title
@@ -131,13 +128,6 @@ private fun CatalogHeader(showAction: Boolean, onOpenComposer: () -> Unit) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(stringResource(Res.string.send_title), style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold)
if (!LocalUiPlatform.current.isDesktop) {
Text(
stringResource(Res.string.send_subtitle),
color = LocalVniDropColors.current.foregroundLighter,
style = MaterialTheme.typography.bodyMedium,
)
}
}
if (showAction) {
Spacer(Modifier.width(16.dp))
@@ -149,27 +139,19 @@ private fun CatalogHeader(showAction: Boolean, onOpenComposer: () -> Unit) {
@Composable
private fun SendEmptyState(onOpenComposer: () -> Unit) {
val colors = LocalVniDropColors.current
val desktop = LocalUiPlatform.current.isDesktop
Column(
modifier = Modifier.fillMaxWidth().heightIn(min = 430.dp).padding(horizontal = 20.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
if (desktop) {
Icon(
imageVector = VniDropIcons.Send,
contentDescription = null,
tint = colors.brandLink,
modifier = Modifier
.size(88.dp)
.testTag("send-empty-icon"),
)
} else {
EmptyStateAnimation(
assetPath = "files/animations/send_empty_state.json",
modifier = Modifier.size(168.dp),
)
}
Icon(
imageVector = VniDropIcons.Send,
contentDescription = null,
tint = colors.brandLink,
modifier = Modifier
.size(88.dp)
.testTag("send-empty-icon"),
)
Text(
stringResource(Res.string.send_empty_title),
modifier = Modifier.padding(top = 12.dp),

View File

@@ -7,11 +7,8 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.vnidrop.app.isDesktop
import com.vnidrop.app.ui.platform.LocalUiPlatform
import com.vnidrop.app.ui.theme.ThemeMode
import org.jetbrains.compose.resources.stringResource
import com.vnidrop.app.ui.theme.LocalVniDropColors
import vnidrop.shared.generated.resources.Res
import vnidrop.shared.generated.resources.about_title
import vnidrop.shared.generated.resources.appearance_dark_mode
@@ -20,7 +17,6 @@ import vnidrop.shared.generated.resources.appearance_system_mode
import vnidrop.shared.generated.resources.appearance_title
import vnidrop.shared.generated.resources.notifications_title
import vnidrop.shared.generated.resources.preferences_title
import vnidrop.shared.generated.resources.settings_subtitle
import vnidrop.shared.generated.resources.settings_title
@Composable
@@ -30,20 +26,11 @@ internal fun SettingsOverview(
largeTitle: Boolean,
) {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(
stringResource(Res.string.settings_title),
style = if (largeTitle) MaterialTheme.typography.headlineLarge else MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold,
)
if (!LocalUiPlatform.current.isDesktop) {
Text(
stringResource(Res.string.settings_subtitle),
color = LocalVniDropColors.current.foregroundLighter,
style = MaterialTheme.typography.bodyMedium,
)
}
}
Text(
stringResource(Res.string.settings_title),
style = if (largeTitle) MaterialTheme.typography.headlineLarge else MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold,
)
SettingsGroup {
SettingsRow(
icon = SettingsIcons.Device,

View File

@@ -1,34 +0,0 @@
package com.vnidrop.app.ui.components
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import io.github.alexzhirkevich.compottie.Compottie
import io.github.alexzhirkevich.compottie.LottieCompositionSpec
import io.github.alexzhirkevich.compottie.rememberLottieComposition
import io.github.alexzhirkevich.compottie.rememberLottiePainter
import vnidrop.shared.generated.resources.Res
@Composable
internal fun EmptyStateAnimation(
assetPath: String,
modifier: Modifier = Modifier,
) {
val composition by rememberLottieComposition {
LottieCompositionSpec.JsonString(
Res.readBytes(assetPath).decodeToString(),
)
}
Image(
painter = rememberLottiePainter(
composition = composition,
iterations = Compottie.IterateForever,
),
contentDescription = null,
modifier = modifier,
contentScale = ContentScale.Fit,
)
}

View File

@@ -291,7 +291,7 @@ class FoundationComposeTest {
}
@Test
fun desktopPagesUseStaticFeatureIconsWithoutTitleDescriptions() = runComposeUiTest {
fun androidPagesUseStaticFeatureIconsWithoutTitleDescriptions() = runComposeUiTest {
val actions = object : ReceiveInvitationActions {
override val fileAvailability = ReceiveMethodAvailability.Hidden
override val qrAvailability = ReceiveMethodAvailability.Hidden
@@ -302,14 +302,14 @@ class FoundationComposeTest {
override fun cancel() = Unit
}
setContent {
CompositionLocalProvider(LocalUiPlatform provides UiPlatform.Linux) {
CompositionLocalProvider(LocalUiPlatform provides UiPlatform.Android) {
VniDropTheme(isDarkTheme = false) {
Row {
Box(Modifier.size(500.dp)) {
TransferCatalog(
transfers = emptyList(),
transferThumbnails = emptyMap(),
windowClass = WindowClass.Desktop,
windowClass = WindowClass.Phone,
onOpenComposer = {},
onTransferSelected = {},
)
@@ -318,7 +318,7 @@ class FoundationComposeTest {
ReceiveScreen(
coreState = CoreState(isInitialized = true),
state = ReceiveState(),
windowClass = WindowClass.Desktop,
windowClass = WindowClass.Phone,
actions = actions,
onOpenAcquisition = {},
onDismissAcquisition = {},