add time line wrapper
Test / test (push) Successful in 3m13s

Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2026-04-18 17:03:42 +02:00
parent db4bf5dbc1
commit b91c8a93db
3 changed files with 125 additions and 19 deletions
+42 -18
View File
@@ -1,30 +1,54 @@
import 'package:toaster_ui/toaster_ui.dart';
const _toasts = [
(
icon: Icons.chat_bubble_outline,
title: 'Messages',
timestamp: '10:42 AM',
content: 'New message received from +1 (555) 019-2834',
tags: ['res/values/strings.xml', 'en-US'],
),
(
icon: Icons.notifications_outlined,
title: 'Push Notification',
timestamp: '10:45 AM',
content: 'Your order has been shipped!',
tags: ['res/values/strings.xml', 'en-US'],
),
(
icon: Icons.email_outlined,
title: 'Email',
timestamp: '11:03 AM',
content: 'You have a new message in your inbox.',
tags: ['res/values/strings.xml', 'en-US'],
),
];
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
final spacing = context.appSpacing;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: .center,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: context.appSpacing.md),
child: ToastCard(
icon: Icons.chat_bubble_outline,
title: 'Messages',
timestamp: '10:42 AM',
content: 'New message received from +1 (555) 019-2834',
tags: ['res/values/strings.xml', 'en-US'],
onDelete: () {
/* ... */
},
),
body: ListView.builder(
padding: EdgeInsets.all(spacing.md),
itemCount: _toasts.length,
itemBuilder: (context, index) {
final toast = _toasts[index];
return TimelineToastCard(
isFirst: index == 0,
isLast: index == _toasts.length - 1,
card: ToastCard(
icon: toast.icon,
title: toast.title,
timestamp: toast.timestamp,
content: toast.content,
tags: [...toast.tags],
),
],
),
);
},
),
);
}
@@ -0,0 +1,81 @@
import 'package:toaster_ui/toaster_ui.dart';
/// {@template timeline_toast_card}
/// Wraps a [ToastCard] with a vertical timeline indicator (dot + line).
/// Lines are drawn above and below the dot based on [isFirst] and [isLast].
/// {@endtemplate}
class TimelineToastCard extends StatelessWidget {
/// {@macro timeline_toast_card}
const TimelineToastCard({
required this.card,
super.key,
this.isFirst = false,
this.isLast = false,
});
/// The card to display.
final ToastCard card;
/// Whether this is the first item in the timeline (no line above the dot).
final bool isFirst;
/// Whether this is the last item in the timeline (no line below the dot).
final bool isLast;
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final spacing = context.appSpacing;
return IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
width: 24,
child: Column(
children: [
if (!isFirst)
Container(width: 2, height: spacing.md, color: cs.outlineVariant)
else
SizedBox(height: spacing.md),
_Dot(color: cs.primary),
if (!isLast)
Expanded(
child: Center(
child: Container(width: 2, color: cs.outlineVariant),
),
),
],
),
),
SizedBox(width: spacing.sm),
Expanded(
child: Padding(
padding: EdgeInsets.only(bottom: isLast ? 0 : spacing.sm),
child: card,
),
),
],
),
);
}
}
class _Dot extends StatelessWidget {
const _Dot({required this.color});
final Color color;
@override
Widget build(BuildContext context) {
return Container(
width: 10,
height: 10,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
),
);
}
}
+1
View File
@@ -8,4 +8,5 @@ export 'src/theme/app_colors.dart';
export 'src/theme/app_spacing.dart';
export 'src/theme/app_theme.dart';
export 'src/widgets/app_button.dart';
export 'src/widgets/timeline_toast_card.dart';
export 'src/widgets/toast_card.dart';