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].

<!-- Links -->
[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
pull/2422/head
Brett Morgan 1 year ago committed by GitHub
parent 61fed76690
commit bee21b55e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1 +1,5 @@
include: package:analysis_defaults/flutter.yaml
linter:
rules:
- prefer_relative_imports

@ -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<Veggie> _veggies;

@ -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<Veggie> veggies = [

@ -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.

@ -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<VeggieApp> 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<VeggieApp> 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<VeggieApp> 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<VeggieApp> 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<VeggieApp> with RestorationMixin {
final veggieId = int.parse(state.pathParameters['id']!);
return CupertinoPage(
restorationId: 'route.details',
fullscreenDialog: true,
child: DetailsScreen(
id: veggieId,
restorationId: 'details',

@ -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<DetailsScreen> createState() => _DetailsScreenState();
}
class _DetailsScreenState extends State<DetailsScreen> 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<DetailsScreen> with RestorationMixin {
}),
),
),
Positioned(
top: 16,
right: 16,
child: SafeArea(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
ShareButton(
() {
showCupertinoModalPopup<void>(
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<AppState>(context);
final veggie = appState.getVeggie(id);
return FavoriteButton(
() => appState.setFavorite(id, !veggie.isFavorite),
veggie.isFavorite,
);
}),
],
),
),
),
],
),
);
@ -296,41 +283,22 @@ class _DetailsScreenState extends State<DetailsScreen> with RestorationMixin {
Widget build(BuildContext context) {
final appState = Provider.of<AppState>(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<int>(
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),
],
),
],
),
),
],
),
);
}

@ -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});

@ -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',
),
],

@ -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<AppState>(context);
final prefs = Provider.of<Preferences>(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);
}

@ -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});

@ -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});

@ -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(

@ -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<CloseButton> createState() => _CloseButtonState();
State<_DetailPageButton> createState() => _DetailPageButtonState();
}
class _CloseButtonState extends State<CloseButton> {
class _DetailPageButtonState extends State<_DetailPageButton> {
bool tapInProgress = false;
@override
@ -111,7 +132,7 @@ class _CloseButtonState extends State<CloseButton> {
),
child: Center(
child: ColorChangingIcon(
CupertinoIcons.clear_thick,
widget.icon,
duration: const Duration(milliseconds: 300),
color: tapInProgress
? Styles.closeButtonPressed

@ -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<T> extends Page<T> {
final Widget child;
final Duration duration;
const FadeTransitionPage({
super.key,
required this.child,
this.duration = const Duration(milliseconds: 300),
super.restorationId,
});
@override
Route<T> createRoute(BuildContext context) =>
PageBasedFadeTransitionRoute<T>(this);
}
class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
PageBasedFadeTransitionRoute(FadeTransitionPage<T> page)
: super(settings: page);
FadeTransitionPage<T> get _page => settings as FadeTransitionPage<T>;
@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<double> animation,
Animation<double> secondaryAnimation,
) {
return _page.child;
}
@override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
final tween = CurveTween(curve: Curves.easeInOut);
return FadeTransition(
opacity: animation.drive(tween),
child: _page.child,
);
}
}

@ -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';

@ -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

@ -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<TriviaView> createState() => _TriviaViewState();
}
/// Possible states of the game.
enum PlayerStatus {
readyToAnswer,
wasCorrect,
wasIncorrect,
}
class _TriviaViewState extends State<TriviaView> 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<AppState>(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<PlayerStatus> {
_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();
}
}

@ -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<PressableCard> createState() => _PressableCardState();
}
class _PressableCardState extends State<PressableCard> {
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),

@ -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({

@ -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<T> extends Page<T> {
final Widget child;
const VeggieSeasonsPage({
super.key,
required this.child,
super.restorationId,
});
@override
VeggieSeasonsPageRoute<T> createRoute(BuildContext context) =>
VeggieSeasonsPageRoute<T>(this);
}
class VeggieSeasonsPageRoute<T> extends PageRoute<T> {
VeggieSeasonsPageRoute(VeggieSeasonsPage<T> page) : super(settings: page);
VeggieSeasonsPage<T> get _page => settings as VeggieSeasonsPage<T>;
@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<double> animation,
Animation<double> secondaryAnimation,
) =>
_page.child;
}
Loading…
Cancel
Save