mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-22 15:30:59 +00:00
implemented MockCameraContainerBloc
to stub camera on simulator
This commit is contained in:
parent
55b0e52d7f
commit
184ef3a916
7 changed files with 150 additions and 35 deletions
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
|
@ -82,5 +82,20 @@
|
|||
],
|
||||
"program": "${workspaceFolder}/lib/main_dev.dart",
|
||||
},
|
||||
{
|
||||
"name": "dev-simulator",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "debug",
|
||||
"args": [
|
||||
"--flavor",
|
||||
"dev",
|
||||
"--dart-define",
|
||||
"cameraPreviewAspectRatio=240/320",
|
||||
"--dart-define",
|
||||
"cameraStubImage=assets/camera_stub_image.jpg"
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main_dev.dart",
|
||||
},
|
||||
],
|
||||
}
|
|
@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:lightmeter/data/models/supported_locale.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/platform_config.dart';
|
||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||
import 'package:lightmeter/screens/metering/flow_metering.dart';
|
||||
import 'package:lightmeter/screens/settings/flow_settings.dart';
|
||||
|
@ -17,13 +18,13 @@ class Application extends StatelessWidget {
|
|||
return AnnotatedRegion(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarBrightness:
|
||||
systemIconsBrightness == Brightness.light ? Brightness.dark : Brightness.light,
|
||||
statusBarBrightness: systemIconsBrightness == Brightness.light ? Brightness.dark : Brightness.light,
|
||||
statusBarIconBrightness: systemIconsBrightness,
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
systemNavigationBarIconBrightness: systemIconsBrightness,
|
||||
),
|
||||
child: MaterialApp(
|
||||
debugShowCheckedModeBanner: !PlatformConfig.isTest,
|
||||
theme: theme,
|
||||
locale: Locale(UserPreferencesProvider.localeOf(context).intlName),
|
||||
localizationsDelegates: const [
|
||||
|
|
|
@ -7,4 +7,6 @@ class PlatformConfig {
|
|||
}
|
||||
|
||||
static String get cameraStubImage => const String.fromEnvironment('cameraStubImage');
|
||||
|
||||
static bool get isTest => cameraStubImage.isNotEmpty;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import 'dart:io';
|
|||
import 'dart:math' as math;
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:exif/exif.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
@ -17,7 +16,9 @@ import 'package:lightmeter/screens/metering/components/camera_container/event_co
|
|||
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';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
import 'package:lightmeter/utils/ev_from_bytes.dart';
|
||||
|
||||
part 'mock_bloc_container_camera.dart';
|
||||
|
||||
class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraContainerState> {
|
||||
final MeteringInteractor _meteringInteractor;
|
||||
|
@ -213,33 +214,15 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
|||
Future<double?> _takePhoto() async {
|
||||
try {
|
||||
// https://github.com/flutter/flutter/issues/84957#issuecomment-1661155095
|
||||
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);
|
||||
final bytes = await file.readAsBytes();
|
||||
Directory(file.path).deleteSync(recursive: true);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
final tags = await readExifFromBytes(bytes);
|
||||
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;
|
||||
}
|
||||
|
||||
final aperture = apertureValueRatio.numerator / apertureValueRatio.denominator;
|
||||
final speed = speedValueRatio.numerator / speedValueRatio.denominator;
|
||||
|
||||
return log2(math.pow(aperture, 2)) - log2(speed) - log2(iso / 100);
|
||||
return await evFromImage(bytes);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return null;
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
part of 'bloc_container_camera.dart';
|
||||
|
||||
class MockCameraContainerBloc extends CameraContainerBloc {
|
||||
MockCameraContainerBloc(
|
||||
super._meteringInteractor,
|
||||
super.communicationBloc,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> _onRequestPermission(_, Emitter emit) async {
|
||||
add(const InitializeEvent());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> _onOpenAppSettings(_, Emitter emit) async {
|
||||
_meteringInteractor.openAppSettings();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> _onInitialize(_, Emitter emit) async {
|
||||
emit(const CameraLoadingState());
|
||||
try {
|
||||
_cameraController = CameraController(
|
||||
const CameraDescription(name: '0', lensDirection: CameraLensDirection.back, sensorOrientation: 0),
|
||||
ResolutionPreset.low,
|
||||
enableAudio: false,
|
||||
);
|
||||
|
||||
_zoomRange = const RangeValues(1, 6);
|
||||
_currentZoom = _zoomRange!.start;
|
||||
|
||||
_exposureOffsetRange = const RangeValues(-4, 4);
|
||||
_exposureStep = 0.1;
|
||||
_currentExposureOffset = 0.0;
|
||||
|
||||
emit(CameraInitializedState(_cameraController!));
|
||||
|
||||
_emitActiveState(emit);
|
||||
} catch (e) {
|
||||
emit(const CameraErrorState(CameraErrorType.other));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> _onZoomChanged(ZoomChangedEvent event, Emitter emit) async {
|
||||
if (event.value >= _zoomRange!.start && event.value <= _zoomRange!.end) {
|
||||
_currentZoom = event.value;
|
||||
_emitActiveState(emit);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> _onExposureOffsetChanged(ExposureOffsetChangedEvent event, Emitter emit) async {
|
||||
_currentExposureOffset = event.value;
|
||||
_emitActiveState(emit);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> _onExposureOffsetResetEvent(ExposureOffsetResetEvent event, Emitter emit) async {
|
||||
_meteringInteractor.quickVibration();
|
||||
add(const ExposureOffsetChangedEvent(0));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> _onExposureSpotChangedEvent(ExposureSpotChangedEvent event, Emitter emit) async {}
|
||||
|
||||
@override
|
||||
bool get _canTakePhoto => PlatformConfig.cameraStubImage.isNotEmpty;
|
||||
|
||||
@override
|
||||
Future<double?> _takePhoto() async {
|
||||
try {
|
||||
final bytes = (await rootBundle.load(PlatformConfig.cameraStubImage)).buffer.asUint8List();
|
||||
return await evFromImage(bytes);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||
import 'package:lightmeter/platform_config.dart';
|
||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
||||
import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart';
|
||||
import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart';
|
||||
|
@ -30,12 +31,18 @@ class CameraContainerProvider extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
return BlocProvider<CameraContainerBloc>(
|
||||
lazy: false,
|
||||
create: (context) => CameraContainerBloc(
|
||||
MeteringInteractorProvider.of(context),
|
||||
context.read<MeteringCommunicationBloc>(),
|
||||
)..add(const RequestPermissionEvent()),
|
||||
create: (context) => (PlatformConfig.cameraStubImage.isNotEmpty
|
||||
? MockCameraContainerBloc(
|
||||
MeteringInteractorProvider.of(context),
|
||||
context.read<MeteringCommunicationBloc>(),
|
||||
)
|
||||
: CameraContainerBloc(
|
||||
MeteringInteractorProvider.of(context),
|
||||
context.read<MeteringCommunicationBloc>(),
|
||||
))
|
||||
..add(const RequestPermissionEvent()),
|
||||
child: CameraContainer(
|
||||
fastest: fastest,
|
||||
slowest: slowest,
|
||||
|
|
27
lib/utils/ev_from_bytes.dart
Normal file
27
lib/utils/ev_from_bytes.dart
Normal file
|
@ -0,0 +1,27 @@
|
|||
import 'dart:developer';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:exif/exif.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
Future<double?> evFromImage(Uint8List bytes) async {
|
||||
try {
|
||||
final tags = await readExifFromBytes(bytes);
|
||||
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;
|
||||
}
|
||||
|
||||
final aperture = apertureValueRatio.numerator / apertureValueRatio.denominator;
|
||||
final speed = speedValueRatio.numerator / speedValueRatio.denominator;
|
||||
|
||||
return log2(math.pow(aperture, 2)) - log2(speed) - log2(iso / 100);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue