mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2025-04-27 20:00:40 +00:00
Merge c215d5fa77
into d6cd537ffd
This commit is contained in:
commit
e044067a12
7 changed files with 115 additions and 57 deletions
|
@ -8,6 +8,7 @@ class MeteringCommunicationBloc extends Bloc<MeteringCommunicationEvent, Meterin
|
||||||
// `MeasureState` is not const, so that `Bloc` treats each state as new and updates state stream
|
// `MeasureState` is not const, so that `Bloc` treats each state as new and updates state stream
|
||||||
// ignore: prefer_const_constructors
|
// ignore: prefer_const_constructors
|
||||||
on<MeasureEvent>((_, emit) => emit(MeasureState()));
|
on<MeasureEvent>((_, emit) => emit(MeasureState()));
|
||||||
|
on<EquipmentProfileChangedEvent>((event, emit) => emit(EquipmentProfileChangedState(event.profile)));
|
||||||
on<MeteringInProgressEvent>((event, emit) => emit(MeteringInProgressState(event.ev100)));
|
on<MeteringInProgressEvent>((event, emit) => emit(MeteringInProgressState(event.ev100)));
|
||||||
on<MeteringEndedEvent>((event, emit) => emit(MeteringEndedState(event.ev100)));
|
on<MeteringEndedEvent>((event, emit) => emit(MeteringEndedState(event.ev100)));
|
||||||
on<ScreenOnTopOpenedEvent>((_, emit) => emit(const SettingsOpenedState()));
|
on<ScreenOnTopOpenedEvent>((_, emit) => emit(const SettingsOpenedState()));
|
||||||
|
|
|
@ -1,19 +1,29 @@
|
||||||
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
abstract class MeteringCommunicationEvent {
|
abstract class MeteringCommunicationEvent {
|
||||||
const MeteringCommunicationEvent();
|
const MeteringCommunicationEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class SourceEvent extends MeteringCommunicationEvent {
|
/// Events sent by the screen to the current metering source.
|
||||||
const SourceEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class ScreenEvent extends MeteringCommunicationEvent {
|
abstract class ScreenEvent extends MeteringCommunicationEvent {
|
||||||
const ScreenEvent();
|
const ScreenEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Event sent by the current metering source in response for the screen events.
|
||||||
|
abstract class SourceEvent extends MeteringCommunicationEvent {
|
||||||
|
const SourceEvent();
|
||||||
|
}
|
||||||
|
|
||||||
class MeasureEvent extends ScreenEvent {
|
class MeasureEvent extends ScreenEvent {
|
||||||
const MeasureEvent();
|
const MeasureEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EquipmentProfileChangedEvent extends ScreenEvent {
|
||||||
|
final EquipmentProfile profile;
|
||||||
|
|
||||||
|
const EquipmentProfileChangedEvent(this.profile);
|
||||||
|
}
|
||||||
|
|
||||||
abstract class MeasuredEvent extends SourceEvent {
|
abstract class MeasuredEvent extends SourceEvent {
|
||||||
final double? ev100;
|
final double? ev100;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
sealed class MeteringCommunicationState {
|
sealed class MeteringCommunicationState {
|
||||||
const MeteringCommunicationState();
|
const MeteringCommunicationState();
|
||||||
}
|
}
|
||||||
|
@ -18,6 +20,12 @@ class MeasureState extends SourceState {
|
||||||
const MeasureState();
|
const MeasureState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EquipmentProfileChangedState extends SourceState {
|
||||||
|
final EquipmentProfile profile;
|
||||||
|
|
||||||
|
const EquipmentProfileChangedState(this.profile);
|
||||||
|
}
|
||||||
|
|
||||||
sealed class MeasuredState extends ScreenState {
|
sealed class MeasuredState extends ScreenState {
|
||||||
final double? ev100;
|
final double? ev100;
|
||||||
|
|
||||||
|
|
|
@ -26,11 +26,12 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
final LightmeterAnalytics _analytics;
|
final LightmeterAnalytics _analytics;
|
||||||
late final _WidgetsBindingObserver _observer;
|
late final _WidgetsBindingObserver _observer;
|
||||||
|
|
||||||
|
CameraDescription? _camera;
|
||||||
CameraController? _cameraController;
|
CameraController? _cameraController;
|
||||||
|
|
||||||
static const _maxZoom = 7.0;
|
static const _maxZoom = 7.0;
|
||||||
RangeValues? _zoomRange;
|
RangeValues? _zoomRange;
|
||||||
double _currentZoom = 0.0;
|
double _currentZoom = 1.0;
|
||||||
|
|
||||||
static const _exposureMaxRange = RangeValues(-4, 4);
|
static const _exposureMaxRange = RangeValues(-4, 4);
|
||||||
RangeValues? _exposureOffsetRange;
|
RangeValues? _exposureOffsetRange;
|
||||||
|
@ -85,6 +86,12 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
case final communication_states.EquipmentProfileChangedState communicationState:
|
||||||
|
if (state is CameraActiveState) {
|
||||||
|
add(ZoomChangedEvent(communicationState.profile.lensZoom));
|
||||||
|
} else {
|
||||||
|
_currentZoom = communicationState.profile.lensZoom;
|
||||||
|
}
|
||||||
case communication_states.SettingsOpenedState():
|
case communication_states.SettingsOpenedState():
|
||||||
_settingsOpened = true;
|
_settingsOpened = true;
|
||||||
add(const DeinitializeEvent());
|
add(const DeinitializeEvent());
|
||||||
|
@ -116,46 +123,66 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final cameras = await availableCameras();
|
if (_camera == null) {
|
||||||
if (cameras.isEmpty) {
|
final cameras = await availableCameras();
|
||||||
emit(const CameraErrorState(CameraErrorType.noCamerasDetected));
|
if (cameras.isEmpty) {
|
||||||
return;
|
emit(const CameraErrorState(CameraErrorType.noCamerasDetected));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
_camera = cameras.firstWhere(
|
||||||
|
(camera) => camera.lensDirection == CameraLensDirection.back,
|
||||||
|
orElse: () => cameras.last,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_cameraController = CameraController(
|
|
||||||
cameras.firstWhere(
|
final cameraController = CameraController(
|
||||||
(camera) => camera.lensDirection == CameraLensDirection.back,
|
_camera!,
|
||||||
orElse: () => cameras.last,
|
|
||||||
),
|
|
||||||
ResolutionPreset.low,
|
ResolutionPreset.low,
|
||||||
enableAudio: false,
|
enableAudio: false,
|
||||||
);
|
);
|
||||||
|
await cameraController.initialize();
|
||||||
|
await cameraController.setFlashMode(FlashMode.off);
|
||||||
|
await cameraController.lockCaptureOrientation(DeviceOrientation.portraitUp);
|
||||||
|
|
||||||
await _cameraController!.initialize();
|
if (_exposureOffsetRange == null) {
|
||||||
await _cameraController!.setFlashMode(FlashMode.off);
|
await Future.wait<double>([
|
||||||
await _cameraController!.lockCaptureOrientation(DeviceOrientation.portraitUp);
|
cameraController.getMinExposureOffset(),
|
||||||
|
cameraController.getMaxExposureOffset(),
|
||||||
|
cameraController.getExposureOffsetStepSize(),
|
||||||
|
]).then((value) {
|
||||||
|
_exposureOffsetRange = RangeValues(
|
||||||
|
math.max(_exposureMaxRange.start, value[0]),
|
||||||
|
math.min(_exposureMaxRange.end, value[1]),
|
||||||
|
);
|
||||||
|
_currentExposureOffset = 0.0;
|
||||||
|
_exposureStep = value[2] == 0 ? 0.1 : value[2];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_zoomRange = await Future.wait<double>([
|
if (_zoomRange == null) {
|
||||||
_cameraController!.getMinZoomLevel(),
|
await Future.wait<double>([
|
||||||
_cameraController!.getMaxZoomLevel(),
|
cameraController.getMinZoomLevel(),
|
||||||
]).then((levels) => RangeValues(math.max(1.0, levels[0]), math.min(_maxZoom, levels[1])));
|
cameraController.getMaxZoomLevel(),
|
||||||
_currentZoom = _zoomRange!.start;
|
]).then((value) {
|
||||||
|
_zoomRange = RangeValues(
|
||||||
|
math.max(1.0, value[0]),
|
||||||
|
math.min(_maxZoom, value[1]),
|
||||||
|
);
|
||||||
|
if (_currentZoom < _zoomRange!.start || _currentZoom > _zoomRange!.end) {
|
||||||
|
_currentZoom = _zoomRange!.start;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_exposureOffsetRange = await Future.wait<double>([
|
/// For app startup initialization this effectively isn't executed.
|
||||||
_cameraController!.getMinExposureOffset(),
|
await Future.wait<void>([
|
||||||
_cameraController!.getMaxExposureOffset(),
|
if (_currentZoom != 1.0) cameraController.setZoomLevel(_currentZoom),
|
||||||
]).then(
|
if (_currentExposureOffset != 0.0) cameraController.setExposureOffset(_currentExposureOffset),
|
||||||
(levels) => RangeValues(
|
]);
|
||||||
math.max(_exposureMaxRange.start, levels[0]),
|
|
||||||
math.min(_exposureMaxRange.end, levels[1]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await _cameraController!.getExposureOffsetStepSize().then((value) {
|
|
||||||
_exposureStep = value == 0 ? 0.1 : value;
|
|
||||||
});
|
|
||||||
_currentExposureOffset = 0.0;
|
|
||||||
|
|
||||||
emit(CameraInitializedState(_cameraController!));
|
|
||||||
|
|
||||||
|
_cameraController = cameraController;
|
||||||
|
emit(CameraInitializedState(cameraController));
|
||||||
_emitActiveState(emit);
|
_emitActiveState(emit);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(const CameraErrorState(CameraErrorType.other));
|
emit(const CameraErrorState(CameraErrorType.other));
|
||||||
|
@ -169,7 +196,7 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onZoomChanged(ZoomChangedEvent event, Emitter emit) async {
|
Future<void> _onZoomChanged(ZoomChangedEvent event, Emitter emit) async {
|
||||||
if (_cameraController != null) {
|
if (_cameraController != null && _zoomRange != null) {
|
||||||
final double zoom = event.value.clamp(_zoomRange!.start, _zoomRange!.end);
|
final double zoom = event.value.clamp(_zoomRange!.start, _zoomRange!.end);
|
||||||
_cameraController!.setZoomLevel(zoom);
|
_cameraController!.setZoomLevel(zoom);
|
||||||
_currentZoom = zoom;
|
_currentZoom = zoom;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
||||||
import 'package:lightmeter/screens/shared/ruler_slider/widget_slider_ruler.dart';
|
import 'package:lightmeter/screens/shared/ruler_slider/widget_slider_ruler.dart';
|
||||||
import 'package:lightmeter/utils/double_to_zoom.dart';
|
import 'package:lightmeter/utils/double_to_zoom.dart';
|
||||||
|
|
||||||
class ZoomSlider extends StatefulWidget {
|
class ZoomSlider extends StatelessWidget {
|
||||||
final RangeValues range;
|
final RangeValues range;
|
||||||
final double value;
|
final double value;
|
||||||
final ValueChanged<double> onChanged;
|
final ValueChanged<double> onChanged;
|
||||||
|
@ -15,23 +15,12 @@ class ZoomSlider extends StatefulWidget {
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
|
||||||
State<ZoomSlider> createState() => _ZoomSliderState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ZoomSliderState extends State<ZoomSlider> {
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
widget.onChanged(EquipmentProfiles.selectedOf(context).lensZoom);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RulerSlider(
|
return RulerSlider(
|
||||||
range: widget.range,
|
range: range,
|
||||||
value: widget.value,
|
value: value,
|
||||||
onChanged: widget.onChanged,
|
onChanged: onChanged,
|
||||||
icon: Icons.search_outlined,
|
icon: Icons.search_outlined,
|
||||||
defaultValue: EquipmentProfiles.selectedOf(context).lensZoom,
|
defaultValue: EquipmentProfiles.selectedOf(context).lensZoom,
|
||||||
rulerValueAdapter: (value) => value.toStringAsFixed(0),
|
rulerValueAdapter: (value) => value.toStringAsFixed(0),
|
||||||
|
|
|
@ -1,13 +1,28 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
||||||
|
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
||||||
|
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart';
|
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart';
|
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart';
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class EquipmentProfilePicker extends StatelessWidget {
|
class EquipmentProfilePicker extends StatefulWidget {
|
||||||
const EquipmentProfilePicker();
|
const EquipmentProfilePicker();
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EquipmentProfilePicker> createState() => _EquipmentProfilePickerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EquipmentProfilePickerState extends State<EquipmentProfilePicker> {
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
final profile = EquipmentProfiles.selectedOf(context);
|
||||||
|
context.read<MeteringCommunicationBloc>().add(EquipmentProfileChangedEvent(profile));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AnimatedDialogPicker<EquipmentProfile>(
|
return AnimatedDialogPicker<EquipmentProfile>(
|
||||||
|
@ -16,7 +31,10 @@ class EquipmentProfilePicker extends StatelessWidget {
|
||||||
selectedValue: EquipmentProfiles.selectedOf(context),
|
selectedValue: EquipmentProfiles.selectedOf(context),
|
||||||
values: EquipmentProfiles.inUseOf(context),
|
values: EquipmentProfiles.inUseOf(context),
|
||||||
itemTitleBuilder: (_, value) => Text(value.id.isEmpty ? S.of(context).none : value.name),
|
itemTitleBuilder: (_, value) => Text(value.id.isEmpty ? S.of(context).none : value.name),
|
||||||
onChanged: EquipmentProfilesProvider.of(context).selectProfile,
|
onChanged: (profile) {
|
||||||
|
EquipmentProfilesProvider.of(context).selectProfile(profile);
|
||||||
|
context.read<MeteringCommunicationBloc>().add(EquipmentProfileChangedEvent(profile));
|
||||||
|
},
|
||||||
closedChild: ReadingValueContainer.singleValue(
|
closedChild: ReadingValueContainer.singleValue(
|
||||||
value: ReadingValue(
|
value: ReadingValue(
|
||||||
label: S.of(context).equipmentProfile,
|
label: S.of(context).equipmentProfile,
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
||||||
|
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/equipment_profile_picker/widget_picker_equipment_profiles.dart';
|
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/equipment_profile_picker/widget_picker_equipment_profiles.dart';
|
||||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
@ -33,8 +35,11 @@ void main() {
|
||||||
],
|
],
|
||||||
child: EquipmentProfilesProvider(
|
child: EquipmentProfilesProvider(
|
||||||
storageService: storageService,
|
storageService: storageService,
|
||||||
child: const WidgetTestApplicationMock(
|
child: WidgetTestApplicationMock(
|
||||||
child: Row(children: [Expanded(child: EquipmentProfilePicker())]),
|
child: BlocProvider(
|
||||||
|
create: (_) => MeteringCommunicationBloc(),
|
||||||
|
child: const Row(children: [Expanded(child: EquipmentProfilePicker())]),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue