mirror of https://github.com/flutter/samples.git
parent
baa1f976b2
commit
fcf6c06152
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "web/slide_puzzle"]
|
||||||
|
path = web/slide_puzzle
|
||||||
|
url = git@github.com:kevmoo/slide_puzzle.git
|
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 5ef5526acb58f9ffb6f3fb22118cbd825613dc73
|
@ -1,4 +0,0 @@
|
|||||||
Get the numbers in order by clicking tiles next to the open space.
|
|
||||||
|
|
||||||
Created by [Kevin Moore](https://twitter.com/kevmoo). Original source at
|
|
||||||
[github.com/kevmoo/slide_puzzle](https://github.com/kevmoo/slide_puzzle).
|
|
@ -1,94 +0,0 @@
|
|||||||
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: 726 KiB |
@ -1,33 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'src/core/puzzle_animator.dart';
|
|
||||||
import 'src/flutter.dart';
|
|
||||||
import 'src/puzzle_home_state.dart';
|
|
||||||
|
|
||||||
void main() => runApp(PuzzleApp());
|
|
||||||
|
|
||||||
class PuzzleApp extends StatelessWidget {
|
|
||||||
final int rows, columns;
|
|
||||||
|
|
||||||
PuzzleApp({int columns = 4, int rows = 4})
|
|
||||||
: columns = columns ?? 4,
|
|
||||||
rows = rows ?? 4;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => MaterialApp(
|
|
||||||
title: 'Slide Puzzle',
|
|
||||||
home: _PuzzleHome(rows, columns),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PuzzleHome extends StatefulWidget {
|
|
||||||
final int _rows, _columns;
|
|
||||||
|
|
||||||
const _PuzzleHome(this._rows, this._columns);
|
|
||||||
|
|
||||||
@override
|
|
||||||
PuzzleHomeState createState() =>
|
|
||||||
PuzzleHomeState(PuzzleAnimator(_columns, _rows));
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
import 'core/puzzle_proxy.dart';
|
|
||||||
|
|
||||||
abstract class AppState {
|
|
||||||
PuzzleProxy get puzzle;
|
|
||||||
|
|
||||||
Listenable get animationNotifier;
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'dart:math' show Point;
|
|
||||||
|
|
||||||
const zeroPoint = Point<double>(0, 0);
|
|
||||||
|
|
||||||
const _epsilon = 0.0001;
|
|
||||||
|
|
||||||
/// Represents a point object with a location and velocity.
|
|
||||||
class Body {
|
|
||||||
Point<double> _velocity;
|
|
||||||
Point<double> _location;
|
|
||||||
|
|
||||||
Point<double> get velocity => _velocity;
|
|
||||||
|
|
||||||
Point<double> get location => _location;
|
|
||||||
|
|
||||||
Body({Point<double> location = zeroPoint, Point<double> velocity = zeroPoint})
|
|
||||||
: assert(location.magnitude.isFinite),
|
|
||||||
_location = location,
|
|
||||||
assert(velocity.magnitude.isFinite),
|
|
||||||
_velocity = velocity;
|
|
||||||
|
|
||||||
factory Body.raw(double x, double y, double vx, double vy) =>
|
|
||||||
Body(location: Point(x, y), velocity: Point(vx, vy));
|
|
||||||
|
|
||||||
Body clone() => Body(location: _location, velocity: _velocity);
|
|
||||||
|
|
||||||
/// Add the velocity specified in [delta] to `this`.
|
|
||||||
void kick(Point<double> delta) {
|
|
||||||
assert(delta.magnitude.isFinite);
|
|
||||||
_velocity = delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [drag] must be greater than or equal to zero. It defines the percent of
|
|
||||||
/// the previous velocity that is lost every second.
|
|
||||||
bool animate(double seconds,
|
|
||||||
{Point<double> force = zeroPoint,
|
|
||||||
double drag = 0,
|
|
||||||
double maxVelocity,
|
|
||||||
Point<double> snapTo}) {
|
|
||||||
assert(seconds.isFinite && seconds > 0,
|
|
||||||
'milliseconds must be finite and > 0 (was $seconds)');
|
|
||||||
|
|
||||||
force ??= zeroPoint;
|
|
||||||
assert(force.x.isFinite && force.y.isFinite, 'force must be finite');
|
|
||||||
|
|
||||||
drag ??= 0;
|
|
||||||
assert(drag.isFinite && drag >= 0, 'drag must be finiate and >= 0');
|
|
||||||
|
|
||||||
maxVelocity ??= double.infinity;
|
|
||||||
assert(maxVelocity > 0, 'maxVelocity must be null or > 0');
|
|
||||||
|
|
||||||
final dragVelocity = _velocity * (1 - drag * seconds);
|
|
||||||
|
|
||||||
if (_sameDirection(_velocity, dragVelocity)) {
|
|
||||||
assert(dragVelocity.magnitude <= _velocity.magnitude,
|
|
||||||
'Huh? $dragVelocity $_velocity');
|
|
||||||
_velocity = dragVelocity;
|
|
||||||
} else {
|
|
||||||
_velocity = zeroPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply force to velocity
|
|
||||||
_velocity += force * seconds;
|
|
||||||
|
|
||||||
// apply terminal velocity
|
|
||||||
if (_velocity.magnitude > maxVelocity) {
|
|
||||||
_velocity = _unitPoint(_velocity) * maxVelocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update location
|
|
||||||
final locationDelta = _velocity * seconds;
|
|
||||||
if (locationDelta.magnitude > _epsilon ||
|
|
||||||
(force.magnitude * seconds) > _epsilon) {
|
|
||||||
_location += locationDelta;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
if (snapTo != null && (_location.distanceTo(snapTo) < _epsilon * 2)) {
|
|
||||||
_location = snapTo;
|
|
||||||
}
|
|
||||||
_velocity = zeroPoint;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'Body @(${_location.x},${_location.y}) ↕(${_velocity.x},${_velocity.y})';
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
other is Body &&
|
|
||||||
other._location == _location &&
|
|
||||||
other._velocity == _velocity;
|
|
||||||
|
|
||||||
// Since this is a mutable class, a constant value is returned for `hashCode`
|
|
||||||
// This ensures values don't get lost in a Hashing data structure.
|
|
||||||
// Note: this means you shouldn't use this type in most Map/Set impls.
|
|
||||||
@override
|
|
||||||
int get hashCode => 199;
|
|
||||||
}
|
|
||||||
|
|
||||||
Point<double> _unitPoint(Point<double> source) {
|
|
||||||
final result = source * (1 / source.magnitude);
|
|
||||||
return Point(result.x.isNaN ? 0 : result.x, result.y.isNaN ? 0 : result.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _sameDirection(Point a, Point b) {
|
|
||||||
return a.x.sign == b.x.sign && a.y.sign == b.y.sign;
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
class Point extends math.Point<int> {
|
|
||||||
Point(int x, int y) : super(x, y);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Point operator +(math.Point<int> other) => Point(x + other.x, y + other.y);
|
|
||||||
}
|
|
@ -1,275 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'dart:collection';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:math' show Random, max;
|
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'point_int.dart';
|
|
||||||
import 'util.dart';
|
|
||||||
|
|
||||||
part 'puzzle_simple.dart';
|
|
||||||
|
|
||||||
part 'puzzle_smart.dart';
|
|
||||||
|
|
||||||
final _rnd = Random();
|
|
||||||
|
|
||||||
final _spacesRegexp = RegExp(' +');
|
|
||||||
|
|
||||||
abstract class Puzzle {
|
|
||||||
int get width;
|
|
||||||
|
|
||||||
int get length;
|
|
||||||
|
|
||||||
int operator [](int index);
|
|
||||||
|
|
||||||
int indexOf(int value);
|
|
||||||
|
|
||||||
List<int> get _intView;
|
|
||||||
|
|
||||||
List<int> _copyData();
|
|
||||||
|
|
||||||
Puzzle _newWithValues(List<int> values);
|
|
||||||
|
|
||||||
Puzzle clone();
|
|
||||||
|
|
||||||
int get height => length ~/ width;
|
|
||||||
|
|
||||||
Puzzle._();
|
|
||||||
|
|
||||||
factory Puzzle._raw(int width, List<int> source) {
|
|
||||||
if (source.length <= 16) {
|
|
||||||
return _PuzzleSmart(width, source);
|
|
||||||
}
|
|
||||||
return _PuzzleSimple(width, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
factory Puzzle.raw(int width, List<int> source) {
|
|
||||||
requireArgument(width >= 3, 'width', 'Must be at least 3.');
|
|
||||||
requireArgument(source.length >= 6, 'source', 'Must be at least 6 items');
|
|
||||||
_validate(source);
|
|
||||||
|
|
||||||
return Puzzle._raw(width, source);
|
|
||||||
}
|
|
||||||
|
|
||||||
factory Puzzle(int width, int height) =>
|
|
||||||
Puzzle.raw(width, _randomList(width, height));
|
|
||||||
|
|
||||||
factory Puzzle.parse(String input) {
|
|
||||||
final rows = LineSplitter.split(input).map((line) {
|
|
||||||
final splits = line.trim().split(_spacesRegexp);
|
|
||||||
return splits.map(int.parse).toList();
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
return Puzzle.raw(rows.first.length, rows.expand((row) => row).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
int valueAt(int x, int y) {
|
|
||||||
final i = _getIndex(x, y);
|
|
||||||
return this[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
int get tileCount => length - 1;
|
|
||||||
|
|
||||||
bool isCorrectPosition(int cellValue) => cellValue == this[cellValue];
|
|
||||||
|
|
||||||
bool get solvable => isSolvable(width, _intView);
|
|
||||||
|
|
||||||
Puzzle reset({List<int> source}) {
|
|
||||||
final data = (source == null)
|
|
||||||
? _randomizeList(width, _intView)
|
|
||||||
: Uint8List.fromList(source);
|
|
||||||
|
|
||||||
if (data.length != length) {
|
|
||||||
throw ArgumentError.value(source, 'source', 'Cannot change the size!');
|
|
||||||
}
|
|
||||||
_validate(data);
|
|
||||||
if (!isSolvable(width, data)) {
|
|
||||||
throw ArgumentError.value(source, 'source', 'Not a solvable puzzle.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return _newWithValues(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
int get incorrectTiles {
|
|
||||||
var count = tileCount;
|
|
||||||
for (var i = 0; i < tileCount; i++) {
|
|
||||||
if (isCorrectPosition(i)) {
|
|
||||||
count--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
Point openPosition() => coordinatesOf(tileCount);
|
|
||||||
|
|
||||||
/// A measure of how close the puzzle is to being solved.
|
|
||||||
///
|
|
||||||
/// The sum of all of the distances squared `(x + y)^2 ` each tile has to move
|
|
||||||
/// to be in the correct position.
|
|
||||||
///
|
|
||||||
/// `0` - you've won!
|
|
||||||
int get fitness {
|
|
||||||
var value = 0;
|
|
||||||
for (var i = 0; i < tileCount; i++) {
|
|
||||||
if (!isCorrectPosition(i)) {
|
|
||||||
final correctColumn = i % width;
|
|
||||||
final correctRow = i ~/ width;
|
|
||||||
|
|
||||||
final index = indexOf(i);
|
|
||||||
final x = index % width;
|
|
||||||
final y = index ~/ width;
|
|
||||||
|
|
||||||
final delta = (correctColumn - x).abs() + (correctRow - y).abs();
|
|
||||||
|
|
||||||
value += delta * delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value * incorrectTiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
Puzzle clickRandom({bool vertical}) {
|
|
||||||
final clickable = clickableValues(vertical: vertical).toList();
|
|
||||||
return clickValue(clickable[_rnd.nextInt(clickable.length)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterable<Puzzle> allMovable() =>
|
|
||||||
(clickableValues()..shuffle(_rnd)).map(_clickValue);
|
|
||||||
|
|
||||||
List<int> clickableValues({bool vertical}) {
|
|
||||||
final open = openPosition();
|
|
||||||
final doRow = vertical == null || vertical == false;
|
|
||||||
final doColumn = vertical == null || vertical;
|
|
||||||
|
|
||||||
final values =
|
|
||||||
Uint8List((doRow ? (width - 1) : 0) + (doColumn ? (height - 1) : 0));
|
|
||||||
|
|
||||||
var index = 0;
|
|
||||||
|
|
||||||
if (doRow) {
|
|
||||||
for (var x = 0; x < width; x++) {
|
|
||||||
if (x != open.x) {
|
|
||||||
values[index++] = valueAt(x, open.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (doColumn) {
|
|
||||||
for (var y = 0; y < height; y++) {
|
|
||||||
if (y != open.y) {
|
|
||||||
values[index++] = valueAt(open.x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _movable(int tileValue) {
|
|
||||||
if (tileValue == tileCount) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final target = coordinatesOf(tileValue);
|
|
||||||
final lastCoord = openPosition();
|
|
||||||
if (lastCoord.x != target.x && lastCoord.y != target.y) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Puzzle clickValue(int tileValue) {
|
|
||||||
if (!_movable(tileValue)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return _clickValue(tileValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
Puzzle _clickValue(int tileValue) {
|
|
||||||
assert(_movable(tileValue));
|
|
||||||
final target = coordinatesOf(tileValue);
|
|
||||||
|
|
||||||
final newStore = _copyData();
|
|
||||||
|
|
||||||
_shift(newStore, target.x, target.y);
|
|
||||||
return _newWithValues(newStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _shift(List<int> source, int targetX, int targetY) {
|
|
||||||
final lastCoord = openPosition();
|
|
||||||
final deltaX = lastCoord.x - targetX;
|
|
||||||
final deltaY = lastCoord.y - targetY;
|
|
||||||
|
|
||||||
if ((deltaX.abs() + deltaY.abs()) > 1) {
|
|
||||||
final shiftPointX = targetX + deltaX.sign;
|
|
||||||
final shiftPointY = targetY + deltaY.sign;
|
|
||||||
_shift(source, shiftPointX, shiftPointY);
|
|
||||||
_staticSwap(source, targetX, targetY, shiftPointX, shiftPointY);
|
|
||||||
} else {
|
|
||||||
_staticSwap(source, lastCoord.x, lastCoord.y, targetX, targetY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _staticSwap(List<int> source, int ax, int ay, int bx, int by) {
|
|
||||||
final aIndex = ax + ay * width;
|
|
||||||
final aValue = source[aIndex];
|
|
||||||
final bIndex = bx + by * width;
|
|
||||||
|
|
||||||
source[aIndex] = source[bIndex];
|
|
||||||
source[bIndex] = aValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Point coordinatesOf(int value) {
|
|
||||||
final index = indexOf(value);
|
|
||||||
final x = index % width;
|
|
||||||
final y = index ~/ width;
|
|
||||||
assert(_getIndex(x, y) == index);
|
|
||||||
return Point(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
int _getIndex(int x, int y) {
|
|
||||||
assert(x >= 0 && x < width);
|
|
||||||
assert(y >= 0 && y < height);
|
|
||||||
return x + y * width;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => _toString();
|
|
||||||
|
|
||||||
String _toString() {
|
|
||||||
final grid = List<List<String>>.generate(
|
|
||||||
height,
|
|
||||||
(row) => List<String>.generate(
|
|
||||||
width, (col) => valueAt(col, row).toString()));
|
|
||||||
|
|
||||||
final longestLength =
|
|
||||||
grid.expand((r) => r).fold(0, (int l, cell) => max(l, cell.length));
|
|
||||||
|
|
||||||
return grid
|
|
||||||
.map((r) => r.map((v) => v.padLeft(longestLength)).join(' '))
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List _randomList(int width, int height) => _randomizeList(
|
|
||||||
width, List<int>.generate(width * height, (i) => i, growable: false));
|
|
||||||
|
|
||||||
Uint8List _randomizeList(int width, List<int> existing) {
|
|
||||||
final copy = Uint8List.fromList(existing);
|
|
||||||
do {
|
|
||||||
copy.shuffle(_rnd);
|
|
||||||
} while (!isSolvable(width, copy) ||
|
|
||||||
copy.any((v) => copy[v] == v || copy[v] == existing[v]));
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _validate(List<int> source) {
|
|
||||||
for (var i = 0; i < source.length; i++) {
|
|
||||||
requireArgument(
|
|
||||||
source.contains(i),
|
|
||||||
'source',
|
|
||||||
'Must contain each number from 0 to `length - 1` '
|
|
||||||
'once and only once.');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'dart:math' show Point;
|
|
||||||
|
|
||||||
enum PuzzleEvent { click, random, reset, noop }
|
|
||||||
|
|
||||||
abstract class PuzzleProxy {
|
|
||||||
int get width;
|
|
||||||
|
|
||||||
int get height;
|
|
||||||
|
|
||||||
int get length;
|
|
||||||
|
|
||||||
bool get solved;
|
|
||||||
|
|
||||||
void clickOrShake(int tileValue);
|
|
||||||
|
|
||||||
int get tileCount;
|
|
||||||
|
|
||||||
Point<double> location(int index);
|
|
||||||
|
|
||||||
bool isCorrectPosition(int value);
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
part of 'puzzle.dart';
|
|
||||||
|
|
||||||
class _PuzzleSimple extends Puzzle {
|
|
||||||
@override
|
|
||||||
final int width;
|
|
||||||
final Uint8List _source;
|
|
||||||
|
|
||||||
_PuzzleSimple(this.width, List<int> source)
|
|
||||||
: _source = UnmodifiableUint8ListView(Uint8List.fromList(source)),
|
|
||||||
super._();
|
|
||||||
|
|
||||||
@override
|
|
||||||
int indexOf(int value) => _source.indexOf(value);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<int> get _intView => _source;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<int> _copyData() => Uint8List.fromList(_source);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Puzzle _newWithValues(List<int> values) => _PuzzleSimple(width, values);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int operator [](int index) => _source[index];
|
|
||||||
|
|
||||||
@override
|
|
||||||
Puzzle clone() => _PuzzleSimple(width, _source);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get length => _source.length;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(other) {
|
|
||||||
if (other is Puzzle &&
|
|
||||||
other.width == width &&
|
|
||||||
other.length == _source.length) {
|
|
||||||
for (var i = 0; i < _source.length; i++) {
|
|
||||||
if (other[i] != _source[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode {
|
|
||||||
var v = 0;
|
|
||||||
for (var i = 0; i < _source.length; i++) {
|
|
||||||
v = (v << 2) + _source[i];
|
|
||||||
}
|
|
||||||
v += v << 3;
|
|
||||||
v ^= v >> 11;
|
|
||||||
v += v << 15;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,188 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
part of 'puzzle.dart';
|
|
||||||
|
|
||||||
mixin _SliceListMixin on ListMixin<int> {
|
|
||||||
static const _bitsPerValue = 4;
|
|
||||||
static const _maxShift = _valuesPerCell - 1;
|
|
||||||
|
|
||||||
static const _bitsPerCell = 32;
|
|
||||||
static const _valuesPerCell = _bitsPerCell ~/ _bitsPerValue;
|
|
||||||
static const _valueMask = (1 << _bitsPerValue) - 1;
|
|
||||||
|
|
||||||
Uint32List get _data;
|
|
||||||
|
|
||||||
int _cellValue(int index) => _data[index ~/ _valuesPerCell];
|
|
||||||
|
|
||||||
@override
|
|
||||||
int operator [](int index) {
|
|
||||||
/*
|
|
||||||
final bigValue = _data[index ~/ _valuesPerCell];
|
|
||||||
final shiftedValue =
|
|
||||||
bigValue >> (_maxShift - (index % _valuesPerCell)) * _bitsPerValue;
|
|
||||||
final flattenedValue = shiftedValue & _valueMask;
|
|
||||||
return flattenedValue;
|
|
||||||
*/
|
|
||||||
return (_cellValue(index) >>
|
|
||||||
(_maxShift - (index % _valuesPerCell)) * _bitsPerValue) &
|
|
||||||
_valueMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int indexOf(Object value, [int start]) {
|
|
||||||
for (var i = 0; i < _data.length; i++) {
|
|
||||||
final cellValue = _data[i];
|
|
||||||
for (var j = 0; j < _valuesPerCell; j++) {
|
|
||||||
final option =
|
|
||||||
(cellValue >> (_maxShift - j) * _bitsPerValue) & _valueMask;
|
|
||||||
|
|
||||||
if (value == option) {
|
|
||||||
final k = i * _valuesPerCell + j;
|
|
||||||
if (k < length && (start == null || k >= start)) {
|
|
||||||
return k;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
set length(int value) => throw UnsupportedError('immutable, yo!');
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SliceList with ListMixin<int>, _SliceListMixin {
|
|
||||||
@override
|
|
||||||
final Uint32List _data;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final int length;
|
|
||||||
|
|
||||||
_SliceList(this.length, this._data);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void operator []=(int index, int value) {
|
|
||||||
var cellValue = _cellValue(index);
|
|
||||||
|
|
||||||
// clear out the target bits in `cellValue`
|
|
||||||
|
|
||||||
final sharedShift =
|
|
||||||
(_SliceListMixin._maxShift - (index % _SliceListMixin._valuesPerCell)) *
|
|
||||||
_SliceListMixin._bitsPerValue;
|
|
||||||
|
|
||||||
final wipeout = _SliceListMixin._valueMask << sharedShift;
|
|
||||||
|
|
||||||
cellValue &= ~wipeout;
|
|
||||||
|
|
||||||
final newShiftedValue = value << sharedShift;
|
|
||||||
|
|
||||||
cellValue |= newShiftedValue;
|
|
||||||
|
|
||||||
_data[index ~/ _SliceListMixin._valuesPerCell] = cellValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PuzzleSmart extends Puzzle with ListMixin<int>, _SliceListMixin {
|
|
||||||
static const _bitsPerValue = 4;
|
|
||||||
static const _maxShift = _valuesPerCell - 1;
|
|
||||||
|
|
||||||
static const _bitsPerCell = 32;
|
|
||||||
static const _valuesPerCell = _bitsPerCell ~/ _bitsPerValue;
|
|
||||||
static const _valueMask = (1 << _bitsPerValue) - 1;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final Uint32List _data;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final int width;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final int length;
|
|
||||||
|
|
||||||
int _fitnessCache;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get fitness => _fitnessCache ??= super.fitness;
|
|
||||||
|
|
||||||
_PuzzleSmart(this.width, List<int> source)
|
|
||||||
: length = source.length,
|
|
||||||
_data = _create(source),
|
|
||||||
super._();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void operator []=(int index, int value) =>
|
|
||||||
throw UnsupportedError('immutable, yo!');
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<int> get _intView => this;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<int> _copyData() => _SliceList(length, Uint32List.fromList(_data));
|
|
||||||
|
|
||||||
@override
|
|
||||||
Puzzle _newWithValues(List<int> values) => _PuzzleSmart(width, values);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Puzzle clone() => _PuzzleSmart(width, this);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => _toString();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(other) {
|
|
||||||
if (other is _PuzzleSmart &&
|
|
||||||
other.width == width &&
|
|
||||||
other._data.length == _data.length) {
|
|
||||||
for (var i = 0; i < _data.length; i++) {
|
|
||||||
if (other._data[i] != _data[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (other is Puzzle && other.width == width && other.length == length) {
|
|
||||||
for (var i = 0; i < length; i++) {
|
|
||||||
if (other[i] != this[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode {
|
|
||||||
var v = 0;
|
|
||||||
for (var i = 0; i < _data.length; i++) {
|
|
||||||
v = (v << 2) + _data[i];
|
|
||||||
}
|
|
||||||
v += v << 3;
|
|
||||||
v ^= v >> 11;
|
|
||||||
v += v << 15;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Uint32List _create(List<int> source) {
|
|
||||||
if (source is _SliceList) {
|
|
||||||
return source._data;
|
|
||||||
}
|
|
||||||
|
|
||||||
final data = Uint32List((source.length / _valuesPerCell).ceil());
|
|
||||||
for (var i = 0; i < data.length; i++) {
|
|
||||||
var value = 0;
|
|
||||||
for (var j = 0; j < _valuesPerCell; j++) {
|
|
||||||
final k = i * _valuesPerCell + j;
|
|
||||||
if (k < source.length) {
|
|
||||||
// shift the value over 4 bits for item 0, 3 for item 2, etc
|
|
||||||
final sourceValue = source[k] << ((_maxShift - j) * _bitsPerValue);
|
|
||||||
value |= sourceValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data[i] = value;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
void requireArgument(bool truth, String argName, [String message]) {
|
|
||||||
if (!truth) {
|
|
||||||
if (message == null || message.isEmpty) {
|
|
||||||
message = 'value was invalid';
|
|
||||||
}
|
|
||||||
throw ArgumentError('`$argName` - $message');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void requireArgumentNotNull(argument, String argName) {
|
|
||||||
if (argument == null) {
|
|
||||||
throw ArgumentError.notNull(argName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logic from
|
|
||||||
// https://www.cs.bham.ac.uk/~mdr/teaching/modules04/java2/TilesSolvability.html
|
|
||||||
// Used with gratitude!
|
|
||||||
bool isSolvable(int width, List<int> list) {
|
|
||||||
final height = list.length ~/ width;
|
|
||||||
assert(width * height == list.length);
|
|
||||||
final inversions = _countInversions(list);
|
|
||||||
|
|
||||||
if (width.isOdd) {
|
|
||||||
return inversions.isEven;
|
|
||||||
}
|
|
||||||
|
|
||||||
final blankRow = list.indexOf(list.length - 1) ~/ width;
|
|
||||||
|
|
||||||
if ((height - blankRow).isEven) {
|
|
||||||
return inversions.isOdd;
|
|
||||||
} else {
|
|
||||||
return inversions.isEven;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int _countInversions(List<int> items) {
|
|
||||||
final tileCount = items.length - 1;
|
|
||||||
var score = 0;
|
|
||||||
for (var i = 0; i < items.length; i++) {
|
|
||||||
final value = items[i];
|
|
||||||
if (value == tileCount) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var j = i + 1; j < items.length; j++) {
|
|
||||||
final v = items[j];
|
|
||||||
if (v != tileCount && v < value) {
|
|
||||||
score++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return score;
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
export 'package:flutter/material.dart';
|
|
||||||
export 'package:flutter/scheduler.dart' show Ticker;
|
|
@ -1,17 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
abstract class PuzzleControls implements Listenable {
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
int get clickCount;
|
|
||||||
|
|
||||||
int get incorrectTiles;
|
|
||||||
|
|
||||||
bool get autoPlay;
|
|
||||||
|
|
||||||
void Function(bool newValue) get setAutoPlayFunction;
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'core/puzzle_proxy.dart';
|
|
||||||
import 'flutter.dart';
|
|
||||||
|
|
||||||
class PuzzleFlowDelegate extends FlowDelegate {
|
|
||||||
final Size _tileSize;
|
|
||||||
final PuzzleProxy _puzzleProxy;
|
|
||||||
|
|
||||||
PuzzleFlowDelegate(this._tileSize, this._puzzleProxy, Listenable repaint)
|
|
||||||
: super(repaint: repaint);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Size getSize(BoxConstraints constraints) => Size(
|
|
||||||
_tileSize.width * _puzzleProxy.width,
|
|
||||||
_tileSize.height * _puzzleProxy.height);
|
|
||||||
|
|
||||||
@override
|
|
||||||
BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) =>
|
|
||||||
BoxConstraints.tight(_tileSize);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paintChildren(FlowPaintingContext context) {
|
|
||||||
for (var i = 0; i < _puzzleProxy.length; i++) {
|
|
||||||
final tileLocation = _puzzleProxy.location(i);
|
|
||||||
context.paintChild(
|
|
||||||
i,
|
|
||||||
transform: Matrix4.translationValues(
|
|
||||||
tileLocation.x * _tileSize.width,
|
|
||||||
tileLocation.y * _tileSize.height,
|
|
||||||
i.toDouble(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(covariant PuzzleFlowDelegate oldDelegate) =>
|
|
||||||
_tileSize != oldDelegate._tileSize ||
|
|
||||||
!identical(oldDelegate._puzzleProxy, _puzzleProxy);
|
|
||||||
}
|
|
@ -1,285 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import 'app_state.dart';
|
|
||||||
import 'core/puzzle_animator.dart';
|
|
||||||
import 'core/puzzle_proxy.dart';
|
|
||||||
import 'flutter.dart';
|
|
||||||
import 'puzzle_controls.dart';
|
|
||||||
import 'puzzle_flow_delegate.dart';
|
|
||||||
import 'shared_theme.dart';
|
|
||||||
import 'themes.dart';
|
|
||||||
import 'value_tab_controller.dart';
|
|
||||||
|
|
||||||
class _PuzzleControls extends ChangeNotifier implements PuzzleControls {
|
|
||||||
final PuzzleHomeState _parent;
|
|
||||||
|
|
||||||
_PuzzleControls(this._parent);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get autoPlay => _parent._autoPlay;
|
|
||||||
|
|
||||||
void _notify() => notifyListeners();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void Function(bool newValue) get setAutoPlayFunction {
|
|
||||||
if (_parent.puzzle.solved) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return _parent._setAutoPlay;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get clickCount => _parent.puzzle.clickCount;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get incorrectTiles => _parent.puzzle.incorrectTiles;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void reset() => _parent.puzzle.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
class PuzzleHomeState extends State
|
|
||||||
with SingleTickerProviderStateMixin, AppState {
|
|
||||||
@override
|
|
||||||
final PuzzleAnimator puzzle;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final _AnimationNotifier animationNotifier = _AnimationNotifier();
|
|
||||||
|
|
||||||
Duration _tickerTimeSinceLastEvent = Duration.zero;
|
|
||||||
Ticker _ticker;
|
|
||||||
Duration _lastElapsed;
|
|
||||||
StreamSubscription _puzzleEventSubscription;
|
|
||||||
|
|
||||||
bool _autoPlay = false;
|
|
||||||
_PuzzleControls _autoPlayListenable;
|
|
||||||
|
|
||||||
PuzzleHomeState(this.puzzle) {
|
|
||||||
_puzzleEventSubscription = puzzle.onEvent.listen(_onPuzzleEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_autoPlayListenable = _PuzzleControls(this);
|
|
||||||
_ticker ??= createTicker(_onTick);
|
|
||||||
_ensureTicking();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setAutoPlay(bool newValue) {
|
|
||||||
if (newValue != _autoPlay) {
|
|
||||||
setState(() {
|
|
||||||
// Only allow enabling autoPlay if the puzzle is not solved
|
|
||||||
_autoPlayListenable._notify();
|
|
||||||
_autoPlay = newValue && !puzzle.solved;
|
|
||||||
if (_autoPlay) {
|
|
||||||
_ensureTicking();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
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();
|
|
||||||
_ticker?.dispose();
|
|
||||||
_autoPlayListenable?.dispose();
|
|
||||||
_puzzleEventSubscription.cancel();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onPuzzleEvent(PuzzleEvent e) {
|
|
||||||
_autoPlayListenable._notify();
|
|
||||||
if (e != PuzzleEvent.random) {
|
|
||||||
_setAutoPlay(false);
|
|
||||||
}
|
|
||||||
_tickerTimeSinceLastEvent = Duration.zero;
|
|
||||||
_ensureTicking();
|
|
||||||
setState(() {
|
|
||||||
// noop
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _ensureTicking() {
|
|
||||||
if (!_ticker.isTicking) {
|
|
||||||
_ticker.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onTick(Duration elapsed) {
|
|
||||||
if (elapsed == Duration.zero) {
|
|
||||||
_lastElapsed = elapsed;
|
|
||||||
}
|
|
||||||
final delta = elapsed - _lastElapsed;
|
|
||||||
_lastElapsed = elapsed;
|
|
||||||
|
|
||||||
if (delta.inMilliseconds <= 0) {
|
|
||||||
// `_delta` may be negative or zero if `elapsed` is zero (first tick)
|
|
||||||
// or during a restart. Just ignore this case.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_tickerTimeSinceLastEvent += delta;
|
|
||||||
puzzle.update(delta > _maxFrameDuration ? _maxFrameDuration : delta);
|
|
||||||
|
|
||||||
if (!puzzle.stable) {
|
|
||||||
animationNotifier.animate();
|
|
||||||
} else {
|
|
||||||
if (!_autoPlay) {
|
|
||||||
_ticker.stop();
|
|
||||||
_lastElapsed = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_autoPlay &&
|
|
||||||
_tickerTimeSinceLastEvent > const Duration(milliseconds: 200)) {
|
|
||||||
puzzle.playRandom();
|
|
||||||
|
|
||||||
if (puzzle.solved) {
|
|
||||||
_setAutoPlay(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,116 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'core/puzzle_proxy.dart';
|
|
||||||
import 'flutter.dart';
|
|
||||||
import 'puzzle_controls.dart';
|
|
||||||
import 'widgets/material_interior_alt.dart';
|
|
||||||
|
|
||||||
final puzzleAnimationDuration = kThemeAnimationDuration * 3;
|
|
||||||
|
|
||||||
abstract class SharedTheme {
|
|
||||||
const SharedTheme();
|
|
||||||
|
|
||||||
String get name;
|
|
||||||
|
|
||||||
Color get puzzleThemeBackground;
|
|
||||||
|
|
||||||
RoundedRectangleBorder puzzleBorder(bool small);
|
|
||||||
|
|
||||||
Color get puzzleBackgroundColor;
|
|
||||||
|
|
||||||
Color get puzzleAccentColor;
|
|
||||||
|
|
||||||
EdgeInsetsGeometry tilePadding(PuzzleProxy puzzle) => const EdgeInsets.all(6);
|
|
||||||
|
|
||||||
Widget tileButton(int i, PuzzleProxy puzzle, bool small);
|
|
||||||
|
|
||||||
Ink createInk(
|
|
||||||
Widget child, {
|
|
||||||
DecorationImage image,
|
|
||||||
EdgeInsetsGeometry padding,
|
|
||||||
}) =>
|
|
||||||
Ink(
|
|
||||||
padding: padding,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
image: image,
|
|
||||||
),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget createButton(
|
|
||||||
PuzzleProxy puzzle,
|
|
||||||
bool small,
|
|
||||||
int tileValue,
|
|
||||||
Widget content, {
|
|
||||||
Color color,
|
|
||||||
RoundedRectangleBorder shape,
|
|
||||||
}) =>
|
|
||||||
AnimatedContainer(
|
|
||||||
duration: puzzleAnimationDuration,
|
|
||||||
padding: tilePadding(puzzle),
|
|
||||||
child: RaisedButton(
|
|
||||||
elevation: 4,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
animationDuration: puzzleAnimationDuration,
|
|
||||||
onPressed: () => puzzle.clickOrShake(tileValue),
|
|
||||||
shape: shape ?? puzzleBorder(small),
|
|
||||||
padding: const EdgeInsets.symmetric(),
|
|
||||||
child: content,
|
|
||||||
color: color,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Thought about using AnimatedContainer here, but it causes some weird
|
|
||||||
// resizing behavior
|
|
||||||
Widget styledWrapper(bool small, Widget child) => MaterialInterior(
|
|
||||||
duration: puzzleAnimationDuration,
|
|
||||||
shape: puzzleBorder(small),
|
|
||||||
color: puzzleBackgroundColor,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
|
|
||||||
TextStyle get _infoStyle => TextStyle(
|
|
||||||
color: puzzleAccentColor,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
);
|
|
||||||
|
|
||||||
List<Widget> bottomControls(PuzzleControls controls) => <Widget>[
|
|
||||||
IconButton(
|
|
||||||
onPressed: controls.reset,
|
|
||||||
icon: Icon(Icons.refresh, color: puzzleAccentColor),
|
|
||||||
),
|
|
||||||
Checkbox(
|
|
||||||
value: controls.autoPlay,
|
|
||||||
onChanged: controls.setAutoPlayFunction,
|
|
||||||
activeColor: puzzleAccentColor,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
controls.clickCount.toString(),
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
style: _infoStyle,
|
|
||||||
),
|
|
||||||
const Text(' Moves'),
|
|
||||||
SizedBox(
|
|
||||||
width: 28,
|
|
||||||
child: Text(
|
|
||||||
controls.incorrectTiles.toString(),
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
style: _infoStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(' Tiles left ')
|
|
||||||
];
|
|
||||||
|
|
||||||
Widget tileButtonCore(int i, PuzzleProxy puzzle, bool small) {
|
|
||||||
if (i == puzzle.tileCount && !puzzle.solved) {
|
|
||||||
return const Center();
|
|
||||||
}
|
|
||||||
|
|
||||||
return tileButton(i, puzzle, small);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'core/puzzle_proxy.dart';
|
|
||||||
import 'flutter.dart';
|
|
||||||
import 'shared_theme.dart';
|
|
||||||
|
|
||||||
const _yellowIsh = Color.fromARGB(255, 248, 244, 233);
|
|
||||||
const _chocolate = Color.fromARGB(255, 66, 66, 68);
|
|
||||||
const _orangeIsh = Color.fromARGB(255, 224, 107, 83);
|
|
||||||
|
|
||||||
class ThemePlaster extends SharedTheme {
|
|
||||||
@override
|
|
||||||
String get name => 'Plaster';
|
|
||||||
|
|
||||||
const ThemePlaster();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Color get puzzleThemeBackground => _chocolate;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Color get puzzleBackgroundColor => _yellowIsh;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Color get puzzleAccentColor => _orangeIsh;
|
|
||||||
|
|
||||||
@override
|
|
||||||
RoundedRectangleBorder puzzleBorder(bool small) => RoundedRectangleBorder(
|
|
||||||
side: const BorderSide(
|
|
||||||
color: Color.fromARGB(255, 103, 103, 105),
|
|
||||||
width: 8,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(small ? 10 : 18),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget tileButton(int i, PuzzleProxy puzzle, bool small) {
|
|
||||||
final correctColumn = i % puzzle.width;
|
|
||||||
final correctRow = i ~/ puzzle.width;
|
|
||||||
|
|
||||||
final primary = (correctColumn + correctRow).isEven;
|
|
||||||
|
|
||||||
if (i == puzzle.tileCount) {
|
|
||||||
assert(puzzle.solved);
|
|
||||||
return Center(
|
|
||||||
child: Icon(
|
|
||||||
Icons.thumb_up,
|
|
||||||
size: small ? 50 : 72,
|
|
||||||
color: _orangeIsh,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final content = Text(
|
|
||||||
(i + 1).toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
color: primary ? _yellowIsh : _chocolate,
|
|
||||||
fontFamily: 'Plaster',
|
|
||||||
fontSize: small ? 40 : 77,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return createButton(
|
|
||||||
puzzle,
|
|
||||||
small,
|
|
||||||
i,
|
|
||||||
content,
|
|
||||||
color: primary ? _orangeIsh : _yellowIsh,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
side: BorderSide(color: primary ? _chocolate : _orangeIsh, width: 5),
|
|
||||||
borderRadius: BorderRadius.circular(5),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'core/puzzle_proxy.dart';
|
|
||||||
import 'flutter.dart';
|
|
||||||
import 'shared_theme.dart';
|
|
||||||
import 'widgets/decoration_image_plus.dart';
|
|
||||||
|
|
||||||
class ThemeSeattle extends SharedTheme {
|
|
||||||
@override
|
|
||||||
String get name => 'Seattle';
|
|
||||||
|
|
||||||
const ThemeSeattle();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Color get puzzleThemeBackground => const Color.fromARGB(153, 90, 135, 170);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Color get puzzleBackgroundColor => Colors.white70;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Color get puzzleAccentColor => const Color(0xff000579f);
|
|
||||||
|
|
||||||
@override
|
|
||||||
RoundedRectangleBorder puzzleBorder(bool small) =>
|
|
||||||
const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(1),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
EdgeInsetsGeometry tilePadding(PuzzleProxy puzzle) =>
|
|
||||||
puzzle.solved ? const EdgeInsets.all(1) : const EdgeInsets.all(4);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget tileButton(int i, PuzzleProxy puzzle, bool small) {
|
|
||||||
if (i == puzzle.tileCount && !puzzle.solved) {
|
|
||||||
assert(puzzle.solved);
|
|
||||||
}
|
|
||||||
|
|
||||||
final decorationImage = DecorationImagePlus(
|
|
||||||
puzzleWidth: puzzle.width,
|
|
||||||
puzzleHeight: puzzle.height,
|
|
||||||
pieceIndex: i,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
image: const AssetImage('asset/seattle.jpg'));
|
|
||||||
|
|
||||||
final correctPosition = puzzle.isCorrectPosition(i);
|
|
||||||
final content = createInk(
|
|
||||||
puzzle.solved
|
|
||||||
? const Center()
|
|
||||||
: Container(
|
|
||||||
decoration: ShapeDecoration(
|
|
||||||
shape: const CircleBorder(),
|
|
||||||
color: correctPosition ? Colors.black38 : Colors.white54,
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(
|
|
||||||
(i + 1).toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
color: correctPosition ? Colors.white : Colors.black,
|
|
||||||
fontSize: small ? 25 : 42,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
image: decorationImage,
|
|
||||||
padding: EdgeInsets.all(small ? 20 : 32),
|
|
||||||
);
|
|
||||||
|
|
||||||
return createButton(puzzle, small, i, content);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'core/puzzle_proxy.dart';
|
|
||||||
import 'flutter.dart';
|
|
||||||
import 'shared_theme.dart';
|
|
||||||
|
|
||||||
const _accentBlue = Color(0xff000579e);
|
|
||||||
|
|
||||||
class ThemeSimple extends SharedTheme {
|
|
||||||
@override
|
|
||||||
String get name => 'Simple';
|
|
||||||
|
|
||||||
const ThemeSimple();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Color get puzzleThemeBackground => Colors.white;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Color get puzzleBackgroundColor => Colors.white70;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Color get puzzleAccentColor => _accentBlue;
|
|
||||||
|
|
||||||
@override
|
|
||||||
RoundedRectangleBorder puzzleBorder(bool small) =>
|
|
||||||
const RoundedRectangleBorder(
|
|
||||||
side: BorderSide(color: Colors.black26, width: 1),
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(4),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget tileButton(int i, PuzzleProxy puzzle, bool small) {
|
|
||||||
if (i == puzzle.tileCount) {
|
|
||||||
assert(puzzle.solved);
|
|
||||||
return const Center(
|
|
||||||
child: Icon(
|
|
||||||
Icons.thumb_up,
|
|
||||||
size: 72,
|
|
||||||
color: _accentBlue,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final correctPosition = puzzle.isCorrectPosition(i);
|
|
||||||
|
|
||||||
final content = createInk(
|
|
||||||
Center(
|
|
||||||
child: Text(
|
|
||||||
(i + 1).toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: correctPosition ? FontWeight.bold : FontWeight.normal,
|
|
||||||
fontSize: small ? 30 : 49,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return createButton(
|
|
||||||
puzzle,
|
|
||||||
small,
|
|
||||||
i,
|
|
||||||
content,
|
|
||||||
color: const Color.fromARGB(255, 13, 87, 155),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'theme_plaster.dart';
|
|
||||||
import 'theme_seattle.dart';
|
|
||||||
import 'theme_simple.dart';
|
|
||||||
|
|
||||||
const themes = [
|
|
||||||
ThemeSimple(),
|
|
||||||
ThemeSeattle(),
|
|
||||||
ThemePlaster(),
|
|
||||||
];
|
|
@ -1,92 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class ValueTabController<T> extends StatefulWidget {
|
|
||||||
/// Creates a default tab controller for the given [child] widget.
|
|
||||||
const ValueTabController({
|
|
||||||
Key key,
|
|
||||||
@required this.child,
|
|
||||||
@required this.values,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
/// The widget below this widget in the tree.
|
|
||||||
///
|
|
||||||
/// Typically a [Scaffold] whose [AppBar] includes a [TabBar].
|
|
||||||
///
|
|
||||||
/// {@macro flutter.widgets.child}
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
final List<T> values;
|
|
||||||
|
|
||||||
/// The closest instance of this class that encloses the given context.
|
|
||||||
///
|
|
||||||
/// Typical usage:
|
|
||||||
///
|
|
||||||
/// ```dart
|
|
||||||
/// TabController controller = DefaultTabBarController.of(context);
|
|
||||||
/// ```
|
|
||||||
static TabController of(BuildContext context) {
|
|
||||||
final scope = context.inheritFromWidgetOfExactType(_ValueTabControllerScope)
|
|
||||||
as _ValueTabControllerScope;
|
|
||||||
return scope?.controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
_ValueTabControllerState<T> createState() => _ValueTabControllerState<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ValueTabControllerState<T> extends State<ValueTabController<T>>
|
|
||||||
with SingleTickerProviderStateMixin {
|
|
||||||
final _notifier = ValueNotifier<T>(null);
|
|
||||||
|
|
||||||
TabController _controller;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_controller = TabController(
|
|
||||||
vsync: this,
|
|
||||||
length: widget.values.length,
|
|
||||||
initialIndex: 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
_notifier.value = widget.values.first;
|
|
||||||
|
|
||||||
_controller.addListener(() {
|
|
||||||
_notifier.value = widget.values[_controller.index];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_controller.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => _ValueTabControllerScope(
|
|
||||||
controller: _controller,
|
|
||||||
enabled: TickerMode.of(context),
|
|
||||||
child: ValueListenableProvider.value(
|
|
||||||
valueListenable: _notifier,
|
|
||||||
child: widget.child,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ValueTabControllerScope extends InheritedWidget {
|
|
||||||
const _ValueTabControllerScope(
|
|
||||||
{Key key, this.controller, this.enabled, Widget child})
|
|
||||||
: super(key: key, child: child);
|
|
||||||
|
|
||||||
final TabController controller;
|
|
||||||
final bool enabled;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool updateShouldNotify(_ValueTabControllerScope old) =>
|
|
||||||
enabled != old.enabled || controller != old.controller;
|
|
||||||
}
|
|
@ -1,334 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// ignore_for_file: omit_local_variable_types, annotate_overrides
|
|
||||||
|
|
||||||
import 'dart:developer' as developer;
|
|
||||||
import 'dart:ui' as ui show Image;
|
|
||||||
|
|
||||||
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
|
|
||||||
@immutable
|
|
||||||
class DecorationImagePlus implements DecorationImage {
|
|
||||||
final int puzzleWidth, puzzleHeight, pieceIndex;
|
|
||||||
|
|
||||||
/// Creates an image to show in a [BoxDecoration].
|
|
||||||
///
|
|
||||||
/// The [image], [alignment], [repeat], and [matchTextDirection] arguments
|
|
||||||
/// must not be null.
|
|
||||||
const DecorationImagePlus({
|
|
||||||
@required this.image,
|
|
||||||
@required this.puzzleWidth,
|
|
||||||
@required this.puzzleHeight,
|
|
||||||
@required this.pieceIndex,
|
|
||||||
this.colorFilter,
|
|
||||||
this.fit,
|
|
||||||
this.alignment = Alignment.center,
|
|
||||||
this.centerSlice,
|
|
||||||
this.repeat = ImageRepeat.noRepeat,
|
|
||||||
this.matchTextDirection = false,
|
|
||||||
}) : assert(image != null),
|
|
||||||
assert(alignment != null),
|
|
||||||
assert(repeat != null),
|
|
||||||
assert(matchTextDirection != null),
|
|
||||||
assert(puzzleHeight > 1 &&
|
|
||||||
puzzleHeight > 1 &&
|
|
||||||
pieceIndex >= 0 &&
|
|
||||||
pieceIndex < (puzzleHeight * puzzleWidth));
|
|
||||||
|
|
||||||
/// The image to be painted into the decoration.
|
|
||||||
///
|
|
||||||
/// Typically this will be an [AssetImage] (for an image shipped with the
|
|
||||||
/// application) or a [NetworkImage] (for an image obtained from the network).
|
|
||||||
final ImageProvider image;
|
|
||||||
|
|
||||||
/// A color filter to apply to the image before painting it.
|
|
||||||
final ColorFilter colorFilter;
|
|
||||||
|
|
||||||
/// How the image should be inscribed into the box.
|
|
||||||
///
|
|
||||||
/// The default is [BoxFit.scaleDown] if [centerSlice] is null, and
|
|
||||||
/// [BoxFit.fill] if [centerSlice] is not null.
|
|
||||||
///
|
|
||||||
/// See the discussion at [_paintImage] for more details.
|
|
||||||
final BoxFit fit;
|
|
||||||
|
|
||||||
/// How to align the image within its bounds.
|
|
||||||
///
|
|
||||||
/// The alignment aligns the given position in the image to the given position
|
|
||||||
/// in the layout bounds. For example, an [Alignment] alignment of (-1.0,
|
|
||||||
/// -1.0) aligns the image to the top-left corner of its layout bounds, while a
|
|
||||||
/// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
|
|
||||||
/// image with the bottom right corner of its layout bounds. Similarly, an
|
|
||||||
/// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
|
|
||||||
/// middle of the bottom edge of its layout bounds.
|
|
||||||
///
|
|
||||||
/// To display a subpart of an image, consider using a [CustomPainter] and
|
|
||||||
/// [Canvas.drawImageRect].
|
|
||||||
///
|
|
||||||
/// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
|
|
||||||
/// [AlignmentDirectional]), then a [TextDirection] must be available
|
|
||||||
/// when the image is painted.
|
|
||||||
///
|
|
||||||
/// Defaults to [Alignment.center].
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [Alignment], a class with convenient constants typically used to
|
|
||||||
/// specify an [AlignmentGeometry].
|
|
||||||
/// * [AlignmentDirectional], like [Alignment] for specifying alignments
|
|
||||||
/// relative to text direction.
|
|
||||||
final AlignmentGeometry alignment;
|
|
||||||
|
|
||||||
/// The center slice for a nine-patch image.
|
|
||||||
///
|
|
||||||
/// The region of the image inside the center slice will be stretched both
|
|
||||||
/// horizontally and vertically to fit the image into its destination. The
|
|
||||||
/// region of the image above and below the center slice will be stretched
|
|
||||||
/// only horizontally and the region of the image to the left and right of
|
|
||||||
/// the center slice will be stretched only vertically.
|
|
||||||
///
|
|
||||||
/// The stretching will be applied in order to make the image fit into the box
|
|
||||||
/// specified by [fit]. When [centerSlice] is not null, [fit] defaults to
|
|
||||||
/// [BoxFit.fill], which distorts the destination image size relative to the
|
|
||||||
/// image's original aspect ratio. Values of [BoxFit] which do not distort the
|
|
||||||
/// destination image size will result in [centerSlice] having no effect
|
|
||||||
/// (since the nine regions of the image will be rendered with the same
|
|
||||||
/// scaling, as if it wasn't specified).
|
|
||||||
final Rect centerSlice;
|
|
||||||
|
|
||||||
/// How to paint any portions of the box that would not otherwise be covered
|
|
||||||
/// by the image.
|
|
||||||
final ImageRepeat repeat;
|
|
||||||
|
|
||||||
/// Whether to paint the image in the direction of the [TextDirection].
|
|
||||||
///
|
|
||||||
/// If this is true, then in [TextDirection.ltr] contexts, the image will be
|
|
||||||
/// drawn with its origin in the top left (the "normal" painting direction for
|
|
||||||
/// images); and in [TextDirection.rtl] contexts, the image will be drawn with
|
|
||||||
/// a scaling factor of -1 in the horizontal direction so that the origin is
|
|
||||||
/// in the top right.
|
|
||||||
final bool matchTextDirection;
|
|
||||||
|
|
||||||
/// Creates a [DecorationImagePainterPlus] for this [DecorationImagePlus].
|
|
||||||
///
|
|
||||||
/// The `onChanged` argument must not be null. It will be called whenever the
|
|
||||||
/// image needs to be repainted, e.g. because it is loading incrementally or
|
|
||||||
/// because it is animated.
|
|
||||||
DecorationImagePainterPlus createPainter(VoidCallback onChanged) {
|
|
||||||
assert(onChanged != null);
|
|
||||||
return DecorationImagePainterPlus._(this, onChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(dynamic other) {
|
|
||||||
if (identical(this, other)) return true;
|
|
||||||
return other is DecorationImagePlus &&
|
|
||||||
other.runtimeType == runtimeType &&
|
|
||||||
image == other.image &&
|
|
||||||
colorFilter == other.colorFilter &&
|
|
||||||
fit == other.fit &&
|
|
||||||
alignment == other.alignment &&
|
|
||||||
centerSlice == other.centerSlice &&
|
|
||||||
repeat == other.repeat &&
|
|
||||||
matchTextDirection == other.matchTextDirection;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => hashValues(image, colorFilter, fit, alignment,
|
|
||||||
centerSlice, repeat, matchTextDirection);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
final List<String> properties = <String>[];
|
|
||||||
properties.add('$image');
|
|
||||||
if (colorFilter != null) properties.add('$colorFilter');
|
|
||||||
if (fit != null &&
|
|
||||||
!(fit == BoxFit.fill && 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].
|
|
||||||
///
|
|
||||||
/// To obtain a painter, call [DecorationImagePlus.createPainter].
|
|
||||||
///
|
|
||||||
/// To paint, call [paint]. The `onChanged` callback passed to
|
|
||||||
/// [DecorationImagePlus.createPainter] will be called if the image needs to paint
|
|
||||||
/// again (e.g. because it is animated or because it had not yet loaded the
|
|
||||||
/// first time the [paint] method was called).
|
|
||||||
///
|
|
||||||
/// This object should be disposed using the [dispose] method when it is no
|
|
||||||
/// longer needed.
|
|
||||||
class DecorationImagePainterPlus implements DecorationImagePainter {
|
|
||||||
DecorationImagePainterPlus._(this._details, this._onChanged)
|
|
||||||
: assert(_details != null);
|
|
||||||
|
|
||||||
final DecorationImagePlus _details;
|
|
||||||
final VoidCallback _onChanged;
|
|
||||||
|
|
||||||
ImageStream _imageStream;
|
|
||||||
ImageInfo _image;
|
|
||||||
|
|
||||||
/// Draw the image onto the given canvas.
|
|
||||||
///
|
|
||||||
/// The image is drawn at the position and size given by the `rect` argument.
|
|
||||||
///
|
|
||||||
/// The image is clipped to the given `clipPath`, if any.
|
|
||||||
///
|
|
||||||
/// The `configuration` object is used to resolve the image (e.g. to pick
|
|
||||||
/// resolution-specific assets), and to implement the
|
|
||||||
/// [DecorationImagePlus.matchTextDirection] feature.
|
|
||||||
///
|
|
||||||
/// If the image needs to be painted again, e.g. because it is animated or
|
|
||||||
/// because it had not yet been loaded the first time this method was called,
|
|
||||||
/// then the `onChanged` callback passed to [DecorationImagePlus.createPainter]
|
|
||||||
/// will be called.
|
|
||||||
void paint(Canvas canvas, Rect rect, Path clipPath,
|
|
||||||
ImageConfiguration configuration) {
|
|
||||||
assert(canvas != null);
|
|
||||||
assert(rect != null);
|
|
||||||
assert(configuration != null);
|
|
||||||
|
|
||||||
if (_details.matchTextDirection) {
|
|
||||||
assert(() {
|
|
||||||
// We check this first so that the assert will fire immediately, not just
|
|
||||||
// when the image is ready.
|
|
||||||
if (configuration.textDirection == null) {
|
|
||||||
throw FlutterError(
|
|
||||||
'ImageDecoration.matchTextDirection can only be used when a TextDirection is available.\n'
|
|
||||||
'When DecorationImagePainter.paint() was called, there was no text direction provided '
|
|
||||||
'in the ImageConfiguration object to match.\n'
|
|
||||||
'The DecorationImage was:\n'
|
|
||||||
' $_details\n'
|
|
||||||
'The ImageConfiguration was:\n'
|
|
||||||
' $configuration');
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
}
|
|
||||||
|
|
||||||
final ImageStream newImageStream = _details.image.resolve(configuration);
|
|
||||||
if (newImageStream.key != _imageStream?.key) {
|
|
||||||
final listener = ImageStreamListener(_imageListener);
|
|
||||||
_imageStream?.removeListener(listener);
|
|
||||||
_imageStream = newImageStream;
|
|
||||||
_imageStream.addListener(listener);
|
|
||||||
}
|
|
||||||
if (_image == null) return;
|
|
||||||
|
|
||||||
if (clipPath != null) {
|
|
||||||
canvas.save();
|
|
||||||
canvas.clipPath(clipPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
_paintImage(
|
|
||||||
canvas: canvas,
|
|
||||||
puzzleWidth: _details.puzzleWidth,
|
|
||||||
puzzleHeight: _details.puzzleHeight,
|
|
||||||
pieceIndex: _details.pieceIndex,
|
|
||||||
rect: rect,
|
|
||||||
image: _image.image,
|
|
||||||
scale: _image.scale,
|
|
||||||
colorFilter: _details.colorFilter,
|
|
||||||
fit: _details.fit,
|
|
||||||
alignment: _details.alignment.resolve(configuration.textDirection),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (clipPath != null) canvas.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _imageListener(ImageInfo value, bool synchronousCall) {
|
|
||||||
if (_image == value) return;
|
|
||||||
_image = value;
|
|
||||||
assert(_onChanged != null);
|
|
||||||
if (!synchronousCall) _onChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Releases the resources used by this painter.
|
|
||||||
///
|
|
||||||
/// This should be called whenever the painter is no longer needed.
|
|
||||||
///
|
|
||||||
/// After this method has been called, the object is no longer usable.
|
|
||||||
@mustCallSuper
|
|
||||||
void dispose() {
|
|
||||||
_imageStream?.removeListener(ImageStreamListener(_imageListener));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return '$runtimeType(stream: $_imageStream, image: $_image) for $_details';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _paintImage(
|
|
||||||
{@required Canvas canvas,
|
|
||||||
@required Rect rect,
|
|
||||||
@required ui.Image image,
|
|
||||||
@required int puzzleWidth,
|
|
||||||
@required int puzzleHeight,
|
|
||||||
@required int pieceIndex,
|
|
||||||
double scale = 1.0,
|
|
||||||
ColorFilter colorFilter,
|
|
||||||
BoxFit fit,
|
|
||||||
Alignment alignment = Alignment.center}) {
|
|
||||||
assert(canvas != null);
|
|
||||||
assert(image != null);
|
|
||||||
assert(alignment != null);
|
|
||||||
|
|
||||||
if (rect.isEmpty) return;
|
|
||||||
final outputSize = rect.size;
|
|
||||||
final inputSize = Size(image.width.toDouble(), image.height.toDouble());
|
|
||||||
fit ??= BoxFit.scaleDown;
|
|
||||||
final FittedSizes fittedSizes =
|
|
||||||
applyBoxFit(fit, inputSize / scale, outputSize);
|
|
||||||
final Size sourceSize = fittedSizes.source * scale;
|
|
||||||
final destinationSize = fittedSizes.destination;
|
|
||||||
final Paint paint = Paint()
|
|
||||||
..isAntiAlias = false
|
|
||||||
..filterQuality = FilterQuality.medium;
|
|
||||||
if (colorFilter != null) paint.colorFilter = colorFilter;
|
|
||||||
final double halfWidthDelta =
|
|
||||||
(outputSize.width - destinationSize.width) / 2.0;
|
|
||||||
final double halfHeightDelta =
|
|
||||||
(outputSize.height - destinationSize.height) / 2.0;
|
|
||||||
final double dx = halfWidthDelta + (alignment.x) * halfWidthDelta;
|
|
||||||
final double dy = halfHeightDelta + alignment.y * halfHeightDelta;
|
|
||||||
final Offset destinationPosition = rect.topLeft.translate(dx, dy);
|
|
||||||
final Rect destinationRect = destinationPosition & destinationSize;
|
|
||||||
final Rect sourceRect =
|
|
||||||
alignment.inscribe(sourceSize, Offset.zero & inputSize);
|
|
||||||
|
|
||||||
final sliceSize =
|
|
||||||
Size(sourceRect.width / puzzleWidth, sourceRect.height / puzzleHeight);
|
|
||||||
|
|
||||||
final col = pieceIndex % puzzleWidth;
|
|
||||||
final row = pieceIndex ~/ puzzleWidth;
|
|
||||||
|
|
||||||
final sliceRect = Rect.fromLTWH(
|
|
||||||
sourceRect.left + col * sliceSize.width,
|
|
||||||
sourceRect.top + row * sliceSize.height,
|
|
||||||
sliceSize.width,
|
|
||||||
sliceSize.height);
|
|
||||||
|
|
||||||
canvas.drawImageRect(image, sliceRect, destinationRect, paint);
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
// Copyright 2020, the Flutter project authors. Please see the AUTHORS file
|
|
||||||
// for details. All rights reserved. Use of this source code is governed by a
|
|
||||||
// BSD-style license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import '../flutter.dart';
|
|
||||||
|
|
||||||
// Copied from
|
|
||||||
// https://github.com/flutter/flutter/blob/f5b02e3c05ed1ab31e890add84fb56e35de2d392/packages/flutter/lib/src/material/material.dart#L593-L715
|
|
||||||
// So I could have animated color!
|
|
||||||
// TODO(kevmoo): file a feature request for this?
|
|
||||||
class MaterialInterior extends ImplicitlyAnimatedWidget {
|
|
||||||
const MaterialInterior({
|
|
||||||
Key key,
|
|
||||||
@required this.child,
|
|
||||||
@required this.shape,
|
|
||||||
@required this.color,
|
|
||||||
Curve curve = Curves.linear,
|
|
||||||
@required Duration duration,
|
|
||||||
}) : assert(child != null),
|
|
||||||
assert(shape != null),
|
|
||||||
assert(color != null),
|
|
||||||
super(key: key, curve: curve, duration: duration);
|
|
||||||
|
|
||||||
/// The widget below this widget in the tree.
|
|
||||||
///
|
|
||||||
/// {@macro flutter.widgets.child}
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
/// The border of the widget.
|
|
||||||
///
|
|
||||||
/// This border will be painted, and in addition the outer path of the border
|
|
||||||
/// determines the physical shape.
|
|
||||||
final ShapeBorder shape;
|
|
||||||
|
|
||||||
/// The target background color.
|
|
||||||
final Color color;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_MaterialInteriorState createState() => _MaterialInteriorState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MaterialInteriorState extends AnimatedWidgetBaseState<MaterialInterior> {
|
|
||||||
ShapeBorderTween _border;
|
|
||||||
ColorTween _color;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void forEachTween(TweenVisitor<dynamic> visitor) {
|
|
||||||
_border = visitor(_border, widget.shape,
|
|
||||||
(value) => ShapeBorderTween(begin: value as ShapeBorder))
|
|
||||||
as ShapeBorderTween;
|
|
||||||
_color = visitor(
|
|
||||||
_color, widget.color, (value) => ColorTween(begin: value as Color))
|
|
||||||
as ColorTween;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final shape = _border.evaluate(animation);
|
|
||||||
return PhysicalShape(
|
|
||||||
child: _ShapeBorderPaint(
|
|
||||||
child: widget.child,
|
|
||||||
shape: shape,
|
|
||||||
),
|
|
||||||
clipper: ShapeBorderClipper(
|
|
||||||
shape: shape,
|
|
||||||
textDirection: Directionality.of(context),
|
|
||||||
),
|
|
||||||
color: _color.evaluate(animation),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ShapeBorderPaint extends StatelessWidget {
|
|
||||||
const _ShapeBorderPaint({
|
|
||||||
@required this.child,
|
|
||||||
@required this.shape,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Widget child;
|
|
||||||
final ShapeBorder shape;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return CustomPaint(
|
|
||||||
child: child,
|
|
||||||
foregroundPainter: _ShapeBorderPainter(shape, Directionality.of(context)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ShapeBorderPainter extends CustomPainter {
|
|
||||||
_ShapeBorderPainter(this.border, this.textDirection);
|
|
||||||
|
|
||||||
final ShapeBorder border;
|
|
||||||
final TextDirection textDirection;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
border.paint(canvas, Offset.zero & size, textDirection: textDirection);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(_ShapeBorderPainter oldDelegate) {
|
|
||||||
return oldDelegate.border != border;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,426 +0,0 @@
|
|||||||
# 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: "3.0.0"
|
|
||||||
analyzer:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: analyzer
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.39.8"
|
|
||||||
archive:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: archive
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.13"
|
|
||||||
args:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: args
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.6.0"
|
|
||||||
async:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: async
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.1"
|
|
||||||
boolean_selector:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: boolean_selector
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.0"
|
|
||||||
charcode:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: charcode
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.3"
|
|
||||||
collection:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: collection
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.14.12"
|
|
||||||
convert:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: convert
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.1"
|
|
||||||
coverage:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: coverage
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.13.9"
|
|
||||||
crypto:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: crypto
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.4"
|
|
||||||
csslib:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: csslib
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.16.1"
|
|
||||||
flutter:
|
|
||||||
dependency: "direct main"
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
flutter_test:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
glob:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: glob
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.0"
|
|
||||||
html:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: html
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.14.0+3"
|
|
||||||
http:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: http
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.12.1"
|
|
||||||
http_multi_server:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: http_multi_server
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.2.0"
|
|
||||||
http_parser:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: http_parser
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "3.1.4"
|
|
||||||
image:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: image
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.12"
|
|
||||||
io:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: io
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.4"
|
|
||||||
js:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: js
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.6.1+1"
|
|
||||||
logging:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: logging
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.11.4"
|
|
||||||
matcher:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: matcher
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.12.6"
|
|
||||||
meta:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: meta
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.8"
|
|
||||||
mime:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: mime
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.9.6+3"
|
|
||||||
multi_server_socket:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: multi_server_socket
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.2"
|
|
||||||
node_interop:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: node_interop
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.3"
|
|
||||||
node_io:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: node_io
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.0"
|
|
||||||
node_preamble:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: node_preamble
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.4.8"
|
|
||||||
package_config:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: package_config
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.9.3"
|
|
||||||
path:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.6.4"
|
|
||||||
pedantic:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: pedantic
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.9.0"
|
|
||||||
petitparser:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: petitparser
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.0"
|
|
||||||
pool:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: pool
|
|
||||||
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.4"
|
|
||||||
quiver:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: quiver
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.3"
|
|
||||||
shelf:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shelf
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.7.5"
|
|
||||||
shelf_packages_handler:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shelf_packages_handler
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.0"
|
|
||||||
shelf_static:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shelf_static
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.2.8"
|
|
||||||
shelf_web_socket:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shelf_web_socket
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.2.3"
|
|
||||||
sky_engine:
|
|
||||||
dependency: transitive
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.99"
|
|
||||||
source_map_stack_trace:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: source_map_stack_trace
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.0"
|
|
||||||
source_maps:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: source_maps
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.10.9"
|
|
||||||
source_span:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: source_span
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.7.0"
|
|
||||||
stack_trace:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: stack_trace
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.9.3"
|
|
||||||
stream_channel:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: stream_channel
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.0"
|
|
||||||
string_scanner:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: string_scanner
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.5"
|
|
||||||
term_glyph:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: term_glyph
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.0"
|
|
||||||
test:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: test
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.14.3"
|
|
||||||
test_api:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: test_api
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.2.15"
|
|
||||||
test_core:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: test_core
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.4"
|
|
||||||
typed_data:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: typed_data
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.6"
|
|
||||||
vector_math:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: vector_math
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.8"
|
|
||||||
vm_service:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: vm_service
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "4.0.2"
|
|
||||||
watcher:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: watcher
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.9.7+15"
|
|
||||||
web_socket_channel:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: web_socket_channel
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.0"
|
|
||||||
webkit_inspection_protocol:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: webkit_inspection_protocol
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.5.3"
|
|
||||||
xml:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: xml
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "3.6.1"
|
|
||||||
yaml:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: yaml
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.2.1"
|
|
||||||
sdks:
|
|
||||||
dart: ">=2.7.0 <3.0.0"
|
|
@ -1,27 +0,0 @@
|
|||||||
name: slide_puzzle
|
|
||||||
|
|
||||||
version: 1.0.0
|
|
||||||
|
|
||||||
environment:
|
|
||||||
sdk: ">=2.0.0 <3.0.0"
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
flutter:
|
|
||||||
sdk: flutter
|
|
||||||
|
|
||||||
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/
|
|
||||||
|
|
||||||
fonts:
|
|
||||||
- family: Plaster
|
|
||||||
fonts:
|
|
||||||
- asset: asset/fonts/plaster/Plaster-Regular.ttf
|
|
@ -1,10 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>slide_puzzle</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script src="main.dart.js" type="application/javascript"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
Reference in new issue