2022-12-14 17:33:38 +00:00
|
|
|
import 'dart:async';
|
2023-05-16 09:47:53 +00:00
|
|
|
import 'dart:developer';
|
2022-12-14 17:33:38 +00:00
|
|
|
import 'dart:io';
|
2023-05-16 09:47:53 +00:00
|
|
|
import 'dart:math' as math;
|
2023-05-11 13:30:18 +00:00
|
|
|
|
2022-12-14 17:33:38 +00:00
|
|
|
import 'package:camera/camera.dart';
|
|
|
|
import 'package:exif/exif.dart';
|
2022-12-21 19:31:22 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2023-09-28 21:29:33 +00:00
|
|
|
import 'package:flutter/services.dart';
|
2022-12-14 17:33:38 +00:00
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
2023-01-26 09:10:23 +00:00
|
|
|
import 'package:lightmeter/interactors/metering_interactor.dart';
|
2023-09-28 21:29:33 +00:00
|
|
|
import 'package:lightmeter/platform_config.dart';
|
2022-12-14 17:33:38 +00:00
|
|
|
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
2023-11-07 16:14:53 +00:00
|
|
|
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' as communication_event;
|
|
|
|
import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' as communication_states;
|
2023-05-11 13:30:18 +00:00
|
|
|
import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart';
|
|
|
|
import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart';
|
|
|
|
import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart';
|
|
|
|
import 'package:lightmeter/screens/metering/components/shared/ev_source_base/bloc_base_ev_source.dart';
|
2023-09-02 08:32:08 +00:00
|
|
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
2022-12-14 17:33:38 +00:00
|
|
|
|
2023-01-29 16:57:47 +00:00
|
|
|
class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraContainerState> {
|
2023-01-26 09:10:23 +00:00
|
|
|
final MeteringInteractor _meteringInteractor;
|
2022-12-14 17:33:38 +00:00
|
|
|
late final _WidgetsBindingObserver _observer;
|
2023-07-09 11:39:33 +00:00
|
|
|
|
2023-05-21 09:50:46 +00:00
|
|
|
CameraController? _cameraController;
|
2022-12-14 17:33:38 +00:00
|
|
|
|
2022-12-21 19:31:22 +00:00
|
|
|
static const _maxZoom = 7.0;
|
|
|
|
RangeValues? _zoomRange;
|
|
|
|
double _currentZoom = 0.0;
|
|
|
|
|
|
|
|
static const _exposureMaxRange = RangeValues(-4, 4);
|
|
|
|
RangeValues? _exposureOffsetRange;
|
2023-09-28 21:29:33 +00:00
|
|
|
double _exposureStep = 0.1;
|
2022-12-21 19:31:22 +00:00
|
|
|
double _currentExposureOffset = 0.0;
|
|
|
|
|
2023-05-16 09:47:53 +00:00
|
|
|
double? _ev100 = 0.0;
|
2023-02-19 10:26:14 +00:00
|
|
|
|
2023-07-09 11:39:33 +00:00
|
|
|
bool _settingsOpened = false;
|
|
|
|
|
2023-01-29 16:57:47 +00:00
|
|
|
CameraContainerBloc(
|
2023-01-26 09:10:23 +00:00
|
|
|
this._meteringInteractor,
|
2023-01-29 16:57:47 +00:00
|
|
|
MeteringCommunicationBloc communicationBloc,
|
2023-01-21 10:37:49 +00:00
|
|
|
) : super(
|
2022-12-15 11:00:28 +00:00
|
|
|
communicationBloc,
|
|
|
|
const CameraInitState(),
|
|
|
|
) {
|
2022-12-14 17:33:38 +00:00
|
|
|
_observer = _WidgetsBindingObserver(_appLifecycleStateObserver);
|
|
|
|
WidgetsBinding.instance.addObserver(_observer);
|
|
|
|
|
2023-02-10 21:49:51 +00:00
|
|
|
on<RequestPermissionEvent>(_onRequestPermission);
|
|
|
|
on<OpenAppSettingsEvent>(_onOpenAppSettings);
|
2022-12-15 11:00:28 +00:00
|
|
|
on<InitializeEvent>(_onInitialize);
|
2023-05-19 09:12:10 +00:00
|
|
|
on<DeinitializeEvent>(_onDeinitialize);
|
2022-12-21 19:31:22 +00:00
|
|
|
on<ZoomChangedEvent>(_onZoomChanged);
|
|
|
|
on<ExposureOffsetChangedEvent>(_onExposureOffsetChanged);
|
2023-01-21 10:37:49 +00:00
|
|
|
on<ExposureOffsetResetEvent>(_onExposureOffsetResetEvent);
|
2023-11-07 16:14:53 +00:00
|
|
|
on<ExposureSpotChangedEvent>(_onExposureSpotChangedEvent);
|
2022-12-14 17:33:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> close() async {
|
|
|
|
WidgetsBinding.instance.removeObserver(_observer);
|
2023-05-21 09:50:46 +00:00
|
|
|
unawaited(_cameraController?.dispose().then((_) => _cameraController = null));
|
2023-02-19 10:26:14 +00:00
|
|
|
communicationBloc.add(communication_event.MeteringEndedEvent(_ev100));
|
2023-01-29 16:57:47 +00:00
|
|
|
return super.close();
|
2022-12-14 17:33:38 +00:00
|
|
|
}
|
|
|
|
|
2022-12-15 11:00:28 +00:00
|
|
|
@override
|
|
|
|
void onCommunicationState(communication_states.SourceState communicationState) {
|
2023-07-09 11:39:33 +00:00
|
|
|
switch (communicationState) {
|
|
|
|
case communication_states.MeasureState():
|
|
|
|
if (_canTakePhoto) {
|
|
|
|
_takePhoto().then((ev100Raw) {
|
|
|
|
if (ev100Raw != null) {
|
|
|
|
_ev100 = ev100Raw + _meteringInteractor.cameraEvCalibration;
|
|
|
|
communicationBloc.add(communication_event.MeteringEndedEvent(_ev100));
|
|
|
|
} else {
|
|
|
|
_ev100 = null;
|
|
|
|
communicationBloc.add(const communication_event.MeteringEndedEvent(null));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
case communication_states.SettingsOpenedState():
|
|
|
|
_settingsOpened = true;
|
|
|
|
add(const DeinitializeEvent());
|
|
|
|
case communication_states.SettingsClosedState():
|
|
|
|
_settingsOpened = false;
|
|
|
|
add(const InitializeEvent());
|
|
|
|
default:
|
2022-12-14 17:33:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-10 21:49:51 +00:00
|
|
|
Future<void> _onRequestPermission(_, Emitter emit) async {
|
2023-07-10 15:49:34 +00:00
|
|
|
final hasPermission = await _meteringInteractor.requestCameraPermission();
|
2023-02-10 21:49:51 +00:00
|
|
|
if (!hasPermission) {
|
|
|
|
emit(const CameraErrorState(CameraErrorType.permissionNotGranted));
|
|
|
|
} else {
|
|
|
|
add(const InitializeEvent());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _onOpenAppSettings(_, Emitter emit) async {
|
|
|
|
_meteringInteractor.openAppSettings();
|
|
|
|
}
|
|
|
|
|
2022-12-15 11:00:28 +00:00
|
|
|
Future<void> _onInitialize(_, Emitter emit) async {
|
2022-12-14 17:33:38 +00:00
|
|
|
emit(const CameraLoadingState());
|
2023-02-10 21:49:51 +00:00
|
|
|
final hasPermission = await _meteringInteractor.checkCameraPermission();
|
|
|
|
if (!hasPermission) {
|
|
|
|
emit(const CameraErrorState(CameraErrorType.permissionNotGranted));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-12-14 17:33:38 +00:00
|
|
|
try {
|
|
|
|
final cameras = await availableCameras();
|
2023-02-10 21:49:51 +00:00
|
|
|
if (cameras.isEmpty) {
|
|
|
|
emit(const CameraErrorState(CameraErrorType.noCamerasDetected));
|
|
|
|
return;
|
|
|
|
}
|
2022-12-14 17:33:38 +00:00
|
|
|
_cameraController = CameraController(
|
|
|
|
cameras.firstWhere(
|
|
|
|
(camera) => camera.lensDirection == CameraLensDirection.back,
|
|
|
|
orElse: () => cameras.last,
|
|
|
|
),
|
2023-08-06 14:28:20 +00:00
|
|
|
ResolutionPreset.low,
|
2022-12-14 17:33:38 +00:00
|
|
|
enableAudio: false,
|
|
|
|
);
|
|
|
|
|
2023-05-21 09:50:46 +00:00
|
|
|
await _cameraController!.initialize();
|
|
|
|
await _cameraController!.setFlashMode(FlashMode.off);
|
2022-12-21 19:31:22 +00:00
|
|
|
|
|
|
|
_zoomRange = await Future.wait<double>([
|
2023-05-21 09:50:46 +00:00
|
|
|
_cameraController!.getMinZoomLevel(),
|
|
|
|
_cameraController!.getMaxZoomLevel(),
|
2023-06-20 06:43:49 +00:00
|
|
|
]).then((levels) => RangeValues(math.max(1.0, levels[0]), math.min(_maxZoom, levels[1])));
|
2022-12-21 19:31:22 +00:00
|
|
|
_currentZoom = _zoomRange!.start;
|
|
|
|
|
|
|
|
_exposureOffsetRange = await Future.wait<double>([
|
2023-05-21 09:50:46 +00:00
|
|
|
_cameraController!.getMinExposureOffset(),
|
|
|
|
_cameraController!.getMaxExposureOffset(),
|
2023-01-26 15:03:48 +00:00
|
|
|
]).then(
|
|
|
|
(levels) => RangeValues(
|
2023-05-16 09:47:53 +00:00
|
|
|
math.max(_exposureMaxRange.start, levels[0]),
|
|
|
|
math.min(_exposureMaxRange.end, levels[1]),
|
2023-01-26 15:03:48 +00:00
|
|
|
),
|
|
|
|
);
|
2023-05-21 09:50:46 +00:00
|
|
|
await _cameraController!.getExposureOffsetStepSize().then((value) {
|
2022-12-21 19:31:22 +00:00
|
|
|
_exposureStep = value == 0 ? 0.1 : value;
|
|
|
|
});
|
2023-01-22 19:40:44 +00:00
|
|
|
_currentExposureOffset = 0.0;
|
2022-12-21 19:31:22 +00:00
|
|
|
|
2023-05-21 09:50:46 +00:00
|
|
|
emit(CameraInitializedState(_cameraController!));
|
2022-12-21 19:31:22 +00:00
|
|
|
|
|
|
|
_emitActiveState(emit);
|
2022-12-14 17:33:38 +00:00
|
|
|
} catch (e) {
|
2023-02-10 21:49:51 +00:00
|
|
|
emit(const CameraErrorState(CameraErrorType.other));
|
2022-12-14 17:33:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-19 09:12:10 +00:00
|
|
|
Future<void> _onDeinitialize(DeinitializeEvent _, Emitter emit) async {
|
2023-07-09 11:39:33 +00:00
|
|
|
emit(const CameraInitState());
|
|
|
|
communicationBloc.add(communication_event.MeteringEndedEvent(_ev100));
|
|
|
|
await _cameraController?.dispose().then((_) => _cameraController = null);
|
2023-05-19 09:12:10 +00:00
|
|
|
}
|
|
|
|
|
2022-12-21 19:31:22 +00:00
|
|
|
Future<void> _onZoomChanged(ZoomChangedEvent event, Emitter emit) async {
|
2023-11-07 16:14:53 +00:00
|
|
|
if (_cameraController != null && event.value >= _zoomRange!.start && event.value <= _zoomRange!.end) {
|
2023-05-21 09:50:46 +00:00
|
|
|
_cameraController!.setZoomLevel(event.value);
|
|
|
|
_currentZoom = event.value;
|
|
|
|
_emitActiveState(emit);
|
|
|
|
}
|
2022-12-21 19:31:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _onExposureOffsetChanged(ExposureOffsetChangedEvent event, Emitter emit) async {
|
2023-05-21 09:50:46 +00:00
|
|
|
if (_cameraController != null) {
|
|
|
|
_cameraController!.setExposureOffset(event.value);
|
|
|
|
_currentExposureOffset = event.value;
|
|
|
|
_emitActiveState(emit);
|
|
|
|
}
|
2022-12-21 19:31:22 +00:00
|
|
|
}
|
|
|
|
|
2023-01-21 10:37:49 +00:00
|
|
|
Future<void> _onExposureOffsetResetEvent(ExposureOffsetResetEvent event, Emitter emit) async {
|
2023-01-26 09:10:23 +00:00
|
|
|
_meteringInteractor.quickVibration();
|
2023-01-21 10:37:49 +00:00
|
|
|
add(const ExposureOffsetChangedEvent(0));
|
|
|
|
}
|
|
|
|
|
2023-11-07 16:14:53 +00:00
|
|
|
Future<void> _onExposureSpotChangedEvent(ExposureSpotChangedEvent event, Emitter emit) async {
|
|
|
|
if (_cameraController != null) {
|
|
|
|
_cameraController!.setExposurePoint(event.offset);
|
|
|
|
_cameraController!.setFocusPoint(event.offset);
|
|
|
|
_emitActiveState(emit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-21 19:31:22 +00:00
|
|
|
void _emitActiveState(Emitter emit) {
|
2023-05-11 13:30:18 +00:00
|
|
|
emit(
|
|
|
|
CameraActiveState(
|
|
|
|
zoomRange: _zoomRange!,
|
|
|
|
currentZoom: _currentZoom,
|
|
|
|
exposureOffsetRange: _exposureOffsetRange!,
|
|
|
|
exposureOffsetStep: _exposureStep,
|
|
|
|
currentExposureOffset: _currentExposureOffset,
|
|
|
|
),
|
|
|
|
);
|
2022-12-21 19:31:22 +00:00
|
|
|
}
|
|
|
|
|
2023-09-28 21:29:33 +00:00
|
|
|
bool get _canTakePhoto =>
|
|
|
|
PlatformConfig.cameraStubImage.isNotEmpty ||
|
|
|
|
!(_cameraController == null ||
|
|
|
|
!_cameraController!.value.isInitialized ||
|
|
|
|
_cameraController!.value.isTakingPicture);
|
2022-12-14 17:33:38 +00:00
|
|
|
|
2023-05-16 09:47:53 +00:00
|
|
|
Future<double?> _takePhoto() async {
|
2022-12-14 17:33:38 +00:00
|
|
|
try {
|
2023-08-03 20:46:01 +00:00
|
|
|
// https://github.com/flutter/flutter/issues/84957#issuecomment-1661155095
|
2023-09-28 21:29:33 +00:00
|
|
|
|
|
|
|
late final Uint8List bytes;
|
|
|
|
if (PlatformConfig.cameraStubImage.isNotEmpty) {
|
|
|
|
bytes = (await rootBundle.load(PlatformConfig.cameraStubImage)).buffer.asUint8List();
|
|
|
|
} else {
|
|
|
|
await _cameraController!.setFocusMode(FocusMode.locked);
|
|
|
|
await _cameraController!.setExposureMode(ExposureMode.locked);
|
|
|
|
final file = await _cameraController!.takePicture();
|
|
|
|
await _cameraController!.setFocusMode(FocusMode.auto);
|
|
|
|
await _cameraController!.setExposureMode(ExposureMode.auto);
|
|
|
|
bytes = await file.readAsBytes();
|
|
|
|
Directory(file.path).deleteSync(recursive: true);
|
|
|
|
}
|
2022-12-14 17:33:38 +00:00
|
|
|
|
|
|
|
final tags = await readExifFromBytes(bytes);
|
2023-05-16 09:47:53 +00:00
|
|
|
final iso = double.tryParse("${tags["EXIF ISOSpeedRatings"]}");
|
|
|
|
final apertureValueRatio = (tags["EXIF FNumber"]?.values as IfdRatios?)?.ratios.first;
|
|
|
|
final speedValueRatio = (tags["EXIF ExposureTime"]?.values as IfdRatios?)?.ratios.first;
|
|
|
|
if (iso == null || apertureValueRatio == null || speedValueRatio == null) {
|
|
|
|
log('Error parsing EXIF: ${tags.keys}');
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2022-12-14 17:33:38 +00:00
|
|
|
final aperture = apertureValueRatio.numerator / apertureValueRatio.denominator;
|
|
|
|
final speed = speedValueRatio.numerator / speedValueRatio.denominator;
|
|
|
|
|
2023-05-16 09:47:53 +00:00
|
|
|
return log2(math.pow(aperture, 2)) - log2(speed) - log2(iso / 100);
|
2023-06-20 06:43:49 +00:00
|
|
|
} catch (e) {
|
|
|
|
log(e.toString());
|
2022-12-14 17:33:38 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _appLifecycleStateObserver(AppLifecycleState state) async {
|
2023-07-09 11:39:33 +00:00
|
|
|
if (!_settingsOpened) {
|
|
|
|
switch (state) {
|
|
|
|
case AppLifecycleState.resumed:
|
|
|
|
add(const InitializeEvent());
|
|
|
|
case AppLifecycleState.paused:
|
|
|
|
case AppLifecycleState.detached:
|
|
|
|
add(const DeinitializeEvent());
|
|
|
|
default:
|
|
|
|
}
|
2022-12-14 17:33:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This is needed only because we cannot use `with` with mixins
|
|
|
|
class _WidgetsBindingObserver with WidgetsBindingObserver {
|
|
|
|
final ValueChanged<AppLifecycleState> onLifecycleStateChanged;
|
2023-02-10 21:49:51 +00:00
|
|
|
AppLifecycleState? _prevState;
|
2022-12-14 17:33:38 +00:00
|
|
|
|
|
|
|
_WidgetsBindingObserver(this.onLifecycleStateChanged);
|
|
|
|
|
|
|
|
@override
|
|
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
2023-02-10 21:49:51 +00:00
|
|
|
if (_prevState == AppLifecycleState.inactive && state == AppLifecycleState.resumed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_prevState = state;
|
2022-12-14 17:33:38 +00:00
|
|
|
onLifecycleStateChanged(state);
|
|
|
|
}
|
|
|
|
}
|