From b7639740f39df40afd0ec32d5590140c5958af74 Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Sun, 19 Feb 2023 13:26:14 +0300 Subject: [PATCH] ML-5 Measure button needs loading state (#40) --- lib/res/dimens.dart | 1 + lib/screens/metering/bloc_metering.dart | 73 +++++++++-------- .../bloc_communication_metering.dart | 3 +- .../event_communication_metering.dart | 10 ++- .../state_communication_metering.dart | 10 ++- .../measure_button/widget_button_measure.dart | 82 ++++++++++++------- .../provider_bottom_controls.dart | 3 + .../widget_bottom_controls.dart | 3 + .../bloc_container_camera.dart | 12 +-- .../bloc_container_light_sensor.dart | 8 +- lib/screens/metering/screen_metering.dart | 65 ++++++++++++--- lib/screens/metering/state_metering.dart | 32 +++++++- 12 files changed, 208 insertions(+), 94 deletions(-) diff --git a/lib/res/dimens.dart b/lib/res/dimens.dart index 79f5dc8..09c05a3 100644 --- a/lib/res/dimens.dart +++ b/lib/res/dimens.dart @@ -13,6 +13,7 @@ class Dimens { static const double grid24 = 24; static const double grid48 = 48; static const double grid56 = 56; + static const double grid72 = 72; static const double grid168 = 168; static const double paddingS = 8; diff --git a/lib/screens/metering/bloc_metering.dart b/lib/screens/metering/bloc_metering.dart index 6078e3e..9f122cc 100644 --- a/lib/screens/metering/bloc_metering.dart +++ b/lib/screens/metering/bloc_metering.dart @@ -4,6 +4,8 @@ import 'dart:math'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lightmeter/data/models/photography_values/aperture_value.dart'; import 'package:lightmeter/data/models/exposure_pair.dart'; +import 'package:lightmeter/data/models/photography_values/iso_value.dart'; +import 'package:lightmeter/data/models/photography_values/nd_value.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'; @@ -29,16 +31,20 @@ class MeteringBloc extends Bloc { StopType stopType; + late IsoValue _iso = _userPreferencesService.iso; + late NdValue _nd = _userPreferencesService.ndFilter; + double _ev = 0.0; + bool _isMeteringInProgress = false; + MeteringBloc( this._communicationBloc, this._userPreferencesService, this._meteringInteractor, this.stopType, ) : super( - MeteringState( + MeteringEndedState( iso: _userPreferencesService.iso, ev: 0.0, - evCompensation: 0.0, nd: _userPreferencesService.ndFilter, exposurePairs: [], ), @@ -53,8 +59,6 @@ class MeteringBloc extends Bloc { on(_onNdChanged); on(_onMeasure); on(_onMeasured); - - add(const MeasureEvent()); } @override @@ -65,60 +69,57 @@ class MeteringBloc extends Bloc { void _onCommunicationState(communication_states.ScreenState communicationState) { if (communicationState is communication_states.MeasuredState) { + _isMeteringInProgress = communicationState is communication_states.MeteringInProgressState; add(MeasuredEvent(communicationState.ev100)); } } void _onStopTypeChanged(StopTypeChangedEvent event, Emitter emit) { stopType = event.stopType; - emit(MeteringState( - iso: state.iso, - ev: state.ev, - evCompensation: state.evCompensation, - nd: state.nd, - exposurePairs: _buildExposureValues(state.ev), - )); + _emitMeasuredState(emit); } void _onIsoChanged(IsoChangedEvent event, Emitter emit) { _userPreferencesService.iso = event.isoValue; - final ev = state.ev + log2(event.isoValue.value / state.iso.value); - emit(MeteringState( - iso: event.isoValue, - ev: ev, - evCompensation: state.evCompensation, - nd: state.nd, - exposurePairs: _buildExposureValues(ev), - )); + _ev = _ev + log2(event.isoValue.value / _iso.value); + _iso = event.isoValue; + _emitMeasuredState(emit); } void _onNdChanged(NdChangedEvent event, Emitter emit) { _userPreferencesService.ndFilter = event.ndValue; - final ev = state.ev - event.ndValue.stopReduction + state.nd.stopReduction; - emit(MeteringState( - iso: state.iso, - ev: ev, - evCompensation: state.evCompensation, - nd: event.ndValue, - exposurePairs: _buildExposureValues(ev), - )); + _ev = _ev - event.ndValue.stopReduction + _nd.stopReduction; + _nd = event.ndValue; + _emitMeasuredState(emit); } - void _onMeasure(_, __) { + void _onMeasure(_, Emitter emit) { _meteringInteractor.quickVibration(); _communicationBloc.add(const communication_events.MeasureEvent()); + _isMeteringInProgress = true; + emit(const LoadingState()); } void _onMeasured(MeasuredEvent event, Emitter emit) { _meteringInteractor.responseVibration(); - final ev = event.ev100 + log2(state.iso.value / 100); - emit(MeteringState( - iso: state.iso, - ev: ev, - evCompensation: state.evCompensation, - nd: state.nd, - exposurePairs: _buildExposureValues(ev), - )); + _ev = event.ev100 + log2(_iso.value / 100); + _emitMeasuredState(emit); + } + + void _emitMeasuredState(Emitter emit) { + emit(_isMeteringInProgress + ? MeteringInProgressState( + iso: _iso, + ev: _ev, + nd: _nd, + exposurePairs: _buildExposureValues(_ev), + ) + : MeteringEndedState( + iso: _iso, + ev: _ev, + nd: _nd, + exposurePairs: _buildExposureValues(_ev), + )); } List _buildExposureValues(double ev) { diff --git a/lib/screens/metering/communication/bloc_communication_metering.dart b/lib/screens/metering/communication/bloc_communication_metering.dart index 14fb5ce..5e84cdd 100644 --- a/lib/screens/metering/communication/bloc_communication_metering.dart +++ b/lib/screens/metering/communication/bloc_communication_metering.dart @@ -9,6 +9,7 @@ class MeteringCommunicationBloc // `MeasureState` is not const, so that `Bloc` treats each state as new and updates state stream // ignore: prefer_const_constructors on((_, emit) => emit(MeasureState())); - on((event, emit) => emit(MeasuredState(event.ev100))); + on((event, emit) => emit(MeteringInProgressState(event.ev100))); + on((event, emit) => emit(MeteringEndedState(event.ev100))); } } diff --git a/lib/screens/metering/communication/event_communication_metering.dart b/lib/screens/metering/communication/event_communication_metering.dart index b8666e9..6c4ce11 100644 --- a/lib/screens/metering/communication/event_communication_metering.dart +++ b/lib/screens/metering/communication/event_communication_metering.dart @@ -14,8 +14,16 @@ class MeasureEvent extends ScreenEvent { const MeasureEvent(); } -class MeasuredEvent extends SourceEvent { +abstract class MeasuredEvent extends SourceEvent { final double ev100; const MeasuredEvent(this.ev100); } + +class MeteringInProgressEvent extends MeasuredEvent { + const MeteringInProgressEvent(super.ev100); +} + +class MeteringEndedEvent extends MeasuredEvent { + const MeteringEndedEvent(super.ev100); +} diff --git a/lib/screens/metering/communication/state_communication_metering.dart b/lib/screens/metering/communication/state_communication_metering.dart index 3a03e58..e60ed67 100644 --- a/lib/screens/metering/communication/state_communication_metering.dart +++ b/lib/screens/metering/communication/state_communication_metering.dart @@ -18,8 +18,16 @@ class MeasureState extends SourceState { const MeasureState(); } -class MeasuredState extends ScreenState { +abstract class MeasuredState extends ScreenState { final double ev100; const MeasuredState(this.ev100); } + +class MeteringInProgressState extends MeasuredState { + const MeteringInProgressState(super.ev100); +} + +class MeteringEndedState extends MeasuredState { + const MeteringEndedState(super.ev100); +} diff --git a/lib/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart b/lib/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart index dc416ed..a584a6f 100644 --- a/lib/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart +++ b/lib/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart @@ -5,13 +5,13 @@ import 'package:lightmeter/screens/shared/filled_circle/widget_circle_filled.dar class MeteringMeasureButton extends StatefulWidget { final double? ev; - final double size; + final bool isMetering; final VoidCallback onTap; const MeteringMeasureButton({ - required this.onTap, required this.ev, - this.size = 72, + required this.isMetering, + required this.onTap, super.key, }); @@ -22,10 +22,18 @@ class MeteringMeasureButton extends StatefulWidget { class _MeteringMeasureButtonState extends State { bool _isPressed = false; + @override + void didUpdateWidget(covariant MeteringMeasureButton oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.isMetering != widget.isMetering) { + _isPressed = widget.isMetering; + } + } + @override Widget build(BuildContext context) { - return SizedBox.fromSize( - size: Size.square(widget.size), + return IgnorePointer( + ignoring: widget.isMetering && widget.ev == null, child: GestureDetector( onTap: widget.onTap, onTapDown: (_) { @@ -43,36 +51,50 @@ class _MeteringMeasureButtonState extends State { _isPressed = false; }); }, - child: DecoratedBox( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(widget.size / 2), - border: Border.all( - width: Dimens.grid4, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - child: Center( - child: AnimatedScale( - duration: Dimens.durationS, - scale: _isPressed ? 0.9 : 1.0, - child: FilledCircle( - color: Theme.of(context).colorScheme.onSurface, - size: widget.size - Dimens.grid16, - child: Center( - child: widget.ev != null - ? Text( - '${widget.ev!.toStringAsFixed(1)}\n${S.of(context).ev}', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.surface, - ), - ) - : null, + child: SizedBox.fromSize( + size: const Size.square(Dimens.grid72), + child: Stack( + children: [ + Center( + child: AnimatedScale( + duration: Dimens.durationS, + scale: _isPressed ? 0.9 : 1.0, + child: FilledCircle( + color: Theme.of(context).colorScheme.onSurface, + size: Dimens.grid72 - Dimens.grid8, + child: Center( + child: widget.ev != null ? _EvValueText(ev: widget.ev!) : null, + ), + ), ), ), - ), + Positioned.fill( + child: CircularProgressIndicator( + color: Theme.of(context).colorScheme.onSurface, + strokeWidth: Dimens.grid4, + value: widget.isMetering ? null : 1, + ), + ), + ], ), ), ), ); } } + +class _EvValueText extends StatelessWidget { + final double ev; + + const _EvValueText({required this.ev}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Text( + '${ev.toStringAsFixed(1)}\n${S.of(context).ev}', + style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.surface), + textAlign: TextAlign.center, + ); + } +} diff --git a/lib/screens/metering/components/bottom_controls/provider_bottom_controls.dart b/lib/screens/metering/components/bottom_controls/provider_bottom_controls.dart index e41f936..a697227 100644 --- a/lib/screens/metering/components/bottom_controls/provider_bottom_controls.dart +++ b/lib/screens/metering/components/bottom_controls/provider_bottom_controls.dart @@ -5,12 +5,14 @@ import 'widget_bottom_controls.dart'; class MeteringBottomControlsProvider extends StatelessWidget { final double? ev; + final bool isMetering; final VoidCallback? onSwitchEvSourceType; final VoidCallback onMeasure; final VoidCallback onSettings; const MeteringBottomControlsProvider({ required this.ev, + required this.isMetering, required this.onSwitchEvSourceType, required this.onMeasure, required this.onSettings, @@ -33,6 +35,7 @@ class MeteringBottomControlsProvider extends StatelessWidget { ), child: MeteringBottomControls( ev: ev, + isMetering: isMetering, onSwitchEvSourceType: onSwitchEvSourceType, onMeasure: onMeasure, onSettings: onSettings, diff --git a/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart b/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart index a1837c3..5565b69 100644 --- a/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart +++ b/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart @@ -7,12 +7,14 @@ import 'components/measure_button/widget_button_measure.dart'; class MeteringBottomControls extends StatelessWidget { final double? ev; + final bool isMetering; final VoidCallback? onSwitchEvSourceType; final VoidCallback onMeasure; final VoidCallback onSettings; const MeteringBottomControls({ required this.ev, + required this.isMetering, required this.onSwitchEvSourceType, required this.onMeasure, required this.onSettings, @@ -50,6 +52,7 @@ class MeteringBottomControls extends StatelessWidget { const Spacer(), MeteringMeasureButton( ev: ev, + isMetering: isMetering, onTap: onMeasure, ), Expanded( 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 db69fcb..67bb623 100644 --- a/lib/screens/metering/components/camera_container/bloc_container_camera.dart +++ b/lib/screens/metering/components/camera_container/bloc_container_camera.dart @@ -34,6 +34,8 @@ class CameraContainerBloc extends EvSourceBlocBase close() async { WidgetsBinding.instance.removeObserver(_observer); unawaited(_cameraController?.dispose()); + communicationBloc.add(communication_event.MeteringEndedEvent(_ev100)); return super.close(); } @override void onCommunicationState(communication_states.SourceState communicationState) { if (communicationState is communication_states.MeasureState) { - _takePhoto().then((ev100) { - if (ev100 != null) { - communicationBloc.add( - communication_event.MeasuredEvent(ev100 + _meteringInteractor.cameraEvCalibration), - ); + _takePhoto().then((ev100Raw) { + if (ev100Raw != null) { + _ev100 = ev100Raw + _meteringInteractor.cameraEvCalibration; + communicationBloc.add(communication_event.MeteringEndedEvent(_ev100)); } }); } diff --git a/lib/screens/metering/components/light_sensor_container/bloc_container_light_sensor.dart b/lib/screens/metering/components/light_sensor_container/bloc_container_light_sensor.dart index f5ea16d..ff2bb9d 100644 --- a/lib/screens/metering/components/light_sensor_container/bloc_container_light_sensor.dart +++ b/lib/screens/metering/components/light_sensor_container/bloc_container_light_sensor.dart @@ -16,6 +16,7 @@ class LightSensorContainerBloc final MeteringInteractor _meteringInteractor; StreamSubscription? _luxSubscriptions; + double _ev100 = 0.0; LightSensorContainerBloc( this._meteringInteractor, @@ -30,11 +31,11 @@ class LightSensorContainerBloc if (communicationState is communication_states.MeasureState) { if (_luxSubscriptions == null) { _luxSubscriptions = _meteringInteractor.luxStream().listen((event) { - communicationBloc.add(communication_event.MeasuredEvent( - log2(event.toDouble() / 2.5) + _meteringInteractor.lightSensorEvCalibration, - )); + _ev100 = log2(event.toDouble() / 2.5) + _meteringInteractor.lightSensorEvCalibration; + communicationBloc.add(communication_event.MeteringInProgressEvent(_ev100)); }); } else { + communicationBloc.add(communication_event.MeteringEndedEvent(_ev100)); _luxSubscriptions?.cancel().then((_) => _luxSubscriptions = null); } } @@ -42,6 +43,7 @@ class LightSensorContainerBloc @override Future close() async { + communicationBloc.add(communication_event.MeteringEndedEvent(_ev100)); _luxSubscriptions?.cancel().then((_) => _luxSubscriptions = null); return super.close(); } diff --git a/lib/screens/metering/screen_metering.dart b/lib/screens/metering/screen_metering.dart index 0c7a297..8abf2fc 100644 --- a/lib/screens/metering/screen_metering.dart +++ b/lib/screens/metering/screen_metering.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lightmeter/data/models/ev_source_type.dart'; +import 'package:lightmeter/data/models/exposure_pair.dart'; +import 'package:lightmeter/data/models/photography_values/iso_value.dart'; +import 'package:lightmeter/data/models/photography_values/nd_value.dart'; import 'package:lightmeter/data/models/photography_values/photography_value.dart'; import 'package:lightmeter/environment.dart'; import 'package:lightmeter/providers/ev_source_type_provider.dart'; @@ -36,8 +39,9 @@ class _MeteringScreenState extends State { children: [ Expanded( child: BlocBuilder( - builder: (context, state) => context.watch() == EvSourceType.camera - ? CameraContainerProvider( + buildWhen: (_, current) => current is MeteringDataState, + builder: (_, state) => state is MeteringDataState + ? _MeteringContainerBuidler( fastest: state.fastest, slowest: state.slowest, iso: state.iso, @@ -46,21 +50,13 @@ class _MeteringScreenState extends State { onNdChanged: (value) => _bloc.add(NdChangedEvent(value)), exposurePairs: state.exposurePairs, ) - : LightSensorContainerProvider( - fastest: state.fastest, - slowest: state.slowest, - iso: state.iso, - nd: state.nd, - onIsoChanged: (value) => _bloc.add(IsoChangedEvent(value)), - onNdChanged: (value) => _bloc.add(NdChangedEvent(value)), - exposurePairs: state.exposurePairs, - ), + : const SizedBox.shrink(), ), ), BlocBuilder( - buildWhen: (previous, current) => previous.ev != current.ev, builder: (context, state) => MeteringBottomControlsProvider( - ev: context.read().buildType == BuildType.dev ? state.ev : null, + ev: state is MeteringDataState ? state.ev : null, + isMetering: state is LoadingState || state is MeteringInProgressState, onSwitchEvSourceType: context.read().hasLightSensor ? EvSourceTypeProvider.of(context).toggleType : null, @@ -73,3 +69,46 @@ class _MeteringScreenState extends State { ); } } + +class _MeteringContainerBuidler extends StatelessWidget { + final ExposurePair? fastest; + final ExposurePair? slowest; + final IsoValue iso; + final NdValue nd; + final ValueChanged onIsoChanged; + final ValueChanged onNdChanged; + final List exposurePairs; + + const _MeteringContainerBuidler({ + required this.fastest, + required this.slowest, + required this.iso, + required this.nd, + required this.onIsoChanged, + required this.onNdChanged, + required this.exposurePairs, + }); + + @override + Widget build(BuildContext context) { + return context.watch() == EvSourceType.camera + ? CameraContainerProvider( + fastest: fastest, + slowest: slowest, + iso: iso, + nd: nd, + onIsoChanged: onIsoChanged, + onNdChanged: onNdChanged, + exposurePairs: exposurePairs, + ) + : LightSensorContainerProvider( + fastest: fastest, + slowest: slowest, + iso: iso, + nd: nd, + onIsoChanged: onIsoChanged, + onNdChanged: onNdChanged, + exposurePairs: exposurePairs, + ); + } +} diff --git a/lib/screens/metering/state_metering.dart b/lib/screens/metering/state_metering.dart index 3110fd3..cde6b13 100644 --- a/lib/screens/metering/state_metering.dart +++ b/lib/screens/metering/state_metering.dart @@ -2,16 +2,22 @@ import 'package:lightmeter/data/models/exposure_pair.dart'; import 'package:lightmeter/data/models/photography_values/iso_value.dart'; import 'package:lightmeter/data/models/photography_values/nd_value.dart'; -class MeteringState { +abstract class MeteringState { + const MeteringState(); +} + +class LoadingState extends MeteringState { + const LoadingState(); +} + +abstract class MeteringDataState extends MeteringState { final double ev; - final double evCompensation; final IsoValue iso; final NdValue nd; final List exposurePairs; - const MeteringState({ + const MeteringDataState({ required this.ev, - required this.evCompensation, required this.iso, required this.nd, required this.exposurePairs, @@ -20,3 +26,21 @@ class MeteringState { ExposurePair? get fastest => exposurePairs.isEmpty ? null : exposurePairs.first; ExposurePair? get slowest => exposurePairs.isEmpty ? null : exposurePairs.last; } + +class MeteringInProgressState extends MeteringDataState { + MeteringInProgressState({ + required super.ev, + required super.iso, + required super.nd, + required super.exposurePairs, + }); +} + +class MeteringEndedState extends MeteringDataState { + MeteringEndedState({ + required super.ev, + required super.iso, + required super.nd, + required super.exposurePairs, + }); +}