feat: implement AI export service and establish project navigation and theming infrastructure + acces internet
Build & Release Android APK / build-apk (push) Successful in 23m34s
Build & Release Android APK / build-apk (push) Successful in 23m34s
This commit is contained in:
+121
-38
@@ -1,3 +1,4 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'features/home/home_screen.dart';
|
||||
import 'features/history/history_screen.dart';
|
||||
@@ -11,8 +12,7 @@ const int mainTabHistory = 1;
|
||||
const int mainTabStats = 2;
|
||||
const int mainTabGarage = 3;
|
||||
|
||||
/// Clé du holder : permet de piloter l'onglet actif depuis n'importe quel
|
||||
/// écran (ex. fin de session -> onglet Stats).
|
||||
/// Clé du holder : permet de piloter l'onglet actif depuis n'importe quel écran.
|
||||
final GlobalKey<State<MainNavigationHolder>> mainNavKey =
|
||||
GlobalKey<State<MainNavigationHolder>>();
|
||||
|
||||
@@ -32,9 +32,6 @@ class MainNavigationHolder extends StatefulWidget {
|
||||
class _MainNavigationHolderState extends State<MainNavigationHolder> {
|
||||
int _selectedIndex = 0;
|
||||
|
||||
// Incrémentés à chaque ouverture de l'onglet correspondant pour forcer le
|
||||
// rechargement (les écrans sont gardés vivants par l'IndexedStack et ne se
|
||||
// rafraîchissent pas seuls).
|
||||
int _statsTick = 0;
|
||||
int _historyTick = 0;
|
||||
int _homeTick = 0;
|
||||
@@ -53,57 +50,143 @@ class _MainNavigationHolderState extends State<MainNavigationHolder> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
final screens = [
|
||||
HomeScreen(refreshTick: _homeTick),
|
||||
HistoryScreen(refreshTick: _historyTick),
|
||||
StatisticsScreen(refreshTick: _statsTick),
|
||||
WeaponListScreen(refreshTick: _garageTick),
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
extendBody: true,
|
||||
body: IndexedStack(
|
||||
index: _selectedIndex,
|
||||
children: screens,
|
||||
),
|
||||
bottomNavigationBar: Container(
|
||||
bottomNavigationBar: _buildFloatingGlassDock(isDark),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFloatingGlassDock(bool isDark) {
|
||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
||||
final navItems = [
|
||||
(Icons.radar_outlined, Icons.radar, 'Accueil'),
|
||||
(Icons.history_outlined, Icons.history_rounded, 'Historique'),
|
||||
(Icons.insights_outlined, Icons.insights_rounded, 'Stats'),
|
||||
(Icons.shield_outlined, Icons.shield, 'Armurerie'),
|
||||
];
|
||||
|
||||
return SafeArea(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 12),
|
||||
height: 68,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, -2),
|
||||
color: Colors.black.withValues(alpha: isDark ? 0.45 : 0.12),
|
||||
blurRadius: 24,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
if (isDark)
|
||||
BoxShadow(
|
||||
color: primaryColor.withValues(alpha: 0.12),
|
||||
blurRadius: 16,
|
||||
spreadRadius: -4,
|
||||
offset: const Offset(0, -2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: BottomNavigationBar(
|
||||
currentIndex: _selectedIndex,
|
||||
onTap: selectTab,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
backgroundColor: Theme.of(context).cardColor,
|
||||
selectedItemColor: AppTheme.primaryColor,
|
||||
unselectedItemColor: Colors.grey,
|
||||
showUnselectedLabels: true,
|
||||
items: const [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.home_outlined),
|
||||
activeIcon: Icon(Icons.home),
|
||||
label: 'Accueil',
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 16, sigmaY: 16),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark
|
||||
? const Color(0xFF101722).withValues(alpha: 0.78)
|
||||
: Colors.white.withValues(alpha: 0.85),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? Colors.white.withValues(alpha: 0.14)
|
||||
: Colors.black.withValues(alpha: 0.08),
|
||||
width: 1.2,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: List.generate(navItems.length, (index) {
|
||||
final item = navItems[index];
|
||||
final isSelected = _selectedIndex == index;
|
||||
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => selectTab(index),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOutCubic,
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? primaryColor.withValues(
|
||||
alpha: isDark ? 0.22 : 0.15,
|
||||
)
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: isSelected
|
||||
? Border.all(
|
||||
color: primaryColor.withValues(
|
||||
alpha: isDark ? 0.45 : 0.35,
|
||||
),
|
||||
width: 1,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
isSelected ? item.$2 : item.$1,
|
||||
size: 22,
|
||||
color: isSelected
|
||||
? primaryColor
|
||||
: (isDark
|
||||
? AppTheme.darkTextSecondary
|
||||
: AppTheme.lightTextSecondary),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
item.$3,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: isSelected
|
||||
? FontWeight.w700
|
||||
: FontWeight.w500,
|
||||
color: isSelected
|
||||
? primaryColor
|
||||
: (isDark
|
||||
? AppTheme.darkTextSecondary
|
||||
: AppTheme.lightTextSecondary),
|
||||
letterSpacing: 0.2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.history_outlined),
|
||||
activeIcon: Icon(Icons.history),
|
||||
label: 'Historique',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.analytics_outlined),
|
||||
activeIcon: Icon(Icons.analytics),
|
||||
label: 'Stats',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.shield_outlined),
|
||||
activeIcon: Icon(Icons.shield),
|
||||
label: 'Armurerie',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user