Merge branch 'development' into feature/168-teamspiele-implementieren
# Conflicts: # lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart # lib/presentation/views/main_menu/match_view/match_detail_view.dart # lib/presentation/views/main_menu/match_view/match_result_view.dart # lib/presentation/widgets/buttons/main_menu_button.dart # pubspec.yaml
This commit is contained in:
@@ -7,6 +7,7 @@ import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/models/game.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_game_view.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
|
||||
import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/game_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/top_centered_message.dart';
|
||||
@@ -70,7 +71,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
leading: HapticIconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(
|
||||
@@ -83,7 +84,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
HapticIconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () async {
|
||||
final result = await Navigator.push(
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/data/models/group.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
|
||||
import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/group_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/top_centered_message.dart';
|
||||
@@ -45,7 +46,7 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
leading: HapticIconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(
|
||||
@@ -111,7 +112,10 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
|
||||
padding: const EdgeInsets.only(bottom: 85),
|
||||
itemCount: filteredGroups.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return GestureDetector(
|
||||
return GroupTile(
|
||||
group: filteredGroups[index],
|
||||
isHighlighted:
|
||||
selectedGroupId == filteredGroups[index].id,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (selectedGroupId != filteredGroups[index].id) {
|
||||
@@ -121,11 +125,6 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
|
||||
}
|
||||
});
|
||||
},
|
||||
child: GroupTile(
|
||||
group: filteredGroups[index],
|
||||
isHighlighted:
|
||||
selectedGroupId == filteredGroups[index].id,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_popup/flutter_popup.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/common.dart';
|
||||
@@ -12,6 +13,7 @@ import 'package:tallee/data/models/game.dart';
|
||||
import 'package:tallee/data/models/group.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
|
||||
import 'package:tallee/presentation/widgets/dialog/custom_alert_dialog.dart';
|
||||
import 'package:tallee/presentation/widgets/dialog/custom_dialog_action.dart';
|
||||
import 'package:tallee/presentation/widgets/text_input/text_input_field.dart';
|
||||
@@ -47,9 +49,9 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
late final AppDatabase db;
|
||||
|
||||
late List<(Ruleset, String)> _rulesets;
|
||||
Ruleset? selectedRuleset = Ruleset.singleWinner;
|
||||
|
||||
late List<(GameColor, String)> _colors;
|
||||
|
||||
Ruleset? selectedRuleset = Ruleset.singleWinner;
|
||||
GameColor? selectedColor = GameColor.orange;
|
||||
|
||||
/// Controller for the game name input field.
|
||||
@@ -77,38 +79,20 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_rulesets = [
|
||||
(
|
||||
Ruleset.singleWinner,
|
||||
translateRulesetToString(Ruleset.singleWinner, context),
|
||||
_rulesets = List.generate(
|
||||
Ruleset.values.length,
|
||||
(index) => (
|
||||
Ruleset.values[index],
|
||||
translateRulesetToString(Ruleset.values[index], context),
|
||||
),
|
||||
(
|
||||
Ruleset.singleLoser,
|
||||
translateRulesetToString(Ruleset.singleLoser, context),
|
||||
);
|
||||
_colors = List.generate(
|
||||
GameColor.values.length,
|
||||
(index) => (
|
||||
GameColor.values[index],
|
||||
translateGameColorToString(GameColor.values[index], context),
|
||||
),
|
||||
(
|
||||
Ruleset.highestScore,
|
||||
translateRulesetToString(Ruleset.highestScore, context),
|
||||
),
|
||||
(
|
||||
Ruleset.lowestScore,
|
||||
translateRulesetToString(Ruleset.lowestScore, context),
|
||||
),
|
||||
(
|
||||
Ruleset.multipleWinners,
|
||||
translateRulesetToString(Ruleset.multipleWinners, context),
|
||||
),
|
||||
];
|
||||
_colors = [
|
||||
(GameColor.green, translateGameColorToString(GameColor.green, context)),
|
||||
(GameColor.teal, translateGameColorToString(GameColor.teal, context)),
|
||||
(GameColor.blue, translateGameColorToString(GameColor.blue, context)),
|
||||
(GameColor.purple, translateGameColorToString(GameColor.purple, context)),
|
||||
(GameColor.pink, translateGameColorToString(GameColor.pink, context)),
|
||||
(GameColor.red, translateGameColorToString(GameColor.red, context)),
|
||||
(GameColor.orange, translateGameColorToString(GameColor.orange, context)),
|
||||
(GameColor.yellow, translateGameColorToString(GameColor.yellow, context)),
|
||||
];
|
||||
);
|
||||
|
||||
if (widget.gameToEdit != null) {
|
||||
_gameNameController.text = widget.gameToEdit!.name;
|
||||
@@ -138,7 +122,7 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
title: Text(isEditing ? loc.edit_game : loc.create_game),
|
||||
actions: [
|
||||
if (isEditMode())
|
||||
IconButton(
|
||||
HapticIconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
if (!context.mounted) return;
|
||||
@@ -214,10 +198,13 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
|
||||
// Choose ruleset tile
|
||||
if (!isEditMode())
|
||||
ChooseTile(title: loc.ruleset, trailing: getColorDropdown(loc)),
|
||||
ChooseTile(
|
||||
title: loc.ruleset,
|
||||
trailing: getRulesetDropdown(loc),
|
||||
),
|
||||
|
||||
// Choose color tile
|
||||
ChooseTile(title: loc.color, trailing: getRulesetDropdown(loc)),
|
||||
ChooseTile(title: loc.color, trailing: getColorDropdown(loc)),
|
||||
|
||||
// Description input field
|
||||
Container(
|
||||
@@ -344,6 +331,12 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 10),
|
||||
barrierColor: Colors.transparent,
|
||||
contentDecoration: CustomTheme.standardBoxDecoration,
|
||||
onBeforePopup: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
},
|
||||
onAfterPopup: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
},
|
||||
content: StatefulBuilder(
|
||||
builder: (context, setPopupState) => SizedBox(
|
||||
width: 280,
|
||||
@@ -353,7 +346,8 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
children: List.generate(
|
||||
_rulesets.length,
|
||||
(index) => GestureDetector(
|
||||
onTap: () {
|
||||
onTap: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
setState(() {
|
||||
selectedRuleset = _rulesets[index].$1;
|
||||
});
|
||||
@@ -427,6 +421,12 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 10),
|
||||
barrierColor: Colors.transparent,
|
||||
contentDecoration: CustomTheme.standardBoxDecoration,
|
||||
onBeforePopup: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
},
|
||||
onAfterPopup: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
},
|
||||
content: StatefulBuilder(
|
||||
builder: (context, setPopupState) => SizedBox(
|
||||
width: 150,
|
||||
@@ -436,7 +436,8 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
children: List.generate(
|
||||
_colors.length,
|
||||
(index) => GestureDetector(
|
||||
onTap: () {
|
||||
onTap: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
setState(() {
|
||||
selectedColor = _colors[index].$1;
|
||||
});
|
||||
|
||||
@@ -273,11 +273,11 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
/// Determines whether the "Create Match" button should be enabled.
|
||||
///
|
||||
/// Returns `true` if:
|
||||
/// - A ruleset is selected AND
|
||||
/// - A game is selected AND
|
||||
/// - Either a group is selected OR at least 2 players are selected.
|
||||
bool isSubmitButtonEnabled() {
|
||||
return (selectedGroup != null ||
|
||||
(selectedPlayers.length > 1) && selectedGame != null);
|
||||
return ((selectedGroup != null || selectedPlayers.length > 1) &&
|
||||
selectedGame != null);
|
||||
}
|
||||
|
||||
/// Handles navigation when the create or save button is pressed.
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:tallee/data/models/team.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_match_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
||||
import 'package:tallee/presentation/widgets/cards/team_card.dart';
|
||||
import 'package:tallee/presentation/widgets/colored_icon_container.dart';
|
||||
@@ -69,7 +70,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
||||
appBar: AppBar(
|
||||
title: Text(loc.match_profile),
|
||||
actions: [
|
||||
IconButton(
|
||||
HapticIconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
showDialog<bool>(
|
||||
@@ -297,6 +298,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
||||
Widget getResultWidget(AppLocalizations loc) {
|
||||
if (isSingleRowResult()) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: getSingleResultRow(loc),
|
||||
);
|
||||
@@ -312,9 +314,12 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
||||
|
||||
if (localMatch.mvp.isNotEmpty || localMatch.mvt.isNotEmpty) {
|
||||
// Single Winner / Loser
|
||||
final mvpName = localMatch.isTeamMatch
|
||||
? localMatch.mvt.first.name
|
||||
: localMatch.mvp.first.name;
|
||||
final mvps = localMatch.isTeamMatch
|
||||
? localMatch.mvt
|
||||
: localMatch.mvp;
|
||||
final mvpName = ruleset == Ruleset.multipleWinners
|
||||
? mvps.map((party) => party.name).join(', ')
|
||||
: mvps.first.name;
|
||||
|
||||
return [
|
||||
Text(
|
||||
@@ -440,7 +445,8 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
||||
// Returns if the result can be displayed in a single row
|
||||
bool isSingleRowResult() {
|
||||
return localMatch.game.ruleset == Ruleset.singleWinner ||
|
||||
localMatch.game.ruleset == Ruleset.singleLoser;
|
||||
localMatch.game.ruleset == Ruleset.singleLoser ||
|
||||
localMatch.game.ruleset == Ruleset.multipleWinners;
|
||||
}
|
||||
|
||||
String getPlacementText(BuildContext context, int rank) {
|
||||
|
||||
@@ -10,6 +10,8 @@ import 'package:tallee/data/models/score_entry.dart';
|
||||
import 'package:tallee/data/models/team.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/match_result_view/custom_checkbox_list_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/match_result_view/custom_radio_list_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/match_result_view/live_edit_list_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/match_result_view/score_list_tile.dart';
|
||||
@@ -49,9 +51,11 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
|
||||
late bool isTeamMatch;
|
||||
|
||||
/// Currently selected winner player
|
||||
/// Currently selected player(s)/team(s) (winner / looser)
|
||||
Player? _selectedPlayer;
|
||||
Team? _selectedTeam;
|
||||
final Set<Player> _selectedPlayers = {};
|
||||
final Set<Team> _selectedTeams = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -84,11 +88,11 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
return Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
leading: HapticIconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
widget.onWinnerChanged?.call();
|
||||
Navigator.of(context).pop(_selectedPlayer);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
title: Text(widget.match.name),
|
||||
@@ -142,17 +146,46 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// Show player selection
|
||||
if (rulesetSupportsWinnerSelection())
|
||||
Expanded(
|
||||
child: buildWinnerSelectionWidget(isTeamMatch),
|
||||
),
|
||||
if (rulesetSupportsPlayerSelection())
|
||||
if (ruleset == Ruleset.multipleWinners)
|
||||
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],
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: buildWinnerSelectionWidget(isTeamMatch),
|
||||
),
|
||||
|
||||
// Show score entry
|
||||
if (rulesetSupportsScoreEntry())
|
||||
Expanded(child: buildScoreEntryWidget(isTeamMatch)),
|
||||
|
||||
// Show draggable placement list
|
||||
if (rulesetSupportsPlacement())
|
||||
if (rulesetSupportsDragBehaviour())
|
||||
Expanded(child: buildPlacementWidget(isTeamMatch)),
|
||||
],
|
||||
),
|
||||
@@ -207,16 +240,24 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
|
||||
// Prefill fields
|
||||
if (widget.match.mvt.isNotEmpty) {
|
||||
if (rulesetSupportsWinnerSelection()) {
|
||||
_selectedTeam = allTeams.firstWhere(
|
||||
(p) => p.id == widget.match.mvt.first.id,
|
||||
);
|
||||
if (rulesetSupportsPlayerSelection()) {
|
||||
if (ruleset == Ruleset.multipleWinners) {
|
||||
for (int i = 0; i < allTeams.length; i++) {
|
||||
if (allTeams[i].score == 1) {
|
||||
_selectedTeams.add(allTeams[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_selectedTeam = allTeams.firstWhere(
|
||||
(team) => team.id == widget.match.mvt.first.id,
|
||||
);
|
||||
}
|
||||
} else if (rulesetSupportsScoreEntry()) {
|
||||
for (int i = 0; i < allTeams.length; i++) {
|
||||
final score = allTeams[i].score;
|
||||
final score = allTeams[i].score ?? 0;
|
||||
controller[i].text = score.toString();
|
||||
}
|
||||
} else if (rulesetSupportsPlacement()) {
|
||||
} else if (rulesetSupportsDragBehaviour()) {
|
||||
allTeams.sort((a, b) {
|
||||
final scoreA = a.score ?? 0;
|
||||
final scoreB = b.score ?? 0;
|
||||
@@ -237,17 +278,25 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
|
||||
// Prefill fields
|
||||
if (widget.match.mvp.isNotEmpty) {
|
||||
if (rulesetSupportsWinnerSelection()) {
|
||||
_selectedPlayer = allPlayers.firstWhere(
|
||||
(p) => p.id == widget.match.mvp.first.id,
|
||||
);
|
||||
if (rulesetSupportsPlayerSelection()) {
|
||||
if (ruleset == Ruleset.multipleWinners) {
|
||||
for (int i = 0; i < allPlayers.length; i++) {
|
||||
if (widget.match.scores[allPlayers[i].id]?.score == 1) {
|
||||
_selectedPlayers.add(allPlayers[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_selectedPlayer = allPlayers.firstWhere(
|
||||
(p) => p.id == widget.match.mvp.first.id,
|
||||
);
|
||||
}
|
||||
} else if (rulesetSupportsScoreEntry()) {
|
||||
for (int i = 0; i < allPlayers.length; i++) {
|
||||
final scoreList = widget.match.scores[allPlayers[i].id];
|
||||
final score = scoreList?.score ?? 0;
|
||||
controller[i].text = score.toString();
|
||||
}
|
||||
} else if (rulesetSupportsPlacement()) {
|
||||
} else if (rulesetSupportsDragBehaviour()) {
|
||||
allPlayers.sort((a, b) {
|
||||
final scoreA = widget.match.scores[a.id]?.score ?? 0;
|
||||
final scoreB = widget.match.scores[b.id]?.score ?? 0;
|
||||
@@ -278,12 +327,14 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
await _handleScores();
|
||||
} else if (ruleset == Ruleset.placement) {
|
||||
await _handlePlacement();
|
||||
} else if (ruleset == Ruleset.multipleWinners) {
|
||||
await _handleWinners();
|
||||
}
|
||||
|
||||
widget.onWinnerChanged?.call();
|
||||
}
|
||||
|
||||
/// Handles saving or removing the winner in the database.
|
||||
/// Handles saving or removing the (single) winner in the database.
|
||||
Future<bool> _handleWinner() async {
|
||||
if (isTeamMatch) {
|
||||
if (_selectedTeam == null) {
|
||||
@@ -309,6 +360,18 @@ 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);
|
||||
} else {
|
||||
return await db.scoreEntryDao.setWinners(
|
||||
matchId: widget.match.id,
|
||||
winners: allPlayers.where((p) => _selectedPlayers.contains(p)).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles saving or removing the loser in the database.
|
||||
Future<bool> _handleLoser() async {
|
||||
if (isTeamMatch) {
|
||||
@@ -389,20 +452,24 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
return loc.select_loser;
|
||||
case Ruleset.placement:
|
||||
return loc.drag_to_set_placement;
|
||||
case Ruleset.multipleWinners:
|
||||
return loc.select_winners;
|
||||
default:
|
||||
return loc.enter_points;
|
||||
}
|
||||
}
|
||||
|
||||
bool rulesetSupportsWinnerSelection() {
|
||||
return ruleset == Ruleset.singleWinner || ruleset == Ruleset.singleLoser;
|
||||
bool rulesetSupportsPlayerSelection() {
|
||||
return ruleset == Ruleset.singleWinner ||
|
||||
ruleset == Ruleset.singleLoser ||
|
||||
ruleset == Ruleset.multipleWinners;
|
||||
}
|
||||
|
||||
bool rulesetSupportsScoreEntry() {
|
||||
return ruleset == Ruleset.lowestScore || ruleset == Ruleset.highestScore;
|
||||
}
|
||||
|
||||
bool rulesetSupportsPlacement() {
|
||||
bool rulesetSupportsDragBehaviour() {
|
||||
return ruleset == Ruleset.placement;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user