State Restoration support for Veggie Seasons app (#433)

pull/582/head
Michael Goderbauer 4 years ago committed by GitHub
parent d30bfd59ec
commit ed1503143e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,42 @@
# Veggie Seasons
An iOS app that shows which fruits and vegetables are currently in season. It
showcases Flutter's Cupertino package.
[Available now in the App Store!](https://itunes.apple.com/is/app/veggie-seasons/id1450855435)
**NOTE:** While Flutter supports many platforms, this application is designed
specifically for iOS. It's not intended to be run on Android, web, or desktop.
## Goals
* Show how to build an interface that iOS users will feel right at home
with.
* Show how Flutter's Cupertino widgets work together.
## The important bits
### `/screens/*`
These are the screens presented in the app, roughly analogous to
UIViewControllers. `HomeScreen` is the root, and the others are shown
as the user navigates.
### `/widgets/search_bar.dart`
An example of how to construct an Cupertino-style search bar. The
Flutter team [is working on an official widget](https://github.com/flutter/flutter/issues/9784)
for this. Once that effort is complete, developers will not need to roll
their own search bars, so to speak.
## Questions/issues
If you have a general question about any of the techniques you see in
the sample, the best places to go are:
* [The FlutterDev Google Group](https://groups.google.com/forum/#!forum/flutter-dev)
* [The Flutter Gitter channel](https://gitter.im/flutter/flutter)
* [StackOverflow](https://stackoverflow.com/questions/tagged/flutter)
If you run into an issue with the sample itself, please file an issue
in the [main Flutter repo](https://github.com/flutter/flutter/issues).

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Before

Width:  |  Height:  |  Size: 305 KiB

After

Width:  |  Height:  |  Size: 305 KiB

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Before

Width:  |  Height:  |  Size: 281 KiB

After

Width:  |  Height:  |  Size: 281 KiB

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 271 KiB

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 187 KiB

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 198 KiB

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 214 KiB

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 225 KiB

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 274 KiB

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 196 KiB

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 190 KiB

@ -38,7 +38,14 @@ class AppState extends ChangeNotifier {
notifyListeners();
}
/// Used in tests to set the season independent of the current date.
static Season debugCurrentSeason;
static Season _getSeasonForDate(DateTime date) {
if (debugCurrentSeason != null) {
return debugCurrentSeason;
}
// Technically the start and end dates of seasons can vary by a day or so,
// but this is close enough for produce.
switch (date.month) {

@ -0,0 +1,89 @@
// Copyright 2018 The Flutter team. 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';
import 'package:flutter/services.dart' show DeviceOrientation, SystemChrome;
import 'package:provider/provider.dart';
import 'package:veggieseasons/data/app_state.dart';
import 'package:veggieseasons/data/preferences.dart';
import 'package:veggieseasons/screens/home.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
runApp(
RootRestorationScope(
restorationId: 'root',
child: VeggieApp(),
),
);
}
class VeggieApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => _VeggieAppState();
}
class _VeggieAppState extends State<VeggieApp> with RestorationMixin {
final _RestorableAppState _appState = _RestorableAppState();
@override
String get restorationId => 'wrapper';
@override
void restoreState(RestorationBucket oldBucket, bool initialRestore) {
registerForRestoration(_appState, 'state');
}
@override
void dispose() {
_appState.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: _appState.value,
),
ChangeNotifierProvider(
create: (_) => Preferences()..load(),
),
],
child: CupertinoApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(restorationId: 'home'),
restorationScopeId: 'app',
),
);
}
}
class _RestorableAppState extends RestorableListenable<AppState> {
@override
AppState createDefaultValue() {
return AppState();
}
@override
AppState fromPrimitives(Object data) {
final appState = AppState();
final favorites = (data as List<dynamic>).cast<int>();
for (var id in favorites) {
appState.setFavorite(id, true);
}
return appState;
}
@override
Object toPrimitives() {
return value.favoriteVeggies.map((veggie) => veggie.id).toList();
}
}

@ -238,15 +238,42 @@ class InfoView extends StatelessWidget {
class DetailsScreen extends StatefulWidget {
final int id;
final String restorationId;
DetailsScreen(this.id);
DetailsScreen({this.id, this.restorationId});
static String show(NavigatorState navigator, int veggieId) {
return navigator.restorablePush<void>(_routeBuilder, arguments: veggieId);
}
static Route<void> _routeBuilder(BuildContext context, Object arguments) {
final veggieId = arguments as int;
return CupertinoPageRoute(
builder: (context) => DetailsScreen(id: veggieId, restorationId: 'details'),
fullscreenDialog: true,
);
}
@override
_DetailsScreenState createState() => _DetailsScreenState();
}
class _DetailsScreenState extends State<DetailsScreen> {
int _selectedViewIndex = 0;
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);
@ -282,33 +309,37 @@ class _DetailsScreenState extends State<DetailsScreen> {
Widget build(BuildContext context) {
final appState = Provider.of<AppState>(context);
return CupertinoPageScaffold(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: ListView(
children: [
_buildHeader(context, appState),
SizedBox(height: 20),
CupertinoSegmentedControl<int>(
children: {
0: Text('Facts & Info'),
1: Text('Trivia'),
},
groupValue: _selectedViewIndex,
onValueChanged: (value) {
setState(() => _selectedViewIndex = value);
},
),
_selectedViewIndex == 0
? InfoView(widget.id)
: TriviaView(widget.id),
],
return UnmanagedRestorationScope(
bucket: bucket,
child: CupertinoPageScaffold(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: ListView(
restorationId: 'list',
children: [
_buildHeader(context, appState),
SizedBox(height: 20),
CupertinoSegmentedControl<int>(
children: {
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'),
],
),
),
),
],
],
),
),
);
}

@ -11,9 +11,14 @@ import 'package:veggieseasons/styles.dart';
import 'package:veggieseasons/widgets/veggie_headline.dart';
class FavoritesScreen extends StatelessWidget {
FavoritesScreen({this.restorationId, Key key}) : super(key: key);
final String restorationId;
@override
Widget build(BuildContext context) {
return CupertinoTabView(
restorationScopeId: restorationId,
builder: (context) {
final model = Provider.of<AppState>(context);
@ -32,6 +37,7 @@ class FavoritesScreen extends StatelessWidget {
),
)
: ListView(
restorationId: 'list',
children: [
SizedBox(height: 24),
for (Veggie veggie in model.favoriteVeggies)

@ -0,0 +1,55 @@
// Copyright 2018 The Flutter team. 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';
import 'package:flutter/widgets.dart';
import 'package:veggieseasons/screens/favorites.dart';
import 'package:veggieseasons/screens/list.dart';
import 'package:veggieseasons/screens/search.dart';
import 'package:veggieseasons/screens/settings.dart';
class HomeScreen extends StatelessWidget {
HomeScreen({Key key, this.restorationId}) : super(key: key);
final String restorationId;
@override
Widget build(BuildContext context) {
return RestorationScope(
restorationId: restorationId,
child: CupertinoTabScaffold(
restorationId: 'scaffold',
tabBar: CupertinoTabBar(items: [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.book),
label: 'My Garden',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.search),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings),
label: 'Settings',
),
]),
tabBuilder: (context, index) {
if (index == 0) {
return ListScreen(restorationId: 'list');
} else if (index == 1) {
return FavoritesScreen(restorationId: 'favorites');
} else if (index == 2) {
return SearchScreen(restorationId: 'search');
} else {
return SettingsScreen(restorationId: 'settings');
}
},
),
);
}
}

@ -13,6 +13,10 @@ import 'package:veggieseasons/styles.dart';
import 'package:veggieseasons/widgets/veggie_card.dart';
class ListScreen extends StatelessWidget {
ListScreen({this.restorationId, Key key}) : super(key: key);
final String restorationId;
Widget _generateVeggieRow(Veggie veggie, Preferences prefs,
{bool inSeason = true}) {
return Padding(
@ -29,6 +33,7 @@ class ListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoTabView(
restorationScopeId: restorationId,
builder: (context) {
var dateString = DateFormat('MMMM y').format(DateTime.now());
@ -38,6 +43,7 @@ class ListScreen extends StatelessWidget {
return SafeArea(
bottom: false,
child: ListView.builder(
restorationId: 'list',
itemCount: appState.allVeggies.length + 2,
itemBuilder: (context, index) {
if (index == 0) {

@ -12,36 +12,45 @@ import 'package:veggieseasons/widgets/search_bar.dart';
import 'package:veggieseasons/widgets/veggie_headline.dart';
class SearchScreen extends StatefulWidget {
SearchScreen({this.restorationId, Key key}) : super(key: key);
final String restorationId;
@override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
final controller = TextEditingController();
class _SearchScreenState extends State<SearchScreen> with RestorationMixin {
final controller = RestorableTextEditingController();
final focusNode = FocusNode();
String terms = '';
String terms;
@override
String get restorationId => widget.restorationId;
@override
void initState() {
super.initState();
void restoreState(RestorationBucket oldBucket, bool initialRestore) {
registerForRestoration(controller, 'text');
controller.addListener(_onTextChanged);
terms = controller.value.text;
}
@override
void dispose() {
focusNode.dispose();
controller.dispose();
super.dispose();
}
void _onTextChanged() {
setState(() => terms = controller.text);
setState(() => terms = controller.value.text);
}
Widget _createSearchBox() {
return Padding(
padding: const EdgeInsets.all(8),
child: SearchBar(
controller: controller,
controller: controller.value,
focusNode: focusNode,
),
);
@ -61,6 +70,7 @@ class _SearchScreenState extends State<SearchScreen> {
}
return ListView.builder(
restorationId: 'list',
itemCount: veggies.length + 1,
itemBuilder: (context, i) {
if (i == 0) {
@ -88,18 +98,20 @@ class _SearchScreenState extends State<SearchScreen> {
Widget build(BuildContext context) {
final model = Provider.of<AppState>(context);
return CupertinoTabView(
builder: (context) {
return SafeArea(
bottom: false,
child: Stack(
children: [
_buildSearchResults(model.searchVeggies(terms)),
_createSearchBox(),
],
),
);
},
return UnmanagedRestorationScope(
child: CupertinoTabView(
builder: (context) {
return SafeArea(
bottom: false,
child: Stack(
children: [
_buildSearchResults(model.searchVeggies(terms)),
_createSearchBox(),
],
),
);
},
),
);
}
}

@ -0,0 +1,250 @@
// Copyright 2018 The Flutter team. 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';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.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';
class VeggieCategorySettingsScreen extends StatelessWidget {
VeggieCategorySettingsScreen({Key key, this.restorationId}) : super(key: key);
final String restorationId;
static String show(NavigatorState navigator) {
return navigator.restorablePush(_routeBuilder);
}
static Route<void> _routeBuilder(BuildContext context, Object argument) {
return CupertinoPageRoute(
builder: (context) => VeggieCategorySettingsScreen(restorationId: 'category'),
title: 'Preferred Categories',
);
}
@override
Widget build(BuildContext context) {
final model = Provider.of<Preferences>(context);
final currentPrefs = model.preferredCategories;
var brightness = CupertinoTheme.brightnessOf(context);
return RestorationScope(
restorationId: restorationId,
child: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Preferred Categories'),
previousPageTitle: 'Settings',
),
backgroundColor: Styles.scaffoldBackground(brightness),
child: FutureBuilder<Set<VeggieCategory>>(
future: currentPrefs,
builder: (context, snapshot) {
final items = <SettingsItem>[];
for (final category in VeggieCategory.values) {
CupertinoSwitch toggle;
// It's possible that category data hasn't loaded from shared prefs
// yet, so display it if possible and fall back to disabled switches
// otherwise.
if (snapshot.hasData) {
toggle = CupertinoSwitch(
value: snapshot.data.contains(category),
onChanged: (value) {
if (value) {
model.addPreferredCategory(category);
} else {
model.removePreferredCategory(category);
}
},
);
} else {
toggle = CupertinoSwitch(
value: false,
onChanged: null,
);
}
items.add(SettingsItem(
label: veggieCategoryNames[category],
content: toggle,
));
}
return ListView(
restorationId: 'list',
children: [
SettingsGroup(
items: items,
),
],
);
},
),
),
);
}
}
class CalorieSettingsScreen extends StatelessWidget {
CalorieSettingsScreen({Key key, this.restorationId}) : super(key: key);
final String restorationId;
static const max = 1000;
static const min = 2600;
static const step = 200;
static String show(NavigatorState navigator) {
return navigator.restorablePush(_routeBuilder);
}
static Route<void> _routeBuilder(BuildContext context, Object argument) {
return CupertinoPageRoute<void>(
builder: (context) => CalorieSettingsScreen(restorationId: 'calorie'),
title: 'Calorie Target',
);
}
@override
Widget build(BuildContext context) {
final model = Provider.of<Preferences>(context);
var brightness = CupertinoTheme.brightnessOf(context);
return RestorationScope(
restorationId: restorationId,
child: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
previousPageTitle: 'Settings',
),
backgroundColor: Styles.scaffoldBackground(brightness),
child: ListView(
restorationId: 'list',
children: [
FutureBuilder<int>(
future: model.desiredCalories,
builder: (context, snapshot) {
final steps = <SettingsItem>[];
for (var cals = max; cals < min; cals += step) {
steps.add(
SettingsItem(
label: cals.toString(),
icon: SettingsIcon(
icon: Styles.checkIcon,
foregroundColor: snapshot.hasData && snapshot.data == cals
? CupertinoColors.activeBlue
: Styles.transparentColor,
backgroundColor: Styles.transparentColor,
),
onPress: snapshot.hasData
? () => model.setDesiredCalories(cals)
: null,
),
);
}
return SettingsGroup(
items: steps,
header: SettingsGroupHeader('Available calorie levels'),
footer: SettingsGroupFooter('These are used for serving '
'calculations'),
);
},
),
],
),
),
);
}
}
class SettingsScreen extends StatelessWidget {
SettingsScreen({this.restorationId, Key key}) : super(key: key);
final String restorationId;
SettingsItem _buildCaloriesItem(BuildContext context, Preferences prefs) {
return SettingsItem(
label: 'Calorie Target',
icon: SettingsIcon(
backgroundColor: Styles.iconBlue,
icon: Styles.calorieIcon,
),
content: FutureBuilder<int>(
future: prefs.desiredCalories,
builder: (context, snapshot) {
return Row(
children: [
Text(
snapshot.data?.toString() ?? '',
style: CupertinoTheme.of(context).textTheme.textStyle,
),
SizedBox(width: 8),
SettingsNavigationIndicator(),
],
);
},
),
onPress: () {
CalorieSettingsScreen.show(Navigator.of(context));
},
);
}
SettingsItem _buildCategoriesItem(BuildContext context, Preferences prefs) {
return SettingsItem(
label: 'Preferred Categories',
subtitle: 'What types of veggies you prefer!',
icon: SettingsIcon(
backgroundColor: Styles.iconGold,
icon: Styles.preferenceIcon,
),
content: SettingsNavigationIndicator(),
onPress: () {
VeggieCategorySettingsScreen.show(Navigator.of(context));
},
);
}
@override
Widget build(BuildContext context) {
final prefs = Provider.of<Preferences>(context);
return RestorationScope(
restorationId: restorationId,
child: CupertinoPageScaffold(
child: Container(
color: Styles.scaffoldBackground(CupertinoTheme.brightnessOf(context)),
child: CustomScrollView(
restorationId: 'list',
slivers: <Widget>[
CupertinoSliverNavigationBar(
largeTitle: Text('Settings'),
),
SliverSafeArea(
top: false,
sliver: SliverList(
delegate: SliverChildListDelegate(
<Widget>[
SettingsGroup(
items: [
_buildCaloriesItem(context, prefs),
_buildCategoriesItem(context, prefs),
],
),
],
),
),
),
],
),
),
),
);
}
}

@ -9,8 +9,9 @@ import 'package:veggieseasons/styles.dart';
/// the user's score.
class TriviaView extends StatefulWidget {
final int id;
final String restorationId;
const TriviaView(this.id);
const TriviaView({this.id, this.restorationId});
@override
_TriviaViewState createState() => _TriviaViewState();
@ -23,7 +24,7 @@ enum PlayerStatus {
wasIncorrect,
}
class _TriviaViewState extends State<TriviaView> {
class _TriviaViewState extends State<TriviaView> with RestorationMixin {
/// Current app state. This is used to fetch veggie data.
AppState appState;
@ -31,16 +32,26 @@ class _TriviaViewState extends State<TriviaView> {
Veggie veggie;
/// Index of the current trivia question.
int triviaIndex = 0;
RestorableInt triviaIndex = RestorableInt(0);
/// User's score on the current veggie.
int score = 0;
RestorableInt score = RestorableInt(0);
/// Trivia question currently being displayed.
Trivia get currentTrivia => veggie.trivia[triviaIndex];
Trivia get currentTrivia => veggie.trivia[triviaIndex.value];
/// The current state of the game.
PlayerStatus status = PlayerStatus.readyToAnswer;
_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.
@ -72,11 +83,19 @@ class _TriviaViewState extends State<TriviaView> {
}
}
@override
void dispose() {
triviaIndex.dispose();
score.dispose();
status.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (triviaIndex >= veggie.trivia.length) {
if (triviaIndex.value >= veggie.trivia.length) {
return _buildFinishedView();
} else if (status == PlayerStatus.readyToAnswer) {
} else if (status.value == PlayerStatus.readyToAnswer) {
return _buildQuestionView();
} else {
return _buildResultView();
@ -85,19 +104,19 @@ class _TriviaViewState extends State<TriviaView> {
void _resetGame() {
setState(() {
triviaIndex = 0;
score = 0;
status = PlayerStatus.readyToAnswer;
triviaIndex.value = 0;
score.value = 0;
status.value = PlayerStatus.readyToAnswer;
});
}
void _processAnswer(int answerIndex) {
setState(() {
if (answerIndex == currentTrivia.correctAnswerIndex) {
status = PlayerStatus.wasCorrect;
score++;
status.value = PlayerStatus.wasCorrect;
score.value++;
} else {
status = PlayerStatus.wasIncorrect;
status.value = PlayerStatus.wasIncorrect;
}
});
}
@ -193,7 +212,7 @@ class _TriviaViewState extends State<TriviaView> {
child: Column(
children: [
Text(
status == PlayerStatus.wasCorrect
status.value == PlayerStatus.wasCorrect
? 'That\'s right!'
: 'Sorry, that wasn\'t the right answer.',
style: CupertinoTheme.of(context).textTheme.textStyle,
@ -202,8 +221,8 @@ class _TriviaViewState extends State<TriviaView> {
CupertinoButton(
child: Text('Next Question'),
onPressed: () => setState(() {
triviaIndex++;
status = PlayerStatus.readyToAnswer;
triviaIndex.value++;
status.value = PlayerStatus.readyToAnswer;
}),
),
],
@ -211,3 +230,29 @@ class _TriviaViewState extends State<TriviaView> {
);
}
}
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();
}
}

@ -142,12 +142,7 @@ class VeggieCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return PressableCard(
onPressed: () {
Navigator.of(context).push<void>(CupertinoPageRoute(
builder: (context) => DetailsScreen(veggie.id),
fullscreenDialog: true,
));
},
onPressed: () => DetailsScreen.show(Navigator.of(context), veggie.id),
child: Stack(
children: [
Semantics(

@ -70,10 +70,7 @@ class VeggieHeadline extends StatelessWidget {
final themeData = CupertinoTheme.of(context);
return GestureDetector(
onTap: () => Navigator.of(context).push<void>(CupertinoPageRoute(
builder: (context) => DetailsScreen(veggie.id),
fullscreenDialog: true,
)),
onTap: () => DetailsScreen.show(Navigator.of(context), veggie.id),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save