implemented VolumeKeysListener (wip)

This commit is contained in:
Vadim 2023-07-03 17:19:48 +02:00
parent 933a0e30cc
commit e20b9f76b9
18 changed files with 210 additions and 18 deletions

View file

@ -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()
}

View file

@ -0,0 +1,3 @@
enum VolumeAction { shutter, zoom, none }
enum VolumeKey { up, down }

View file

@ -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,

View file

@ -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<VolumeKey> 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<void> quickVibration() async {
if (_userPreferencesService.haptics) await _hapticsService.quickVibration();

View file

@ -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<void> 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;

View file

@ -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",

View file

@ -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",

View file

@ -56,6 +56,9 @@
"general": "Общие",
"keepScreenOn": "Запрет блокировки",
"haptics": "Вибрация",
"volumeKeysAction": "Кнопки регулировки громкости",
"shutter": "Затвор",
"zoom": "Зум",
"language": "Язык",
"chooseLanguage": "Выберите язык",
"theme": "Тема",

View file

@ -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<HapticsService>(
data: const HapticsService(),
child: InheritedWidgetBase<PermissionsService>(
data: const PermissionsService(),
child: MeteringScreenLayoutProvider(
child: StopTypeProvider(
child: EquipmentProfileProvider(
child: EvSourceTypeProvider(
child: SupportedLocaleProvider(
child: ThemeProvider(
child: Builder(
builder: (context) => builder(context, true),
child: InheritedWidgetBase<VolumeEventsService>(
data: const VolumeEventsService(),
child: InheritedWidgetBase<PermissionsService>(
data: const PermissionsService(),
child: MeteringScreenLayoutProvider(
child: StopTypeProvider(
child: EquipmentProfileProvider(
child: EvSourceTypeProvider(
child: SupportedLocaleProvider(
child: ThemeProvider(
child: Builder(
builder: (context) => builder(context, true),
),
),
),
),

View file

@ -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<MeteringEvent, MeteringState> {
final MeteringCommunicationBloc _communicationBloc;
late final StreamSubscription<communication_states.ScreenState> _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<MeteringEvent, MeteringState> {
@override
Future<void> close() async {
await _volumeKeysListener.dispose();
await _communicationSubscription.cancel();
return super.close();
}

View file

@ -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<CameraContainerEvent, CameraContainerState> {
@ -36,6 +38,19 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
double? _ev100 = 0.0;
late final VolumeKeysListener _volumeKeysListener = VolumeKeysListener(
_meteringInteractor,
action: VolumeAction.zoom,
onKey: (key) {
switch (key) {
case VolumeKey.up:
add(ZoomChangedEvent(_currentZoom + 0.5));
case VolumeKey.down:
add(ZoomChangedEvent(_currentZoom - 0.5));
}
},
);
CameraContainerBloc(
this._meteringInteractor,
MeteringCommunicationBloc communicationBloc,
@ -58,6 +73,7 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
@override
Future<void> close() async {
WidgetsBinding.instance.removeObserver(_observer);
_volumeKeysListener.dispose();
unawaited(_cameraController?.dispose().then((_) => _cameraController = null));
communicationBloc.add(communication_event.MeteringEndedEvent(_ev100));
return super.close();

View file

@ -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<VolumeKey> _volumeKeysSubscription;
VolumeKeysListener(
this._meteringInteractor, {
required VolumeAction action,
required ValueChanged<VolumeKey> onKey,
}) {
_volumeKeysSubscription = _meteringInteractor.volumeKeysStream().listen((event) {
if (_meteringInteractor.volumeAction == action) {
onKey(event);
}
});
// TODO: add RouteObserver and disable overriden action if SettingScreen is opened
}
Future<void> dispose() async {
await _volumeKeysSubscription.cancel();
}
}

View file

@ -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<MeteringFlow> {
context.get<HapticsService>(),
context.get<PermissionsService>(),
context.get<LightSensorService>(),
context.get<VolumeEventsService>(),
),
child: MultiBlocProvider(
providers: [

View file

@ -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<VolumeAction> {
final SettingsInteractor _settingsInteractor;
VolumeActionsListTileBloc(
this._settingsInteractor,
) : super(_settingsInteractor.volumeAction);
void onVolumeActionChanged(VolumeAction value) {
_settingsInteractor.setVolumeAction(value);
emit(value);
}
}

View file

@ -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<SettingsInteractor>()),
child: const VolumeActionsListTile(),
);
}
}

View file

@ -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<VolumeActionsListTileBloc, VolumeAction>(
builder: (context, state) => ListTile(
leading: const Icon(Icons.volume_up),
title: Text(S.of(context).volumeKeysAction),
trailing: Text(actionToString(context, state)),
onTap: () {
showDialog<VolumeAction>(
context: context,
builder: (_) => DialogPicker<VolumeAction>(
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<VolumeActionsListTileBloc>().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;
}
}
}

View file

@ -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(),
],
);
}

View file

@ -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<UserPreferencesService>(),
context.get<CaffeineService>(),
context.get<HapticsService>(),
context.get<VolumeEventsService>(),
),
child: const SettingsScreen(),
);