diff --git a/lib/data/shared_prefs_service.dart b/lib/data/shared_prefs_service.dart index 3bb6dfb..57f947d 100644 --- a/lib/data/shared_prefs_service.dart +++ b/lib/data/shared_prefs_service.dart @@ -24,6 +24,7 @@ class UserPreferencesService { static const caffeineKey = "caffeine"; static const hapticsKey = "haptics"; + static const autostartTimerKey = "autostartTimer"; static const volumeActionKey = "volumeAction"; static const localeKey = "locale"; @@ -127,6 +128,9 @@ class UserPreferencesService { bool get haptics => _sharedPreferences.getBool(hapticsKey) ?? true; set haptics(bool value) => _sharedPreferences.setBool(hapticsKey, value); + bool get autostartTimer => _sharedPreferences.getBool(autostartTimerKey) ?? true; + set autostartTimer(bool value) => _sharedPreferences.setBool(autostartTimerKey, value); + VolumeAction get volumeAction => VolumeAction.values.firstWhere( (e) => e.toString() == _sharedPreferences.getString(volumeActionKey), orElse: () => VolumeAction.shutter, diff --git a/lib/interactors/settings_interactor.dart b/lib/interactors/settings_interactor.dart index 8f74dd2..d5669c9 100644 --- a/lib/interactors/settings_interactor.dart +++ b/lib/interactors/settings_interactor.dart @@ -21,8 +21,7 @@ class SettingsInteractor { void setCameraEvCalibration(double value) => _userPreferencesService.cameraEvCalibration = value; double get lightSensorEvCalibration => _userPreferencesService.lightSensorEvCalibration; - void setLightSensorEvCalibration(double value) => - _userPreferencesService.lightSensorEvCalibration = value; + void setLightSensorEvCalibration(double value) => _userPreferencesService.lightSensorEvCalibration = value; bool get isCaffeineEnabled => _userPreferencesService.caffeine; Future enableCaffeine(bool enable) async { @@ -31,12 +30,15 @@ class SettingsInteractor { }); } + bool get isAutostartTimerEnabled => _userPreferencesService.autostartTimer; + void enableAutostartTimer(bool enable) => _userPreferencesService.autostartTimer = enable; + Future disableVolumeHandling() async { await _volumeEventsService.setVolumeHandling(false); } + Future restoreVolumeHandling() async { - await _volumeEventsService - .setVolumeHandling(_userPreferencesService.volumeAction != VolumeAction.none); + await _volumeEventsService.setVolumeHandling(_userPreferencesService.volumeAction != VolumeAction.none); } VolumeAction get volumeAction => _userPreferencesService.volumeAction; diff --git a/lib/interactors/timer_interactor.dart b/lib/interactors/timer_interactor.dart index 35ed8b9..65f1b59 100644 --- a/lib/interactors/timer_interactor.dart +++ b/lib/interactors/timer_interactor.dart @@ -19,4 +19,6 @@ class TimerInteractor { Future responseVibration() async { if (_userPreferencesService.haptics) await _hapticsService.responseVibration(); } + + bool get isAutostartTimerEnabled => _userPreferencesService.autostartTimer; } diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 4d0d9c0..34247a6 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -72,6 +72,7 @@ "general": "General", "keepScreenOn": "Keep screen on", "haptics": "Haptics", + "autostartTimer": "Autostart timer", "volumeKeysAction": "Shutter by volume keys", "language": "Language", "chooseLanguage": "Choose language", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 96debfd..3d0a684 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -72,6 +72,7 @@ "general": "Général", "keepScreenOn": "Garder l'écran allumé", "haptics": "Haptiques", + "autostartTimer": "Minuterie de démarrage automatique", "volumeKeysAction": "Obturateur par boutons de volume", "language": "Langue", "chooseLanguage": "Choisissez la langue", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 4c81b02..d005f77 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -72,6 +72,7 @@ "general": "Общие", "keepScreenOn": "Запрет блокировки", "haptics": "Вибрация", + "autostartTimer": "Автозапуск таймера", "volumeKeysAction": "Затвор по кнопкам громкости", "language": "Язык", "chooseLanguage": "Выберите язык", diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 10ab81f..aa17748 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -72,6 +72,7 @@ "general": "通用", "keepScreenOn": "保持屏幕常亮", "haptics": "震动", + "autostartTimer": "自动启动定时器", "volumeKeysAction": "音量键快门", "language": "语言", "chooseLanguage": "选择语言", diff --git a/lib/screens/settings/components/general/components/timer/bloc_list_tile_timer.dart b/lib/screens/settings/components/general/components/timer/bloc_list_tile_timer.dart new file mode 100644 index 0000000..f67cebd --- /dev/null +++ b/lib/screens/settings/components/general/components/timer/bloc_list_tile_timer.dart @@ -0,0 +1,15 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lightmeter/interactors/settings_interactor.dart'; + +class TimerListTileBloc extends Cubit { + final SettingsInteractor _settingsInteractor; + + TimerListTileBloc( + this._settingsInteractor, + ) : super(_settingsInteractor.isAutostartTimerEnabled); + + void onChanged(bool value) { + _settingsInteractor.enableAutostartTimer(value); + emit(value); + } +} diff --git a/lib/screens/settings/components/general/components/timer/provider_list_tile_timer.dart b/lib/screens/settings/components/general/components/timer/provider_list_tile_timer.dart new file mode 100644 index 0000000..e359949 --- /dev/null +++ b/lib/screens/settings/components/general/components/timer/provider_list_tile_timer.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:lightmeter/screens/settings/components/general/components/timer/bloc_list_tile_timer.dart'; +import 'package:lightmeter/screens/settings/components/general/components/timer/widget_list_tile_timer.dart'; +import 'package:lightmeter/screens/settings/flow_settings.dart'; + +class TimerListTileProvider extends StatelessWidget { + const TimerListTileProvider({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => TimerListTileBloc(SettingsInteractorProvider.of(context)), + child: const TimerListTile(), + ); + } +} diff --git a/lib/screens/settings/components/general/components/timer/widget_list_tile_timer.dart b/lib/screens/settings/components/general/components/timer/widget_list_tile_timer.dart new file mode 100644 index 0000000..0fdaf52 --- /dev/null +++ b/lib/screens/settings/components/general/components/timer/widget_list_tile_timer.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/res/dimens.dart'; +import 'package:lightmeter/screens/settings/components/general/components/timer/bloc_list_tile_timer.dart'; +import 'package:lightmeter/screens/settings/components/shared/disable/widget_disable.dart'; +import 'package:lightmeter/utils/context_utils.dart'; + +class TimerListTile extends StatelessWidget { + const TimerListTile({super.key}); + + @override + Widget build(BuildContext context) { + return Disable( + disable: !context.isPro, + child: BlocBuilder( + builder: (context, state) => SwitchListTile( + secondary: const Icon(Icons.timer_outlined), + title: Text(S.of(context).autostartTimer), + value: state && context.isPro, + onChanged: context.read().onChanged, + contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM), + ), + ), + ); + } +} diff --git a/lib/screens/settings/components/general/widget_settings_section_general.dart b/lib/screens/settings/components/general/widget_settings_section_general.dart index bc123d7..278d342 100644 --- a/lib/screens/settings/components/general/widget_settings_section_general.dart +++ b/lib/screens/settings/components/general/widget_settings_section_general.dart @@ -5,6 +5,7 @@ import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/screens/settings/components/general/components/caffeine/provider_list_tile_caffeine.dart'; import 'package:lightmeter/screens/settings/components/general/components/haptics/provider_list_tile_haptics.dart'; import 'package:lightmeter/screens/settings/components/general/components/language/widget_list_tile_language.dart'; +import 'package:lightmeter/screens/settings/components/general/components/timer/provider_list_tile_timer.dart'; import 'package:lightmeter/screens/settings/components/general/components/volume_actions/provider_list_tile_volume_actions.dart'; import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart'; @@ -18,6 +19,7 @@ class GeneralSettingsSection extends StatelessWidget { children: [ const CaffeineListTileProvider(), const HapticsListTileProvider(), + const TimerListTileProvider(), if (Platform.isAndroid) const VolumeActionsListTileProvider(), const LanguageListTile(), ], diff --git a/lib/screens/timer/bloc_timer.dart b/lib/screens/timer/bloc_timer.dart index 5a0a0e3..93f1083 100644 --- a/lib/screens/timer/bloc_timer.dart +++ b/lib/screens/timer/bloc_timer.dart @@ -14,6 +14,8 @@ class TimerBloc extends Bloc { on(_onTimerEnded); on(_onStopTimer); on(_onResetTimer); + + if (_timerInteractor.isAutostartTimerEnabled) add(const StartTimerEvent()); } Future _onStartTimer(StartTimerEvent _, Emitter emit) async { diff --git a/test/data/shared_prefs_service_test.dart b/test/data/shared_prefs_service_test.dart index 269d002..f4b480b 100644 --- a/test/data/shared_prefs_service_test.dart +++ b/test/data/shared_prefs_service_test.dart @@ -326,6 +326,26 @@ void main() { verify(() => sharedPreferences.setBool(UserPreferencesService.hapticsKey, false)).called(1); }); }); + + group('autostartTimer', () { + test('get default', () { + when(() => sharedPreferences.getBool(UserPreferencesService.autostartTimerKey)).thenReturn(null); + expect(service.autostartTimer, true); + }); + + test('get', () { + when(() => sharedPreferences.getBool(UserPreferencesService.autostartTimerKey)).thenReturn(true); + expect(service.autostartTimer, true); + }); + + test('set', () { + when(() => sharedPreferences.setBool(UserPreferencesService.autostartTimerKey, false)) + .thenAnswer((_) => Future.value(true)); + service.autostartTimer = false; + verify(() => sharedPreferences.setBool(UserPreferencesService.autostartTimerKey, false)).called(1); + }); + }); + group('volumeAction', () { test('get default', () { when(() => sharedPreferences.getBool(UserPreferencesService.volumeActionKey)).thenReturn(null); diff --git a/test/interactors/settings_interactor_test.dart b/test/interactors/settings_interactor_test.dart index 03437dc..982345a 100644 --- a/test/interactors/settings_interactor_test.dart +++ b/test/interactors/settings_interactor_test.dart @@ -85,6 +85,28 @@ void main() { }, ); + group( + 'AutostartTimer', + () { + test('isAutostartTimerEnabled', () { + when(() => mockUserPreferencesService.autostartTimer).thenReturn(true); + expect(interactor.isAutostartTimerEnabled, true); + when(() => mockUserPreferencesService.autostartTimer).thenReturn(false); + expect(interactor.isAutostartTimerEnabled, false); + verify(() => mockUserPreferencesService.autostartTimer).called(2); + }); + + test('enableAutostartTimer(true)', () { + when(() => mockUserPreferencesService.autostartTimer = true).thenReturn(true); + interactor.enableAutostartTimer(true); + verify(() => mockUserPreferencesService.autostartTimer = true).called(1); + when(() => mockUserPreferencesService.autostartTimer = true).thenReturn(false); + interactor.enableAutostartTimer(false); + verify(() => mockUserPreferencesService.autostartTimer = false).called(1); + }); + }, + ); + group( 'Volume action', () { @@ -123,8 +145,7 @@ void main() { }); test('setVolumeAction(VolumeAction.shutter)', () async { - when(() => mockUserPreferencesService.volumeAction = VolumeAction.shutter) - .thenReturn(VolumeAction.shutter); + when(() => mockUserPreferencesService.volumeAction = VolumeAction.shutter).thenReturn(VolumeAction.shutter); when(() => mockVolumeEventsService.setVolumeHandling(true)).thenAnswer((_) async => true); expectLater(interactor.setVolumeAction(VolumeAction.shutter), isA>()); verify(() => mockVolumeEventsService.setVolumeHandling(true)).called(1); @@ -132,8 +153,7 @@ void main() { }); test('setVolumeAction(VolumeAction.none)', () async { - when(() => mockUserPreferencesService.volumeAction = VolumeAction.none) - .thenReturn(VolumeAction.none); + when(() => mockUserPreferencesService.volumeAction = VolumeAction.none).thenReturn(VolumeAction.none); when(() => mockVolumeEventsService.setVolumeHandling(false)).thenAnswer((_) async => false); expectLater(interactor.setVolumeAction(VolumeAction.none), isA>()); verify(() => mockVolumeEventsService.setVolumeHandling(false)).called(1); diff --git a/test/interactors/timer_interactor_test.dart b/test/interactors/timer_interactor_test.dart index 1116b74..3c153e9 100644 --- a/test/interactors/timer_interactor_test.dart +++ b/test/interactors/timer_interactor_test.dart @@ -60,4 +60,15 @@ void main() { }); }, ); + + group( + 'AutostartTimer', + () { + test('isAutostartTimerEnabled', () { + when(() => mockUserPreferencesService.autostartTimer).thenReturn(true); + expect(interactor.isAutostartTimerEnabled, true); + verify(() => mockUserPreferencesService.autostartTimer).called(1); + }); + }, + ); } diff --git a/test/screens/timer/bloc_timer_test.dart b/test/screens/timer/bloc_timer_test.dart index 80c7349..845edb0 100644 --- a/test/screens/timer/bloc_timer_test.dart +++ b/test/screens/timer/bloc_timer_test.dart @@ -9,32 +9,40 @@ import 'package:test/test.dart'; class _MockTimerInteractor extends Mock implements TimerInteractor {} void main() { - late _MockTimerInteractor meteringInteractor; - late TimerBloc bloc; + late _MockTimerInteractor timerInteractor; - setUp(() { - meteringInteractor = _MockTimerInteractor(); - when(meteringInteractor.quickVibration).thenAnswer((_) async {}); - when(meteringInteractor.responseVibration).thenAnswer((_) async {}); - - bloc = TimerBloc(meteringInteractor, const Duration(seconds: 1)); - }); - - tearDown(() { - bloc.close(); + setUpAll(() { + timerInteractor = _MockTimerInteractor(); + when(() => timerInteractor.isAutostartTimerEnabled).thenReturn(true); + when(timerInteractor.quickVibration).thenAnswer((_) async {}); + when(timerInteractor.responseVibration).thenAnswer((_) async {}); }); + blocTest( + 'Autostart', + build: () => TimerBloc(timerInteractor, const Duration(seconds: 1)), + verify: (_) { + verify(() => timerInteractor.quickVibration()).called(1); + }, + expect: () => [ + isA(), + ], + ); + blocTest( 'Start -> wait till the end -> reset', - build: () => bloc, - act: (bloc) async { + build: () => TimerBloc(timerInteractor, const Duration(seconds: 1)), + setUp: () { + when(() => timerInteractor.isAutostartTimerEnabled).thenReturn(false); + }, + act: (bloc) { bloc.add(const StartTimerEvent()); bloc.add(const TimerEndedEvent()); bloc.add(const ResetTimerEvent()); }, verify: (_) { - verify(() => meteringInteractor.quickVibration()).called(1); - verify(() => meteringInteractor.responseVibration()).called(1); + verify(() => timerInteractor.quickVibration()).called(1); + verify(() => timerInteractor.responseVibration()).called(1); }, expect: () => [ isA(), @@ -45,7 +53,10 @@ void main() { blocTest( 'Start -> stop -> start -> wait till the end', - build: () => bloc, + build: () => TimerBloc(timerInteractor, const Duration(seconds: 1)), + setUp: () { + when(() => timerInteractor.isAutostartTimerEnabled).thenReturn(false); + }, act: (bloc) async { bloc.add(const StartTimerEvent()); bloc.add(const StopTimerEvent()); @@ -53,8 +64,8 @@ void main() { bloc.add(const TimerEndedEvent()); }, verify: (_) { - verify(() => meteringInteractor.quickVibration()).called(3); - verify(() => meteringInteractor.responseVibration()).called(1); + verify(() => timerInteractor.quickVibration()).called(3); + verify(() => timerInteractor.responseVibration()).called(1); }, expect: () => [ isA(), diff --git a/test/screens/timer/screen_timer_golden_test.dart b/test/screens/timer/screen_timer_golden_test.dart index 142f3c2..41b682a 100644 --- a/test/screens/timer/screen_timer_golden_test.dart +++ b/test/screens/timer/screen_timer_golden_test.dart @@ -3,6 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:lightmeter/data/models/exposure_pair.dart'; import 'package:lightmeter/data/models/theme_type.dart'; +import 'package:lightmeter/data/shared_prefs_service.dart'; import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/shared/animated_circular_button/widget_button_circular_animated.dart'; @@ -82,7 +83,9 @@ void main() { } setUpAll(() { - SharedPreferences.setMockInitialValues({}); + SharedPreferences.setMockInitialValues({ + UserPreferencesService.autostartTimerKey: false, + }); }); testGoldens(