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-datastore = "1.2.1"
androidx-testExt = "1.3.0" androidx-testExt = "1.3.0"
composeMultiplatform = "1.11.1" composeMultiplatform = "1.11.1"
compottie = "2.2.4"
filekit = "0.14.2" filekit = "0.14.2"
gobley = "0.3.7" gobley = "0.3.7"
junit = "4.13.2" 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-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-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" } 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" } 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-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" } 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.ui)
implementation(libs.compose.components.resources) implementation(libs.compose.components.resources)
implementation(libs.compose.uiToolingPreview) implementation(libs.compose.uiToolingPreview)
implementation(libs.compottie.lite)
implementation(libs.androidx.lifecycle.viewmodelCompose) implementation(libs.androidx.lifecycle.viewmodelCompose)
implementation(libs.androidx.lifecycle.runtimeCompose) implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.datastore) 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.Transfer
import com.vnidrop.app.core.TransferDirection import com.vnidrop.app.core.TransferDirection
import com.vnidrop.app.core.TransferStatus import com.vnidrop.app.core.TransferStatus
import com.vnidrop.app.isDesktop
import com.vnidrop.app.ui.components.AdaptiveDrawer import com.vnidrop.app.ui.components.AdaptiveDrawer
import com.vnidrop.app.ui.components.DestructiveButton import com.vnidrop.app.ui.components.DestructiveButton
import com.vnidrop.app.ui.components.DestructiveQuietButton 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.Field
import com.vnidrop.app.ui.components.PrimaryButton import com.vnidrop.app.ui.components.PrimaryButton
import com.vnidrop.app.ui.components.ProgressRow import com.vnidrop.app.ui.components.ProgressRow
@@ -166,9 +164,6 @@ private fun ReceiveHeader(showAction: Boolean, onOpen: () -> Unit) {
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) { Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(stringResource(Res.string.receive_title), style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) 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) { if (showAction) {
Spacer(Modifier.width(16.dp)) Spacer(Modifier.width(16.dp))
@@ -180,13 +175,11 @@ private fun ReceiveHeader(showAction: Boolean, onOpen: () -> Unit) {
@Composable @Composable
private fun ReceiveEmptyState(onOpen: () -> Unit) { private fun ReceiveEmptyState(onOpen: () -> Unit) {
val colors = LocalVniDropColors.current val colors = LocalVniDropColors.current
val desktop = LocalUiPlatform.current.isDesktop
Column( Column(
Modifier.fillMaxWidth().heightIn(min = 430.dp).padding(horizontal = 20.dp), Modifier.fillMaxWidth().heightIn(min = 430.dp).padding(horizontal = 20.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {
if (desktop) {
Icon( Icon(
imageVector = VniDropIcons.Receive, imageVector = VniDropIcons.Receive,
contentDescription = null, contentDescription = null,
@@ -195,12 +188,6 @@ private fun ReceiveEmptyState(onOpen: () -> Unit) {
.size(88.dp) .size(88.dp)
.testTag("receive-empty-icon"), .testTag("receive-empty-icon"),
) )
} else {
EmptyStateAnimation(
assetPath = "files/animations/receive_empty_state.json",
modifier = Modifier.size(168.dp),
)
}
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_title), modifier = Modifier.padding(top = 12.dp), style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text( Text(
stringResource(Res.string.receive_empty_body), 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.CoreEventModel
import com.vnidrop.app.core.Transfer import com.vnidrop.app.core.Transfer
import com.vnidrop.app.core.TransferStatus 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.PillTone
import com.vnidrop.app.ui.components.PrimaryButton import com.vnidrop.app.ui.components.PrimaryButton
import com.vnidrop.app.ui.components.ProgressRow 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_empty_title
import vnidrop.shared.generated.resources.send_new_transfer_description import vnidrop.shared.generated.resources.send_new_transfer_description
import vnidrop.shared.generated.resources.send_new_transfer_title 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_title
import vnidrop.shared.generated.resources.send_transfers_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) { Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) { Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(stringResource(Res.string.send_title), style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) 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) { if (showAction) {
Spacer(Modifier.width(16.dp)) Spacer(Modifier.width(16.dp))
@@ -149,13 +139,11 @@ private fun CatalogHeader(showAction: Boolean, onOpenComposer: () -> Unit) {
@Composable @Composable
private fun SendEmptyState(onOpenComposer: () -> Unit) { private fun SendEmptyState(onOpenComposer: () -> Unit) {
val colors = LocalVniDropColors.current val colors = LocalVniDropColors.current
val desktop = LocalUiPlatform.current.isDesktop
Column( Column(
modifier = Modifier.fillMaxWidth().heightIn(min = 430.dp).padding(horizontal = 20.dp), modifier = Modifier.fillMaxWidth().heightIn(min = 430.dp).padding(horizontal = 20.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {
if (desktop) {
Icon( Icon(
imageVector = VniDropIcons.Send, imageVector = VniDropIcons.Send,
contentDescription = null, contentDescription = null,
@@ -164,12 +152,6 @@ private fun SendEmptyState(onOpenComposer: () -> Unit) {
.size(88.dp) .size(88.dp)
.testTag("send-empty-icon"), .testTag("send-empty-icon"),
) )
} else {
EmptyStateAnimation(
assetPath = "files/animations/send_empty_state.json",
modifier = Modifier.size(168.dp),
)
}
Text( Text(
stringResource(Res.string.send_empty_title), stringResource(Res.string.send_empty_title),
modifier = Modifier.padding(top = 12.dp), 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.runtime.Composable
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp 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 com.vnidrop.app.ui.theme.ThemeMode
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
import com.vnidrop.app.ui.theme.LocalVniDropColors
import vnidrop.shared.generated.resources.Res import vnidrop.shared.generated.resources.Res
import vnidrop.shared.generated.resources.about_title import vnidrop.shared.generated.resources.about_title
import vnidrop.shared.generated.resources.appearance_dark_mode 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.appearance_title
import vnidrop.shared.generated.resources.notifications_title import vnidrop.shared.generated.resources.notifications_title
import vnidrop.shared.generated.resources.preferences_title import vnidrop.shared.generated.resources.preferences_title
import vnidrop.shared.generated.resources.settings_subtitle
import vnidrop.shared.generated.resources.settings_title import vnidrop.shared.generated.resources.settings_title
@Composable @Composable
@@ -30,20 +26,11 @@ internal fun SettingsOverview(
largeTitle: Boolean, largeTitle: Boolean,
) { ) {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text( Text(
stringResource(Res.string.settings_title), stringResource(Res.string.settings_title),
style = if (largeTitle) MaterialTheme.typography.headlineLarge else MaterialTheme.typography.headlineMedium, style = if (largeTitle) MaterialTheme.typography.headlineLarge else MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
) )
if (!LocalUiPlatform.current.isDesktop) {
Text(
stringResource(Res.string.settings_subtitle),
color = LocalVniDropColors.current.foregroundLighter,
style = MaterialTheme.typography.bodyMedium,
)
}
}
SettingsGroup { SettingsGroup {
SettingsRow( SettingsRow(
icon = SettingsIcons.Device, 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 @Test
fun desktopPagesUseStaticFeatureIconsWithoutTitleDescriptions() = runComposeUiTest { fun androidPagesUseStaticFeatureIconsWithoutTitleDescriptions() = runComposeUiTest {
val actions = object : ReceiveInvitationActions { val actions = object : ReceiveInvitationActions {
override val fileAvailability = ReceiveMethodAvailability.Hidden override val fileAvailability = ReceiveMethodAvailability.Hidden
override val qrAvailability = ReceiveMethodAvailability.Hidden override val qrAvailability = ReceiveMethodAvailability.Hidden
@@ -302,14 +302,14 @@ class FoundationComposeTest {
override fun cancel() = Unit override fun cancel() = Unit
} }
setContent { setContent {
CompositionLocalProvider(LocalUiPlatform provides UiPlatform.Linux) { CompositionLocalProvider(LocalUiPlatform provides UiPlatform.Android) {
VniDropTheme(isDarkTheme = false) { VniDropTheme(isDarkTheme = false) {
Row { Row {
Box(Modifier.size(500.dp)) { Box(Modifier.size(500.dp)) {
TransferCatalog( TransferCatalog(
transfers = emptyList(), transfers = emptyList(),
transferThumbnails = emptyMap(), transferThumbnails = emptyMap(),
windowClass = WindowClass.Desktop, windowClass = WindowClass.Phone,
onOpenComposer = {}, onOpenComposer = {},
onTransferSelected = {}, onTransferSelected = {},
) )
@@ -318,7 +318,7 @@ class FoundationComposeTest {
ReceiveScreen( ReceiveScreen(
coreState = CoreState(isInitialized = true), coreState = CoreState(isInitialized = true),
state = ReceiveState(), state = ReceiveState(),
windowClass = WindowClass.Desktop, windowClass = WindowClass.Phone,
actions = actions, actions = actions,
onOpenAcquisition = {}, onOpenAcquisition = {},
onDismissAcquisition = {}, onDismissAcquisition = {},