improved performance and battery usage on older devices
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import 'package:rrule/rrule.dart';
|
||||
|
||||
/// Memoised RRULE expansion.
|
||||
///
|
||||
/// `RecurrenceRule.getInstances` always starts at the series anchor, so asking
|
||||
/// for one week of a daily event created two years ago walks ~750 occurrences
|
||||
/// (tens of ms each time on older phones) — and the calendar asks once per
|
||||
/// week and event, the home widget twice per publish. Here each series keeps
|
||||
/// its parsed rule, the occurrences found so far and the live iterator, so
|
||||
/// later queries only extend the expansion instead of restarting it.
|
||||
class RecurrenceOccurrences {
|
||||
RecurrenceOccurrences._();
|
||||
|
||||
static const int _maxSeries = 64;
|
||||
static final Map<(String, DateTime), _Series> _cache = {};
|
||||
|
||||
/// UTC occurrences of [rule] anchored at [anchorUtc] in
|
||||
/// `[fromUtc, toUtc)`, in ascending order. Throws like
|
||||
/// [RecurrenceRule.fromString] for an invalid rule.
|
||||
static List<DateTime> between(
|
||||
String rule,
|
||||
DateTime anchorUtc,
|
||||
DateTime fromUtc,
|
||||
DateTime toUtc,
|
||||
) {
|
||||
final key = (rule, anchorUtc);
|
||||
var series = _cache.remove(key);
|
||||
series ??= _Series(RecurrenceRule.fromString(rule), anchorUtc);
|
||||
// Re-insert to keep the map in least-recently-used order.
|
||||
_cache[key] = series;
|
||||
if (_cache.length > _maxSeries) _cache.remove(_cache.keys.first);
|
||||
return series.between(fromUtc, toUtc);
|
||||
}
|
||||
|
||||
/// Drops all memoised series (tests).
|
||||
static void clear() => _cache.clear();
|
||||
}
|
||||
|
||||
class _Series {
|
||||
final Iterator<DateTime> _iterator;
|
||||
final List<DateTime> _found = [];
|
||||
bool _exhausted = false;
|
||||
|
||||
_Series(RecurrenceRule rule, DateTime anchorUtc)
|
||||
: _iterator = rule.getInstances(start: anchorUtc).iterator;
|
||||
|
||||
List<DateTime> between(DateTime fromUtc, DateTime toUtc) {
|
||||
while (!_exhausted && (_found.isEmpty || _found.last.isBefore(toUtc))) {
|
||||
if (_iterator.moveNext()) {
|
||||
_found.add(_iterator.current);
|
||||
} else {
|
||||
_exhausted = true;
|
||||
}
|
||||
}
|
||||
final start = _lowerBound(fromUtc);
|
||||
return [
|
||||
for (var i = start; i < _found.length && _found[i].isBefore(toUtc); i++)
|
||||
_found[i],
|
||||
];
|
||||
}
|
||||
|
||||
int _lowerBound(DateTime value) {
|
||||
var low = 0;
|
||||
var high = _found.length;
|
||||
while (low < high) {
|
||||
final mid = (low + high) >> 1;
|
||||
if (_found[mid].isBefore(value)) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
return low;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user