From cb5036305ea278e53be742c555442b62b5327881 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Sat, 25 May 2019 16:22:29 -0700 Subject: [PATCH] More state cleanup --- web/slide_puzzle/lib/src/app_state.dart | 21 +---- .../lib/src/core/puzzle_animator.dart | 32 +------- .../lib/src/core/puzzle_proxy.dart | 25 ++++++ web/slide_puzzle/lib/src/puzzle_controls.dart | 17 ++++ .../lib/src/puzzle_flow_delegate.dart | 2 +- .../lib/src/puzzle_home_state.dart | 79 +++++++++++++------ web/slide_puzzle/lib/src/shared_theme.dart | 31 ++++---- web/slide_puzzle/lib/src/theme_plaster.dart | 14 ++-- web/slide_puzzle/lib/src/theme_seattle.dart | 8 +- web/slide_puzzle/lib/src/theme_simple.dart | 12 +-- 10 files changed, 134 insertions(+), 107 deletions(-) create mode 100644 web/slide_puzzle/lib/src/core/puzzle_proxy.dart create mode 100644 web/slide_puzzle/lib/src/puzzle_controls.dart diff --git a/web/slide_puzzle/lib/src/app_state.dart b/web/slide_puzzle/lib/src/app_state.dart index 30148812c..2d9f20d72 100644 --- a/web/slide_puzzle/lib/src/app_state.dart +++ b/web/slide_puzzle/lib/src/app_state.dart @@ -2,27 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'core/puzzle_animator.dart'; -import 'flutter.dart'; +import 'package:flutter_web/foundation.dart'; + +import 'core/puzzle_proxy.dart'; abstract class AppState { PuzzleProxy get puzzle; - bool get autoPlay; - - void setAutoPlay(bool newValue); - Listenable get animationNotifier; - - void clickOrShake(int tileValue) { - setAutoPlay(false); - puzzle.clickOrShake(tileValue); - } - - void Function(bool newValue) get setAutoPlayFunction { - if (puzzle.solved) { - return null; - } - return setAutoPlay; - } } diff --git a/web/slide_puzzle/lib/src/core/puzzle_animator.dart b/web/slide_puzzle/lib/src/core/puzzle_animator.dart index 897f05c3c..3088c7f8b 100644 --- a/web/slide_puzzle/lib/src/core/puzzle_animator.dart +++ b/web/slide_puzzle/lib/src/core/puzzle_animator.dart @@ -7,32 +7,7 @@ import 'dart:math' show Point, Random; import 'body.dart'; import 'puzzle.dart'; - -enum PuzzleEvent { click, reset, noop } - -abstract class PuzzleProxy { - int get width; - - int get height; - - int get length; - - bool get solved; - - void reset(); - - void clickOrShake(int tileValue); - - int get tileCount; - - int get clickCount; - - int get incorrectTiles; - - Point location(int index); - - bool isCorrectPosition(int value); -} +import 'puzzle_proxy.dart'; class PuzzleAnimator implements PuzzleProxy { final _rnd = Random(); @@ -61,13 +36,10 @@ class PuzzleAnimator implements PuzzleProxy { @override int get tileCount => _puzzle.tileCount; - @override int get incorrectTiles => _puzzle.incorrectTiles; - @override int get clickCount => _clickCount; - @override void reset() => _resetCore(); Stream get onEvent => _controller.stream; @@ -97,7 +69,7 @@ class PuzzleAnimator implements PuzzleProxy { _puzzle = _puzzle.clickRandom(vertical: _nextRandomVertical); _nextRandomVertical = !_nextRandomVertical; _clickCount++; - _controller.add(PuzzleEvent.click); + _controller.add(PuzzleEvent.random); } @override diff --git a/web/slide_puzzle/lib/src/core/puzzle_proxy.dart b/web/slide_puzzle/lib/src/core/puzzle_proxy.dart new file mode 100644 index 000000000..3ac3ea416 --- /dev/null +++ b/web/slide_puzzle/lib/src/core/puzzle_proxy.dart @@ -0,0 +1,25 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:math' show Point; + +enum PuzzleEvent { click, random, reset, noop } + +abstract class PuzzleProxy { + int get width; + + int get height; + + int get length; + + bool get solved; + + void clickOrShake(int tileValue); + + int get tileCount; + + Point location(int index); + + bool isCorrectPosition(int value); +} diff --git a/web/slide_puzzle/lib/src/puzzle_controls.dart b/web/slide_puzzle/lib/src/puzzle_controls.dart new file mode 100644 index 000000000..83be15310 --- /dev/null +++ b/web/slide_puzzle/lib/src/puzzle_controls.dart @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_web/foundation.dart'; + +abstract class PuzzleControls implements Listenable { + void reset(); + + int get clickCount; + + int get incorrectTiles; + + bool get autoPlay; + + void Function(bool newValue) get setAutoPlayFunction; +} diff --git a/web/slide_puzzle/lib/src/puzzle_flow_delegate.dart b/web/slide_puzzle/lib/src/puzzle_flow_delegate.dart index 5e8aea630..45bb63a32 100644 --- a/web/slide_puzzle/lib/src/puzzle_flow_delegate.dart +++ b/web/slide_puzzle/lib/src/puzzle_flow_delegate.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'core/puzzle_animator.dart'; +import 'core/puzzle_proxy.dart'; import 'flutter.dart'; class PuzzleFlowDelegate extends FlowDelegate { diff --git a/web/slide_puzzle/lib/src/puzzle_home_state.dart b/web/slide_puzzle/lib/src/puzzle_home_state.dart index 5d8c24c59..eb5a49968 100644 --- a/web/slide_puzzle/lib/src/puzzle_home_state.dart +++ b/web/slide_puzzle/lib/src/puzzle_home_state.dart @@ -8,12 +8,42 @@ import 'package:provider/provider.dart'; import 'app_state.dart'; import 'core/puzzle_animator.dart'; +import 'core/puzzle_proxy.dart'; import 'flutter.dart'; +import 'puzzle_controls.dart'; import 'puzzle_flow_delegate.dart'; import 'shared_theme.dart'; import 'themes.dart'; import 'value_tab_controller.dart'; +class _PuzzleControls extends ChangeNotifier implements PuzzleControls { + final PuzzleHomeState _parent; + + _PuzzleControls(this._parent); + + @override + bool get autoPlay => _parent._autoPlay; + + void _notify() => notifyListeners(); + + @override + void Function(bool newValue) get setAutoPlayFunction { + if (_parent.puzzle.solved) { + return null; + } + return _parent._setAutoPlay; + } + + @override + int get clickCount => _parent.puzzle.clickCount; + + @override + int get incorrectTiles => _parent.puzzle.incorrectTiles; + + @override + void reset() => _parent.puzzle.reset(); +} + class PuzzleHomeState extends State with TickerProviderStateMixin, AppState { @override final PuzzleAnimator puzzle; @@ -26,8 +56,8 @@ class PuzzleHomeState extends State with TickerProviderStateMixin, AppState { Duration _lastElapsed; StreamSubscription _puzzleEventSubscription; - @override - bool autoPlay = false; + bool _autoPlay = false; + _PuzzleControls _autoPlayListsenable; PuzzleHomeState(this.puzzle) { _puzzleEventSubscription = puzzle.onEvent.listen(_onPuzzleEvent); @@ -36,37 +66,31 @@ class PuzzleHomeState extends State with TickerProviderStateMixin, AppState { @override void initState() { super.initState(); + _autoPlayListsenable = _PuzzleControls(this); _ticker ??= createTicker(_onTick); _ensureTicking(); } - @override - void setAutoPlay(bool newValue) { - if (newValue != autoPlay) { + void _setAutoPlay(bool newValue) { + if (newValue != _autoPlay) { setState(() { // Only allow enabling autoPlay if the puzzle is not solved - autoPlay = newValue && !puzzle.solved; - if (autoPlay) { + _autoPlayListsenable._notify(); + _autoPlay = newValue && !puzzle.solved; + if (_autoPlay) { _ensureTicking(); } }); } } - bool _badHack; - @override Widget build(BuildContext context) => MultiProvider( providers: [ - Provider.value( - value: this, - updateShouldNotify: (p, c) { - if (c.autoPlay != _badHack) { - _badHack = c.autoPlay; - return true; - } - return false; - }), + Provider.value(value: this), + ListenableProvider.value( + listenable: _autoPlayListsenable, + ) ], child: Material( child: Stack( @@ -89,11 +113,16 @@ class PuzzleHomeState extends State with TickerProviderStateMixin, AppState { void dispose() { animationNotifier.dispose(); _ticker?.dispose(); + _autoPlayListsenable?.dispose(); _puzzleEventSubscription.cancel(); super.dispose(); } void _onPuzzleEvent(PuzzleEvent e) { + _autoPlayListsenable._notify(); + if (e != PuzzleEvent.random) { + _setAutoPlay(false); + } _tickerTimeSinceLastEvent = Duration.zero; _ensureTicking(); setState(() { @@ -126,18 +155,18 @@ class PuzzleHomeState extends State with TickerProviderStateMixin, AppState { if (!puzzle.stable) { animationNotifier.animate(); } else { - if (!autoPlay) { + if (!_autoPlay) { _ticker.stop(); _lastElapsed = null; } } - if (autoPlay && + if (_autoPlay && _tickerTimeSinceLastEvent > const Duration(milliseconds: 200)) { puzzle.playRandom(); if (puzzle.solved) { - setAutoPlay(false); + _setAutoPlay(false); } } } @@ -225,7 +254,7 @@ Widget _doBuildCore(bool small) => ValueTabController( children: List.generate( appState.puzzle.length, (i) => theme.tileButtonCore( - i, appState, small), + i, appState.puzzle, small), ), ), ), @@ -242,8 +271,10 @@ Widget _doBuildCore(bool small) => ValueTabController( top: 2, right: 10, ), - child: Row( - children: theme.bottomControls(appState)), + child: Consumer( + builder: (_, controls, __) => Row( + children: theme.bottomControls(controls)), + ), ) ], ), diff --git a/web/slide_puzzle/lib/src/shared_theme.dart b/web/slide_puzzle/lib/src/shared_theme.dart index e51a216dc..b49cbe5f4 100644 --- a/web/slide_puzzle/lib/src/shared_theme.dart +++ b/web/slide_puzzle/lib/src/shared_theme.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'app_state.dart'; -import 'core/puzzle_animator.dart'; +import 'core/puzzle_proxy.dart'; import 'flutter.dart'; +import 'puzzle_controls.dart'; import 'widgets/material_interior_alt.dart'; final puzzleAnimationDuration = kThemeAnimationDuration * 3; @@ -24,7 +24,7 @@ abstract class SharedTheme { EdgeInsetsGeometry tilePadding(PuzzleProxy puzzle) => const EdgeInsets.all(6); - Widget tileButton(int i, AppState appState, bool small); + Widget tileButton(int i, PuzzleProxy puzzle, bool small); Ink createInk( Widget child, { @@ -40,7 +40,7 @@ abstract class SharedTheme { ); Widget createButton( - AppState appState, + PuzzleProxy puzzle, bool small, int tileValue, Widget content, { @@ -49,12 +49,12 @@ abstract class SharedTheme { }) => AnimatedContainer( duration: puzzleAnimationDuration, - padding: tilePadding(appState.puzzle), + padding: tilePadding(puzzle), child: RaisedButton( elevation: 4, clipBehavior: Clip.hardEdge, animationDuration: puzzleAnimationDuration, - onPressed: () => appState.clickOrShake(tileValue), + onPressed: () => puzzle.clickOrShake(tileValue), shape: shape ?? puzzleBorder(small), padding: const EdgeInsets.symmetric(), child: content, @@ -76,22 +76,21 @@ abstract class SharedTheme { fontWeight: FontWeight.bold, ); - List bottomControls(AppState appState) => [ + List bottomControls(PuzzleControls controls) => [ IconButton( - onPressed: appState.puzzle.reset, + onPressed: controls.reset, icon: Icon(Icons.refresh, color: puzzleAccentColor), - //Icons.refresh, ), Checkbox( - value: appState.autoPlay, - onChanged: appState.setAutoPlayFunction, + value: controls.autoPlay, + onChanged: controls.setAutoPlayFunction, activeColor: puzzleAccentColor, ), Expanded( child: Container(), ), Text( - appState.puzzle.clickCount.toString(), + controls.clickCount.toString(), textAlign: TextAlign.right, style: _infoStyle, ), @@ -99,7 +98,7 @@ abstract class SharedTheme { SizedBox( width: 28, child: Text( - appState.puzzle.incorrectTiles.toString(), + controls.incorrectTiles.toString(), textAlign: TextAlign.right, style: _infoStyle, ), @@ -107,11 +106,11 @@ abstract class SharedTheme { const Text(' Tiles left ') ]; - Widget tileButtonCore(int i, AppState appState, bool small) { - if (i == appState.puzzle.tileCount && !appState.puzzle.solved) { + Widget tileButtonCore(int i, PuzzleProxy puzzle, bool small) { + if (i == puzzle.tileCount && !puzzle.solved) { return const Center(); } - return tileButton(i, appState, small); + return tileButton(i, puzzle, small); } } diff --git a/web/slide_puzzle/lib/src/theme_plaster.dart b/web/slide_puzzle/lib/src/theme_plaster.dart index 63ba5f0fb..ecdf7b133 100644 --- a/web/slide_puzzle/lib/src/theme_plaster.dart +++ b/web/slide_puzzle/lib/src/theme_plaster.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'app_state.dart'; +import 'core/puzzle_proxy.dart'; import 'flutter.dart'; import 'shared_theme.dart'; @@ -37,14 +37,14 @@ class ThemePlaster extends SharedTheme { ); @override - Widget tileButton(int i, AppState appState, bool small) { - final correctColumn = i % appState.puzzle.width; - final correctRow = i ~/ appState.puzzle.width; + Widget tileButton(int i, PuzzleProxy puzzle, bool small) { + final correctColumn = i % puzzle.width; + final correctRow = i ~/ puzzle.width; final primary = (correctColumn + correctRow).isEven; - if (i == appState.puzzle.tileCount) { - assert(appState.puzzle.solved); + if (i == puzzle.tileCount) { + assert(puzzle.solved); return Center( child: Icon( Icons.thumb_up, @@ -64,7 +64,7 @@ class ThemePlaster extends SharedTheme { ); return createButton( - appState, + puzzle, small, i, content, diff --git a/web/slide_puzzle/lib/src/theme_seattle.dart b/web/slide_puzzle/lib/src/theme_seattle.dart index acbf5ee69..c5e44158a 100644 --- a/web/slide_puzzle/lib/src/theme_seattle.dart +++ b/web/slide_puzzle/lib/src/theme_seattle.dart @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'app_state.dart'; -import 'core/puzzle_animator.dart'; +import 'core/puzzle_proxy.dart'; import 'flutter.dart'; import 'shared_theme.dart'; import 'widgets/decoration_image_plus.dart'; @@ -36,8 +35,7 @@ class ThemeSeattle extends SharedTheme { puzzle.solved ? const EdgeInsets.all(1) : const EdgeInsets.all(4); @override - Widget tileButton(int i, AppState appState, bool small) { - final puzzle = appState.puzzle; + Widget tileButton(int i, PuzzleProxy puzzle, bool small) { if (i == puzzle.tileCount && !puzzle.solved) { assert(puzzle.solved); } @@ -72,6 +70,6 @@ class ThemeSeattle extends SharedTheme { padding: EdgeInsets.all(small ? 20 : 32), ); - return createButton(appState, small, i, content); + return createButton(puzzle, small, i, content); } } diff --git a/web/slide_puzzle/lib/src/theme_simple.dart b/web/slide_puzzle/lib/src/theme_simple.dart index 1e1114fb8..70cc73037 100644 --- a/web/slide_puzzle/lib/src/theme_simple.dart +++ b/web/slide_puzzle/lib/src/theme_simple.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'app_state.dart'; +import 'core/puzzle_proxy.dart'; import 'flutter.dart'; import 'shared_theme.dart'; @@ -33,9 +33,9 @@ class ThemeSimple extends SharedTheme { ); @override - Widget tileButton(int i, AppState appState, bool small) { - if (i == appState.puzzle.tileCount) { - assert(appState.puzzle.solved); + Widget tileButton(int i, PuzzleProxy puzzle, bool small) { + if (i == puzzle.tileCount) { + assert(puzzle.solved); return const Center( child: Icon( Icons.thumb_up, @@ -45,7 +45,7 @@ class ThemeSimple extends SharedTheme { ); } - final correctPosition = appState.puzzle.isCorrectPosition(i); + final correctPosition = puzzle.isCorrectPosition(i); final content = createInk( Center( @@ -61,7 +61,7 @@ class ThemeSimple extends SharedTheme { ); return createButton( - appState, + puzzle, small, i, content,