This commit is contained in:
Vadim 2025-04-26 08:54:27 +00:00 committed by GitHub
commit e044067a12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 115 additions and 57 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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