m3_lightmeter/lib/screens/metering/screen_metering.dart

205 lines
7.9 KiB
Dart
Raw Normal View History

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';
import 'package:lightmeter/data/models/ev_source_type.dart';
import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
2023-08-13 19:59:37 +00:00
import 'package:lightmeter/providers/equipment_profile_provider.dart';
2023-08-13 19:14:32 +00:00
import 'package:lightmeter/providers/services_provider.dart';
import 'package:lightmeter/providers/user_preferences_provider.dart';
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-13 19:30:14 +00:00
import 'package:lightmeter/screens/metering/utils/listener_metering_layout_feature.dart';
2023-08-13 19:59:37 +00:00
import 'package:lightmeter/screens/metering/utils/listsner_equipment_profiles.dart';
ML-42 Implement equipment profiles creating (#45) * added Equipment section placeholder * get iso & nd values from equipment profile * use photography values from remote repo * removed equipment section * wip * moved `EquipmentProfileProvider` from iap repo * wip * moved equipment profiles screen from iap * improved equipment profiles screen * mock add/delete * collapse on expand * add profile with name * show selected values count (wip) * fixed profile update * cleanup * Update pubspec.yaml * made `AnimatedDialogPicker` more generic * switched to local `Dimens` * fixed `MeteringTopBarShape` * rename * animated `EquipmentProfileContainer` * added default equipment profile * change equipment profile name via dialog * fixed profile selection * filter equipment profile update/delete * removed `enabled` param from settings section * non-null `EquipmentProfile` * fixed duplicate GlobalKeys * animated equipment list * Update ci.yml * fixed shutter speed anchor issue * autofocus * added firebase to project * save/restore equipment profiles * unified `SliverList` * added SSH key to iap repo * Update ci.yml * ci recursive submodules * try full url * Revert "try full url" This reverts commit a9b692b60ea5b2e88188a5d497467708becb4a02. * restore firebase_options.dart * changed runner to macos * restore options earlier * removed problematic file from analysis :) * removed launch_app * textoverflow * implemented `DialogRangePicker` * add iap repo to cd * typo * added workflow_dispatch to crowdin push * removed `equipmentProfileValuesCount` from intl * fr & ru translations * style * removed iap
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
class MeteringScreen extends StatelessWidget {
const MeteringScreen({super.key});
2022-10-24 20:25:38 +00:00
@override
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(
ev: state is MeteringDataState ? state.ev : null,
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,
isMetering: state.isMetering,
onSwitchEvSourceType: ServicesProvider.of(context).environment.hasLightSensor
? UserPreferencesProvider.of(context).toggleEvSourceType
: null,
onMeasure: () => context.read<MeteringBloc>().add(const MeasureEvent()),
onSettings: () {
context.read<MeteringBloc>().add(const SettingsOpenedEvent());
Navigator.pushNamed(context, 'settings').then((value) {
context.read<MeteringBloc>().add(const SettingsClosedEvent());
});
},
),
),
],
),
),
);
}
}
class _InheritedListeners extends StatelessWidget {
final Widget child;
2023-01-26 15:03:48 +00:00
const _InheritedListeners({required this.child});
2022-10-24 20:25:38 +00:00
@override
Widget build(BuildContext context) {
2023-08-13 19:59:37 +00:00
return EquipmentProfileListener(
onDidChangeDependencies: (value) {
context.read<MeteringBloc>().add(EquipmentProfileChangedEvent(value));
},
2023-08-13 19:30:14 +00:00
child: MeteringScreenLayoutFeatureListener(
feature: MeteringScreenLayoutFeature.filmPicker,
onDidChangeDependencies: (value) {
if (!value) context.read<MeteringBloc>().add(const FilmChangedEvent(Film.other()));
},
child: child,
2022-10-24 20:25:38 +00:00
),
);
}
}
class _MeteringContainerBuidler extends StatelessWidget {
final double? ev;
final Film film;
final IsoValue iso;
final NdValue nd;
final ValueChanged<Film> onFilmChanged;
final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged;
const _MeteringContainerBuidler({
required this.ev,
required this.film,
required this.iso,
required this.nd,
required this.onFilmChanged,
required this.onIsoChanged,
required this.onNdChanged,
});
@override
Widget build(BuildContext context) {
final exposurePairs = ev != null ? buildExposureValues(context, ev!, film) : <ExposurePair>[];
final fastest = exposurePairs.isNotEmpty ? exposurePairs.first : null;
final slowest = exposurePairs.isNotEmpty ? exposurePairs.last : null;
// Doubled build here when switching evSourceType. As new source bloc fires a new state on init
return UserPreferencesProvider.evSourceTypeOf(context) == EvSourceType.camera
? CameraContainerProvider(
fastest: fastest,
slowest: slowest,
film: film,
iso: iso,
nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged,
exposurePairs: exposurePairs,
)
: LightSensorContainerProvider(
fastest: fastest,
slowest: slowest,
film: film,
iso: iso,
nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged,
exposurePairs: exposurePairs,
);
}
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
final StopType stopType = UserPreferencesProvider.stopTypeOf(context);
final int evSteps = (ev * (stopType.index + 1)).round();
2023-08-13 19:59:37 +00:00
final EquipmentProfile equipmentProfile = EquipmentProfiles.selectedOf(context);
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,
);
}
}