2023-06-20 06:43:49 +00:00
|
|
|
import 'dart:math';
|
|
|
|
|
2022-10-24 20:25:38 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2022-10-29 18:02:45 +00:00
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
2023-01-29 16:57:47 +00:00
|
|
|
import 'package:lightmeter/data/models/ev_source_type.dart';
|
2023-02-19 10:26:14 +00:00
|
|
|
import 'package:lightmeter/data/models/exposure_pair.dart';
|
2023-04-01 19:04:55 +00:00
|
|
|
import 'package:lightmeter/data/models/film.dart';
|
2023-04-05 19:15:11 +00:00
|
|
|
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
2023-08-14 10:25:37 +00:00
|
|
|
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
|
|
|
import 'package:lightmeter/providers/services_provider.dart';
|
|
|
|
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
2023-05-11 13:30:18 +00:00
|
|
|
import 'package:lightmeter/screens/metering/bloc_metering.dart';
|
|
|
|
import 'package:lightmeter/screens/metering/components/bottom_controls/provider_bottom_controls.dart';
|
|
|
|
import 'package:lightmeter/screens/metering/components/camera_container/provider_container_camera.dart';
|
|
|
|
import 'package:lightmeter/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart';
|
|
|
|
import 'package:lightmeter/screens/metering/event_metering.dart';
|
|
|
|
import 'package:lightmeter/screens/metering/state_metering.dart';
|
2023-08-14 10:25:37 +00:00
|
|
|
import 'package:lightmeter/screens/metering/utils/listener_metering_layout_feature.dart';
|
|
|
|
import 'package:lightmeter/screens/metering/utils/listsner_equipment_profiles.dart';
|
2023-03-30 19:24:18 +00:00
|
|
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
2022-10-24 20:25:38 +00:00
|
|
|
|
2023-06-04 11:04:04 +00:00
|
|
|
class MeteringScreen extends StatelessWidget {
|
2022-12-11 14:09:10 +00:00
|
|
|
const MeteringScreen({super.key});
|
2022-10-24 20:25:38 +00:00
|
|
|
|
2022-11-26 18:14:51 +00:00
|
|
|
@override
|
2023-06-04 11:04:04 +00:00
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return _InheritedListeners(
|
|
|
|
child: Scaffold(
|
|
|
|
backgroundColor: Theme.of(context).colorScheme.background,
|
|
|
|
body: Column(
|
|
|
|
children: [
|
|
|
|
Expanded(
|
|
|
|
child: BlocBuilder<MeteringBloc, MeteringState>(
|
|
|
|
builder: (_, state) => _MeteringContainerBuidler(
|
2023-06-20 06:43:49 +00:00
|
|
|
ev: state is MeteringDataState ? state.ev : null,
|
2023-06-04 11:04:04 +00:00
|
|
|
film: state.film,
|
|
|
|
iso: state.iso,
|
|
|
|
nd: state.nd,
|
|
|
|
onFilmChanged: (value) =>
|
|
|
|
context.read<MeteringBloc>().add(FilmChangedEvent(value)),
|
|
|
|
onIsoChanged: (value) => context.read<MeteringBloc>().add(IsoChangedEvent(value)),
|
|
|
|
onNdChanged: (value) => context.read<MeteringBloc>().add(NdChangedEvent(value)),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
BlocBuilder<MeteringBloc, MeteringState>(
|
|
|
|
builder: (context, state) => MeteringBottomControlsProvider(
|
|
|
|
ev: state is MeteringDataState ? state.ev : null,
|
2023-06-20 06:43:49 +00:00
|
|
|
isMetering: state.isMetering,
|
2023-08-14 10:25:37 +00:00
|
|
|
onSwitchEvSourceType: ServicesProvider.of(context).environment.hasLightSensor
|
|
|
|
? UserPreferencesProvider.of(context).toggleEvSourceType
|
2023-06-04 11:04:04 +00:00
|
|
|
: null,
|
|
|
|
onMeasure: () => context.read<MeteringBloc>().add(const MeasureEvent()),
|
2023-07-09 11:39:33 +00:00
|
|
|
onSettings: () {
|
|
|
|
context.read<MeteringBloc>().add(const SettingsOpenedEvent());
|
|
|
|
Navigator.pushNamed(context, 'settings').then((value) {
|
|
|
|
context.read<MeteringBloc>().add(const SettingsClosedEvent());
|
|
|
|
});
|
|
|
|
},
|
2023-06-04 11:04:04 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2022-11-26 18:14:51 +00:00
|
|
|
}
|
|
|
|
|
2023-06-04 11:04:04 +00:00
|
|
|
class _InheritedListeners extends StatelessWidget {
|
|
|
|
final Widget child;
|
2023-01-26 15:03:48 +00:00
|
|
|
|
2023-06-04 11:04:04 +00:00
|
|
|
const _InheritedListeners({required this.child});
|
2022-12-11 14:04:08 +00:00
|
|
|
|
2022-10-24 20:25:38 +00:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2023-08-14 10:25:37 +00:00
|
|
|
return EquipmentProfileListener(
|
2023-06-04 11:04:04 +00:00
|
|
|
onDidChangeDependencies: (value) {
|
|
|
|
context.read<MeteringBloc>().add(EquipmentProfileChangedEvent(value));
|
|
|
|
},
|
2023-08-14 10:25:37 +00:00
|
|
|
child: MeteringScreenLayoutFeatureListener(
|
|
|
|
feature: MeteringScreenLayoutFeature.filmPicker,
|
2023-06-04 11:04:04 +00:00
|
|
|
onDidChangeDependencies: (value) {
|
2023-06-20 06:43:49 +00:00
|
|
|
if (!value) context.read<MeteringBloc>().add(const FilmChangedEvent(Film.other()));
|
2023-06-04 11:04:04 +00:00
|
|
|
},
|
2023-06-20 06:43:49 +00:00
|
|
|
child: child,
|
2022-10-24 20:25:38 +00:00
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2023-02-19 10:26:14 +00:00
|
|
|
|
|
|
|
class _MeteringContainerBuidler extends StatelessWidget {
|
2023-06-20 06:43:49 +00:00
|
|
|
final double? ev;
|
2023-04-01 19:04:55 +00:00
|
|
|
final Film film;
|
2023-02-19 10:26:14 +00:00
|
|
|
final IsoValue iso;
|
|
|
|
final NdValue nd;
|
2023-04-01 19:04:55 +00:00
|
|
|
final ValueChanged<Film> onFilmChanged;
|
2023-02-19 10:26:14 +00:00
|
|
|
final ValueChanged<IsoValue> onIsoChanged;
|
|
|
|
final ValueChanged<NdValue> onNdChanged;
|
|
|
|
|
|
|
|
const _MeteringContainerBuidler({
|
2023-06-20 06:43:49 +00:00
|
|
|
required this.ev,
|
2023-04-01 19:04:55 +00:00
|
|
|
required this.film,
|
2023-02-19 10:26:14 +00:00
|
|
|
required this.iso,
|
|
|
|
required this.nd,
|
2023-04-01 19:04:55 +00:00
|
|
|
required this.onFilmChanged,
|
2023-02-19 10:26:14 +00:00
|
|
|
required this.onIsoChanged,
|
|
|
|
required this.onNdChanged,
|
|
|
|
});
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2023-06-20 06:43:49 +00:00
|
|
|
final exposurePairs = ev != null ? buildExposureValues(context, ev!, film) : <ExposurePair>[];
|
|
|
|
final fastest = exposurePairs.isNotEmpty ? exposurePairs.first : null;
|
|
|
|
final slowest = exposurePairs.isNotEmpty ? exposurePairs.last : null;
|
2023-08-14 10:25:37 +00:00
|
|
|
// Doubled build here when switching evSourceType. As new source bloc fires a new state on init
|
|
|
|
return UserPreferencesProvider.evSourceTypeOf(context) == EvSourceType.camera
|
2023-02-19 10:26:14 +00:00
|
|
|
? CameraContainerProvider(
|
|
|
|
fastest: fastest,
|
|
|
|
slowest: slowest,
|
2023-04-01 19:04:55 +00:00
|
|
|
film: film,
|
2023-02-19 10:26:14 +00:00
|
|
|
iso: iso,
|
|
|
|
nd: nd,
|
2023-04-01 19:04:55 +00:00
|
|
|
onFilmChanged: onFilmChanged,
|
2023-02-19 10:26:14 +00:00
|
|
|
onIsoChanged: onIsoChanged,
|
|
|
|
onNdChanged: onNdChanged,
|
|
|
|
exposurePairs: exposurePairs,
|
|
|
|
)
|
|
|
|
: LightSensorContainerProvider(
|
|
|
|
fastest: fastest,
|
|
|
|
slowest: slowest,
|
2023-04-01 19:04:55 +00:00
|
|
|
film: film,
|
2023-02-19 10:26:14 +00:00
|
|
|
iso: iso,
|
|
|
|
nd: nd,
|
2023-04-01 19:04:55 +00:00
|
|
|
onFilmChanged: onFilmChanged,
|
2023-02-19 10:26:14 +00:00
|
|
|
onIsoChanged: onIsoChanged,
|
|
|
|
onNdChanged: onNdChanged,
|
|
|
|
exposurePairs: exposurePairs,
|
|
|
|
);
|
|
|
|
}
|
2023-06-20 06:43:49 +00:00
|
|
|
|
|
|
|
List<ExposurePair> buildExposureValues(BuildContext context, double ev, Film film) {
|
|
|
|
if (ev.isNaN || ev.isInfinite) {
|
|
|
|
return List.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Depending on the `stopType` the exposure pairs list length is multiplied by 1,2 or 3
|
2023-08-14 10:25:37 +00:00
|
|
|
final StopType stopType = UserPreferencesProvider.stopTypeOf(context);
|
2023-06-20 06:43:49 +00:00
|
|
|
final int evSteps = (ev * (stopType.index + 1)).round();
|
|
|
|
|
2023-08-14 10:25:37 +00:00
|
|
|
final EquipmentProfile equipmentProfile = EquipmentProfiles.selectedOf(context);
|
2023-06-20 06:43:49 +00:00
|
|
|
final List<ApertureValue> apertureValues =
|
|
|
|
equipmentProfile.apertureValues.whereStopType(stopType);
|
|
|
|
final List<ShutterSpeedValue> shutterSpeedValues =
|
|
|
|
equipmentProfile.shutterSpeedValues.whereStopType(stopType);
|
|
|
|
|
|
|
|
/// Basically we use 1" shutter speed as an anchor point for building the exposure pairs list.
|
|
|
|
/// But user can exclude this value from the list using custom equipment profile.
|
|
|
|
/// So we have to restore the index of the anchor value.
|
|
|
|
const ShutterSpeedValue anchorShutterSpeed = ShutterSpeedValue(1, false, StopType.full);
|
|
|
|
int anchorIndex = shutterSpeedValues.indexOf(anchorShutterSpeed);
|
|
|
|
if (anchorIndex < 0) {
|
|
|
|
final filteredFullList = ShutterSpeedValue.values.whereStopType(stopType);
|
|
|
|
final customListStartIndex = filteredFullList.indexOf(shutterSpeedValues.first);
|
|
|
|
final fullListAnchor = filteredFullList.indexOf(anchorShutterSpeed);
|
|
|
|
if (customListStartIndex < fullListAnchor) {
|
|
|
|
/// This means, that user excluded anchor value at the end,
|
|
|
|
/// i.e. all shutter speed values are shorter than 1".
|
|
|
|
anchorIndex = fullListAnchor - customListStartIndex;
|
|
|
|
} else {
|
|
|
|
/// In case user excludes anchor value at the start,
|
|
|
|
/// we can do no adjustment.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
final int evOffset = anchorIndex - evSteps;
|
|
|
|
|
|
|
|
late final int apertureOffset;
|
|
|
|
late final int shutterSpeedOffset;
|
|
|
|
if (evOffset >= 0) {
|
|
|
|
apertureOffset = 0;
|
|
|
|
shutterSpeedOffset = evOffset;
|
|
|
|
} else {
|
|
|
|
apertureOffset = -evOffset;
|
|
|
|
shutterSpeedOffset = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
final int itemsCount = min(
|
|
|
|
apertureValues.length + shutterSpeedOffset,
|
|
|
|
shutterSpeedValues.length + apertureOffset,
|
|
|
|
) -
|
|
|
|
max(apertureOffset, shutterSpeedOffset);
|
|
|
|
|
|
|
|
if (itemsCount < 0) {
|
|
|
|
return List.empty();
|
|
|
|
}
|
|
|
|
return List.generate(
|
|
|
|
itemsCount,
|
|
|
|
(index) => ExposurePair(
|
|
|
|
apertureValues[index + apertureOffset],
|
|
|
|
film.reciprocityFailure(shutterSpeedValues[index + shutterSpeedOffset]),
|
|
|
|
),
|
|
|
|
growable: false,
|
|
|
|
);
|
|
|
|
}
|
2023-02-19 10:26:14 +00:00
|
|
|
}
|