diff --git a/lib/src/model/board_editor/board_editor_controller.dart b/lib/src/model/board_editor/board_editor_controller.dart index f0459c4c2d..2ac5f9fe4e 100644 --- a/lib/src/model/board_editor/board_editor_controller.dart +++ b/lib/src/model/board_editor/board_editor_controller.dart @@ -64,8 +64,25 @@ class BoardEditorController extends _$BoardEditorController { ); } - void loadFen(String fen) { - _updatePosition(readFen(fen).lock); + void loadFen( + String fen, { + bool loadSideToPlay = false, + bool loadCastling = false, + }) { + final splits = fen.split(' '); + _updatePosition(readFen(splits[0]).lock); + + if (loadSideToPlay && splits.length >= 2) { + setSideToPlay(splits[1].toLowerCase() == 'w' ? Side.white : Side.black); + } + + if (loadCastling && splits.length >= 3) { + final castling = splits[2]; + setCastling(Side.white, CastlingSide.king, castling.contains('K')); + setCastling(Side.white, CastlingSide.queen, castling.contains('Q')); + setCastling(Side.black, CastlingSide.king, castling.contains('k')); + setCastling(Side.black, CastlingSide.queen, castling.contains('q')); + } } /// Calculates the squares where an en passant capture could be possible. diff --git a/lib/src/view/board_editor/board_editor_screen.dart b/lib/src/view/board_editor/board_editor_screen.dart index 2012ab8195..e2b9937c08 100644 --- a/lib/src/view/board_editor/board_editor_screen.dart +++ b/lib/src/view/board_editor/board_editor_screen.dart @@ -14,10 +14,12 @@ import 'package:lichess_mobile/src/utils/navigation.dart'; import 'package:lichess_mobile/src/utils/screen.dart'; import 'package:lichess_mobile/src/utils/share.dart'; import 'package:lichess_mobile/src/view/analysis/analysis_screen.dart'; -import 'package:lichess_mobile/src/view/board_editor/board_editor_menu.dart'; +import 'package:lichess_mobile/src/view/board_editor/board_editor_settings.dart'; +import 'package:lichess_mobile/src/widgets/adaptive_action_sheet.dart'; import 'package:lichess_mobile/src/widgets/adaptive_bottom_sheet.dart'; import 'package:lichess_mobile/src/widgets/bottom_bar.dart'; import 'package:lichess_mobile/src/widgets/bottom_bar_button.dart'; +import 'package:lichess_mobile/src/widgets/buttons.dart'; import 'package:lichess_mobile/src/widgets/platform_scaffold.dart'; class BoardEditorScreen extends StatelessWidget { @@ -30,6 +32,21 @@ class BoardEditorScreen extends StatelessWidget { return PlatformScaffold( appBar: PlatformAppBar( title: Text(context.l10n.boardEditor), + actions: [ + AppBarIconButton( + onPressed: () => showAdaptiveBottomSheet( + context: context, + isScrollControlled: true, + showDragHandle: true, + isDismissible: true, + builder: (context) => BoardEditorPositionSettings( + initialFen: initialFen, + ), + ), + semanticsLabel: context.l10n.settingsSettings, + icon: const Icon(Icons.settings), + ), + ], ), body: _Body(initialFen), ); @@ -306,21 +323,39 @@ class _BottomBar extends ConsumerWidget { children: [ BottomBarButton( label: context.l10n.menu, - onTap: () => showAdaptiveBottomSheet( - context: context, - builder: (BuildContext context) => BoardEditorMenu( - initialFen: initialFen, - ), - ), - icon: Icons.tune, - ), - BottomBarButton( - key: const Key('flip-button'), - label: context.l10n.flipBoard, - onTap: ref - .read(boardEditorControllerProvider(initialFen).notifier) - .flipBoard, - icon: CupertinoIcons.arrow_2_squarepath, + key: const Key('board-editor-menu-button'), + onTap: () { + final editorProviderData = + ref.read(boardEditorControllerProvider(initialFen).notifier); + showAdaptiveActionSheet( + context: context, + actions: [ + BottomSheetAction( + makeLabel: (context) => Text(context.l10n.startPosition), + onPressed: (context) { + editorProviderData.loadFen( + kInitialFEN, + loadCastling: true, + loadSideToPlay: true, + ); + }, + ), + BottomSheetAction( + makeLabel: (context) => Text(context.l10n.clearBoard), + onPressed: (context) { + editorProviderData.loadFen(kEmptyBoardFEN); + }, + ), + BottomSheetAction( + makeLabel: (context) => Text(context.l10n.flipBoard), + onPressed: (context) { + editorProviderData.flipBoard(); + }, + ), + ], + ); + }, + icon: Icons.menu, ), BottomBarButton( label: context.l10n.analysis, diff --git a/lib/src/view/board_editor/board_editor_menu.dart b/lib/src/view/board_editor/board_editor_settings.dart similarity index 96% rename from lib/src/view/board_editor/board_editor_menu.dart rename to lib/src/view/board_editor/board_editor_settings.dart index f3b641bac1..d59c4450b1 100644 --- a/lib/src/view/board_editor/board_editor_menu.dart +++ b/lib/src/view/board_editor/board_editor_settings.dart @@ -6,8 +6,8 @@ import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/widgets/adaptive_bottom_sheet.dart'; -class BoardEditorMenu extends ConsumerWidget { - const BoardEditorMenu({required this.initialFen, super.key}); +class BoardEditorPositionSettings extends ConsumerWidget { + const BoardEditorPositionSettings({required this.initialFen, super.key}); final String? initialFen; diff --git a/test/view/board_editor/board_editor_screen_test.dart b/test/view/board_editor/board_editor_screen_test.dart index c5a5ded5f3..ff65a85dee 100644 --- a/test/view/board_editor/board_editor_screen_test.dart +++ b/test/view/board_editor/board_editor_screen_test.dart @@ -36,24 +36,62 @@ void main() { ); }); - testWidgets('Flip board', (tester) async { - final app = await buildTestApp( - tester, - home: const BoardEditorScreen(), - ); - await tester.pumpWidget(app); - - await tester.tap(find.byKey(const Key('flip-button'))); - await tester.pump(); - - expect( - tester - .widget( - find.byType(ChessboardEditor), - ) - .orientation, - Side.black, - ); + group('Board editor menu', () { + Future loadWidgetAndOpenMenu(WidgetTester tester) async { + final app = await buildTestApp( + tester, + home: const BoardEditorScreen(), + ); + await tester.pumpWidget(app); + + await tester.tap(find.byKey(const Key('board-editor-menu-button'))); + await tester.pump(); + } + + testWidgets('Flip board', (tester) async { + await loadWidgetAndOpenMenu(tester); + await tester.tap(find.text('Flip board')); + await tester.pump(); + + expect( + tester + .widget( + find.byType(ChessboardEditor), + ) + .orientation, + Side.black, + ); + }); + + testWidgets('Reset to Starting Position', (tester) async { + await loadWidgetAndOpenMenu(tester); + await tester.tap(find.text('Starting position')); + await tester.pump(); + + expect( + tester + .widget( + find.byType(ChessboardEditor), + ) + .pieces, + readFen(kInitialFEN), + ); + }); + + testWidgets('Clear board', (tester) async { + await loadWidgetAndOpenMenu(tester); + await tester.tap(find.text('Clear board')); + await tester.pump(); + + expect( + tester + .widget( + find.byType(ChessboardEditor), + ) + .pieces, + readFen(kEmptyBoardFEN), + ); + }); }); testWidgets('Side to play and castling rights', (tester) async { @@ -63,7 +101,6 @@ void main() { ); await tester.pumpWidget(app); - await tester.tap(find.byKey(const Key('flip-button'))); await tester.pump(); final container = ProviderScope.containerOf(