feat(ui): add animated empty states

This commit is contained in:
2026-07-15 18:34:26 +02:00
parent 7cd125053c
commit 43db35e9a2
7 changed files with 51 additions and 11 deletions

View File

@@ -11,6 +11,7 @@ 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"
gobley = "0.3.7" gobley = "0.3.7"
junit = "4.13.2" junit = "4.13.2"
kotlin = "2.4.0" kotlin = "2.4.0"
@@ -41,6 +42,7 @@ 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" }
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" }
kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }

View File

@@ -137,6 +137,7 @@ 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

@@ -49,6 +49,7 @@ import com.vnidrop.app.core.TransferStatus
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
@@ -176,10 +177,11 @@ private fun ReceiveEmptyState(onOpen: () -> Unit) {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {
Box(Modifier.size(68.dp).background(colors.brandLink.copy(alpha = 0.12f), RoundedCornerShape(20.dp)), contentAlignment = Alignment.Center) { EmptyStateAnimation(
Icon(ReceiveIcons.Download, null, tint = colors.brandLink, modifier = Modifier.size(30.dp)) assetPath = "files/animations/receive_empty_state.json",
} modifier = Modifier.size(168.dp),
Text(stringResource(Res.string.receive_empty_title), modifier = Modifier.padding(top = 22.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),
modifier = Modifier.padding(top = 8.dp).widthIn(max = 480.dp), modifier = Modifier.padding(top = 8.dp).widthIn(max = 480.dp),

View File

@@ -37,6 +37,7 @@ 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.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
@@ -145,15 +146,13 @@ private fun SendEmptyState(onOpenComposer: () -> Unit) {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {
Box( EmptyStateAnimation(
modifier = Modifier.size(68.dp).clip(RoundedCornerShape(22.dp)).background(colors.brandLink.copy(alpha = 0.12f)), assetPath = "files/animations/send_empty_state.json",
contentAlignment = Alignment.Center, modifier = Modifier.size(168.dp),
) { )
Icon(SendIcons.File, contentDescription = null, tint = colors.brandLink, modifier = Modifier.size(30.dp))
}
Text( Text(
stringResource(Res.string.send_empty_title), stringResource(Res.string.send_empty_title),
modifier = Modifier.padding(top = 22.dp), modifier = Modifier.padding(top = 12.dp),
style = MaterialTheme.typography.headlineSmall, style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,

View File

@@ -0,0 +1,34 @@
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,
)
}