mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-21 23:10:40 +00:00
Added camera
package to project
added camera plugin to project more reverse naming added communication bloc layout fix
This commit is contained in:
parent
7018f06270
commit
9b0b387514
18 changed files with 413 additions and 58 deletions
|
@ -43,7 +43,7 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
minSdkVersion 21
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
|
3
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
3
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
|
@ -4,4 +4,7 @@
|
|||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
|
126
lib/data/ev_source/camera/bloc_camera.dart
Normal file
126
lib/data/ev_source/camera/bloc_camera.dart
Normal 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);
|
||||
}
|
||||
}
|
7
lib/data/ev_source/camera/event_camera.dart
Normal file
7
lib/data/ev_source/camera/event_camera.dart
Normal file
|
@ -0,0 +1,7 @@
|
|||
abstract class CameraEvent {
|
||||
const CameraEvent();
|
||||
}
|
||||
|
||||
class InitializeEvent extends CameraEvent {
|
||||
const InitializeEvent();
|
||||
}
|
23
lib/data/ev_source/camera/state_camera.dart
Normal file
23
lib/data/ev_source/camera/state_camera.dart
Normal 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();
|
||||
}
|
|
@ -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,8 +42,6 @@ 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,
|
||||
|
@ -63,14 +58,13 @@ class _ApplicationState extends State<Application> {
|
|||
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
|
||||
child: child!,
|
||||
),
|
||||
home: const MeteringScreen(),
|
||||
home: const MeteringFlow(),
|
||||
routes: {
|
||||
"metering": (context) => const MeteringScreen(),
|
||||
"metering": (context) => const MeteringFlow(),
|
||||
"settings": (context) => const SettingsScreen(),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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);
|
||||
}
|
29
lib/screens/metering/flow_metering.dart
Normal file
29
lib/screens/metering/flow_metering.dart
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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});
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue