From bf752b02812aa13e0ccb777187325b6bbef48a14 Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Mon, 8 Aug 2022 02:26:27 +0300 Subject: [PATCH] feat: v0.5.0 --- .editorconfig | 8 + .prettierrc | 15 ++ CHANGELOG.md | 31 +++- LICENSE | 21 ++- android/local.properties | 4 +- ios/Flutter/flutter_export_environment.sh | 13 ++ lib/utils.dart | 1 + lib/wheel_spinner.dart | 203 +++++++++------------- pubspec.lock | 92 +++++----- pubspec.yaml | 6 +- 10 files changed, 222 insertions(+), 172 deletions(-) create mode 100644 .editorconfig create mode 100644 .prettierrc create mode 100755 ios/Flutter/flutter_export_environment.sh diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4f00475 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +[*] +tab_width = 2 +indent_size = 2 +indent_style = space +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..548c817 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,15 @@ +{ + "printWidth": 100, + "semi": false, + "singleQuote": true, + "trailingComma": "all", + "overrides": [ + { + "files": "*.md", + "options": { + "printWidth": 100, + "proseWrap": "always" + } + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index f667279..20a14e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,31 @@ +## [0.5.0] - Aug 8, 2022 + +- General code improvement + finalize null safety support + +## [0.0.7-nullsafety] - Jul 10, 2021 + +- No changes + +## [0.0.6-nullsafety] - Jul 30, 2021 + +- Updated the package for null-safety + ## [0.0.6] - 22 Mar, 2019 -* Added style customization options + +- Added style customization options + ## [0.0.5] - 22 Mar, 2019 -* Added finger fling physics to control + +- Added finger fling physics to control + ## [0.0.3-0.0.4] - 10 Mar, 2019 -* Bugfixes + +- Bugfixes + ## [0.0.2] - 10 Mar, 2019 -* Added `minMaxLabelBuilder` + +- Added `minMaxLabelBuilder` + ## [0.0.1] - 10 Mar, 2019 -* Initial release + +- Initial release diff --git a/LICENSE b/LICENSE index ff80f26..33f3a2d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,20 @@ -Copyright 2019 Chen Asraf +Copyright 2019-2022 Chen Asraf -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +Redistribution and use in source and binary forms, with or without modification, are permitted +provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +1. Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/android/local.properties b/android/local.properties index a887a62..7679f58 100644 --- a/android/local.properties +++ b/android/local.properties @@ -1,2 +1,2 @@ -sdk.dir=/usr/local/Cellar/android-sdk/24.4.1_1 -flutter.sdk=/Users/chenasraf/.flutter.src \ No newline at end of file +sdk.dir=/Users/chen/Library/Android/sdk +flutter.sdk=/Users/chen/.flutter-src \ No newline at end of file diff --git a/ios/Flutter/flutter_export_environment.sh b/ios/Flutter/flutter_export_environment.sh new file mode 100755 index 0000000..9b9e6fe --- /dev/null +++ b/ios/Flutter/flutter_export_environment.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=/Users/chen/.flutter-src" +export "FLUTTER_APPLICATION_PATH=/Users/chen/Dev/wheel_spinner" +export "COCOAPODS_PARALLEL_CODE_SIGN=true" +export "FLUTTER_TARGET=lib/main.dart" +export "FLUTTER_BUILD_DIR=build" +export "FLUTTER_BUILD_NAME=0.0.7" +export "FLUTTER_BUILD_NUMBER=0.0.7" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=true" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.dart_tool/package_config.json" diff --git a/lib/utils.dart b/lib/utils.dart index edf1255..e93c98d 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -1,4 +1,5 @@ import 'dart:math'; +/// clamps [value] to [min] and [max] double clamp(T number, T low, T high) => max(low * 1.0, min(number * 1.0, high * 1.0)); diff --git a/lib/wheel_spinner.dart b/lib/wheel_spinner.dart index f00a205..febd952 100644 --- a/lib/wheel_spinner.dart +++ b/lib/wheel_spinner.dart @@ -1,21 +1,19 @@ library wheel_spinner; -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:wheel_spinner/utils.dart'; -typedef Widget ValueBuilder(double value); -typedef String ValueStringBuilder(double value); +typedef ValueBuilder = Widget Function(double value); +typedef ValueStringBuilder = String Function(double value); /// Shows a "dial" spinner that can be dragged up or down, either unlimited or /// restricted by [max] and [min]. class WheelSpinner extends StatefulWidget { /// Callback for when the user drags the slider - final Function(double value) onSlideUpdate; + final Function(double value)? onSlideUpdate; /// Callback for when the user lets go of the slider - final Function(double value) onSlideDone; + final Function(double value)? onSlideDone; /// The widget [width] final double width; @@ -33,47 +31,47 @@ class WheelSpinner extends StatefulWidget { final double max; /// Builder for children of the slider. - final ValueBuilder childBuilder; + final ValueBuilder? childBuilder; /// Allows overriding the format of the left top and bottom labels for the [min]/[max] values - final ValueStringBuilder minMaxLabelBuilder; + final ValueStringBuilder? minMaxLabelBuilder; /// Allows to override style of labels - final TextStyle labelStyle; + final TextStyle? labelStyle; /// Override box decoration for the control - final BoxDecoration boxDecoration; + final BoxDecoration? boxDecoration; /// Override box border for the control's [boxDecoration]. /// If [boxDecoration] is specified, it overrides this property. - final Border border; + final Border? border; /// Override border radius for the control's [boxDecoration]. /// If [boxDecoration] is specified, it overrides this property. - final BorderRadius borderRadius; + final BorderRadius? borderRadius; /// Override background color for the control's [boxDecoration]. /// If [boxDecoration] is specified, it overrides this property. - final Color color; + final Color? color; /// Override background gradient for the control's [boxDecoration]. /// If [boxDecoration] is specified, it overrides this property. - final Gradient gradient; + final Gradient? gradient; /// Amount of divisions to show on the knob final int dividerCount; /// Color of the lines dividing the control. - final Color dividerColor; + final Color? dividerColor; - /// + /// The drag speed factor final double _dragSpeedFactor = 1.0; - static ValueStringBuilder defaultMinMaxLabelBuilder = - (v) => v.toStringAsFixed(2); + /// The default min/max label builder. + static ValueStringBuilder defaultMinMaxLabelBuilder = (v) => v.toStringAsFixed(2); const WheelSpinner( - {Key key, + {Key? key, this.onSlideUpdate, this.onSlideDone, this.width = 60, @@ -94,23 +92,23 @@ class WheelSpinner extends StatefulWidget { : super(key: key); @override + // ignore: library_private_types_in_public_api _WheelSpinnerState createState() => _WheelSpinnerState(); } -class _WheelSpinnerState extends State - with TickerProviderStateMixin { - double value; - double dragStartValue; - Offset dragStartOffset; - AnimationController flingController; - Animation flingAnimation; - void Function() currentFlingListener; +class _WheelSpinnerState extends State with SingleTickerProviderStateMixin { + late double value; + late double dragStartValue; + Offset? dragStartOffset; + late AnimationController flingController; + late Animation flingAnimation; + late void Function() currentFlingListener; - _WheelSpinnerState({this.value}); + _WheelSpinnerState(); @override void initState() { - flingAnimation = AlwaysStoppedAnimation(0.0); + flingAnimation = const AlwaysStoppedAnimation(0.0); flingController = AnimationController(vsync: this); value = widget.value; super.initState(); @@ -118,18 +116,14 @@ class _WheelSpinnerState extends State @override Widget build(BuildContext context) { - ValueStringBuilder minMaxBuilder = - widget.minMaxLabelBuilder ?? WheelSpinner.defaultMinMaxLabelBuilder; - double labelFontSize = Theme.of(context).textTheme.body1.fontSize * 0.75; - TextStyle labelStyle = - TextStyle(fontSize: labelFontSize).merge(widget.labelStyle); + final minMaxBuilder = widget.minMaxLabelBuilder ?? WheelSpinner.defaultMinMaxLabelBuilder; + final labelFontSize = Theme.of(context).textTheme.bodyText2!.fontSize! * 0.75; + final labelStyle = TextStyle(fontSize: labelFontSize).merge(widget.labelStyle); - String minText = - widget.max < double.infinity ? minMaxBuilder(widget.max) : null; - String maxText = - widget.min > double.negativeInfinity ? minMaxBuilder(widget.min) : null; + final minText = widget.max < double.infinity ? minMaxBuilder(widget.max) : null; + final maxText = widget.min > double.negativeInfinity ? minMaxBuilder(widget.min) : null; - return Container( + return SizedBox( height: widget.height, child: Row( mainAxisSize: MainAxisSize.min, @@ -141,12 +135,8 @@ class _WheelSpinnerState extends State mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, children: [ - minText != null - ? Text(minText, style: labelStyle) - : Container(), - maxText != null - ? Text(maxText, style: labelStyle) - : Container(), + minText != null ? Text(minText, style: labelStyle) : Container(), + maxText != null ? Text(maxText, style: labelStyle) : Container(), ], ), ), @@ -154,42 +144,36 @@ class _WheelSpinnerState extends State AnimatedBuilder( animation: flingAnimation, builder: (context, child) => GestureDetector( - onVerticalDragStart: onDragStart, - onVerticalDragUpdate: onDragUpdate, - onVerticalDragEnd: onDragDone, - child: SizedBox.fromSize( - size: - Size(widget.width.toDouble(), widget.height.toDouble()), - child: Container( - child: Stack( - children: List.generate( - widget.dividerCount + 1, - (i) { - var top = - lineTopPos(value, i, flingAnimation.value); - return Positioned.fromRect( - rect: Rect.fromLTWH( - 0.0, - top, - widget.width.toDouble(), - 0, - ), - child: Divider( - color: - widget.dividerColor ?? Colors.grey[600], - ), - ); - }, - ).toList() + - (widget.childBuilder != null - ? [widget.childBuilder(value)] - : []), - ), - decoration: widget.boxDecoration ?? - _defaultBoxDecorationBuilder(), - ), + onVerticalDragStart: onDragStart, + onVerticalDragUpdate: onDragUpdate, + onVerticalDragEnd: onDragDone, + child: SizedBox.fromSize( + size: Size(widget.width.toDouble(), widget.height.toDouble()), + child: Container( + decoration: widget.boxDecoration ?? _boxDecorationBuilder(), + child: Stack( + children: List.generate( + widget.dividerCount + 1, + (i) { + final top = lineTopPos(value, i, flingAnimation.value); + return Positioned.fromRect( + rect: Rect.fromLTWH( + 0.0, + top, + widget.width.toDouble(), + 0, + ), + child: Divider( + color: widget.dividerColor ?? Colors.grey[600], + ), + ); + }, + ).toList() + + (widget.childBuilder != null ? [widget.childBuilder!(value)] : []), ), ), + ), + ), ), Expanded( child: Padding( @@ -209,20 +193,15 @@ class _WheelSpinnerState extends State ); } - BoxDecoration _defaultBoxDecorationBuilder() { - double shadowOffset = 0.2; - var decoration = BoxDecoration( - gradient: widget.gradient ?? widget.color == null + BoxDecoration _boxDecorationBuilder() { + const shadowOffset = 0.2; + return BoxDecoration( + gradient: (widget.gradient ?? widget.color) == null ? LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - stops: [0.0, shadowOffset, 1.0 - shadowOffset, 1.0], - colors: [ - Colors.grey[350], - Colors.grey[50], - Colors.grey[50], - Colors.grey[350] - ], + stops: const [0.0, shadowOffset, 1.0 - shadowOffset, 1.0], + colors: [Colors.grey[350]!, Colors.grey[50]!, Colors.grey[50]!, Colors.grey[350]!], ) : null, color: widget.color, @@ -230,23 +209,22 @@ class _WheelSpinnerState extends State Border.all( width: 1, style: BorderStyle.solid, - color: Colors.grey[600], + color: Colors.grey[600]!, ), borderRadius: widget.borderRadius ?? BorderRadius.circular(3.5), ); - return decoration; } double lineTopPos(double value, int i, double fling) { - double valueFraction = (value.ceil() - value) * widget.dividerCount; - double indexedTop = (widget.height / widget.dividerCount * i); - double top = indexedTop + valueFraction; + final valueFraction = (value.ceil() - value) * widget.dividerCount; + final indexedTop = (widget.height / widget.dividerCount * i); + final top = indexedTop + valueFraction; return top; } void onDragStart(details) { flingController.stop(); - flingAnimation = AlwaysStoppedAnimation(0.0); + flingAnimation = const AlwaysStoppedAnimation(0.0); setState(() { dragStartOffset = details.globalPosition; dragStartValue = value; @@ -255,40 +233,34 @@ class _WheelSpinnerState extends State void onDragUpdate(details) { flingController.stop(); - var newValue = clamp( + final newValue = clamp( dragStartValue - - (details.globalPosition - dragStartOffset).dy / - (20.0 / widget._dragSpeedFactor), + (details.globalPosition - dragStartOffset).dy / (20.0 / widget._dragSpeedFactor), widget.min, widget.max); setState(() { value = newValue; }); - if (widget.onSlideUpdate != null) { - widget.onSlideUpdate(value); - } + widget.onSlideUpdate?.call(value); } void onDragDone(DragEndDetails details) { setState(() { dragStartOffset = null; }); - double velocity = details.primaryVelocity; + final velocity = details.primaryVelocity!; if (velocity.abs() == 0) { - if (widget.onSlideDone != null) { - widget.onSlideDone(value); - } + widget.onSlideDone?.call(value); return; } - double originalValue = value; + final originalValue = value; currentFlingListener = flingListener(originalValue); flingController.duration = Duration(milliseconds: velocity.abs().toInt()); - flingAnimation = - Tween(begin: 0.0, end: velocity / 100).animate(CurvedAnimation( + flingAnimation = Tween(begin: 0.0, end: velocity / 100).animate(CurvedAnimation( curve: Curves.decelerate, parent: flingController, )) - ..addListener(currentFlingListener); + ..addListener(currentFlingListener); flingController ..reset() ..forward(); @@ -296,20 +268,15 @@ class _WheelSpinnerState extends State flingListener(double originalValue) { return () { - double newValue = - clamp(originalValue - flingAnimation.value, widget.min, widget.max); + final newValue = clamp(originalValue - flingAnimation.value, widget.min, widget.max); if (newValue != value) { setState(() { value = newValue; }); if (flingAnimation.value == flingController.upperBound) { - if (widget.onSlideDone != null) { - widget.onSlideDone(value); - } + widget.onSlideDone?.call(value); } else { - if (widget.onSlideUpdate != null) { - widget.onSlideUpdate(value); - } + widget.onSlideUpdate?.call(value); } } }; diff --git a/pubspec.lock b/pubspec.lock index 2af49c9..2295121 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,5 +1,5 @@ # Generated by pub -# See https://www.dartlang.org/tools/pub/glossary#lockfile +# See https://dart.dev/tools/pub/glossary#lockfile packages: async: dependency: transitive @@ -7,73 +7,94 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.9.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" - charcode: + version: "2.1.0" + characters: dependency: transitive description: - name: charcode + name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.2.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.16.0" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.3+1" + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.0" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" + version: "1.8.2" sky_engine: dependency: transitive description: flutter @@ -85,55 +106,48 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.9.0" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "1.6.8" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.2" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.6" + version: "0.4.12" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.2" sdks: - dart: ">=2.2.0 <3.0.0" + dart: ">=2.17.0 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index fd2fb61..d56b380 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,17 +1,17 @@ name: wheel_spinner description: A simple Flutter widget for updating a number using a pitch bender-like spinner -version: 0.0.6 -author: Chen Asraf +version: 0.5.0 homepage: https://github.com/chenasraf/wheel_spinner environment: - sdk: ">=2.1.0 <3.0.0" + sdk: '>=2.17.0 <3.0.0' dependencies: flutter: sdk: flutter dev_dependencies: + flutter_lints: ^2.0.1 flutter_test: sdk: flutter