From 97166868f142a83f9cf6c0da602b9e705c96c015 Mon Sep 17 00:00:00 2001 From: u221638 Date: Thu, 24 Oct 2024 10:29:21 +0200 Subject: [PATCH] feat: basic fahrbild header with static data (#79) --- das_client/.fvm/fvm_config.json | 4 - das_client/.fvmrc | 4 + .../test/navigation_test.dart | 7 +- das_client/ios/Podfile.lock | 2 +- das_client/ios/Runner/AppDelegate.swift | 2 +- das_client/lib/app.dart | 9 ++- das_client/lib/nav/app_router.dart | 24 ++++-- das_client/lib/nav/das_navigation_drawer.dart | 2 +- .../lib/pages/fahrbild/fahrbild_page.dart | 60 ++++++++++++++ .../lib/pages/fahrbild/widgets/fahrbild.dart | 17 ++++ .../fahrbild/widgets/header/button_area.dart | 25 ++++++ .../pages/fahrbild/widgets/header/header.dart | 78 +++++++++++++++++++ .../fahrbild/widgets/header/next_stop.dart | 17 ++++ .../widgets/header/punctuality_display.dart | 20 +++++ .../widgets/header/radio_channel.dart | 20 +++++ .../widgets/train_journey.dart} | 4 +- das_client/lib/pages/login/login_page.dart | 2 +- das_client/lib/pages/login/splash_page.dart | 2 +- .../train_selection_page.dart} | 21 +++-- .../widgets/train_selection.dart | 0 20 files changed, 289 insertions(+), 31 deletions(-) delete mode 100644 das_client/.fvm/fvm_config.json create mode 100644 das_client/.fvmrc create mode 100644 das_client/lib/pages/fahrbild/fahrbild_page.dart create mode 100644 das_client/lib/pages/fahrbild/widgets/fahrbild.dart create mode 100644 das_client/lib/pages/fahrbild/widgets/header/button_area.dart create mode 100644 das_client/lib/pages/fahrbild/widgets/header/header.dart create mode 100644 das_client/lib/pages/fahrbild/widgets/header/next_stop.dart create mode 100644 das_client/lib/pages/fahrbild/widgets/header/punctuality_display.dart create mode 100644 das_client/lib/pages/fahrbild/widgets/header/radio_channel.dart rename das_client/lib/pages/{fahrt/widgets/fahrbild.dart => fahrbild/widgets/train_journey.dart} (96%) rename das_client/lib/pages/{fahrt/fahrt_page.dart => train_selection/train_selection_page.dart} (72%) rename das_client/lib/pages/{fahrt => train_selection}/widgets/train_selection.dart (100%) diff --git a/das_client/.fvm/fvm_config.json b/das_client/.fvm/fvm_config.json deleted file mode 100644 index 8a383785..00000000 --- a/das_client/.fvm/fvm_config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "flutterSdkVersion": "3.24.3", - "flavors": {} -} \ No newline at end of file diff --git a/das_client/.fvmrc b/das_client/.fvmrc new file mode 100644 index 00000000..c62692b4 --- /dev/null +++ b/das_client/.fvmrc @@ -0,0 +1,4 @@ +{ + "flutter": "3.24.3", + "flavors": {} +} \ No newline at end of file diff --git a/das_client/integration_test/test/navigation_test.dart b/das_client/integration_test/test/navigation_test.dart index 59586dc7..e7db59e4 100644 --- a/das_client/integration_test/test/navigation_test.dart +++ b/das_client/integration_test/test/navigation_test.dart @@ -1,7 +1,7 @@ -import 'package:das_client/pages/fahrt/fahrt_page.dart'; import 'package:das_client/pages/links/links_page.dart'; import 'package:das_client/pages/profile/profile_page.dart'; import 'package:das_client/pages/settings/settings_page.dart'; +import 'package:das_client/pages/train_selection/train_selection_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -93,6 +93,7 @@ void main() { expect(find.byType(ProfilePage), findsOneWidget); }); + // TODO: testWidgets('test navigate to fahrbild', (tester) async { // Load app widget. await prepareAndStartApp(tester); @@ -118,8 +119,8 @@ void main() { await tapElement(tester, find.text(l10n.w_navigation_drawer_fahrtinfo_title)); - // Check on FahrtPage - expect(find.byType(FahrtPage), findsOneWidget); + // TODO: Handle TrainSelectionPage/FahrbildPage + expect(find.byType(TrainSelectionPage), findsOneWidget); }); }); } diff --git a/das_client/ios/Podfile.lock b/das_client/ios/Podfile.lock index e1ca177f..d9c08fbd 100644 --- a/das_client/ios/Podfile.lock +++ b/das_client/ios/Podfile.lock @@ -61,7 +61,7 @@ SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_appauth: 1ce438877bc111c5d8f42da47729909290624886 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 - integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 + integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 diff --git a/das_client/ios/Runner/AppDelegate.swift b/das_client/ios/Runner/AppDelegate.swift index 70693e4a..b6363034 100644 --- a/das_client/ios/Runner/AppDelegate.swift +++ b/das_client/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/das_client/lib/app.dart b/das_client/lib/app.dart index de28c47c..b33b724f 100644 --- a/das_client/lib/app.dart +++ b/das_client/lib/app.dart @@ -13,10 +13,11 @@ class App extends StatelessWidget { return MaterialApp.router( themeMode: ThemeMode.system, theme: SBBTheme.light( - baseStyle: SBBBaseStyle( - primaryColor: SBBColors.royal, - primaryColorDark: SBBColors.royal125, - )), + baseStyle: SBBBaseStyle( + primaryColor: SBBColors.royal, + primaryColorDark: SBBColors.royal125, + ), + ), //darkTheme: SBBTheme.dark(), localizationsDelegates: localizationDelegates, supportedLocales: supportedLocales, diff --git a/das_client/lib/nav/app_router.dart b/das_client/lib/nav/app_router.dart index cca1934e..703eca05 100644 --- a/das_client/lib/nav/app_router.dart +++ b/das_client/lib/nav/app_router.dart @@ -1,5 +1,6 @@ import 'package:auto_route/auto_route.dart'; -import 'package:das_client/pages/fahrt/fahrt_page.dart'; +import 'package:das_client/pages/train_selection/train_selection_page.dart'; +import 'package:das_client/pages/fahrbild/fahrbild_page.dart'; import 'package:das_client/pages/links/links_page.dart'; import 'package:das_client/pages/profile/profile_page.dart'; import 'package:das_client/pages/login/login_page.dart'; @@ -11,7 +12,15 @@ part 'app_router.gr.dart'; @AutoRouterConfig(replaceInRouteName: 'Page,Route') class AppRouter extends RootStackRouter { @override - List get routes => [_splash, _login, _fahrt, _links, _settings, _profile]; + List get routes => [ + _splash, + _login, + _trainSelection, + _fahrbild, + _links, + _settings, + _profile, + ]; @override get defaultRouteType => const RouteType.custom(); @@ -30,9 +39,14 @@ final _login = AutoRoute( page: LoginRoute.page, ); -final _fahrt = AutoRoute( - path: '/fahrt', - page: FahrtRoute.page, +final _trainSelection = AutoRoute( + path: '/train_selection', + page: TrainSelectionRoute.page, +); + +final _fahrbild = AutoRoute( + path: '/fahrbild', + page: FahrbildRoute.page, ); final _links = AutoRoute( diff --git a/das_client/lib/nav/das_navigation_drawer.dart b/das_client/lib/nav/das_navigation_drawer.dart index 6d110012..c1b01400 100644 --- a/das_client/lib/nav/das_navigation_drawer.dart +++ b/das_client/lib/nav/das_navigation_drawer.dart @@ -22,7 +22,7 @@ class DASNavigationDrawer extends StatelessWidget { context, icon: SBBIcons.route_circle_start_small, title: context.l10n.w_navigation_drawer_fahrtinfo_title, - route: const FahrtRoute(), + route: const FahrbildRoute(), ), _navigationTile( context, diff --git a/das_client/lib/pages/fahrbild/fahrbild_page.dart b/das_client/lib/pages/fahrbild/fahrbild_page.dart new file mode 100644 index 00000000..383d7692 --- /dev/null +++ b/das_client/lib/pages/fahrbild/fahrbild_page.dart @@ -0,0 +1,60 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:das_client/bloc/fahrbild_cubit.dart'; +import 'package:das_client/nav/app_router.dart'; +import 'package:das_client/nav/das_navigation_drawer.dart'; +import 'package:das_client/pages/fahrbild/widgets/fahrbild.dart'; +import 'package:design_system_flutter/design_system_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +// TODO: discuss general naming in DEV team +@RoutePage() +class FahrbildPage extends StatelessWidget { + const FahrbildPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: _appBar(context), + body: _body(context), + drawer: const DASNavigationDrawer(), + ); + } + + SBBHeader _appBar(BuildContext context) { + return SBBHeader( + title: 'Fahrbild', + // TODO: Workaround as otherwise the SBB logo is shown + actions: const [SizedBox.shrink()], + leadingWidget: Builder( + builder: (context) => IconButton( + icon: const Icon(SBBIcons.hamburger_menu_small), + onPressed: () => Scaffold.of(context).openDrawer(), + ), + ), + ); + } + + Widget _body(BuildContext context) { + return Column( + children: [ + Expanded(child: _content()), + ], + ); + } + + Widget _content() { + return BlocBuilder( + builder: (context, state) { + if (state is FahrbildLoadedState) { + return const Fahrbild(); + } else if (state is FahrbildLoadedState) { + // TODO: unsexy, as Listener doesn't use initial state. + context.router.replace(const TrainSelectionRoute()); + } + + return const Center(child: CircularProgressIndicator()); + }, + ); + } +} diff --git a/das_client/lib/pages/fahrbild/widgets/fahrbild.dart b/das_client/lib/pages/fahrbild/widgets/fahrbild.dart new file mode 100644 index 00000000..376148c4 --- /dev/null +++ b/das_client/lib/pages/fahrbild/widgets/fahrbild.dart @@ -0,0 +1,17 @@ +import 'package:das_client/pages/fahrbild/widgets/header/header.dart'; +import 'package:das_client/pages/fahrbild/widgets/train_journey.dart'; +import 'package:flutter/material.dart'; + +class Fahrbild extends StatelessWidget { + const Fahrbild({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Header(), + Expanded(child: TrainJourney()), + ], + ); + } +} diff --git a/das_client/lib/pages/fahrbild/widgets/header/button_area.dart b/das_client/lib/pages/fahrbild/widgets/header/button_area.dart new file mode 100644 index 00000000..aa2c1a92 --- /dev/null +++ b/das_client/lib/pages/fahrbild/widgets/header/button_area.dart @@ -0,0 +1,25 @@ +import 'package:design_system_flutter/design_system_flutter.dart'; +import 'package:flutter/material.dart'; + +class ButtonArea extends StatelessWidget { + const ButtonArea({super.key}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + SBBTertiaryButtonLarge(label: 'Button', onPressed: () {}), + SBBIconButtonLarge(icon: SBBIcons.tick_small, onPressed: () {}), + SBBIconButtonLarge(icon: SBBIcons.context_menu_small, onPressed: () {}), + ].withSpacing(8.0), + ); + } +} + +// extensions + +extension _Spacing on List { + withSpacing(double width) { + return expand((x) => [SizedBox(width: width), x]).skip(1).toList(); + } +} diff --git a/das_client/lib/pages/fahrbild/widgets/header/header.dart b/das_client/lib/pages/fahrbild/widgets/header/header.dart new file mode 100644 index 00000000..4346ba0f --- /dev/null +++ b/das_client/lib/pages/fahrbild/widgets/header/header.dart @@ -0,0 +1,78 @@ +import 'package:das_client/pages/fahrbild/widgets/header/button_area.dart'; +import 'package:das_client/pages/fahrbild/widgets/header/next_stop.dart'; +import 'package:das_client/pages/fahrbild/widgets/header/punctuality_display.dart'; +import 'package:das_client/pages/fahrbild/widgets/header/radio_channel.dart'; +import 'package:design_system_flutter/design_system_flutter.dart'; +import 'package:flutter/material.dart'; + +class Header extends StatelessWidget { + const Header({super.key}); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + _background(context), + _group(context), + ], + ); + } + + Widget _background(BuildContext context) { + final primary = Theme.of(context).colorScheme.secondary; + return Container( + color: primary, + height: 16.0, + ); + } + + Widget _group(BuildContext context) { + return SBBGroup( + margin: const EdgeInsetsDirectional.fromSTEB(8, 0, 8, 16), + padding: const EdgeInsets.all(16).copyWith(bottom: 8.0), + useShadow: true, + child: SizedBox( + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _topHeaderRow(), + _divider(), + _bottomHeaderRow(), + ], + ), + ), + ); + } + + Widget _bottomHeaderRow() { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 6.0), + child: Row( + children: [ + RadioChannel(), + SizedBox(width: 48.0), + NextStop(), + ], + ), + ); + } + + Widget _divider() { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(height: 1.0, color: SBBColors.cloud), + ); + } + + Widget _topHeaderRow() { + return const Row( + children: [ + Expanded( + child: PunctualityDisplay(), + ), + ButtonArea(), + ], + ); + } +} diff --git a/das_client/lib/pages/fahrbild/widgets/header/next_stop.dart b/das_client/lib/pages/fahrbild/widgets/header/next_stop.dart new file mode 100644 index 00000000..97a57618 --- /dev/null +++ b/das_client/lib/pages/fahrbild/widgets/header/next_stop.dart @@ -0,0 +1,17 @@ +import 'package:design_system_flutter/design_system_flutter.dart'; +import 'package:flutter/material.dart'; + +class NextStop extends StatelessWidget { + const NextStop({super.key}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + const Icon(SBBIcons.station_small), + const SizedBox(width: 8.0), + Text('Baden', style: SBBTextStyles.largeLight), + ], + ); + } +} diff --git a/das_client/lib/pages/fahrbild/widgets/header/punctuality_display.dart b/das_client/lib/pages/fahrbild/widgets/header/punctuality_display.dart new file mode 100644 index 00000000..2582bb16 --- /dev/null +++ b/das_client/lib/pages/fahrbild/widgets/header/punctuality_display.dart @@ -0,0 +1,20 @@ +import 'package:design_system_flutter/design_system_flutter.dart'; +import 'package:flutter/material.dart'; + +class PunctualityDisplay extends StatelessWidget { + const PunctualityDisplay({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Row( + children: [ + Text('05:43:00', style: SBBTextStyles.largeBold.copyWith(fontSize: 24.0)), + const SizedBox(width: 8.0), + Text('+00:01:30', style: SBBTextStyles.largeLight.copyWith(fontSize: 24.0)), + ], + ), + ); + } +} diff --git a/das_client/lib/pages/fahrbild/widgets/header/radio_channel.dart b/das_client/lib/pages/fahrbild/widgets/header/radio_channel.dart new file mode 100644 index 00000000..dd235798 --- /dev/null +++ b/das_client/lib/pages/fahrbild/widgets/header/radio_channel.dart @@ -0,0 +1,20 @@ +import 'package:design_system_flutter/design_system_flutter.dart'; +import 'package:flutter/material.dart'; + +class RadioChannel extends StatelessWidget { + const RadioChannel({super.key}); + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints(minWidth: 240.0), + child: Row( + children: [ + const Icon(SBBIcons.telephone_gsm_small), + const SizedBox(width: 8.0), + Text('1311', style: SBBTextStyles.largeLight), + ], + ), + ); + } +} diff --git a/das_client/lib/pages/fahrt/widgets/fahrbild.dart b/das_client/lib/pages/fahrbild/widgets/train_journey.dart similarity index 96% rename from das_client/lib/pages/fahrt/widgets/fahrbild.dart rename to das_client/lib/pages/fahrbild/widgets/train_journey.dart index 578c44d1..cca44747 100644 --- a/das_client/lib/pages/fahrt/widgets/fahrbild.dart +++ b/das_client/lib/pages/fahrbild/widgets/train_journey.dart @@ -7,8 +7,8 @@ import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; -class Fahrbild extends StatelessWidget { - const Fahrbild({super.key}); +class TrainJourney extends StatelessWidget { + const TrainJourney({super.key}); @override Widget build(BuildContext context) { diff --git a/das_client/lib/pages/login/login_page.dart b/das_client/lib/pages/login/login_page.dart index 4edf7156..3178f022 100644 --- a/das_client/lib/pages/login/login_page.dart +++ b/das_client/lib/pages/login/login_page.dart @@ -93,7 +93,7 @@ class _LoginPageState extends State { try { await authenticator.login(); if (context.mounted) { - context.router.replace(const FahrtRoute()); + context.router.replace(const TrainSelectionRoute()); } } catch (e) { Fimber.d('Login failed', ex: e); diff --git a/das_client/lib/pages/login/splash_page.dart b/das_client/lib/pages/login/splash_page.dart index 43bf873a..af44efa4 100644 --- a/das_client/lib/pages/login/splash_page.dart +++ b/das_client/lib/pages/login/splash_page.dart @@ -23,7 +23,7 @@ class SplashPage extends StatelessWidget { return BlocBuilder( builder: (context, state) { if (state is Authenticated) { - context.router.replace(const FahrtRoute()); + context.router.replace(const TrainSelectionRoute()); } else if (state is Unauthenticated) { context.router.replace(const LoginRoute()); } diff --git a/das_client/lib/pages/fahrt/fahrt_page.dart b/das_client/lib/pages/train_selection/train_selection_page.dart similarity index 72% rename from das_client/lib/pages/fahrt/fahrt_page.dart rename to das_client/lib/pages/train_selection/train_selection_page.dart index 087ab330..6c940e9f 100644 --- a/das_client/lib/pages/fahrt/fahrt_page.dart +++ b/das_client/lib/pages/train_selection/train_selection_page.dart @@ -4,28 +4,33 @@ import 'package:das_client/bloc/fahrbild_cubit.dart'; import 'package:das_client/i18n/src/build_context_x.dart'; import 'package:das_client/nav/app_router.dart'; import 'package:das_client/nav/das_navigation_drawer.dart'; -import 'package:das_client/pages/fahrt/widgets/fahrbild.dart'; -import 'package:das_client/pages/fahrt/widgets/train_selection.dart'; +import 'package:das_client/pages/train_selection/widgets/train_selection.dart'; import 'package:design_system_flutter/design_system_flutter.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @RoutePage() -class FahrtPage extends StatelessWidget { - const FahrtPage({super.key}); +class TrainSelectionPage extends StatelessWidget { + const TrainSelectionPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: _appBar(context), body: _body(context), - drawer: DASNavigationDrawer(), + drawer: const DASNavigationDrawer(), ); } SBBHeader _appBar(BuildContext context) { return SBBHeader( title: context.l10n.c_app_name, + leadingWidget: Builder( + builder: (context) => IconButton( + icon: const Icon(SBBIcons.hamburger_menu_small), + onPressed: () => Scaffold.of(context).openDrawer(), + ), + ), actions: [ IconButton( icon: const Icon(SBBIcons.exit_small), @@ -58,10 +63,10 @@ class FahrtPage extends StatelessWidget { if (state is SelectingFahrbildState) { return const TrainSelection(); } else if (state is FahrbildLoadedState) { - return const Fahrbild(); - } else { - return const Center(child: CircularProgressIndicator()); + // TODO: unsexy, as Listener doesn't use initial state. + context.router.replace(const FahrbildRoute()); } + return const Center(child: CircularProgressIndicator()); }, ), ); diff --git a/das_client/lib/pages/fahrt/widgets/train_selection.dart b/das_client/lib/pages/train_selection/widgets/train_selection.dart similarity index 100% rename from das_client/lib/pages/fahrt/widgets/train_selection.dart rename to das_client/lib/pages/train_selection/widgets/train_selection.dart