Teamspiele implementieren #217
@@ -86,6 +86,7 @@
|
|||||||
"match_name": "Spieltitel",
|
"match_name": "Spieltitel",
|
||||||
"match_profile": "Spielprofil",
|
"match_profile": "Spielprofil",
|
||||||
"matches": "Spiele",
|
"matches": "Spiele",
|
||||||
|
"member": "Mitglied",
|
||||||
"members": "Mitglieder",
|
"members": "Mitglieder",
|
||||||
"most_points": "Höchste Punkte",
|
"most_points": "Höchste Punkte",
|
||||||
"multiple_winners": "Mehrere Gewinner:innen",
|
"multiple_winners": "Mehrere Gewinner:innen",
|
||||||
|
|||||||
@@ -86,6 +86,7 @@
|
|||||||
"match_name": "Match name",
|
"match_name": "Match name",
|
||||||
"match_profile": "Match Profile",
|
"match_profile": "Match Profile",
|
||||||
"matches": "Matches",
|
"matches": "Matches",
|
||||||
|
"member": "Member",
|
||||||
"members": "Members",
|
"members": "Members",
|
||||||
"most_points": "Most Points",
|
"most_points": "Most Points",
|
||||||
"multiple_winners": "Multiple Winners",
|
"multiple_winners": "Multiple Winners",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:core' hide Match;
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_numeric_text/flutter_numeric_text.dart';
|
||||||
import 'package:fluttericon/rpg_awesome_icons.dart';
|
import 'package:fluttericon/rpg_awesome_icons.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tallee/core/common.dart';
|
import 'package:tallee/core/common.dart';
|
||||||
@@ -117,24 +118,58 @@ class _ManageMembersViewState extends State<ManageMembersView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void submitMatch() async {
|
Widget buildTeamTile({required Team team}) {
|
||||||
await db.matchDao.addMatch(match: widget.match);
|
final color = getColorFromGameColor(team.color);
|
||||||
if (mounted) {
|
final loc = AppLocalizations.of(context);
|
||||||
Navigator.pushAndRemoveUntil(
|
final length = team.members.length;
|
||||||
context,
|
final memberText = length == 1 ? loc.member : loc.members;
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => MatchResultView(
|
|
||||||
match: widget.match,
|
|
||||||
onWinnerChanged: widget.onWinnerChanged,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(route) => route.isFirst,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get allTeamsHaveMembers {
|
return Padding(
|
||||||
return teams.every((team) => team.members.isNotEmpty);
|
key: ValueKey(team.id),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Color circle
|
||||||
|
Container(
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
|
||||||
|
// Team name
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
team.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: CustomTheme.textColor,
|
||||||
|
fontSize: 17,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Member length
|
||||||
|
SizedBox(
|
||||||
|
width: 150,
|
||||||
|
child: NumericText(
|
||||||
|
'$length $memberText',
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: CustomTheme.hintColor,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterates through all teams and redistributes players randomly and
|
// Iterates through all teams and redistributes players randomly and
|
||||||
@@ -158,6 +193,63 @@ class _ManageMembersViewState extends State<ManageMembersView> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles moving a member from one team to another
|
||||||
|
void onReorder(int oldIndex, int newIndex) {
|
||||||
|
final sourceTeamIndex = teamIndexForFlat(oldIndex);
|
||||||
|
final sourceMemberIndex = memberIndexForFlat(oldIndex, sourceTeamIndex);
|
||||||
|
|
||||||
|
// Headers themselves can't be reordered.
|
||||||
|
if (sourceMemberIndex == -1) return;
|
||||||
|
|
||||||
|
// When moving down, the target index is shifted by 1
|
||||||
|
// because the item is removed first.
|
||||||
|
var targetIndex = newIndex;
|
||||||
|
if (newIndex > oldIndex) targetIndex -= 1;
|
||||||
|
targetIndex = targetIndex.clamp(0, allItemsCount - 1);
|
||||||
|
|
||||||
|
// Resolve target location based on the item currently at targetIndex
|
||||||
|
// before the move.
|
||||||
|
int destTeamIndex;
|
||||||
|
int insertPositionInTeam;
|
||||||
|
|
||||||
|
if (targetIndex >= allItemsCount - 1 && newIndex >= allItemsCount) {
|
||||||
|
// Dropped at the very end, append to the last team.
|
||||||
|
destTeamIndex = teams.length - 1;
|
||||||
|
insertPositionInTeam = teams[destTeamIndex].members.length;
|
||||||
|
} else {
|
||||||
|
destTeamIndex = teamIndexForFlat(targetIndex);
|
||||||
|
final anchorMemberIndex = memberIndexForFlat(targetIndex, destTeamIndex);
|
||||||
|
|
||||||
|
if (anchorMemberIndex == -1) {
|
||||||
|
// Dropped right before a header, append to the previous team.
|
||||||
|
destTeamIndex = destTeamIndex - 1;
|
||||||
|
if (destTeamIndex < 0) {
|
||||||
|
// Dropped above the very first header, stay in team 0 at top.
|
||||||
|
destTeamIndex = 0;
|
||||||
|
insertPositionInTeam = 0;
|
||||||
|
} else {
|
||||||
|
insertPositionInTeam = teams[destTeamIndex].members.length;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
insertPositionInTeam = anchorMemberIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
final sourceMembers = teams[sourceTeamIndex].members;
|
||||||
|
final player = sourceMembers.removeAt(sourceMemberIndex);
|
||||||
|
|
||||||
|
// Adjust insert index if removed from before the insert point in the
|
||||||
|
// same team.
|
||||||
|
if (sourceTeamIndex == destTeamIndex &&
|
||||||
|
insertPositionInTeam > sourceMembers.length) {
|
||||||
|
insertPositionInTeam = sourceMembers.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
teams[destTeamIndex].members.insert(insertPositionInTeam, player);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Total players + teams length
|
/// Total players + teams length
|
||||||
int get allItemsCount {
|
int get allItemsCount {
|
||||||
var count = 0;
|
var count = 0;
|
||||||
@@ -191,96 +283,23 @@ class _ManageMembersViewState extends State<ManageMembersView> {
|
|||||||
return localIndex == 0 ? -1 : localIndex - 1;
|
return localIndex == 0 ? -1 : localIndex - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onReorder(int oldIndex, int newIndex) {
|
bool get allTeamsHaveMembers =>
|
||||||
final sourceTeamIndex = teamIndexForFlat(oldIndex);
|
teams.every((team) => team.members.isNotEmpty);
|
||||||
final sourceMemberIndex = memberIndexForFlat(oldIndex, sourceTeamIndex);
|
|
||||||
|
|
||||||
// Headers themselves can't be reordered.
|
void submitMatch() async {
|
||||||
if (sourceMemberIndex == -1) return;
|
final match = widget.match.copyWith(teams: teams);
|
||||||
|
await db.matchDao.addMatch(match: match);
|
||||||
// Flutter convention: when moving down, the target index is shifted by 1
|
if (mounted) {
|
||||||
// because the item is removed first.
|
Navigator.pushAndRemoveUntil(
|
||||||
var targetIndex = newIndex;
|
context,
|
||||||
if (newIndex > oldIndex) targetIndex -= 1;
|
MaterialPageRoute(
|
||||||
targetIndex = targetIndex.clamp(0, allItemsCount - 1);
|
builder: (_) => MatchResultView(
|
||||||
|
match: match,
|
||||||
// Resolve target location based on the item currently at [targetIndex]
|
onWinnerChanged: widget.onWinnerChanged,
|
||||||
// *before* the move.
|
|
||||||
int destTeamIndex;
|
|
||||||
int insertPositionInTeam;
|
|
||||||
|
|
||||||
if (targetIndex >= allItemsCount - 1 && newIndex >= allItemsCount) {
|
|
||||||
// Dropped at the very end -> append to the last team.
|
|
||||||
destTeamIndex = teams.length - 1;
|
|
||||||
insertPositionInTeam = teams[destTeamIndex].members.length;
|
|
||||||
} else {
|
|
||||||
destTeamIndex = teamIndexForFlat(targetIndex);
|
|
||||||
final anchorMemberIndex = memberIndexForFlat(targetIndex, destTeamIndex);
|
|
||||||
|
|
||||||
if (anchorMemberIndex == -1) {
|
|
||||||
// Dropped right before a header -> append to the previous team.
|
|
||||||
destTeamIndex = destTeamIndex - 1;
|
|
||||||
if (destTeamIndex < 0) {
|
|
||||||
// Dropped above the very first header -> stay in team 0 at top.
|
|
||||||
destTeamIndex = 0;
|
|
||||||
insertPositionInTeam = 0;
|
|
||||||
} else {
|
|
||||||
insertPositionInTeam = teams[destTeamIndex].members.length;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
insertPositionInTeam = anchorMemberIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
final sourceMembers = teams[sourceTeamIndex].members;
|
|
||||||
final player = sourceMembers.removeAt(sourceMemberIndex);
|
|
||||||
|
|
||||||
// Adjust insert index if we removed from before the insert point in the
|
|
||||||
// same team.
|
|
||||||
if (sourceTeamIndex == destTeamIndex &&
|
|
||||||
insertPositionInTeam > sourceMembers.length) {
|
|
||||||
insertPositionInTeam = sourceMembers.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
teams[destTeamIndex].members.insert(insertPositionInTeam, player);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildTeamTile({required Team team}) {
|
|
||||||
final color = getColorFromGameColor(team.color);
|
|
||||||
return Padding(
|
|
||||||
key: ValueKey('header_${team.id}'),
|
|
||||||
padding: const EdgeInsets.fromLTRB(12, 16, 12, 8),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 14,
|
|
||||||
height: 14,
|
|
||||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
team.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: CustomTheme.textColor,
|
|
||||||
fontSize: 17,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
(route) => route.isFirst,
|
||||||
'${team.members.length}',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: CustomTheme.hintColor,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user