|
...
|
...
|
@@ -9,6 +9,13 @@ DateTime _today() { |
|
|
|
return DateTime(now.year, now.month, now.day);
|
|
|
|
}
|
|
|
|
|
|
|
|
class _ReportPeriodAnchor {
|
|
|
|
const _ReportPeriodAnchor({required this.date, this.sourceMonth});
|
|
|
|
|
|
|
|
final DateTime date;
|
|
|
|
final DateTime? sourceMonth;
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract class ReportPeriodLogic {
|
|
|
|
ReportPeriodLogic({this.forceRefresh = true});
|
|
|
|
|
|
...
|
...
|
@@ -16,7 +23,7 @@ abstract class ReportPeriodLogic { |
|
|
|
final selectedPeriod = ReportPeriod.day.obs;
|
|
|
|
final selectedDate = _today().obs;
|
|
|
|
final isLoading = false.obs;
|
|
|
|
var _anchorDate = _today();
|
|
|
|
var _anchor = _ReportPeriodAnchor(date: _today());
|
|
|
|
|
|
|
|
List<ReportPeriod> get supportedPeriods => ReportPeriod.values;
|
|
|
|
|
|
...
|
...
|
@@ -50,7 +57,7 @@ abstract class ReportPeriodLogic { |
|
|
|
void initializeQuery(ReportPeriod period, DateTime date) {
|
|
|
|
final nextPeriod = normalizePeriod(period);
|
|
|
|
final nextDate = normalizeDate(nextPeriod, date);
|
|
|
|
_anchorDate = _anchorDateFor(nextPeriod, date);
|
|
|
|
_setAnchorFor(nextPeriod, date);
|
|
|
|
selectedPeriod.value = nextPeriod;
|
|
|
|
selectedDate.value = nextDate;
|
|
|
|
}
|
|
...
|
...
|
@@ -69,7 +76,7 @@ abstract class ReportPeriodLogic { |
|
|
|
return Future.value();
|
|
|
|
}
|
|
|
|
if (!sameQuery) {
|
|
|
|
_anchorDate = _anchorDateFor(nextPeriod, date);
|
|
|
|
_setAnchorFor(nextPeriod, date);
|
|
|
|
}
|
|
|
|
selectedPeriod.value = nextPeriod;
|
|
|
|
selectedDate.value = nextDate;
|
|
...
|
...
|
@@ -132,9 +139,14 @@ abstract class ReportPeriodLogic { |
|
|
|
if (!shouldRefresh && selectedPeriod.value == nextPeriod) {
|
|
|
|
return Future.value();
|
|
|
|
}
|
|
|
|
final nextDate = normalizeDate(nextPeriod, _anchorDate);
|
|
|
|
final nextDate = _dateForPeriodTransition(nextPeriod);
|
|
|
|
final sourceMonth = selectedPeriod.value == ReportPeriod.month &&
|
|
|
|
nextPeriod == ReportPeriod.week
|
|
|
|
? monthStart
|
|
|
|
: null;
|
|
|
|
selectedPeriod.value = nextPeriod;
|
|
|
|
selectedDate.value = nextDate;
|
|
|
|
_setAnchorFor(nextPeriod, nextDate, sourceMonth: sourceMonth);
|
|
|
|
return loadReport();
|
|
|
|
}
|
|
|
|
|
|
...
|
...
|
@@ -144,7 +156,7 @@ abstract class ReportPeriodLogic { |
|
|
|
}) {
|
|
|
|
final shouldRefresh = forceRefresh ?? this.forceRefresh;
|
|
|
|
final nextDate = ReportDateRangeConfig.clampDate(date);
|
|
|
|
_anchorDate = _anchorDateFor(ReportPeriod.day, nextDate);
|
|
|
|
_setAnchorFor(ReportPeriod.day, nextDate);
|
|
|
|
if (!shouldRefresh && selectedDate.value == nextDate) return Future.value();
|
|
|
|
selectedDate.value = nextDate;
|
|
|
|
return loadReport();
|
|
...
|
...
|
@@ -160,7 +172,7 @@ abstract class ReportPeriodLogic { |
|
|
|
ReportDateRangeConfig.firstWeekStart(),
|
|
|
|
ReportDateRangeConfig.lastWeekStart(),
|
|
|
|
);
|
|
|
|
_anchorDate = _anchorDateFor(ReportPeriod.week, nextWeekStart);
|
|
|
|
_setAnchorFor(ReportPeriod.week, nextWeekStart);
|
|
|
|
if (!shouldRefresh && weekStart == nextWeekStart) return Future.value();
|
|
|
|
selectedDate.value = nextWeekStart;
|
|
|
|
return loadReport();
|
|
...
|
...
|
@@ -177,7 +189,7 @@ abstract class ReportPeriodLogic { |
|
|
|
ReportDateRangeConfig.firstMonth(),
|
|
|
|
ReportDateRangeConfig.lastMonth(),
|
|
|
|
);
|
|
|
|
_anchorDate = _anchorDateFor(ReportPeriod.month, nextMonth);
|
|
|
|
_setAnchorFor(ReportPeriod.month, nextMonth);
|
|
|
|
if (!shouldRefresh && monthStart == nextMonth) return Future.value();
|
|
|
|
selectedDate.value = nextMonth;
|
|
|
|
return loadReport();
|
|
...
|
...
|
@@ -192,11 +204,11 @@ abstract class ReportPeriodLogic { |
|
|
|
ReportDateRangeConfig.firstYear(),
|
|
|
|
ReportDateRangeConfig.lastYear(),
|
|
|
|
);
|
|
|
|
final nextDate = DateTime(nextYear);
|
|
|
|
_setAnchorFor(ReportPeriod.year, nextDate);
|
|
|
|
if (!shouldRefresh && selectedDate.value.year == nextYear) {
|
|
|
|
return Future.value();
|
|
|
|
}
|
|
|
|
final nextDate = DateTime(nextYear);
|
|
|
|
_anchorDate = _anchorDateFor(ReportPeriod.year, nextDate);
|
|
|
|
selectedDate.value = nextDate;
|
|
|
|
return loadReport();
|
|
|
|
}
|
|
...
|
...
|
@@ -224,7 +236,7 @@ abstract class ReportPeriodLogic { |
|
|
|
Future<void> previousDay() {
|
|
|
|
final candidate = _previousCandidate;
|
|
|
|
if (_isBeforeRange(candidate)) return Future.value();
|
|
|
|
_anchorDate = _anchorDateFor(selectedPeriod.value, candidate);
|
|
|
|
_setAnchorFor(selectedPeriod.value, candidate);
|
|
|
|
selectedDate.value = candidate;
|
|
|
|
return loadReport();
|
|
|
|
}
|
|
...
|
...
|
@@ -232,7 +244,7 @@ abstract class ReportPeriodLogic { |
|
|
|
Future<void> nextDay() {
|
|
|
|
final candidate = _nextCandidate;
|
|
|
|
if (_isAfterRange(candidate)) return Future.value();
|
|
|
|
_anchorDate = _anchorDateFor(selectedPeriod.value, candidate);
|
|
|
|
_setAnchorFor(selectedPeriod.value, candidate);
|
|
|
|
selectedDate.value = candidate;
|
|
|
|
return loadReport();
|
|
|
|
}
|
|
...
|
...
|
@@ -241,19 +253,60 @@ abstract class ReportPeriodLogic { |
|
|
|
|
|
|
|
void dispose() {}
|
|
|
|
|
|
|
|
DateTime _anchorDateFor(ReportPeriod period, DateTime date) {
|
|
|
|
void _setAnchorFor(
|
|
|
|
ReportPeriod period,
|
|
|
|
DateTime date, {
|
|
|
|
DateTime? sourceMonth,
|
|
|
|
}) {
|
|
|
|
final normalizedPeriod = normalizePeriod(period);
|
|
|
|
final normalizedDate = normalizeDate(normalizedPeriod, date);
|
|
|
|
return switch (normalizedPeriod) {
|
|
|
|
ReportPeriod.day => normalizedDate,
|
|
|
|
ReportPeriod.week => ReportDateRangeConfig.clampDate(
|
|
|
|
normalizedDate.add(const Duration(days: 3)),
|
|
|
|
final weekSourceMonth =
|
|
|
|
DateTime(normalizedDate.year, normalizedDate.month);
|
|
|
|
_anchor = switch (normalizedPeriod) {
|
|
|
|
ReportPeriod.day => _ReportPeriodAnchor(date: normalizedDate),
|
|
|
|
ReportPeriod.week => _ReportPeriodAnchor(
|
|
|
|
date: ReportDateRangeConfig.clampDate(
|
|
|
|
normalizedDate.add(const Duration(days: 6)),
|
|
|
|
),
|
|
|
|
sourceMonth: sourceMonth ?? weekSourceMonth,
|
|
|
|
),
|
|
|
|
ReportPeriod.month => _ReportPeriodAnchor(
|
|
|
|
date: ReportDateRangeConfig.clampDate(
|
|
|
|
_lastDayOfMonth(normalizedDate),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
ReportPeriod.year => _ReportPeriodAnchor(
|
|
|
|
date: ReportDateRangeConfig.clampDate(
|
|
|
|
_lastDayOfYear(normalizedDate),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
ReportPeriod.month => normalizedDate,
|
|
|
|
ReportPeriod.year => normalizedDate,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
DateTime _dateForPeriodTransition(ReportPeriod nextPeriod) {
|
|
|
|
final currentPeriod = selectedPeriod.value;
|
|
|
|
|
|
|
|
if (currentPeriod == ReportPeriod.week &&
|
|
|
|
nextPeriod == ReportPeriod.month) {
|
|
|
|
// Preserve the source month when entering from a month. A directly
|
|
|
|
// selected week uses the month containing its Monday as the source.
|
|
|
|
return normalizeDate(nextPeriod, _anchor.sourceMonth ?? weekStart);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currentPeriod == ReportPeriod.week &&
|
|
|
|
nextPeriod == ReportPeriod.year) {
|
|
|
|
// A cross-year week belongs to the year containing its Monday.
|
|
|
|
return normalizeDate(nextPeriod, weekStart);
|
|
|
|
}
|
|
|
|
|
|
|
|
return normalizeDate(nextPeriod, _anchor.date);
|
|
|
|
}
|
|
|
|
|
|
|
|
DateTime _lastDayOfYear(DateTime date) => DateTime(date.year + 1, 1, 0);
|
|
|
|
|
|
|
|
DateTime _lastDayOfMonth(DateTime date) =>
|
|
|
|
DateTime(date.year, date.month + 1, 0);
|
|
|
|
|
|
|
|
DateTime get _previousCandidate => switch (selectedPeriod.value) {
|
|
|
|
ReportPeriod.year => DateTime(selectedDate.value.year - 1),
|
|
|
|
ReportPeriod.month =>
|
...
|
...
|
|