💄 Better bottom nav, snowing animation and notification tile

💫 Animated snowing animation
This commit is contained in:
2025-12-22 23:28:38 +08:00
parent b0b227f36b
commit 09abe79f6a
3 changed files with 99 additions and 69 deletions

View File

@@ -35,7 +35,8 @@ class AppWrapper extends HookConsumerWidget {
final networkStateShowing = useState(false);
final wsNotifier = ref.watch(websocketStateProvider.notifier);
final websocketState = ref.watch(websocketStateProvider);
final showSnow = useState(false);
final isShowSnow = useState(false);
final isSnowGone = useState(false);
// Handle network status modal
if (websocketState == WebSocketState.duplicateDevice() &&
@@ -131,41 +132,56 @@ class AppWrapper extends HookConsumerWidget {
settings.festivalFeatures &&
now.month == 12 &&
(now.day >= 22 && now.day <= 28);
if (doesShowSnow) {
showSnow.value = true;
Future.delayed(const Duration(seconds: 10), () {
showSnow.value = false;
});
}
if (settings.firstLaunchAt == null) {
settingsNotifier.setFirstLaunchAt(now.toIso8601String());
} else if (!settings.askedReview) {
final launchAt = DateTime.parse(settings.firstLaunchAt!);
final daysSinceFirstLaunch = now.difference(launchAt).inDays;
if (daysSinceFirstLaunch >= 3 &&
!kIsWeb &&
(Platform.isAndroid || Platform.isIOS || Platform.isMacOS)) {
final InAppReview inAppReview = InAppReview.instance;
Future(() async {
if (await inAppReview.isAvailable()) {
inAppReview.requestReview();
}
useEffect(() {
final now = DateTime.now();
if (doesShowSnow) {
isShowSnow.value = true;
Future.delayed(const Duration(seconds: 60), () {
if (!context.mounted) return;
isShowSnow.value = false;
Future.delayed(const Duration(seconds: 3), () {
if (!context.mounted) return;
isSnowGone.value = true;
});
});
settingsNotifier.setAskedReview(true);
}
}
if (settings.firstLaunchAt == null) {
settingsNotifier.setFirstLaunchAt(now.toIso8601String());
} else if (!settings.askedReview) {
final launchAt = DateTime.parse(settings.firstLaunchAt!);
final daysSinceFirstLaunch = now.difference(launchAt).inDays;
if (daysSinceFirstLaunch >= 3 &&
!kIsWeb &&
(Platform.isAndroid || Platform.isIOS || Platform.isMacOS)) {
final InAppReview inAppReview = InAppReview.instance;
Future(() async {
if (await inAppReview.isAvailable()) {
inAppReview.requestReview();
}
});
settingsNotifier.setAskedReview(true);
}
}
return null;
}, []);
return TourTriggerWidget(
key: const Key("app_tour_trigger"),
child: Stack(
children: [
child,
if (showSnow.value)
if (doesShowSnow && !isSnowGone.value)
IgnorePointer(
child: SnowFallAnimation(
key: const Key("app_snow_animation"),
config: SnowfallConfig(numberOfSnowflakes: 50, speed: 1.0),
child: AnimatedOpacity(
opacity: isShowSnow.value ? 1 : 00,
duration: const Duration(seconds: 3),
child: SnowFallAnimation(
key: const Key("app_snow_animation"),
config: SnowfallConfig(numberOfSnowflakes: 50, speed: 1.0),
),
),
),
],

View File

@@ -81,14 +81,14 @@ class NotificationTile extends StatelessWidget {
style: compact
? Theme.of(context).textTheme.bodySmall
: Theme.of(context).textTheme.titleMedium,
maxLines: compact ? 2 : null,
maxLines: compact ? 1 : null,
overflow: compact ? TextOverflow.ellipsis : null,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (notification.subtitle.isNotEmpty && !compact)
Text(notification.subtitle).bold(),
Text(notification.subtitle, maxLines: compact ? 3 : null).bold(),
Row(
spacing: 6,
children: [
@@ -114,7 +114,9 @@ class NotificationTile extends StatelessWidget {
],
).opacity(0.75).padding(bottom: compact ? 2 : 4),
MarkdownTextContent(
content: notification.content,
content: (compact && notification.content.length > 60)
? '${notification.content.substring(0, 60).replaceAll('\n', ' ')}...'
: notification.content,
textStyle:
(compact
? Theme.of(context).textTheme.bodySmall