Initial commit: Krow Worker App
This commit is contained in:
107
lib/screens/home_shell.dart
Normal file
107
lib/screens/home_shell.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user