From 0e50bbd6c968b37338cd33c5152f8e87a5911aa8 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Thu, 23 May 2019 18:05:12 -0700 Subject: [PATCH] slide_puzzle: use pkg:provider --- web/slide_puzzle/lib/main.dart | 2 +- web/slide_puzzle/lib/src/app_state.dart | 25 +- .../lib/src/puzzle_home_state.dart | 65 ++--- web/slide_puzzle/lib/src/shared_theme.dart | 259 +++++++++--------- web/slide_puzzle/lib/src/theme_plaster.dart | 16 +- web/slide_puzzle/lib/src/theme_seattle.dart | 13 +- web/slide_puzzle/lib/src/theme_simple.dart | 15 +- web/slide_puzzle/lib/src/themes.dart | 9 + web/slide_puzzle/pubspec.lock | 9 + web/slide_puzzle/pubspec.yaml | 5 + 10 files changed, 205 insertions(+), 213 deletions(-) create mode 100644 web/slide_puzzle/lib/src/themes.dart diff --git a/web/slide_puzzle/lib/main.dart b/web/slide_puzzle/lib/main.dart index 261c3d06a..fa2bc3a0d 100644 --- a/web/slide_puzzle/lib/main.dart +++ b/web/slide_puzzle/lib/main.dart @@ -25,7 +25,7 @@ class PuzzleApp extends StatelessWidget { class _PuzzleHome extends StatefulWidget { final int _rows, _columns; - const _PuzzleHome(this._rows, this._columns, {Key key}) : super(key: key); + const _PuzzleHome(this._rows, this._columns); @override PuzzleHomeState createState() => diff --git a/web/slide_puzzle/lib/src/app_state.dart b/web/slide_puzzle/lib/src/app_state.dart index a94c31c94..30148812c 100644 --- a/web/slide_puzzle/lib/src/app_state.dart +++ b/web/slide_puzzle/lib/src/app_state.dart @@ -4,28 +4,25 @@ import 'core/puzzle_animator.dart'; import 'flutter.dart'; -import 'shared_theme.dart'; abstract class AppState { - TabController get tabController; - PuzzleProxy get puzzle; bool get autoPlay; void setAutoPlay(bool newValue); - AnimationNotifier get animationNotifier; - - Iterable get themeData; - - SharedTheme get currentTheme; - - set currentTheme(SharedTheme theme); -} + Listenable get animationNotifier; -abstract class AnimationNotifier implements Listenable { - void animate(); + void clickOrShake(int tileValue) { + setAutoPlay(false); + puzzle.clickOrShake(tileValue); + } - void dispose(); + void Function(bool newValue) get setAutoPlayFunction { + if (puzzle.solved) { + return null; + } + return setAutoPlay; + } } diff --git a/web/slide_puzzle/lib/src/puzzle_home_state.dart b/web/slide_puzzle/lib/src/puzzle_home_state.dart index a0abdb53b..33fc4807d 100644 --- a/web/slide_puzzle/lib/src/puzzle_home_state.dart +++ b/web/slide_puzzle/lib/src/puzzle_home_state.dart @@ -4,17 +4,15 @@ import 'dart:async'; +import 'package:provider/provider.dart'; + import 'app_state.dart'; import 'core/puzzle_animator.dart'; import 'flutter.dart'; import 'shared_theme.dart'; -import 'theme_plaster.dart'; -import 'theme_seattle.dart'; -import 'theme_simple.dart'; +import 'themes.dart'; -class PuzzleHomeState extends State - with TickerProviderStateMixin - implements AppState { +class PuzzleHomeState extends State with TickerProviderStateMixin, AppState { TabController _tabController; AnimationController _controller; @@ -22,41 +20,21 @@ class PuzzleHomeState extends State final PuzzleAnimator puzzle; @override - final animationNotifier = _AnimationNotifier(); - - @override - TabController get tabController => _tabController; + final _AnimationNotifier animationNotifier = _AnimationNotifier(); SharedTheme _currentTheme; - - @override - SharedTheme get currentTheme => _currentTheme; - - @override - set currentTheme(SharedTheme theme) { - setState(() { - _currentTheme = theme; - }); - } - Duration _tickerTimeSinceLastEvent = Duration.zero; Ticker _ticker; Duration _lastElapsed; - StreamSubscription sub; + StreamSubscription _puzzleEventSubscription; @override bool autoPlay = false; PuzzleHomeState(this.puzzle) { - sub = puzzle.onEvent.listen(_onPuzzleEvent); - - _themeDataCache = List.unmodifiable([ - ThemeSimple(this), - ThemeSeattle(this), - ThemePlaster(this), - ]); + _puzzleEventSubscription = puzzle.onEvent.listen(_onPuzzleEvent); - _currentTheme = themeData.first; + _currentTheme = themes.first; } @override @@ -70,18 +48,15 @@ class PuzzleHomeState extends State duration: const Duration(milliseconds: 200), ); - _tabController = TabController(vsync: this, length: _themeDataCache.length); + _tabController = TabController(vsync: this, length: themes.length); _tabController.addListener(() { - currentTheme = _themeDataCache[_tabController.index]; + setState(() { + _currentTheme = themes[_tabController.index]; + }); }); } - List _themeDataCache; - - @override - Iterable get themeData => _themeDataCache; - @override void setAutoPlay(bool newValue) { if (newValue != autoPlay) { @@ -96,8 +71,13 @@ class PuzzleHomeState extends State } @override - Widget build(BuildContext context) => - LayoutBuilder(builder: _currentTheme.build); + Widget build(BuildContext context) => MultiProvider( + providers: [ + ListenableProvider.value(listenable: _tabController), + Provider.value(value: this), + ], + child: LayoutBuilder(builder: _currentTheme.build), + ); @override void dispose() { @@ -105,7 +85,7 @@ class PuzzleHomeState extends State _tabController.dispose(); _controller?.dispose(); _ticker?.dispose(); - sub.cancel(); + _puzzleEventSubscription.cancel(); super.dispose(); } @@ -165,10 +145,7 @@ class PuzzleHomeState extends State } } -class _AnimationNotifier extends ChangeNotifier implements AnimationNotifier { - _AnimationNotifier(); - - @override +class _AnimationNotifier extends ChangeNotifier { void animate() { notifyListeners(); } diff --git a/web/slide_puzzle/lib/src/shared_theme.dart b/web/slide_puzzle/lib/src/shared_theme.dart index 0dca32bb9..8a544d88b 100644 --- a/web/slide_puzzle/lib/src/shared_theme.dart +++ b/web/slide_puzzle/lib/src/shared_theme.dart @@ -2,32 +2,31 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:provider/provider.dart'; + import 'app_state.dart'; import 'core/puzzle_animator.dart'; import 'flutter.dart'; import 'puzzle_flow_delegate.dart'; +import 'themes.dart'; import 'widgets/material_interior_alt.dart'; abstract class SharedTheme { - SharedTheme(this._appState); - - final AppState _appState; - - PuzzleProxy get puzzle => _appState.puzzle; + const SharedTheme(); String get name; Color get puzzleThemeBackground; - RoundedRectangleBorder get puzzleBorder; + RoundedRectangleBorder puzzleBorder(bool small); Color get puzzleBackgroundColor; Color get puzzleAccentColor; - EdgeInsetsGeometry get tilePadding => const EdgeInsets.all(6); + EdgeInsetsGeometry tilePadding(PuzzleProxy puzzle) => const EdgeInsets.all(6); - Widget tileButton(int i); + Widget tileButton(int i, AppState appState, bool small); Ink createInk( Widget child, { @@ -43,6 +42,8 @@ abstract class SharedTheme { ); Widget createButton( + AppState appState, + bool small, int tileValue, Widget content, { Color color, @@ -50,178 +51,164 @@ abstract class SharedTheme { }) => AnimatedContainer( duration: _puzzleAnimationDuration, - padding: tilePadding, + padding: tilePadding(appState.puzzle), child: RaisedButton( elevation: 4, clipBehavior: Clip.hardEdge, animationDuration: _puzzleAnimationDuration, - onPressed: () => _tilePress(tileValue), - shape: shape ?? puzzleBorder, + onPressed: () => appState.clickOrShake(tileValue), + shape: shape ?? puzzleBorder(small), padding: const EdgeInsets.symmetric(), child: content, color: color, ), ); - double _previousConstraintWidth; - bool _small; - - bool get small => _small; - - void _updateConstraints(BoxConstraints constraints) { + Widget _updateConstraints( + BoxConstraints constraints, Widget Function(bool small) builder) { const _smallWidth = 580; final constraintWidth = constraints.hasBoundedWidth ? constraints.maxWidth : 1000.0; - if (constraintWidth == _previousConstraintWidth) { - assert(_small != null); - return; - } - - _previousConstraintWidth = constraintWidth; - - if (_previousConstraintWidth < _smallWidth) { - _small = true; - } else { - _small = false; - } + return builder(constraintWidth < _smallWidth); } - Widget build(BuildContext context, BoxConstraints constraints) { - _updateConstraints(constraints); - return Material( - child: Stack( - children: [ - const SizedBox.expand( - child: FittedBox( - fit: BoxFit.cover, - child: Image( - image: AssetImage('seattle.jpg'), - ), - ), - ), - AnimatedContainer( - duration: _puzzleAnimationDuration, - color: puzzleThemeBackground, - child: Center( - child: _styledWrapper( - SizedBox( - width: 580, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( - color: Colors.black26, - width: 1, - ), - ), - ), - margin: const EdgeInsets.symmetric(horizontal: 20), - child: TabBar( - controller: _appState.tabController, - labelPadding: const EdgeInsets.fromLTRB(0, 20, 0, 12), - labelColor: puzzleAccentColor, - indicatorColor: puzzleAccentColor, - indicatorWeight: 1.5, - unselectedLabelColor: Colors.black.withOpacity(0.6), - tabs: _appState.themeData - .map((st) => Text( - st.name.toUpperCase(), - style: const TextStyle( - letterSpacing: 0.5, - ), - )) - .toList(), + Widget build(BuildContext context, BoxConstraints constraints) => + _updateConstraints( + constraints, + (small) => Material( + child: Stack( + children: [ + const SizedBox.expand( + child: FittedBox( + fit: BoxFit.cover, + child: Image( + image: AssetImage('seattle.jpg'), ), ), - Container( - constraints: const BoxConstraints.tightForFinite(), - padding: const EdgeInsets.all(10), - child: Flow( - delegate: PuzzleFlowDelegate( - small ? const Size(90, 90) : const Size(140, 140), - puzzle, - _appState.animationNotifier, - ), - children: List.generate( - puzzle.length, - _tileButton, + ), + AnimatedContainer( + duration: _puzzleAnimationDuration, + color: puzzleThemeBackground, + child: Center( + child: _styledWrapper( + small, + SizedBox( + width: 580, + child: Consumer( + builder: (context, appState, _) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: Colors.black26, + width: 1, + ), + ), + ), + margin: const EdgeInsets.symmetric( + horizontal: 20), + child: TabBar( + controller: + Provider.of(context), + labelPadding: const EdgeInsets.fromLTRB( + 0, 20, 0, 12), + labelColor: puzzleAccentColor, + indicatorColor: puzzleAccentColor, + indicatorWeight: 1.5, + unselectedLabelColor: + Colors.black.withOpacity(0.6), + tabs: themes + .map((st) => Text( + st.name.toUpperCase(), + style: const TextStyle( + letterSpacing: 0.5, + ), + )) + .toList(), + ), + ), + Container( + constraints: + const BoxConstraints.tightForFinite(), + padding: const EdgeInsets.all(10), + child: Flow( + delegate: PuzzleFlowDelegate( + small + ? const Size(90, 90) + : const Size(140, 140), + appState.puzzle, + appState.animationNotifier, + ), + children: List.generate( + appState.puzzle.length, + (i) => + _tileButton(i, appState, small), + ), + ), + ), + Container( + decoration: const BoxDecoration( + border: Border( + top: BorderSide( + color: Colors.black26, width: 1), + ), + ), + padding: const EdgeInsets.only( + left: 10, + bottom: 6, + top: 2, + right: 10, + ), + child: Row( + children: _bottomControls(appState)), + ) + ], + ), + ), ), ), ), - Container( - decoration: const BoxDecoration( - border: Border( - top: BorderSide(color: Colors.black26, width: 1), - ), - ), - padding: const EdgeInsets.only( - left: 10, - bottom: 6, - top: 2, - right: 10, - ), - child: Row(children: _bottomControls(context)), - ) - ], - ), - ), - ), - ), - ) - ], - )); - } + ) + ], + ))); Duration get _puzzleAnimationDuration => kThemeAnimationDuration * 3; // Thought about using AnimatedContainer here, but it causes some weird // resizing behavior - Widget _styledWrapper(Widget child) => MaterialInterior( + Widget _styledWrapper(bool small, Widget child) => MaterialInterior( duration: _puzzleAnimationDuration, - shape: puzzleBorder, + shape: puzzleBorder(small), color: puzzleBackgroundColor, child: child, ); - void Function(bool newValue) get _setAutoPlay { - if (puzzle.solved) { - return null; - } - return _appState.setAutoPlay; - } - - void _tilePress(int tileValue) { - _appState.setAutoPlay(false); - _appState.puzzle.clickOrShake(tileValue); - } - TextStyle get _infoStyle => TextStyle( color: puzzleAccentColor, fontWeight: FontWeight.bold, ); - List _bottomControls(BuildContext context) => [ + List _bottomControls(AppState appState) => [ IconButton( - onPressed: puzzle.reset, + onPressed: appState.puzzle.reset, icon: Icon(Icons.refresh, color: puzzleAccentColor), //Icons.refresh, ), Checkbox( - value: _appState.autoPlay, - onChanged: _setAutoPlay, + value: appState.autoPlay, + onChanged: appState.setAutoPlayFunction, activeColor: puzzleAccentColor, ), Expanded( child: Container(), ), Text( - puzzle.clickCount.toString(), + appState.puzzle.clickCount.toString(), textAlign: TextAlign.right, style: _infoStyle, ), @@ -229,7 +216,7 @@ abstract class SharedTheme { SizedBox( width: 28, child: Text( - puzzle.incorrectTiles.toString(), + appState.puzzle.incorrectTiles.toString(), textAlign: TextAlign.right, style: _infoStyle, ), @@ -237,11 +224,11 @@ abstract class SharedTheme { const Text(' Tiles left ') ]; - Widget _tileButton(int i) { - if (i == puzzle.tileCount && !puzzle.solved) { + Widget _tileButton(int i, AppState appState, bool small) { + if (i == appState.puzzle.tileCount && !appState.puzzle.solved) { return const Center(); } - return tileButton(i); + return tileButton(i, appState, small); } } diff --git a/web/slide_puzzle/lib/src/theme_plaster.dart b/web/slide_puzzle/lib/src/theme_plaster.dart index ca2921b9f..63ba5f0fb 100644 --- a/web/slide_puzzle/lib/src/theme_plaster.dart +++ b/web/slide_puzzle/lib/src/theme_plaster.dart @@ -14,7 +14,7 @@ class ThemePlaster extends SharedTheme { @override String get name => 'Plaster'; - ThemePlaster(AppState baseTheme) : super(baseTheme); + const ThemePlaster(); @override Color get puzzleThemeBackground => _chocolate; @@ -26,7 +26,7 @@ class ThemePlaster extends SharedTheme { Color get puzzleAccentColor => _orangeIsh; @override - RoundedRectangleBorder get puzzleBorder => RoundedRectangleBorder( + RoundedRectangleBorder puzzleBorder(bool small) => RoundedRectangleBorder( side: const BorderSide( color: Color.fromARGB(255, 103, 103, 105), width: 8, @@ -37,14 +37,14 @@ class ThemePlaster extends SharedTheme { ); @override - Widget tileButton(int i) { - final correctColumn = i % puzzle.width; - final correctRow = i ~/ puzzle.width; + Widget tileButton(int i, AppState appState, bool small) { + final correctColumn = i % appState.puzzle.width; + final correctRow = i ~/ appState.puzzle.width; final primary = (correctColumn + correctRow).isEven; - if (i == puzzle.tileCount) { - assert(puzzle.solved); + if (i == appState.puzzle.tileCount) { + assert(appState.puzzle.solved); return Center( child: Icon( Icons.thumb_up, @@ -64,6 +64,8 @@ class ThemePlaster extends SharedTheme { ); return createButton( + appState, + small, i, content, color: primary ? _orangeIsh : _yellowIsh, diff --git a/web/slide_puzzle/lib/src/theme_seattle.dart b/web/slide_puzzle/lib/src/theme_seattle.dart index f834c1bd6..acbf5ee69 100644 --- a/web/slide_puzzle/lib/src/theme_seattle.dart +++ b/web/slide_puzzle/lib/src/theme_seattle.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'app_state.dart'; +import 'core/puzzle_animator.dart'; import 'flutter.dart'; import 'shared_theme.dart'; import 'widgets/decoration_image_plus.dart'; @@ -11,7 +12,7 @@ class ThemeSeattle extends SharedTheme { @override String get name => 'Seattle'; - ThemeSeattle(AppState proxy) : super(proxy); + const ThemeSeattle(); @override Color get puzzleThemeBackground => const Color.fromARGB(153, 90, 135, 170); @@ -23,18 +24,20 @@ class ThemeSeattle extends SharedTheme { Color get puzzleAccentColor => const Color(0xff000579f); @override - RoundedRectangleBorder get puzzleBorder => const RoundedRectangleBorder( + RoundedRectangleBorder puzzleBorder(bool small) => + const RoundedRectangleBorder( borderRadius: BorderRadius.all( Radius.circular(1), ), ); @override - EdgeInsetsGeometry get tilePadding => + EdgeInsetsGeometry tilePadding(PuzzleProxy puzzle) => puzzle.solved ? const EdgeInsets.all(1) : const EdgeInsets.all(4); @override - Widget tileButton(int i) { + Widget tileButton(int i, AppState appState, bool small) { + final puzzle = appState.puzzle; if (i == puzzle.tileCount && !puzzle.solved) { assert(puzzle.solved); } @@ -69,6 +72,6 @@ class ThemeSeattle extends SharedTheme { padding: EdgeInsets.all(small ? 20 : 32), ); - return createButton(i, content); + return createButton(appState, small, i, content); } } diff --git a/web/slide_puzzle/lib/src/theme_simple.dart b/web/slide_puzzle/lib/src/theme_simple.dart index 8246a50c2..1e1114fb8 100644 --- a/web/slide_puzzle/lib/src/theme_simple.dart +++ b/web/slide_puzzle/lib/src/theme_simple.dart @@ -12,7 +12,7 @@ class ThemeSimple extends SharedTheme { @override String get name => 'Simple'; - ThemeSimple(AppState proxy) : super(proxy); + const ThemeSimple(); @override Color get puzzleThemeBackground => Colors.white; @@ -24,7 +24,8 @@ class ThemeSimple extends SharedTheme { Color get puzzleAccentColor => _accentBlue; @override - RoundedRectangleBorder get puzzleBorder => const RoundedRectangleBorder( + RoundedRectangleBorder puzzleBorder(bool small) => + const RoundedRectangleBorder( side: BorderSide(color: Colors.black26, width: 1), borderRadius: BorderRadius.all( Radius.circular(4), @@ -32,9 +33,9 @@ class ThemeSimple extends SharedTheme { ); @override - Widget tileButton(int i) { - if (i == puzzle.tileCount) { - assert(puzzle.solved); + Widget tileButton(int i, AppState appState, bool small) { + if (i == appState.puzzle.tileCount) { + assert(appState.puzzle.solved); return const Center( child: Icon( Icons.thumb_up, @@ -44,7 +45,7 @@ class ThemeSimple extends SharedTheme { ); } - final correctPosition = puzzle.isCorrectPosition(i); + final correctPosition = appState.puzzle.isCorrectPosition(i); final content = createInk( Center( @@ -60,6 +61,8 @@ class ThemeSimple extends SharedTheme { ); return createButton( + appState, + small, i, content, color: const Color.fromARGB(255, 13, 87, 155), diff --git a/web/slide_puzzle/lib/src/themes.dart b/web/slide_puzzle/lib/src/themes.dart new file mode 100644 index 000000000..7889db6c3 --- /dev/null +++ b/web/slide_puzzle/lib/src/themes.dart @@ -0,0 +1,9 @@ +import 'theme_plaster.dart'; +import 'theme_seattle.dart'; +import 'theme_simple.dart'; + +const themes = [ + ThemeSimple(), + ThemeSeattle(), + ThemePlaster(), +]; diff --git a/web/slide_puzzle/pubspec.lock b/web/slide_puzzle/pubspec.lock index 95309d315..72b364ed0 100644 --- a/web/slide_puzzle/pubspec.lock +++ b/web/slide_puzzle/pubspec.lock @@ -334,6 +334,15 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.13.11" + provider: + dependency: "direct main" + description: + path: "." + ref: flutter_web + resolved-ref: "5cf4521d4d635d7d7ca8ddbd6e28048a7f319ee0" + url: "https://github.com/kevmoo/provider" + source: git + version: "2.1.0" pub_semver: dependency: transitive description: diff --git a/web/slide_puzzle/pubspec.yaml b/web/slide_puzzle/pubspec.yaml index e9feb5cac..7d4458df1 100644 --- a/web/slide_puzzle/pubspec.yaml +++ b/web/slide_puzzle/pubspec.yaml @@ -6,6 +6,7 @@ environment: dependencies: flutter_web: any flutter_web_ui: any + provider: any dev_dependencies: pedantic: ^1.3.0 @@ -24,3 +25,7 @@ dependency_overrides: git: url: https://github.com/flutter/flutter_web path: packages/flutter_web_ui + provider: + git: + url: https://github.com/kevmoo/provider + ref: flutter_web