mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-22 07:20:39 +00:00
added MeteringBloc
This commit is contained in:
parent
888a9a6a76
commit
b6e377959f
9 changed files with 199 additions and 84 deletions
|
@ -1,6 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import 'models/photography_value.dart';
|
||||||
import 'res/theme.dart';
|
import 'res/theme.dart';
|
||||||
|
import 'screens/metering/metering_bloc.dart';
|
||||||
import 'screens/metering/metering_screen.dart';
|
import 'screens/metering/metering_screen.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -12,13 +15,16 @@ class MyApp extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return BlocProvider(
|
||||||
|
create: (context) => MeteringBloc(Stop.third),
|
||||||
|
child: MaterialApp(
|
||||||
title: 'Flutter Demo',
|
title: 'Flutter Demo',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
colorScheme: lightColorScheme,
|
colorScheme: lightColorScheme,
|
||||||
),
|
),
|
||||||
home: const MeteringScreen(),
|
home: const MeteringScreen(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
8
lib/models/exposure_pair.dart
Normal file
8
lib/models/exposure_pair.dart
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import 'photography_value.dart';
|
||||||
|
|
||||||
|
class ExposurePair {
|
||||||
|
final ApertureValue aperture;
|
||||||
|
final ShutterSpeedValue shutterSpeed;
|
||||||
|
|
||||||
|
const ExposurePair(this.aperture, this.shutterSpeed);
|
||||||
|
}
|
|
@ -1,56 +1,12 @@
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
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/res/dimens.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/exposure_pairs_list/components/exposure_pair_item.dart';
|
import 'package:lightmeter/screens/metering/components/exposure_pairs_list/components/exposure_pair_item.dart';
|
||||||
|
|
||||||
class ExposurePairsList extends StatelessWidget {
|
class ExposurePairsList extends StatelessWidget {
|
||||||
final double ev;
|
final List<ExposurePair> exposurePairs;
|
||||||
final Stop stopType;
|
|
||||||
final List<ApertureValue> _apertureValuesList;
|
|
||||||
final List<ShutterSpeedValue> _shutterSpeedValuesList;
|
|
||||||
late final int _apertureOffset;
|
|
||||||
late final int _shutterSpeedOffset;
|
|
||||||
late final int _itemsCount;
|
|
||||||
|
|
||||||
ExposurePairsList({
|
const ExposurePairsList(this.exposurePairs, {super.key});
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -59,7 +15,8 @@ class ExposurePairsList extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: _itemsCount,
|
key: ValueKey(exposurePairs.hashCode),
|
||||||
|
itemCount: exposurePairs.length,
|
||||||
itemBuilder: (_, index) => Row(
|
itemBuilder: (_, index) => Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
@ -67,7 +24,7 @@ class ExposurePairsList extends StatelessWidget {
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: ExposurePaitListItem(
|
child: ExposurePaitListItem(
|
||||||
_apertureValuesList[index + _apertureOffset],
|
exposurePairs[index].aperture,
|
||||||
tickOnTheLeft: false,
|
tickOnTheLeft: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -77,7 +34,7 @@ class ExposurePairsList extends StatelessWidget {
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: ExposurePaitListItem(
|
child: ExposurePaitListItem(
|
||||||
_shutterSpeedValuesList[index + _shutterSpeedOffset],
|
exposurePairs[index].shutterSpeed,
|
||||||
tickOnTheLeft: true,
|
tickOnTheLeft: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lightmeter/models/exposure_pair.dart';
|
||||||
import 'package:lightmeter/res/dimens.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 'components/reading_container.dart';
|
||||||
import 'package:lightmeter/utils/text_line_height.dart';
|
|
||||||
|
|
||||||
class MeteringTopBar extends StatelessWidget {
|
class MeteringTopBar extends StatelessWidget {
|
||||||
static const _columnsCount = 3;
|
static const _columnsCount = 3;
|
||||||
|
|
||||||
|
final ExposurePair? fastest;
|
||||||
|
final ExposurePair? slowest;
|
||||||
final double ev;
|
final double ev;
|
||||||
final int iso;
|
final int iso;
|
||||||
final double nd;
|
final double nd;
|
||||||
|
|
||||||
const MeteringTopBar({
|
const MeteringTopBar({
|
||||||
|
required this.fastest,
|
||||||
|
required this.slowest,
|
||||||
required this.ev,
|
required this.ev,
|
||||||
required this.iso,
|
required this.iso,
|
||||||
required this.nd,
|
required this.nd,
|
||||||
|
@ -43,14 +47,18 @@ class MeteringTopBar extends StatelessWidget {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: columnWidth / 3 * 4,
|
height: columnWidth / 3 * 4,
|
||||||
child: ReadingContainer(
|
child: ReadingContainer(
|
||||||
values: const [
|
values: [
|
||||||
ReadingValue(
|
ReadingValue(
|
||||||
label: 'Fastest',
|
label: 'Fastest',
|
||||||
value: 'f/5.6 - 1/2000',
|
value: fastest != null
|
||||||
|
? '${fastest!.aperture.toString()} - ${fastest!.shutterSpeed.toString()}'
|
||||||
|
: 'N/A',
|
||||||
),
|
),
|
||||||
ReadingValue(
|
ReadingValue(
|
||||||
label: 'Slowest',
|
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(
|
child: ReadingContainer.singleValue(
|
||||||
value: ReadingValue(
|
value: ReadingValue(
|
||||||
label: 'EV',
|
label: 'EV',
|
||||||
value: ev.toString(),
|
value: ev.toStringAsFixed(1),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
90
lib/screens/metering/metering_bloc.dart
Normal file
90
lib/screens/metering/metering_bloc.dart
Normal file
|
@ -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<MeteringEvent, MeteringState> {
|
||||||
|
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<MeasureEvent>(_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<ExposurePair> _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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
7
lib/screens/metering/metering_event.dart
Normal file
7
lib/screens/metering/metering_event.dart
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
abstract class MeteringEvent {
|
||||||
|
const MeteringEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MeasureEvent extends MeteringEvent {
|
||||||
|
const MeasureEvent();
|
||||||
|
}
|
|
@ -1,37 +1,55 @@
|
||||||
import 'package:flutter/material.dart';
|
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/bottom_controls/bottom_controls.dart';
|
||||||
import 'components/exposure_pairs_list/exposure_pairs_list.dart';
|
import 'components/exposure_pairs_list/exposure_pairs_list.dart';
|
||||||
import 'components/topbar/topbar.dart';
|
import 'components/topbar/topbar.dart';
|
||||||
|
import 'metering_bloc.dart';
|
||||||
|
import 'metering_state.dart';
|
||||||
|
|
||||||
class MeteringScreen extends StatelessWidget {
|
class MeteringScreen extends StatelessWidget {
|
||||||
const MeteringScreen({super.key});
|
const MeteringScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const ev = 0.3;
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
body: Column(
|
body: BlocBuilder<MeteringBloc, MeteringState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
const MeteringTopBar(
|
MeteringTopBar(
|
||||||
ev: ev,
|
fastest: state.fastest,
|
||||||
iso: 6400,
|
slowest: state.slowest,
|
||||||
nd: 0,
|
ev: state.ev,
|
||||||
|
iso: state.iso,
|
||||||
|
nd: state.nd,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ExposurePairsList(
|
child: Padding(
|
||||||
ev: ev,
|
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||||
stopType: Stop.third,
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: ExposurePairsList(state.exposurePairs),
|
||||||
|
),
|
||||||
|
const SizedBox(width: Dimens.grid16),
|
||||||
|
const Spacer()
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MeteringBottomControls(
|
MeteringBottomControls(
|
||||||
onSourceChanged: () {},
|
onSourceChanged: () {},
|
||||||
onMeasure: () {},
|
onMeasure: () => context.read<MeteringBloc>().add(const MeasureEvent()),
|
||||||
onSettings: () {},
|
onSettings: () {},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
20
lib/screens/metering/metering_state.dart
Normal file
20
lib/screens/metering/metering_state.dart
Normal file
|
@ -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<ExposurePair> 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;
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_bloc: ^8.1.1
|
||||||
material_color_utilities: ^0.2.0
|
material_color_utilities: ^0.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
Loading…
Reference in a new issue