Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.

Commit e160067

Browse files
committed
Min Max Cursor
1 parent 613ce71 commit e160067

4 files changed

Lines changed: 54 additions & 17 deletions

File tree

example/lib/main.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ class MyHomePage extends StatefulWidget {
3232
}
3333

3434
class _MyHomePageState extends State<MyHomePage> {
35-
InteractiveTimelineCubit timelineCubit = InteractiveTimelineCubit();
35+
InteractiveTimelineCubit timelineCubit = InteractiveTimelineCubit(
36+
minCursor: DateTime.now().subtract(Duration(hours: 1)),
37+
maxCursor: DateTime.now().add(Duration(hours: 1)),
38+
);
3639

3740
@override
3841
Widget build(BuildContext context) {

lib/src/canvas.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,16 @@ class TimelinePainter extends CustomPainter {
7474
getCandlesInFrame(interval).forEach((x) => paintCandle(canvas, size, x, paint, heightRatio, candleAlignment));
7575
}
7676

77-
List<double> getCandlesInFrame(Duration interval) {
77+
List<double> getCandlesInFrame(Duration interval, [DateTime? min, DateTime? max]) {
7878
// last full minute before cursorLeft
7979
Duration offset = Duration(milliseconds: state.leftCursor.millisecondsSinceEpoch % interval.inMilliseconds);
8080
DateTime startTime = state.leftCursor.subtract(offset);
8181
List<double> minuteCandles = [];
8282
while (startTime.isBefore(state.rightCursor.add(interval))) {
83-
minuteCandles.add(timeToPixel(startTime));
83+
// hie candles out of min max
84+
if (state.insideMinMaxCursor(startTime)) {
85+
minuteCandles.add(timeToPixel(startTime));
86+
}
8487
startTime = startTime.add(interval);
8588
}
8689
return minuteCandles;

lib/src/cubit.dart

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,21 @@ class InteractiveTimelineCubit extends Cubit<InteractiveTimelineState> {
1212
Duration timerUpdateInterval; // Milliseconds
1313
double initialZoomLevel; // Seconds per screen width
1414
DateTime? initialTime;
15+
DateTime? minCursor;
16+
DateTime? maxCursor;
1517
InteractiveTimelineCubit({
1618
this.minSecondsPerScreenWidth = 10,
1719
this.maxSecondsPerScreenWidth = 10000,
1820
this.timerUpdateInterval = const Duration(milliseconds: 25),
1921
this.initialZoomLevel = 500,
2022
this.initialTime,
23+
minCursor,
24+
maxCursor,
2125
}) : super(
2226
InteractiveTimelineState.initializeAtTime(
2327
initialTime ?? DateTime.now(),
28+
minCursor,
29+
maxCursor,
2430
),
2531
);
2632

@@ -40,12 +46,13 @@ class InteractiveTimelineCubit extends Cubit<InteractiveTimelineState> {
4046

4147
void dragHorizontally(DragUpdateDetails update) {
4248
num offsetSeconds = update.primaryDelta! * state.secondsPerPixel;
43-
emit(state.overwrite(
44-
middleCursor: state.middleCursor.subtract(
45-
Duration(milliseconds: (offsetSeconds * 1000).toInt()),
46-
),
47-
isInteracting: true,
48-
));
49+
DateTime newCursor = state.middleCursor.subtract(Duration(milliseconds: (offsetSeconds * 1000).toInt()));
50+
state.insideMinMaxCursor(newCursor)
51+
? emit(state.overwrite(
52+
middleCursor: newCursor,
53+
isInteracting: true,
54+
))
55+
: {};
4956
}
5057

5158
void zoomStart(ScaleStartDetails details) {
@@ -97,14 +104,22 @@ class InteractiveTimelineCubit extends Cubit<InteractiveTimelineState> {
97104
}
98105
}
99106

107+
// TODO: Using null won't clear the variables
108+
void setMinMax({DateTime? min, DateTime? max}) => emit(state.overwrite(minCursor: min, maxCursor: max));
109+
100110
void _tickTimer(Duration duration) {
101111
if (!state.isInteracting) {
102112
double timerMilliesecondsPerScreenWidth = 10 * 1000; // timer scroll speed
103113
double deltaScreenWidth = duration.inMilliseconds / timerMilliesecondsPerScreenWidth;
104114
Duration deltaCursor = Duration(milliseconds: (deltaScreenWidth * state.secondsPerScreenWidth * 1000).toInt());
105-
emit(state.overwrite(
106-
middleCursor: state.middleCursor.add(deltaCursor),
107-
));
115+
DateTime newCursor = state.middleCursor.add(deltaCursor);
116+
state.insideMinMaxCursor(newCursor)
117+
? emit(
118+
state.overwrite(
119+
middleCursor: newCursor,
120+
),
121+
)
122+
: stopTimer();
108123
}
109124
}
110125

@@ -128,6 +143,8 @@ class InteractiveTimelineState extends Equatable {
128143
required this.middleCursor,
129144
required this.leftCursor,
130145
required this.rightCursor,
146+
required this.minCursor,
147+
required this.maxCursor,
131148
required this.playTimer,
132149
required this.isPlaying,
133150
required this.isInteracting,
@@ -141,14 +158,16 @@ class InteractiveTimelineState extends Equatable {
141158
final DateTime middleCursor;
142159
final DateTime leftCursor;
143160
final DateTime rightCursor;
161+
final DateTime? minCursor;
162+
final DateTime? maxCursor;
144163
final Timer? playTimer;
145164
final bool isPlaying;
146165
final bool isInteracting;
147166

148167
@override
149168
List<Object> get props => [width, height, secondsPerPixel, secondsPerScreenWidth, secondsPerScreenWidthBeforeZoom, middleCursor, leftCursor, rightCursor, isPlaying, isInteracting];
150169

151-
static InteractiveTimelineState initializeAtTime(DateTime time) {
170+
static InteractiveTimelineState initializeAtTime(DateTime time, DateTime? minCursor, DateTime? maxCursor) {
152171
return InteractiveTimelineState(
153172
width: 0,
154173
height: 0,
@@ -158,6 +177,8 @@ class InteractiveTimelineState extends Equatable {
158177
middleCursor: time,
159178
leftCursor: time,
160179
rightCursor: time,
180+
minCursor: minCursor,
181+
maxCursor: maxCursor,
161182
playTimer: null,
162183
isPlaying: false,
163184
isInteracting: false,
@@ -176,12 +197,20 @@ class InteractiveTimelineState extends Equatable {
176197
);
177198
}
178199

200+
bool insideMinMaxCursor(DateTime time) {
201+
if (minCursor != null) if (time.isBefore(minCursor!)) return false;
202+
if (maxCursor != null) if (time.isAfter(maxCursor!)) return false;
203+
return true;
204+
}
205+
179206
InteractiveTimelineState overwrite({
180207
double? width,
181208
double? height,
182209
double? secondsPerScreenWidth,
183210
double? secondsPerScreenWidthBeforeZoom,
184211
DateTime? middleCursor,
212+
DateTime? minCursor,
213+
DateTime? maxCursor,
185214
Timer? playTimer,
186215
bool? isPlaying,
187216
bool? isInteracting,
@@ -198,6 +227,8 @@ class InteractiveTimelineState extends Equatable {
198227
middleCursor: middleCursor ?? this.middleCursor,
199228
leftCursor: getLeftCursor(width ?? this.width, newSecondsPerPixel),
200229
rightCursor: getRightCursor(width ?? this.width, newSecondsPerPixel),
230+
minCursor: minCursor ?? this.minCursor,
231+
maxCursor: maxCursor ?? this.maxCursor,
201232
playTimer: playTimer ?? this.playTimer,
202233
isPlaying: isPlaying ?? this.isPlaying,
203234
isInteracting: isInteracting ?? this.isInteracting,
@@ -206,9 +237,9 @@ class InteractiveTimelineState extends Equatable {
206237
}
207238

208239
class InteractiveTimelineBlocBuilder extends StatelessWidget {
209-
InteractiveTimelineCubit bloc;
210-
Widget Function(BuildContext, InteractiveTimelineState) builder;
211-
InteractiveTimelineBlocBuilder({
240+
final InteractiveTimelineCubit bloc;
241+
final Widget Function(BuildContext, InteractiveTimelineState) builder;
242+
const InteractiveTimelineBlocBuilder({
212243
Key? key,
213244
required this.bloc,
214245
required this.builder,

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: interactive_timeline
22
description: Draggable and zoomable interactive timeline.
3-
version: 1.0.0-dev.2
3+
version: 1.0.0-dev.3
44
# homepage: https://www.example.com
55

66
environment:

0 commit comments

Comments
 (0)