blob: 30a3746f4abe20fafae5b7e28debcbda83bbb834 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.6
part of engine;
/// A function that returns current system time.
typedef TimestampFunction = DateTime Function();
/// Notifies the [callback] at the given [datetime].
///
/// Allows changing [datetime] in either direction before the alarm goes off.
///
/// The implementation uses [Timer]s and therefore does not guarantee that it
/// will go off precisely at the specified time. For more details see:
///
/// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Notes
class AlarmClock {
AlarmClock(TimestampFunction timestampFunction)
: _timestampFunction = timestampFunction;
/// The function used to get current time.
final TimestampFunction _timestampFunction;
/// The underlying timer used to schedule the callback.
Timer _timer;
/// Current target time the [callback] is scheduled for.
DateTime _datetime;
/// The callback called when the alarm goes off.
ui.VoidCallback callback;
/// The time when the alarm clock will go off.
///
/// If the time is in the past or is `null` the alarm clock will not go off.
///
/// If the value is updated before an already scheduled timer goes off, the
/// previous time will not call the [callback]. Think of the updating this
/// value as "changing your mind" about when you want the next timer to fire.
DateTime get datetime => _datetime;
set datetime(DateTime value) {
if (value == _datetime) {
return;
}
if (value == null) {
_cancelTimer();
_datetime = null;
return;
}
final DateTime now = _timestampFunction();
// We use the "not before" logic instead of "is after" because zero-duration
// timers are valid.
final bool isInTheFuture = !value.isBefore(now);
if (!isInTheFuture) {
_cancelTimer();
_datetime = value;
return;
}
// At this point we have a non-null value that's in the future, and it is
// different from the current _datetime. We need to decide whether we need
// to create a new timer, or keep the existing one.
if (_timer == null) {
// We didn't have an existing timer, so create a new one.
_timer = Timer(value.difference(now), _timerDidFire);
} else {
assert(_datetime != null,
'We can only have a timer if there is a non-null datetime');
if (_datetime.isAfter(value)) {
// This is the case when the value moves the target time to an earlier
// point. Because there is no way to reconfigure an existing timer, we
// must cancel the old timer and schedule a new one.
_cancelTimer();
_timer = Timer(value.difference(now), _timerDidFire);
}
// We don't need to do anything in the "else" branch. If the new value
// is in the future relative to the current datetime, the _timerDidFire
// will reschedule.
}
_datetime = value;
}
void _cancelTimer() {
if (_timer != null) {
_timer.cancel();
_timer = null;
}
}
void _timerDidFire() {
assert(_datetime != null,
'If _datetime is null, the timer would have been cancelled');
final DateTime now = _timestampFunction();
// We use the "not before" logic instead of "is after" because we may have
// zero difference between now and _datetime.
if (!now.isBefore(_datetime)) {
_timer = null;
callback();
} else {
// The timer fired before the target date. We need to reschedule.
_timer = Timer(_datetime.difference(now), _timerDidFire);
}
}
}