Files
TagesByte/lib/ui/common.dart
T

86 lines
2.7 KiB
Dart
Raw Normal View History

2026-10-16 14:53:37 +02:00
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
/// User-facing app name. Platform launcher labels (AndroidManifest, Info.plist,
/// web/manifest.json, windows/runner) are set separately.
const appName = 'Tagesbyte';
String timeAgo(DateTime? date) {
if (date == null) return '';
final diff = DateTime.now().difference(date.toLocal());
if (diff.inMinutes < 1) return 'just now';
if (diff.inMinutes < 60) return '${diff.inMinutes} min ago';
if (diff.inHours < 24) return '${diff.inHours} h ago';
if (diff.inDays == 1) return 'yesterday';
return '${diff.inDays} days ago';
}
String formatDateTime(DateTime date) {
final d = date.toLocal();
String two(int n) => n.toString().padLeft(2, '0');
return '${two(d.day)}.${two(d.month)}.${d.year} ${two(d.hour)}:${two(d.minute)}';
}
const weekdayNames = [
'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday',
];
const monthNames = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December',
];
bool isSameDay(DateTime a, DateTime b) =>
a.year == b.year && a.month == b.month && a.day == b.day;
Future<void> openUrl(BuildContext context, String url) async {
final ok = await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication);
if (!ok && context.mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Could not open $url')));
}
}
/// Centered icon + text + optional action, used for empty and error states.
class EmptyState extends StatelessWidget {
const EmptyState({
super.key,
required this.icon,
required this.title,
this.message,
this.action,
});
final IconData icon;
final String title;
final String? message;
final Widget? action;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 56, color: theme.colorScheme.primary),
const SizedBox(height: 16),
Text(title, style: theme.textTheme.titleMedium, textAlign: TextAlign.center),
if (message != null) ...[
const SizedBox(height: 8),
Text(
message!,
style: theme.textTheme.bodyMedium
?.copyWith(color: theme.colorScheme.onSurfaceVariant),
textAlign: TextAlign.center,
),
],
if (action != null) ...[const SizedBox(height: 20), action!],
],
),
),
);
}
}