Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions lib/application/app_clock.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const bool kEnableTestingDateOverride = bool.fromEnvironment(
'ENABLE_TESTING_DATE',
defaultValue: false,
);

class AppClock {
DateTime? _overrideDate;

DateTime now() {
final overrideDate = _overrideDate;
if (overrideDate == null) {
return DateTime.now();
}
final systemNow = DateTime.now();
return DateTime(
overrideDate.year,
overrideDate.month,
overrideDate.day,
systemNow.hour,
systemNow.minute,
systemNow.second,
systemNow.millisecond,
systemNow.microsecond,
);
}

DateTime? get overrideDate => _overrideDate;

void setOverrideDate(DateTime? value) {
if (value == null) {
_overrideDate = null;
return;
}
_overrideDate = DateTime(value.year, value.month, value.day);
}
}
18 changes: 18 additions & 0 deletions lib/application/app_dependencies.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:subctrl/application/app_clock.dart';
import 'package:subctrl/application/currencies/add_custom_currency_use_case.dart';
import 'package:subctrl/application/currencies/delete_custom_currency_use_case.dart';
import 'package:subctrl/application/currencies/get_currencies_use_case.dart';
Expand All @@ -19,11 +20,13 @@ import 'package:subctrl/application/settings/get_locale_code_use_case.dart';
import 'package:subctrl/application/settings/get_notification_reminder_offset_use_case.dart';
import 'package:subctrl/application/settings/get_notifications_enabled_use_case.dart';
import 'package:subctrl/application/settings/get_theme_preference_use_case.dart';
import 'package:subctrl/application/settings/get_testing_date_override_use_case.dart';
import 'package:subctrl/application/settings/set_base_currency_code_use_case.dart';
import 'package:subctrl/application/settings/set_currency_rates_auto_download_use_case.dart';
import 'package:subctrl/application/settings/set_locale_code_use_case.dart';
import 'package:subctrl/application/settings/set_notification_reminder_offset_use_case.dart';
import 'package:subctrl/application/settings/set_notifications_enabled_use_case.dart';
import 'package:subctrl/application/settings/set_testing_date_override_use_case.dart';
import 'package:subctrl/application/settings/set_theme_preference_use_case.dart';
import 'package:subctrl/application/subscriptions/add_subscription_use_case.dart';
import 'package:subctrl/application/subscriptions/delete_subscription_use_case.dart';
Expand Down Expand Up @@ -83,11 +86,14 @@ class AppDependencies {
required this.setNotificationsEnabledUseCase,
required this.getNotificationReminderOffsetUseCase,
required this.setNotificationReminderOffsetUseCase,
required this.getTestingDateOverrideUseCase,
required this.setTestingDateOverrideUseCase,
required this.getPendingNotificationsUseCase,
required this.cancelNotificationsUseCase,
required this.scheduleNotificationsUseCase,
required this.requestNotificationPermissionUseCase,
required this.openNotificationSettingsUseCase,
required this.appClock,
required YahooFinanceCurrencyClient yahooFinanceCurrencyClient,
}) : _yahooFinanceCurrencyClient = yahooFinanceCurrencyClient;

Expand All @@ -112,6 +118,7 @@ class AppDependencies {
yahooFinanceCurrencyClient: yahooFinanceClient,
currencyRepository: currencyRepository,
);
final appClock = AppClock();
final localNotificationsService = LocalNotificationsService();
final notificationPermissionService = NotificationPermissionService();

