Some cleanup
This commit is contained in:
@ -24,13 +24,7 @@ class _LightCardBodyState extends State<LightCardBody> {
|
|||||||
widthFactor: 0.5,
|
widthFactor: 0.5,
|
||||||
child: Container(
|
child: Container(
|
||||||
//color: Colors.redAccent,
|
//color: Colors.redAccent,
|
||||||
child: SingleCircularSlider(
|
child: Text('Pffffff'),
|
||||||
255,
|
|
||||||
entity.brightness ?? 0,
|
|
||||||
baseColor: Colors.white,
|
|
||||||
handlerColor: Colors.blue[200],
|
|
||||||
selectionColor: Colors.blue[100],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:date_format/date_format.dart';
|
import 'package:date_format/date_format.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:charts_flutter/flutter.dart' as charts;
|
import 'package:charts_flutter/flutter.dart' as charts;
|
||||||
import 'package:progress_indicators/progress_indicators.dart';
|
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
@ -21,7 +20,6 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|||||||
import 'package:device_info/device_info.dart';
|
import 'package:device_info/device_info.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:in_app_purchase/in_app_purchase.dart';
|
import 'package:in_app_purchase/in_app_purchase.dart';
|
||||||
import 'plugins/circular_slider/single_circular_slider.dart';
|
|
||||||
import 'plugins/dynamic_multi_column_layout.dart';
|
import 'plugins/dynamic_multi_column_layout.dart';
|
||||||
import 'plugins/spoiler_card.dart';
|
import 'plugins/spoiler_card.dart';
|
||||||
import 'package:workmanager/workmanager.dart' as workManager;
|
import 'package:workmanager/workmanager.dart' as workManager;
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'utils.dart';
|
|
||||||
|
|
||||||
class BasePainter extends CustomPainter {
|
|
||||||
Color baseColor;
|
|
||||||
Color selectionColor;
|
|
||||||
int primarySectors;
|
|
||||||
int secondarySectors;
|
|
||||||
double sliderStrokeWidth;
|
|
||||||
|
|
||||||
Offset center;
|
|
||||||
double radius;
|
|
||||||
|
|
||||||
BasePainter({
|
|
||||||
@required this.baseColor,
|
|
||||||
@required this.selectionColor,
|
|
||||||
@required this.primarySectors,
|
|
||||||
@required this.secondarySectors,
|
|
||||||
@required this.sliderStrokeWidth,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
Paint base = _getPaint(color: baseColor);
|
|
||||||
|
|
||||||
center = Offset(size.width / 2, size.height / 2);
|
|
||||||
radius = min(size.width / 2, size.height / 2) - sliderStrokeWidth;
|
|
||||||
// we need this in the parent to calculate if the user clicks on the circumference
|
|
||||||
|
|
||||||
assert(radius > 0);
|
|
||||||
|
|
||||||
canvas.drawCircle(center, radius, base);
|
|
||||||
|
|
||||||
if (primarySectors > 0) {
|
|
||||||
_paintSectors(primarySectors, 8.0, selectionColor, canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (secondarySectors > 0) {
|
|
||||||
_paintSectors(secondarySectors, 6.0, baseColor, canvas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _paintSectors(
|
|
||||||
int sectors, double radiusPadding, Color color, Canvas canvas) {
|
|
||||||
Paint section = _getPaint(color: color, width: 2.0);
|
|
||||||
|
|
||||||
var endSectors =
|
|
||||||
getSectionsCoordinatesInCircle(center, radius + radiusPadding, sectors);
|
|
||||||
var initSectors =
|
|
||||||
getSectionsCoordinatesInCircle(center, radius - radiusPadding, sectors);
|
|
||||||
_paintLines(canvas, initSectors, endSectors, section);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _paintLines(
|
|
||||||
Canvas canvas, List<Offset> inits, List<Offset> ends, Paint section) {
|
|
||||||
assert(inits.length == ends.length && inits.length > 0);
|
|
||||||
|
|
||||||
for (var i = 0; i < inits.length; i++) {
|
|
||||||
canvas.drawLine(inits[i], ends[i], section);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Paint _getPaint({@required Color color, double width, PaintingStyle style}) =>
|
|
||||||
Paint()
|
|
||||||
..color = color
|
|
||||||
..strokeCap = StrokeCap.round
|
|
||||||
..style = style ?? PaintingStyle.stroke
|
|
||||||
..strokeWidth = width ?? sliderStrokeWidth;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,366 +0,0 @@
|
|||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'base_painter.dart';
|
|
||||||
import 'slider_painter.dart';
|
|
||||||
import 'utils.dart';
|
|
||||||
|
|
||||||
enum CircularSliderMode { singleHandler, doubleHandler }
|
|
||||||
|
|
||||||
enum SlidingState { none, endIsBiggerThanStart, endIsSmallerThanStart }
|
|
||||||
|
|
||||||
typedef SelectionChanged<T> = void Function(T a, T b, T c);
|
|
||||||
|
|
||||||
class CircularSliderPaint extends StatefulWidget {
|
|
||||||
final CircularSliderMode mode;
|
|
||||||
final int init;
|
|
||||||
final int end;
|
|
||||||
final int divisions;
|
|
||||||
final int primarySectors;
|
|
||||||
final int secondarySectors;
|
|
||||||
final SelectionChanged<int> onSelectionChange;
|
|
||||||
final SelectionChanged<int> onSelectionEnd;
|
|
||||||
final Color baseColor;
|
|
||||||
final Color selectionColor;
|
|
||||||
final Color handlerColor;
|
|
||||||
final double handlerOutterRadius;
|
|
||||||
final Widget child;
|
|
||||||
final bool showRoundedCapInSelection;
|
|
||||||
final bool showHandlerOutter;
|
|
||||||
final double sliderStrokeWidth;
|
|
||||||
final bool shouldCountLaps;
|
|
||||||
|
|
||||||
CircularSliderPaint({
|
|
||||||
@required this.mode,
|
|
||||||
@required this.divisions,
|
|
||||||
@required this.init,
|
|
||||||
@required this.end,
|
|
||||||
this.child,
|
|
||||||
@required this.primarySectors,
|
|
||||||
@required this.secondarySectors,
|
|
||||||
@required this.onSelectionChange,
|
|
||||||
@required this.onSelectionEnd,
|
|
||||||
@required this.baseColor,
|
|
||||||
@required this.selectionColor,
|
|
||||||
@required this.handlerColor,
|
|
||||||
@required this.handlerOutterRadius,
|
|
||||||
@required this.showRoundedCapInSelection,
|
|
||||||
@required this.showHandlerOutter,
|
|
||||||
@required this.sliderStrokeWidth,
|
|
||||||
@required this.shouldCountLaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
_CircularSliderState createState() => _CircularSliderState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CircularSliderState extends State<CircularSliderPaint> {
|
|
||||||
bool _isInitHandlerSelected = false;
|
|
||||||
bool _isEndHandlerSelected = false;
|
|
||||||
|
|
||||||
SliderPainter _painter;
|
|
||||||
|
|
||||||
/// start angle in radians where we need to locate the init handler
|
|
||||||
double _startAngle;
|
|
||||||
|
|
||||||
/// end angle in radians where we need to locate the end handler
|
|
||||||
double _endAngle;
|
|
||||||
|
|
||||||
/// the absolute angle in radians representing the selection
|
|
||||||
double _sweepAngle;
|
|
||||||
|
|
||||||
/// in case we have a double slider and we want to move the whole selection by clicking in the slider
|
|
||||||
/// this will capture the position in the selection relative to the initial handler
|
|
||||||
/// that way we will be able to keep the selection constant when moving
|
|
||||||
int _differenceFromInitPoint;
|
|
||||||
|
|
||||||
/// will store the number of full laps (2pi radians) as part of the selection
|
|
||||||
int _laps = 0;
|
|
||||||
|
|
||||||
/// will be used to calculate in the next movement if we need to increase or decrease _laps
|
|
||||||
SlidingState _slidingState = SlidingState.none;
|
|
||||||
|
|
||||||
bool get isDoubleHandler => widget.mode == CircularSliderMode.doubleHandler;
|
|
||||||
|
|
||||||
bool get isSingleHandler => widget.mode == CircularSliderMode.singleHandler;
|
|
||||||
|
|
||||||
bool get isBothHandlersSelected =>
|
|
||||||
_isEndHandlerSelected && _isInitHandlerSelected;
|
|
||||||
|
|
||||||
bool get isNoHandlersSelected =>
|
|
||||||
!_isEndHandlerSelected && !_isInitHandlerSelected;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_calculatePaintData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need to update this widget both with gesture detector but
|
|
||||||
// also when the parent widget rebuilds itself
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(CircularSliderPaint oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
if (oldWidget.init != widget.init || oldWidget.end != widget.end) {
|
|
||||||
_calculatePaintData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return RawGestureDetector(
|
|
||||||
gestures: <Type, GestureRecognizerFactory>{
|
|
||||||
CustomPanGestureRecognizer:
|
|
||||||
GestureRecognizerFactoryWithHandlers<CustomPanGestureRecognizer>(
|
|
||||||
() => CustomPanGestureRecognizer(
|
|
||||||
onPanDown: _onPanDown,
|
|
||||||
onPanUpdate: _onPanUpdate,
|
|
||||||
onPanEnd: _onPanEnd,
|
|
||||||
),
|
|
||||||
(CustomPanGestureRecognizer instance) {},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
child: CustomPaint(
|
|
||||||
painter: BasePainter(
|
|
||||||
baseColor: widget.baseColor,
|
|
||||||
selectionColor: widget.selectionColor,
|
|
||||||
primarySectors: widget.primarySectors,
|
|
||||||
secondarySectors: widget.secondarySectors,
|
|
||||||
sliderStrokeWidth: widget.sliderStrokeWidth,
|
|
||||||
),
|
|
||||||
foregroundPainter: _painter,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: widget.child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _calculatePaintData() {
|
|
||||||
var initPercent = isDoubleHandler
|
|
||||||
? valueToPercentage(widget.init, widget.divisions)
|
|
||||||
: 0.0;
|
|
||||||
var endPercent = valueToPercentage(widget.end, widget.divisions);
|
|
||||||
var sweep = getSweepAngle(initPercent, endPercent);
|
|
||||||
|
|
||||||
var previousStartAngle = _startAngle;
|
|
||||||
var previousEndAngle = _endAngle;
|
|
||||||
|
|
||||||
_startAngle = isDoubleHandler ? percentageToRadians(initPercent) : 0.0;
|
|
||||||
_endAngle = percentageToRadians(endPercent);
|
|
||||||
_sweepAngle = percentageToRadians(sweep.abs());
|
|
||||||
|
|
||||||
// update full laps if need be
|
|
||||||
if (widget.shouldCountLaps) {
|
|
||||||
var newSlidingState = _calculateSlidingState(_startAngle, _endAngle);
|
|
||||||
if (isSingleHandler) {
|
|
||||||
_laps = _calculateLapsForsSingleHandler(
|
|
||||||
_endAngle, previousEndAngle, _slidingState, _laps);
|
|
||||||
_slidingState = newSlidingState;
|
|
||||||
} else {
|
|
||||||
// is double handler
|
|
||||||
if (newSlidingState != _slidingState) {
|
|
||||||
_laps = _calculateLapsForDoubleHandler(
|
|
||||||
_startAngle,
|
|
||||||
_endAngle,
|
|
||||||
previousStartAngle,
|
|
||||||
previousEndAngle,
|
|
||||||
_slidingState,
|
|
||||||
newSlidingState,
|
|
||||||
_laps);
|
|
||||||
_slidingState = newSlidingState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_painter = SliderPainter(
|
|
||||||
mode: widget.mode,
|
|
||||||
startAngle: _startAngle,
|
|
||||||
endAngle: _endAngle,
|
|
||||||
sweepAngle: _sweepAngle,
|
|
||||||
selectionColor: widget.selectionColor,
|
|
||||||
handlerColor: widget.handlerColor,
|
|
||||||
handlerOutterRadius: widget.handlerOutterRadius,
|
|
||||||
showRoundedCapInSelection: widget.showRoundedCapInSelection,
|
|
||||||
showHandlerOutter: widget.showHandlerOutter,
|
|
||||||
sliderStrokeWidth: widget.sliderStrokeWidth,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
int _calculateLapsForsSingleHandler(
|
|
||||||
double end, double prevEnd, SlidingState slidingState, int laps) {
|
|
||||||
if (slidingState != SlidingState.none) {
|
|
||||||
if (radiansWasModuloed(end, prevEnd)) {
|
|
||||||
var lapIncrement = end < prevEnd ? 1 : -1;
|
|
||||||
var newLaps = laps + lapIncrement;
|
|
||||||
return newLaps < 0 ? 0 : newLaps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return laps;
|
|
||||||
}
|
|
||||||
|
|
||||||
int _calculateLapsForDoubleHandler(
|
|
||||||
double start,
|
|
||||||
double end,
|
|
||||||
double prevStart,
|
|
||||||
double prevEnd,
|
|
||||||
SlidingState slidingState,
|
|
||||||
SlidingState newSlidingState,
|
|
||||||
int laps) {
|
|
||||||
if (slidingState != SlidingState.none) {
|
|
||||||
if (!radiansWasModuloed(start, prevStart) &&
|
|
||||||
!radiansWasModuloed(end, prevEnd)) {
|
|
||||||
var lapIncrement =
|
|
||||||
newSlidingState == SlidingState.endIsBiggerThanStart ? 1 : -1;
|
|
||||||
var newLaps = laps + lapIncrement;
|
|
||||||
return newLaps < 0 ? 0 : newLaps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return laps;
|
|
||||||
}
|
|
||||||
|
|
||||||
SlidingState _calculateSlidingState(double start, double end) {
|
|
||||||
return end > start
|
|
||||||
? SlidingState.endIsBiggerThanStart
|
|
||||||
: SlidingState.endIsSmallerThanStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onPanUpdate(Offset details) {
|
|
||||||
if (!_isInitHandlerSelected && !_isEndHandlerSelected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_painter.center == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_handlePan(details, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onPanEnd(Offset details) {
|
|
||||||
_handlePan(details, true);
|
|
||||||
|
|
||||||
_isInitHandlerSelected = false;
|
|
||||||
_isEndHandlerSelected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handlePan(Offset details, bool isPanEnd) {
|
|
||||||
RenderBox renderBox = context.findRenderObject();
|
|
||||||
var position = renderBox.globalToLocal(details);
|
|
||||||
|
|
||||||
var angle = coordinatesToRadians(_painter.center, position);
|
|
||||||
var percentage = radiansToPercentage(angle);
|
|
||||||
var newValue = percentageToValue(percentage, widget.divisions);
|
|
||||||
|
|
||||||
if (isBothHandlersSelected) {
|
|
||||||
var newValueInit =
|
|
||||||
(newValue - _differenceFromInitPoint) % widget.divisions;
|
|
||||||
if (newValueInit != widget.init) {
|
|
||||||
var newValueEnd =
|
|
||||||
(widget.end + (newValueInit - widget.init)) % widget.divisions;
|
|
||||||
widget.onSelectionChange(newValueInit, newValueEnd, _laps);
|
|
||||||
if (isPanEnd) {
|
|
||||||
widget.onSelectionEnd(newValueInit, newValueEnd, _laps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// isDoubleHandler but one handler was selected
|
|
||||||
if (_isInitHandlerSelected) {
|
|
||||||
widget.onSelectionChange(newValue, widget.end, _laps);
|
|
||||||
if (isPanEnd) {
|
|
||||||
widget.onSelectionEnd(newValue, widget.end, _laps);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
widget.onSelectionChange(widget.init, newValue, _laps);
|
|
||||||
if (isPanEnd) {
|
|
||||||
widget.onSelectionEnd(widget.init, newValue, _laps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _onPanDown(Offset details) {
|
|
||||||
if (_painter == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
RenderBox renderBox = context.findRenderObject();
|
|
||||||
var position = renderBox.globalToLocal(details);
|
|
||||||
|
|
||||||
if (position == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSingleHandler) {
|
|
||||||
if (isPointAlongCircle(position, _painter.center, _painter.radius)) {
|
|
||||||
_isEndHandlerSelected = true;
|
|
||||||
_onPanUpdate(details);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_isInitHandlerSelected = isPointInsideCircle(
|
|
||||||
position, _painter.initHandler, widget.handlerOutterRadius);
|
|
||||||
|
|
||||||
if (!_isInitHandlerSelected) {
|
|
||||||
_isEndHandlerSelected = isPointInsideCircle(
|
|
||||||
position, _painter.endHandler, widget.handlerOutterRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNoHandlersSelected) {
|
|
||||||
// we check if the user pressed in the selection in a double handler slider
|
|
||||||
// that means the user wants to move the selection as a whole
|
|
||||||
if (isPointAlongCircle(position, _painter.center, _painter.radius)) {
|
|
||||||
var angle = coordinatesToRadians(_painter.center, position);
|
|
||||||
if (isAngleInsideRadiansSelection(angle, _startAngle, _sweepAngle)) {
|
|
||||||
_isEndHandlerSelected = true;
|
|
||||||
_isInitHandlerSelected = true;
|
|
||||||
var positionPercentage = radiansToPercentage(angle);
|
|
||||||
|
|
||||||
// no need to account for negative values, that will be sorted out in the onPanUpdate
|
|
||||||
_differenceFromInitPoint =
|
|
||||||
percentageToValue(positionPercentage, widget.divisions) -
|
|
||||||
widget.init;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _isInitHandlerSelected || _isEndHandlerSelected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CustomPanGestureRecognizer extends OneSequenceGestureRecognizer {
|
|
||||||
final Function onPanDown;
|
|
||||||
final Function onPanUpdate;
|
|
||||||
final Function onPanEnd;
|
|
||||||
|
|
||||||
CustomPanGestureRecognizer({
|
|
||||||
@required this.onPanDown,
|
|
||||||
@required this.onPanUpdate,
|
|
||||||
@required this.onPanEnd,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
void addPointer(PointerEvent event) {
|
|
||||||
if (onPanDown(event.position)) {
|
|
||||||
startTrackingPointer(event.pointer);
|
|
||||||
resolve(GestureDisposition.accepted);
|
|
||||||
} else {
|
|
||||||
stopTrackingPointer(event.pointer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void handleEvent(PointerEvent event) {
|
|
||||||
if (event is PointerMoveEvent) {
|
|
||||||
onPanUpdate(event.position);
|
|
||||||
}
|
|
||||||
if (event is PointerUpEvent) {
|
|
||||||
onPanEnd(event.position);
|
|
||||||
stopTrackingPointer(event.pointer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get debugDescription => 'customPan';
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didStopTrackingLastPointer(int pointer) {}
|
|
||||||
}
|
|
@ -1,148 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'circular_slider_paint.dart';
|
|
||||||
|
|
||||||
/// Returns a widget which displays a circle to be used as a slider.
|
|
||||||
///
|
|
||||||
/// Required arguments are init and end to set the initial selection.
|
|
||||||
/// onSelectionChange is a callback function which returns new values as the user
|
|
||||||
/// changes the interval.
|
|
||||||
/// The rest of the params are used to change the look and feel.
|
|
||||||
///
|
|
||||||
/// DoubleCircularSlider(5, 10, onSelectionChange: () => {});
|
|
||||||
class DoubleCircularSlider extends StatefulWidget {
|
|
||||||
/// the selection will be values between 0..divisions; max value is 300
|
|
||||||
final int divisions;
|
|
||||||
|
|
||||||
/// the initial value in the selection
|
|
||||||
final int init;
|
|
||||||
|
|
||||||
/// the end value in the selection
|
|
||||||
final int end;
|
|
||||||
|
|
||||||
/// the number of primary sectors to be painted
|
|
||||||
/// will be painted using selectionColor
|
|
||||||
final int primarySectors;
|
|
||||||
|
|
||||||
/// the number of secondary sectors to be painted
|
|
||||||
/// will be painted using baseColor
|
|
||||||
final int secondarySectors;
|
|
||||||
|
|
||||||
/// an optional widget that would be mounted inside the circle
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
/// height of the canvas, default at 220
|
|
||||||
final double height;
|
|
||||||
|
|
||||||
/// width of the canvas, default at 220
|
|
||||||
final double width;
|
|
||||||
|
|
||||||
/// color of the base circle and sections
|
|
||||||
final Color baseColor;
|
|
||||||
|
|
||||||
/// color of the selection
|
|
||||||
final Color selectionColor;
|
|
||||||
|
|
||||||
/// color of the handlers
|
|
||||||
final Color handlerColor;
|
|
||||||
|
|
||||||
/// callback function when init and end change
|
|
||||||
/// (int init, int end) => void
|
|
||||||
final SelectionChanged<int> onSelectionChange;
|
|
||||||
|
|
||||||
/// callback function when init and end finish
|
|
||||||
/// (int init, int end) => void
|
|
||||||
final SelectionChanged<int> onSelectionEnd;
|
|
||||||
|
|
||||||
/// outter radius for the handlers
|
|
||||||
final double handlerOutterRadius;
|
|
||||||
|
|
||||||
/// if true an extra handler ring will be displayed in the handler
|
|
||||||
final bool showHandlerOutter;
|
|
||||||
|
|
||||||
/// stroke width for the slider, defaults at 12.0
|
|
||||||
final double sliderStrokeWidth;
|
|
||||||
|
|
||||||
/// if true, the onSelectionChange will also return the number of laps in the slider
|
|
||||||
/// otherwise, everytime the user completes a full lap, the selection restarts from 0
|
|
||||||
final bool shouldCountLaps;
|
|
||||||
|
|
||||||
DoubleCircularSlider(
|
|
||||||
this.divisions,
|
|
||||||
this.init,
|
|
||||||
this.end, {
|
|
||||||
this.height,
|
|
||||||
this.width,
|
|
||||||
this.child,
|
|
||||||
this.primarySectors,
|
|
||||||
this.secondarySectors,
|
|
||||||
this.baseColor,
|
|
||||||
this.selectionColor,
|
|
||||||
this.handlerColor,
|
|
||||||
this.onSelectionChange,
|
|
||||||
this.onSelectionEnd,
|
|
||||||
this.handlerOutterRadius,
|
|
||||||
this.showHandlerOutter,
|
|
||||||
this.sliderStrokeWidth,
|
|
||||||
this.shouldCountLaps,
|
|
||||||
}) : assert(init >= 0 && init <= divisions,
|
|
||||||
'init has to be > 0 and < divisions value'),
|
|
||||||
assert(end >= 0 && end <= divisions,
|
|
||||||
'end has to be > 0 and < divisions value'),
|
|
||||||
assert(divisions >= 0 && divisions <= 300,
|
|
||||||
'divisions has to be > 0 and <= 300');
|
|
||||||
|
|
||||||
@override
|
|
||||||
_DoubleCircularSliderState createState() => _DoubleCircularSliderState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DoubleCircularSliderState extends State<DoubleCircularSlider> {
|
|
||||||
int _init;
|
|
||||||
int _end;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_init = widget.init;
|
|
||||||
_end = widget.end;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
height: widget.height ?? 220,
|
|
||||||
width: widget.width ?? 220,
|
|
||||||
child: CircularSliderPaint(
|
|
||||||
mode: CircularSliderMode.doubleHandler,
|
|
||||||
init: _init,
|
|
||||||
end: _end,
|
|
||||||
divisions: widget.divisions,
|
|
||||||
primarySectors: widget.primarySectors ?? 0,
|
|
||||||
secondarySectors: widget.secondarySectors ?? 0,
|
|
||||||
child: widget.child,
|
|
||||||
onSelectionChange: (newInit, newEnd, laps) {
|
|
||||||
if (widget.onSelectionChange != null) {
|
|
||||||
widget.onSelectionChange(newInit, newEnd, laps);
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_init = newInit;
|
|
||||||
_end = newEnd;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSelectionEnd: (newInit, newEnd, laps) {
|
|
||||||
if (widget.onSelectionEnd != null) {
|
|
||||||
widget.onSelectionEnd(newInit, newEnd, laps);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sliderStrokeWidth: widget.sliderStrokeWidth ?? 12.0,
|
|
||||||
baseColor: widget.baseColor ?? Color.fromRGBO(255, 255, 255, 0.1),
|
|
||||||
selectionColor:
|
|
||||||
widget.selectionColor ?? Color.fromRGBO(255, 255, 255, 0.3),
|
|
||||||
handlerColor: widget.handlerColor ?? Colors.white,
|
|
||||||
handlerOutterRadius: widget.handlerOutterRadius ?? 12.0,
|
|
||||||
showRoundedCapInSelection: false,
|
|
||||||
showHandlerOutter: widget.showHandlerOutter ?? true,
|
|
||||||
shouldCountLaps: widget.shouldCountLaps ?? false,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,147 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'circular_slider_paint.dart';
|
|
||||||
|
|
||||||
import '../../utils/logger.dart';
|
|
||||||
|
|
||||||
/// Returns a widget which displays a circle to be used as a slider.
|
|
||||||
///
|
|
||||||
/// Required arguments are position and divisions to set the initial selection.
|
|
||||||
/// onSelectionChange is a callback function which returns new values as the user
|
|
||||||
/// changes the interval.
|
|
||||||
/// The rest of the params are used to change the look and feel.
|
|
||||||
///
|
|
||||||
/// SingleCircularSlider(5, 10, onSelectionChange: () => {});
|
|
||||||
class SingleCircularSlider extends StatefulWidget {
|
|
||||||
/// the selection will be values between 0..divisions; max value is 300
|
|
||||||
final int divisions;
|
|
||||||
|
|
||||||
/// the initial value in the selection
|
|
||||||
int position;
|
|
||||||
|
|
||||||
/// the number of primary sectors to be painted
|
|
||||||
/// will be painted using selectionColor
|
|
||||||
final int primarySectors;
|
|
||||||
|
|
||||||
/// the number of secondary sectors to be painted
|
|
||||||
/// will be painted using baseColor
|
|
||||||
final int secondarySectors;
|
|
||||||
|
|
||||||
/// an optional widget that would be mounted inside the circle
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
/// height of the canvas, default at 220
|
|
||||||
final double height;
|
|
||||||
|
|
||||||
/// width of the canvas, default at 220
|
|
||||||
final double width;
|
|
||||||
|
|
||||||
/// color of the base circle and sections
|
|
||||||
final Color baseColor;
|
|
||||||
|
|
||||||
/// color of the selection
|
|
||||||
final Color selectionColor;
|
|
||||||
|
|
||||||
/// color of the handlers
|
|
||||||
final Color handlerColor;
|
|
||||||
|
|
||||||
/// callback function when init and end change
|
|
||||||
/// (int init, int end) => void
|
|
||||||
final SelectionChanged<int> onSelectionChange;
|
|
||||||
|
|
||||||
/// callback function when init and end finish
|
|
||||||
/// (int init, int end) => void
|
|
||||||
final SelectionChanged<int> onSelectionEnd;
|
|
||||||
|
|
||||||
/// outter radius for the handlers
|
|
||||||
final double handlerOutterRadius;
|
|
||||||
|
|
||||||
/// if true will paint a rounded cap in the selection slider start
|
|
||||||
final bool showRoundedCapInSelection;
|
|
||||||
|
|
||||||
/// if true an extra handler ring will be displayed in the handler
|
|
||||||
final bool showHandlerOutter;
|
|
||||||
|
|
||||||
/// stroke width for the slider, defaults at 12.0
|
|
||||||
final double sliderStrokeWidth;
|
|
||||||
|
|
||||||
/// if true, the onSelectionChange will also return the number of laps in the slider
|
|
||||||
/// otherwise, everytime the user completes a full lap, the selection restarts from 0
|
|
||||||
final bool shouldCountLaps;
|
|
||||||
|
|
||||||
SingleCircularSlider(
|
|
||||||
this.divisions,
|
|
||||||
this.position, {
|
|
||||||
this.height,
|
|
||||||
this.width,
|
|
||||||
this.child,
|
|
||||||
this.primarySectors,
|
|
||||||
this.secondarySectors,
|
|
||||||
this.baseColor,
|
|
||||||
this.selectionColor,
|
|
||||||
this.handlerColor,
|
|
||||||
this.onSelectionChange,
|
|
||||||
this.onSelectionEnd,
|
|
||||||
this.handlerOutterRadius,
|
|
||||||
this.showRoundedCapInSelection,
|
|
||||||
this.showHandlerOutter,
|
|
||||||
this.sliderStrokeWidth,
|
|
||||||
this.shouldCountLaps,
|
|
||||||
}) : assert(position >= 0 && position <= divisions,
|
|
||||||
'init has to be > 0 and < divisions value'),
|
|
||||||
assert(divisions >= 0 && divisions <= 300,
|
|
||||||
'divisions has to be > 0 and <= 300');
|
|
||||||
|
|
||||||
@override
|
|
||||||
_SingleCircularSliderState createState() => _SingleCircularSliderState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SingleCircularSliderState extends State<SingleCircularSlider> {
|
|
||||||
int _end;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_end = widget.position;
|
|
||||||
Logger.d('Init: _end=$_end');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
Logger.d('Build: _end=$_end');
|
|
||||||
return Container(
|
|
||||||
height: widget.height ?? 220,
|
|
||||||
width: widget.width ?? 220,
|
|
||||||
child: CircularSliderPaint(
|
|
||||||
mode: CircularSliderMode.singleHandler,
|
|
||||||
init: 0,
|
|
||||||
end: _end,
|
|
||||||
divisions: widget.divisions,
|
|
||||||
primarySectors: widget.primarySectors ?? 0,
|
|
||||||
secondarySectors: widget.secondarySectors ?? 0,
|
|
||||||
child: widget.child,
|
|
||||||
onSelectionChange: (newInit, newEnd, laps) {
|
|
||||||
if (widget.onSelectionChange != null) {
|
|
||||||
widget.onSelectionChange(newInit, newEnd, laps);
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_end = newEnd;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSelectionEnd: (newInit, newEnd, laps) {
|
|
||||||
if (widget.onSelectionEnd != null) {
|
|
||||||
widget.onSelectionEnd(newInit, newEnd, laps);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sliderStrokeWidth: widget.sliderStrokeWidth ?? 12.0,
|
|
||||||
baseColor: widget.baseColor ?? Color.fromRGBO(255, 255, 255, 0.1),
|
|
||||||
selectionColor:
|
|
||||||
widget.selectionColor ?? Color.fromRGBO(255, 255, 255, 0.3),
|
|
||||||
handlerColor: widget.handlerColor ?? Colors.white,
|
|
||||||
handlerOutterRadius: widget.handlerOutterRadius ?? 12.0,
|
|
||||||
showRoundedCapInSelection: widget.showRoundedCapInSelection ?? false,
|
|
||||||
showHandlerOutter: widget.showHandlerOutter ?? true,
|
|
||||||
shouldCountLaps: widget.shouldCountLaps ?? false,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'circular_slider_paint.dart' show CircularSliderMode;
|
|
||||||
import 'utils.dart';
|
|
||||||
|
|
||||||
class SliderPainter extends CustomPainter {
|
|
||||||
CircularSliderMode mode;
|
|
||||||
double startAngle;
|
|
||||||
double endAngle;
|
|
||||||
double sweepAngle;
|
|
||||||
Color selectionColor;
|
|
||||||
Color handlerColor;
|
|
||||||
double handlerOutterRadius;
|
|
||||||
bool showRoundedCapInSelection;
|
|
||||||
bool showHandlerOutter;
|
|
||||||
double sliderStrokeWidth;
|
|
||||||
|
|
||||||
Offset initHandler;
|
|
||||||
Offset endHandler;
|
|
||||||
Offset center;
|
|
||||||
double radius;
|
|
||||||
|
|
||||||
SliderPainter({
|
|
||||||
@required this.mode,
|
|
||||||
@required this.startAngle,
|
|
||||||
@required this.endAngle,
|
|
||||||
@required this.sweepAngle,
|
|
||||||
@required this.selectionColor,
|
|
||||||
@required this.handlerColor,
|
|
||||||
@required this.handlerOutterRadius,
|
|
||||||
@required this.showRoundedCapInSelection,
|
|
||||||
@required this.showHandlerOutter,
|
|
||||||
@required this.sliderStrokeWidth,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
Paint progress = _getPaint(color: selectionColor);
|
|
||||||
|
|
||||||
center = Offset(size.width / 2, size.height / 2);
|
|
||||||
radius = min(size.width / 2, size.height / 2) - sliderStrokeWidth;
|
|
||||||
|
|
||||||
canvas.drawArc(Rect.fromCircle(center: center, radius: radius),
|
|
||||||
-pi / 2 + startAngle, sweepAngle, false, progress);
|
|
||||||
|
|
||||||
Paint handler = _getPaint(color: handlerColor, style: PaintingStyle.fill);
|
|
||||||
Paint handlerOutter = _getPaint(color: handlerColor, width: 2.0);
|
|
||||||
|
|
||||||
// draw handlers
|
|
||||||
if (mode == CircularSliderMode.doubleHandler) {
|
|
||||||
initHandler = radiansToCoordinates(center, -pi / 2 + startAngle, radius);
|
|
||||||
canvas.drawCircle(initHandler, 8.0, handler);
|
|
||||||
canvas.drawCircle(initHandler, handlerOutterRadius, handlerOutter);
|
|
||||||
}
|
|
||||||
|
|
||||||
endHandler = radiansToCoordinates(center, -pi / 2 + endAngle, radius);
|
|
||||||
canvas.drawCircle(endHandler, 8.0, handler);
|
|
||||||
if (showHandlerOutter) {
|
|
||||||
canvas.drawCircle(endHandler, handlerOutterRadius, handlerOutter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Paint _getPaint({@required Color color, double width, PaintingStyle style}) =>
|
|
||||||
Paint()
|
|
||||||
..color = color
|
|
||||||
..strokeCap =
|
|
||||||
showRoundedCapInSelection ? StrokeCap.round : StrokeCap.butt
|
|
||||||
..style = style ?? PaintingStyle.stroke
|
|
||||||
..strokeWidth = width ?? sliderStrokeWidth;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
import 'dart:math';
|
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
double percentageToRadians(double percentage) => ((2 * pi * percentage) / 100);
|
|
||||||
|
|
||||||
double radiansToPercentage(double radians) {
|
|
||||||
var normalized = radians < 0 ? -radians : 2 * pi - radians;
|
|
||||||
var percentage = ((100 * normalized) / (2 * pi));
|
|
||||||
// TODO we have an inconsistency of pi/2 in terms of percentage and radians
|
|
||||||
return (percentage + 25) % 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
double coordinatesToRadians(Offset center, Offset coords) {
|
|
||||||
var a = coords.dx - center.dx;
|
|
||||||
var b = center.dy - coords.dy;
|
|
||||||
return atan2(b, a);
|
|
||||||
}
|
|
||||||
|
|
||||||
Offset radiansToCoordinates(Offset center, double radians, double radius) {
|
|
||||||
var dx = center.dx + radius * cos(radians);
|
|
||||||
var dy = center.dy + radius * sin(radians);
|
|
||||||
return Offset(dx, dy);
|
|
||||||
}
|
|
||||||
|
|
||||||
double valueToPercentage(int time, int intervals) => (time / intervals) * 100;
|
|
||||||
|
|
||||||
int percentageToValue(double percentage, int intervals) =>
|
|
||||||
((percentage * intervals) / 100).round();
|
|
||||||
|
|
||||||
bool isPointInsideCircle(Offset point, Offset center, double rradius) {
|
|
||||||
var radius = rradius * 1.2;
|
|
||||||
return point.dx < (center.dx + radius) &&
|
|
||||||
point.dx > (center.dx - radius) &&
|
|
||||||
point.dy < (center.dy + radius) &&
|
|
||||||
point.dy > (center.dy - radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isPointAlongCircle(Offset point, Offset center, double radius) {
|
|
||||||
// distance is root(sqr(x2 - x1) + sqr(y2 - y1))
|
|
||||||
// i.e., (7,8) and (3,2) -> 7.21
|
|
||||||
var d1 = pow(point.dx - center.dx, 2);
|
|
||||||
var d2 = pow(point.dy - center.dy, 2);
|
|
||||||
var distance = sqrt(d1 + d2);
|
|
||||||
return (distance - radius).abs() < 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
double getSweepAngle(double init, double end) {
|
|
||||||
if (end > init) {
|
|
||||||
return end - init;
|
|
||||||
}
|
|
||||||
return (100 - init + end).abs();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Offset> getSectionsCoordinatesInCircle(
|
|
||||||
Offset center, double radius, int sections) {
|
|
||||||
var intervalAngle = (pi * 2) / sections;
|
|
||||||
return List<int>.generate(sections, (int index) => index).map((i) {
|
|
||||||
var radians = (pi / 2) + (intervalAngle * i);
|
|
||||||
return radiansToCoordinates(center, radians, radius);
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isAngleInsideRadiansSelection(double angle, double start, double sweep) {
|
|
||||||
var normalized = angle > pi / 2 ? 5 * pi / 2 - angle : pi / 2 - angle;
|
|
||||||
var end = (start + sweep) % (2 * pi);
|
|
||||||
return end > start
|
|
||||||
? normalized > start && normalized < end
|
|
||||||
: normalized > start || normalized < end;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is not 100% accurate but it works
|
|
||||||
// we just want to see if a value changed drastically its value
|
|
||||||
bool radiansWasModuloed(double current, double previous) {
|
|
||||||
return (previous - current).abs() > (3 * pi / 2);
|
|
||||||
}
|
|
@ -12,7 +12,6 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
web_socket_channel: ^1.1.0
|
web_socket_channel: ^1.1.0
|
||||||
shared_preferences: ^0.5.6+1
|
shared_preferences: ^0.5.6+1
|
||||||
progress_indicators: ^0.1.4
|
|
||||||
path_provider: ^1.6.5
|
path_provider: ^1.6.5
|
||||||
event_bus: ^1.1.1
|
event_bus: ^1.1.1
|
||||||
cached_network_image: ^2.0.0
|
cached_network_image: ^2.0.0
|
||||||
|
Reference in New Issue
Block a user