Added camera package to project

added camera plugin to project

more reverse naming

added communication bloc

layout fix
This commit is contained in:
Vadim 2022-12-14 20:33:38 +03:00
parent 7018f06270
commit 9b0b387514
18 changed files with 413 additions and 58 deletions

View file

@ -43,7 +43,7 @@ android {
}
defaultConfig {
minSdkVersion flutter.minSdkVersion
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

View file

@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View file

@ -0,0 +1,126 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:exif/exif.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
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;
import 'package:lightmeter/utils/log_2.dart';
import 'event_camera.dart';
import 'state_camera.dart';
class CameraBloc extends Bloc<CameraEvent, CameraState> {
final MeteringCommunicationBloc _communicationBloc;
late final StreamSubscription<communication_states.MeteringCommunicationState> _communicationSubscription;
late final _WidgetsBindingObserver _observer;
CameraController? _cameraController;
CameraController? get cameraController => _cameraController;
CameraBloc(this._communicationBloc) : super(const CameraInitState()) {
_communicationSubscription = _communicationBloc.stream.listen(_onCommunicationState);
_observer = _WidgetsBindingObserver(_appLifecycleStateObserver);
WidgetsBinding.instance.addObserver(_observer);
on<InitializeEvent>(_onInitialized);
add(const InitializeEvent());
}
@override
Future<void> close() async {
WidgetsBinding.instance.removeObserver(_observer);
_cameraController?.dispose();
await _communicationSubscription.cancel();
super.close();
}
void _onCommunicationState(communication_states.MeteringCommunicationState communicationState) {
if (communicationState is communication_states.MeasureState) {
_takePhoto().then((ev100) {
if (ev100 != null) {
_communicationBloc.add(communication_event.MeasuredEvent(ev100));
}
});
}
}
Future<void> _onInitialized(_, Emitter emit) async {
emit(const CameraLoadingState());
try {
final cameras = await availableCameras();
_cameraController = CameraController(
cameras.firstWhere(
(camera) => camera.lensDirection == CameraLensDirection.back,
orElse: () => cameras.last,
),
ResolutionPreset.medium,
enableAudio: false,
);
await _cameraController!.initialize();
await _cameraController!.setFlashMode(FlashMode.off);
emit(CameraReadyState(_cameraController!));
} catch (e) {
emit(const CameraErrorState());
}
}
Future<double?> _takePhoto() async {
if (_cameraController == null ||
!_cameraController!.value.isInitialized ||
_cameraController!.value.isTakingPicture) {
return null;
}
try {
final file = await _cameraController!.takePicture();
final Uint8List bytes = await file.readAsBytes();
Directory(file.path).deleteSync(recursive: true);
final tags = await readExifFromBytes(bytes);
final iso = double.parse("${tags["EXIF ISOSpeedRatings"]}");
final apertureValueRatio = (tags["EXIF FNumber"]!.values as IfdRatios).ratios.first;
final aperture = apertureValueRatio.numerator / apertureValueRatio.denominator;
final speedValueRatio = (tags["EXIF ExposureTime"]!.values as IfdRatios).ratios.first;
final speed = speedValueRatio.numerator / speedValueRatio.denominator;
return log2(pow(aperture, 2)) - log2(speed) - log2(iso / 100);
} on CameraException catch (e) {
debugPrint('Error: ${e.code}\nError Message: ${e.description}');
return null;
}
}
Future<void> _appLifecycleStateObserver(AppLifecycleState state) async {
switch (state) {
case AppLifecycleState.resumed:
add(const InitializeEvent());
break;
case AppLifecycleState.paused:
case AppLifecycleState.inactive:
_cameraController?.dispose();
_cameraController = null;
break;
default:
}
}
}
/// This is needed only because we cannot use `with` with mixins
class _WidgetsBindingObserver with WidgetsBindingObserver {
final ValueChanged<AppLifecycleState> onLifecycleStateChanged;
_WidgetsBindingObserver(this.onLifecycleStateChanged);
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
onLifecycleStateChanged(state);
}
}

View file

