From ef0114c2109134651b27f13990a8194eb0468249 Mon Sep 17 00:00:00 2001 From: John Ryan Date: Thu, 23 Apr 2020 09:33:43 -0700 Subject: [PATCH] update slide_puzzle for beta channel (#419) includes PR: https://github.com/kevmoo/slide_puzzle/pull/5 --- web/slide_puzzle/asset/fonts/plaster/OFL.txt | 188 ++++++------- web/slide_puzzle/assets/preview.png | Bin 17509 -> 0 bytes web/slide_puzzle/lib/main.dart | 4 +- web/slide_puzzle/lib/src/app_state.dart | 28 +- web/slide_puzzle/lib/src/core/puzzle.dart | 7 +- .../lib/src/core/puzzle_animator.dart | 34 +-- web/slide_puzzle/lib/src/frame_nanny.dart | 63 ----- .../lib/src/puzzle_flow_delegate.dart | 4 +- .../lib/src/puzzle_home_state.dart | 262 ++++++++++++------ web/slide_puzzle/lib/src/shared_theme.dart | 160 ++--------- web/slide_puzzle/lib/src/theme_plaster.dart | 23 +- web/slide_puzzle/lib/src/theme_seattle.dart | 20 +- web/slide_puzzle/lib/src/theme_simple.dart | 16 +- .../src/widgets/decoration_image_plus.dart | 26 +- .../src/widgets/material_interior_alt.dart | 2 +- web/slide_puzzle/pubspec.lock | 83 +++--- web/slide_puzzle/pubspec.yaml | 2 +- 17 files changed, 415 insertions(+), 507 deletions(-) delete mode 100644 web/slide_puzzle/assets/preview.png delete mode 100644 web/slide_puzzle/lib/src/frame_nanny.dart diff --git a/web/slide_puzzle/asset/fonts/plaster/OFL.txt b/web/slide_puzzle/asset/fonts/plaster/OFL.txt index f074fc5df..e0d637407 100755 --- a/web/slide_puzzle/asset/fonts/plaster/OFL.txt +++ b/web/slide_puzzle/asset/fonts/plaster/OFL.txt @@ -1,94 +1,94 @@ -Copyright (c) 2011 by Sorkin Type Co (www.sorkintype.com), -with Reserved Font Name "Plaster". - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. +Copyright (c) 2011 by Sorkin Type Co (www.sorkintype.com), +with Reserved Font Name "Plaster". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/web/slide_puzzle/assets/preview.png b/web/slide_puzzle/assets/preview.png deleted file mode 100644 index 0f55f737a598169e69f7498de5629be5155fd38c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17509 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 runApp(PuzzleApp()); @@ -21,7 +21,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 efef589ed..f9860773c 100644 --- a/web/slide_puzzle/lib/src/app_state.dart +++ b/web/slide_puzzle/lib/src/app_state.dart @@ -1,29 +1,9 @@ -import 'core/puzzle_animator.dart'; -import 'package:flutter/material.dart'; -import 'shared_theme.dart'; +import 'package:flutter/foundation.dart'; -abstract class AppState { - TabController get tabController; - - Animation get shuffleOffsetAnimation; +import 'core/puzzle_proxy.dart'; +abstract class AppState { PuzzleProxy get puzzle; - bool get autoPlay; - - void setAutoPlay(bool newValue); - - AnimationNotifier get animationNotifier; - - Iterable get themeData; - - SharedTheme get currentTheme; - - set currentTheme(SharedTheme theme); -} - -abstract class AnimationNotifier implements Listenable { - void animate(); - - void dispose(); + Listenable get animationNotifier; } diff --git a/web/slide_puzzle/lib/src/core/puzzle.dart b/web/slide_puzzle/lib/src/core/puzzle.dart index 4419dc9e6..d758fba3a 100644 --- a/web/slide_puzzle/lib/src/core/puzzle.dart +++ b/web/slide_puzzle/lib/src/core/puzzle.dart @@ -123,8 +123,7 @@ abstract class Puzzle { value += delta * delta; } } - value *= incorrectTiles; - return value; + return value * incorrectTiles; } Puzzle clickRandom({bool vertical}) { @@ -137,8 +136,8 @@ abstract class Puzzle { List clickableValues({bool vertical}) { final open = openPosition(); - final doRow = (vertical == null || vertical == false); - final doColumn = (vertical == null || vertical); + final doRow = vertical == null || vertical == false; + final doColumn = vertical == null || vertical; final values = Uint8List((doRow ? (width - 1) : 0) + (doColumn ? (height - 1) : 0)); diff --git a/web/slide_puzzle/lib/src/core/puzzle_animator.dart b/web/slide_puzzle/lib/src/core/puzzle_animator.dart index 3646e4d6e..9197c1e3f 100644 --- a/web/slide_puzzle/lib/src/core/puzzle_animator.dart +++ b/web/slide_puzzle/lib/src/core/puzzle_animator.dart @@ -3,32 +3,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(); @@ -57,13 +32,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; @@ -93,7 +65,7 @@ class PuzzleAnimator implements PuzzleProxy { _puzzle = _puzzle.clickRandom(vertical: _nextRandomVertical); _nextRandomVertical = !_nextRandomVertical; _clickCount++; - _controller.add(PuzzleEvent.click); + _controller.add(PuzzleEvent.random); } @override @@ -165,7 +137,7 @@ class PuzzleAnimator implements PuzzleProxy { final delta = _puzzle.openPosition() - _puzzle.coordinatesOf(tileValue); deltaDouble = Point(delta.x.toDouble(), delta.y.toDouble()); } - deltaDouble *= (0.5 / deltaDouble.magnitude); + deltaDouble *= 0.5 / deltaDouble.magnitude; _locations[tileValue].kick(deltaDouble); } diff --git a/web/slide_puzzle/lib/src/frame_nanny.dart b/web/slide_puzzle/lib/src/frame_nanny.dart deleted file mode 100644 index 8c58e7de0..000000000 --- a/web/slide_puzzle/lib/src/frame_nanny.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'dart:collection'; - -class FrameNanny { - static const _bufferSize = 200; - static const _maxFrameDuration = Duration(milliseconds: 34); - final _buffer = ListQueue(_bufferSize); - final _watch = Stopwatch(); - - Duration tick(Duration source) { - _watch.start(); - _buffer.add(source); - - while (_buffer.length > _bufferSize) { - _buffer.removeFirst(); - } - - if (source > _maxFrameDuration) { - source = _maxFrameDuration; - } - - if (_watch.elapsed > const Duration(seconds: 2)) { - var goodCount = 0; - var sum = const Duration(); - Duration best, worst; - - for (var e in _buffer) { - sum += e; - if (e <= _maxFrameDuration) { - goodCount++; - } - - if (best == null || e < best) { - best = e; - } - - if (worst == null || e > worst) { - worst = e; - } - } - - _watch.reset(); - print([ - '**Nanny**', - '${(100 * goodCount / _buffer.length).toStringAsFixed(1)}%', - '<= ${_maxFrameDuration.inMilliseconds}ms', - 'best:', - best?.inMilliseconds, - 'avg:', - _safeDivide(sum, _buffer.length), - 'worst', - worst?.inMilliseconds - ].join(' ')); - } - return source; - } -} - -Object _safeDivide(Duration source, int divisor) { - if (divisor == 0) { - return double.nan; - } - return (source ~/ divisor).inMilliseconds; -} diff --git a/web/slide_puzzle/lib/src/puzzle_flow_delegate.dart b/web/slide_puzzle/lib/src/puzzle_flow_delegate.dart index 348dbd6e2..7f19d1960 100644 --- a/web/slide_puzzle/lib/src/puzzle_flow_delegate.dart +++ b/web/slide_puzzle/lib/src/puzzle_flow_delegate.dart @@ -1,5 +1,5 @@ -import 'core/puzzle_animator.dart'; -import 'package:flutter/material.dart'; +import 'core/puzzle_proxy.dart'; +import 'flutter.dart'; class PuzzleFlowDelegate extends FlowDelegate { final Size _tileSize; diff --git a/web/slide_puzzle/lib/src/puzzle_home_state.dart b/web/slide_puzzle/lib/src/puzzle_home_state.dart index 8b69c73c8..6310d07f4 100644 --- a/web/slide_puzzle/lib/src/puzzle_home_state.dart +++ b/web/slide_puzzle/lib/src/puzzle_home_state.dart @@ -1,101 +1,80 @@ import 'dart:async'; -import 'dart:math' as math; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; +import 'package:provider/provider.dart'; import 'app_state.dart'; import 'core/puzzle_animator.dart'; -import 'frame_nanny.dart'; +import 'core/puzzle_proxy.dart'; +import 'flutter.dart'; +import 'puzzle_controls.dart'; +import 'puzzle_flow_delegate.dart'; import 'shared_theme.dart'; -import 'theme_plaster.dart'; -import 'theme_seattle.dart'; -import 'theme_simple.dart'; +import 'themes.dart'; +import 'value_tab_controller.dart'; -class PuzzleHomeState extends State - with TickerProviderStateMixin - implements AppState { - TabController _tabController; - AnimationController _controller; - Animation _shuffleOffsetAnimation; +class _PuzzleControls extends ChangeNotifier implements PuzzleControls { + final PuzzleHomeState _parent; - @override - Animation get shuffleOffsetAnimation => _shuffleOffsetAnimation; + _PuzzleControls(this._parent); @override - final PuzzleAnimator puzzle; + bool get autoPlay => _parent._autoPlay; + + void _notify() => notifyListeners(); @override - final animationNotifier = _AnimationNotifier(); + void Function(bool newValue) get setAutoPlayFunction { + if (_parent.puzzle.solved) { + return null; + } + return _parent._setAutoPlay; + } @override - TabController get tabController => _tabController; + int get clickCount => _parent.puzzle.clickCount; - final _nanny = FrameNanny(); + @override + int get incorrectTiles => _parent.puzzle.incorrectTiles; - SharedTheme _currentTheme; + @override + void reset() => _parent.puzzle.reset(); +} +class PuzzleHomeState extends State + with SingleTickerProviderStateMixin, AppState { @override - SharedTheme get currentTheme => _currentTheme; + final PuzzleAnimator puzzle; @override - set currentTheme(SharedTheme theme) { - setState(() { - _currentTheme = theme; - }); - } + final _AnimationNotifier animationNotifier = _AnimationNotifier(); Duration _tickerTimeSinceLastEvent = Duration.zero; Ticker _ticker; Duration _lastElapsed; - StreamSubscription sub; + StreamSubscription _puzzleEventSubscription; - @override - bool autoPlay = false; + bool _autoPlay = false; + _PuzzleControls _autoPlayListenable; PuzzleHomeState(this.puzzle) { - sub = puzzle.onEvent.listen(_onPuzzleEvent); - - _themeDataCache = List.unmodifiable([ - ThemeSimple(this), - ThemeSeattle(this), - ThemePlaster(this), - ]); - - _currentTheme = themeData.first; + _puzzleEventSubscription = puzzle.onEvent.listen(_onPuzzleEvent); } @override void initState() { super.initState(); + _autoPlayListenable = _PuzzleControls(this); _ticker ??= createTicker(_onTick); _ensureTicking(); - - _controller = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 200), - ); - - _shuffleOffsetAnimation = _controller.drive(const _Shake()); - _tabController = TabController(vsync: this, length: _themeDataCache.length); - - _tabController.addListener(() { - currentTheme = _themeDataCache[_tabController.index]; - }); } - List _themeDataCache; - - @override - Iterable get themeData => _themeDataCache; - - @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) { + _autoPlayListenable._notify(); + _autoPlay = newValue && !puzzle.solved; + if (_autoPlay) { _ensureTicking(); } }); @@ -103,26 +82,46 @@ class PuzzleHomeState extends State } @override - Widget build(BuildContext context) => _currentTheme.build(context); + Widget build(BuildContext context) => MultiProvider( + providers: [ + Provider.value(value: this), + ListenableProvider.value( + listenable: _autoPlayListenable, + ) + ], + child: Material( + child: Stack( + children: [ + const SizedBox.expand( + child: FittedBox( + fit: BoxFit.cover, + child: Image( + image: AssetImage('asset/seattle.jpg'), + ), + ), + ), + const LayoutBuilder(builder: _doBuild), + ], + ), + ), + ); @override void dispose() { animationNotifier.dispose(); - _tabController.dispose(); - _controller?.dispose(); _ticker?.dispose(); - sub.cancel(); + _autoPlayListenable?.dispose(); + _puzzleEventSubscription.cancel(); super.dispose(); } void _onPuzzleEvent(PuzzleEvent e) { + _autoPlayListenable._notify(); + if (e != PuzzleEvent.random) { + _setAutoPlay(false); + } _tickerTimeSinceLastEvent = Duration.zero; _ensureTicking(); - if (e == PuzzleEvent.noop) { - assert(e == PuzzleEvent.noop); - _controller.reset(); - _controller.forward(); - } setState(() { // noop }); @@ -148,40 +147,135 @@ class PuzzleHomeState extends State } _tickerTimeSinceLastEvent += delta; - puzzle.update(_nanny.tick(delta)); + puzzle.update(delta > _maxFrameDuration ? _maxFrameDuration : delta); 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); } } } } -class _Shake extends Animatable { - const _Shake(); - - @override - Offset transform(double t) => Offset(0.01 * math.sin(t * math.pi * 3), 0); -} - -class _AnimationNotifier extends ChangeNotifier implements AnimationNotifier { - _AnimationNotifier(); - - @override +class _AnimationNotifier extends ChangeNotifier { void animate() { notifyListeners(); } } + +const _maxFrameDuration = Duration(milliseconds: 34); + +Widget _updateConstraints( + BoxConstraints constraints, Widget Function(bool small) builder) { + const _smallWidth = 580; + + final constraintWidth = + constraints.hasBoundedWidth ? constraints.maxWidth : 1000.0; + + final constraintHeight = + constraints.hasBoundedHeight ? constraints.maxHeight : 1000.0; + + return builder(constraintWidth < _smallWidth || constraintHeight < 690); +} + +Widget _doBuild(BuildContext _, BoxConstraints constraints) => + _updateConstraints(constraints, _doBuildCore); + +Widget _doBuildCore(bool small) => ValueTabController( + values: themes, + child: Consumer( + builder: (_, theme, __) => AnimatedContainer( + duration: puzzleAnimationDuration, + color: theme.puzzleThemeBackground, + child: Center( + child: theme.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: ValueTabController.of(context), + labelPadding: const EdgeInsets.fromLTRB(0, 20, 0, 12), + labelColor: theme.puzzleAccentColor, + indicatorColor: theme.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(), + ), + ), + Flexible( + child: Container( + 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) => theme.tileButtonCore( + i, appState.puzzle, 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: 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 3240ce4ab..34536d46f 100644 --- a/web/slide_puzzle/lib/src/shared_theme.dart +++ b/web/slide_puzzle/lib/src/shared_theme.dart @@ -1,30 +1,26 @@ -import 'package:flutter/material.dart'; - -import 'app_state.dart'; -import 'core/puzzle_animator.dart'; -import 'puzzle_flow_delegate.dart'; +import 'core/puzzle_proxy.dart'; +import 'flutter.dart'; +import 'puzzle_controls.dart'; import 'widgets/material_interior_alt.dart'; -abstract class SharedTheme { - SharedTheme(this._appState); - - final AppState _appState; +final puzzleAnimationDuration = kThemeAnimationDuration * 3; - PuzzleProxy get puzzle => _appState.puzzle; +abstract class SharedTheme { + 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, PuzzleProxy puzzle, bool small); Ink createInk( Widget child, { @@ -40,159 +36,57 @@ abstract class SharedTheme { ); Widget createButton( + PuzzleProxy puzzle, + bool small, int tileValue, Widget content, { Color color, RoundedRectangleBorder shape, }) => AnimatedContainer( - duration: _puzzleAnimationDuration, - padding: tilePadding, + duration: puzzleAnimationDuration, + padding: tilePadding(puzzle), child: RaisedButton( elevation: 4, clipBehavior: Clip.hardEdge, - animationDuration: _puzzleAnimationDuration, - onPressed: () => _tilePress(tileValue), - shape: shape ?? puzzleBorder, + animationDuration: puzzleAnimationDuration, + onPressed: () => puzzle.clickOrShake(tileValue), + shape: shape ?? puzzleBorder(small), padding: const EdgeInsets.symmetric(), child: content, color: color, ), ); - Widget build(BuildContext context) => Material( - child: Stack( - children: [ - const SizedBox.expand( - child: FittedBox( - fit: BoxFit.cover, - child: Image( - image: AssetImage('asset/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(), - ), - ), - Container( - constraints: const BoxConstraints.tightForFinite(), - padding: const EdgeInsets.all(10), - child: Flow( - delegate: PuzzleFlowDelegate( - _tileSize, - puzzle, - _appState.animationNotifier, - ), - children: List.generate( - puzzle.length, - _tileButton, - ), - ), - ), - 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( - duration: _puzzleAnimationDuration, - shape: puzzleBorder, + Widget styledWrapper(bool small, Widget child) => MaterialInterior( + duration: puzzleAnimationDuration, + shape: puzzleBorder(small), color: puzzleBackgroundColor, child: child, ); - Size get _tileSize => const Size(140.0, 140.0); - - 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(PuzzleControls controls) => [ IconButton( - onPressed: puzzle.reset, + onPressed: controls.reset, icon: Icon(Icons.refresh, color: puzzleAccentColor), - //Icons.refresh, ), Checkbox( - value: _appState.autoPlay, - onChanged: _setAutoPlay, + value: controls.autoPlay, + onChanged: controls.setAutoPlayFunction, activeColor: puzzleAccentColor, ), Expanded( child: Container(), ), Text( - puzzle.clickCount.toString(), + controls.clickCount.toString(), textAlign: TextAlign.right, style: _infoStyle, ), @@ -200,7 +94,7 @@ abstract class SharedTheme { SizedBox( width: 28, child: Text( - puzzle.incorrectTiles.toString(), + controls.incorrectTiles.toString(), textAlign: TextAlign.right, style: _infoStyle, ), @@ -208,11 +102,11 @@ abstract class SharedTheme { const Text(' Tiles left ') ]; - Widget _tileButton(int i) { + Widget tileButtonCore(int i, PuzzleProxy puzzle, bool small) { if (i == puzzle.tileCount && !puzzle.solved) { return const Center(); } - return tileButton(i); + 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 e80b6fe02..d676856a6 100644 --- a/web/slide_puzzle/lib/src/theme_plaster.dart +++ b/web/slide_puzzle/lib/src/theme_plaster.dart @@ -1,6 +1,5 @@ -import 'package:flutter/material.dart'; - -import 'app_state.dart'; +import 'core/puzzle_proxy.dart'; +import 'flutter.dart'; import 'shared_theme.dart'; const _yellowIsh = Color.fromARGB(255, 248, 244, 233); @@ -11,7 +10,7 @@ class ThemePlaster extends SharedTheme { @override String get name => 'Plaster'; - ThemePlaster(AppState baseTheme) : super(baseTheme); + const ThemePlaster(); @override Color get puzzleThemeBackground => _chocolate; @@ -23,18 +22,18 @@ class ThemePlaster extends SharedTheme { Color get puzzleAccentColor => _orangeIsh; @override - RoundedRectangleBorder get puzzleBorder => const RoundedRectangleBorder( - side: BorderSide( + RoundedRectangleBorder puzzleBorder(bool small) => RoundedRectangleBorder( + side: const BorderSide( color: Color.fromARGB(255, 103, 103, 105), width: 8, ), borderRadius: BorderRadius.all( - Radius.circular(18), + Radius.circular(small ? 10 : 18), ), ); @override - Widget tileButton(int i) { + Widget tileButton(int i, PuzzleProxy puzzle, bool small) { final correctColumn = i % puzzle.width; final correctRow = i ~/ puzzle.width; @@ -42,10 +41,10 @@ class ThemePlaster extends SharedTheme { if (i == puzzle.tileCount) { assert(puzzle.solved); - return const Center( + return Center( child: Icon( Icons.thumb_up, - size: 72, + size: small ? 50 : 72, color: _orangeIsh, ), ); @@ -56,11 +55,13 @@ class ThemePlaster extends SharedTheme { style: TextStyle( color: primary ? _yellowIsh : _chocolate, fontFamily: 'Plaster', - fontSize: 77, + fontSize: small ? 40 : 77, ), ); return createButton( + puzzle, + 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 2a9505d9d..15f75afb8 100644 --- a/web/slide_puzzle/lib/src/theme_seattle.dart +++ b/web/slide_puzzle/lib/src/theme_seattle.dart @@ -1,6 +1,5 @@ -import 'package:flutter/material.dart'; - -import 'app_state.dart'; +import 'core/puzzle_proxy.dart'; +import 'flutter.dart'; import 'shared_theme.dart'; import 'widgets/decoration_image_plus.dart'; @@ -8,7 +7,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); @@ -20,18 +19,19 @@ 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, PuzzleProxy puzzle, bool small) { if (i == puzzle.tileCount && !puzzle.solved) { assert(puzzle.solved); } @@ -58,14 +58,14 @@ class ThemeSeattle extends SharedTheme { style: TextStyle( fontWeight: FontWeight.normal, color: correctPosition ? Colors.white : Colors.black, - fontSize: 42, + fontSize: small ? 25 : 42, ), ), ), image: decorationImage, - padding: const EdgeInsets.all(32), + padding: EdgeInsets.all(small ? 20 : 32), ); - return createButton(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 c962da38f..fa68d1c38 100644 --- a/web/slide_puzzle/lib/src/theme_simple.dart +++ b/web/slide_puzzle/lib/src/theme_simple.dart @@ -1,6 +1,5 @@ -import 'package:flutter/material.dart'; - -import 'app_state.dart'; +import 'core/puzzle_proxy.dart'; +import 'flutter.dart'; import 'shared_theme.dart'; const _accentBlue = Color(0xff000579e); @@ -9,7 +8,7 @@ class ThemeSimple extends SharedTheme { @override String get name => 'Simple'; - ThemeSimple(AppState proxy) : super(proxy); + const ThemeSimple(); @override Color get puzzleThemeBackground => Colors.white; @@ -21,7 +20,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), @@ -29,7 +29,7 @@ class ThemeSimple extends SharedTheme { ); @override - Widget tileButton(int i) { + Widget tileButton(int i, PuzzleProxy puzzle, bool small) { if (i == puzzle.tileCount) { assert(puzzle.solved); return const Center( @@ -50,13 +50,15 @@ class ThemeSimple extends SharedTheme { style: TextStyle( color: Colors.white, fontWeight: correctPosition ? FontWeight.bold : FontWeight.normal, - fontSize: 49, + fontSize: small ? 30 : 49, ), ), ), ); return createButton( + puzzle, + small, i, content, color: const Color.fromARGB(255, 13, 87, 155), diff --git a/web/slide_puzzle/lib/src/widgets/decoration_image_plus.dart b/web/slide_puzzle/lib/src/widgets/decoration_image_plus.dart index f48803c75..4ee6a6057 100644 --- a/web/slide_puzzle/lib/src/widgets/decoration_image_plus.dart +++ b/web/slide_puzzle/lib/src/widgets/decoration_image_plus.dart @@ -1,8 +1,9 @@ // ignore_for_file: omit_local_variable_types, annotate_overrides +import 'dart:developer' as developer; import 'dart:ui' as ui show Image; -import 'package:flutter/material.dart'; +import '../flutter.dart'; // A model on top of DecorationImage that supports slicing up the source image // efficiently to draw it as tiles in the puzzle game @@ -143,14 +144,22 @@ class DecorationImagePlus implements DecorationImage { if (colorFilter != null) properties.add('$colorFilter'); if (fit != null && !(fit == BoxFit.fill && centerSlice != null) && - !(fit == BoxFit.scaleDown && centerSlice == null)) + !(fit == BoxFit.scaleDown && centerSlice == null)) { properties.add('$fit'); + } properties.add('$alignment'); if (centerSlice != null) properties.add('centerSlice: $centerSlice'); if (repeat != ImageRepeat.noRepeat) properties.add('$repeat'); if (matchTextDirection) properties.add('match text direction'); return '$runtimeType(${properties.join(", ")})'; } + + @override + ImageErrorListener get onError => (error, stackTrace) { + developer.log('Failed to load image.\n' + '$error\n' + '$stackTrace', name: 'slide_puzzle.decoration_image_plus'); + }; } /// The painter for a [DecorationImagePlus]. @@ -166,16 +175,13 @@ class DecorationImagePlus implements DecorationImage { /// longer needed. class DecorationImagePainterPlus implements DecorationImagePainter { DecorationImagePainterPlus._(this._details, this._onChanged) - : assert(_details != null) { - _imageStreamListener = ImageStreamListener(_imageListener); - } + : assert(_details != null); final DecorationImagePlus _details; final VoidCallback _onChanged; ImageStream _imageStream; ImageInfo _image; - ImageStreamListener _imageStreamListener; /// Draw the image onto the given canvas. /// @@ -217,8 +223,10 @@ class DecorationImagePainterPlus implements DecorationImagePainter { final ImageStream newImageStream = _details.image.resolve(configuration); if (newImageStream.key != _imageStream?.key) { - _imageStream?.removeListener(_imageStreamListener); - _imageStream = newImageStream..addListener(_imageStreamListener); + final listener = ImageStreamListener(_imageListener); + _imageStream?.removeListener(listener); + _imageStream = newImageStream; + _imageStream.addListener(listener); } if (_image == null) return; @@ -257,7 +265,7 @@ class DecorationImagePainterPlus implements DecorationImagePainter { /// After this method has been called, the object is no longer usable. @mustCallSuper void dispose() { - _imageStream?.removeListener(_imageStreamListener); + _imageStream?.removeListener(ImageStreamListener(_imageListener)); } @override diff --git a/web/slide_puzzle/lib/src/widgets/material_interior_alt.dart b/web/slide_puzzle/lib/src/widgets/material_interior_alt.dart index 9673cebae..9cef4c52e 100644 --- a/web/slide_puzzle/lib/src/widgets/material_interior_alt.dart +++ b/web/slide_puzzle/lib/src/widgets/material_interior_alt.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import '../flutter.dart'; // Copied from // https://github.com/flutter/flutter/blob/f5b02e3c05ed1ab31e890add84fb56e35de2d392/packages/flutter/lib/src/material/material.dart#L593-L715 diff --git a/web/slide_puzzle/pubspec.lock b/web/slide_puzzle/pubspec.lock index fa7e49419..f7f6cf21d 100644 --- a/web/slide_puzzle/pubspec.lock +++ b/web/slide_puzzle/pubspec.lock @@ -1,62 +1,55 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.39.4" + version: "0.36.4" archive: dependency: transitive description: name: archive url: "https://pub.dartlang.org" source: hosted - version: "2.0.11" + version: "2.0.13" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.5.2" + version: "1.6.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "2.4.1" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "2.0.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.1.3" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.14.12" convert: dependency: transitive description: @@ -70,14 +63,14 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "0.13.8" + version: "0.13.9" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "2.1.4" csslib: dependency: transitive description: @@ -95,6 +88,13 @@ packages: description: flutter source: sdk version: "0.0.0" + front_end: + dependency: transitive + description: + name: front_end + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.19" glob: dependency: transitive description: @@ -115,14 +115,14 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.0+4" + version: "0.12.0+2" http_multi_server: dependency: transitive description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "2.1.0" http_parser: dependency: transitive description: @@ -136,7 +136,7 @@ packages: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "2.1.12" io: dependency: transitive description: @@ -151,6 +151,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.1+1" + kernel: + dependency: transitive + description: + name: kernel + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.19" logging: dependency: transitive description: @@ -213,7 +220,14 @@ packages: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.1" + version: "1.9.3" + package_resolver: + dependency: transitive + description: + name: package_resolver + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.10" path: dependency: transitive description: @@ -227,7 +241,7 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.9.0" + version: "1.8.0+1" petitparser: dependency: transitive description: @@ -242,20 +256,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.4.0" + provider: + dependency: "direct dev" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1+1" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "1.4.3" + version: "1.4.2" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.1.3" shelf: dependency: transitive description: @@ -269,7 +290,7 @@ packages: name: shelf_packages_handler url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "1.0.4" shelf_static: dependency: transitive description: @@ -302,14 +323,14 @@ packages: name: source_maps url: "https://pub.dartlang.org" source: hosted - version: "0.10.9" + version: "0.10.8" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.7.0" stack_trace: dependency: transitive description: @@ -344,7 +365,7 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.14.1" + version: "1.14.2" test_api: dependency: transitive description: @@ -358,7 +379,7 @@ packages: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.3.2" + version: "0.3.3" typed_data: dependency: transitive description: @@ -379,14 +400,14 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "3.0.0+1" + version: "4.0.1" watcher: dependency: transitive description: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+14" + version: "0.9.7+12" web_socket_channel: dependency: transitive description: @@ -407,7 +428,7 @@ packages: name: xml url: "https://pub.dartlang.org" source: hosted - version: "3.5.0" + version: "3.6.1" yaml: dependency: transitive description: diff --git a/web/slide_puzzle/pubspec.yaml b/web/slide_puzzle/pubspec.yaml index e782f2387..e28692e1b 100644 --- a/web/slide_puzzle/pubspec.yaml +++ b/web/slide_puzzle/pubspec.yaml @@ -13,13 +13,13 @@ dev_dependencies: flutter_test: sdk: flutter pedantic: ^1.3.0 + provider: ^2.0.0 test: ^1.3.4 flutter: uses-material-design: true assets: - asset/ - - preview.png fonts: - family: Plaster