part of dart._engine; // 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. /// 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 { /// Initializes Alarmclock with a closure that gets called with a timestamp. 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. /// /// If this is null, the alarm goes off without calling the callback. Set the /// callback to null if the callback is a closure holding onto expensive /// resources. 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() { _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?.call(); } else { // The timer fired before the target date. We need to reschedule. _timer = Timer(_datetime!.difference(now), _timerDidFire); } } }