From e20b9f76b9f4abfa3ab3e02bfccc4a38c634c498 Mon Sep 17 00:00:00 2001 From: Vadim Date: Mon, 3 Jul 2023 17:19:48 +0200 Subject: [PATCH] implemented `VolumeKeysListener` (wip) --- .../com/vodemn/lightmeter/MainActivity.kt | 2 +- lib/data/models/volume_action.dart | 3 ++ lib/data/shared_prefs_service.dart | 15 ++++-- lib/interactors/metering_interactor.dart | 12 +++++ lib/interactors/settings_interactor.dart | 11 +++++ lib/l10n/intl_en.arb | 3 ++ lib/l10n/intl_fr.arb | 3 ++ lib/l10n/intl_ru.arb | 3 ++ lib/providers.dart | 24 ++++++---- lib/screens/metering/bloc_metering.dart | 9 ++++ .../bloc_container_camera.dart | 16 +++++++ .../listener_volume_keys.dart | 28 +++++++++++ lib/screens/metering/flow_metering.dart | 2 + .../bloc_list_tile_volume_actions.dart | 16 +++++++ .../provider_list_tile_volume_actions.dart | 19 ++++++++ .../widget_list_tile_volume_actions.dart | 48 +++++++++++++++++++ .../widget_settings_section_general.dart | 12 +++-- lib/screens/settings/flow_settings.dart | 2 + 18 files changed, 210 insertions(+), 18 deletions(-) create mode 100644 lib/data/models/volume_action.dart create mode 100644 lib/screens/metering/components/shared/volume_keys_listener/listener_volume_keys.dart create mode 100644 lib/screens/settings/components/general/components/volume_actions/bloc_list_tile_volume_actions.dart create mode 100644 lib/screens/settings/components/general/components/volume_actions/provider_list_tile_volume_actions.dart create mode 100644 lib/screens/settings/components/general/components/volume_actions/widget_list_tile_volume_actions.dart diff --git a/android/app/src/main/kotlin/com/vodemn/lightmeter/MainActivity.kt b/android/app/src/main/kotlin/com/vodemn/lightmeter/MainActivity.kt index c53ff0a..a0cbe97 100644 --- a/android/app/src/main/kotlin/com/vodemn/lightmeter/MainActivity.kt +++ b/android/app/src/main/kotlin/com/vodemn/lightmeter/MainActivity.kt @@ -52,7 +52,7 @@ class MainActivity : FlutterActivity() { when (call.method) { "setVolumeHandling" -> { handleVolume = call.arguments as Boolean - result.success(true) + result.success(handleVolume) } else -> result.notImplemented() } diff --git a/lib/data/models/volume_action.dart b/lib/data/models/volume_action.dart new file mode 100644 index 0000000..0ab98e8 --- /dev/null +++ b/lib/data/models/volume_action.dart @@ -0,0 +1,3 @@ +enum VolumeAction { shutter, zoom, none } + +enum VolumeKey { up, down } diff --git a/lib/data/shared_prefs_service.dart b/lib/data/shared_prefs_service.dart index 8fbb6af..76f9045 100644 --- a/lib/data/shared_prefs_service.dart +++ b/lib/data/shared_prefs_service.dart @@ -6,6 +6,7 @@ import 'package:lightmeter/data/models/film.dart'; import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:lightmeter/data/models/supported_locale.dart'; import 'package:lightmeter/data/models/theme_type.dart'; +import 'package:lightmeter/data/models/volume_action.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -22,6 +23,7 @@ class UserPreferencesService { static const caffeineKey = "caffeine"; static const hapticsKey = "haptics"; + static const volumeActionKey = "volumeAction"; static const localeKey = "locale"; static const themeTypeKey = "themeType"; @@ -82,9 +84,6 @@ class UserPreferencesService { EvSourceType.values[_sharedPreferences.getInt(evSourceTypeKey) ?? 0]; set evSourceType(EvSourceType value) => _sharedPreferences.setInt(evSourceTypeKey, value.index); - bool get caffeine => _sharedPreferences.getBool(caffeineKey) ?? false; - set caffeine(bool value) => _sharedPreferences.setBool(caffeineKey, value); - StopType get stopType => StopType.values[_sharedPreferences.getInt(stopTypeKey) ?? 2]; set stopType(StopType value) => _sharedPreferences.setInt(stopTypeKey, value.index); @@ -105,9 +104,19 @@ class UserPreferencesService { set meteringScreenLayout(MeteringScreenLayoutConfig value) => _sharedPreferences.setString(meteringScreenLayoutKey, json.encode(value.toJson())); + bool get caffeine => _sharedPreferences.getBool(caffeineKey) ?? false; + set caffeine(bool value) => _sharedPreferences.setBool(caffeineKey, value); + bool get haptics => _sharedPreferences.getBool(hapticsKey) ?? true; set haptics(bool value) => _sharedPreferences.setBool(hapticsKey, value); + VolumeAction get volumeAction => VolumeAction.values.firstWhere( + (e) => e.toString() == _sharedPreferences.getString(volumeActionKey), + orElse: () => VolumeAction.shutter, + ); + set volumeAction(VolumeAction value) => + _sharedPreferences.setString(volumeActionKey, value.toString()); + SupportedLocale get locale => SupportedLocale.values.firstWhere( (e) => e.toString() == _sharedPreferences.getString(localeKey), orElse: () => SupportedLocale.en, diff --git a/lib/interactors/metering_interactor.dart b/lib/interactors/metering_interactor.dart index ca94f8e..53e0538 100644 --- a/lib/interactors/metering_interactor.dart +++ b/lib/interactors/metering_interactor.dart @@ -5,8 +5,10 @@ import 'package:lightmeter/data/caffeine_service.dart'; import 'package:lightmeter/data/haptics_service.dart'; import 'package:lightmeter/data/light_sensor_service.dart'; import 'package:lightmeter/data/models/film.dart'; +import 'package:lightmeter/data/models/volume_action.dart'; import 'package:lightmeter/data/permissions_service.dart'; import 'package:lightmeter/data/shared_prefs_service.dart'; +import 'package:lightmeter/data/volume_events_service.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -16,6 +18,7 @@ class MeteringInteractor { final HapticsService _hapticsService; final PermissionsService _permissionsService; final LightSensorService _lightSensorService; + final VolumeEventsService _volumeEventsService; MeteringInteractor( this._userPreferencesService, @@ -23,10 +26,13 @@ class MeteringInteractor { this._hapticsService, this._permissionsService, this._lightSensorService, + this._volumeEventsService, ) { if (_userPreferencesService.caffeine) { _caffeineService.keepScreenOn(true); } + _volumeEventsService + .setVolumeHandling(_userPreferencesService.volumeAction != VolumeAction.none); } double get cameraEvCalibration => _userPreferencesService.cameraEvCalibration; @@ -42,6 +48,12 @@ class MeteringInteractor { Film get film => _userPreferencesService.film; set film(Film value) => _userPreferencesService.film = value; + VolumeAction get volumeAction => _userPreferencesService.volumeAction; + Stream volumeKeysStream() => _volumeEventsService + .volumeButtonsEventStream() + .where((event) => event == 24 || event == 25) + .map((event) => event == 24 ? VolumeKey.up : VolumeKey.down); + /// Executes vibration if haptics are enabled in settings Future quickVibration() async { if (_userPreferencesService.haptics) await _hapticsService.quickVibration(); diff --git a/lib/interactors/settings_interactor.dart b/lib/interactors/settings_interactor.dart index db99a7a..7bb6f7b 100644 --- a/lib/interactors/settings_interactor.dart +++ b/lib/interactors/settings_interactor.dart @@ -1,16 +1,20 @@ import 'package:lightmeter/data/caffeine_service.dart'; import 'package:lightmeter/data/haptics_service.dart'; +import 'package:lightmeter/data/models/volume_action.dart'; import 'package:lightmeter/data/shared_prefs_service.dart'; +import 'package:lightmeter/data/volume_events_service.dart'; class SettingsInteractor { final UserPreferencesService _userPreferencesService; final CaffeineService _caffeineService; final HapticsService _hapticsService; + final VolumeEventsService _volumeEventsService; const SettingsInteractor( this._userPreferencesService, this._caffeineService, this._hapticsService, + this._volumeEventsService, ); double get cameraEvCalibration => _userPreferencesService.cameraEvCalibration; @@ -27,6 +31,13 @@ class SettingsInteractor { }); } + VolumeAction get volumeAction => _userPreferencesService.volumeAction; + Future setVolumeAction(VolumeAction value) async { + /// If user selects `VolumeAction.volume` we allow system to handle key events + await _volumeEventsService.setVolumeHandling(value != VolumeAction.none); + _userPreferencesService.volumeAction = value; + } + bool get isHapticsEnabled => _userPreferencesService.haptics; void enableHaptics(bool enable) { _userPreferencesService.haptics = enable; diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index c404d33..61a682d 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -56,6 +56,9 @@ "general": "General", "keepScreenOn": "Keep screen on", "haptics": "Haptics", + "volumeKeysAction": "Volume keys action", + "shutter": "Shutter", + "zoom": "Zoom", "language": "Language", "chooseLanguage": "Choose language", "theme": "Theme", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 5102a59..46dff93 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -56,6 +56,9 @@ "general": "Général", "keepScreenOn": "Garder l'écran allumé", "haptics": "Haptiques", + "volumeKeysAction": "Action des touches de volume", + "shutter": "Obturateur", + "zoom": "Zoom", "language": "Langue", "chooseLanguage": "Choisissez la langue", "theme": "Thème", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index ca746dd..d61529f 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -56,6 +56,9 @@ "general": "Общие", "keepScreenOn": "Запрет блокировки", "haptics": "Вибрация", + "volumeKeysAction": "Кнопки регулировки громкости", + "shutter": "Затвор", + "zoom": "Зум", "language": "Язык", "chooseLanguage": "Выберите язык", "theme": "Тема", diff --git a/lib/providers.dart b/lib/providers.dart index 7805e69..cede377 100644 --- a/lib/providers.dart +++ b/lib/providers.dart @@ -6,6 +6,7 @@ import 'package:lightmeter/data/haptics_service.dart'; import 'package:lightmeter/data/light_sensor_service.dart'; import 'package:lightmeter/data/permissions_service.dart'; import 'package:lightmeter/data/shared_prefs_service.dart'; +import 'package:lightmeter/data/volume_events_service.dart'; import 'package:lightmeter/environment.dart'; import 'package:lightmeter/providers/equipment_profile_provider.dart'; import 'package:lightmeter/providers/ev_source_type_provider.dart'; @@ -41,16 +42,19 @@ class LightmeterProviders extends StatelessWidget { data: const CaffeineService(), child: InheritedWidgetBase( data: const HapticsService(), - child: InheritedWidgetBase( - data: const PermissionsService(), - child: MeteringScreenLayoutProvider( - child: StopTypeProvider( - child: EquipmentProfileProvider( - child: EvSourceTypeProvider( - child: SupportedLocaleProvider( - child: ThemeProvider( - child: Builder( - builder: (context) => builder(context, true), + child: InheritedWidgetBase( + data: const VolumeEventsService(), + child: InheritedWidgetBase( + data: const PermissionsService(), + child: MeteringScreenLayoutProvider( + child: StopTypeProvider( + child: EquipmentProfileProvider( + child: EvSourceTypeProvider( + child: SupportedLocaleProvider( + child: ThemeProvider( + child: Builder( + builder: (context) => builder(context, true), + ), ), ), ), diff --git a/lib/screens/metering/bloc_metering.dart b/lib/screens/metering/bloc_metering.dart index 042991a..5228165 100644 --- a/lib/screens/metering/bloc_metering.dart +++ b/lib/screens/metering/bloc_metering.dart @@ -4,12 +4,14 @@ import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lightmeter/data/models/film.dart'; +import 'package:lightmeter/data/models/volume_action.dart'; import 'package:lightmeter/interactors/metering_interactor.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' as communication_events; import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' as communication_states; +import 'package:lightmeter/screens/metering/components/shared/volume_keys_listener/listener_volume_keys.dart'; import 'package:lightmeter/screens/metering/event_metering.dart'; import 'package:lightmeter/screens/metering/state_metering.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; @@ -19,6 +21,12 @@ class MeteringBloc extends Bloc { final MeteringCommunicationBloc _communicationBloc; late final StreamSubscription _communicationSubscription; + late final VolumeKeysListener _volumeKeysListener = VolumeKeysListener( + _meteringInteractor, + action: VolumeAction.shutter, + onKey: (value) => add(const MeasureEvent()), + ); + MeteringBloc( this._meteringInteractor, this._communicationBloc, @@ -64,6 +72,7 @@ class MeteringBloc extends Bloc { @override Future close() async { + await _volumeKeysListener.dispose(); await _communicationSubscription.cancel(); return super.close(); } diff --git a/lib/screens/metering/components/camera_container/bloc_container_camera.dart b/lib/screens/metering/components/camera_container/bloc_container_camera.dart index 2bb8ed1..a23ac20 100644 --- a/lib/screens/metering/components/camera_container/bloc_container_camera.dart +++ b/lib/screens/metering/components/camera_container/bloc_container_camera.dart @@ -8,6 +8,7 @@ import 'package:camera/camera.dart'; import 'package:exif/exif.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lightmeter/data/models/volume_action.dart'; import 'package:lightmeter/interactors/metering_interactor.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' @@ -18,6 +19,7 @@ import 'package:lightmeter/screens/metering/components/camera_container/event_co import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart'; import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart'; import 'package:lightmeter/screens/metering/components/shared/ev_source_base/bloc_base_ev_source.dart'; +import 'package:lightmeter/screens/metering/components/shared/volume_keys_listener/listener_volume_keys.dart'; import 'package:lightmeter/utils/log_2.dart'; class CameraContainerBloc extends EvSourceBlocBase { @@ -36,6 +38,19 @@ class CameraContainerBloc extends EvSourceBlocBase close() async { WidgetsBinding.instance.removeObserver(_observer); + _volumeKeysListener.dispose(); unawaited(_cameraController?.dispose().then((_) => _cameraController = null)); communicationBloc.add(communication_event.MeteringEndedEvent(_ev100)); return super.close(); diff --git a/lib/screens/metering/components/shared/volume_keys_listener/listener_volume_keys.dart b/lib/screens/metering/components/shared/volume_keys_listener/listener_volume_keys.dart new file mode 100644 index 0000000..a6b6fe2 --- /dev/null +++ b/lib/screens/metering/components/shared/volume_keys_listener/listener_volume_keys.dart @@ -0,0 +1,28 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:lightmeter/data/models/volume_action.dart'; +import 'package:lightmeter/interactors/metering_interactor.dart'; + +class VolumeKeysListener { + final MeteringInteractor _meteringInteractor; + late final StreamSubscription _volumeKeysSubscription; + + VolumeKeysListener( + this._meteringInteractor, { + required VolumeAction action, + required ValueChanged onKey, + }) { + _volumeKeysSubscription = _meteringInteractor.volumeKeysStream().listen((event) { + if (_meteringInteractor.volumeAction == action) { + onKey(event); + } + }); + + // TODO: add RouteObserver and disable overriden action if SettingScreen is opened + } + + Future dispose() async { + await _volumeKeysSubscription.cancel(); + } +} diff --git a/lib/screens/metering/flow_metering.dart b/lib/screens/metering/flow_metering.dart index ac6cb0d..f4dae72 100644 --- a/lib/screens/metering/flow_metering.dart +++ b/lib/screens/metering/flow_metering.dart @@ -5,6 +5,7 @@ import 'package:lightmeter/data/haptics_service.dart'; import 'package:lightmeter/data/light_sensor_service.dart'; import 'package:lightmeter/data/permissions_service.dart'; import 'package:lightmeter/data/shared_prefs_service.dart'; +import 'package:lightmeter/data/volume_events_service.dart'; import 'package:lightmeter/interactors/metering_interactor.dart'; import 'package:lightmeter/screens/metering/bloc_metering.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; @@ -28,6 +29,7 @@ class _MeteringFlowState extends State { context.get(), context.get(), context.get(), + context.get(), ), child: MultiBlocProvider( providers: [ diff --git a/lib/screens/settings/components/general/components/volume_actions/bloc_list_tile_volume_actions.dart b/lib/screens/settings/components/general/components/volume_actions/bloc_list_tile_volume_actions.dart new file mode 100644 index 0000000..d0a51a9 --- /dev/null +++ b/lib/screens/settings/components/general/components/volume_actions/bloc_list_tile_volume_actions.dart @@ -0,0 +1,16 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lightmeter/data/models/volume_action.dart'; +import 'package:lightmeter/interactors/settings_interactor.dart'; + +class VolumeActionsListTileBloc extends Cubit { + final SettingsInteractor _settingsInteractor; + + VolumeActionsListTileBloc( + this._settingsInteractor, + ) : super(_settingsInteractor.volumeAction); + + void onVolumeActionChanged(VolumeAction value) { + _settingsInteractor.setVolumeAction(value); + emit(value); + } +} diff --git a/lib/screens/settings/components/general/components/volume_actions/provider_list_tile_volume_actions.dart b/lib/screens/settings/components/general/components/volume_actions/provider_list_tile_volume_actions.dart new file mode 100644 index 0000000..790ad4f --- /dev/null +++ b/lib/screens/settings/components/general/components/volume_actions/provider_list_tile_volume_actions.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lightmeter/interactors/settings_interactor.dart'; + +import 'package:lightmeter/screens/settings/components/general/components/volume_actions/bloc_list_tile_volume_actions.dart'; +import 'package:lightmeter/screens/settings/components/general/components/volume_actions/widget_list_tile_volume_actions.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; + +class VolumeActionsListTileProvider extends StatelessWidget { + const VolumeActionsListTileProvider({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => VolumeActionsListTileBloc(context.get()), + child: const VolumeActionsListTile(), + ); + } +} diff --git a/lib/screens/settings/components/general/components/volume_actions/widget_list_tile_volume_actions.dart b/lib/screens/settings/components/general/components/volume_actions/widget_list_tile_volume_actions.dart new file mode 100644 index 0000000..232c610 --- /dev/null +++ b/lib/screens/settings/components/general/components/volume_actions/widget_list_tile_volume_actions.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lightmeter/data/models/volume_action.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/screens/settings/components/general/components/volume_actions/bloc_list_tile_volume_actions.dart'; +import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart'; + +class VolumeActionsListTile extends StatelessWidget { + const VolumeActionsListTile({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) => ListTile( + leading: const Icon(Icons.volume_up), + title: Text(S.of(context).volumeKeysAction), + trailing: Text(actionToString(context, state)), + onTap: () { + showDialog( + context: context, + builder: (_) => DialogPicker( + icon: Icons.volume_up, + title: S.of(context).volumeKeysAction, + selectedValue: state, + values: VolumeAction.values, + titleAdapter: (context, value) => actionToString(context, value), + ), + ).then((value) { + if (value != null) { + context.read().onVolumeActionChanged(value); + } + }); + }, + ), + ); + } + + String actionToString(BuildContext context, VolumeAction themeType) { + switch (themeType) { + case VolumeAction.shutter: + return S.of(context).shutter; + case VolumeAction.zoom: + return S.of(context).zoom; + case VolumeAction.none: + return S.of(context).none; + } + } +} 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 b873d3f..bc123d7 100644 --- a/lib/screens/settings/components/general/widget_settings_section_general.dart +++ b/lib/screens/settings/components/general/widget_settings_section_general.dart @@ -1,8 +1,11 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; 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/volume_actions/provider_list_tile_volume_actions.dart'; import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart'; class GeneralSettingsSection extends StatelessWidget { @@ -12,10 +15,11 @@ class GeneralSettingsSection extends StatelessWidget { Widget build(BuildContext context) { return SettingsSection( title: S.of(context).general, - children: const [ - CaffeineListTileProvider(), - HapticsListTileProvider(), - LanguageListTile(), + children: [ + const CaffeineListTileProvider(), + const HapticsListTileProvider(), + if (Platform.isAndroid) const VolumeActionsListTileProvider(), + const LanguageListTile(), ], ); } diff --git a/lib/screens/settings/flow_settings.dart b/lib/screens/settings/flow_settings.dart index 5b9d5b3..3195c25 100644 --- a/lib/screens/settings/flow_settings.dart +++ b/lib/screens/settings/flow_settings.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/data/caffeine_service.dart'; import 'package:lightmeter/data/haptics_service.dart'; import 'package:lightmeter/data/shared_prefs_service.dart'; +import 'package:lightmeter/data/volume_events_service.dart'; import 'package:lightmeter/interactors/settings_interactor.dart'; import 'package:lightmeter/screens/settings/screen_settings.dart'; import 'package:lightmeter/utils/inherited_generics.dart'; @@ -16,6 +17,7 @@ class SettingsFlow extends StatelessWidget { context.get(), context.get(), context.get(), + context.get(), ), child: const SettingsScreen(), );