Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
This package is forked from the original at https://github.com/Chris1234567899/flutter_time_range_picker

It improves it by:
- blocking the sliders from entering in the disabled interval
- fixes a bug where moving the slider quickly can make it skip over the other slider, causing the selected range to go through the disabled interval
- added option for the header background color

# Time Range Picker

A time range picker for flutter.
Expand Down
136 changes: 84 additions & 52 deletions lib/src/time-range-dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ showTimeRangePicker({
/// the color of the circle outline
Color? backgroundColor,

/// the background color of the header
Color? headerBackgroundColor,

/// a widget displayed in the background, use e.g. an image
Widget? backgroundWidget,

Expand Down Expand Up @@ -145,6 +148,7 @@ showTimeRangePicker({
handlerColor: handlerColor,
selectedColor: selectedColor,
backgroundColor: backgroundColor,
headerBackgroundColor: headerBackgroundColor,
disabledColor: disabledColor,
backgroundWidget: backgroundWidget,
ticks: ticks,
Expand Down Expand Up @@ -200,6 +204,7 @@ class TimeRangePicker extends StatefulWidget {
final Color? handlerColor;
final Color? selectedColor;
final Color? backgroundColor;
final Color? headerBackgroundColor;
final Color? disabledColor;
final PaintingStyle paintingStyle;

Expand Down Expand Up @@ -248,6 +253,7 @@ class TimeRangePicker extends StatefulWidget {
this.handlerColor,
this.selectedColor,
this.backgroundColor,
this.headerBackgroundColor,
this.disabledColor,
this.paintingStyle = PaintingStyle.stroke,
this.backgroundWidget,
Expand Down Expand Up @@ -449,43 +455,55 @@ class TimeRangePickerState extends State<TimeRangePicker>
var minDurationSigned = durationToAngle(widget.minDuration);
var minDurationAngle =
minDurationSigned < 0 ? 2 * pi + minDurationSigned : minDurationSigned;
//print("min duration angle " + (minDurationAngle * 180 / pi).toString());
if (_activeTime == ActiveTime.Start) {
var angleToEndSigned = signedAngle(_endAngle, dir);
var angleToEnd =
angleToEndSigned < 0 ? 2 * pi + angleToEndSigned : angleToEndSigned;
final beforeEnd = _endAngle - minDurationAngle;

//check if hitting disabled
if (widget.disabledTime != null) {
var angleToDisabledStart = signedAngle(_disabledStartAngle!, dir);
var angleToDisabledEnd = signedAngle(_disabledEndAngle!, dir);

var disabledAngleSigned =
signedAngle(_disabledEndAngle!, _disabledStartAngle!);
var disabledDiff = disabledAngleSigned < 0
? 2 * pi + disabledAngleSigned
: disabledAngleSigned;

//print("to disabled start " + (angleToDisabledStart * 180 / pi).toString());
// print("to disabled end " + (angleToDisabledEnd * 180 / pi).toString());

if (angleToDisabledStart - minDurationAngle < 0 &&
angleToDisabledStart > -disabledDiff / 2) {
dir = _disabledStartAngle! - minDurationAngle;
_updateTimeAndSnapAngle(ActiveTime.End, _disabledStartAngle!);
} else if (angleToDisabledEnd > 0 &&
angleToDisabledEnd < disabledDiff / 2) {
dir = _disabledEndAngle!;
// Keep start slider at disabled end if trying to move it through the disabled interval
// to between disabled start and just before end slider
final betweenBeforeEndAndDisabledEnd =
anglesInOrder(beforeEnd, dir, _disabledEndAngle!);
final startSliderIsNearerDisabledEnd =
(_startAngle - _disabledEndAngle!).abs() <
(beforeEnd - _startAngle).abs();
final endSliderAtDisabledStart = _endAngle == _disabledStartAngle!;
if (betweenBeforeEndAndDisabledEnd) {
if (startSliderIsNearerDisabledEnd) {
dir = _disabledEndAngle!;
} else if (endSliderAtDisabledStart) {
dir = _disabledStartAngle! - minDurationAngle;
}
}
}

// if after end time -> push end time ahead
if (angleToEnd > 0 && angleToEnd < minDurationAngle) {
var angle = dir + minDurationAngle;
_updateTimeAndSnapAngle(ActiveTime.End, angle);
final dirIsAfterStart = angleFromFirstToSecond(_startAngle, dir) < pi;
final dirIsAfterBeforeEnd = angleFromFirstToSecond(beforeEnd, dir) < pi;
final beforeEndIsAfterStart =
angleFromFirstToSecond(_startAngle, _endAngle) < pi;
if (_startAngle != _disabledEndAngle &&
_endAngle != _disabledStartAngle &&
dirIsAfterStart &&
dirIsAfterBeforeEnd &&
beforeEndIsAfterStart) {
var newAngle = dir + minDurationAngle;
// check if newAngle is in disabled interval, and if so make sure both sliders
// do not go inside
if (widget.disabledTime != null) {
final newAngleInDisabledInterval =
anglesInOrder(_disabledStartAngle!, newAngle, _disabledEndAngle!);
if (newAngleInDisabledInterval) {
newAngle = _disabledStartAngle!;
dir = _disabledStartAngle! - minDurationAngle;
}
}
_updateTimeAndSnapAngle(ActiveTime.End, newAngle);
}

//check end time
// check end time
if (widget.maxDuration != null) {
var startSigned = signedAngle(_endAngle, dir);
var startDiff = startSigned < 0 ? 2 * pi + startSigned : startSigned;
Expand All @@ -501,37 +519,49 @@ class TimeRangePickerState extends State<TimeRangePicker>
var angleToStart = angleToStartSigned < 0
? 2 * pi + angleToStartSigned
: angleToStartSigned;
final afterStart = _startAngle + minDurationAngle;

//check if hitting disabled
if (widget.disabledTime != null) {
var angleToDisabledStart = signedAngle(_disabledStartAngle!, dir);
var angleToDisabledEnd = signedAngle(_disabledEndAngle!, dir);

var disabledAngleSigned =
signedAngle(_disabledEndAngle!, _disabledStartAngle!);
var disabledDiff = disabledAngleSigned < 0
? 2 * pi + disabledAngleSigned
: disabledAngleSigned;

//print("to disabled start " + (angleToDisabledStart * 180 / pi).toString());
//print("to disabled end " + (angleToDisabledEnd * 180 / pi).toString());

//print("disabled diff " + (disabledDiff * 180 / pi).toString());

if (angleToDisabledStart < 0 &&
angleToDisabledStart > -disabledDiff / 2) {
dir = _disabledStartAngle!;
} else if (angleToDisabledEnd + minDurationAngle > 0 &&
angleToDisabledEnd < disabledDiff / 2) {
dir = _disabledEndAngle! + minDurationAngle;
_updateTimeAndSnapAngle(ActiveTime.Start, _disabledEndAngle!);
// Keep end slider at disabled start if trying to move it through the disabled interval
// to between disabled start and just after start slider
final betweenDisabledStartAndAfterStart =
anglesInOrder(_disabledStartAngle!, dir, afterStart);
final endSliderIsNearerDisabledStart =
(_disabledStartAngle! - _endAngle).abs() <
(_endAngle - afterStart).abs();
final startSliderAtDisabledEnd = _startAngle == _disabledEndAngle!;
if (betweenDisabledStartAndAfterStart) {
if (endSliderIsNearerDisabledStart) {
dir = _disabledStartAngle!;
} else if (startSliderAtDisabledEnd) {
dir = _disabledEndAngle! + minDurationAngle;
}
}
}

// if before start time -> push start time ahead
if (angleToStart > 0 && angleToStart < minDurationAngle) {
var angle = dir - minDurationAngle;
_updateTimeAndSnapAngle(ActiveTime.Start, angle);
final dirIsBeforeEnd = angleFromFirstToSecond(dir, _endAngle) < pi;
final dirIsBeforeAfterStart =
angleFromFirstToSecond(dir, afterStart) < pi;
final afterStartIsBeforeEnd =
angleFromFirstToSecond(_startAngle, _endAngle) < pi;
if (_endAngle != _disabledStartAngle &&
_startAngle != _disabledEndAngle &&
dirIsBeforeEnd &&
dirIsBeforeAfterStart &&
afterStartIsBeforeEnd) {
var newAngle = dir - minDurationAngle;
// check if newAngle is in disabled interval, and if so make sure both sliders
// do not go inside
if (widget.disabledTime != null) {
final newAngleInDisabledInterval =
anglesInOrder(_disabledStartAngle!, newAngle, _disabledEndAngle!);
if (newAngleInDisabledInterval) {
newAngle = _disabledEndAngle!;
dir = _disabledEndAngle! + minDurationAngle;
}
}
_updateTimeAndSnapAngle(ActiveTime.Start, newAngle);
}

//check end time
Expand Down Expand Up @@ -737,10 +767,12 @@ class TimeRangePickerState extends State<TimeRangePicker>
Color backgroundColor;
switch (themeData.brightness) {
case Brightness.light:
backgroundColor = themeData.primaryColor;
backgroundColor =
widget.headerBackgroundColor ?? themeData.primaryColor;
break;
case Brightness.dark:
backgroundColor = themeData.colorScheme.surface;
backgroundColor =
widget.headerBackgroundColor ?? themeData.colorScheme.surface;
break;
}

Expand Down
12 changes: 12 additions & 0 deletions lib/src/utils.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import 'dart:math';
import 'package:flutter/material.dart';

double angleFromFirstToSecond(double firstAngle, double secondAngle) {
return secondAngle - firstAngle > 0
? secondAngle - firstAngle
: 2 * pi + secondAngle - firstAngle;
}

bool anglesInOrder(double firstAngle, double secondAngle, double thirdAngle) {
final firstToSecond = angleFromFirstToSecond(firstAngle, secondAngle);
final firstToThird = angleFromFirstToSecond(firstAngle, thirdAngle);
return firstToSecond < firstToThird;
}

//normalize angle
double normalizeAngle(double radians) {
var normalized = atan2(sin(radians), cos(radians));
Expand Down