mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-25 00:40:39 +00:00
Added haptics
added `HapticsService` added haptics handling added `HapticsInteractor`
This commit is contained in:
parent
9bedc6e665
commit
c7ed4d332e
15 changed files with 190 additions and 33 deletions
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:lightmeter/data/haptics_service.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
@ -29,6 +30,7 @@ class Application extends StatelessWidget {
|
|||
return MultiProvider(
|
||||
providers: [
|
||||
Provider(create: (_) => UserPreferencesService(snapshot.data!)),
|
||||
Provider(create: (_) => HapticsService()),
|
||||
Provider.value(value: evSource),
|
||||
],
|
||||
child: Provider(
|
||||
|
|
18
lib/data/haptics_service.dart
Normal file
18
lib/data/haptics_service.dart
Normal file
|
@ -0,0 +1,18 @@
|
|||
import 'package:vibration/vibration.dart';
|
||||
|
||||
class HapticsService {
|
||||
Future<void> quickVibration() async => _tryVibrate(duration: 25, amplitude: 96);
|
||||
|
||||
Future<void> responseVibration() async => _tryVibrate(duration: 50, amplitude: 128);
|
||||
|
||||
Future<void> _tryVibrate({required int duration, required int amplitude}) async {
|
||||
if (await _canVibrate()) {
|
||||
Vibration.vibrate(
|
||||
duration: duration,
|
||||
amplitude: amplitude,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _canVibrate() async => await Vibration.hasVibrator() ?? false;
|
||||
}
|
|
@ -5,10 +5,11 @@ import 'models/photography_values/nd_value.dart';
|
|||
import 'models/theme_type.dart';
|
||||
|
||||
class UserPreferencesService {
|
||||
static const _isoKey = "ISO";
|
||||
static const _ndFilterKey = "ND";
|
||||
static const _isoKey = "iso";
|
||||
static const _ndFilterKey = "nd";
|
||||
|
||||
static const _themeTypeKey = "ThemeType";
|
||||
static const _hapticsKey = "haptics";
|
||||
static const _themeTypeKey = "themeType";
|
||||
|
||||
final SharedPreferences _sharedPreferences;
|
||||
|
||||
|
@ -20,6 +21,9 @@ class UserPreferencesService {
|
|||
NdValue get ndFilter => ndValues.firstWhere((v) => v.value == (_sharedPreferences.getInt(_ndFilterKey) ?? 0));
|
||||
set ndFilter(NdValue value) => _sharedPreferences.setInt(_ndFilterKey, value.value);
|
||||
|
||||
bool get haptics => _sharedPreferences.getBool(_hapticsKey) ?? false;
|
||||
set haptics(bool value) => _sharedPreferences.setBool(_hapticsKey, value);
|
||||
|
||||
ThemeType get themeType => ThemeType.values[_sharedPreferences.getInt(_themeTypeKey) ?? 0];
|
||||
set themeType(ThemeType value) => _sharedPreferences.setInt(_themeTypeKey, value.index);
|
||||
}
|
||||
|
|
26
lib/interactors/haptics_interactor.dart
Normal file
26
lib/interactors/haptics_interactor.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
import 'package:lightmeter/data/haptics_service.dart';
|
||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||
|
||||
class HapticsInteractor {
|
||||
final UserPreferencesService _userPreferencesService;
|
||||
final HapticsService _hapticsService;
|
||||
|
||||
const HapticsInteractor(
|
||||
this._userPreferencesService,
|
||||
this._hapticsService,
|
||||
);
|
||||
|
||||
bool get isEnabled => _userPreferencesService.haptics;
|
||||
|
||||
/// Executes vibration if haptics are enabled in settings
|
||||
void quickVibration() {
|
||||
if (_userPreferencesService.haptics) _hapticsService.quickVibration();
|
||||
}
|
||||
|
||||
/// Executes vibration if haptics are enabled in settings
|
||||
void responseVibration() {
|
||||
if (_userPreferencesService.haptics) _hapticsService.responseVibration();
|
||||
}
|
||||
|
||||
void enableHaptics(bool enable) => _userPreferencesService.haptics = enable;
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
"showFractionalStops": "Show fractional stops",
|
||||
"halfStops": "1/2",
|
||||
"thirdStops": "1/3",
|
||||
"haptics": "Haptics",
|
||||
"theme": "Theme",
|
||||
"chooseTheme": "Choose theme",
|
||||
"themeLight": "Light",
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:lightmeter/data/models/exposure_pair.dart';
|
|||
import 'package:lightmeter/data/models/photography_values/photography_value.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/shutter_speed_value.dart';
|
||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||
import 'package:lightmeter/interactors/haptics_interactor.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/utils/log_2.dart';
|
||||
|
@ -18,6 +19,7 @@ import 'state_metering.dart';
|
|||
class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
|
||||
final MeteringCommunicationBloc _communicationBloc;
|
||||
final UserPreferencesService _userPreferencesService;
|
||||
final HapticsInteractor _hapticsInteractor;
|
||||
late final StreamSubscription<communication_states.ScreenState> _communicationSubscription;
|
||||
|
||||
List<ApertureValue> get _apertureValues => apertureValues.whereStopType(stopType);
|
||||
|
@ -28,6 +30,7 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
|
|||
MeteringBloc(
|
||||
this._communicationBloc,
|
||||
this._userPreferencesService,
|
||||
this._hapticsInteractor,
|
||||
this.stopType,
|
||||
) : super(
|
||||
MeteringState(
|
||||
|
@ -46,7 +49,7 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
|
|||
on<StopTypeChangedEvent>(_onStopTypeChanged);
|
||||
on<IsoChangedEvent>(_onIsoChanged);
|
||||
on<NdChangedEvent>(_onNdChanged);
|
||||
on<MeasureEvent>((_, __) => _communicationBloc.add(const communication_events.MeasureEvent()));
|
||||
on<MeasureEvent>(_onMeasure);
|
||||
on<MeasuredEvent>(_onMeasured);
|
||||
|
||||
add(const MeasureEvent());
|
||||
|
@ -99,7 +102,13 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
|
|||
));
|
||||
}
|
||||
|
||||
void _onMeasure(_, __) {
|
||||
_hapticsInteractor.quickVibration();
|
||||
_communicationBloc.add(const communication_events.MeasureEvent());
|
||||
}
|
||||
|
||||
void _onMeasured(MeasuredEvent event, Emitter emit) {
|
||||
_hapticsInteractor.responseVibration();
|
||||
final ev = event.ev100 + log2(state.iso.value / 100);
|
||||
emit(MeteringState(
|
||||
iso: state.iso,
|
||||
|
|
|
@ -21,7 +21,7 @@ class CameraExposureSlider extends StatelessWidget {
|
|||
IconButton(
|
||||
icon: const Icon(Icons.sync),
|
||||
onPressed: state.currentExposureOffset != 0.0
|
||||
? () => context.read<CameraBloc>().add(const ExposureOffsetChangedEvent(0))
|
||||
? () => context.read<CameraBloc>().add(const ExposureOffsetResetEvent())
|
||||
: null,
|
||||
),
|
||||
Expanded(
|
||||
|
|
|
@ -6,6 +6,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/interactors/haptics_interactor.dart';
|
||||
import 'package:lightmeter/screens/metering/ev_source/ev_source_bloc.dart';
|
||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
||||
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' as communication_event;
|
||||
|
@ -16,6 +17,7 @@ import 'event_camera.dart';
|
|||
import 'state_camera.dart';
|
||||
|
||||
class CameraBloc extends EvSourceBloc<CameraEvent, CameraState> {
|
||||
final HapticsInteractor _hapticsInteractor;
|
||||
late final _WidgetsBindingObserver _observer;
|
||||
CameraController? _cameraController;
|
||||
CameraController? get cameraController => _cameraController;
|
||||
|
@ -29,8 +31,10 @@ class CameraBloc extends EvSourceBloc<CameraEvent, CameraState> {
|
|||
double _exposureStep = 0.0;
|
||||
double _currentExposureOffset = 0.0;
|
||||
|
||||
CameraBloc(MeteringCommunicationBloc communicationBloc)
|
||||
: super(
|
||||
CameraBloc(
|
||||
MeteringCommunicationBloc communicationBloc,
|
||||
this._hapticsInteractor,
|
||||
) : super(
|
||||
communicationBloc,
|
||||
const CameraInitState(),
|
||||
) {
|
||||
|
@ -40,6 +44,7 @@ class CameraBloc extends EvSourceBloc<CameraEvent, CameraState> {
|
|||
on<InitializeEvent>(_onInitialize);
|
||||
on<ZoomChangedEvent>(_onZoomChanged);
|
||||
on<ExposureOffsetChangedEvent>(_onExposureOffsetChanged);
|
||||
on<ExposureOffsetResetEvent>(_onExposureOffsetResetEvent);
|
||||
|
||||
add(const InitializeEvent());
|
||||
}
|
||||
|
@ -117,6 +122,11 @@ class CameraBloc extends EvSourceBloc<CameraEvent, CameraState> {
|
|||
_emitActiveState(emit);
|
||||
}
|
||||
|
||||
Future<void> _onExposureOffsetResetEvent(ExposureOffsetResetEvent event, Emitter emit) async {
|
||||
_hapticsInteractor.quickVibration();
|
||||
add(const ExposureOffsetChangedEvent(0));
|
||||
}
|
||||
|
||||
void _emitActiveState(Emitter emit) {
|
||||
emit(CameraActiveState(
|
||||
minZoom: _zoomRange!.start,
|
||||
|
|
|
@ -17,3 +17,7 @@ class ExposureOffsetChangedEvent extends CameraEvent {
|
|||
|
||||
const ExposureOffsetChangedEvent(this.value);
|
||||
}
|
||||
|
||||
class ExposureOffsetResetEvent extends CameraEvent {
|
||||
const ExposureOffsetResetEvent();
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/data/haptics_service.dart';
|
||||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/photography_value.dart';
|
||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||
import 'package:lightmeter/interactors/haptics_interactor.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'ev_source/camera/bloc_camera.dart';
|
||||
import 'ev_source/random_ev/bloc_random_ev.dart';
|
||||
import 'bloc_metering.dart';
|
||||
import 'communication/bloc_communication_metering.dart';
|
||||
|
||||
import 'screen_metering.dart';
|
||||
|
||||
class MeteringFlow extends StatelessWidget {
|
||||
|
@ -16,24 +18,36 @@ class MeteringFlow extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (_) => MeteringCommunicationBloc()),
|
||||
BlocProvider(
|
||||
create: (context) => MeteringBloc(
|
||||
context.read<MeteringCommunicationBloc>(),
|
||||
context.read<UserPreferencesService>(),
|
||||
context.read<StopType>(),
|
||||
),
|
||||
),
|
||||
BlocProvider(create: (context) => CameraBloc(context.read<MeteringCommunicationBloc>())),
|
||||
if (context.read<EvSourceType>() == EvSourceType.mock)
|
||||
return Provider(
|
||||
create: (context) => HapticsInteractor(
|
||||
context.read<UserPreferencesService>(),
|
||||
context.read<HapticsService>(),
|
||||
),
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (_) => MeteringCommunicationBloc()),
|
||||
BlocProvider(
|
||||
lazy: false,
|
||||
create: (context) => RandomEvBloc(context.read<MeteringCommunicationBloc>()),
|
||||
create: (context) => MeteringBloc(
|
||||
context.read<MeteringCommunicationBloc>(),
|
||||
context.read<UserPreferencesService>(),
|
||||
context.read<HapticsInteractor>(),
|
||||
context.read<StopType>(),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: const MeteringScreen(),
|
||||
BlocProvider(
|
||||
create: (context) => CameraBloc(
|
||||
context.read<MeteringCommunicationBloc>(),
|
||||
context.read<HapticsInteractor>(),
|
||||
),
|
||||
),
|
||||
if (context.read<EvSourceType>() == EvSourceType.mock)
|
||||
BlocProvider(
|
||||
lazy: false,
|
||||
create: (context) => RandomEvBloc(context.read<MeteringCommunicationBloc>()),
|
||||
),
|
||||
],
|
||||
child: const MeteringScreen(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/interactors/haptics_interactor.dart';
|
||||
|
||||
class HapticsListTileBloc extends Cubit<bool> {
|
||||
final HapticsInteractor _hapticsInteractor;
|
||||
|
||||
HapticsListTileBloc(
|
||||
this._hapticsInteractor,
|
||||
) : super(_hapticsInteractor.isEnabled);
|
||||
|
||||
void onHapticsChange(bool value) {
|
||||
_hapticsInteractor.enableHaptics(value);
|
||||
if (value) {
|
||||
_hapticsInteractor.quickVibration();
|
||||
}
|
||||
emit(value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/data/haptics_service.dart';
|
||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||
import 'package:lightmeter/interactors/haptics_interactor.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'bloc_list_tile_haptics.dart';
|
||||
import 'widget_list_tile_haptics.dart';
|
||||
|
||||
class HapticsListTileProvider extends StatelessWidget {
|
||||
const HapticsListTileProvider({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Provider(
|
||||
create: (context) => HapticsInteractor(
|
||||
context.read<UserPreferencesService>(),
|
||||
context.read<HapticsService>(),
|
||||
),
|
||||
child: BlocProvider(
|
||||
create: (context) => HapticsListTileBloc(
|
||||
context.read<HapticsInteractor>()
|
||||
),
|
||||
child: const HapticsListTile(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
|
||||
import 'bloc_list_tile_haptics.dart';
|
||||
|
||||
class HapticsListTile extends StatelessWidget {
|
||||
const HapticsListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<HapticsListTileBloc, bool>(
|
||||
builder: (context, state) => SwitchListTile(
|
||||
secondary: const Icon(Icons.vibration),
|
||||
title: Text(S.of(context).haptics),
|
||||
value: state,
|
||||
onChanged: context.read<HapticsListTileBloc>().onHapticsChange,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
import 'components/haptics/provider_list_tile_haptics.dart';
|
||||
import 'components/widget_list_tile_fractional_stops.dart';
|
||||
import 'components/widget_list_tile_theme_type.dart';
|
||||
import 'components/widget_label_version.dart';
|
||||
|
@ -40,8 +41,7 @@ class SettingsScreen extends StatelessWidget {
|
|||
delegate: SliverChildListDelegate(
|
||||
[
|
||||
const StopTypeListTile(),
|
||||
// const CaffeineListTile(),
|
||||
// const HapticsListTile(),
|
||||
const HapticsListTileProvider(),
|
||||
const ThemeTypeListTile(),
|
||||
],
|
||||
),
|
||||
|
|
15
pubspec.yaml
15
pubspec.yaml
|
@ -11,26 +11,27 @@ dependencies:
|
|||
exif: 3.1.2
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_bloc: ^8.1.1
|
||||
flutter_bloc: 8.1.1
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
intl: 0.17.0
|
||||
intl_utils: 2.8.1
|
||||
material_color_utilities: ^0.2.0
|
||||
material_color_utilities: 0.2.0
|
||||
package_info_plus: 3.0.2
|
||||
permission_handler: 10.2.0
|
||||
provider: ^6.0.4
|
||||
provider: 6.0.4
|
||||
shared_preferences: 2.0.15
|
||||
vibration: 1.7.6
|
||||
|
||||
dev_dependencies:
|
||||
google_fonts: ^3.0.1
|
||||
google_fonts: 3.0.1
|
||||
flutter_launcher_icons: 0.11.0
|
||||
flutter_lints: ^2.0.0
|
||||
flutter_lints: 2.0.0
|
||||
flutter_native_splash: 2.2.16
|
||||
test: ^1.21.6
|
||||
test: 1.21.6
|
||||
|
||||
dependency_overrides:
|
||||
material_color_utilities: ^0.2.0
|
||||
material_color_utilities: 0.2.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
|
Loading…
Reference in a new issue