2026-04-09 06:47:03 +00:00
|
|
|
import 'dart:async';
|
|
|
|
|
import 'dart:ui';
|
|
|
|
|
|
|
|
|
|
import 'package:cb_prestige_qr/core/presentation/widgets/global_loading_overlay.dart';
|
2026-04-27 09:08:26 +00:00
|
|
|
import 'package:cb_prestige_qr/core/widgets/center_nav_button.dart';
|
|
|
|
|
import 'package:cb_prestige_qr/core/widgets/nav_item.dart';
|
|
|
|
|
import 'package:cb_prestige_qr/features/analysis/presentation/views/analysis_view.dart';
|
|
|
|
|
import 'package:cb_prestige_qr/features/history/presentation/views/history_view.dart';
|
|
|
|
|
import 'package:cb_prestige_qr/features/home/presentation/views/home_view.dart';
|
|
|
|
|
import 'package:cb_prestige_qr/features/scan/presentation/views/scan_flow.dart';
|
|
|
|
|
import 'package:cb_prestige_qr/features/settings/presentation/views/settings_view.dart';
|
2026-04-09 06:47:03 +00:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
|
|
|
|
2026-04-27 09:08:26 +00:00
|
|
|
final navIndexProvider = NotifierProvider<NavIndexNotifier, int>(
|
2026-04-09 06:47:03 +00:00
|
|
|
NavIndexNotifier.new,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
class NavIndexNotifier extends Notifier<int> {
|
|
|
|
|
@override
|
|
|
|
|
int build() => 0;
|
|
|
|
|
|
|
|
|
|
void setIndex(int index) {
|
|
|
|
|
if (state == index) return;
|
|
|
|
|
state = index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MainShell extends ConsumerStatefulWidget {
|
|
|
|
|
const MainShell({super.key});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
ConsumerState<MainShell> createState() => _MainShellState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _MainShellState extends ConsumerState<MainShell> {
|
|
|
|
|
Timer? _timer;
|
|
|
|
|
var _isLoading = false;
|
|
|
|
|
|
|
|
|
|
void _startLoading([Duration duration = const Duration(seconds: 1)]) {
|
|
|
|
|
_timer?.cancel();
|
2026-04-27 09:08:26 +00:00
|
|
|
if (!_isLoading) {
|
|
|
|
|
setState(() => _isLoading = true);
|
|
|
|
|
}
|
2026-04-09 06:47:03 +00:00
|
|
|
_timer = Timer(duration, () {
|
|
|
|
|
if (!mounted) return;
|
|
|
|
|
setState(() => _isLoading = false);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void initState() {
|
|
|
|
|
super.initState();
|
2026-04-27 09:08:26 +00:00
|
|
|
final initialIndex = ref.read(navIndexProvider);
|
|
|
|
|
if (initialIndex != 0) {
|
|
|
|
|
_startLoading();
|
|
|
|
|
}
|
2026-04-09 06:47:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
void dispose() {
|
|
|
|
|
_timer?.cancel();
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
2026-04-27 09:08:26 +00:00
|
|
|
final selectedIndex = ref.watch(navIndexProvider);
|
2026-04-09 06:47:03 +00:00
|
|
|
final theme = Theme.of(context);
|
|
|
|
|
|
2026-04-27 09:08:26 +00:00
|
|
|
ref.listen<int>(navIndexProvider, (previous, next) {
|
|
|
|
|
if (previous == null || previous == next || next == 0) return;
|
2026-04-09 06:47:03 +00:00
|
|
|
_startLoading();
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-27 09:08:26 +00:00
|
|
|
const pages = <Widget>[
|
|
|
|
|
HomeView(),
|
|
|
|
|
AnalysisView(),
|
|
|
|
|
SizedBox(),
|
|
|
|
|
HistoryView(),
|
|
|
|
|
SettingsView(),
|
2026-04-09 06:47:03 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return Stack(
|
|
|
|
|
children: [
|
|
|
|
|
Scaffold(
|
|
|
|
|
extendBody: true,
|
|
|
|
|
body: IndexedStack(index: selectedIndex, children: pages),
|
|
|
|
|
bottomNavigationBar: Padding(
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
|
|
|
|
child: ClipRRect(
|
|
|
|
|
borderRadius: BorderRadius.circular(16),
|
|
|
|
|
child: BackdropFilter(
|
|
|
|
|
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
|
|
|
|
|
child: Container(
|
|
|
|
|
height: 70 + MediaQuery.of(context).padding.bottom,
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: theme.scaffoldBackgroundColor,
|
|
|
|
|
borderRadius: BorderRadius.circular(16),
|
|
|
|
|
border: Border.all(color: Colors.white.withAlpha(31)),
|
|
|
|
|
boxShadow: [
|
|
|
|
|
BoxShadow(
|
|
|
|
|
color: Colors.black.withAlpha(31),
|
|
|
|
|
blurRadius: 25,
|
|
|
|
|
offset: const Offset(0, 10),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: EdgeInsets.only(
|
|
|
|
|
bottom: MediaQuery.of(context).padding.bottom,
|
|
|
|
|
),
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(
|
|
|
|
|
child: NavItem(
|
|
|
|
|
icon: Icons.home_rounded,
|
|
|
|
|
isActive: selectedIndex == 0,
|
2026-04-27 09:08:26 +00:00
|
|
|
onTap: () =>
|
|
|
|
|
ref.read(navIndexProvider.notifier).setIndex(0),
|
2026-04-09 06:47:03 +00:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: NavItem(
|
|
|
|
|
icon: Icons.analytics,
|
|
|
|
|
isActive: selectedIndex == 1,
|
2026-04-27 09:08:26 +00:00
|
|
|
onTap: () =>
|
|
|
|
|
ref.read(navIndexProvider.notifier).setIndex(1),
|
2026-04-09 06:47:03 +00:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: CenterNavButton(
|
|
|
|
|
isActive: false,
|
|
|
|
|
onTap: () {
|
|
|
|
|
Navigator.push(
|
|
|
|
|
context,
|
|
|
|
|
MaterialPageRoute(
|
|
|
|
|
builder: (_) => const ScanFlow(),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: NavItem(
|
|
|
|
|
icon: Icons.history_rounded,
|
|
|
|
|
isActive: selectedIndex == 3,
|
2026-04-27 09:08:26 +00:00
|
|
|
onTap: () =>
|
|
|
|
|
ref.read(navIndexProvider.notifier).setIndex(3),
|
2026-04-09 06:47:03 +00:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: NavItem(
|
|
|
|
|
icon: Icons.person_rounded,
|
|
|
|
|
isActive: selectedIndex == 4,
|
2026-04-27 09:08:26 +00:00
|
|
|
onTap: () =>
|
|
|
|
|
ref.read(navIndexProvider.notifier).setIndex(4),
|
2026-04-09 06:47:03 +00:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
GlobalLoadingOverlay(isLoading: _isLoading),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|