1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-10 14:58:34 +00:00

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
This commit is contained in:
Brett Morgan
2024-08-31 05:24:59 +10:00
committed by GitHub
parent 61fed76690
commit bee21b55e6
20 changed files with 219 additions and 550 deletions

View File

@@ -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});
final VoidCallback onPressed;
@override
State<CloseButton> createState() => _CloseButtonState();
/// A close button that invokes a callback when pressed.
class CloseButton extends _DetailPageButton {
const CloseButton(VoidCallback onPressed, {super.key})
: super(onPressed, CupertinoIcons.chevron_back);
}
class _CloseButtonState extends State<CloseButton> {
/// 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<_DetailPageButton> createState() => _DetailPageButtonState();
}
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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