@ -0,0 +1,7 @@
abstract class CameraEvent {
const CameraEvent();
}
class InitializeEvent extends CameraEvent {
const InitializeEvent();
}

View file

@ -0,0 +1,23 @@
import 'package:camera/camera.dart';
abstract class CameraState {
const CameraState();
}
class CameraInitState extends CameraState {
const CameraInitState();
}
class CameraLoadingState extends CameraState {
const CameraLoadingState();
}
class CameraReadyState extends CameraState {
final CameraController controller;
const CameraReadyState(this.controller);
}
class CameraErrorState extends CameraState {
const CameraErrorState();
}

View file

@ -1,16 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:lightmeter/data/permissions_service.dart';
import 'package:lightmeter/screens/settings/settings_screen.dart';
import 'package:provider/provider.dart';
import 'generated/l10n.dart';
import 'models/photography_value.dart';
import 'res/theme.dart';
import 'screens/metering/metering_bloc.dart';
import 'screens/metering/metering_screen.dart';
import 'screens/metering/flow_metering.dart';
import 'utils/stop_type_provider.dart';
void main() {
@ -45,30 +42,27 @@ class _ApplicationState extends State<Application> {
return Provider(
create: (context) => PermissionsService(),
child: StopTypeProvider(
child: BlocProvider(
create: (context) => MeteringBloc(context.read<StopType>()),
child: MaterialApp(
theme: ThemeData(
useMaterial3: true,
colorScheme: lightColorScheme,
),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
builder: (context, child) => MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: child!,
),
home: const MeteringScreen(),
routes: {
"metering": (context) => const MeteringScreen(),
"settings": (context) => const SettingsScreen(),
},
child: MaterialApp(
theme: ThemeData(
useMaterial3: true,
colorScheme: lightColorScheme,
),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
builder: (context, child) => MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: child!,
),
home: const MeteringFlow(),
routes: {
"metering": (context) => const MeteringFlow(),
"settings": (context) => const SettingsScreen(),
},
),
),
);

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:math';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -7,19 +8,25 @@ import 'package:lightmeter/models/iso_value.dart';
import 'package:lightmeter/models/nd_value.dart';
import 'package:lightmeter/models/photography_value.dart';
import 'package:lightmeter/models/shutter_speed_value.dart';
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' as communication_events;
import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' as communication_states;
import 'package:lightmeter/utils/log_2.dart';
import 'metering_event.dart';
import 'metering_state.dart';
import 'communication/bloc_communication_metering.dart';
import 'event_metering.dart';
import 'state_metering.dart';
class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
final MeteringCommunicationBloc _communicationBloc;
late final StreamSubscription<communication_states.ScreenState> _communicationSubscription;
List<ApertureValue> get _apertureValues => apertureValues.whereStopType(stopType);
List<ShutterSpeedValue> get _shutterSpeedValues => shutterSpeedValues.whereStopType(stopType);
final _random = Random();
StopType stopType;
MeteringBloc(this.stopType)
MeteringBloc(this._communicationBloc, this.stopType)
: super(
MeteringState(
iso: isoValues.where((element) => element.value == 100).first,
@ -29,14 +36,42 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
exposurePairs: [],
),
) {
_communicationSubscription = _communicationBloc.stream
.where((state) => state is communication_states.ScreenState)
.map((state) => state as communication_states.ScreenState)
.listen(_onCommunicationState);
on<StopTypeChangedEvent>(_onStopTypeChanged);
on<IsoChangedEvent>(_onIsoChanged);
on<NdChangedEvent>(_onNdChanged);
on<MeasureEvent>(_onMeasure);
on<MeasureEvent>((_, __) => _communicationBloc.add(const communication_events.MeasureEvent()));
on<MeasuredEvent>(_onMeasured);
add(const MeasureEvent());
}
@override
Future<void> close() async {
await _communicationSubscription.cancel();
return super.close();
}
void _onCommunicationState(communication_states.ScreenState communicationState) {
if (communicationState is communication_states.MeasuredState) {
add(MeasuredEvent(communicationState.ev100));
}
}
/// https://www.scantips.com/lights/exposurecalc.html
Future<double> measureEv() async {
final aperture = _apertureValues[_random.nextInt(_apertureValues.length)];
final shutterSpeed = _shutterSpeedValues[_random.nextInt(_shutterSpeedValues.thirdStops().length)];
final iso = isoValues[_random.nextInt(isoValues.thirdStops().length)];
final evAtSystemIso = log2(pow(aperture.value, 2).toDouble() / shutterSpeed.value);
return evAtSystemIso - log2(iso.value / state.iso.value);
}
void _onStopTypeChanged(StopTypeChangedEvent event, Emitter emit) {
stopType = event.stopType;
emit(MeteringState(
@ -70,15 +105,8 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
));
}
/// https://www.scantips.com/lights/exposurecalc.html
void _onMeasure(_, Emitter emit) {
final aperture = _apertureValues[_random.nextInt(_apertureValues.length)];
final shutterSpeed = _shutterSpeedValues[_random.nextInt(_shutterSpeedValues.thirdStops().length)];
final iso = isoValues[_random.nextInt(isoValues.thirdStops().length)];
final evAtSystemIso = log2(pow(aperture.value, 2).toDouble() / shutterSpeed.value);
final ev = evAtSystemIso - log2(iso.value / state.iso.value);
void _onMeasured(MeasuredEvent event, Emitter emit) {
final ev = event.ev100 + log2(state.iso.value / 100);
emit(MeteringState(
iso: state.iso,
ev: ev,

View file

@ -0,0 +1,11 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'event_communication_metering.dart';
import 'state_communication_metering.dart';
class MeteringCommunicationBloc extends Bloc<MeteringCommunicationEvent, MeteringCommunicationState> {
MeteringCommunicationBloc() : super(const InitState()) {
on<MeasureEvent>((_, emit) => emit(const MeasureState()));
on<MeasuredEvent>((event, emit) => emit(MeasuredState(event.ev100)));
}
}

View file

@ -0,0 +1,21 @@
abstract class MeteringCommunicationEvent {
const MeteringCommunicationEvent();
}
abstract class SourceEvent extends MeteringCommunicationEvent {
const SourceEvent();
}
abstract class ScreenEvent extends MeteringCommunicationEvent {
const ScreenEvent();
}
class MeasureEvent extends ScreenEvent {
const MeasureEvent();
}
class MeasuredEvent extends SourceEvent {
final double ev100;
const MeasuredEvent(this.ev100);
}

View file

@ -0,0 +1,25 @@
abstract class MeteringCommunicationState {
const MeteringCommunicationState();
}
class InitState extends MeteringCommunicationState {
const InitState();
}
abstract class SourceState extends MeteringCommunicationState {
const SourceState();
}
abstract class ScreenState extends MeteringCommunicationState {
const ScreenState();
}
class MeasureState extends SourceState {
const MeasureState();
}
class MeasuredState extends ScreenState {
final double ev100;
const MeasuredState(this.ev100);
}

View file

@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/res/dimens.dart';
class ZoomSlider extends StatelessWidget {
const ZoomSlider({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL),
child: Row(
children: [
const Icon(Icons.zoom_out),
Expanded(
child: Slider(
value: 0,
onChanged: (value) {},
),
),
const Icon(Icons.zoom_in),
],
),
);
}
}

View file

@ -0,0 +1,57 @@
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/data/ev_source/camera/bloc_camera.dart';
import 'package:lightmeter/data/ev_source/camera/state_camera.dart';
class CameraView extends StatelessWidget {
const CameraView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 3 / 4,
child: BlocBuilder<CameraBloc, CameraState>(
builder: (context, state) {
if (state is CameraReadyState) {
final value = state.controller.value;
return ValueListenableBuilder<CameraValue>(
valueListenable: state.controller,
builder: (_, __, ___) => AspectRatio(
aspectRatio:
_isLandscape(value) ? value.aspectRatio : (1 / value.aspectRatio),
child: RotatedBox(
quarterTurns: _getQuarterTurns(value),
child: state.controller.buildPreview(),
),
),
);
}
return const ColoredBox(color: Colors.black);
},
),
);
}
bool _isLandscape(CameraValue value) {
return <DeviceOrientation>[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]
.contains(_getApplicableOrientation(value));
}
int _getQuarterTurns(CameraValue value) {
final Map<DeviceOrientation, int> turns = <DeviceOrientation, int>{
DeviceOrientation.portraitUp: 0,
DeviceOrientation.landscapeRight: 1,
DeviceOrientation.portraitDown: 2,
DeviceOrientation.landscapeLeft: 3,
};
return turns[_getApplicableOrientation(value)]!;
}
DeviceOrientation _getApplicableOrientation(CameraValue value) {
return value.isRecordingVideo
? value.recordingOrientation!
: (value.previewPauseOrientation ?? value.lockedCaptureOrientation ?? value.deviceOrientation);
}
}

View file

@ -6,6 +6,7 @@ import 'package:lightmeter/models/nd_value.dart';
import 'package:lightmeter/models/photography_value.dart';
import 'package:lightmeter/res/dimens.dart';
import 'components/camera_preview.dart';
import 'components/shared/animated_dialog.dart';
import 'components/dialog_picker.dart';
import 'components/reading_container.dart';
@ -74,6 +75,13 @@ class MeteringTopBar extends StatelessWidget {
),
),
const _InnerPadding(),
ReadingContainer.singleValue(
value: ReadingValue(
label: 'EV',
value: ev.toString(),
),
),
const _InnerPadding(),
Row(
children: [
Expanded(
@ -111,18 +119,8 @@ class MeteringTopBar extends StatelessWidget {
const _InnerPadding(),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AnimatedDialog(
openedSize: Size(
MediaQuery.of(context).size.width - Dimens.paddingM * 2,
(MediaQuery.of(context).size.width - Dimens.paddingM * 2) / 3 * 4,
),
child: const AspectRatio(
aspectRatio: 3 / 4,
child: ColoredBox(color: Colors.black),
),
),
const CameraView(),
],
),
),

View file

@ -27,3 +27,9 @@ class NdChangedEvent extends MeteringEvent {
class MeasureEvent extends MeteringEvent {
const MeasureEvent();
}
class MeasuredEvent extends MeteringEvent {
final double ev100;
const MeasuredEvent(this.ev100);
}

View file

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/data/ev_source/camera/bloc_camera.dart';
import 'package:lightmeter/models/photography_value.dart';
import 'package:lightmeter/screens/metering/bloc_metering.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
import 'screen_metering.dart';
class MeteringFlow extends StatelessWidget {
const MeteringFlow({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => MeteringCommunicationBloc()),
BlocProvider(
create: (context) => MeteringBloc(
context.read<MeteringCommunicationBloc>(),
context.read<StopType>(),
),
),
BlocProvider(create: (context) => CameraBloc(context.read<MeteringCommunicationBloc>())),
],
child: const MeteringScreen(),
);
}
}

View file

@ -7,9 +7,9 @@ import 'package:lightmeter/screens/settings/settings_screen.dart';
import 'components/bottom_controls/bottom_controls.dart';
import 'components/exposure_pairs_list/exposure_pairs_list.dart';
import 'components/topbar/topbar.dart';
import 'metering_bloc.dart';
import 'metering_event.dart';
import 'metering_state.dart';
import 'bloc_metering.dart';
import 'event_metering.dart';
import 'state_metering.dart';
class MeteringScreen extends StatefulWidget {
const MeteringScreen({super.key});

View file

@ -1,12 +1,14 @@
name: lightmeter
description: A new Flutter project.
publish_to: 'none'
publish_to: "none"
version: 1.0.0+1
environment:
sdk: '>=2.18.0 <3.0.0'
sdk: ">=2.18.0 <3.0.0"
dependencies:
camera: 0.10.0+4
exif: 3.1.2
flutter:
sdk: flutter
flutter_bloc: ^8.1.1
@ -61,4 +63,4 @@ flutter:
# see https://flutter.dev/custom-fonts/#from-packages
flutter_intl:
enabled: true
enabled: true