diff --git a/lib/components/loading/loading.dart b/lib/components/loading/loading.dart index 30e7daa..a070088 100644 --- a/lib/components/loading/loading.dart +++ b/lib/components/loading/loading.dart @@ -1,19 +1,19 @@ import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/components/loading/loading_cupertino.dart'; import 'package:info_tren/components/loading/loading_material.dart'; import 'package:info_tren/models.dart'; -import 'package:info_tren/utils/default_ui_design.dart'; +import 'package:info_tren/providers.dart'; -class Loading extends StatelessWidget { +class Loading extends ConsumerWidget { static const defaultText = 'Loading...'; - final UiDesign? uiDesign; final String? text; - const Loading({ Key? key, this.text, this.uiDesign }) : super(key: key); + const Loading({ super.key, this.text, }); @override - Widget build(BuildContext context) { - final uiDesign = this.uiDesign ?? defaultUiDesign; + Widget build(BuildContext context, WidgetRef ref) { + final uiDesign = ref.watch(uiDesignProvider); switch (uiDesign) { case UiDesign.MATERIAL: return LoadingMaterial(text: text ?? defaultText,); diff --git a/lib/components/refresh_future_builder.dart b/lib/components/refresh_future_builder.dart index f6b8438..53ab89c 100644 --- a/lib/components/refresh_future_builder.dart +++ b/lib/components/refresh_future_builder.dart @@ -1,4 +1,5 @@ import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; class RefreshFutureBuilder extends StatefulWidget { final Future Function()? futureCreator; @@ -134,3 +135,40 @@ enum RefreshFutureBuilderState { refreshing, refreshError, } + +class RefreshFutureBuilderProviderAdapter extends ConsumerWidget { + final Provider> futureProvider; + final Widget Function(BuildContext context, Future Function() refresh, Future Function(Future Function()) replaceFuture, RefreshFutureBuilderSnapshot snapshot) builder; + + const RefreshFutureBuilderProviderAdapter({required this.futureProvider, required this.builder, super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final value = ref.watch(futureProvider); + + return builder( + context, + () async { + ref.invalidate(futureProvider); + }, + (_) => throw UnimplementedError('Cannot replace the future when adapting a FutureProvider'), + value.when( + data: (data) => value.isLoading || value.isRefreshing + ? RefreshFutureBuilderSnapshot.refresh(data) + : RefreshFutureBuilderSnapshot.withData(data), + error: (error, st) => value.isLoading || value.isRefreshing + ? RefreshFutureBuilderSnapshot.refreshError(value.value, value.error, value.stackTrace) + : RefreshFutureBuilderSnapshot.withError(error, st), + loading: () { + if (value.hasValue) { + return RefreshFutureBuilderSnapshot.refresh(value.value, value.error, value.stackTrace); + } + else if (value.hasError) { + return RefreshFutureBuilderSnapshot.refreshError(value.value, value.error, value.stackTrace); + } + return const RefreshFutureBuilderSnapshot.waiting(); + }, + ), + ); + } +} diff --git a/lib/components/select_train_suggestions/select_train_suggestions.dart b/lib/components/select_train_suggestions/select_train_suggestions.dart index ae787f5..9cf6105 100644 --- a/lib/components/select_train_suggestions/select_train_suggestions.dart +++ b/lib/components/select_train_suggestions/select_train_suggestions.dart @@ -1,50 +1,70 @@ import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_cupertino.dart'; import 'package:info_tren/components/select_train_suggestions/select_train_suggestions_material.dart'; import 'package:info_tren/models.dart'; +import 'package:info_tren/providers.dart'; import 'package:info_tren/utils/default_ui_design.dart'; -class SelectTrainSuggestions extends StatefulWidget { - final UiDesign? uiDesign; +class SelectTrainSuggestions extends ConsumerWidget { final List choices; final String? currentInput; final void Function(String trainNumber) onTrainSelected; - const SelectTrainSuggestions({Key? key, required this.uiDesign, required this.choices, this.currentInput, required this.onTrainSelected }) : super(key: key); - + const SelectTrainSuggestions({required this.choices, this.currentInput, required this.onTrainSelected, super.key, }); + @override - SelectTrainSuggestionsState createState() { - final uiDesign = this.uiDesign ?? defaultUiDesign; + Widget build(BuildContext context, WidgetRef ref) { + final uiDesign = ref.watch(uiDesignProvider); + switch(uiDesign) { case UiDesign.MATERIAL: - return SelectTrainSuggestionsStateMaterial(); + return SelectTrainSuggestionsMaterial( + choices: choices, + onTrainSelected: onTrainSelected, + currentInput: currentInput, + ); case UiDesign.CUPERTINO: - return SelectTrainSuggestionsStateCupertino(); + return SelectTrainSuggestionsCupertino( + choices: choices, + onTrainSelected: onTrainSelected, + currentInput: currentInput, + ); default: throw UnmatchedUiDesignException(uiDesign); } } } -abstract class SelectTrainSuggestionsState extends State { - String getUseCurrentInputWidgetText(String currentInput) => 'Caută trenul cu numărul ${widget.currentInput}'; +abstract class SelectTrainSuggestionsShared extends StatelessWidget { + String getUseCurrentInputWidgetText(String currentInput) => 'Caută trenul cu numărul $currentInput'; Widget getUseCurrentInputWidget(String currentInput, void Function(String) onTrainSelected); + final List choices; + final String? currentInput; + final void Function(String trainNumber) onTrainSelected; + + const SelectTrainSuggestionsShared({ + required this.choices, + this.currentInput, + required this.onTrainSelected, + super.key, + }); + @override Widget build(BuildContext context) { - var slivers = widget.choices.map((c) => c.company).toSet().map((operator) => OperatorAutocompleteSliver( - uiDesign: widget.uiDesign, + var slivers = choices.map((c) => c.company).toSet().map((operator) => OperatorAutocompleteSliver( operatorName: operator, - trains: widget.choices.where((c) => c.company == operator).toList(), - onTrainSelected: widget.onTrainSelected, + trains: choices.where((c) => c.company == operator).toList(), + onTrainSelected: onTrainSelected, )).toList(); return CustomScrollView( slivers: [ ...slivers, SliverToBoxAdapter( - child: widget.currentInput != null && int.tryParse(widget.currentInput!) != null ? getUseCurrentInputWidget(widget.currentInput!, widget.onTrainSelected) : Container(), + child: currentInput != null && int.tryParse(currentInput!) != null ? getUseCurrentInputWidget(currentInput!, onTrainSelected) : Container(), ), SliverToBoxAdapter( child: Container( @@ -56,16 +76,19 @@ abstract class SelectTrainSuggestionsState extends State } } -class OperatorAutocompleteSliver extends StatelessWidget { - final UiDesign? uiDesign; +class OperatorAutocompleteSliver extends ConsumerWidget { final String operatorName; final List trains; final void Function(String) onTrainSelected; - const OperatorAutocompleteSliver({ Key? key, required this.uiDesign, required this.operatorName, required this.trains, required this.onTrainSelected }) : super(key: key); + const OperatorAutocompleteSliver({ + super.key, + required this.operatorName, + required this.trains, + required this.onTrainSelected, + }); - Widget mapTrainToItem(TrainsResult train) { - final uiDesign = this.uiDesign ?? defaultUiDesign; + Widget mapTrainToItem(TrainsResult train, UiDesign uiDesign) { switch (uiDesign) { case UiDesign.MATERIAL: return OperatorAutocompleteTileMaterial( @@ -85,7 +108,8 @@ class OperatorAutocompleteSliver extends StatelessWidget { } @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final uiDesign = ref.watch(uiDesignProvider); if (trains.isEmpty) { return SliverToBoxAdapter(child: Container(),); } @@ -93,14 +117,14 @@ class OperatorAutocompleteSliver extends StatelessWidget { return SliverPrototypeExtentList( prototypeItem: Column( children: [ - mapTrainToItem(const TrainsResult(company: 'Company', number: '123', rank: 'R')), + mapTrainToItem(const TrainsResult(company: 'Company', number: '123', rank: 'R'), uiDesign), ], ), delegate: SliverChildBuilderDelegate( (context, index) { return Column( children: [ - mapTrainToItem(trains[index]), + mapTrainToItem(trains[index], uiDesign), ], ); }, diff --git a/lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart b/lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart index 80071bd..fa87f65 100644 --- a/lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart +++ b/lib/components/select_train_suggestions/select_train_suggestions_cupertino.dart @@ -3,7 +3,14 @@ import 'package:info_tren/components/cupertino_divider.dart'; import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; import 'package:info_tren/models.dart'; -class SelectTrainSuggestionsStateCupertino extends SelectTrainSuggestionsState { +class SelectTrainSuggestionsCupertino extends SelectTrainSuggestionsShared { + const SelectTrainSuggestionsCupertino({ + super.key, + required super.choices, + required super.onTrainSelected, + super.currentInput, + }); + @override Widget getUseCurrentInputWidget(String currentInput, void Function(String) onTrainSelected) { return Column( diff --git a/lib/components/select_train_suggestions/select_train_suggestions_material.dart b/lib/components/select_train_suggestions/select_train_suggestions_material.dart index a1f9a5d..4026e79 100644 --- a/lib/components/select_train_suggestions/select_train_suggestions_material.dart +++ b/lib/components/select_train_suggestions/select_train_suggestions_material.dart @@ -2,7 +2,14 @@ import 'package:flutter/material.dart'; import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; import 'package:info_tren/models.dart'; -class SelectTrainSuggestionsStateMaterial extends SelectTrainSuggestionsState { +class SelectTrainSuggestionsMaterial extends SelectTrainSuggestionsShared { + const SelectTrainSuggestionsMaterial({ + super.key, + required super.choices, + required super.onTrainSelected, + super.currentInput, + }); + @override Widget getUseCurrentInputWidget(String currentInput, void Function(String) onTrainSelected) { return Column( diff --git a/lib/main.dart b/lib/main.dart index dbf92e3..c06fa9c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,54 +1,55 @@ - import 'package:flutter/material.dart'; -import 'package:info_tren/models.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/pages/about/about_page.dart'; import 'package:info_tren/pages/main/main_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'; import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; +import 'package:info_tren/providers.dart'; +import 'package:shared_preferences/shared_preferences.dart'; - - -void main() { +void main() async { + final sharedPreferences = await SharedPreferences.getInstance(); runApp( - const StartPoint(), + ProviderScope( + overrides: [ + sharedPreferencesProvider.overrideWithValue(sharedPreferences), + ], + child: const StartPoint(), + ), ); } -Map routesByUiDesign(UiDesign uiDesign) => { +Map get routes => { Navigator.defaultRouteName: (context) { - return MainPage( - uiDesign: uiDesign, - ); + return const MainPage(); }, AboutPage.routeName: (context) { - return AboutPage( - uiDesign: uiDesign, - ); + return const AboutPage(); }, SelectTrainPage.routeName: (context) { - return SelectTrainPage( - uiDesign: uiDesign, - ); + return const SelectTrainPage(); }, TrainInfo.routeName: (context) { final args = ModalRoute.of(context)!.settings.arguments as TrainInfoArguments; - return TrainInfo( - trainNumber: args.trainNumber, - date: args.date, - uiDesign: uiDesign, + return ProviderScope( + overrides: [ + trainInfoArgumentsProvider.overrideWithValue(args), + ], + child: const TrainInfo(), ); }, SelectStationPage.routeName: (context) { - return SelectStationPage( - uiDesign: uiDesign, - ); + return const SelectStationPage(); }, ViewStationPage.routeName: (context) { - return ViewStationPage( - stationName: ModalRoute.of(context)!.settings.arguments as String, - uiDesign: uiDesign, + final args = ModalRoute.of(context)!.settings.arguments as ViewStationArguments; + return ProviderScope( + overrides: [ + viewStationArgumentsProvider.overrideWithValue(args), + ], + child: const ViewStationPage(), ); }, }; @@ -93,7 +94,7 @@ class StartPoint extends StatelessWidget { useMaterial3: true, // fontFamily: 'Atkinson Hyperlegible', ), - routes: routesByUiDesign(UiDesign.MATERIAL), + routes: routes, ); // } } diff --git a/lib/pages/about/about_page.dart b/lib/pages/about/about_page.dart index cee42bd..c5835bd 100644 --- a/lib/pages/about/about_page.dart +++ b/lib/pages/about/about_page.dart @@ -1,34 +1,37 @@ import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/api/releases.dart'; import 'package:info_tren/models.dart'; import 'package:info_tren/pages/about/about_page_cupertino.dart'; import 'package:info_tren/pages/about/about_page_material.dart'; -import 'package:info_tren/utils/default_ui_design.dart'; +import 'package:info_tren/providers.dart'; import 'package:package_info_plus/package_info_plus.dart'; -class AboutPage extends StatefulWidget { - final UiDesign? uiDesign; - - const AboutPage({Key? key, this.uiDesign}) : super(key: key); +class AboutPage extends ConsumerWidget { + const AboutPage({super.key}); static String routeName = '/about'; @override - State createState() { - final uiDesign = this.uiDesign ?? defaultUiDesign; - + Widget build(BuildContext context, WidgetRef ref) { + final uiDesign = ref.watch(uiDesignProvider); + switch (uiDesign) { case UiDesign.MATERIAL: - return AboutPageStateMaterial(); + return const AboutPageMaterial(); case UiDesign.CUPERTINO: - return AboutPageStateCupertino(); + return const AboutPageCupertino(); default: throw UnmatchedUiDesignException(uiDesign); } } } -abstract class AboutPageState extends State { +abstract class AboutPageShared extends StatefulWidget { + const AboutPageShared({super.key}); +} + +abstract class AboutPageState extends State { static const String download = String.fromEnvironment('DOWNLOAD'); final String pageTitle = 'Despre aplicație'; diff --git a/lib/pages/about/about_page_cupertino.dart b/lib/pages/about/about_page_cupertino.dart index 37f37cf..6c25f6b 100644 --- a/lib/pages/about/about_page_cupertino.dart +++ b/lib/pages/about/about_page_cupertino.dart @@ -3,6 +3,13 @@ import 'package:info_tren/components/cupertino_divider.dart'; import 'package:info_tren/pages/about/about_page.dart'; import 'package:url_launcher/url_launcher.dart'; +class AboutPageCupertino extends StatefulWidget { + const AboutPageCupertino({super.key}); + + @override + State createState() => AboutPageStateCupertino(); +} + class AboutPageStateCupertino extends AboutPageState { @override Widget build(BuildContext context) { diff --git a/lib/pages/about/about_page_material.dart b/lib/pages/about/about_page_material.dart index 579c01e..94d3847 100644 --- a/lib/pages/about/about_page_material.dart +++ b/lib/pages/about/about_page_material.dart @@ -3,6 +3,13 @@ import 'package:flutter/services.dart'; import 'package:info_tren/pages/about/about_page.dart'; import 'package:url_launcher/url_launcher.dart'; +class AboutPageMaterial extends StatefulWidget { + const AboutPageMaterial({super.key}); + + @override + State createState() => AboutPageStateMaterial(); +} + class AboutPageStateMaterial extends AboutPageState { @override Widget build(BuildContext context) { diff --git a/lib/pages/main/main_page.dart b/lib/pages/main/main_page.dart index 0c63ce1..9b1b983 100644 --- a/lib/pages/main/main_page.dart +++ b/lib/pages/main/main_page.dart @@ -1,20 +1,19 @@ import 'package:flutter/widgets.dart'; +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_cupertino.dart'; import 'package:info_tren/pages/main/main_page_material.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/utils/default_ui_design.dart'; +import 'package:info_tren/providers.dart'; -class MainPage extends StatelessWidget { - final UiDesign? uiDesign; - - const MainPage({ Key? key, this.uiDesign }) : super(key: key); +class MainPage extends ConsumerWidget { + const MainPage({super.key,}); @override - Widget build(BuildContext context) { - final uiDesign = this.uiDesign ?? defaultUiDesign; + Widget build(BuildContext context, WidgetRef ref) { + final uiDesign = ref.watch(uiDesignProvider); switch (uiDesign) { case UiDesign.MATERIAL: diff --git a/lib/pages/station_arrdep_page/select_station/select_station.dart b/lib/pages/station_arrdep_page/select_station/select_station.dart index 826a844..074ad3c 100644 --- a/lib/pages/station_arrdep_page/select_station/select_station.dart +++ b/lib/pages/station_arrdep_page/select_station/select_station.dart @@ -1,33 +1,36 @@ import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/models.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_cupertino.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station_material.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; -import 'package:info_tren/utils/default_ui_design.dart'; +import 'package:info_tren/providers.dart'; import 'package:info_tren/api/stations.dart' as api_stations; -class SelectStationPage extends StatefulWidget { - final UiDesign? uiDesign; - - const SelectStationPage({ Key? key, this.uiDesign }) : super(key: key); +class SelectStationPage extends ConsumerWidget { + const SelectStationPage({ super.key }); static String routeName = '/stationArrDep/selectStation'; - + @override - SelectStationPageState createState() { - final uiDesign = this.uiDesign ?? defaultUiDesign; + Widget build(BuildContext context, WidgetRef ref) { + final uiDesign = ref.watch(uiDesignProvider); switch (uiDesign) { case UiDesign.MATERIAL: - return SelectStationPageStateMaterial(); + return const SelectStationPageMaterial(); case UiDesign.CUPERTINO: - return SelectStationPageStateCupertino(); + return const SelectStationPageCupertino(); default: throw UnmatchedUiDesignException(uiDesign); } } } -abstract class SelectStationPageState extends State { +abstract class SelectStationPageShared extends StatefulWidget { + const SelectStationPageShared({super.key}); +} + +abstract class SelectStationPageState extends State { static const pageTitle = 'Plecări/sosiri stație'; static const textFieldLabel = 'Numele stației'; static const roToEn = { @@ -77,7 +80,10 @@ abstract class SelectStationPageState extends State { } void onSuggestionSelected(String suggestion) { - Navigator.of(context).pushNamed(ViewStationPage.routeName, arguments: suggestion); + Navigator.of(context).pushNamed( + ViewStationPage.routeName, + arguments: ViewStationArguments(stationName: suggestion), + ); } } diff --git a/lib/pages/station_arrdep_page/select_station/select_station_cupertino.dart b/lib/pages/station_arrdep_page/select_station/select_station_cupertino.dart index 49b3f16..ec2bfa9 100644 --- a/lib/pages/station_arrdep_page/select_station/select_station_cupertino.dart +++ b/lib/pages/station_arrdep_page/select_station/select_station_cupertino.dart @@ -2,6 +2,13 @@ import 'package:flutter/cupertino.dart'; import 'package:info_tren/components/cupertino_divider.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart'; +class SelectStationPageCupertino extends SelectStationPageShared { + const SelectStationPageCupertino({super.key}); + + @override + State createState() => SelectStationPageStateCupertino(); +} + class SelectStationPageStateCupertino extends SelectStationPageState { @override Widget build(BuildContext context) { diff --git a/lib/pages/station_arrdep_page/select_station/select_station_material.dart b/lib/pages/station_arrdep_page/select_station/select_station_material.dart index 22bbb4f..4f5f7da 100644 --- a/lib/pages/station_arrdep_page/select_station/select_station_material.dart +++ b/lib/pages/station_arrdep_page/select_station/select_station_material.dart @@ -1,6 +1,13 @@ import 'package:flutter/material.dart'; import 'package:info_tren/pages/station_arrdep_page/select_station/select_station.dart'; +class SelectStationPageMaterial extends SelectStationPageShared { + const SelectStationPageMaterial({super.key}); + + @override + State createState() => SelectStationPageStateMaterial(); +} + class SelectStationPageStateMaterial extends SelectStationPageState { @override Widget build(BuildContext context) { diff --git a/lib/pages/station_arrdep_page/view_station/view_station.dart b/lib/pages/station_arrdep_page/view_station/view_station.dart index 04ba124..1cb69c0 100644 --- a/lib/pages/station_arrdep_page/view_station/view_station.dart +++ b/lib/pages/station_arrdep_page/view_station/view_station.dart @@ -1,33 +1,49 @@ import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/api/station_data.dart'; import 'package:info_tren/components/refresh_future_builder.dart'; import 'package:info_tren/models.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_cupertino.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station_material.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info.dart'; +import 'package:info_tren/providers.dart'; import 'package:info_tren/utils/default_ui_design.dart'; -class ViewStationPage extends StatefulWidget { - final UiDesign? uiDesign; - final String stationName; - - const ViewStationPage({ Key? key, required this.stationName, this.uiDesign }) : super(key: key); +class ViewStationPage extends HookConsumerWidget { + const ViewStationPage({ super.key, }); static String routeName = '/stationArrDep/viewStation'; + ValueNotifier useTab() => useState(ViewStationPageTab.departures); + @override - ViewStationPageState createState() { - final uiDesign = this.uiDesign ?? defaultUiDesign; + Widget build(BuildContext context, WidgetRef ref) { + final tab = useTab(); + + final uiDesign = ref.watch(uiDesignProvider); switch (uiDesign) { case UiDesign.MATERIAL: - return ViewStationPageStateMaterial(); + return ViewStationPageMaterial( + tab: tab.value, + setTab: (newTab) => tab.value = newTab, + ); case UiDesign.CUPERTINO: - return ViewStationPageStateCupertino(); + return ViewStationPageCupertino( + tab: tab.value, + setTab: (newTab) => tab.value = newTab, + ); } } } -abstract class ViewStationPageState extends State { +class ViewStationArguments { + final String stationName; + + const ViewStationArguments({required this.stationName}); +} + +abstract class ViewStationPageShared extends StatelessWidget { static const arrivals = 'Sosiri'; static const departures = 'Plecări'; static const loadingText = 'Se încarcă...'; @@ -38,42 +54,15 @@ abstract class ViewStationPageState extends State { static const departedTo = 'A plecat către'; static const cancelledDeparture = 'Anulat - către'; - ViewStationPageTab tab = ViewStationPageTab.departures; - late String stationName; - late Future Function() futureCreator; - - @override - void initState() { - initData(); - super.initState(); - } - - @override - void didChangeDependencies() { - if (stationName != widget.stationName) { - setState(() { - initData(); - }); - } - super.didChangeDependencies(); - } - - void initData() { - stationName = widget.stationName; - futureCreator = () => getStationData(stationName); - } + final ViewStationPageTab tab; + final void Function(ViewStationPageTab) setTab; + const ViewStationPageShared({required this.tab, required this.setTab, super.key}); Widget buildContent(BuildContext context, Future Function() refresh, Future Function(Future Function() newFutureBuilder) _, RefreshFutureBuilderSnapshot snapshot); Widget buildStationArrivalItem(BuildContext context, StationArrDep item); Widget buildStationDepartureItem(BuildContext context, StationArrDep item); - void onTabChange(int index) { - setState(() { - tab = ViewStationPageTab.values[index]; - }); - } - - void onTrainTapped(StationTrain train) { + void onTrainTapped(BuildContext context, StationTrain train) { Navigator.of(context).pushNamed( TrainInfo.routeName, arguments: TrainInfoArguments( @@ -83,10 +72,14 @@ abstract class ViewStationPageState extends State { ); } + void onTabChange (int index) { + setTab(ViewStationPageTab.values[index]); + } + @override Widget build(BuildContext context) { - return RefreshFutureBuilder( - futureCreator: futureCreator, + return RefreshFutureBuilderProviderAdapter( + futureProvider: viewStationDataProvider, builder: buildContent, ); } diff --git a/lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart b/lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart index bf7d18a..f98747b 100644 --- a/lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart +++ b/lib/pages/station_arrdep_page/view_station/view_station_cupertino.dart @@ -1,27 +1,36 @@ import 'package:flutter/cupertino.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/components/loading/loading.dart'; import 'package:info_tren/components/refresh_future_builder.dart'; import 'package:info_tren/components/sliver_persistent_header_padding.dart'; import 'package:info_tren/models.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; +import 'package:info_tren/providers.dart'; + +class ViewStationPageCupertino extends ViewStationPageShared { + const ViewStationPageCupertino({super.key, required super.tab, required super.setTab}); -class ViewStationPageStateCupertino extends ViewStationPageState { @override Widget buildContent(BuildContext context, Future Function() refresh, Future Function(Future Function()) _, RefreshFutureBuilderSnapshot snapshot) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( - middle: Text(snapshot.hasData ? snapshot.data!.stationName : stationName), + middle: Consumer( + builder: (context, ref, _) { + final stationName = ref.watch(viewStationArgumentsProvider.select((value) => value.stationName)); + return Text(snapshot.hasData ? snapshot.data!.stationName : stationName); + } + ), ), child: snapshot.hasData ? CupertinoTabScaffold( tabBar: CupertinoTabBar( items: const [ BottomNavigationBarItem( icon: Icon(CupertinoIcons.arrow_down), - label: ViewStationPageState.arrivals, + label: ViewStationPageShared.arrivals, ), BottomNavigationBarItem( icon: Icon(CupertinoIcons.arrow_up), - label: ViewStationPageState.departures, + label: ViewStationPageShared.departures, ), ], onTap: onTabChange, @@ -47,14 +56,14 @@ class ViewStationPageStateCupertino extends ViewStationPageState { ), ); }, - ) : snapshot.state == RefreshFutureBuilderState.waiting ? Loading(text: ViewStationPageState.loadingText, uiDesign: widget.uiDesign,) : Container(), + ) : snapshot.state == RefreshFutureBuilderState.waiting ? const Loading(text: ViewStationPageShared.loadingText,) : Container(), ); } @override Widget buildStationArrivalItem(BuildContext context, StationArrDep item) { return GestureDetector( - onTap: () => onTrainTapped(item.train), + onTap: () => onTrainTapped(context, item.train), child: CupertinoFormRow( prefix: Text.rich( TextSpan( @@ -73,7 +82,7 @@ class ViewStationPageStateCupertino extends ViewStationPageState { helper: Text.rich( TextSpan( children: [ - const TextSpan(text: ViewStationPageState.arrivesFrom), + const TextSpan(text: ViewStationPageShared.arrivesFrom), const TextSpan(text: ' '), TextSpan(text: item.train.terminus), ], @@ -87,7 +96,7 @@ class ViewStationPageStateCupertino extends ViewStationPageState { @override Widget buildStationDepartureItem(BuildContext context, StationArrDep item) { return GestureDetector( - onTap: () => onTrainTapped(item.train), + onTap: () => onTrainTapped(context, item.train), child: CupertinoFormRow( prefix: Text.rich( TextSpan( @@ -106,7 +115,7 @@ class ViewStationPageStateCupertino extends ViewStationPageState { helper: Text.rich( TextSpan( children: [ - const TextSpan(text: ViewStationPageState.departsTo), + const TextSpan(text: ViewStationPageShared.departsTo), const TextSpan(text: ' '), TextSpan(text: item.train.terminus), ], diff --git a/lib/pages/station_arrdep_page/view_station/view_station_material.dart b/lib/pages/station_arrdep_page/view_station/view_station_material.dart index 06db745..a8ed230 100644 --- a/lib/pages/station_arrdep_page/view_station/view_station_material.dart +++ b/lib/pages/station_arrdep_page/view_station/view_station_material.dart @@ -1,22 +1,31 @@ import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/components/badge.dart'; import 'package:info_tren/components/loading/loading.dart'; import 'package:info_tren/components/refresh_future_builder.dart'; import 'package:info_tren/models.dart'; import 'package:info_tren/pages/station_arrdep_page/view_station/view_station.dart'; +import 'package:info_tren/providers.dart'; + +class ViewStationPageMaterial extends ViewStationPageShared { + const ViewStationPageMaterial({super.key, required super.tab, required super.setTab}); -class ViewStationPageStateMaterial extends ViewStationPageState { @override Widget buildContent(BuildContext context, Future Function() refresh, Future Function(Future Function()) _, RefreshFutureBuilderSnapshot snapshot) { return Scaffold( appBar: AppBar( - title: Text(snapshot.hasData ? snapshot.data!.stationName : stationName), + title: Consumer( + builder: (context, ref, _) { + final stationName = ref.watch(viewStationArgumentsProvider.select((value) => value.stationName)); + return Text(snapshot.hasData ? snapshot.data!.stationName : stationName); + } + ), centerTitle: true, ), body: snapshot.state == RefreshFutureBuilderState.waiting - ? Loading(text: ViewStationPageState.loadingText, uiDesign: widget.uiDesign,) + ? const Loading(text: ViewStationPageShared.loadingText,) : snapshot.state == RefreshFutureBuilderState.error ? Container() : CustomScrollView( @@ -36,11 +45,11 @@ class ViewStationPageStateMaterial extends ViewStationPageState { items: const [ BottomNavigationBarItem( icon: Icon(Icons.arrow_downward), - label: ViewStationPageState.arrivals, + label: ViewStationPageShared.arrivals, ), BottomNavigationBarItem( icon: Icon(Icons.arrow_upward), - label: ViewStationPageState.departures, + label: ViewStationPageShared.departures, ), ], currentIndex: tab.index, @@ -51,7 +60,7 @@ class ViewStationPageStateMaterial extends ViewStationPageState { Widget buildStationItem(BuildContext context, StationArrDep item, {required bool arrival}) { return InkWell( - onTap: () => onTrainTapped(item.train), + onTap: () => onTrainTapped(context, item.train), child: Container( color: item.status.cancelled ? Colors.red.withAlpha(100) : null, child: Row( @@ -117,10 +126,10 @@ class ViewStationPageStateMaterial extends ViewStationPageState { children: [ TextSpan( text: item.status.cancelled - ? (arrival ? ViewStationPageState.cancelledArrival : ViewStationPageState.cancelledDeparture) + ? (arrival ? ViewStationPageShared.cancelledArrival : ViewStationPageShared.cancelledDeparture) : item.time.add(Duration(minutes: max(0, item.status.delay))).compareTo(DateTime.now()) < 0 - ? (arrival ? ViewStationPageState.arrivedFrom : ViewStationPageState.departedTo) - : (arrival ? ViewStationPageState.arrivesFrom : ViewStationPageState.departsTo) + ? (arrival ? ViewStationPageShared.arrivedFrom : ViewStationPageShared.departedTo) + : (arrival ? ViewStationPageShared.arrivesFrom : ViewStationPageShared.departsTo) ), const TextSpan(text: ' '), TextSpan(text: item.train.terminus), diff --git a/lib/pages/train_info_page/select_train/select_train.dart b/lib/pages/train_info_page/select_train/select_train.dart index 5154b22..8f73a06 100644 --- a/lib/pages/train_info_page/select_train/select_train.dart +++ b/lib/pages/train_info_page/select_train/select_train.dart @@ -1,46 +1,50 @@ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/components/select_train_suggestions/select_train_suggestions.dart'; import 'package:info_tren/models.dart'; import 'package:info_tren/pages/train_info_page/select_train/select_train_cupertino.dart'; import 'package:info_tren/pages/train_info_page/select_train/select_train_material.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info.dart'; -import 'package:info_tren/utils/default_ui_design.dart'; +import 'package:info_tren/providers.dart'; import 'package:info_tren/api/trains.dart' as api_trains; typedef TrainSelectedCallback = Function(int trainNumber); -class SelectTrainPage extends StatefulWidget { - final UiDesign? uiDesign; - - const SelectTrainPage({Key? key, this.uiDesign}) : super(key: key); +class SelectTrainPage extends ConsumerWidget { + const SelectTrainPage({super.key}); static String routeName = "/trainInfo/selectTrain"; - - void onTrainSelected(BuildContext context, String selection) { - selection = selection.characters.takeWhile((char) => List.generate(10, (i) => i.toString()).contains(char)).join(); - Navigator.of(context).pushNamed( - TrainInfo.routeName, - arguments: TrainInfoArguments(trainNumber: selection), - ); - } - + @override - SelectTrainPageState createState() { - final uiDesign = this.uiDesign ?? defaultUiDesign; + Widget build(BuildContext context, WidgetRef ref) { + final uiDesign = ref.watch(uiDesignProvider); + switch(uiDesign) { case UiDesign.MATERIAL: - return SelectTrainPageStateMaterial(); + return const SelectTrainPageMaterial(); case UiDesign.CUPERTINO: - return SelectTrainPageStateCupertino(); + return const SelectTrainPageCupertino(); default: throw UnmatchedUiDesignException(uiDesign); } } } -abstract class SelectTrainPageState extends State { +abstract class SelectTrainPageShared extends StatefulWidget { + const SelectTrainPageShared({super.key}); + + void onTrainSelected(BuildContext context, String selection) { + selection = selection.characters.takeWhile((char) => List.generate(10, (i) => i.toString()).contains(char)).join(); + Navigator.of(context).pushNamed( + TrainInfo.routeName, + arguments: TrainInfoArguments(trainNumber: selection), + ); + } +} + +abstract class SelectTrainPageState extends State { final String pageTitle = 'Informații despre tren'; final String textFieldLabel = 'Numărul trenului'; @@ -102,7 +106,6 @@ abstract class SelectTrainPageState extends State { } Widget get suggestionsList => SelectTrainSuggestions( - uiDesign: widget.uiDesign, choices: filteredTrains, onTrainSelected: (trainNumber) => widget.onTrainSelected(context, trainNumber), currentInput: trainNoController.text, diff --git a/lib/pages/train_info_page/select_train/select_train_cupertino.dart b/lib/pages/train_info_page/select_train/select_train_cupertino.dart index e9d0ba4..34ffd40 100644 --- a/lib/pages/train_info_page/select_train/select_train_cupertino.dart +++ b/lib/pages/train_info_page/select_train/select_train_cupertino.dart @@ -2,6 +2,13 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; +class SelectTrainPageCupertino extends SelectTrainPageShared { + const SelectTrainPageCupertino({super.key}); + + @override + State createState() => SelectTrainPageStateCupertino(); +} + class SelectTrainPageStateCupertino extends SelectTrainPageState { @override Widget build(BuildContext context) { diff --git a/lib/pages/train_info_page/select_train/select_train_material.dart b/lib/pages/train_info_page/select_train/select_train_material.dart index 078701e..f36c987 100644 --- a/lib/pages/train_info_page/select_train/select_train_material.dart +++ b/lib/pages/train_info_page/select_train/select_train_material.dart @@ -2,6 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:info_tren/pages/train_info_page/select_train/select_train.dart'; +class SelectTrainPageMaterial extends SelectTrainPageShared { + const SelectTrainPageMaterial({super.key}); + + @override + State createState() => SelectTrainPageStateMaterial(); +} + class SelectTrainPageStateMaterial extends SelectTrainPageState { @override Widget build(BuildContext context) { diff --git a/lib/pages/train_info_page/view_train/train_info.dart b/lib/pages/train_info_page/view_train/train_info.dart index 17b19cf..f14f743 100644 --- a/lib/pages/train_info_page/view_train/train_info.dart +++ b/lib/pages/train_info_page/view_train/train_info.dart @@ -1,27 +1,27 @@ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:info_tren/api/train_data.dart'; import 'package:info_tren/components/loading/loading.dart'; import 'package:info_tren/components/refresh_future_builder.dart'; import 'package:info_tren/models.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info_cupertino.dart'; import 'package:info_tren/pages/train_info_page/view_train/train_info_material.dart'; -import 'package:info_tren/utils/default_ui_design.dart'; +import 'package:info_tren/providers.dart'; -class TrainInfo extends StatelessWidget { +class TrainInfo extends ConsumerWidget { static String routeName = "/trainInfo/display"; - final UiDesign? uiDesign; - final String trainNumber; - final DateTime? date; - - const TrainInfo({Key? key, required this.trainNumber, this.date, this.uiDesign}): super(key: key); + const TrainInfo({super.key,}); @override - Widget build(BuildContext context) { - final uiDesign = this.uiDesign ?? defaultUiDesign; + Widget build(BuildContext context, WidgetRef ref) { + final uiDesign = ref.watch(uiDesignProvider); + final args = ref.watch(trainInfoArgumentsProvider); + final trainNumber = args.trainNumber; + final date = args.date; return RefreshFutureBuilder( futureCreator: () => getTrain(trainNumber, date: date), @@ -80,9 +80,8 @@ abstract class TrainInfoLoading extends StatelessWidget { TrainInfoLoading({ required this.title, String? loadingText, - UiDesign? uiDesign, super.key, - }) : loadingWidget = Loading(uiDesign: uiDesign, text: loadingText,); + }) : loadingWidget = Loading(text: loadingText,); } abstract class TrainInfoError extends StatelessWidget { diff --git a/lib/pages/train_info_page/view_train/train_info_cupertino.dart b/lib/pages/train_info_page/view_train/train_info_cupertino.dart index f9fc269..2fdfc23 100644 --- a/lib/pages/train_info_page/view_train/train_info_cupertino.dart +++ b/lib/pages/train_info_page/view_train/train_info_cupertino.dart @@ -11,8 +11,7 @@ import 'package:info_tren/pages/train_info_page/view_train/train_info_cupertino_ import 'package:info_tren/utils/state_to_string.dart'; class TrainInfoLoadingCupertino extends TrainInfoLoading { - TrainInfoLoadingCupertino({required super.title, super.loadingText, super.key,}) - : super(uiDesign: UiDesign.CUPERTINO); + TrainInfoLoadingCupertino({required super.title, super.loadingText, super.key,}); @override Widget build(BuildContext context) { diff --git a/lib/pages/train_info_page/view_train/train_info_material.dart b/lib/pages/train_info_page/view_train/train_info_material.dart index 20a4c45..f6a9484 100644 --- a/lib/pages/train_info_page/view_train/train_info_material.dart +++ b/lib/pages/train_info_page/view_train/train_info_material.dart @@ -8,8 +8,7 @@ import 'package:info_tren/pages/train_info_page/view_train/train_info_material_D import 'package:info_tren/utils/state_to_string.dart'; class TrainInfoLoadingMaterial extends TrainInfoLoading { - TrainInfoLoadingMaterial({required super.title, super.loadingText, super.key,}) - : super(uiDesign: UiDesign.MATERIAL); + TrainInfoLoadingMaterial({required super.title, super.loadingText, super.key,}); @override Widget build(BuildContext context) { diff --git a/lib/providers.dart b/lib/providers.dart new file mode 100644 index 0000000..724d9f5 --- /dev/null +++ b/lib/providers.dart @@ -0,0 +1,43 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:info_tren/api/station_data.dart'; +import 'package:info_tren/models.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'; +import 'package:info_tren/utils/default_ui_design.dart'; +import 'package:info_tren/utils/iterable_extensions.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +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); + } + else { + await sharedPreferences.remove('uiDesign'); + } + ref.invalidate(uiDesignProvider); +} + +final trainInfoArgumentsProvider = Provider( + (_) => throw UnimplementedError('Please override in ProviderScope'), +); + +final stationDataProvider = FutureProvider.family((ref, String stationName) => getStationData(stationName)); +final viewStationArgumentsProvider = Provider( + (_) => throw UnimplementedError('Please override in ProviderScope'), +); +final viewStationDataProvider = Provider((ref) { + final args = ref.watch(viewStationArgumentsProvider); + final data = ref.watch(stationDataProvider(args.stationName)); + return data; +}); diff --git a/pubspec.lock b/pubspec.lock index 3cf4a8e..0de5139 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -354,6 +354,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.7" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" plugin_platform_interface: dependency: transitive description: @@ -368,6 +396,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.1" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" pub_semver: dependency: transitive description: @@ -396,6 +431,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.2" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.15" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.14" + shared_preferences_ios: + dependency: transitive + description: + name: shared_preferences_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" shelf: dependency: transitive description: @@ -590,6 +681,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+2" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 694e7b6..404a282 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,6 +32,7 @@ dependencies: hooks_riverpod: ^2.0.2 freezed_annotation: ^2.2.0 json_annotation: ^4.7.0 + shared_preferences: ^2.0.15 dev_dependencies: # flutter_test: