diff --git a/lib/application.dart b/lib/application.dart index ed596a5..8f88746 100644 --- a/lib/application.dart +++ b/lib/application.dart @@ -18,6 +18,7 @@ import 'environment.dart'; import 'generated/l10n.dart'; import 'providers/equipment_profile_provider.dart'; import 'providers/ev_source_type_provider.dart'; +import 'providers/metering_screen_layout_provider.dart'; import 'providers/theme_provider.dart'; import 'screens/metering/flow_metering.dart'; import 'screens/settings/flow_settings.dart'; @@ -47,31 +48,33 @@ class Application extends StatelessWidget { Provider(create: (_) => PermissionsService()), Provider(create: (_) => const LightSensorService()), ], - child: StopTypeProvider( - child: EquipmentProfileProvider( - child: EvSourceTypeProvider( - child: SupportedLocaleProvider( - child: ThemeProvider( - builder: (context, _) => _AnnotatedRegionWrapper( - child: MaterialApp( - theme: context.watch(), - locale: Locale(context.watch().intlName), - localizationsDelegates: const [ - S.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: S.delegate.supportedLocales, - builder: (context, child) => MediaQuery( - data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), - child: child!, + child: MeteringScreenLayoutProvider( + child: StopTypeProvider( + child: EquipmentProfileProvider( + child: EvSourceTypeProvider( + child: SupportedLocaleProvider( + child: ThemeProvider( + builder: (context, _) => _AnnotatedRegionWrapper( + child: MaterialApp( + theme: context.watch(), + locale: Locale(context.watch().intlName), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, + builder: (context, child) => MediaQuery( + data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), + child: child!, + ), + initialRoute: "metering", + routes: { + "metering": (context) => const MeteringFlow(), + "settings": (context) => const SettingsFlow(), + }, ), - initialRoute: "metering", - routes: { - "metering": (context) => const MeteringFlow(), - "settings": (context) => const SettingsFlow(), - }, ), ), ), diff --git a/lib/data/models/film.dart b/lib/data/models/film.dart index 8b789ce..75d7ae1 100644 --- a/lib/data/models/film.dart +++ b/lib/data/models/film.dart @@ -124,6 +124,9 @@ class FomapanFilm extends Film { b = 5.75, c = 1.5, super('Fomapan ACTION 400', 400); + + @override + double reciprocityFormula(double t) => t * log10polynomian(t, a, b, c); } class IlfordFilm extends Film { diff --git a/lib/data/models/metering_screen_layout_config.dart b/lib/data/models/metering_screen_layout_config.dart new file mode 100644 index 0000000..68ff40e --- /dev/null +++ b/lib/data/models/metering_screen_layout_config.dart @@ -0,0 +1,10 @@ +enum MeteringScreenLayoutFeature { extremeExposurePairs, filmPicker } + +typedef MeteringScreenLayoutConfig = Map; + +extension MeteringScreenLayoutConfigJson on MeteringScreenLayoutConfig { + static MeteringScreenLayoutConfig fromJson(Map data) => data.map( + (key, value) => MapEntry(MeteringScreenLayoutFeature.values[int.parse(key)], value as bool)); + + Map toJson() => map((key, value) => MapEntry(key.index.toString(), value)); +} diff --git a/lib/data/shared_prefs_service.dart b/lib/data/shared_prefs_service.dart index c5054fb..7109bc3 100644 --- a/lib/data/shared_prefs_service.dart +++ b/lib/data/shared_prefs_service.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/supported_locale.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; @@ -5,6 +7,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'models/ev_source_type.dart'; import 'models/film.dart'; +import 'models/metering_screen_layout_config.dart'; import 'models/theme_type.dart'; class UserPreferencesService { @@ -14,6 +17,7 @@ class UserPreferencesService { static const _evSourceTypeKey = "evSourceType"; static const _cameraEvCalibrationKey = "cameraEvCalibration"; static const _lightSensorEvCalibrationKey = "lightSensorEvCalibration"; + static const _meteringScreenLayoutKey = "meteringScreenLayout"; static const _filmKey = "film"; static const _caffeineKey = "caffeine"; @@ -33,15 +37,15 @@ class UserPreferencesService { Future _migrateOldKeys() async { final legacyIsoIndex = _sharedPreferences.getInt("curIsoIndex"); if (legacyIsoIndex != null) { - iso = isoValues[legacyIsoIndex]; + iso = IsoValue.values[legacyIsoIndex]; await _sharedPreferences.remove("curIsoIndex"); } final legacyNdIndex = _sharedPreferences.getInt("curndIndex"); if (legacyNdIndex != null) { /// Legacy ND list has 1 extra value at the end, so this check is needed - if (legacyNdIndex < ndValues.length) { - ndFilter = ndValues[legacyNdIndex]; + if (legacyNdIndex < NdValue.values.length) { + ndFilter = NdValue.values[legacyNdIndex]; } await _sharedPreferences.remove("curndIndex"); } @@ -66,11 +70,11 @@ class UserPreferencesService { } IsoValue get iso => - isoValues.firstWhere((v) => v.value == (_sharedPreferences.getInt(_isoKey) ?? 100)); + IsoValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(_isoKey) ?? 100)); set iso(IsoValue value) => _sharedPreferences.setInt(_isoKey, value.value); NdValue get ndFilter => - ndValues.firstWhere((v) => v.value == (_sharedPreferences.getInt(_ndFilterKey) ?? 0)); + NdValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(_ndFilterKey) ?? 0)); set ndFilter(NdValue value) => _sharedPreferences.setInt(_ndFilterKey, value.value); EvSourceType get evSourceType => @@ -80,6 +84,21 @@ class UserPreferencesService { bool get caffeine => _sharedPreferences.getBool(_caffeineKey) ?? false; set caffeine(bool value) => _sharedPreferences.setBool(_caffeineKey, value); + MeteringScreenLayoutConfig get meteringScreenLayout { + final configJson = _sharedPreferences.getString(_meteringScreenLayoutKey); + if (configJson != null) { + return MeteringScreenLayoutConfigJson.fromJson(json.decode(configJson)); + } else { + return { + MeteringScreenLayoutFeature.extremeExposurePairs: true, + MeteringScreenLayoutFeature.filmPicker: true, + }; + } + } + + set meteringScreenLayout(MeteringScreenLayoutConfig value) => + _sharedPreferences.setString(_meteringScreenLayoutKey, json.encode(value.toJson())); + bool get haptics => _sharedPreferences.getBool(_hapticsKey) ?? true; set haptics(bool value) => _sharedPreferences.setBool(_hapticsKey, value); diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 4a2b65a..9bf3139 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -34,6 +34,10 @@ "calibrationMessageCameraOnly": "The accuracy of the readings measured by this application depends entirely on the rear camera of the device. Therefore, consider testing this application and setting up an EV calibration value that will give you the desired measurement results.", "camera": "Camera", "lightSensor": "Light sensor", + "meteringScreenLayout": "Metering screen layout", + "meteringScreenLayoutHint": "Hide elements on the metering screen that you don't need so that they don't waste exposure pairs list space.", + "meteringScreenFeatureExtremeExposurePairs": "Fastest & shortest exposure pairs", + "meteringScreenFeatureFilmPicker": "Film picker", "film": "Film", "equipment": "Equipment", "equipmentProfileName": "Equipment profile name", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 49debfb..1881e66 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -34,6 +34,10 @@ "calibrationMessageCameraOnly": "La précision des lectures mesurées par cette application dépend entièrement de la caméra arrière de l'appareil. Par conséquent, envisagez de tester cette application et de configurer une valeur d'étalonnage EV qui vous donnera les résultats de mesure souhaités.", "camera": "Caméra", "lightSensor": "Capteur de lumière", + "meteringScreenLayout": "Disposition de l'écran de mesure", + "meteringScreenLayoutHint": "Masquer les éléments sur l'écran de mesure dont vous n'avez pas besoin pour qu'ils ne gaspillent pas de l'espace dans les paires d'exposition.", + "meteringScreenFeatureExtremeExposurePairs": "Paires d'exposition les plus rapides et les plus courtes", + "meteringScreenFeatureFilmPicker": "Sélecteur de film", "film": "Pellicule", "equipment": "Équipement", "equipmentProfileName": "Nom du profil de l'équipement", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index dece4f7..13be45b 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -34,6 +34,10 @@ "calibrationMessageCameraOnly": "Точность измерений данного приложения полностью зависит от точности камеры вашего устройства. Поэтому рекомендуется самостоятельно подобрать калибровочное значение, которое даст желаемый результат измерений.", "camera": "Камера", "lightSensor": "Датчик освещённости", + "meteringScreenLayout": "Элементы главного экрана", + "meteringScreenLayoutHint": "Здесь вы можете скрыть некоторые ненужные или неиспользуемые элементы с главного экрана.", + "meteringScreenFeatureExtremeExposurePairs": "Длинная и короткая выдержки", + "meteringScreenFeatureFilmPicker": "Выбор пленки", "film": "Пленка", "equipment": "Оборудование", "equipmentProfileName": "Название профиля", diff --git a/lib/providers/equipment_profile_provider.dart b/lib/providers/equipment_profile_provider.dart index df38277..610ecd7 100644 --- a/lib/providers/equipment_profile_provider.dart +++ b/lib/providers/equipment_profile_provider.dart @@ -21,10 +21,10 @@ class EquipmentProfileProviderState extends State { static const EquipmentProfileData _defaultProfile = EquipmentProfileData( id: '', name: '', - apertureValues: apertureValues, - ndValues: ndValues, - shutterSpeedValues: shutterSpeedValues, - isoValues: isoValues, + apertureValues: ApertureValue.values, + ndValues: NdValue.values, + shutterSpeedValues: ShutterSpeedValue.values, + isoValues: IsoValue.values, ); List _customProfiles = []; @@ -68,10 +68,10 @@ class EquipmentProfileProviderState extends State { _customProfiles.add(EquipmentProfileData( id: const Uuid().v1(), name: name, - apertureValues: apertureValues, - ndValues: ndValues, - shutterSpeedValues: shutterSpeedValues, - isoValues: isoValues, + apertureValues: ApertureValue.values, + ndValues: NdValue.values, + shutterSpeedValues: ShutterSpeedValue.values, + isoValues: IsoValue.values, )); _refreshSavedProfiles(); } diff --git a/lib/providers/metering_screen_layout_provider.dart b/lib/providers/metering_screen_layout_provider.dart new file mode 100644 index 0000000..83e3773 --- /dev/null +++ b/lib/providers/metering_screen_layout_provider.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; +import 'package:lightmeter/data/shared_prefs_service.dart'; +import 'package:provider/provider.dart'; + +class MeteringScreenLayoutProvider extends StatefulWidget { + final Widget child; + + const MeteringScreenLayoutProvider({required this.child, super.key}); + + static MeteringScreenLayoutProviderState of(BuildContext context) { + return context.findAncestorStateOfType()!; + } + + @override + State createState() => MeteringScreenLayoutProviderState(); +} + +class MeteringScreenLayoutProviderState extends State { + late final MeteringScreenLayoutConfig _config = + context.read().meteringScreenLayout; + + @override + Widget build(BuildContext context) { + return MeteringScreenLayout( + config: MeteringScreenLayoutConfig.from(_config), + child: widget.child, + ); + } + + void updateFeatures(MeteringScreenLayoutConfig config) { + setState(() { + config.forEach((key, value) { + _config.update( + key, + (_) => value, + ifAbsent: () => value, + ); + }); + }); + context.read().meteringScreenLayout = _config; + } +} + +class MeteringScreenLayout extends InheritedModel { + final MeteringScreenLayoutConfig config; + + const MeteringScreenLayout({ + required this.config, + required super.child, + super.key, + }); + + static MeteringScreenLayoutConfig of(BuildContext context, {bool listen = true}) { + if (listen) { + return context.dependOnInheritedWidgetOfExactType()!.config; + } else { + return context.findAncestorWidgetOfExactType()!.config; + } + } + + static bool featureStatusOf(BuildContext context, MeteringScreenLayoutFeature feature) { + return InheritedModel.inheritFrom(context, aspect: feature)! + .config[feature]!; + } + + @override + bool updateShouldNotify(MeteringScreenLayout oldWidget) => true; + + @override + bool updateShouldNotifyDependent( + MeteringScreenLayout oldWidget, + Set dependencies, + ) { + for (final dependecy in dependencies) { + if (oldWidget.config[dependecy] != config[dependecy]) { + return true; + } + } + return false; + } +} diff --git a/lib/res/dimens.dart b/lib/res/dimens.dart index 61d79b5..1eefdc1 100644 --- a/lib/res/dimens.dart +++ b/lib/res/dimens.dart @@ -30,9 +30,8 @@ class Dimens { static const double disabledOpacity = 0.38; // TopBar - /// Probably this is a bad practice, but with text size locked, the height is always 212 + static const double readingContainerDoubleValueHeight = 128; static const double readingContainerSingleValueHeight = 76; - static const double readingContainerDefaultHeight = 288; // `CenteredSlider` static const double cameraSliderTrackHeight = grid4; diff --git a/lib/screens/metering/bloc_metering.dart b/lib/screens/metering/bloc_metering.dart index 8f3dc98..3f78928 100644 --- a/lib/screens/metering/bloc_metering.dart +++ b/lib/screens/metering/bloc_metering.dart @@ -106,7 +106,7 @@ class MeteringBloc extends Bloc { void _onFilmChanged(FilmChangedEvent event, Emitter emit) { if (_iso.value != event.data.iso) { - final newIso = isoValues.firstWhere( + final newIso = IsoValue.values.firstWhere( (e) => e.value == event.data.iso, orElse: () => _iso, ); @@ -175,7 +175,7 @@ class MeteringBloc extends Bloc { const ShutterSpeedValue anchorShutterSpeed = ShutterSpeedValue(1, false, StopType.full); int anchorIndex = _shutterSpeedValues.indexOf(anchorShutterSpeed); if (anchorIndex < 0) { - final filteredFullList = shutterSpeedValues.whereStopType(stopType); + final filteredFullList = ShutterSpeedValue.values.whereStopType(stopType); final customListStartIndex = filteredFullList.indexOf(_shutterSpeedValues.first); final fullListAnchor = filteredFullList.indexOf(anchorShutterSpeed); if (customListStartIndex < fullListAnchor) { diff --git a/lib/screens/metering/components/camera_container/widget_container_camera.dart b/lib/screens/metering/components/camera_container/widget_container_camera.dart index a9cf631..0362b5f 100644 --- a/lib/screens/metering/components/camera_container/widget_container_camera.dart +++ b/lib/screens/metering/components/camera_container/widget_container_camera.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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'; import 'package:lightmeter/features.dart'; import 'package:lightmeter/platform_config.dart'; +import 'package:lightmeter/providers/metering_screen_layout_provider.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/metering/components/camera_container/components/camera_view/widget_camera_view.dart'; import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart'; @@ -49,11 +51,26 @@ class CameraContainer extends StatelessWidget { ((MediaQuery.of(context).size.width - Dimens.grid8 - 2 * Dimens.paddingM) / 2) / PlatformConfig.cameraPreviewAspectRatio; - double topBarOverflow = Dimens.readingContainerDefaultHeight - cameraViewHeight; + double topBarOverflow = Dimens.readingContainerSingleValueHeight + // ISO & ND + -cameraViewHeight; if (FeaturesConfig.equipmentProfilesEnabled) { topBarOverflow += Dimens.readingContainerSingleValueHeight; topBarOverflow += Dimens.paddingS; } + if (MeteringScreenLayout.featureStatusOf( + context, + MeteringScreenLayoutFeature.extremeExposurePairs, + )) { + topBarOverflow += Dimens.readingContainerDoubleValueHeight; + topBarOverflow += Dimens.paddingS; + } + if (MeteringScreenLayout.featureStatusOf( + context, + MeteringScreenLayoutFeature.filmPicker, + )) { + topBarOverflow += Dimens.readingContainerSingleValueHeight; + topBarOverflow += Dimens.paddingS; + } return Column( children: [ diff --git a/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart b/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart index 62ba265..a5d6d45 100644 --- a/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart +++ b/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart @@ -6,6 +6,7 @@ typedef DialogPickerItemTitleBuilder = Widget Function(BuildContext context, typedef DialogPickerItemTrailingBuilder = Widget? Function(T selected, T value); class DialogPicker extends StatefulWidget { + final IconData icon; final String title; final String? subtitle; final T initialValue; @@ -16,6 +17,7 @@ class DialogPicker extends StatefulWidget { final ValueChanged onSelect; const DialogPicker({ + required this.icon, required this.title, this.subtitle, required this.initialValue, @@ -47,25 +49,36 @@ class _DialogPickerState extends State> { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Padding( - padding: Dimens.dialogTitlePadding, - child: Column( - children: [ - Text( + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: Dimens.dialogTitlePadding, + child: Icon(widget.icon), + ), + Padding( + padding: Dimens.dialogIconTitlePadding, + child: Text( widget.title, style: Theme.of(context).textTheme.headlineSmall!, textAlign: TextAlign.center, ), - if (widget.subtitle != null) ...[ - const SizedBox(height: Dimens.grid16), - Text( + ), + if (widget.subtitle != null) + Padding( + padding: const EdgeInsets.fromLTRB( + Dimens.paddingL, + 0, + Dimens.paddingL, + Dimens.paddingM, + ), + child: Text( widget.subtitle!, style: Theme.of(context).textTheme.bodyMedium!, textAlign: TextAlign.center, ), - ] - ], - ), + ), + ], ), const Divider(), Expanded( diff --git a/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/widget_picker_dialog_animated.dart b/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/widget_picker_dialog_animated.dart index 0bbdabc..0c3cd48 100644 --- a/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/widget_picker_dialog_animated.dart +++ b/lib/screens/metering/components/shared/readings_container/components/animated_dialog_picker/widget_picker_dialog_animated.dart @@ -5,6 +5,7 @@ import 'components/dialog_picker/widget_picker_dialog.dart'; class AnimatedDialogPicker extends StatelessWidget { final _key = GlobalKey(); + final IconData icon; final String title; final String? subtitle; final T selectedValue; @@ -15,6 +16,7 @@ class AnimatedDialogPicker extends StatelessWidget { final Widget closedChild; AnimatedDialogPicker({ + required this.icon, required this.title, this.subtitle, required this.selectedValue, @@ -32,6 +34,7 @@ class AnimatedDialogPicker extends StatelessWidget { key: _key, closedChild: closedChild, openedChild: DialogPicker( + icon: icon, title: title, subtitle: subtitle, initialValue: selectedValue, diff --git a/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart b/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart index 92902b8..313b408 100644 --- a/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart +++ b/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.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'; import 'package:lightmeter/features.dart'; import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/providers/equipment_profile_provider.dart'; +import 'package:lightmeter/providers/metering_screen_layout_provider.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; @@ -42,25 +44,35 @@ class ReadingsContainer extends StatelessWidget { const _EquipmentProfilePicker(), const _InnerPadding(), ], - ReadingValueContainer( - values: [ - ReadingValue( - label: S.of(context).fastestExposurePair, - value: fastest != null ? fastest!.toString() : '-', - ), - ReadingValue( - label: S.of(context).slowestExposurePair, - value: fastest != null ? slowest!.toString() : '-', - ), - ], - ), - const _InnerPadding(), - _FilmPicker( - values: Film.values, - selectedValue: film, - onChanged: onFilmChanged, - ), - const _InnerPadding(), + if (MeteringScreenLayout.featureStatusOf( + context, + MeteringScreenLayoutFeature.extremeExposurePairs, + )) ...[ + ReadingValueContainer( + values: [ + ReadingValue( + label: S.of(context).fastestExposurePair, + value: fastest != null ? fastest!.toString() : '-', + ), + ReadingValue( + label: S.of(context).slowestExposurePair, + value: fastest != null ? slowest!.toString() : '-', + ), + ], + ), + const _InnerPadding(), + ], + if (MeteringScreenLayout.featureStatusOf( + context, + MeteringScreenLayoutFeature.filmPicker, + )) ...[ + _FilmPicker( + values: Film.values, + selectedValue: film, + onChanged: onFilmChanged, + ), + const _InnerPadding(), + ], Row( children: [ Expanded( @@ -95,6 +107,7 @@ class _EquipmentProfilePicker extends StatelessWidget { @override Widget build(BuildContext context) { return AnimatedDialogPicker( + icon: Icons.camera, title: S.of(context).equipmentProfile, selectedValue: EquipmentProfile.of(context), values: EquipmentProfiles.of(context), @@ -126,6 +139,7 @@ class _FilmPicker extends StatelessWidget { @override Widget build(BuildContext context) { return AnimatedDialogPicker( + icon: Icons.camera_roll, title: S.of(context).film, selectedValue: selectedValue, values: values, @@ -155,6 +169,7 @@ class _IsoValuePicker extends StatelessWidget { @override Widget build(BuildContext context) { return AnimatedDialogPicker( + icon: Icons.iso, title: S.of(context).iso, subtitle: S.of(context).filmSpeed, selectedValue: selectedValue, @@ -189,6 +204,7 @@ class _NdValuePicker extends StatelessWidget { @override Widget build(BuildContext context) { return AnimatedDialogPicker( + icon: Icons.filter_b_and_w, title: S.of(context).nd, subtitle: S.of(context).ndFilterFactor, selectedValue: selectedValue, diff --git a/lib/screens/metering/screen_metering.dart b/lib/screens/metering/screen_metering.dart index f9ac3ad..cf27875 100644 --- a/lib/screens/metering/screen_metering.dart +++ b/lib/screens/metering/screen_metering.dart @@ -3,9 +3,11 @@ 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'; import 'package:lightmeter/environment.dart'; import 'package:lightmeter/providers/equipment_profile_provider.dart'; import 'package:lightmeter/providers/ev_source_type_provider.dart'; +import 'package:lightmeter/providers/metering_screen_layout_provider.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'components/bottom_controls/provider_bottom_controls.dart'; @@ -30,6 +32,9 @@ class _MeteringScreenState extends State { super.didChangeDependencies(); _bloc.add(EquipmentProfileChangedEvent(EquipmentProfile.of(context))); _bloc.add(StopTypeChangedEvent(context.watch())); + if (!MeteringScreenLayout.featureStatusOf(context, MeteringScreenLayoutFeature.filmPicker)) { + _bloc.add(const FilmChangedEvent(Film.other())); + } } @override diff --git a/lib/screens/settings/components/general/components/language/widget_list_tile_language.dart b/lib/screens/settings/components/general/components/language/widget_list_tile_language.dart index 06c4e79..4bea398 100644 --- a/lib/screens/settings/components/general/components/language/widget_list_tile_language.dart +++ b/lib/screens/settings/components/general/components/language/widget_list_tile_language.dart @@ -18,6 +18,7 @@ class LanguageListTile extends StatelessWidget { showDialog( context: context, builder: (_) => DialogPicker( + icon: Icons.language, title: S.of(context).chooseLanguage, selectedValue: context.read(), values: SupportedLocale.values, diff --git a/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart b/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart index 62025ac..fc26df3 100644 --- a/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart +++ b/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart @@ -17,7 +17,8 @@ class CalibrationDialog extends StatelessWidget { Widget build(BuildContext context) { final bool hasLightSensor = context.read().hasLightSensor; return AlertDialog( - titlePadding: Dimens.dialogTitlePadding, + icon: const Icon(Icons.settings_brightness), + titlePadding: Dimens.dialogIconTitlePadding, title: Text(S.of(context).calibration), contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL), content: SingleChildScrollView( diff --git a/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/components/equipment_list_tiles/widget_list_tiles_equipments.dart b/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/components/equipment_list_tiles/widget_list_tiles_equipments.dart index 38f25e8..52a2960 100644 --- a/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/components/equipment_list_tiles/widget_list_tiles_equipments.dart +++ b/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/components/equipment_list_tiles/widget_list_tiles_equipments.dart @@ -35,8 +35,8 @@ class EquipmentListTiles extends StatelessWidget { icon: Icons.iso, title: S.of(context).isoValues, description: S.of(context).isoValuesFilterDescription, - values: isoValues, - valuesCount: selectedIsoValues.length == isoValues.length + values: IsoValue.values, + valuesCount: selectedIsoValues.length == IsoValue.values.length ? S.of(context).equipmentProfileAllValues : selectedIsoValues.length.toString(), selectedValues: selectedIsoValues, @@ -47,8 +47,8 @@ class EquipmentListTiles extends StatelessWidget { icon: Icons.filter_b_and_w, title: S.of(context).ndFilters, description: S.of(context).ndFiltersFilterDescription, - values: ndValues, - valuesCount: selectedNdValues.length == ndValues.length + values: NdValue.values, + valuesCount: selectedNdValues.length == NdValue.values.length ? S.of(context).equipmentProfileAllValues : selectedNdValues.length.toString(), selectedValues: selectedNdValues, @@ -59,8 +59,8 @@ class EquipmentListTiles extends StatelessWidget { icon: Icons.camera, title: S.of(context).apertureValues, description: S.of(context).apertureValuesFilterDescription, - values: apertureValues, - valuesCount: selectedApertureValues.length == apertureValues.length + values: ApertureValue.values, + valuesCount: selectedApertureValues.length == ApertureValue.values.length ? S.of(context).equipmentProfileAllValues : selectedApertureValues.length.toString(), selectedValues: selectedApertureValues, @@ -71,8 +71,8 @@ class EquipmentListTiles extends StatelessWidget { icon: Icons.shutter_speed, title: S.of(context).shutterSpeedValues, description: S.of(context).shutterSpeedValuesFilterDescription, - values: shutterSpeedValues, - valuesCount: selectedShutterSpeedValues.length == shutterSpeedValues.length + values: ShutterSpeedValue.values, + valuesCount: selectedShutterSpeedValues.length == ShutterSpeedValue.values.length ? S.of(context).equipmentProfileAllValues : selectedShutterSpeedValues.length.toString(), selectedValues: selectedShutterSpeedValues, diff --git a/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart b/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart index d202b94..e3014ac 100644 --- a/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart +++ b/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/res/dimens.dart'; class EquipmentProfileNameDialog extends StatefulWidget { final String initialValue; @@ -22,6 +23,8 @@ class _EquipmentProfileNameDialogState extends State @override Widget build(BuildContext context) { return AlertDialog( + icon: const Icon(Icons.edit), + titlePadding: Dimens.dialogIconTitlePadding, title: Text(S.of(context).equipmentProfileName), content: TextField( autofocus: true, diff --git a/lib/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart b/lib/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart index d164430..65a6309 100644 --- a/lib/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart +++ b/lib/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart @@ -18,6 +18,7 @@ class StopTypeListTile extends StatelessWidget { showDialog( context: context, builder: (_) => DialogPicker( + icon: Icons.straighten, title: S.of(context).showFractionalStops, selectedValue: context.read(), values: StopType.values, diff --git a/lib/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart b/lib/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart new file mode 100644 index 0000000..654a6c4 --- /dev/null +++ b/lib/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/metering_screen_layout_provider.dart'; +import 'package:lightmeter/res/dimens.dart'; + +class MeteringScreenLayoutFeaturesDialog extends StatefulWidget { + const MeteringScreenLayoutFeaturesDialog({super.key}); + + @override + State createState() => + _MeteringScreenLayoutFeaturesDialogState(); +} + +class _MeteringScreenLayoutFeaturesDialogState extends State { + late final _features = + MeteringScreenLayoutConfig.from(MeteringScreenLayout.of(context, listen: false)); + + @override + Widget build(BuildContext context) { + return AlertDialog( + icon: const Icon(Icons.layers_outlined), + titlePadding: Dimens.dialogIconTitlePadding, + title: Text(S.of(context).meteringScreenLayout), + contentPadding: EdgeInsets.zero, + content: SizedBox( + width: double.maxFinite, + child: ListView( + shrinkWrap: true, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL), + child: Text(S.of(context).meteringScreenLayoutHint), + ), + const SizedBox(height: Dimens.grid16), + ...MeteringScreenLayoutFeature.values.map( + (f) => SwitchListTile( + contentPadding: EdgeInsets.symmetric(horizontal: Dimens.dialogTitlePadding.left), + title: Text(_toStringLocalized(context, f)), + value: _features[f]!, + onChanged: (value) { + setState(() { + _features.update(f, (_) => value); + }); + }, + ), + ), + ], + ), + ), + actionsPadding: Dimens.dialogActionsPadding, + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: Text(S.of(context).cancel), + ), + TextButton( + onPressed: () { + MeteringScreenLayoutProvider.of(context).updateFeatures(_features); + Navigator.of(context).pop(); + }, + child: Text(S.of(context).save), + ), + ], + ); + } + + String _toStringLocalized(BuildContext context, MeteringScreenLayoutFeature feature) { + switch (feature) { + case MeteringScreenLayoutFeature.extremeExposurePairs: + return S.of(context).meteringScreenFeatureExtremeExposurePairs; + case MeteringScreenLayoutFeature.filmPicker: + return S.of(context).meteringScreenFeatureFilmPicker; + } + } +} diff --git a/lib/screens/settings/components/metering/components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart b/lib/screens/settings/components/metering/components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart new file mode 100644 index 0000000..9823366 --- /dev/null +++ b/lib/screens/settings/components/metering/components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/generated/l10n.dart'; + +import 'components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart'; + +class MeteringScreenLayoutListTile extends StatelessWidget { + const MeteringScreenLayoutListTile({super.key}); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: const Icon(Icons.layers_outlined), + title: Text(S.of(context).meteringScreenLayout), + onTap: () { + showDialog( + context: context, + builder: (_) => const MeteringScreenLayoutFeaturesDialog(), + ); + }, + ); + } +} diff --git a/lib/screens/settings/components/metering/widget_settings_section_metering.dart b/lib/screens/settings/components/metering/widget_settings_section_metering.dart index 45e057a..495c57f 100644 --- a/lib/screens/settings/components/metering/widget_settings_section_metering.dart +++ b/lib/screens/settings/components/metering/widget_settings_section_metering.dart @@ -4,6 +4,7 @@ import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart'; import 'components/calibration/widget_list_tile_calibration.dart'; +import 'components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart'; import 'components/equipment_profiles/widget_list_tile_equipment_profiles.dart'; import 'components/fractional_stops/widget_list_tile_fractional_stops.dart'; @@ -17,6 +18,7 @@ class MeteringSettingsSection extends StatelessWidget { children: const [ StopTypeListTile(), CalibrationListTile(), + MeteringScreenLayoutListTile(), if (FeaturesConfig.equipmentProfilesEnabled) EquipmentProfilesListTile(), ], ); diff --git a/lib/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart b/lib/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart index 8044fb4..c893027 100644 --- a/lib/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart +++ b/lib/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart @@ -3,12 +3,14 @@ import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/res/dimens.dart'; class DialogPicker extends StatefulWidget { + final IconData icon; final String title; final T selectedValue; final List values; final String Function(BuildContext context, T value) titleAdapter; const DialogPicker({ + required this.icon, required this.title, required this.selectedValue, required this.values, @@ -26,7 +28,8 @@ class _DialogPickerState extends State> { @override Widget build(BuildContext context) { return AlertDialog( - titlePadding: Dimens.dialogTitlePadding, + icon: Icon(widget.icon), + titlePadding: Dimens.dialogIconTitlePadding, title: Text(widget.title), contentPadding: EdgeInsets.zero, content: Column( diff --git a/lib/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart b/lib/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart index e2e6b8e..9a4dd07 100644 --- a/lib/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart +++ b/lib/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart @@ -24,7 +24,8 @@ class _PrimaryColorDialogPickerState extends State { @override Widget build(BuildContext context) { return AlertDialog( - titlePadding: Dimens.dialogTitlePadding, + icon: const Icon(Icons.palette), + titlePadding: Dimens.dialogIconTitlePadding, title: Text(S.of(context).choosePrimaryColor), content: SizedBox( height: Dimens.grid48, diff --git a/lib/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart b/lib/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart index 53df421..3c43224 100644 --- a/lib/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart +++ b/lib/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart @@ -18,6 +18,7 @@ class ThemeTypeListTile extends StatelessWidget { showDialog( context: context, builder: (_) => DialogPicker( + icon: Icons.brightness_6, title: S.of(context).chooseTheme, selectedValue: context.read(), values: ThemeType.values,