From bee21b55e6d3272dc0dd187ed6e95dbaf4338565 Mon Sep 17 00:00:00 2001 From: Brett Morgan Date: Sat, 31 Aug 2024 05:24:59 +1000 Subject: [PATCH] Cleaning up Veggie Seasons (#2416) ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md --- veggieseasons/analysis_options.yaml | 4 + veggieseasons/lib/data/app_state.dart | 4 +- .../lib/data/local_veggie_provider.dart | 2 +- veggieseasons/lib/data/preferences.dart | 2 +- veggieseasons/lib/main.dart | 19 +- veggieseasons/lib/screens/details.dart | 162 +++++------- veggieseasons/lib/screens/favorites.dart | 6 +- veggieseasons/lib/screens/home.dart | 22 +- veggieseasons/lib/screens/list.dart | 30 +-- veggieseasons/lib/screens/search.dart | 6 +- veggieseasons/lib/screens/settings.dart | 10 +- veggieseasons/lib/styles.dart | 2 +- ...{close_button.dart => detail_buttons.dart} | 35 ++- .../lib/widgets/fade_transition_page.dart | 66 ----- veggieseasons/lib/widgets/settings_group.dart | 2 +- veggieseasons/lib/widgets/settings_item.dart | 2 +- veggieseasons/lib/widgets/trivia.dart | 249 ------------------ veggieseasons/lib/widgets/veggie_card.dart | 89 ++----- .../lib/widgets/veggie_headline.dart | 4 +- .../lib/widgets/veggie_seasons_page.dart | 45 ++++ 20 files changed, 215 insertions(+), 546 deletions(-) rename veggieseasons/lib/widgets/{close_button.dart => detail_buttons.dart} (72%) delete mode 100644 veggieseasons/lib/widgets/fade_transition_page.dart delete mode 100644 veggieseasons/lib/widgets/trivia.dart create mode 100644 veggieseasons/lib/widgets/veggie_seasons_page.dart diff --git a/veggieseasons/analysis_options.yaml b/veggieseasons/analysis_options.yaml index 13d6fe105..e99e9ce28 100644 --- a/veggieseasons/analysis_options.yaml +++ b/veggieseasons/analysis_options.yaml @@ -1 +1,5 @@ include: package:analysis_defaults/flutter.yaml + +linter: + rules: + - prefer_relative_imports \ No newline at end of file diff --git a/veggieseasons/lib/data/app_state.dart b/veggieseasons/lib/data/app_state.dart index 34261d64e..9c802c001 100644 --- a/veggieseasons/lib/data/app_state.dart +++ b/veggieseasons/lib/data/app_state.dart @@ -3,8 +3,8 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:veggieseasons/data/local_veggie_provider.dart'; -import 'package:veggieseasons/data/veggie.dart'; +import 'local_veggie_provider.dart'; +import 'veggie.dart'; class AppState extends ChangeNotifier { final List _veggies; diff --git a/veggieseasons/lib/data/local_veggie_provider.dart b/veggieseasons/lib/data/local_veggie_provider.dart index 5064bfcf4..454909d31 100644 --- a/veggieseasons/lib/data/local_veggie_provider.dart +++ b/veggieseasons/lib/data/local_veggie_provider.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import 'package:flutter/cupertino.dart'; -import 'package:veggieseasons/data/veggie.dart'; +import 'veggie.dart'; class LocalVeggieProvider { static List veggies = [ diff --git a/veggieseasons/lib/data/preferences.dart b/veggieseasons/lib/data/preferences.dart index 67a6dd3fd..5ae157669 100644 --- a/veggieseasons/lib/data/preferences.dart +++ b/veggieseasons/lib/data/preferences.dart @@ -6,7 +6,7 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:veggieseasons/data/veggie.dart'; +import 'veggie.dart'; /// A model class that mirrors the options in [SettingsScreen] and stores data /// in shared preferences. diff --git a/veggieseasons/lib/main.dart b/veggieseasons/lib/main.dart index 4bd78bc5f..e50308ae8 100644 --- a/veggieseasons/lib/main.dart +++ b/veggieseasons/lib/main.dart @@ -9,18 +9,18 @@ import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/services.dart' show DeviceOrientation, SystemChrome; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; -import 'package:veggieseasons/data/app_state.dart'; -import 'package:veggieseasons/data/preferences.dart'; -import 'package:veggieseasons/screens/home.dart'; -import 'package:veggieseasons/styles.dart'; -import 'package:veggieseasons/widgets/fade_transition_page.dart'; import 'package:window_size/window_size.dart'; +import 'data/app_state.dart'; +import 'data/preferences.dart'; import 'screens/details.dart'; import 'screens/favorites.dart'; +import 'screens/home.dart'; import 'screens/list.dart'; import 'screens/search.dart'; import 'screens/settings.dart'; +import 'styles.dart'; +import 'widgets/veggie_seasons_page.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -136,7 +136,7 @@ class _VeggieAppState extends State with RestorationMixin { GoRoute( path: '/list', pageBuilder: (context, state) { - return FadeTransitionPage( + return VeggieSeasonsPage( key: state.pageKey, restorationId: 'route.list', child: const ListScreen(restorationId: 'list'), @@ -149,7 +149,7 @@ class _VeggieAppState extends State with RestorationMixin { GoRoute( path: '/favorites', pageBuilder: (context, state) { - return FadeTransitionPage( + return VeggieSeasonsPage( key: state.pageKey, restorationId: 'route.favorites', child: const FavoritesScreen(restorationId: 'favorites'), @@ -162,7 +162,7 @@ class _VeggieAppState extends State with RestorationMixin { GoRoute( path: '/search', pageBuilder: (context, state) { - return FadeTransitionPage( + return VeggieSeasonsPage( key: state.pageKey, restorationId: 'route.search', child: const SearchScreen(restorationId: 'search'), @@ -175,7 +175,7 @@ class _VeggieAppState extends State with RestorationMixin { GoRoute( path: '/settings', pageBuilder: (context, state) { - return FadeTransitionPage( + return VeggieSeasonsPage( key: state.pageKey, restorationId: 'route.settings', child: const SettingsScreen(restorationId: 'settings'), @@ -217,7 +217,6 @@ class _VeggieAppState extends State with RestorationMixin { final veggieId = int.parse(state.pathParameters['id']!); return CupertinoPage( restorationId: 'route.details', - fullscreenDialog: true, child: DetailsScreen( id: veggieId, restorationId: 'details', diff --git a/veggieseasons/lib/screens/details.dart b/veggieseasons/lib/screens/details.dart index e672fee8c..949f3701e 100644 --- a/veggieseasons/lib/screens/details.dart +++ b/veggieseasons/lib/screens/details.dart @@ -5,12 +5,11 @@ import 'package:flutter/cupertino.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; -import 'package:veggieseasons/data/app_state.dart'; -import 'package:veggieseasons/data/preferences.dart'; -import 'package:veggieseasons/data/veggie.dart'; -import 'package:veggieseasons/styles.dart'; -import 'package:veggieseasons/widgets/close_button.dart'; -import 'package:veggieseasons/widgets/trivia.dart'; +import '../data/app_state.dart'; +import '../data/preferences.dart'; +import '../data/veggie.dart'; +import '../styles.dart'; +import '../widgets/detail_buttons.dart'; class ServingInfoChart extends StatelessWidget { const ServingInfoChart(this.veggie, this.prefs, {super.key}); @@ -44,23 +43,7 @@ class ServingInfoChart extends StatelessWidget { return Column( children: [ const SizedBox(height: 16), - Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.only( - left: 9, - bottom: 4, - ), - child: Text( - 'Serving info', - style: CupertinoTheme.of(context).textTheme.textStyle, - ), - ), - ), Container( - decoration: BoxDecoration( - border: Border.all(color: Styles.servingInfoBorderColor), - ), padding: const EdgeInsets.all(8), child: Column( children: [ @@ -212,61 +195,23 @@ class InfoView extends StatelessWidget { style: CupertinoTheme.of(context).textTheme.textStyle, ), ServingInfoChart(veggie, prefs), - const SizedBox(height: 24), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CupertinoSwitch( - value: veggie.isFavorite, - onChanged: (value) { - appState.setFavorite(id, value); - }, - ), - const SizedBox(width: 8), - Text( - 'Save to Garden', - style: CupertinoTheme.of(context).textTheme.textStyle, - ), - ], - ), ], ), ); } } -class DetailsScreen extends StatefulWidget { +class DetailsScreen extends StatelessWidget { final int? id; final String? restorationId; const DetailsScreen({this.id, this.restorationId, super.key}); - @override - State createState() => _DetailsScreenState(); -} - -class _DetailsScreenState extends State with RestorationMixin { - final RestorableInt _selectedViewIndex = RestorableInt(0); - - @override - String? get restorationId => widget.restorationId; - - @override - void restoreState(RestorationBucket? oldBucket, bool initialRestore) { - registerForRestoration(_selectedViewIndex, 'tab'); - } - - @override - void dispose() { - _selectedViewIndex.dispose(); - super.dispose(); - } - Widget _buildHeader(BuildContext context, AppState model) { - final veggie = model.getVeggie(widget.id); + final veggie = model.getVeggie(id); return SizedBox( - height: 150, + height: 240, child: Stack( children: [ Positioned( @@ -287,6 +232,48 @@ class _DetailsScreenState extends State with RestorationMixin { }), ), ), + Positioned( + top: 16, + right: 16, + child: SafeArea( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + ShareButton( + () { + showCupertinoModalPopup( + context: context, + builder: (context) { + return CupertinoActionSheet( + title: Text('Share ${veggie.name}'), + message: Text(veggie.shortDescription), + actions: [ + CupertinoActionSheetAction( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('OK'), + ), + ], + ); + }, + ); + }, + ), + const SizedBox(width: 8), + Builder(builder: (context) { + final appState = Provider.of(context); + final veggie = appState.getVeggie(id); + + return FavoriteButton( + () => appState.setFavorite(id, !veggie.isFavorite), + veggie.isFavorite, + ); + }), + ], + ), + ), + ), ], ), ); @@ -296,41 +283,22 @@ class _DetailsScreenState extends State with RestorationMixin { Widget build(BuildContext context) { final appState = Provider.of(context); - return UnmanagedRestorationScope( - bucket: bucket, - child: CupertinoPageScaffold( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: ListView( - restorationId: 'list', - children: [ - _buildHeader(context, appState), - const SizedBox(height: 20), - CupertinoSegmentedControl( - children: const { - 0: Text( - 'Facts & Info', - ), - 1: Text( - 'Trivia', - ) - }, - groupValue: _selectedViewIndex.value, - onValueChanged: (value) { - setState(() => _selectedViewIndex.value = value); - }, - ), - _selectedViewIndex.value == 0 - ? InfoView(widget.id) - : TriviaView(id: widget.id, restorationId: 'trivia'), - ], - ), + return CupertinoPageScaffold( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: ListView( + restorationId: 'list', + children: [ + _buildHeader(context, appState), + const SizedBox(height: 20), + InfoView(id), + ], ), - ], - ), + ), + ], ), ); } diff --git a/veggieseasons/lib/screens/favorites.dart b/veggieseasons/lib/screens/favorites.dart index e84502f9e..3f4bcf914 100644 --- a/veggieseasons/lib/screens/favorites.dart +++ b/veggieseasons/lib/screens/favorites.dart @@ -4,9 +4,9 @@ import 'package:flutter/cupertino.dart'; import 'package:provider/provider.dart'; -import 'package:veggieseasons/data/app_state.dart'; -import 'package:veggieseasons/data/veggie.dart'; -import 'package:veggieseasons/widgets/veggie_headline.dart'; +import '../data/app_state.dart'; +import '../data/veggie.dart'; +import '../widgets/veggie_headline.dart'; class FavoritesScreen extends StatelessWidget { const FavoritesScreen({this.restorationId, super.key}); diff --git a/veggieseasons/lib/screens/home.dart b/veggieseasons/lib/screens/home.dart index 901294bfd..123716eaa 100644 --- a/veggieseasons/lib/screens/home.dart +++ b/veggieseasons/lib/screens/home.dart @@ -5,6 +5,8 @@ import 'package:flutter/cupertino.dart'; import 'package:go_router/go_router.dart'; +const _bottomNavigationBarItemIconPadding = EdgeInsets.only(top: 4.0); + class HomeScreen extends StatelessWidget { const HomeScreen({ super.key, @@ -30,19 +32,31 @@ class HomeScreen extends StatelessWidget { currentIndex: index, items: const [ BottomNavigationBarItem( - icon: Icon(CupertinoIcons.home), + icon: Padding( + padding: _bottomNavigationBarItemIconPadding, + child: Icon(CupertinoIcons.home), + ), label: 'Home', ), BottomNavigationBarItem( - icon: Icon(CupertinoIcons.book), + icon: Padding( + padding: _bottomNavigationBarItemIconPadding, + child: Icon(CupertinoIcons.book), + ), label: 'My Garden', ), BottomNavigationBarItem( - icon: Icon(CupertinoIcons.search), + icon: Padding( + padding: _bottomNavigationBarItemIconPadding, + child: Icon(CupertinoIcons.search), + ), label: 'Search', ), BottomNavigationBarItem( - icon: Icon(CupertinoIcons.settings), + icon: Padding( + padding: _bottomNavigationBarItemIconPadding, + child: Icon(CupertinoIcons.settings), + ), label: 'Settings', ), ], diff --git a/veggieseasons/lib/screens/list.dart b/veggieseasons/lib/screens/list.dart index 6cb6da270..3408c05b3 100644 --- a/veggieseasons/lib/screens/list.dart +++ b/veggieseasons/lib/screens/list.dart @@ -4,20 +4,19 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; -import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; -import 'package:veggieseasons/data/app_state.dart'; -import 'package:veggieseasons/data/preferences.dart'; -import 'package:veggieseasons/data/veggie.dart'; -import 'package:veggieseasons/styles.dart'; -import 'package:veggieseasons/widgets/veggie_card.dart'; +import '../data/app_state.dart'; +import '../data/preferences.dart'; +import '../data/veggie.dart'; +import '../styles.dart'; +import '../widgets/veggie_card.dart'; class ListScreen extends StatelessWidget { const ListScreen({this.restorationId, super.key}); final String? restorationId; - Widget _generateVeggieRow(Veggie veggie, Preferences prefs, + Widget _generateVeggieCard(Veggie veggie, Preferences prefs, {bool inSeason = true}) { return Padding( padding: const EdgeInsets.only(left: 16, right: 16, bottom: 24), @@ -35,8 +34,6 @@ class ListScreen extends StatelessWidget { return CupertinoTabView( restorationScopeId: restorationId, builder: (context) { - var dateString = DateFormat('MMMM y').format(DateTime.now()); - final appState = Provider.of(context); final prefs = Provider.of(context); final themeData = CupertinoTheme.of(context); @@ -52,18 +49,11 @@ class ListScreen extends StatelessWidget { if (index == 0) { return Padding( padding: const EdgeInsets.fromLTRB(16, 24, 16, 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(dateString.toUpperCase(), - style: Styles.minorText(themeData)), - Text('In season today', - style: Styles.headlineText(themeData)), - ], - ), + child: Text('In season today', + style: Styles.headlineText(themeData)), ); } else if (index <= appState.availableVeggies.length) { - return _generateVeggieRow( + return _generateVeggieCard( appState.availableVeggies[index - 1], prefs, ); @@ -76,7 +66,7 @@ class ListScreen extends StatelessWidget { } else { var relativeIndex = index - (appState.availableVeggies.length + 2); - return _generateVeggieRow( + return _generateVeggieCard( appState.unavailableVeggies[relativeIndex], prefs, inSeason: false); } diff --git a/veggieseasons/lib/screens/search.dart b/veggieseasons/lib/screens/search.dart index 0caa8186f..deb8dadb5 100644 --- a/veggieseasons/lib/screens/search.dart +++ b/veggieseasons/lib/screens/search.dart @@ -5,9 +5,9 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; -import 'package:veggieseasons/data/app_state.dart'; -import 'package:veggieseasons/data/veggie.dart'; -import 'package:veggieseasons/widgets/veggie_headline.dart'; +import '../data/app_state.dart'; +import '../data/veggie.dart'; +import '../widgets/veggie_headline.dart'; class SearchScreen extends StatefulWidget { const SearchScreen({this.restorationId, super.key}); diff --git a/veggieseasons/lib/screens/settings.dart b/veggieseasons/lib/screens/settings.dart index 7e5a872fd..48a8be26e 100644 --- a/veggieseasons/lib/screens/settings.dart +++ b/veggieseasons/lib/screens/settings.dart @@ -5,11 +5,11 @@ import 'package:flutter/cupertino.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; -import 'package:veggieseasons/data/preferences.dart'; -import 'package:veggieseasons/data/veggie.dart'; -import 'package:veggieseasons/styles.dart'; -import 'package:veggieseasons/widgets/settings_group.dart'; -import 'package:veggieseasons/widgets/settings_item.dart'; +import '../data/preferences.dart'; +import '../data/veggie.dart'; +import '../styles.dart'; +import '../widgets/settings_group.dart'; +import '../widgets/settings_item.dart'; class VeggieCategorySettingsScreen extends StatelessWidget { const VeggieCategorySettingsScreen({super.key, this.restorationId}); diff --git a/veggieseasons/lib/styles.dart b/veggieseasons/lib/styles.dart index 3a136fd94..b3bd168df 100644 --- a/veggieseasons/lib/styles.dart +++ b/veggieseasons/lib/styles.dart @@ -4,7 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:veggieseasons/data/veggie.dart'; +import 'data/veggie.dart'; abstract class Styles { static CupertinoThemeData veggieThemeData = const CupertinoThemeData( diff --git a/veggieseasons/lib/widgets/close_button.dart b/veggieseasons/lib/widgets/detail_buttons.dart similarity index 72% rename from veggieseasons/lib/widgets/close_button.dart rename to veggieseasons/lib/widgets/detail_buttons.dart index 7eb11891c..58294502c 100644 --- a/veggieseasons/lib/widgets/close_button.dart +++ b/veggieseasons/lib/widgets/detail_buttons.dart @@ -5,7 +5,7 @@ import 'dart:ui' as ui; import 'package:flutter/cupertino.dart'; -import 'package:veggieseasons/styles.dart'; +import '../styles.dart'; /// Partially overlays and then blurs its child's background. class FrostedBox extends StatelessWidget { @@ -75,17 +75,38 @@ class _ColorChangingIconState } } -/// A simple "close this modal" button that invokes a callback when pressed. -class CloseButton extends StatefulWidget { - const CloseButton(this.onPressed, {super.key}); +/// A close button that invokes a callback when pressed. +class CloseButton extends _DetailPageButton { + const CloseButton(VoidCallback onPressed, {super.key}) + : super(onPressed, CupertinoIcons.chevron_back); +} + +/// A share button that invokes a callback when pressed. +class ShareButton extends _DetailPageButton { + const ShareButton(VoidCallback onPressed, {super.key}) + : super(onPressed, CupertinoIcons.share); +} + +/// A favorite button that invokes a callback when pressed. +class FavoriteButton extends _DetailPageButton { + const FavoriteButton(VoidCallback onPressed, bool isFavorite, {super.key}) + : super( + onPressed, + isFavorite ? CupertinoIcons.heart_fill : CupertinoIcons.heart, + ); +} + +class _DetailPageButton extends StatefulWidget { + const _DetailPageButton(this.onPressed, this.icon, {super.key}); final VoidCallback onPressed; + final IconData icon; @override - State createState() => _CloseButtonState(); + State<_DetailPageButton> createState() => _DetailPageButtonState(); } -class _CloseButtonState extends State { +class _DetailPageButtonState extends State<_DetailPageButton> { bool tapInProgress = false; @override @@ -111,7 +132,7 @@ class _CloseButtonState extends State { ), child: Center( child: ColorChangingIcon( - CupertinoIcons.clear_thick, + widget.icon, duration: const Duration(milliseconds: 300), color: tapInProgress ? Styles.closeButtonPressed diff --git a/veggieseasons/lib/widgets/fade_transition_page.dart b/veggieseasons/lib/widgets/fade_transition_page.dart deleted file mode 100644 index 066ca5d89..000000000 --- a/veggieseasons/lib/widgets/fade_transition_page.dart +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2021, the Flutter project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:flutter/cupertino.dart'; - -class FadeTransitionPage extends Page { - final Widget child; - final Duration duration; - - const FadeTransitionPage({ - super.key, - required this.child, - this.duration = const Duration(milliseconds: 300), - super.restorationId, - }); - - @override - Route createRoute(BuildContext context) => - PageBasedFadeTransitionRoute(this); -} - -class PageBasedFadeTransitionRoute extends PageRoute { - PageBasedFadeTransitionRoute(FadeTransitionPage page) - : super(settings: page); - - FadeTransitionPage get _page => settings as FadeTransitionPage; - - @override - Color? get barrierColor => null; - - @override - String? get barrierLabel => null; - - @override - Duration get transitionDuration => _page.duration; - - @override - Duration get reverseTransitionDuration => _page.duration; - - @override - bool get maintainState => true; - - @override - Widget buildPage( - BuildContext context, - Animation animation, - Animation secondaryAnimation, - ) { - return _page.child; - } - - @override - Widget buildTransitions( - BuildContext context, - Animation animation, - Animation secondaryAnimation, - Widget child, - ) { - final tween = CurveTween(curve: Curves.easeInOut); - return FadeTransition( - opacity: animation.drive(tween), - child: _page.child, - ); - } -} diff --git a/veggieseasons/lib/widgets/settings_group.dart b/veggieseasons/lib/widgets/settings_group.dart index da5cd95a0..dacbaee7a 100644 --- a/veggieseasons/lib/widgets/settings_group.dart +++ b/veggieseasons/lib/widgets/settings_group.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import 'package:flutter/cupertino.dart'; -import 'package:veggieseasons/styles.dart'; +import '../styles.dart'; import 'settings_item.dart'; diff --git a/veggieseasons/lib/widgets/settings_item.dart b/veggieseasons/lib/widgets/settings_item.dart index b503e0dee..16a7dd19a 100644 --- a/veggieseasons/lib/widgets/settings_item.dart +++ b/veggieseasons/lib/widgets/settings_item.dart @@ -5,7 +5,7 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; -import 'package:veggieseasons/styles.dart'; +import '../styles.dart'; // The widgets in this file present a Cupertino-style settings item to the user. // In the future, the Cupertino package in the Flutter SDK will include diff --git a/veggieseasons/lib/widgets/trivia.dart b/veggieseasons/lib/widgets/trivia.dart deleted file mode 100644 index 74583c310..000000000 --- a/veggieseasons/lib/widgets/trivia.dart +++ /dev/null @@ -1,249 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:provider/provider.dart'; -import 'package:veggieseasons/data/app_state.dart'; -import 'package:veggieseasons/data/veggie.dart'; -import 'package:veggieseasons/styles.dart'; - -/// Presents a series of trivia questions about a particular widget, and tracks -/// the user's score. -class TriviaView extends StatefulWidget { - final int? id; - final String? restorationId; - - const TriviaView({this.id, this.restorationId, super.key}); - - @override - State createState() => _TriviaViewState(); -} - -/// Possible states of the game. -enum PlayerStatus { - readyToAnswer, - wasCorrect, - wasIncorrect, -} - -class _TriviaViewState extends State with RestorationMixin { - /// Current app state. This is used to fetch veggie data. - late AppState appState; - - /// The veggie trivia about which to show. - late Veggie veggie; - - /// Index of the current trivia question. - RestorableInt triviaIndex = RestorableInt(0); - - /// User's score on the current veggie. - RestorableInt score = RestorableInt(0); - - /// Trivia question currently being displayed. - Trivia get currentTrivia => veggie.trivia[triviaIndex.value]; - - /// The current state of the game. - _RestorablePlayerStatus status = - _RestorablePlayerStatus(PlayerStatus.readyToAnswer); - - @override - String? get restorationId => widget.restorationId; - - @override - void restoreState(RestorationBucket? oldBucket, bool initialRestore) { - registerForRestoration(triviaIndex, 'index'); - registerForRestoration(score, 'score'); - registerForRestoration(status, 'status'); - } - - // Called at init and again if any dependencies (read: InheritedWidgets) on - // on which this object relies are changed. - @override - void didChangeDependencies() { - super.didChangeDependencies(); - - final newAppState = Provider.of(context); - - setState(() { - appState = newAppState; - veggie = appState.getVeggie(widget.id); - }); - } - - // Called when the widget associated with this object is swapped out for a new - // one. If the new widget has a different Veggie ID value, the state object - // needs to do a little work to reset itself for the new Veggie. - @override - void didUpdateWidget(TriviaView oldWidget) { - super.didUpdateWidget(oldWidget); - - if (oldWidget.id != widget.id) { - setState(() { - veggie = appState.getVeggie(widget.id); - }); - - _resetGame(); - } - } - - @override - void dispose() { - triviaIndex.dispose(); - score.dispose(); - status.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - if (triviaIndex.value >= veggie.trivia.length) { - return _buildFinishedView(); - } else if (status.value == PlayerStatus.readyToAnswer) { - return _buildQuestionView(); - } else { - return _buildResultView(); - } - } - - void _resetGame() { - setState(() { - triviaIndex.value = 0; - score.value = 0; - status.value = PlayerStatus.readyToAnswer; - }); - } - - void _processAnswer(int answerIndex) { - setState(() { - if (answerIndex == currentTrivia.correctAnswerIndex) { - status.value = PlayerStatus.wasCorrect; - score.value++; - } else { - status.value = PlayerStatus.wasIncorrect; - } - }); - } - - // Widget shown when the game is over. It includes the score and a button to - // restart everything. - Widget _buildFinishedView() { - final themeData = CupertinoTheme.of(context); - - return Padding( - padding: const EdgeInsets.all(32), - child: Column( - children: [ - Text( - 'All done!', - style: Styles.triviaFinishedTitleText(themeData), - ), - const SizedBox(height: 16), - Text('You answered', style: themeData.textTheme.textStyle), - Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - textBaseline: TextBaseline.alphabetic, - children: [ - Text( - '${score.value}', - style: Styles.triviaFinishedBigText(themeData), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text(' of ', style: themeData.textTheme.textStyle), - ), - Text( - '${veggie.trivia.length}', - style: Styles.triviaFinishedBigText(themeData), - ), - ], - ), - Text('questions correctly!', style: themeData.textTheme.textStyle), - const SizedBox(height: 16), - CupertinoButton( - child: const Text('Try Again'), - onPressed: () => _resetGame(), - ), - ], - ), - ); - } - - // Presents the current trivia's question and answer choices. - Widget _buildQuestionView() { - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - const SizedBox(height: 16), - Text( - currentTrivia.question, - style: CupertinoTheme.of(context).textTheme.textStyle, - ), - const SizedBox(height: 32), - for (int i = 0; i < currentTrivia.answers.length; i++) - Padding( - padding: const EdgeInsets.all(8), - child: CupertinoButton( - color: CupertinoColors.activeBlue, - child: Text( - currentTrivia.answers[i], - textAlign: TextAlign.center, - ), - onPressed: () => _processAnswer(i), - ), - ), - ], - ), - ); - } - - // Shows whether the last answer was right or wrong and prompts the user to - // continue through the game. - Widget _buildResultView() { - return Padding( - padding: const EdgeInsets.all(32), - child: Column( - children: [ - Text( - status.value == PlayerStatus.wasCorrect - ? 'That\'s right!' - : 'Sorry, that wasn\'t the right answer.', - style: CupertinoTheme.of(context).textTheme.textStyle, - ), - const SizedBox(height: 16), - CupertinoButton( - child: const Text('Next Question'), - onPressed: () => setState(() { - triviaIndex.value++; - status.value = PlayerStatus.readyToAnswer; - }), - ), - ], - ), - ); - } -} - -class _RestorablePlayerStatus extends RestorableValue { - _RestorablePlayerStatus(this._defaultValue); - - final PlayerStatus _defaultValue; - - @override - PlayerStatus createDefaultValue() { - return _defaultValue; - } - - @override - PlayerStatus fromPrimitives(Object? data) { - return PlayerStatus.values[data as int]; - } - - @override - Object toPrimitives() { - return value.index; - } - - @override - void didUpdateValue(PlayerStatus? oldValue) { - notifyListeners(); - } -} diff --git a/veggieseasons/lib/widgets/veggie_card.dart b/veggieseasons/lib/widgets/veggie_card.dart index bf6dd154f..21ba68589 100644 --- a/veggieseasons/lib/widgets/veggie_card.dart +++ b/veggieseasons/lib/widgets/veggie_card.dart @@ -2,51 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui' as ui; - import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:veggieseasons/data/veggie.dart'; -import 'package:veggieseasons/styles.dart'; - -class FrostyBackground extends StatelessWidget { - const FrostyBackground({ - this.color, - this.intensity = 25, - this.child, - super.key, - }); - - final Color? color; - final double intensity; - final Widget? child; - - @override - Widget build(BuildContext context) { - return ClipRect( - child: BackdropFilter( - filter: ui.ImageFilter.blur(sigmaX: intensity, sigmaY: intensity), - child: DecoratedBox( - decoration: BoxDecoration( - color: color, - ), - child: child, - ), - ), - ); - } -} +import '../data/veggie.dart'; +import '../styles.dart'; /// A Card-like Widget that responds to tap events by animating changes to its /// elevation and invoking an optional [onPressed] callback. -class PressableCard extends StatefulWidget { +class PressableCard extends StatelessWidget { const PressableCard({ required this.child, - this.borderRadius = const BorderRadius.all(Radius.circular(5)), - this.upElevation = 2, - this.downElevation = 0, - this.shadowColor = CupertinoColors.black, - this.duration = const Duration(milliseconds: 100), + this.borderRadius = const BorderRadius.all(Radius.circular(16)), this.onPressed, super.key, }); @@ -57,42 +24,17 @@ class PressableCard extends StatefulWidget { final BorderRadius borderRadius; - final double upElevation; - - final double downElevation; - - final Color shadowColor; - - final Duration duration; - - @override - State createState() => _PressableCardState(); -} - -class _PressableCardState extends State { - bool cardIsDown = false; - @override Widget build(BuildContext context) { return GestureDetector( - onTap: () { - setState(() => cardIsDown = false); - if (widget.onPressed != null) { - widget.onPressed!(); - } - }, - onTapDown: (details) => setState(() => cardIsDown = true), - onTapCancel: () => setState(() => cardIsDown = false), - child: AnimatedPhysicalModel( - elevation: cardIsDown ? widget.downElevation : widget.upElevation, - borderRadius: widget.borderRadius, - shape: BoxShape.rectangle, - shadowColor: widget.shadowColor, - duration: widget.duration, - color: CupertinoColors.lightBackgroundGray, + onTap: onPressed, + child: Container( + decoration: BoxDecoration( + borderRadius: borderRadius, + ), child: ClipRRect( - borderRadius: widget.borderRadius, - child: widget.child, + borderRadius: borderRadius, + child: child, ), ), ); @@ -115,10 +57,10 @@ class VeggieCard extends StatelessWidget { Widget _buildDetails(BuildContext context) { final themeData = CupertinoTheme.of(context); - return FrostyBackground( - color: const Color(0x90ffffff), + return Container( + color: Colors.white, child: Padding( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.fromLTRB(20, 16, 16, 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -126,6 +68,7 @@ class VeggieCard extends StatelessWidget { veggie.name, style: Styles.cardTitleText(themeData), ), + const SizedBox(height: 8), Text( veggie.shortDescription, style: Styles.cardDescriptionText(themeData), diff --git a/veggieseasons/lib/widgets/veggie_headline.dart b/veggieseasons/lib/widgets/veggie_headline.dart index c0c00c300..2ef52bef5 100644 --- a/veggieseasons/lib/widgets/veggie_headline.dart +++ b/veggieseasons/lib/widgets/veggie_headline.dart @@ -4,8 +4,8 @@ import 'package:flutter/cupertino.dart'; import 'package:go_router/go_router.dart'; -import 'package:veggieseasons/data/veggie.dart'; -import 'package:veggieseasons/styles.dart'; +import '../data/veggie.dart'; +import '../styles.dart'; class ZoomClipAssetImage extends StatelessWidget { const ZoomClipAssetImage({ diff --git a/veggieseasons/lib/widgets/veggie_seasons_page.dart b/veggieseasons/lib/widgets/veggie_seasons_page.dart new file mode 100644 index 000000000..76f240219 --- /dev/null +++ b/veggieseasons/lib/widgets/veggie_seasons_page.dart @@ -0,0 +1,45 @@ +// Copyright 2024, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +class VeggieSeasonsPage extends Page { + final Widget child; + + const VeggieSeasonsPage({ + super.key, + required this.child, + super.restorationId, + }); + + @override + VeggieSeasonsPageRoute createRoute(BuildContext context) => + VeggieSeasonsPageRoute(this); +} + +class VeggieSeasonsPageRoute extends PageRoute { + VeggieSeasonsPageRoute(VeggieSeasonsPage page) : super(settings: page); + + VeggieSeasonsPage get _page => settings as VeggieSeasonsPage; + + @override + Color? get barrierColor => null; + + @override + String? get barrierLabel => null; + + @override + bool get maintainState => true; + + @override + Duration get transitionDuration => Duration.zero; + + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) => + _page.child; +}