diff --git a/lib/components/cupertino_listtile.dart b/lib/components/cupertino_listtile.dart index 7e9ee9b..8ec41fe 100644 --- a/lib/components/cupertino_listtile.dart +++ b/lib/components/cupertino_listtile.dart @@ -5,39 +5,54 @@ class CupertinoListTile extends StatelessWidget { final Widget? title; final Widget? subtitle; final Widget? trailing; + final void Function()? onTap; - const CupertinoListTile({ Key? key, this.leading, this.title, this.subtitle, this.trailing, }) : super(key: key); + const CupertinoListTile({ super.key, this.leading, this.title, this.subtitle, this.trailing, this.onTap, }); @override Widget build(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.max, - children: [ - if (leading != null) - leading!, - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (title != null) - title!, - if (subtitle != null) - CupertinoTheme( - data: CupertinoTheme.of(context).copyWith( - textTheme: CupertinoTextThemeData( - textStyle: TextStyle( - fontSize: CupertinoTheme.of(context).textTheme.textStyle.fontSize! - 2, - ) - ) - ), - child: subtitle!, - ), - ], + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + if (leading != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: leading!, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (title != null) + title!, + if (subtitle != null) + CupertinoTheme( + data: CupertinoTheme.of(context).copyWith( + textTheme: CupertinoTextThemeData( + textStyle: TextStyle( + fontSize: CupertinoTheme.of(context).textTheme.textStyle.fontSize! - 2, + ) + ) + ), + child: subtitle!, + ), + ], + ), + ), ), - ), - if (trailing != null) - trailing!, - ], + if (trailing != null) + Padding( + padding: const EdgeInsets.all(8.0), + child: trailing!, + ), + ], + ), ); } } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 94948d3..dc52227 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/models.dart'; import 'package:info_tren/pages/about/about_page.dart'; import 'package:info_tren/pages/main/main_page.dart'; +import 'package:info_tren/pages/settings/setings_page.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info.dart'; @@ -36,6 +37,9 @@ Map get routes => { AboutPage.routeName: (context) { return const AboutPage(); }, + SettingsPage.routeName: (context) { + return const SettingsPage(); + }, SelectTrainPage.routeName: (context) { return const SelectTrainPage(); }, diff --git a/lib/models/ui_design.dart b/lib/models/ui_design.dart index 91b1c4c..eb9fd3a 100644 --- a/lib/models/ui_design.dart +++ b/lib/models/ui_design.dart @@ -4,6 +4,14 @@ enum UiDesign { FLUENT, } +extension UIName on UiDesign { + String get userInterfaceName => (const { + UiDesign.MATERIAL: 'Material', + UiDesign.CUPERTINO: 'Cupertino', + UiDesign.FLUENT: 'Fluent', + })[this]!; +} + class UnmatchedUiDesignException implements Exception { final UiDesign uiDesign; diff --git a/lib/pages/main/main_page.dart b/lib/pages/main/main_page.dart index e175e9c..4a368cf 100644 --- a/lib/pages/main/main_page.dart +++ b/lib/pages/main/main_page.dart @@ -5,6 +5,7 @@ import 'package:info_tren/pages/about/about_page.dart'; import 'package:info_tren/pages/main/main_page_cupertino.dart'; import 'package:info_tren/pages/main/main_page_fluent.dart'; import 'package:info_tren/pages/main/main_page_material.dart'; +import 'package:info_tren/pages/settings/setings_page.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart'; import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; import 'package:info_tren/providers.dart'; @@ -36,6 +37,12 @@ abstract class MainPageShared extends StatelessWidget { const MainPageShared({super.key}); List get popupMenu => [ + MainPageAction( + name: 'Setări', + action: (context) { + Navigator.of(context).pushNamed(SettingsPage.routeName); + }, + ), MainPageAction( name: 'Despre aplicație', action: (context) { @@ -85,5 +92,9 @@ class MainPageAction { final String? description; final void Function(BuildContext context)? action; - MainPageAction({required this.name, this.action, this.description}); + MainPageAction({ + required this.name, + this.action, + this.description, + }); } diff --git a/lib/pages/main/main_page_fluent.dart b/lib/pages/main/main_page_fluent.dart index 2279c96..f325b7e 100644 --- a/lib/pages/main/main_page_fluent.dart +++ b/lib/pages/main/main_page_fluent.dart @@ -10,6 +10,22 @@ class MainPageFluent extends MainPageShared { appBar: NavigationAppBar( automaticallyImplyLeading: false, title: Text(pageTitle), + actions: Row( + mainAxisSize: MainAxisSize.min, + children: popupMenu.map((i) => Center( + child: SizedBox( + width: 32, + height: 32, + child: IconButton( + icon: Icon({ + 'Setări': FluentIcons.settings, + 'Despre aplicație': FluentIcons.info, + }[i.name]), + onPressed: i.action != null ? () => i.action!(context) : null, + ), + ), + )).toList(), + ), // centerTitle: true, // actions: [ // PopupMenuButton( diff --git a/lib/pages/settings/setings_page.dart b/lib/pages/settings/setings_page.dart new file mode 100644 index 0000000..f1c5c1d --- /dev/null +++ b/lib/pages/settings/setings_page.dart @@ -0,0 +1,37 @@ +import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:info_tren/models.dart'; +import 'package:info_tren/pages/settings/settings_page_cupertino.dart'; +import 'package:info_tren/pages/settings/settings_page_fluent.dart'; +import 'package:info_tren/pages/settings/settings_page_material.dart'; +import 'package:info_tren/providers.dart'; + +class SettingsPage extends ConsumerWidget { + const SettingsPage({super.key,}); + + static const String routeName = '/settings'; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final uiDesign = ref.watch(uiDesignProvider); + + switch (uiDesign) { + case UiDesign.MATERIAL: + return const SettingsPageMaterial(); + case UiDesign.CUPERTINO: + return const SettingsPageCupertino(); + case UiDesign.FLUENT: + return const SettingsPageFluent(); + default: + throw UnmatchedUiDesignException(uiDesign); + } + } +} + +abstract class SettingsPageShared extends StatelessWidget { + final String pageTitle = 'Setări'; + final String appearanceTitle = 'Aspect'; + + const SettingsPageShared({super.key}); + +} diff --git a/lib/pages/settings/settings_page_cupertino.dart b/lib/pages/settings/settings_page_cupertino.dart new file mode 100644 index 0000000..3fadb6c --- /dev/null +++ b/lib/pages/settings/settings_page_cupertino.dart @@ -0,0 +1,77 @@ +import 'package:flutter/cupertino.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:info_tren/components/cupertino_listtile.dart'; +import 'package:info_tren/models.dart'; +import 'package:info_tren/pages/settings/setings_page.dart'; +import 'package:info_tren/providers.dart'; + +class SettingsPageCupertino extends SettingsPageShared { + const SettingsPageCupertino({super.key,}); + + Future singleChoice({required BuildContext context, required List choices, required String Function(T) labelBuilder, String? title}) async { + return await showCupertinoModalPopup( + context: context, + builder: (context) { + return CupertinoActionSheet( + title: title != null ? Text(title) : null, + actions: choices.map((c) => CupertinoActionSheetAction( + onPressed: () { + Navigator.of(context).pop(c); + }, + child: Text(labelBuilder(c)), + )).toList(), + cancelButton: CupertinoActionSheetAction( + child: const Text('Anulare'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + previousPageTitle: 'Info Tren', + middle: Text(pageTitle), + ), + child: Builder( + builder: (context) { + final mq = MediaQuery.of(context); + return SingleChildScrollView( + child: Column( + children: [ + SizedBox( + height: mq.padding.top, + ), + Consumer( + builder: (context, ref, _) { + final currentUiDesign = ref.watch(uiDesignProvider); + return CupertinoListTile( + title: Text(appearanceTitle), + trailing: Text(currentUiDesign.userInterfaceName), + onTap: () async { + final choice = await singleChoice( + context: context, + choices: UiDesign.values, + labelBuilder: (UiDesign ud) => ud.userInterfaceName, + title: appearanceTitle, + ); + if (choice != null) { + ref.read(uiDesignProvider.notifier).set(choice); + } + }, + ); + }, + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/pages/settings/settings_page_fluent.dart b/lib/pages/settings/settings_page_fluent.dart new file mode 100644 index 0000000..30cfe36 --- /dev/null +++ b/lib/pages/settings/settings_page_fluent.dart @@ -0,0 +1,42 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:info_tren/models.dart'; +import 'package:info_tren/pages/settings/setings_page.dart'; +import 'package:info_tren/providers.dart'; + +class SettingsPageFluent extends SettingsPageShared { + const SettingsPageFluent({super.key,}); + + @override + Widget build(BuildContext context) { + return NavigationView( + appBar: NavigationAppBar( + title: Text(pageTitle), + ), + content: SingleChildScrollView( + child: Column( + children: [ + Consumer( + builder: (context, ref, _) { + final currentUiDesign = ref.watch(uiDesignProvider); + return ListTile( + title: Text(appearanceTitle), + trailing: ComboBox( + items: UiDesign.values.map((d) => ComboBoxItem( + value: d, + child: Text(d.userInterfaceName), + )).toList(), + value: currentUiDesign, + onChanged: (newUiDesign) { + ref.read(uiDesignProvider.notifier).set(newUiDesign); + }, + ), + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/settings/settings_page_material.dart b/lib/pages/settings/settings_page_material.dart new file mode 100644 index 0000000..470b65f --- /dev/null +++ b/lib/pages/settings/settings_page_material.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:info_tren/models.dart'; +import 'package:info_tren/pages/settings/setings_page.dart'; +import 'package:info_tren/providers.dart'; + +class SettingsPageMaterial extends SettingsPageShared { + const SettingsPageMaterial({super.key,}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(pageTitle), + centerTitle: true, + ), + body: SingleChildScrollView( + child: Column( + children: [ + Consumer( + builder: (context, ref, _) { + final currentUiDesign = ref.watch(uiDesignProvider); + return ListTile( + title: Text(appearanceTitle), + trailing: DropdownButton( + items: UiDesign.values.map((d) => DropdownMenuItem( + value: d, + child: Text(d.userInterfaceName), + )).toList(), + value: currentUiDesign, + onChanged: (newUiDesign) { + ref.read(uiDesignProvider.notifier).set(newUiDesign); + }, + ), + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/providers.dart b/lib/providers.dart index 001b8ee..fd1da57 100644 --- a/lib/providers.dart +++ b/lib/providers.dart @@ -13,22 +13,30 @@ final sharedPreferencesProvider = Provider( (_) => throw UnimplementedError('Please override in ProviderScope'), ); -final uiDesignProvider = Provider((ref) { - final sharedPreferences = ref.watch(sharedPreferencesProvider); - final stored = sharedPreferences.getString('uiDesign'); - final design = UiDesign.values.where((element) => element.name == stored).firstOrNull ?? defaultUiDesign; - return design; -}); -Future setUiDesign(Ref ref, UiDesign? design) async { - final sharedPreferences = ref.watch(sharedPreferencesProvider); - if (design != null) { - await sharedPreferences.setString('uiDesign', design.name); +class UiDesignNotifier extends StateNotifier { + final SharedPreferences sharedPreferences; + + UiDesignNotifier({required this.sharedPreferences,}) : super(UiDesign.MATERIAL) { + final stored = sharedPreferences.getString('uiDesign'); + final design = UiDesign.values.where((element) => element.name == stored).firstOrNull ?? defaultUiDesign; + state = design; } - else { - await sharedPreferences.remove('uiDesign'); + + void set(UiDesign? design) async { + if (design != null) { + await sharedPreferences.setString('uiDesign', design.name); + } + else { + await sharedPreferences.remove('uiDesign'); + } + state = design ?? defaultUiDesign; } - ref.invalidate(uiDesignProvider); } +final uiDesignProvider = StateNotifierProvider( + (ref) => UiDesignNotifier( + sharedPreferences: ref.watch(sharedPreferencesProvider), + ), +); final trainInfoArgumentsProvider = Provider( (_) => throw UnimplementedError('Please override in ProviderScope'),