| // 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. |
| |
| import 'dart:async'; |
| |
| import 'package:ui/ui.dart' as ui; |
| |
| /// 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. |
| late 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(); |
| } else { |
| // The timer fired before the target date. We need to reschedule. |
| _timer = Timer(_datetime!.difference(now), _timerDidFire); |
| } |
| } |
| } |