From b6e377959fc6ef9ab3e9ee0c35a9b0554fd26028 Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Sat, 29 Oct 2022 21:02:45 +0300 Subject: [PATCH] added `MeteringBloc` --- lib/main.dart | 18 ++-- lib/models/exposure_pair.dart | 8 ++ .../exposure_pairs_list.dart | 57 ++---------- .../metering/components/topbar/topbar.dart | 22 +++-- lib/screens/metering/metering_bloc.dart | 90 +++++++++++++++++++ lib/screens/metering/metering_event.dart | 7 ++ lib/screens/metering/metering_screen.dart | 60 ++++++++----- lib/screens/metering/metering_state.dart | 20 +++++ pubspec.yaml | 1 + 9 files changed, 199 insertions(+), 84 deletions(-) create mode 100644 lib/models/exposure_pair.dart create mode 100644 lib/screens/metering/metering_bloc.dart create mode 100644 lib/screens/metering/metering_event.dart create mode 100644 lib/screens/metering/metering_state.dart diff --git a/lib/main.dart b/lib/main.dart index 1668269..31aff18 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'models/photography_value.dart'; import 'res/theme.dart'; +import 'screens/metering/metering_bloc.dart'; import 'screens/metering/metering_screen.dart'; void main() { @@ -12,13 +15,16 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - useMaterial3: true, - colorScheme: lightColorScheme, + return BlocProvider( + create: (context) => MeteringBloc(Stop.third), + child: MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + useMaterial3: true, + colorScheme: lightColorScheme, + ), + home: const MeteringScreen(), ), - home: const MeteringScreen(), ); } } diff --git a/lib/models/exposure_pair.dart b/lib/models/exposure_pair.dart new file mode 100644 index 0000000..cfc2ed6 --- /dev/null +++ b/lib/models/exposure_pair.dart @@ -0,0 +1,8 @@ +import 'photography_value.dart'; + +class ExposurePair { + final ApertureValue aperture; + final ShutterSpeedValue shutterSpeed; + + const ExposurePair(this.aperture, this.shutterSpeed); +} diff --git a/lib/screens/metering/components/exposure_pairs_list/exposure_pairs_list.dart b/lib/screens/metering/components/exposure_pairs_list/exposure_pairs_list.dart index 22b9609..2b66ccd 100644 --- a/lib/screens/metering/components/exposure_pairs_list/exposure_pairs_list.dart +++ b/lib/screens/metering/components/exposure_pairs_list/exposure_pairs_list.dart @@ -1,56 +1,12 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; -import 'package:lightmeter/models/photography_value.dart'; +import 'package:lightmeter/models/exposure_pair.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/metering/components/exposure_pairs_list/components/exposure_pair_item.dart'; class ExposurePairsList extends StatelessWidget { - final double ev; - final Stop stopType; - final List _apertureValuesList; - final List _shutterSpeedValuesList; - late final int _apertureOffset; - late final int _shutterSpeedOffset; - late final int _itemsCount; + final List exposurePairs; - ExposurePairsList({ - required this.ev, - required this.stopType, - super.key, - }) : _apertureValuesList = apertureValues.whereStopType(stopType), - _shutterSpeedValuesList = shutterSpeedValues.whereStopType(stopType) { - late final int evSteps; - switch (stopType) { - case Stop.full: - evSteps = ev.floor(); - break; - case Stop.half: - evSteps = (ev / 0.5).floor(); - break; - case Stop.third: - evSteps = (ev / 0.3).floor(); - break; - } - - final evOffset = _shutterSpeedValuesList.indexOf(const ShutterSpeedValue(1, false, Stop.full)) - evSteps; - if (evOffset >= 0) { - _apertureOffset = 0; - _shutterSpeedOffset = evOffset; - } else { - _apertureOffset = -evOffset; - _shutterSpeedOffset = 0; - } - - _itemsCount = min( - _apertureValuesList.length + _shutterSpeedOffset, - _shutterSpeedValuesList.length + _apertureOffset, - ) - - max( - _apertureOffset, - _shutterSpeedOffset, - ); - } + const ExposurePairsList(this.exposurePairs, {super.key}); @override Widget build(BuildContext context) { @@ -59,7 +15,8 @@ class ExposurePairsList extends StatelessWidget { children: [ Positioned.fill( child: ListView.builder( - itemCount: _itemsCount, + key: ValueKey(exposurePairs.hashCode), + itemCount: exposurePairs.length, itemBuilder: (_, index) => Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -67,7 +24,7 @@ class ExposurePairsList extends StatelessWidget { child: Align( alignment: Alignment.centerLeft, child: ExposurePaitListItem( - _apertureValuesList[index + _apertureOffset], + exposurePairs[index].aperture, tickOnTheLeft: false, ), ), @@ -77,7 +34,7 @@ class ExposurePairsList extends StatelessWidget { child: Align( alignment: Alignment.centerLeft, child: ExposurePaitListItem( - _shutterSpeedValuesList[index + _shutterSpeedOffset], + exposurePairs[index].shutterSpeed, tickOnTheLeft: true, ), ), diff --git a/lib/screens/metering/components/topbar/topbar.dart b/lib/screens/metering/components/topbar/topbar.dart index 3d7b348..90d8fbb 100644 --- a/lib/screens/metering/components/topbar/topbar.dart +++ b/lib/screens/metering/components/topbar/topbar.dart @@ -1,17 +1,21 @@ import 'package:flutter/material.dart'; +import 'package:lightmeter/models/exposure_pair.dart'; import 'package:lightmeter/res/dimens.dart'; -import 'package:lightmeter/screens/metering/components/topbar/components/reading_container.dart'; -import 'package:lightmeter/screens/metering/components/topbar/topbar_shape.dart'; -import 'package:lightmeter/utils/text_line_height.dart'; + +import 'components/reading_container.dart'; class MeteringTopBar extends StatelessWidget { static const _columnsCount = 3; + final ExposurePair? fastest; + final ExposurePair? slowest; final double ev; final int iso; final double nd; const MeteringTopBar({ + required this.fastest, + required this.slowest, required this.ev, required this.iso, required this.nd, @@ -43,14 +47,18 @@ class MeteringTopBar extends StatelessWidget { SizedBox( height: columnWidth / 3 * 4, child: ReadingContainer( - values: const [ + values: [ ReadingValue( label: 'Fastest', - value: 'f/5.6 - 1/2000', + value: fastest != null + ? '${fastest!.aperture.toString()} - ${fastest!.shutterSpeed.toString()}' + : 'N/A', ), ReadingValue( label: 'Slowest', - value: 'f/45 - 1/30', + value: fastest != null + ? '${slowest!.aperture.toString()} - ${slowest!.shutterSpeed.toString()}' + : 'N/A', ), ], ), @@ -63,7 +71,7 @@ class MeteringTopBar extends StatelessWidget { child: ReadingContainer.singleValue( value: ReadingValue( label: 'EV', - value: ev.toString(), + value: ev.toStringAsFixed(1), ), ), ), diff --git a/lib/screens/metering/metering_bloc.dart b/lib/screens/metering/metering_bloc.dart new file mode 100644 index 0000000..f5513ca --- /dev/null +++ b/lib/screens/metering/metering_bloc.dart @@ -0,0 +1,90 @@ +import 'dart:math'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lightmeter/models/exposure_pair.dart'; +import 'package:lightmeter/models/photography_value.dart'; + +import 'metering_event.dart'; +import 'metering_state.dart'; + +class MeteringBloc extends Bloc { + final Stop stopType; + late final _apertureValues = apertureValues.whereStopType(stopType); + late final _shutterSpeedValues = shutterSpeedValues.whereStopType(stopType); + late final _isoValues = isoValues.whereStopType(stopType); + final _random = Random(); + + MeteringBloc(this.stopType) + : super( + const MeteringState( + iso: 100, + ev: 21.3, + evCompensation: 0.0, + nd: 0.0, + exposurePairs: [], + ), + ) { + on(_onMeasure); + + add(const MeasureEvent()); + } + + /// https://stackoverflow.com/questions/5401738/how-to-convert-between-lux-and-exposure-value + void _onMeasure(_, Emitter emit) { + double log2(double x) => log(x) / log(2); + + 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()) - log2(shutterSpeed.value); + final ev = evAtSystemIso - log2(iso.value / state.iso); + final exposurePairs = _buildExposureValues(ev); + + emit(MeteringState( + iso: state.iso, + ev: ev, + evCompensation: state.evCompensation, + nd: state.nd, + exposurePairs: exposurePairs, + )); + } + + List _buildExposureValues(double ev) { + late final int evSteps; + switch (stopType) { + case Stop.full: + evSteps = ev.floor(); + break; + case Stop.half: + evSteps = (ev / 0.5).floor(); + break; + case Stop.third: + evSteps = (ev / 0.3).floor(); + break; + } + final evOffset = _shutterSpeedValues.indexOf(const ShutterSpeedValue(1, false, Stop.full)) - evSteps; + + late final int apertureOffset; + late final int shutterSpeedOffset; + if (evOffset >= 0) { + apertureOffset = 0; + shutterSpeedOffset = evOffset; + } else { + apertureOffset = -evOffset; + shutterSpeedOffset = 0; + } + + int itemsCount = min(_apertureValues.length + shutterSpeedOffset, _shutterSpeedValues.length + apertureOffset) - + max(apertureOffset, shutterSpeedOffset); + + return List.generate( + itemsCount, + (index) => ExposurePair( + _apertureValues[index + apertureOffset], + _shutterSpeedValues[index + shutterSpeedOffset], + ), + growable: false, + ); + } +} diff --git a/lib/screens/metering/metering_event.dart b/lib/screens/metering/metering_event.dart new file mode 100644 index 0000000..d165a52 --- /dev/null +++ b/lib/screens/metering/metering_event.dart @@ -0,0 +1,7 @@ +abstract class MeteringEvent { + const MeteringEvent(); +} + +class MeasureEvent extends MeteringEvent { + const MeasureEvent(); +} diff --git a/lib/screens/metering/metering_screen.dart b/lib/screens/metering/metering_screen.dart index d13cf20..6046bac 100644 --- a/lib/screens/metering/metering_screen.dart +++ b/lib/screens/metering/metering_screen.dart @@ -1,37 +1,55 @@ import 'package:flutter/material.dart'; -import 'package:lightmeter/models/photography_value.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lightmeter/res/dimens.dart'; +import 'package:lightmeter/screens/metering/metering_event.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_state.dart'; class MeteringScreen extends StatelessWidget { const MeteringScreen({super.key}); @override Widget build(BuildContext context) { - const ev = 0.3; return Scaffold( backgroundColor: Theme.of(context).colorScheme.background, - body: Column( - children: [ - const MeteringTopBar( - ev: ev, - iso: 6400, - nd: 0, - ), - Expanded( - child: ExposurePairsList( - ev: ev, - stopType: Stop.third, - ), - ), - MeteringBottomControls( - onSourceChanged: () {}, - onMeasure: () {}, - onSettings: () {}, - ), - ], + body: BlocBuilder( + builder: (context, state) { + return Column( + children: [ + MeteringTopBar( + fastest: state.fastest, + slowest: state.slowest, + ev: state.ev, + iso: state.iso, + nd: state.nd, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM), + child: Row( + children: [ + Expanded( + flex: 2, + child: ExposurePairsList(state.exposurePairs), + ), + const SizedBox(width: Dimens.grid16), + const Spacer() + ], + ), + ), + ), + MeteringBottomControls( + onSourceChanged: () {}, + onMeasure: () => context.read().add(const MeasureEvent()), + onSettings: () {}, + ), + ], + ); + }, ), ); } diff --git a/lib/screens/metering/metering_state.dart b/lib/screens/metering/metering_state.dart new file mode 100644 index 0000000..331d03d --- /dev/null +++ b/lib/screens/metering/metering_state.dart @@ -0,0 +1,20 @@ +import 'package:lightmeter/models/exposure_pair.dart'; + +class MeteringState { + final double ev; + final double evCompensation; + final int iso; + final double nd; + final List exposurePairs; + + const MeteringState({ + required this.ev, + required this.evCompensation, + required this.iso, + required this.nd, + required this.exposurePairs, + }); + + ExposurePair? get fastest => exposurePairs.isEmpty ? null : exposurePairs.first; + ExposurePair? get slowest => exposurePairs.isEmpty ? null : exposurePairs.last; +} diff --git a/pubspec.yaml b/pubspec.yaml index c1b2ded..6ad2456 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: flutter: sdk: flutter + flutter_bloc: ^8.1.1 material_color_utilities: ^0.2.0 dev_dependencies: