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