mirror of https://github.com/flutter/samples.git
Add workflow to deploy the sample index (#791)
* Add workflow to build and deploy the sample index * update gh-pages action * fix yaml * create web/ directory in build * grammar * add ignored directories * revert pubspec.lock files * add job to run _tool/verify_samples.dart * Update filipino_cuisine for Flutter 2 * remove timeflow demo. The unnamed List constructor is now deprecated, refactoring this code to use add() requires more knowledge about the code for this demo. https://dart.dev/null-safety/understanding-null-safety#no-unnamed-list-constructor * update slide_puzzle * ensure stable channel is used to verify * move verify web demos action into separate yaml file - avoid running on each flutter version. * add on: pull_request * update slide_puzzle * Update gh-pages.yml * Add copyright headerpull/796/head
parent
b26f2cccc1
commit
3f5ab56485
@ -0,0 +1,31 @@
|
|||||||
|
name: Deploy to GitHub Pages
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- uses: subosito/flutter-action@v1
|
||||||
|
|
||||||
|
- name: Init scripts
|
||||||
|
run: dart pub get
|
||||||
|
working-directory: web/_tool
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: dart _tool/build_ci.dart
|
||||||
|
working-directory: web
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_dir: web/samples_index/public
|
@ -0,0 +1,20 @@
|
|||||||
|
name: Verify web demos
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
verify-web-demos:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: subosito/flutter-action@v1
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
- name: Init scripts
|
||||||
|
run: dart pub get
|
||||||
|
working-directory: web/_tool
|
||||||
|
- name: Verify packages
|
||||||
|
run: dart _tool/verify_packages.dart
|
||||||
|
working-directory: web
|
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2021 The Flutter team. 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:io';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
final ignoredDirectories = ['_tool', 'samples_index'];
|
||||||
|
|
||||||
|
main() async {
|
||||||
|
final packageDirs = [
|
||||||
|
...listPackageDirs(Directory.current)
|
||||||
|
.map((path) => p.relative(path, from: Directory.current.path))
|
||||||
|
.where((path) => !ignoredDirectories.contains(path))
|
||||||
|
];
|
||||||
|
|
||||||
|
print('Building the sample index...');
|
||||||
|
await run('samples_index', 'pub', ['get']);
|
||||||
|
await run('samples_index', 'pub', ['run', 'grinder', 'deploy']);
|
||||||
|
|
||||||
|
// Create the directory each Flutter Web sample lives in
|
||||||
|
Directory(p.join(Directory.current.path, 'samples_index', 'public', 'web'))
|
||||||
|
.createSync(recursive: true);
|
||||||
|
|
||||||
|
for (var i = 0; i < packageDirs.length; i++) {
|
||||||
|
var directory = packageDirs[i];
|
||||||
|
|
||||||
|
logWrapped(ansiMagenta, '\n$directory (${i + 1} of ${packageDirs.length})');
|
||||||
|
|
||||||
|
// Create the target directory
|
||||||
|
var directoryName = p.basename(directory);
|
||||||
|
var sourceBuildDir =
|
||||||
|
p.join(Directory.current.path, directory, 'build', 'web');
|
||||||
|
var targetDirectory = p.join(Directory.current.path, 'samples_index',
|
||||||
|
'public', 'web', directoryName);
|
||||||
|
|
||||||
|
// Build the sample and copy the files
|
||||||
|
await run(directory, 'flutter', ['pub', 'get']);
|
||||||
|
await run(directory, 'flutter', ['build', 'web']);
|
||||||
|
await run(directory, 'mv', [sourceBuildDir, targetDirectory]);
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit 5ef5526acb58f9ffb6f3fb22118cbd825613dc73
|
Subproject commit 5c590d0b0252cf3d7cbddf9998d7807b87c91550
|
@ -1,13 +0,0 @@
|
|||||||
Copyright 2019 Fabian Stein
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
|
|
||||||
(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
||||||
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
||||||
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
Before Width: | Height: | Size: 40 KiB |
@ -1,264 +0,0 @@
|
|||||||
// Package infinite_listview:
|
|
||||||
// https://pub.dartlang.org/packages/infinite_listview
|
|
||||||
|
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:flutter/rendering.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
|
|
||||||
/// Infinite ListView
|
|
||||||
///
|
|
||||||
/// ListView that builds its children with to an infinite extent.
|
|
||||||
///
|
|
||||||
class InfiniteListView extends StatelessWidget {
|
|
||||||
/// See [ListView.builder]
|
|
||||||
InfiniteListView.builder({
|
|
||||||
Key key,
|
|
||||||
this.scrollDirection = Axis.vertical,
|
|
||||||
this.reverse = false,
|
|
||||||
InfiniteScrollController controller,
|
|
||||||
this.physics,
|
|
||||||
this.padding,
|
|
||||||
this.itemExtent,
|
|
||||||
@required IndexedWidgetBuilder itemBuilder,
|
|
||||||
int itemCount,
|
|
||||||
bool addAutomaticKeepAlives = true,
|
|
||||||
bool addRepaintBoundaries = true,
|
|
||||||
this.cacheExtent,
|
|
||||||
}) : positiveChildrenDelegate = SliverChildBuilderDelegate(
|
|
||||||
itemBuilder,
|
|
||||||
childCount: itemCount,
|
|
||||||
addAutomaticKeepAlives: addAutomaticKeepAlives,
|
|
||||||
addRepaintBoundaries: addRepaintBoundaries,
|
|
||||||
),
|
|
||||||
negativeChildrenDelegate = SliverChildBuilderDelegate(
|
|
||||||
(BuildContext context, int index) => itemBuilder(context, -1 - index),
|
|
||||||
childCount: itemCount,
|
|
||||||
addAutomaticKeepAlives: addAutomaticKeepAlives,
|
|
||||||
addRepaintBoundaries: addRepaintBoundaries,
|
|
||||||
),
|
|
||||||
controller = controller ?? InfiniteScrollController(),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
/// See [ListView.separated]
|
|
||||||
InfiniteListView.separated({
|
|
||||||
Key key,
|
|
||||||
this.scrollDirection = Axis.vertical,
|
|
||||||
this.reverse = false,
|
|
||||||
InfiniteScrollController controller,
|
|
||||||
this.physics,
|
|
||||||
this.padding,
|
|
||||||
@required IndexedWidgetBuilder itemBuilder,
|
|
||||||
@required IndexedWidgetBuilder separatorBuilder,
|
|
||||||
int itemCount,
|
|
||||||
bool addAutomaticKeepAlives = true,
|
|
||||||
bool addRepaintBoundaries = true,
|
|
||||||
this.cacheExtent,
|
|
||||||
}) : assert(itemBuilder != null),
|
|
||||||
assert(separatorBuilder != null),
|
|
||||||
itemExtent = null,
|
|
||||||
positiveChildrenDelegate = SliverChildBuilderDelegate(
|
|
||||||
(BuildContext context, int index) {
|
|
||||||
final itemIndex = index ~/ 2;
|
|
||||||
return index.isEven
|
|
||||||
? itemBuilder(context, itemIndex)
|
|
||||||
: separatorBuilder(context, itemIndex);
|
|
||||||
},
|
|
||||||
childCount: itemCount != null ? math.max(0, itemCount * 2 - 1) : null,
|
|
||||||
addAutomaticKeepAlives: addAutomaticKeepAlives,
|
|
||||||
addRepaintBoundaries: addRepaintBoundaries,
|
|
||||||
),
|
|
||||||
negativeChildrenDelegate = SliverChildBuilderDelegate(
|
|
||||||
(BuildContext context, int index) {
|
|
||||||
final itemIndex = (-1 - index) ~/ 2;
|
|
||||||
return index.isOdd
|
|
||||||
? itemBuilder(context, itemIndex)
|
|
||||||
: separatorBuilder(context, itemIndex);
|
|
||||||
},
|
|
||||||
childCount: itemCount,
|
|
||||||
addAutomaticKeepAlives: addAutomaticKeepAlives,
|
|
||||||
addRepaintBoundaries: addRepaintBoundaries,
|
|
||||||
),
|
|
||||||
controller = controller ?? InfiniteScrollController(),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
/// See: [ScrollView.scrollDirection]
|
|
||||||
final Axis scrollDirection;
|
|
||||||
|
|
||||||
/// See: [ScrollView.reverse]
|
|
||||||
final bool reverse;
|
|
||||||
|
|
||||||
/// See: [ScrollView.controller]
|
|
||||||
final InfiniteScrollController controller;
|
|
||||||
|
|
||||||
/// See: [ScrollView.physics]
|
|
||||||
final ScrollPhysics physics;
|
|
||||||
|
|
||||||
/// See: [BoxScrollView.padding]
|
|
||||||
final EdgeInsets padding;
|
|
||||||
|
|
||||||
/// See: [ListView.itemExtent]
|
|
||||||
final double itemExtent;
|
|
||||||
|
|
||||||
/// See: [ScrollView.cacheExtent]
|
|
||||||
final double cacheExtent;
|
|
||||||
|
|
||||||
/// See: [ListView.childrenDelegate]
|
|
||||||
final SliverChildDelegate negativeChildrenDelegate;
|
|
||||||
|
|
||||||
/// See: [ListView.childrenDelegate]
|
|
||||||
final SliverChildDelegate positiveChildrenDelegate;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final List<Widget> slivers = _buildSlivers(context, negative: false);
|
|
||||||
final List<Widget> negativeSlivers = _buildSlivers(context, negative: true);
|
|
||||||
final AxisDirection axisDirection = _getDirection(context);
|
|
||||||
final scrollPhysics = AlwaysScrollableScrollPhysics(parent: physics);
|
|
||||||
return Scrollable(
|
|
||||||
axisDirection: axisDirection,
|
|
||||||
controller: controller,
|
|
||||||
physics: scrollPhysics,
|
|
||||||
viewportBuilder: (BuildContext context, ViewportOffset offset) {
|
|
||||||
return Builder(builder: (BuildContext context) {
|
|
||||||
/// Build negative [ScrollPosition] for the negative scrolling [Viewport].
|
|
||||||
final state = Scrollable.of(context);
|
|
||||||
final negativeOffset = _InfiniteScrollPosition(
|
|
||||||
physics: scrollPhysics,
|
|
||||||
context: state,
|
|
||||||
initialPixels: -offset.pixels,
|
|
||||||
keepScrollOffset: controller.keepScrollOffset,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Keep the negative scrolling [Viewport] positioned to the [ScrollPosition].
|
|
||||||
offset.addListener(() {
|
|
||||||
negativeOffset._forceNegativePixels(offset.pixels);
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Stack the two [Viewport]s on top of each other so they move in sync.
|
|
||||||
return Stack(
|
|
||||||
children: <Widget>[
|
|
||||||
Viewport(
|
|
||||||
axisDirection: flipAxisDirection(axisDirection),
|
|
||||||
anchor: 1.0,
|
|
||||||
offset: negativeOffset,
|
|
||||||
slivers: negativeSlivers,
|
|
||||||
cacheExtent: cacheExtent,
|
|
||||||
),
|
|
||||||
Viewport(
|
|
||||||
axisDirection: axisDirection,
|
|
||||||
offset: offset,
|
|
||||||
slivers: slivers,
|
|
||||||
cacheExtent: cacheExtent,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AxisDirection _getDirection(BuildContext context) {
|
|
||||||
return getAxisDirectionFromAxisReverseAndDirectionality(
|
|
||||||
context, scrollDirection, reverse);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _buildSlivers(BuildContext context, {bool negative = false}) {
|
|
||||||
Widget sliver;
|
|
||||||
if (itemExtent != null) {
|
|
||||||
sliver = SliverFixedExtentList(
|
|
||||||
delegate:
|
|
||||||
negative ? negativeChildrenDelegate : positiveChildrenDelegate,
|
|
||||||
itemExtent: itemExtent,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
sliver = SliverList(
|
|
||||||
delegate:
|
|
||||||
negative ? negativeChildrenDelegate : positiveChildrenDelegate);
|
|
||||||
}
|
|
||||||
if (padding != null) {
|
|
||||||
sliver = new SliverPadding(
|
|
||||||
padding: negative
|
|
||||||
? padding - EdgeInsets.only(bottom: padding.bottom)
|
|
||||||
: padding - EdgeInsets.only(top: padding.top),
|
|
||||||
sliver: sliver,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <Widget>[sliver];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
||||||
super.debugFillProperties(properties);
|
|
||||||
properties.add(new EnumProperty<Axis>('scrollDirection', scrollDirection));
|
|
||||||
properties.add(new FlagProperty('reverse',
|
|
||||||
value: reverse, ifTrue: 'reversed', showName: true));
|
|
||||||
properties.add(new DiagnosticsProperty<ScrollController>(
|
|
||||||
'controller', controller,
|
|
||||||
showName: false, defaultValue: null));
|
|
||||||
properties.add(new DiagnosticsProperty<ScrollPhysics>('physics', physics,
|
|
||||||
showName: false, defaultValue: null));
|
|
||||||
properties.add(new DiagnosticsProperty<EdgeInsetsGeometry>(
|
|
||||||
'padding', padding,
|
|
||||||
defaultValue: null));
|
|
||||||
properties
|
|
||||||
.add(new DoubleProperty('itemExtent', itemExtent, defaultValue: null));
|
|
||||||
properties.add(
|
|
||||||
new DoubleProperty('cacheExtent', cacheExtent, defaultValue: null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Same as a [ScrollController] except it provides [ScrollPosition] objects with infinite bounds.
|
|
||||||
class InfiniteScrollController extends ScrollController {
|
|
||||||
/// Creates a new [InfiniteScrollController]
|
|
||||||
InfiniteScrollController({
|
|
||||||
double initialScrollOffset = 0.0,
|
|
||||||
bool keepScrollOffset = true,
|
|
||||||
String debugLabel,
|
|
||||||
}) : super(
|
|
||||||
initialScrollOffset: initialScrollOffset,
|
|
||||||
keepScrollOffset: keepScrollOffset,
|
|
||||||
debugLabel: debugLabel,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
ScrollPosition createScrollPosition(ScrollPhysics physics,
|
|
||||||
ScrollContext context, ScrollPosition oldPosition) {
|
|
||||||
return new _InfiniteScrollPosition(
|
|
||||||
physics: physics,
|
|
||||||
context: context,
|
|
||||||
initialPixels: initialScrollOffset,
|
|
||||||
keepScrollOffset: keepScrollOffset,
|
|
||||||
oldPosition: oldPosition,
|
|
||||||
debugLabel: debugLabel,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _InfiniteScrollPosition extends ScrollPositionWithSingleContext {
|
|
||||||
_InfiniteScrollPosition({
|
|
||||||
@required ScrollPhysics physics,
|
|
||||||
@required ScrollContext context,
|
|
||||||
double initialPixels = 0.0,
|
|
||||||
bool keepScrollOffset = true,
|
|
||||||
ScrollPosition oldPosition,
|
|
||||||
String debugLabel,
|
|
||||||
}) : super(
|
|
||||||
physics: physics,
|
|
||||||
context: context,
|
|
||||||
initialPixels: initialPixels,
|
|
||||||
keepScrollOffset: keepScrollOffset,
|
|
||||||
oldPosition: oldPosition,
|
|
||||||
debugLabel: debugLabel,
|
|
||||||
);
|
|
||||||
|
|
||||||
void _forceNegativePixels(double value) {
|
|
||||||
super.forcePixels(-value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get minScrollExtent => double.negativeInfinity;
|
|
||||||
|
|
||||||
@override
|
|
||||||
double get maxScrollExtent => double.infinity;
|
|
||||||
}
|
|
@ -1,260 +0,0 @@
|
|||||||
import 'dart:core';
|
|
||||||
import 'dart:math';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/scheduler.dart';
|
|
||||||
|
|
||||||
import 'numberpicker.dart';
|
|
||||||
|
|
||||||
main() => runApp(MaterialApp(home: App(), debugShowCheckedModeBanner: false));
|
|
||||||
|
|
||||||
class App extends StatefulWidget {
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => TM();
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SI { pause, play, stop }
|
|
||||||
List<T> triangles;
|
|
||||||
var percent = 0.0, cTime = 0.0, dur = 120000.0, rng = Random(), rebuild = true;
|
|
||||||
|
|
||||||
class TM extends State<App> {
|
|
||||||
SI cState = SI.stop;
|
|
||||||
Ticker t;
|
|
||||||
var pTime = 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
initState() {
|
|
||||||
// Screen.keepOn(true);
|
|
||||||
t = Ticker(up);
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
up(Duration d) {
|
|
||||||
if (cState == SI.play) {
|
|
||||||
setState(() {
|
|
||||||
if (cTime >= dur)
|
|
||||||
stop();
|
|
||||||
else {
|
|
||||||
cTime = d.inMilliseconds.toDouble() + pTime;
|
|
||||||
percent = cTime / dur;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
press() {
|
|
||||||
if (cState == SI.play)
|
|
||||||
pause();
|
|
||||||
else if (cState == SI.pause)
|
|
||||||
play();
|
|
||||||
else {
|
|
||||||
cState = SI.play;
|
|
||||||
t.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pause() {
|
|
||||||
setState(() {
|
|
||||||
cState = SI.pause;
|
|
||||||
t.stop();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
play() {
|
|
||||||
setState(() {
|
|
||||||
cState = SI.play;
|
|
||||||
t.start();
|
|
||||||
pTime = cTime;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
setState(() {
|
|
||||||
cState = SI.stop;
|
|
||||||
t.stop();
|
|
||||||
pTime = 0.0;
|
|
||||||
cTime = 0.0;
|
|
||||||
percent = 0.0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
openDialog() {
|
|
||||||
showDialog<num>(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return NumberPickerDialog.integer(
|
|
||||||
initialIntegerValue: (dur + 1.0) ~/ 60000,
|
|
||||||
maxValue: 20,
|
|
||||||
minValue: 1,
|
|
||||||
title: Text('Minutes'));
|
|
||||||
}).then((num v) {
|
|
||||||
if (v != null) dur = 60000.0 * v;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
List<Widget> w = List();
|
|
||||||
|
|
||||||
if (cState == SI.pause) {
|
|
||||||
w.add(fab(Colors.green, play, Icons.play_arrow));
|
|
||||||
w.add(SizedBox(height: 10));
|
|
||||||
w.add(fab(Colors.red, stop, Icons.close));
|
|
||||||
w.add(SizedBox(height: 20));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cState == SI.stop) {
|
|
||||||
w.add(fab(Colors.lightBlue, openDialog, Icons.timer));
|
|
||||||
w.add(SizedBox(height: 10));
|
|
||||||
w.add(fab(Colors.yellow[900], () {
|
|
||||||
rebuild = true;
|
|
||||||
}, Icons.loop));
|
|
||||||
w.add(SizedBox(height: 20));
|
|
||||||
}
|
|
||||||
|
|
||||||
Column r = Column(mainAxisAlignment: MainAxisAlignment.end, children: w);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
body: SizedBox.expand(
|
|
||||||
child: Container(
|
|
||||||
child: CustomPaint(
|
|
||||||
painter: P(),
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: press,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [r]))))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FloatingActionButton fab(Color c, VoidCallback f, IconData ic) =>
|
|
||||||
FloatingActionButton(backgroundColor: c, onPressed: f, child: Icon(ic));
|
|
||||||
|
|
||||||
class P extends CustomPainter {
|
|
||||||
@override
|
|
||||||
paint(Canvas canvas, Size size) {
|
|
||||||
var w = size.width, h = size.height, d = 2 / 3 * w;
|
|
||||||
if (w > 0.1 && h > 0.1) {
|
|
||||||
if (rebuild) {
|
|
||||||
rebuild = false;
|
|
||||||
setupT();
|
|
||||||
for (var t in triangles) t.setupdP(w / d, h / d);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var t in triangles) {
|
|
||||||
var cP = t.cP(), p = Path();
|
|
||||||
p.moveTo(cP[0].x * d + w / 2, cP[0].y * d + h / 2);
|
|
||||||
for (i = 1; i < 3; i++)
|
|
||||||
p.lineTo(cP[i].x * d + w / 2, cP[i].y * d + h / 2);
|
|
||||||
p.close();
|
|
||||||
canvas.drawPath(p, t.p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) => true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int i;
|
|
||||||
|
|
||||||
class T {
|
|
||||||
List<Point> dP = List(3), sP = List(3);
|
|
||||||
Paint p;
|
|
||||||
|
|
||||||
T(Point p1, p2, p3, var c) {
|
|
||||||
p = Paint()..style = PaintingStyle.fill;
|
|
||||||
sP[0] = p1;
|
|
||||||
sP[1] = p2;
|
|
||||||
sP[2] = p3;
|
|
||||||
p.color = c[100 * (rng.nextInt(9) + 1)];
|
|
||||||
|
|
||||||
double x = 0, y = 0;
|
|
||||||
for (i = 0; i < 3; i++) {
|
|
||||||
x += sP[i].x;
|
|
||||||
y += sP[i].y;
|
|
||||||
}
|
|
||||||
|
|
||||||
x = 2 * x / 3;
|
|
||||||
y = 2 * y / 3;
|
|
||||||
if (x * x + y * y < 1) triangles.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupdP(double wR, hR) {
|
|
||||||
var x = (rng.nextDouble() - 0.5) * (wR - 0.1),
|
|
||||||
y = (rng.nextDouble() - 0.5) * (hR - 0.1);
|
|
||||||
dP[0] = Point(x, y);
|
|
||||||
for (i = 1; i < 3; i++)
|
|
||||||
dP[i] = Point(sP[i].x + x - sP[0].x, sP[i].y + y - sP[0].y);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Point> cP() {
|
|
||||||
List<Point> res = List(3);
|
|
||||||
var p, k, o = 6000, r;
|
|
||||||
if (cTime < o)
|
|
||||||
p = 1 - cTime / o;
|
|
||||||
else
|
|
||||||
p = (cTime - o) / (dur - o);
|
|
||||||
k = 2 * ((cTime.toInt() % o) - o / 2).abs() / o;
|
|
||||||
r = min(min(1, (dur - cTime) / o), cTime / o);
|
|
||||||
this.p.color = this.p.color.withAlpha(255 - (200 * k * r).toInt());
|
|
||||||
|
|
||||||
for (i = 0; i < 3; i++)
|
|
||||||
res[i] = Point(
|
|
||||||
sP[i].x * p + dP[i].x * (1 - p), sP[i].y * p + dP[i].y * (1 - p));
|
|
||||||
|
|
||||||
if (cTime > o) {
|
|
||||||
var d = res[0].distanceTo(sP[0]);
|
|
||||||
var a = acos((sP[0].x - res[0].x) / d);
|
|
||||||
if (sP[0].y > res[0].y) a = 2 * pi - a;
|
|
||||||
var b = pi - a + p * pi * dur / 120000;
|
|
||||||
var dX = cos(b) * d, dY = sin(b) * d;
|
|
||||||
for (i = 0; i < 3; i++) res[i] = Point(sP[i].x + dX, sP[i].y + dY);
|
|
||||||
}
|
|
||||||
|
|
||||||
double mx = 0, my = 0;
|
|
||||||
for (i = 0; i < 3; i++) {
|
|
||||||
mx += res[i].x;
|
|
||||||
my += res[i].y;
|
|
||||||
}
|
|
||||||
mx /= 3;
|
|
||||||
my /= 3;
|
|
||||||
for (i = 0; i < 3; i++)
|
|
||||||
res[i] = Point(res[i].x + (res[i].x - mx) * (1 - k) * r / 2,
|
|
||||||
res[i].y + (res[i].y - my) * (1 - k) * r / 2);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setupT() {
|
|
||||||
int dim = 20, x, y;
|
|
||||||
List<Point> tri = List(dim * dim);
|
|
||||||
|
|
||||||
for (x = 0; x < dim; x++) {
|
|
||||||
for (y = 0; y < dim; y++) {
|
|
||||||
var dx = rng.nextDouble() - 0.5, dy = rng.nextDouble() - 0.5, off;
|
|
||||||
if (x % 2 == 0)
|
|
||||||
off = 0;
|
|
||||||
else
|
|
||||||
off = 0.5;
|
|
||||||
tri[x * dim + y] =
|
|
||||||
Point((x + dx) / (dim - 1) - 0.5, (y + off + dy) / (dim - 1) - 0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
triangles = List();
|
|
||||||
var r = rng.nextInt(5), c;
|
|
||||||
if (r == 0) c = Colors.lightBlue;
|
|
||||||
if (r == 1) c = Colors.yellow;
|
|
||||||
if (r == 2) c = Colors.lightGreen;
|
|
||||||
if (r == 3) c = Colors.red;
|
|
||||||
if (r == 4) c = Colors.pink;
|
|
||||||
|
|
||||||
for (x = 0; x < dim - 1; x++) {
|
|
||||||
for (y = 0; y < dim - 1; y++) {
|
|
||||||
int off = x * dim;
|
|
||||||
T(tri[y + off], tri[y + 1 + off], tri[y + off + dim], c);
|
|
||||||
T(tri[y + off + dim], tri[y + 1 + off], tri[y + 1 + off + dim], c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,527 +0,0 @@
|
|||||||
// Package numberpicker:
|
|
||||||
// https://pub.dartlang.org/packages/numberpicker
|
|
||||||
|
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/rendering.dart';
|
|
||||||
|
|
||||||
import 'infinite_listview.dart';
|
|
||||||
|
|
||||||
/// Created by Marcin Szałek
|
|
||||||
|
|
||||||
///NumberPicker is a widget designed to pick a number between #minValue and #maxValue
|
|
||||||
class NumberPicker extends StatelessWidget {
|
|
||||||
///height of every list element
|
|
||||||
static const double DEFAULT_ITEM_EXTENT = 50.0;
|
|
||||||
|
|
||||||
///width of list view
|
|
||||||
static const double DEFAULT_LISTVIEW_WIDTH = 100.0;
|
|
||||||
|
|
||||||
///constructor for integer number picker
|
|
||||||
NumberPicker.integer({
|
|
||||||
Key key,
|
|
||||||
@required int initialValue,
|
|
||||||
@required this.minValue,
|
|
||||||
@required this.maxValue,
|
|
||||||
@required this.onChanged,
|
|
||||||
this.itemExtent = DEFAULT_ITEM_EXTENT,
|
|
||||||
this.listViewWidth = DEFAULT_LISTVIEW_WIDTH,
|
|
||||||
this.step = 1,
|
|
||||||
this.infiniteLoop = false,
|
|
||||||
}) : assert(initialValue != null),
|
|
||||||
assert(minValue != null),
|
|
||||||
assert(maxValue != null),
|
|
||||||
assert(maxValue > minValue),
|
|
||||||
assert(initialValue >= minValue && initialValue <= maxValue),
|
|
||||||
assert(step > 0),
|
|
||||||
selectedIntValue = initialValue,
|
|
||||||
selectedDecimalValue = -1,
|
|
||||||
decimalPlaces = 0,
|
|
||||||
intScrollController = infiniteLoop
|
|
||||||
? new InfiniteScrollController(
|
|
||||||
initialScrollOffset:
|
|
||||||
(initialValue - minValue) ~/ step * itemExtent,
|
|
||||||
)
|
|
||||||
: new ScrollController(
|
|
||||||
initialScrollOffset:
|
|
||||||
(initialValue - minValue) ~/ step * itemExtent,
|
|
||||||
),
|
|
||||||
decimalScrollController = null,
|
|
||||||
_listViewHeight = 3 * itemExtent,
|
|
||||||
integerItemCount = (maxValue - minValue) ~/ step + 1,
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
///constructor for decimal number picker
|
|
||||||
NumberPicker.decimal({
|
|
||||||
Key key,
|
|
||||||
@required double initialValue,
|
|
||||||
@required this.minValue,
|
|
||||||
@required this.maxValue,
|
|
||||||
@required this.onChanged,
|
|
||||||
this.decimalPlaces = 1,
|
|
||||||
this.itemExtent = DEFAULT_ITEM_EXTENT,
|
|
||||||
this.listViewWidth = DEFAULT_LISTVIEW_WIDTH,
|
|
||||||
}) : assert(initialValue != null),
|
|
||||||
assert(minValue != null),
|
|
||||||
assert(maxValue != null),
|
|
||||||
assert(decimalPlaces != null && decimalPlaces > 0),
|
|
||||||
assert(maxValue > minValue),
|
|
||||||
assert(initialValue >= minValue && initialValue <= maxValue),
|
|
||||||
selectedIntValue = initialValue.floor(),
|
|
||||||
selectedDecimalValue = ((initialValue - initialValue.floorToDouble()) *
|
|
||||||
math.pow(10, decimalPlaces))
|
|
||||||
.round(),
|
|
||||||
intScrollController = new ScrollController(
|
|
||||||
initialScrollOffset: (initialValue.floor() - minValue) * itemExtent,
|
|
||||||
),
|
|
||||||
decimalScrollController = new ScrollController(
|
|
||||||
initialScrollOffset: ((initialValue - initialValue.floorToDouble()) *
|
|
||||||
math.pow(10, decimalPlaces))
|
|
||||||
.roundToDouble() *
|
|
||||||
itemExtent,
|
|
||||||
),
|
|
||||||
_listViewHeight = 3 * itemExtent,
|
|
||||||
step = 1,
|
|
||||||
integerItemCount = maxValue.floor() - minValue.floor() + 1,
|
|
||||||
infiniteLoop = false,
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
///called when selected value changes
|
|
||||||
final ValueChanged<num> onChanged;
|
|
||||||
|
|
||||||
///min value user can pick
|
|
||||||
final int minValue;
|
|
||||||
|
|
||||||
///max value user can pick
|
|
||||||
final int maxValue;
|
|
||||||
|
|
||||||
///inidcates how many decimal places to show
|
|
||||||
/// e.g. 0=>[1,2,3...], 1=>[1.0, 1.1, 1.2...] 2=>[1.00, 1.01, 1.02...]
|
|
||||||
final int decimalPlaces;
|
|
||||||
|
|
||||||
///height of every list element in pixels
|
|
||||||
final double itemExtent;
|
|
||||||
|
|
||||||
///view will always contain only 3 elements of list in pixels
|
|
||||||
final double _listViewHeight;
|
|
||||||
|
|
||||||
///width of list view in pixels
|
|
||||||
final double listViewWidth;
|
|
||||||
|
|
||||||
///ScrollController used for integer list
|
|
||||||
final ScrollController intScrollController;
|
|
||||||
|
|
||||||
///ScrollController used for decimal list
|
|
||||||
final ScrollController decimalScrollController;
|
|
||||||
|
|
||||||
///Currently selected integer value
|
|
||||||
final int selectedIntValue;
|
|
||||||
|
|
||||||
///Currently selected decimal value
|
|
||||||
final int selectedDecimalValue;
|
|
||||||
|
|
||||||
///Step between elements. Only for integer datePicker
|
|
||||||
///Examples:
|
|
||||||
/// if step is 100 the following elements may be 100, 200, 300...
|
|
||||||
/// if min=0, max=6, step=3, then items will be 0, 3 and 6
|
|
||||||
/// if min=0, max=5, step=3, then items will be 0 and 3.
|
|
||||||
final int step;
|
|
||||||
|
|
||||||
///Repeat values infinitely
|
|
||||||
final bool infiniteLoop;
|
|
||||||
|
|
||||||
///Amount of items
|
|
||||||
final int integerItemCount;
|
|
||||||
|
|
||||||
//
|
|
||||||
//----------------------------- PUBLIC ------------------------------
|
|
||||||
//
|
|
||||||
|
|
||||||
animateInt(int valueToSelect) {
|
|
||||||
int diff = valueToSelect - minValue;
|
|
||||||
int index = diff ~/ step;
|
|
||||||
animateIntToIndex(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
animateIntToIndex(int index) {
|
|
||||||
_animate(intScrollController, index * itemExtent);
|
|
||||||
}
|
|
||||||
|
|
||||||
animateDecimal(int decimalValue) {
|
|
||||||
_animate(decimalScrollController, decimalValue * itemExtent);
|
|
||||||
}
|
|
||||||
|
|
||||||
animateDecimalAndInteger(double valueToSelect) {
|
|
||||||
animateInt(valueToSelect.floor());
|
|
||||||
animateDecimal(((valueToSelect - valueToSelect.floorToDouble()) *
|
|
||||||
math.pow(10, decimalPlaces))
|
|
||||||
.round());
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
//----------------------------- VIEWS -----------------------------
|
|
||||||
//
|
|
||||||
|
|
||||||
///main widget
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final ThemeData themeData = Theme.of(context);
|
|
||||||
|
|
||||||
if (infiniteLoop) {
|
|
||||||
return _integerInfiniteListView(themeData);
|
|
||||||
}
|
|
||||||
if (decimalPlaces == 0) {
|
|
||||||
return _integerListView(themeData);
|
|
||||||
} else {
|
|
||||||
return new Row(
|
|
||||||
children: <Widget>[
|
|
||||||
_integerListView(themeData),
|
|
||||||
_decimalListView(themeData),
|
|
||||||
],
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _integerListView(ThemeData themeData) {
|
|
||||||
TextStyle defaultStyle = themeData.textTheme.bodyText2;
|
|
||||||
TextStyle selectedStyle =
|
|
||||||
themeData.textTheme.headline5.copyWith(color: themeData.accentColor);
|
|
||||||
|
|
||||||
var listItemCount = integerItemCount + 2;
|
|
||||||
|
|
||||||
return new NotificationListener(
|
|
||||||
child: new Container(
|
|
||||||
height: _listViewHeight,
|
|
||||||
width: listViewWidth,
|
|
||||||
child: new ListView.builder(
|
|
||||||
controller: intScrollController,
|
|
||||||
itemExtent: itemExtent,
|
|
||||||
itemCount: listItemCount,
|
|
||||||
cacheExtent: _calculateCacheExtent(listItemCount),
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
|
||||||
final int value = _intValueFromIndex(index);
|
|
||||||
|
|
||||||
//define special style for selected (middle) element
|
|
||||||
final TextStyle itemStyle =
|
|
||||||
value == selectedIntValue ? selectedStyle : defaultStyle;
|
|
||||||
|
|
||||||
bool isExtra = index == 0 || index == listItemCount - 1;
|
|
||||||
|
|
||||||
return isExtra
|
|
||||||
? new Container() //empty first and last element
|
|
||||||
: new Center(
|
|
||||||
child: new Text(value.toString(), style: itemStyle),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onNotification: _onIntegerNotification,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _decimalListView(ThemeData themeData) {
|
|
||||||
TextStyle defaultStyle = themeData.textTheme.bodyText2;
|
|
||||||
TextStyle selectedStyle =
|
|
||||||
themeData.textTheme.headline5.copyWith(color: themeData.accentColor);
|
|
||||||
|
|
||||||
int decimalItemCount =
|
|
||||||
selectedIntValue == maxValue ? 3 : math.pow(10, decimalPlaces) + 2;
|
|
||||||
|
|
||||||
return new NotificationListener(
|
|
||||||
child: new Container(
|
|
||||||
height: _listViewHeight,
|
|
||||||
width: listViewWidth,
|
|
||||||
child: new ListView.builder(
|
|
||||||
controller: decimalScrollController,
|
|
||||||
itemExtent: itemExtent,
|
|
||||||
itemCount: decimalItemCount,
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
|
||||||
final int value = index - 1;
|
|
||||||
|
|
||||||
//define special style for selected (middle) element
|
|
||||||
final TextStyle itemStyle =
|
|
||||||
value == selectedDecimalValue ? selectedStyle : defaultStyle;
|
|
||||||
|
|
||||||
bool isExtra = index == 0 || index == decimalItemCount - 1;
|
|
||||||
|
|
||||||
return isExtra
|
|
||||||
? new Container() //empty first and last element
|
|
||||||
: new Center(
|
|
||||||
child: new Text(
|
|
||||||
value.toString().padLeft(decimalPlaces, '0'),
|
|
||||||
style: itemStyle),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onNotification: _onDecimalNotification,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _integerInfiniteListView(ThemeData themeData) {
|
|
||||||
TextStyle defaultStyle = themeData.textTheme.bodyText2;
|
|
||||||
TextStyle selectedStyle =
|
|
||||||
themeData.textTheme.headline5.copyWith(color: themeData.accentColor);
|
|
||||||
|
|
||||||
return new NotificationListener(
|
|
||||||
child: new Container(
|
|
||||||
height: _listViewHeight,
|
|
||||||
width: listViewWidth,
|
|
||||||
child: new InfiniteListView.builder(
|
|
||||||
controller: intScrollController,
|
|
||||||
itemExtent: itemExtent,
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
|
||||||
final int value = _intValueFromIndex(index);
|
|
||||||
|
|
||||||
//define special style for selected (middle) element
|
|
||||||
final TextStyle itemStyle =
|
|
||||||
value == selectedIntValue ? selectedStyle : defaultStyle;
|
|
||||||
|
|
||||||
return new Center(
|
|
||||||
child: new Text(value.toString(), style: itemStyle),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onNotification: _onIntegerNotification,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// ----------------------------- LOGIC -----------------------------
|
|
||||||
//
|
|
||||||
|
|
||||||
int _intValueFromIndex(int index) {
|
|
||||||
index--;
|
|
||||||
index %= integerItemCount;
|
|
||||||
return minValue + index * step;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _onIntegerNotification(Notification notification) {
|
|
||||||
if (notification is ScrollNotification) {
|
|
||||||
//calculate
|
|
||||||
int intIndexOfMiddleElement =
|
|
||||||
(notification.metrics.pixels / itemExtent).round();
|
|
||||||
if (!infiniteLoop) {
|
|
||||||
intIndexOfMiddleElement =
|
|
||||||
intIndexOfMiddleElement.clamp(0, integerItemCount - 1);
|
|
||||||
}
|
|
||||||
int intValueInTheMiddle = _intValueFromIndex(intIndexOfMiddleElement + 1);
|
|
||||||
intValueInTheMiddle = _normalizeIntegerMiddleValue(intValueInTheMiddle);
|
|
||||||
|
|
||||||
if (_userStoppedScrolling(notification, intScrollController)) {
|
|
||||||
//center selected value
|
|
||||||
animateIntToIndex(intIndexOfMiddleElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
//update selection
|
|
||||||
if (intValueInTheMiddle != selectedIntValue) {
|
|
||||||
num newValue;
|
|
||||||
if (decimalPlaces == 0) {
|
|
||||||
//return integer value
|
|
||||||
newValue = (intValueInTheMiddle);
|
|
||||||
} else {
|
|
||||||
if (intValueInTheMiddle == maxValue) {
|
|
||||||
//if new value is maxValue, then return that value and ignore decimal
|
|
||||||
newValue = (intValueInTheMiddle.toDouble());
|
|
||||||
animateDecimal(0);
|
|
||||||
} else {
|
|
||||||
//return integer+decimal
|
|
||||||
double decimalPart = _toDecimal(selectedDecimalValue);
|
|
||||||
newValue = ((intValueInTheMiddle + decimalPart).toDouble());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onChanged(newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _onDecimalNotification(Notification notification) {
|
|
||||||
if (notification is ScrollNotification) {
|
|
||||||
//calculate middle value
|
|
||||||
int indexOfMiddleElement =
|
|
||||||
(notification.metrics.pixels + _listViewHeight / 2) ~/ itemExtent;
|
|
||||||
int decimalValueInTheMiddle = indexOfMiddleElement - 1;
|
|
||||||
decimalValueInTheMiddle =
|
|
||||||
_normalizeDecimalMiddleValue(decimalValueInTheMiddle);
|
|
||||||
|
|
||||||
if (_userStoppedScrolling(notification, decimalScrollController)) {
|
|
||||||
//center selected value
|
|
||||||
animateDecimal(decimalValueInTheMiddle);
|
|
||||||
}
|
|
||||||
|
|
||||||
//update selection
|
|
||||||
if (selectedIntValue != maxValue &&
|
|
||||||
decimalValueInTheMiddle != selectedDecimalValue) {
|
|
||||||
double decimalPart = _toDecimal(decimalValueInTheMiddle);
|
|
||||||
double newValue = ((selectedIntValue + decimalPart).toDouble());
|
|
||||||
onChanged(newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
///There was a bug, when if there was small integer range, e.g. from 1 to 5,
|
|
||||||
///When user scrolled to the top, whole listview got displayed.
|
|
||||||
///To prevent this we are calculating cacheExtent by our own so it gets smaller if number of items is smaller
|
|
||||||
double _calculateCacheExtent(int itemCount) {
|
|
||||||
double cacheExtent = 250.0; //default cache extent
|
|
||||||
if ((itemCount - 2) * DEFAULT_ITEM_EXTENT <= cacheExtent) {
|
|
||||||
cacheExtent = ((itemCount - 3) * DEFAULT_ITEM_EXTENT);
|
|
||||||
}
|
|
||||||
return cacheExtent;
|
|
||||||
}
|
|
||||||
|
|
||||||
///When overscroll occurs on iOS,
|
|
||||||
///we can end up with value not in the range between [minValue] and [maxValue]
|
|
||||||
///To avoid going out of range, we change values out of range to border values.
|
|
||||||
int _normalizeMiddleValue(int valueInTheMiddle, int min, int max) {
|
|
||||||
return math.max(math.min(valueInTheMiddle, max), min);
|
|
||||||
}
|
|
||||||
|
|
||||||
int _normalizeIntegerMiddleValue(int integerValueInTheMiddle) {
|
|
||||||
//make sure that max is a multiple of step
|
|
||||||
int max = (maxValue ~/ step) * step;
|
|
||||||
return _normalizeMiddleValue(integerValueInTheMiddle, minValue, max);
|
|
||||||
}
|
|
||||||
|
|
||||||
int _normalizeDecimalMiddleValue(int decimalValueInTheMiddle) {
|
|
||||||
return _normalizeMiddleValue(
|
|
||||||
decimalValueInTheMiddle, 0, math.pow(10, decimalPlaces) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
///indicates if user has stopped scrolling so we can center value in the middle
|
|
||||||
bool _userStoppedScrolling(
|
|
||||||
Notification notification, ScrollController scrollController) {
|
|
||||||
return notification is UserScrollNotification &&
|
|
||||||
notification.direction == ScrollDirection.idle &&
|
|
||||||
// ignore: invalid_use_of_protected_member,invalid_use_of_visible_for_testing_member
|
|
||||||
scrollController.position.activity is! HoldScrollActivity;
|
|
||||||
}
|
|
||||||
|
|
||||||
///converts integer indicator of decimal value to double
|
|
||||||
///e.g. decimalPlaces = 1, value = 4 >>> result = 0.4
|
|
||||||
/// decimalPlaces = 2, value = 12 >>> result = 0.12
|
|
||||||
double _toDecimal(int decimalValueAsInteger) {
|
|
||||||
return double.parse((decimalValueAsInteger * math.pow(10, -decimalPlaces))
|
|
||||||
.toStringAsFixed(decimalPlaces));
|
|
||||||
}
|
|
||||||
|
|
||||||
///scroll to selected value
|
|
||||||
_animate(ScrollController scrollController, double value) {
|
|
||||||
scrollController.animateTo(value,
|
|
||||||
duration: new Duration(seconds: 1), curve: new ElasticOutCurve());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///Returns AlertDialog as a Widget so it is designed to be used in showDialog method
|
|
||||||
class NumberPickerDialog extends StatefulWidget {
|
|
||||||
final int minValue;
|
|
||||||
final int maxValue;
|
|
||||||
final int initialIntegerValue;
|
|
||||||
final double initialDoubleValue;
|
|
||||||
final int decimalPlaces;
|
|
||||||
final Widget title;
|
|
||||||
final EdgeInsets titlePadding;
|
|
||||||
final Widget confirmWidget;
|
|
||||||
final Widget cancelWidget;
|
|
||||||
final int step;
|
|
||||||
final bool infiniteLoop;
|
|
||||||
|
|
||||||
///constructor for integer values
|
|
||||||
NumberPickerDialog.integer({
|
|
||||||
@required this.minValue,
|
|
||||||
@required this.maxValue,
|
|
||||||
@required this.initialIntegerValue,
|
|
||||||
this.title,
|
|
||||||
this.titlePadding,
|
|
||||||
this.step = 1,
|
|
||||||
this.infiniteLoop = false,
|
|
||||||
Widget confirmWidget,
|
|
||||||
Widget cancelWidget,
|
|
||||||
}) : confirmWidget = confirmWidget ?? new Text("OK"),
|
|
||||||
cancelWidget = cancelWidget ?? new Text("CANCEL"),
|
|
||||||
decimalPlaces = 0,
|
|
||||||
initialDoubleValue = -1.0;
|
|
||||||
|
|
||||||
///constructor for decimal values
|
|
||||||
NumberPickerDialog.decimal({
|
|
||||||
@required this.minValue,
|
|
||||||
@required this.maxValue,
|
|
||||||
@required this.initialDoubleValue,
|
|
||||||
this.decimalPlaces = 1,
|
|
||||||
this.title,
|
|
||||||
this.titlePadding,
|
|
||||||
Widget confirmWidget,
|
|
||||||
Widget cancelWidget,
|
|
||||||
}) : confirmWidget = confirmWidget ?? new Text("OK"),
|
|
||||||
cancelWidget = cancelWidget ?? new Text("CANCEL"),
|
|
||||||
initialIntegerValue = -1,
|
|
||||||
step = 1,
|
|
||||||
infiniteLoop = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<NumberPickerDialog> createState() =>
|
|
||||||
new _NumberPickerDialogControllerState(
|
|
||||||
initialIntegerValue, initialDoubleValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NumberPickerDialogControllerState extends State<NumberPickerDialog> {
|
|
||||||
int selectedIntValue;
|
|
||||||
double selectedDoubleValue;
|
|
||||||
|
|
||||||
_NumberPickerDialogControllerState(
|
|
||||||
this.selectedIntValue, this.selectedDoubleValue);
|
|
||||||
|
|
||||||
_handleValueChanged(num value) {
|
|
||||||
if (value is int) {
|
|
||||||
setState(() => selectedIntValue = value);
|
|
||||||
} else {
|
|
||||||
setState(() => selectedDoubleValue = value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberPicker _buildNumberPicker() {
|
|
||||||
if (widget.decimalPlaces > 0) {
|
|
||||||
return new NumberPicker.decimal(
|
|
||||||
initialValue: selectedDoubleValue,
|
|
||||||
minValue: widget.minValue,
|
|
||||||
maxValue: widget.maxValue,
|
|
||||||
decimalPlaces: widget.decimalPlaces,
|
|
||||||
onChanged: _handleValueChanged);
|
|
||||||
} else {
|
|
||||||
return new NumberPicker.integer(
|
|
||||||
initialValue: selectedIntValue,
|
|
||||||
minValue: widget.minValue,
|
|
||||||
maxValue: widget.maxValue,
|
|
||||||
step: widget.step,
|
|
||||||
infiniteLoop: widget.infiniteLoop,
|
|
||||||
onChanged: _handleValueChanged,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return new AlertDialog(
|
|
||||||
title: widget.title,
|
|
||||||
titlePadding: widget.titlePadding,
|
|
||||||
content: _buildNumberPicker(),
|
|
||||||
actions: [
|
|
||||||
new TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
child: widget.cancelWidget,
|
|
||||||
),
|
|
||||||
new TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(widget.decimalPlaces > 0
|
|
||||||
? selectedDoubleValue
|
|
||||||
: selectedIntValue),
|
|
||||||
child: widget.confirmWidget),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
# Generated by pub
|
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
|
||||||
packages:
|
|
||||||
characters:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: characters
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.0-nullsafety.5"
|
|
||||||
collection:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: collection
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.15.0-nullsafety.5"
|
|
||||||
flutter:
|
|
||||||
dependency: "direct main"
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
meta:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: meta
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.0-nullsafety.6"
|
|
||||||
sky_engine:
|
|
||||||
dependency: transitive
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.99"
|
|
||||||
typed_data:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: typed_data
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.0-nullsafety.5"
|
|
||||||
vector_math:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: vector_math
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.0-nullsafety.5"
|
|
||||||
sdks:
|
|
||||||
dart: ">=2.12.0-0 <3.0.0"
|
|
@ -1,12 +0,0 @@
|
|||||||
name: timeflow
|
|
||||||
|
|
||||||
environment:
|
|
||||||
sdk: ">=2.2.0 <3.0.0"
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
flutter:
|
|
||||||
sdk: flutter
|
|
||||||
flutter:
|
|
||||||
uses-material-design: true
|
|
||||||
assets:
|
|
||||||
- preview.png
|
|
@ -1,11 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title></title>
|
|
||||||
<script defer src="main.dart.js" type="application/javascript"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
Reference in new issue