1// Copyright 2013 The Flutter Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5part of engine; 6 7/// A function that returns current system time. 8typedef TimestampFunction = DateTime Function(); 9 10/// Notifies the [callback] at the given [datetime]. 11/// 12/// Allows changing [datetime] in either direction before the alarm goes off. 13/// 14/// The implementation uses [Timer]s and therefore does not guarantee that it 15/// will go off precisely at the specified time. For more details see: 16/// 17/// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Notes 18class AlarmClock { 19 AlarmClock(TimestampFunction timestampFunction) 20 : _timestampFunction = timestampFunction; 21 22 /// The function used to get current time. 23 final TimestampFunction _timestampFunction; 24 25 /// The underlying timer used to schedule the callback. 26 Timer _timer; 27 28 /// Current target time the [callback] is scheduled for. 29 DateTime _datetime; 30 31 /// The callback called when the alarm goes off. 32 ui.VoidCallback callback; 33 34 /// The time when the alarm clock will go off. 35 /// 36 /// If the time is in the past or is `null` the alarm clock will not go off. 37 /// 38 /// If the value is updated before an already scheduled timer goes off, the 39 /// previous time will not call the [callback]. Think of the updating this 40 /// value as "changing your mind" about when you want the next timer to fire. 41 DateTime get datetime => _datetime; 42 set datetime(DateTime value) { 43 if (value == _datetime) { 44 return; 45 } 46 47 if (value == null) { 48 _cancelTimer(); 49 _datetime = null; 50 return; 51 } 52 53 final DateTime now = _timestampFunction(); 54 55 // We use the "not before" logic instead of "is after" because zero-duration 56 // timers are valid. 57 final bool isInTheFuture = !value.isBefore(now); 58 59 if (!isInTheFuture) { 60 _cancelTimer(); 61 _datetime = value; 62 return; 63 } 64 65 // At this point we have a non-null value that's in the future, and it is 66 // different from the current _datetime. We need to decide whether we need 67 // to create a new timer, or keep the existing one. 68 if (_timer == null) { 69 // We didn't have an existing timer, so create a new one. 70 _timer = Timer(value.difference(now), _timerDidFire); 71 } else { 72 assert(_datetime != null, 73 'We can only have a timer if there is a non-null datetime'); 74 if (_datetime.isAfter(value)) { 75 // This is the case when the value moves the target time to an earlier 76 // point. Because there is no way to reconfigure an existing timer, we 77 // must cancel the old timer and schedule a new one. 78 _cancelTimer(); 79 _timer = Timer(value.difference(now), _timerDidFire); 80 } 81 // We don't need to do anything in the "else" branch. If the new value 82 // is in the future relative to the current datetime, the _timerDidFire 83 // will reschedule. 84 } 85 86 _datetime = value; 87 } 88 89 void _cancelTimer() { 90 if (_timer != null) { 91 _timer.cancel(); 92 _timer = null; 93 } 94 } 95 96 void _timerDidFire() { 97 assert(_datetime != null, 98 'If _datetime is null, the timer would have been cancelled'); 99 final DateTime now = _timestampFunction(); 100 // We use the "not before" logic instead of "is after" because we may have 101 // zero difference between now and _datetime. 102 if (!now.isBefore(_datetime)) { 103 _timer = null; 104 callback(); 105 } else { 106 // The timer fired before the target date. We need to reschedule. 107 _timer = Timer(_datetime.difference(now), _timerDidFire); 108 } 109 } 110} 111