diff --git a/lib/crossword/game/crossword_game.dart b/lib/crossword/game/crossword_game.dart index dd4833212..3b26b1d26 100644 --- a/lib/crossword/game/crossword_game.dart +++ b/lib/crossword/game/crossword_game.dart @@ -137,4 +137,21 @@ class CrosswordGame extends FlameGame with PanDetector { _updateVisibleSections(); } } + + void zoomOut() { + if (camera.viewfinder.zoom <= 0.05) { + return; + } + camera.viewport.position /= 1.05; + camera.viewfinder.zoom = camera.viewfinder.zoom - 0.05; + + _updateVisibleSections(); + } + + void zoomIn() { + camera.viewport.position *= 1.05; + camera.viewfinder.zoom += 0.05; + + _updateVisibleSections(); + } } diff --git a/lib/crossword/view/crossword_page.dart b/lib/crossword/view/crossword_page.dart index 8988df74e..ebb65ce0c 100644 --- a/lib/crossword/view/crossword_page.dart +++ b/lib/crossword/view/crossword_page.dart @@ -39,13 +39,65 @@ class CrosswordView extends StatelessWidget { } else if (state is CrosswordError) { child = const Center(child: Text('Error loading crossword')); } else if (state is CrosswordLoaded) { - child = GameWidget.controlled( - gameFactory: () => CrosswordGame( - context.read(), - ), - ); + child = const LoadedBoardView(); } return Scaffold(body: child); } } + +class LoadedBoardView extends StatefulWidget { + const LoadedBoardView({super.key}); + + @visibleForTesting + static const zoomInKey = Key('game_zoomIn'); + @visibleForTesting + static const zoomOutKey = Key('game_zoomOut'); + + @override + State createState() => LoadedBoardViewState(); +} + +@visibleForTesting +class LoadedBoardViewState extends State { + late final CrosswordGame game; + + @override + void initState() { + super.initState(); + + game = CrosswordGame(context.read()); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + GameWidget(game: game), + Positioned( + right: 16, + bottom: 16, + child: Column( + children: [ + ElevatedButton( + key: LoadedBoardView.zoomInKey, + onPressed: () { + game.zoomIn(); + }, + child: const Icon(Icons.zoom_in), + ), + const SizedBox(height: 16), + ElevatedButton( + key: LoadedBoardView.zoomOutKey, + onPressed: () { + game.zoomOut(); + }, + child: const Icon(Icons.zoom_out), + ), + ], + ), + ), + ], + ); + } +} diff --git a/test/crossword/game/crossword_game_test.dart b/test/crossword/game/crossword_game_test.dart index 814508bb8..4cbc9c5c6 100644 --- a/test/crossword/game/crossword_game_test.dart +++ b/test/crossword/game/crossword_game_test.dart @@ -321,5 +321,61 @@ void main() { expect(newCurrentSections.contains(subjectComponent), isFalse); }, ); + + testWithGame( + 'can zoom in', + createGame, + (game) async { + const state = CrosswordLoaded( + width: 40, + height: 40, + sectionSize: 400, + sections: {}, + ); + mockState(state); + + await game.ready(); + game.zoomIn(); + expect(game.camera.viewfinder.zoom, 1.05); + }, + ); + + testWithGame( + 'can zoom out', + createGame, + (game) async { + const state = CrosswordLoaded( + width: 40, + height: 40, + sectionSize: 400, + sections: {}, + ); + mockState(state); + + await game.ready(); + game.zoomOut(); + expect(game.camera.viewfinder.zoom, .95); + }, + ); + + testWithGame( + 'cannot zoom out more than 0.05', + createGame, + (game) async { + const state = CrosswordLoaded( + width: 40, + height: 40, + sectionSize: 400, + sections: {}, + ); + mockState(state); + + await game.ready(); + for (var i = 0; i < 100; i++) { + game.zoomOut(); + } + expect(game.camera.viewfinder.zoom, greaterThan(0)); + }, + ); }); } diff --git a/test/crossword/view/crossword_page_test.dart b/test/crossword/view/crossword_page_test.dart index 55b39923b..50a3165eb 100644 --- a/test/crossword/view/crossword_page_test.dart +++ b/test/crossword/view/crossword_page_test.dart @@ -88,5 +88,54 @@ void main() { await tester.pumpCrosswordView(bloc); expect(find.byType(GameWidget), findsOneWidget); }); + + testWidgets('can zoom in', (tester) async { + when(() => bloc.state).thenReturn( + CrosswordLoaded( + width: 40, + height: 40, + sectionSize: 40, + sections: const {}, + ), + ); + + await tester.pumpCrosswordView(bloc); + + final crosswordViewState = tester.state( + find.byType(LoadedBoardView), + ); + await crosswordViewState.game.loaded; + + await tester.tap(find.byKey(LoadedBoardView.zoomInKey)); + + expect( + crosswordViewState.game.camera.viewfinder.zoom, + greaterThan(1), + ); + }); + + testWidgets('can zoom out', (tester) async { + when(() => bloc.state).thenReturn( + CrosswordLoaded( + width: 40, + height: 40, + sectionSize: 40, + sections: const {}, + ), + ); + + await tester.pumpCrosswordView(bloc); + final crosswordViewState = tester.state( + find.byType(LoadedBoardView), + ); + await crosswordViewState.game.loaded; + + await tester.tap(find.byKey(LoadedBoardView.zoomOutKey)); + + expect( + crosswordViewState.game.camera.viewfinder.zoom, + lessThan(1), + ); + }); }); }