mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-07-02 23:10:55 +10:00
Add shared UI components for mesh application
- Introduced `mesh_ui.dart` with reusable widgets including SectionHeader, MeshCard, StatusChip, StatTile, AvatarCircle, SignalBars, RouteChip, PulseDot, BottomSheetHeader, ErrorRetryCard, and ListEntrance. - Implemented `path_map_ui.dart` for path map screens, featuring path distance calculations, playback controls, and a summary list of observed paths. - Created `themed_map_tile_layer.dart` for shared cached map tiles with automatic dark-mode treatment.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A centered empty state display with icon, title, and optional subtitle/action.
|
||||
class EmptyState extends StatelessWidget {
|
||||
/// Features a tinted icon circle, fade+slide entrance animation, and clear
|
||||
/// typography hierarchy using the MeshCore design system.
|
||||
class EmptyState extends StatefulWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
@@ -15,29 +17,97 @@ class EmptyState extends StatelessWidget {
|
||||
this.action,
|
||||
});
|
||||
|
||||
@override
|
||||
State<EmptyState> createState() => _EmptyStateState();
|
||||
}
|
||||
|
||||
class _EmptyStateState extends State<EmptyState>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 420),
|
||||
);
|
||||
late final CurvedAnimation _curve = CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Curves.easeOutCubic,
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_curve.dispose();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final onSurfaceVariant = Theme.of(context).colorScheme.onSurfaceVariant;
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, size: 64, color: onSurfaceVariant.withValues(alpha: 0.6)),
|
||||
const SizedBox(height: 16),
|
||||
Text(title, style: TextStyle(fontSize: 16, color: onSurfaceVariant)),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: onSurfaceVariant.withValues(alpha: 0.8),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return FadeTransition(
|
||||
opacity: _curve,
|
||||
child: SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0, 0.06),
|
||||
end: Offset.zero,
|
||||
).animate(_curve),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: scheme.primary.withValues(alpha: 0.08),
|
||||
border: Border.all(
|
||||
color: scheme.primary.withValues(alpha: 0.18),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
widget.icon,
|
||||
size: 36,
|
||||
color: scheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
widget.title,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: scheme.onSurface,
|
||||
letterSpacing: -0.1,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (widget.subtitle != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
widget.subtitle!,
|
||||
style: TextStyle(
|
||||
fontSize: 13.5,
|
||||
color: scheme.onSurfaceVariant,
|
||||
height: 1.45,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
if (widget.action != null) ...[
|
||||
const SizedBox(height: 28),
|
||||
widget.action!,
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
if (action != null) ...[const SizedBox(height: 24), action!],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user