Expand All @@ -128,6 +135,7 @@ class AppDependencies {
),
refreshOverdueNextPaymentsUseCase: RefreshOverdueNextPaymentsUseCase(
subscriptionRepository,
nowProvider: appClock.now,
),
watchCurrenciesUseCase: WatchCurrenciesUseCase(currencyRepository),
getCurrenciesUseCase: GetCurrenciesUseCase(currencyRepository),
Expand Down Expand Up @@ -179,6 +187,12 @@ class AppDependencies {
GetNotificationReminderOffsetUseCase(settingsRepository),
setNotificationReminderOffsetUseCase:
SetNotificationReminderOffsetUseCase(settingsRepository),
getTestingDateOverrideUseCase: GetTestingDateOverrideUseCase(
settingsRepository,
),
setTestingDateOverrideUseCase: SetTestingDateOverrideUseCase(
settingsRepository,
),
getPendingNotificationsUseCase: GetPendingNotificationsUseCase(
localNotificationsService,
),
Expand All @@ -194,6 +208,7 @@ class AppDependencies {
openNotificationSettingsUseCase: OpenNotificationSettingsUseCase(
notificationPermissionService,
),
appClock: appClock,
yahooFinanceCurrencyClient: yahooFinanceClient,
);
}
Expand Down Expand Up @@ -235,12 +250,15 @@ class AppDependencies {
getNotificationReminderOffsetUseCase;
final SetNotificationReminderOffsetUseCase
setNotificationReminderOffsetUseCase;
final GetTestingDateOverrideUseCase getTestingDateOverrideUseCase;
final SetTestingDateOverrideUseCase setTestingDateOverrideUseCase;
final GetPendingNotificationsUseCase getPendingNotificationsUseCase;
final CancelNotificationsUseCase cancelNotificationsUseCase;
final ScheduleNotificationsUseCase scheduleNotificationsUseCase;
final RequestNotificationPermissionUseCase
requestNotificationPermissionUseCase;
final OpenNotificationSettingsUseCase openNotificationSettingsUseCase;
final AppClock appClock;

final YahooFinanceCurrencyClient _yahooFinanceCurrencyClient;

Expand Down
11 changes: 11 additions & 0 deletions lib/application/settings/get_testing_date_override_use_case.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:subctrl/domain/repositories/settings_repository.dart';

class GetTestingDateOverrideUseCase {
GetTestingDateOverrideUseCase(this._repository);

final SettingsRepository _repository;

Future<DateTime?> call() {
return _repository.getTestingDateOverride();
}
}
11 changes: 11 additions & 0 deletions lib/application/settings/set_testing_date_override_use_case.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:subctrl/domain/repositories/settings_repository.dart';

class SetTestingDateOverrideUseCase {
SetTestingDateOverrideUseCase(this._repository);

final SettingsRepository _repository;

Future<void> call(DateTime? value) {
return _repository.setTestingDateOverride(value);
}
}
4 changes: 4 additions & 0 deletions lib/domain/repositories/settings_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ abstract class SettingsRepository {
Future<String?> getNotificationReminderOffset();

Future<void> setNotificationReminderOffset(String value);

Future<DateTime?> getTestingDateOverride();

Future<void> setTestingDateOverride(DateTime? value);
}
21 changes: 21 additions & 0 deletions lib/infrastructure/repositories/drift_settings_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class DriftSettingsRepository implements SettingsRepository {
static const _notificationsEnabledKey = 'notifications_enabled';
static const _notificationReminderOffsetKey =
'notification_reminder_offset';
static const _testingDateOverrideKey = 'testing_date_override';

@override
Future<String?> getBaseCurrencyCode() {
Expand Down Expand Up @@ -85,4 +86,24 @@ class DriftSettingsRepository implements SettingsRepository {
Future<void> setNotificationReminderOffset(String value) {
return _dao.saveSetting(_notificationReminderOffsetKey, value);
}

@override
Future<DateTime?> getTestingDateOverride() async {
final stored = await _dao.getSetting(_testingDateOverrideKey);
if (stored == null || stored.isEmpty) {
return null;
}
return DateTime.tryParse(stored);
}

@override
Future<void> setTestingDateOverride(DateTime? value) {
final stored = value == null ? null : _formatDate(value);
return _dao.saveSetting(_testingDateOverrideKey, stored);
}

String _formatDate(DateTime value) {
final normalized = DateTime(value.year, value.month, value.day);
return normalized.toIso8601String().split('T').first;
}
}
41 changes: 39 additions & 2 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';

import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:subctrl/application/app_clock.dart';
import 'package:subctrl/application/app_dependencies.dart';
import 'package:subctrl/domain/entities/notification_reminder_option.dart';
import 'package:subctrl/presentation/l10n/app_localizations.dart';
Expand Down Expand Up @@ -33,6 +34,7 @@ class _SubctrlAppState extends State<SubctrlApp> {
bool _areNotificationsEnabled = false;
NotificationReminderOption _notificationReminderOption =
NotificationReminderOption.twoDaysBefore;
DateTime? _testingDateOverride;
late final AppDependencies _dependencies;

@override
Expand Down Expand Up @@ -66,6 +68,9 @@ class _SubctrlAppState extends State<SubctrlApp> {
.getNotificationsEnabledUseCase();
final storedReminder = await _dependencies
.getNotificationReminderOffsetUseCase();
final storedTestingDate = kEnableTestingDateOverride
? await _dependencies.getTestingDateOverrideUseCase()
: null;
final reminderOption = NotificationReminderOption.fromStorage(
storedReminder,
);
Expand All @@ -92,7 +97,13 @@ class _SubctrlAppState extends State<SubctrlApp> {
_isCurrencyRatesAutoDownloadEnabled = shouldDownloadRates;
_areNotificationsEnabled = shouldEnableNotifications;
_notificationReminderOption = reminderOption;
_testingDateOverride = storedTestingDate;
});
if (kEnableTestingDateOverride) {
_dependencies.appClock.setOverrideDate(storedTestingDate);
} else {
_dependencies.appClock.setOverrideDate(null);
}

if (shouldPersistBaseCurrency) {
await _dependencies.setBaseCurrencyCodeUseCase(storedBaseCurrency);
Expand Down Expand Up @@ -145,6 +156,14 @@ class _SubctrlAppState extends State<SubctrlApp> {
);
}

Future<void> _handleTestingDateOverrideChanged(DateTime? value) async {
setState(() {
_testingDateOverride = value;
});
_dependencies.appClock.setOverrideDate(value);
await _dependencies.setTestingDateOverrideUseCase(value);
}

@override
void dispose() {
_dependencies.dispose();
Expand Down Expand Up @@ -186,6 +205,9 @@ class _SubctrlAppState extends State<SubctrlApp> {
onNotificationsPreferenceChanged: _handleNotificationsPreferenceChanged,
notificationReminderOption: _notificationReminderOption,
onNotificationReminderChanged: _handleNotificationReminderChanged,
testingDateOverride: _testingDateOverride,
onTestingDateOverrideChanged: _handleTestingDateOverrideChanged,
nowProvider: _dependencies.appClock.now,
),
);
}
Expand All @@ -207,6 +229,9 @@ class HomeTabs extends StatelessWidget {
required this.onNotificationsPreferenceChanged,
required this.notificationReminderOption,
required this.onNotificationReminderChanged,
required this.testingDateOverride,
required this.onTestingDateOverrideChanged,
required this.nowProvider,
});

final AppDependencies dependencies;
Expand All @@ -222,6 +247,9 @@ class HomeTabs extends StatelessWidget {
final ValueChanged<bool> onNotificationsPreferenceChanged;
final NotificationReminderOption notificationReminderOption;
final NotificationReminderChangedCallback onNotificationReminderChanged;
final DateTime? testingDateOverride;
final TestingDateOverrideChangedCallback onTestingDateOverrideChanged;
final DateTime Function() nowProvider;

@override
Widget build(BuildContext context) {
Expand All @@ -231,6 +259,7 @@ class HomeTabs extends StatelessWidget {
final baseCurrencyKey = baseCurrencyCode ?? 'none';
final notificationsKey = notificationsEnabled ? 'on' : 'off';
final reminderKey = notificationReminderOption.storageValue;
final testingDateKey = testingDateOverride?.toIso8601String() ?? 'system';

return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
Expand All @@ -250,11 +279,11 @@ class HomeTabs extends StatelessWidget {
key: index == 0
? ValueKey(
'subscriptions-$themeKey-$localeKey-$baseCurrencyKey-'
'$notificationsKey-$reminderKey',
'$notificationsKey-$reminderKey-$testingDateKey',
)
: ValueKey(
'analytics-$themeKey-$localeKey-$baseCurrencyKey-'
'$notificationsKey-$reminderKey',
'$notificationsKey-$reminderKey-$testingDateKey',
),
builder: (context) {
switch (index) {
Expand All @@ -276,6 +305,10 @@ class HomeTabs extends StatelessWidget {
onNotificationsPreferenceChanged,
notificationReminderOption: notificationReminderOption,
onNotificationReminderChanged: onNotificationReminderChanged,
testingDateOverride: testingDateOverride,
onTestingDateOverrideChanged:
onTestingDateOverrideChanged,
nowProvider: nowProvider,
);
case 1:
return AnalyticsScreen(
Expand All @@ -295,6 +328,10 @@ class HomeTabs extends StatelessWidget {
onNotificationsPreferenceChanged,
notificationReminderOption: notificationReminderOption,
onNotificationReminderChanged: onNotificationReminderChanged,
testingDateOverride: testingDateOverride,
onTestingDateOverrideChanged:
onTestingDateOverrideChanged,
nowProvider: nowProvider,
);
default:
return const SizedBox.shrink();
Expand Down
14 changes: 14 additions & 0 deletions lib/presentation/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ class AppLocalizations {
'notificationReminderBody': '{name} will renew on {date}',
'settingsCurrenciesSection': 'Currencies',
'settingsCurrenciesManage': 'Manage currencies',
'settingsTestingSection': 'Testing',
'settingsTestingDateLabel': 'Testing date',
'settingsTestingDateSystem': 'System date',
'settingsTestingDateReset': 'Use system date',
'settingsTagsSection': 'Tags',
'settingsTagsManage': 'Manage tags',
'settingsTagsTitle': 'Tags',
Expand Down Expand Up @@ -208,6 +212,10 @@ class AppLocalizations {
'notificationReminderBody': '{name} — будет продлена {date}',
'settingsCurrenciesSection': 'Валюты',
'settingsCurrenciesManage': 'Управление списком',
'settingsTestingSection': 'Тестирование',
'settingsTestingDateLabel': 'Тестовая дата',
'settingsTestingDateSystem': 'Дата системы',
'settingsTestingDateReset': 'Использовать дату системы',
'settingsTagsSection': 'Теги',
'settingsTagsManage': 'Управление тегами',
'settingsTagsTitle': 'Теги',
Expand Down Expand Up @@ -398,6 +406,12 @@ class AppLocalizations {
String get settingsCurrenciesSection =>
_strings['settingsCurrenciesSection']!;
String get settingsCurrenciesManage => _strings['settingsCurrenciesManage']!;
String get settingsTestingSection => _strings['settingsTestingSection']!;
String get settingsTestingDateLabel => _strings['settingsTestingDateLabel']!;
String get settingsTestingDateSystem =>
_strings['settingsTestingDateSystem']!;
String get settingsTestingDateReset =>
_strings['settingsTestingDateReset']!;
String get settingsTagsSection => _strings['settingsTagsSection']!;
String get settingsTagsManage => _strings['settingsTagsManage']!;
String get settingsTagsTitle => _strings['settingsTagsTitle']!;
Expand Down
Loading
Loading