update slide_puzzle for beta channel (#419)

includes PR: https://github.com/kevmoo/slide_puzzle/pull/5
pull/422/head
John Ryan 5 years ago committed by GitHub
parent ed284c78bd
commit ef0114c210
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'src/core/puzzle_animator.dart';
import 'src/flutter.dart';
import 'src/puzzle_home_state.dart';
void main() => 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() =>

@ -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<Offset> get shuffleOffsetAnimation;
import 'core/puzzle_proxy.dart';
abstract class AppState {
PuzzleProxy get puzzle;
bool get autoPlay;
void setAutoPlay(bool newValue);
AnimationNotifier get animationNotifier;
Iterable<SharedTheme> get themeData;
SharedTheme get currentTheme;
set currentTheme(SharedTheme theme);
}
abstract class AnimationNotifier implements Listenable {
void animate();
void dispose();
Listenable get animationNotifier;
}

@ -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<int> 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));

@ -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<double> 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<PuzzleEvent> 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);
}

@ -1,63 +0,0 @@
import 'dart:collection';
class FrameNanny {
static const _bufferSize = 200;
static const _maxFrameDuration = Duration(milliseconds: 34);
final _buffer = ListQueue<Duration>(_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;
}

@ -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;

@ -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<Offset> _shuffleOffsetAnimation;
class _PuzzleControls extends ChangeNotifier implements PuzzleControls {
final PuzzleHomeState _parent;
@override
Animation<Offset> 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<SharedTheme> _themeDataCache;
@override
Iterable<SharedTheme> 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<AppState>.value(value: this),
ListenableProvider<PuzzleControls>.value(
listenable: _autoPlayListenable,
)
],
child: Material(
child: Stack(
children: <Widget>[
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<Offset> {
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<SharedTheme>(
values: themes,
child: Consumer<SharedTheme>(
builder: (_, theme, __) => AnimatedContainer(
duration: puzzleAnimationDuration,
color: theme.puzzleThemeBackground,
child: Center(
child: theme.styledWrapper(
small,
SizedBox(
width: 580,
child: Consumer<AppState>(
builder: (context, appState, _) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
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<Widget>.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<PuzzleControls>(
builder: (_, controls, __) =>
Row(children: theme.bottomControls(controls)),
),
)
],
),
),
),
),
),
),
),
);

@ -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: <Widget>[
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: <Widget>[
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<Widget>.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<Widget> _bottomControls(BuildContext context) => <Widget>[
List<Widget> bottomControls(PuzzleControls controls) => <Widget>[
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);
}
}

@ -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,

@ -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);
}
}

@ -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),

@ -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

@ -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

@ -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:

@ -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

Loading…
Cancel
Save