Brings the Flutter app to parity with the recent Web App changes, which touched features the Phone App did not yet have — so this builds the Charging and Integrations subsystems, then applies the tab/fold structure. Charging (new nav destination): split into "Public chargers" (stylized discovery map + demo session + nearby public stations) and "Home chargers" (the real Anker Solix OCPP control card — serial refresh, connector/energy tiles, start/stop, current limit, password step-up on reset — plus the user's home charger list). Gated by the per-user control mode, degrading to a Settings hint when off. Settings: split into "Personal settings" (the existing account/appearance/ profile/security/privacy/danger sections) and "Integrations". The latter holds foldable Toyota and Anker Solix cards over the superadmin -> org -> user cascade: locked fields with "inherited from" notes, org-admin scope switch, enable toggle, save/test with health result, and Anker OCPP token provisioning. Both tabs stay mounted (IndexedStack) so in-flight edits survive a switch. Adds the integration + OCPP control endpoints to api.dart, the resolved IntegrationView/Scope/Field, IntegrationHealth and AnkerControl models, and charging.*/settings.tabs.*/nav.charging strings (en/pl/da) plus settings.integrations.* (en) — mirroring the Web App's own pl/da coverage, which leaves integrations and charger control untranslated as an English fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
2.1 KiB
Dart
70 lines
2.1 KiB
Dart
import "package:flutter/material.dart";
|
|
|
|
import "../i18n.dart";
|
|
import "../main.dart";
|
|
import "dashboard_screen.dart";
|
|
import "charging_screen.dart";
|
|
import "settings_screen.dart";
|
|
import "admin_users_screen.dart";
|
|
|
|
/// The app's root shell — a persistent bottom navigation bar (DriverVault mockup
|
|
/// layout) hosting the Garage, Charging, Settings and (for admins) Users sections
|
|
/// in an IndexedStack so each keeps its state as you switch tabs.
|
|
class RootShell extends StatefulWidget {
|
|
const RootShell({super.key});
|
|
@override
|
|
State<RootShell> createState() => _RootShellState();
|
|
}
|
|
|
|
class _RootShellState extends State<RootShell> {
|
|
int _index = 0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isAdmin = authService.user?.isAdmin == true;
|
|
|
|
final pages = <Widget>[
|
|
const DashboardScreen(),
|
|
const ChargingScreen(),
|
|
const SettingsScreen(),
|
|
if (isAdmin) const AdminUsersScreen(),
|
|
];
|
|
|
|
final destinations = <NavigationDestination>[
|
|
NavigationDestination(
|
|
icon: const Icon(Icons.grid_view_outlined),
|
|
selectedIcon: const Icon(Icons.grid_view_rounded),
|
|
label: t("nav.garage"),
|
|
),
|
|
NavigationDestination(
|
|
icon: const Icon(Icons.ev_station_outlined),
|
|
selectedIcon: const Icon(Icons.ev_station),
|
|
label: t("nav.charging"),
|
|
),
|
|
NavigationDestination(
|
|
icon: const Icon(Icons.settings_outlined),
|
|
selectedIcon: const Icon(Icons.settings),
|
|
label: t("nav.settings"),
|
|
),
|
|
if (isAdmin)
|
|
NavigationDestination(
|
|
icon: const Icon(Icons.group_outlined),
|
|
selectedIcon: const Icon(Icons.group),
|
|
label: t("nav.users"),
|
|
),
|
|
];
|
|
|
|
// Guard against the index falling out of range if admin status changes.
|
|
final index = _index.clamp(0, pages.length - 1);
|
|
|
|
return Scaffold(
|
|
body: IndexedStack(index: index, children: pages),
|
|
bottomNavigationBar: NavigationBar(
|
|
selectedIndex: index,
|
|
onDestinationSelected: (i) => setState(() => _index = i),
|
|
destinations: destinations,
|
|
),
|
|
);
|
|
}
|
|
}
|