feat: implemented multiple winners with teams
This commit is contained in:
@@ -92,11 +92,10 @@ IconData getRulesetIcon(Ruleset ruleset) {
|
|||||||
case Ruleset.lowestScore:
|
case Ruleset.lowestScore:
|
||||||
return Icons.arrow_downward;
|
return Icons.arrow_downward;
|
||||||
case Ruleset.singleWinner:
|
case Ruleset.singleWinner:
|
||||||
|
case Ruleset.multipleWinners:
|
||||||
return Icons.emoji_events;
|
return Icons.emoji_events;
|
||||||
case Ruleset.singleLoser:
|
case Ruleset.singleLoser:
|
||||||
return Icons.sentiment_dissatisfied;
|
return Icons.sentiment_dissatisfied;
|
||||||
case Ruleset.multipleWinners:
|
|
||||||
return Icons.group;
|
|
||||||
case Ruleset.placement:
|
case Ruleset.placement:
|
||||||
return RpgAwesome.podium;
|
return RpgAwesome.podium;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,6 +268,21 @@ class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
|
|||||||
return await updateTeamScore(teamId: teamId, matchId: matchId, score: 1);
|
return await updateTeamScore(teamId: teamId, matchId: matchId, score: 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> setWinnerTeams({
|
||||||
|
required List<Team> winners,
|
||||||
|
required String matchId,
|
||||||
|
}) async {
|
||||||
|
List<bool?> success = List.generate(winners.length, (index) => null);
|
||||||
|
for (int i = 0; i < winners.length; i++) {
|
||||||
|
success[i] = await updateTeamScore(
|
||||||
|
teamId: winners[i].id,
|
||||||
|
matchId: matchId,
|
||||||
|
score: 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return success.every((result) => result == true);
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> removeWinnerTeam({
|
Future<bool> removeWinnerTeam({
|
||||||
required String teamId,
|
required String teamId,
|
||||||
required String matchId,
|
required String matchId,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tallee/core/common.dart';
|
import 'package:tallee/core/common.dart';
|
||||||
@@ -103,20 +105,7 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: isLiveEditMode
|
child: isLiveEditMode
|
||||||
// Live Edit Mode
|
// Live Edit Mode
|
||||||
? ListView.builder(
|
? buildLiveEditWidet(isTeamMatch)
|
||||||
itemCount: allPlayers.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return LiveEditListTile(
|
|
||||||
title: allPlayers[index].name,
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
controller[index].text = value.toString();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
value: int.tryParse(controller[index].text) ?? 0,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
// Normal Container
|
// Normal Container
|
||||||
: Container(
|
: Container(
|
||||||
margin: const EdgeInsets.symmetric(
|
margin: const EdgeInsets.symmetric(
|
||||||
@@ -150,35 +139,13 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
if (ruleset == Ruleset.multipleWinners)
|
if (ruleset == Ruleset.multipleWinners)
|
||||||
// TODO: Implement view for teams
|
// TODO: Implement view for teams
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: buildMultipleWinnerSelectionWidget(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
isTeamMatch,
|
||||||
itemCount: allPlayers.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return CustomCheckboxListTile(
|
|
||||||
text: allPlayers[index].name,
|
|
||||||
value: _selectedPlayers.contains(
|
|
||||||
allPlayers[index],
|
|
||||||
),
|
|
||||||
onChanged: (bool value) {
|
|
||||||
setState(() {
|
|
||||||
if (value) {
|
|
||||||
_selectedPlayers.add(
|
|
||||||
allPlayers[index],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
_selectedPlayers.remove(
|
|
||||||
allPlayers[index],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: buildWinnerSelectionWidget(isTeamMatch),
|
child: buildPlayerSelectionWidget(isTeamMatch),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Show score entry
|
// Show score entry
|
||||||
@@ -363,13 +330,24 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
|
|
||||||
/// Handles saving the (multiple) winners to the database.
|
/// Handles saving the (multiple) winners to the database.
|
||||||
Future<bool> _handleWinners() async {
|
Future<bool> _handleWinners() async {
|
||||||
if (_selectedPlayers.isEmpty) {
|
if (isTeamMatch) {
|
||||||
return await db.scoreEntryDao.removeWinner(matchId: widget.match.id);
|
if (_selectedTeams.isEmpty) {
|
||||||
|
return await db.scoreEntryDao.removeWinner(matchId: widget.match.id);
|
||||||
|
} else {
|
||||||
|
return await db.teamDao.setWinnerTeams(
|
||||||
|
matchId: widget.match.id,
|
||||||
|
winners: _selectedTeams.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return await db.scoreEntryDao.setWinners(
|
if (_selectedPlayers.isEmpty) {
|
||||||
matchId: widget.match.id,
|
return await db.scoreEntryDao.removeWinner(matchId: widget.match.id);
|
||||||
winners: allPlayers.where((p) => _selectedPlayers.contains(p)).toList(),
|
} else {
|
||||||
);
|
return await db.scoreEntryDao.setWinners(
|
||||||
|
matchId: widget.match.id,
|
||||||
|
winners: _selectedPlayers.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,7 +452,11 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
return ruleset == Ruleset.placement;
|
return ruleset == Ruleset.placement;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildTeamTile({required Team team, double? width}) {
|
Widget buildTeamTile({
|
||||||
|
required Team team,
|
||||||
|
double? width,
|
||||||
|
int showingPlayerAmount = 3,
|
||||||
|
}) {
|
||||||
return Container(
|
return Container(
|
||||||
width: width,
|
width: width,
|
||||||
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 2),
|
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 2),
|
||||||
@@ -498,7 +480,11 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
spacing: 4,
|
spacing: 4,
|
||||||
runSpacing: 4,
|
runSpacing: 4,
|
||||||
children: [
|
children: [
|
||||||
for (final member in team.members)
|
for (
|
||||||
|
int i = 0;
|
||||||
|
i < min(team.members.length, showingPlayerAmount);
|
||||||
|
i++
|
||||||
|
)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 4,
|
vertical: 4,
|
||||||
@@ -509,7 +495,23 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
member.name,
|
team.members[i].name,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: CustomTheme.textColor.withAlpha(180),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (team.members.length > 4)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 4,
|
||||||
|
horizontal: 4,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'+${team.members.length - showingPlayerAmount}',
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -525,7 +527,7 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildWinnerSelectionWidget(bool isTeamMatch) {
|
Widget buildPlayerSelectionWidget(bool isTeamMatch) {
|
||||||
if (isTeamMatch) {
|
if (isTeamMatch) {
|
||||||
return RadioGroup<Team>(
|
return RadioGroup<Team>(
|
||||||
groupValue: _selectedTeam,
|
groupValue: _selectedTeam,
|
||||||
@@ -604,7 +606,11 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
itemCount: allTeams.length,
|
itemCount: allTeams.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return ScoreListTile(
|
return ScoreListTile(
|
||||||
content: buildTeamTile(team: allTeams[index], width: 220),
|
content: buildTeamTile(
|
||||||
|
team: allTeams[index],
|
||||||
|
width: 220,
|
||||||
|
showingPlayerAmount: 2,
|
||||||
|
),
|
||||||
horizontalPadding: 0,
|
horizontalPadding: 0,
|
||||||
controller: controller[index],
|
controller: controller[index],
|
||||||
);
|
);
|
||||||
@@ -780,4 +786,86 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
|
|
||||||
return Row(children: [placementCol, valueCol]);
|
return Row(children: [placementCol, valueCol]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildMultipleWinnerSelectionWidget(bool isTeamMatch) {
|
||||||
|
if (isTeamMatch) {
|
||||||
|
return ListView.builder(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: allTeams.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return CustomCheckboxListTile(
|
||||||
|
content: buildTeamTile(team: allTeams[index]),
|
||||||
|
value: _selectedTeams.contains(allTeams[index]),
|
||||||
|
onChanged: (bool value) {
|
||||||
|
setState(() {
|
||||||
|
if (value) {
|
||||||
|
_selectedTeams.add(allTeams[index]);
|
||||||
|
} else {
|
||||||
|
_selectedTeams.remove(allTeams[index]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return ListView.builder(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: allPlayers.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return CustomCheckboxListTile(
|
||||||
|
content: Text(
|
||||||
|
allPlayers[index].name,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
value: _selectedPlayers.contains(allPlayers[index]),
|
||||||
|
onChanged: (bool value) {
|
||||||
|
setState(() {
|
||||||
|
if (value) {
|
||||||
|
_selectedPlayers.add(allPlayers[index]);
|
||||||
|
} else {
|
||||||
|
_selectedPlayers.remove(allPlayers[index]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildLiveEditWidet(bool isTeamMatch) {
|
||||||
|
if (isTeamMatch) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: allTeams.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return LiveEditListTile(
|
||||||
|
title: allTeams[index].name,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
controller[index].text = value.toString();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
value: int.tryParse(controller[index].text) ?? 0,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: allPlayers.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return LiveEditListTile(
|
||||||
|
title: allPlayers[index].name,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
controller[index].text = value.toString();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
value: int.tryParse(controller[index].text) ?? 0,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class CustomWidthButton extends StatelessWidget {
|
|||||||
onPressed!.call();
|
onPressed!.call();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
|
splashFactory: NoSplash.splashFactory,
|
||||||
foregroundColor: textcolor,
|
foregroundColor: textcolor,
|
||||||
disabledForegroundColor: disabledTextColor,
|
disabledForegroundColor: disabledTextColor,
|
||||||
backgroundColor: buttonBackgroundColor,
|
backgroundColor: buttonBackgroundColor,
|
||||||
@@ -91,6 +92,7 @@ class CustomWidthButton extends StatelessWidget {
|
|||||||
onPressed!.call();
|
onPressed!.call();
|
||||||
},
|
},
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
|
splashFactory: NoSplash.splashFactory,
|
||||||
foregroundColor: textcolor,
|
foregroundColor: textcolor,
|
||||||
disabledForegroundColor: disabledTextColor,
|
disabledForegroundColor: disabledTextColor,
|
||||||
backgroundColor: buttonBackgroundColor,
|
backgroundColor: buttonBackgroundColor,
|
||||||
@@ -128,6 +130,7 @@ class CustomWidthButton extends StatelessWidget {
|
|||||||
onPressed!.call();
|
onPressed!.call();
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
|
splashFactory: NoSplash.splashFactory,
|
||||||
foregroundColor: textcolor,
|
foregroundColor: textcolor,
|
||||||
disabledForegroundColor: disabledTextColor,
|
disabledForegroundColor: disabledTextColor,
|
||||||
backgroundColor: buttonBackgroundColor,
|
backgroundColor: buttonBackgroundColor,
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import 'package:tallee/core/custom_theme.dart';
|
|||||||
class CustomCheckboxListTile extends StatelessWidget {
|
class CustomCheckboxListTile extends StatelessWidget {
|
||||||
const CustomCheckboxListTile({
|
const CustomCheckboxListTile({
|
||||||
super.key,
|
super.key,
|
||||||
required this.text,
|
required this.content,
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String text;
|
final Widget content;
|
||||||
final bool value;
|
final bool value;
|
||||||
final ValueChanged<bool> onChanged;
|
final ValueChanged<bool> onChanged;
|
||||||
|
|
||||||
@@ -39,16 +39,7 @@ class CustomCheckboxListTile extends StatelessWidget {
|
|||||||
onChanged(v);
|
onChanged(v);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(child: content),
|
||||||
child: Text(
|
|
||||||
text,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -347,24 +347,27 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
if (widget.match.mvt.isEmpty) return '';
|
if (widget.match.mvt.isEmpty) return '';
|
||||||
final ruleset = widget.match.game.ruleset;
|
final ruleset = widget.match.game.ruleset;
|
||||||
|
|
||||||
if (ruleset == Ruleset.singleWinner) {
|
switch (ruleset) {
|
||||||
return '${loc.winner}: ${widget.match.mvt.first.name}';
|
case Ruleset.singleWinner:
|
||||||
} else if (ruleset == Ruleset.singleLoser) {
|
return '${loc.winner}: ${widget.match.mvt.first.name}';
|
||||||
return '${loc.loser}: ${widget.match.mvt.first.name}';
|
case Ruleset.singleLoser:
|
||||||
} else if (ruleset == Ruleset.highestScore ||
|
return '${loc.loser}: ${widget.match.mvt.first.name}';
|
||||||
ruleset == Ruleset.lowestScore) {
|
case Ruleset.highestScore:
|
||||||
final mvt = widget.match.mvt;
|
case Ruleset.lowestScore:
|
||||||
final mvtScore =
|
final mvt = widget.match.mvt;
|
||||||
widget.match.teams!
|
final mvtScore =
|
||||||
.firstWhere((team) => team.id == mvt.first.id)
|
widget.match.teams!
|
||||||
.score ??
|
.firstWhere((team) => team.id == mvt.first.id)
|
||||||
0;
|
.score ??
|
||||||
final mvtNames = mvt.map((team) => team.name).join(', ');
|
0;
|
||||||
return '${loc.winner}: $mvtNames (${getPointLabel(loc, mvtScore)})';
|
final mvtNames = mvt.map((team) => team.name).join(', ');
|
||||||
} else if (ruleset == Ruleset.placement) {
|
return '${loc.winner}: $mvtNames (${getPointLabel(loc, mvtScore)})';
|
||||||
return '${loc.winner}: ${widget.match.mvt.first.name}';
|
case Ruleset.placement:
|
||||||
|
return '${loc.winner}: ${widget.match.mvt.first.name}';
|
||||||
|
case Ruleset.multipleWinners:
|
||||||
|
final mvtNames = widget.match.mvt.map((team) => team.name).join(', ');
|
||||||
|
return '${loc.winners}: $mvtNames';
|
||||||
}
|
}
|
||||||
return '${loc.winner}: n.A.';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Icon getMvpIcon() {
|
Icon getMvpIcon() {
|
||||||
@@ -372,6 +375,7 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
|
|
||||||
switch (widget.match.game.ruleset) {
|
switch (widget.match.game.ruleset) {
|
||||||
case Ruleset.singleWinner:
|
case Ruleset.singleWinner:
|
||||||
|
case Ruleset.multipleWinners:
|
||||||
return Icon(icon, size: 20, color: Colors.amber);
|
return Icon(icon, size: 20, color: Colors.amber);
|
||||||
case Ruleset.singleLoser:
|
case Ruleset.singleLoser:
|
||||||
return Icon(icon, size: 20, color: Colors.blue);
|
return Icon(icon, size: 20, color: Colors.blue);
|
||||||
@@ -379,8 +383,6 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
return Icon(icon, size: 20, color: Colors.orange);
|
return Icon(icon, size: 20, color: Colors.orange);
|
||||||
case Ruleset.highestScore:
|
case Ruleset.highestScore:
|
||||||
return Icon(icon, size: 20, color: Colors.green);
|
return Icon(icon, size: 20, color: Colors.green);
|
||||||
case Ruleset.multipleWinners:
|
|
||||||
return Icon(icon, size: 20, color: Colors.amber);
|
|
||||||
case Ruleset.placement:
|
case Ruleset.placement:
|
||||||
return Icon(icon, size: 20, color: Colors.deepOrangeAccent);
|
return Icon(icon, size: 20, color: Colors.deepOrangeAccent);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: tallee
|
name: tallee
|
||||||
description: "Tracking App for Card Games"
|
description: "Tracking App for Card Games"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.0.30+316
|
version: 0.0.30+325
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.8.1
|
sdk: ^3.8.1
|
||||||
|
|||||||
Reference in New Issue
Block a user