You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
samples/veggieseasons/lib/widgets/trivia.dart

250 lines
6.8 KiB

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