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