Worklog, anti geo spoofing and UI Polish

This commit is contained in:
2026-05-05 05:51:19 +08:00
parent d068887354
commit ccedf6e5f0
15 changed files with 2589 additions and 45 deletions
+61 -19
View File
@@ -254,11 +254,18 @@ class _StatusChip extends StatelessWidget {
}
}
class _CacheGrid extends StatelessWidget {
class _CacheGrid extends StatefulWidget {
final Map<String, int> counts;
const _CacheGrid({required this.counts});
@override
State<_CacheGrid> createState() => _CacheGridState();
}
class _CacheGridState extends State<_CacheGrid> with SingleTickerProviderStateMixin {
late final AnimationController _controller;
static const _groups = [
('Tasks', 'tasks', Icons.task_alt_rounded),
('Tickets', 'tickets', Icons.confirmation_number_rounded),
@@ -274,6 +281,21 @@ class _CacheGrid extends StatelessWidget {
('Offices', 'offices', Icons.business_rounded),
];
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
)..forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
@@ -284,22 +306,41 @@ class _CacheGrid extends StatelessWidget {
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 1.1,
childAspectRatio: 0.9,
),
itemCount: _groups.length,
itemBuilder: (context, i) {
final (label, key, icon) = _groups[i];
final count = counts[key] ?? -1;
final count = widget.counts[key] ?? -1;
final cached = count >= 0;
return AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
child: _CacheTile(
key: ValueKey('$key-$count'),
label: label,
icon: icon,
count: count,
cached: cached,
scheme: scheme,
final start = i * 0.055;
final end = (start + 0.45).clamp(0.0, 1.0);
final curve = CurvedAnimation(
parent: _controller,
curve: Interval(start, end, curve: Curves.easeOutCubic),
);
final fadeAnim = Tween<double>(begin: 0.0, end: 1.0).animate(curve);
final slideAnim = Tween<Offset>(
begin: const Offset(0, 0.25),
end: Offset.zero,
).animate(curve);
return FadeTransition(
opacity: fadeAnim,
child: SlideTransition(
position: slideAnim,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
child: _CacheTile(
key: ValueKey('$key-$count'),
label: label,
icon: icon,
count: count,
cached: cached,
scheme: scheme,
),
),
),
);
},
@@ -328,24 +369,25 @@ class _CacheTile extends StatelessWidget {
final bg = cached ? scheme.primaryContainer.withValues(alpha: 0.5) : scheme.surfaceContainerHighest;
final fg = cached ? scheme.onPrimaryContainer : scheme.onSurfaceVariant;
return Container(
decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(14)),
padding: const EdgeInsets.all(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: fg, size: 22),
const SizedBox(height: 4),
Icon(icon, color: fg, size: 24),
const SizedBox(height: 6),
Text(
label,
style: TextStyle(color: fg, fontSize: 10, fontWeight: FontWeight.w500),
style: TextStyle(color: fg, fontSize: 11, fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
maxLines: 1,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
cached ? '$count items' : 'Not cached',
style: TextStyle(color: fg.withValues(alpha: 0.7), fontSize: 9),
style: TextStyle(color: fg.withValues(alpha: 0.7), fontSize: 10),
textAlign: TextAlign.center,
),
],
),