Beta-Version 0.4.4 (#105)
* Update README.md * Tried new design for im- and export-button * Moved views to presentation folder * Moved widgets to presentation folder * Implemented CustomRowForm Widget * Used new custom form row * Removed double information * Refactored methods to private * Changed label * Modified paddings and text color * Changed string * Updated CustomFormRow padding and pressed handler * Implemented various new forms of CustomFormRow into SettingsView * Implemented VersionService * Updated strings, added wiki button * Corrected replaced string * Added import dialog feedback (got lost in refactoring) * Corrected function duplication * changed suffixWidget assignment and moved stepperKeys * Changed icons * Added rate_my_app package * Renamed folder * Implement native rating dialog * Implemented logic for pre rating and refactored rating dialog * updated launch mode * Small changes * Updated launch mode * Updated linting rules * Renamed folders * Changed l10n files location * Implemented new link constants * Changed privacy policy link * Corrected wiki link * Removed import * Updated links * Updated links to subdomains * Updated file paths * Updated strings * Updated identifiers * Added break in switch case * Updated strings * Implemented new popup * Corrected links * Changed color * Ensured rating dialog wont show in Beta * Refactoring * Adding const * Renamed variables * Corrected links * updated Dialog function * Added version number in about view * Changed order and corrected return * Changed translation * Changed popups because of unmounted context errors * corrected string typo * Replaced int constants with enums * Renamed Stepper to CustomStepper * Changed argument order * Reordered properties * Implemented empty builder for GraphView * Added jitterStip to prevent the graphs overlaying each other * Removed german comments * Added comment to jitter calculation * Overhauled comments in CustomTheme * Updated version * Added Delete all games button to Settings * Updated version * Updated en string * Updated RoundView buttons when game is finished * Changed lock emoji to CuperinoIcons.lock and placed it in trailing of app bar * Simplified comparison * Updated version * Corrected scaling * Updates constant names and lint rule * HOTFIX: Graph showed wrong data * Graph starts at round 0 now where all players have 0 points * Adjusted jitterStep * Removed dead code * Updated Y-Axis and removed values under y = 0 * Changed overflow mode * Replaced string & if statement with visibility widget * updated accessability of graph view * Changed string for GraphView title * Updated comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Updated generated files * Updated version in README --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
402
lib/presentation/views/round_view.dart
Normal file
402
lib/presentation/views/round_view.dart
Normal file
@@ -0,0 +1,402 @@
|
||||
import 'package:cabo_counter/core/custom_theme.dart';
|
||||
import 'package:cabo_counter/data/game_session.dart';
|
||||
import 'package:cabo_counter/l10n/generated/app_localizations.dart';
|
||||
import 'package:cabo_counter/services/local_storage_service.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
||||
|
||||
class RoundView extends StatefulWidget {
|
||||
final GameSession gameSession;
|
||||
final int roundNumber;
|
||||
const RoundView(
|
||||
{super.key, required this.roundNumber, required this.gameSession});
|
||||
|
||||
@override
|
||||
// ignore: library_private_types_in_public_api
|
||||
_RoundViewState createState() => _RoundViewState();
|
||||
}
|
||||
|
||||
class _RoundViewState extends State<RoundView> {
|
||||
/// The current game session.
|
||||
late GameSession gameSession = widget.gameSession;
|
||||
|
||||
/// Index of the player who said CABO.
|
||||
int _caboPlayerIndex = 0;
|
||||
|
||||
/// Index of the player who has Kamikaze.
|
||||
/// Default is null (no Kamikaze player).
|
||||
int? _kamikazePlayerIndex;
|
||||
|
||||
/// List of text controllers for the score text fields.
|
||||
late final List<TextEditingController> _scoreControllerList = List.generate(
|
||||
widget.gameSession.players.length,
|
||||
(index) => TextEditingController(),
|
||||
);
|
||||
|
||||
/// List of focus nodes for the score text fields.
|
||||
late final List<FocusNode> _focusNodeList = List.generate(
|
||||
widget.gameSession.players.length,
|
||||
(index) => FocusNode(),
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
print('=== Runde ${widget.roundNumber} geöffnet ===');
|
||||
if (widget.roundNumber < widget.gameSession.roundNumber ||
|
||||
widget.gameSession.isGameFinished == true) {
|
||||
print(
|
||||
'Diese wurde bereits gespielt, deshalb werden die alten Punktestaende angezeigt');
|
||||
|
||||
// If the current round has already been played, the text fields
|
||||
// are filled with the scores from this round
|
||||
for (int i = 0; i < _scoreControllerList.length; i++) {
|
||||
_scoreControllerList[i].text =
|
||||
gameSession.roundList[widget.roundNumber - 1].scores[i].toString();
|
||||
}
|
||||
_caboPlayerIndex =
|
||||
gameSession.roundList[widget.roundNumber - 1].caboPlayerIndex;
|
||||
_kamikazePlayerIndex =
|
||||
gameSession.roundList[widget.roundNumber - 1].kamikazePlayerIndex;
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
|
||||
|
||||
return CupertinoPageScaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
transitionBetweenRoutes: true,
|
||||
leading: CupertinoButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () =>
|
||||
{LocalStorageService.saveGameSessions(), Navigator.pop(context)},
|
||||
child: Text(AppLocalizations.of(context).cancel),
|
||||
),
|
||||
middle: Text(AppLocalizations.of(context).results),
|
||||
trailing: widget.gameSession.isGameFinished
|
||||
? const Icon(
|
||||
CupertinoIcons.lock,
|
||||
size: 25,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.only(bottom: 100 + bottomInset),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 40),
|
||||
Text(
|
||||
'${AppLocalizations.of(context).round} ${widget.roundNumber}',
|
||||
style: CustomTheme.roundTitle),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
AppLocalizations.of(context).who_said_cabo,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal:
|
||||
widget.gameSession.players.length > 3 ? 5 : 20,
|
||||
vertical: 10,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: CupertinoSegmentedControl<int>(
|
||||
unselectedColor: CustomTheme.backgroundTintColor,
|
||||
selectedColor: CustomTheme.primaryColor,
|
||||
groupValue: _caboPlayerIndex,
|
||||
children: Map.fromEntries(widget.gameSession.players
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) {
|
||||
final index = entry.key;
|
||||
final name = entry.value;
|
||||
return MapEntry(
|
||||
index,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 6,
|
||||
),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
name,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
})),
|
||||
onValueChanged: (value) {
|
||||
setState(() {
|
||||
_caboPlayerIndex = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: CupertinoListTile(
|
||||
title: Text(AppLocalizations.of(context).player),
|
||||
trailing: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).points))),
|
||||
const SizedBox(width: 20),
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child: Center(
|
||||
child: Text(AppLocalizations.of(context)
|
||||
.kamikaze))),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: widget.gameSession.players.length,
|
||||
itemBuilder: (context, index) {
|
||||
final name = widget.gameSession.players[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10, horizontal: 20),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: CupertinoListTile(
|
||||
backgroundColor: CupertinoColors.secondaryLabel,
|
||||
title: Row(children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))
|
||||
]),
|
||||
subtitle: Text(
|
||||
'${widget.gameSession.playerScores[index]}'
|
||||
' ${AppLocalizations.of(context).points}'),
|
||||
trailing: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: CupertinoTextField(
|
||||
maxLength: 3,
|
||||
focusNode: _focusNodeList[index],
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(
|
||||
signed: true,
|
||||
decimal: false,
|
||||
),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
],
|
||||
textInputAction: index ==
|
||||
widget.gameSession.players
|
||||
.length -
|
||||
1
|
||||
? TextInputAction.done
|
||||
: TextInputAction.next,
|
||||
controller: _scoreControllerList[index],
|
||||
placeholder:
|
||||
AppLocalizations.of(context).points,
|
||||
textAlign: TextAlign.center,
|
||||
onSubmitted: (_) =>
|
||||
_focusNextTextfield(index),
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 50),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_kamikazePlayerIndex =
|
||||
(_kamikazePlayerIndex == index)
|
||||
? null
|
||||
: index;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _kamikazePlayerIndex == index
|
||||
? CupertinoColors.systemRed
|
||||
: CupertinoColors
|
||||
.tertiarySystemFill,
|
||||
border: Border.all(
|
||||
color: _kamikazePlayerIndex == index
|
||||
? CupertinoColors.systemRed
|
||||
: CupertinoColors.systemGrey,
|
||||
),
|
||||
),
|
||||
child: _kamikazePlayerIndex == index
|
||||
? const Icon(
|
||||
CupertinoIcons.exclamationmark,
|
||||
size: 16,
|
||||
color: CupertinoColors.white,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 22),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: bottomInset,
|
||||
child: KeyboardVisibilityBuilder(builder: (context, visible) {
|
||||
if (!visible) {
|
||||
return Container(
|
||||
height: 80,
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
color: CustomTheme.backgroundTintColor,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
CupertinoButton(
|
||||
onPressed: _areRoundInputsValid()
|
||||
? () {
|
||||
_finishRound();
|
||||
LocalStorageService.saveGameSessions();
|
||||
Navigator.pop(context);
|
||||
}
|
||||
: null,
|
||||
child: Text(AppLocalizations.of(context).done),
|
||||
),
|
||||
if (!widget.gameSession.isGameFinished)
|
||||
CupertinoButton(
|
||||
onPressed: _areRoundInputsValid()
|
||||
? () {
|
||||
_finishRound();
|
||||
LocalStorageService.saveGameSessions();
|
||||
if (widget.gameSession.isGameFinished) {
|
||||
Navigator.pop(context);
|
||||
} else {
|
||||
Navigator.pop(
|
||||
context, widget.roundNumber + 1);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Text(AppLocalizations.of(context).next_round),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Focuses the next text field in the list of text fields.
|
||||
/// [index] is the index of the current text field.
|
||||
void _focusNextTextfield(int index) {
|
||||
if (index < widget.gameSession.players.length - 1) {
|
||||
FocusScope.of(context).requestFocus(_focusNodeList[index + 1]);
|
||||
} else {
|
||||
_focusNodeList[index].unfocus();
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the inputs for the round are valid.
|
||||
/// Returns true if the inputs are valid, false otherwise.
|
||||
/// Round Inputs are valid if every player has a score or
|
||||
/// kamikaze is selected for a player
|
||||
bool _areRoundInputsValid() {
|
||||
if (_areTextFieldsEmpty() && _kamikazePlayerIndex == null) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Checks if any of the text fields for the players points are empty.
|
||||
/// Returns true if any of the text fields is empty, false otherwise.
|
||||
bool _areTextFieldsEmpty() {
|
||||
for (TextEditingController t in _scoreControllerList) {
|
||||
if (t.text.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Finishes the current round.
|
||||
/// It first determines, ifCalls the [_calculateScoredPoints()] method to calculate the points for
|
||||
/// every player. If the round is the highest round played in this game,
|
||||
/// it expands the player score lists. At the end it updates the score
|
||||
/// array for the game.
|
||||
void _finishRound() {
|
||||
print('====================================');
|
||||
print('Runde ${widget.roundNumber} beendet');
|
||||
// The shown round is smaller than the newest round
|
||||
if (widget.roundNumber < widget.gameSession.roundNumber) {
|
||||
print('Da diese Runde bereits gespielt wurde, werden die alten '
|
||||
'Punktestaende ueberschrieben');
|
||||
}
|
||||
if (_kamikazePlayerIndex != null) {
|
||||
print('${widget.gameSession.players[_kamikazePlayerIndex!]} hat Kamikaze '
|
||||
'und bekommt 0 Punkte');
|
||||
print('Alle anderen Spieler bekommen 50 Punkte');
|
||||
widget.gameSession
|
||||
.applyKamikaze(widget.roundNumber, _kamikazePlayerIndex!);
|
||||
} else {
|
||||
List<int> roundScores = [];
|
||||
for (TextEditingController c in _scoreControllerList) {
|
||||
if (c.text.isNotEmpty) roundScores.add(int.parse(c.text));
|
||||
}
|
||||
widget.gameSession.calculateScoredPoints(
|
||||
widget.roundNumber, roundScores, _caboPlayerIndex);
|
||||
}
|
||||
widget.gameSession.updatePoints();
|
||||
if (widget.gameSession.isGameFinished == true) {
|
||||
print('Das Spiel ist beendet');
|
||||
} else if (widget.roundNumber == widget.gameSession.roundNumber) {
|
||||
widget.gameSession.increaseRound();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (final controller in _scoreControllerList) {
|
||||
controller.dispose();
|
||||
}
|
||||
for (final focusNode in _focusNodeList) {
|
||||
focusNode.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user