mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-22 07:20:39 +00:00
ML-5 Measure button needs loading state (#40)
This commit is contained in:
parent
333563b0f0
commit
b7639740f3
12 changed files with 208 additions and 94 deletions
|
@ -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;
|
||||
|
|
|
@ -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<MeteringEvent, MeteringState> {
|
|||
|
||||
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<MeteringEvent, MeteringState> {
|
|||
on<NdChangedEvent>(_onNdChanged);
|
||||
on<MeasureEvent>(_onMeasure);
|
||||
on<MeasuredEvent>(_onMeasured);
|
||||
|
||||
add(const MeasureEvent());
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -65,59 +69,56 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
|
|||
|
||||
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),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -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<MeasureEvent>((_, emit) => emit(MeasureState()));
|
||||
on<MeasuredEvent>((event, emit) => emit(MeasuredState(event.ev100)));
|
||||
on<MeteringInProgressEvent>((event, emit) => emit(MeteringInProgressState(event.ev100)));
|
||||
on<MeteringEndedEvent>((event, emit) => emit(MeteringEndedState(event.ev100)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<MeteringMeasureButton> {
|
||||
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<MeteringMeasureButton> {
|
|||
_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: 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: widget.size - Dimens.grid16,
|
||||
size: Dimens.grid72 - Dimens.grid8,
|
||||
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: 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -34,6 +34,8 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
|||
double _exposureStep = 0.0;
|
||||
double _currentExposureOffset = 0.0;
|
||||
|
||||
double _ev100 = 0.0;
|
||||
|
||||
CameraContainerBloc(
|
||||
this._meteringInteractor,
|
||||
MeteringCommunicationBloc communicationBloc,
|
||||
|
@ -58,17 +60,17 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
|||
Future<void> 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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ class LightSensorContainerBloc
|
|||
final MeteringInteractor _meteringInteractor;
|
||||
|
||||
StreamSubscription<int>? _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<void> close() async {
|
||||
communicationBloc.add(communication_event.MeteringEndedEvent(_ev100));
|
||||
_luxSubscriptions?.cancel().then((_) => _luxSubscriptions = null);
|
||||
return super.close();
|
||||
}
|
||||
|
|
|
@ -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<MeteringScreen> {
|
|||
children: [
|
||||
Expanded(
|
||||
child: BlocBuilder<MeteringBloc, MeteringState>(
|
||||
builder: (context, state) => context.watch<EvSourceType>() == 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<MeteringScreen> {
|
|||
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<MeteringBloc, MeteringState>(
|
||||
buildWhen: (previous, current) => previous.ev != current.ev,
|
||||
builder: (context, state) => MeteringBottomControlsProvider(
|
||||
ev: context.read<Environment>().buildType == BuildType.dev ? state.ev : null,
|
||||
ev: state is MeteringDataState ? state.ev : null,
|
||||
isMetering: state is LoadingState || state is MeteringInProgressState,
|
||||
onSwitchEvSourceType: context.read<Environment>().hasLightSensor
|
||||
? EvSourceTypeProvider.of(context).toggleType
|
||||
: null,
|
||||
|
@ -73,3 +69,46 @@ class _MeteringScreenState extends State<MeteringScreen> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MeteringContainerBuidler extends StatelessWidget {
|
||||
final ExposurePair? fastest;
|
||||
final ExposurePair? slowest;
|
||||
final IsoValue iso;
|
||||
final NdValue nd;
|
||||
final ValueChanged<IsoValue> onIsoChanged;
|
||||
final ValueChanged<NdValue> onNdChanged;
|
||||
final List<ExposurePair> 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>() == 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ExposurePair> 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,
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue