Files
krow-worker-app/lib/screens/home_shell.dart
2026-09-17 22:26:11 +05:30

108 lines
3.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import '../state/timecards_controller.dart';
import '../widgets/krow_nav_bar.dart';
/// The five primary tabs. Each keeps its own navigation stack.
class HomeShell extends ConsumerWidget {
const HomeShell({super.key, required this.navigationShell});
final StatefulNavigationShell navigationShell;
@override
Widget build(BuildContext context, WidgetRef ref) {
final needsReview = ref.watch(timecardsNeedingReviewProvider).isNotEmpty;
return Scaffold(
body: _TabTransition(
index: navigationShell.currentIndex,
child: navigationShell,
),
bottomNavigationBar: KrowNavBar(
currentIndex: navigationShell.currentIndex,
onSelect: (i) => navigationShell.goBranch(
i,
initialLocation: i == navigationShell.currentIndex,
),
items: [
const KrowNavItem(icon: LucideIcons.house, label: 'Today'),
const KrowNavItem(icon: LucideIcons.briefcase, label: 'Shifts'),
KrowNavItem(
icon: LucideIcons.clock,
label: 'Clock',
showDot: needsReview,
),
const KrowNavItem(icon: LucideIcons.wallet, label: 'Pay'),
const KrowNavItem(icon: LucideIcons.user, label: 'Profile'),
],
),
);
}
}
/// Fades and lifts the tab content each time the tab changes.
///
/// Deliberately not an AnimatedSwitcher: that would hold two copies of the
/// shell in the tree at once, and the branch navigators are keyed by
/// GlobalKey, so duplicating them would throw. This keeps one subtree — the
/// branches stay alive and keep their scroll positions — and animates it in
/// place when the index changes.
class _TabTransition extends StatefulWidget {
const _TabTransition({required this.index, required this.child});
final int index;
final Widget child;
@override
State<_TabTransition> createState() => _TabTransitionState();
}
class _TabTransitionState extends State<_TabTransition>
with SingleTickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 260),
value: 1,
);
late final Animation<double> _curve = CurvedAnimation(
parent: _controller,
curve: Curves.easeOutCubic,
);
@override
void didUpdateWidget(_TabTransition oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.index != widget.index) _controller.forward(from: 0);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _curve,
// The shell is passed through untouched so it is never rebuilt.
child: widget.child,
builder: (context, child) {
final t = _curve.value;
return Opacity(
// Never fully transparent: a tab that blinks out reads as a glitch.
opacity: 0.35 + 0.65 * t,
child: Transform.translate(
offset: Offset(0, (1 - t) * 10),
child: child,
),
);
},
);
}
}