mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-21 15:00:40 +00:00
* implemented `MeteringScreenLayoutProvider` * refined topbar height difference calculation * implemented `MeteringScreenLayoutFeaturesDialog` * added icons to all dialogs * save & restore `MeteringScreenLayoutConfig` * reset film on film picker disabling * fixed Fomapan reciprocity * fixed dependencies * added translations
This commit is contained in:
parent
be0617a99c
commit
aaadd1ded6
27 changed files with 376 additions and 83 deletions
|
@ -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<ThemeData>(),
|
||||
locale: Locale(context.watch<SupportedLocale>().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<ThemeData>(),
|
||||
locale: Locale(context.watch<SupportedLocale>().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(),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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 {
|
||||
|
|
10
lib/data/models/metering_screen_layout_config.dart
Normal file
10
lib/data/models/metering_screen_layout_config.dart
Normal file
|
@ -0,0 +1,10 @@
|
|||
enum MeteringScreenLayoutFeature { extremeExposurePairs, filmPicker }
|
||||
|
||||
typedef MeteringScreenLayoutConfig = Map<MeteringScreenLayoutFeature, bool>;
|
||||
|
||||
extension MeteringScreenLayoutConfigJson on MeteringScreenLayoutConfig {
|
||||
static MeteringScreenLayoutConfig fromJson(Map<String, dynamic> data) => data.map(
|
||||
(key, value) => MapEntry(MeteringScreenLayoutFeature.values[int.parse(key)], value as bool));
|
||||
|
||||
Map<String, dynamic> toJson() => map((key, value) => MapEntry(key.index.toString(), value));
|
||||
}
|
|
@ -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<void> _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);
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -34,6 +34,10 @@
|
|||
"calibrationMessageCameraOnly": "Точность измерений данного приложения полностью зависит от точности камеры вашего устройства. Поэтому рекомендуется самостоятельно подобрать калибровочное значение, которое даст желаемый результат измерений.",
|
||||
"camera": "Камера",
|
||||
"lightSensor": "Датчик освещённости",
|
||||
"meteringScreenLayout": "Элементы главного экрана",
|
||||
"meteringScreenLayoutHint": "Здесь вы можете скрыть некоторые ненужные или неиспользуемые элементы с главного экрана.",
|
||||
"meteringScreenFeatureExtremeExposurePairs": "Длинная и короткая выдержки",
|
||||
"meteringScreenFeatureFilmPicker": "Выбор пленки",
|
||||
"film": "Пленка",
|
||||
"equipment": "Оборудование",
|
||||
"equipmentProfileName": "Название профиля",
|
||||
|
|
|
@ -21,10 +21,10 @@ class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
|
|||
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<EquipmentProfileData> _customProfiles = [];
|
||||
|
@ -68,10 +68,10 @@ class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
|
|||
_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();
|
||||
}
|
||||
|
|
82
lib/providers/metering_screen_layout_provider.dart
Normal file
82
lib/providers/metering_screen_layout_provider.dart
Normal file
|
@ -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<MeteringScreenLayoutProviderState>()!;
|
||||
}
|
||||
|
||||
@override
|
||||
State<MeteringScreenLayoutProvider> createState() => MeteringScreenLayoutProviderState();
|
||||
}
|
||||
|
||||
class MeteringScreenLayoutProviderState extends State<MeteringScreenLayoutProvider> {
|
||||
late final MeteringScreenLayoutConfig _config =
|
||||
context.read<UserPreferencesService>().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<UserPreferencesService>().meteringScreenLayout = _config;
|
||||
}
|
||||
}
|
||||
|
||||
class MeteringScreenLayout extends InheritedModel<MeteringScreenLayoutFeature> {
|
||||
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<MeteringScreenLayout>()!.config;
|
||||
} else {
|
||||
return context.findAncestorWidgetOfExactType<MeteringScreenLayout>()!.config;
|
||||
}
|
||||
}
|
||||
|
||||
static bool featureStatusOf(BuildContext context, MeteringScreenLayoutFeature feature) {
|
||||
return InheritedModel.inheritFrom<MeteringScreenLayout>(context, aspect: feature)!
|
||||
.config[feature]!;
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(MeteringScreenLayout oldWidget) => true;
|
||||
|
||||
@override
|
||||
bool updateShouldNotifyDependent(
|
||||
MeteringScreenLayout oldWidget,
|
||||
Set<MeteringScreenLayoutFeature> dependencies,
|
||||
) {
|
||||
for (final dependecy in dependencies) {
|
||||
if (oldWidget.config[dependecy] != config[dependecy]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -106,7 +106,7 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
|
|||
|
||||
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<MeteringEvent, MeteringState> {
|
|||
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) {
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -6,6 +6,7 @@ typedef DialogPickerItemTitleBuilder<T> = Widget Function(BuildContext context,
|
|||
typedef DialogPickerItemTrailingBuilder<T> = Widget? Function(T selected, T value);
|
||||
|
||||
class DialogPicker<T> extends StatefulWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final T initialValue;
|
||||
|
@ -16,6 +17,7 @@ class DialogPicker<T> extends StatefulWidget {
|
|||
final ValueChanged onSelect;
|
||||
|
||||
const DialogPicker({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
required this.initialValue,
|
||||
|
@ -47,25 +49,36 @@ class _DialogPickerState<T> extends State<DialogPicker<T>> {
|
|||
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(
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'components/dialog_picker/widget_picker_dialog.dart';
|
|||
|
||||
class AnimatedDialogPicker<T> extends StatelessWidget {
|
||||
final _key = GlobalKey<AnimatedDialogState>();
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final T selectedValue;
|
||||
|
@ -15,6 +16,7 @@ class AnimatedDialogPicker<T> extends StatelessWidget {
|
|||
final Widget closedChild;
|
||||
|
||||
AnimatedDialogPicker({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
required this.selectedValue,
|
||||
|
@ -32,6 +34,7 @@ class AnimatedDialogPicker<T> extends StatelessWidget {
|
|||
key: _key,
|
||||
closedChild: closedChild,
|
||||
openedChild: DialogPicker<T>(
|
||||
icon: icon,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
initialValue: selectedValue,
|
||||
|
|
|
@ -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<EquipmentProfileData>(
|
||||
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<Film>(
|
||||
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<IsoValue>(
|
||||
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<NdValue>(
|
||||
icon: Icons.filter_b_and_w,
|
||||
title: S.of(context).nd,
|
||||
subtitle: S.of(context).ndFilterFactor,
|
||||
selectedValue: selectedValue,
|
||||
|
|
|
@ -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<MeteringScreen> {
|
|||
super.didChangeDependencies();
|
||||
_bloc.add(EquipmentProfileChangedEvent(EquipmentProfile.of(context)));
|
||||
_bloc.add(StopTypeChangedEvent(context.watch<StopType>()));
|
||||
if (!MeteringScreenLayout.featureStatusOf(context, MeteringScreenLayoutFeature.filmPicker)) {
|
||||
_bloc.add(const FilmChangedEvent(Film.other()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -18,6 +18,7 @@ class LanguageListTile extends StatelessWidget {
|
|||
showDialog<SupportedLocale>(
|
||||
context: context,
|
||||
builder: (_) => DialogPicker<SupportedLocale>(
|
||||
icon: Icons.language,
|
||||
title: S.of(context).chooseLanguage,
|
||||
selectedValue: context.read<SupportedLocale>(),
|
||||
values: SupportedLocale.values,
|
||||
|
|
|
@ -17,7 +17,8 @@ class CalibrationDialog extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final bool hasLightSensor = context.read<Environment>().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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<EquipmentProfileNameDialog>
|
|||
@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,
|
||||
|
|
|
@ -18,6 +18,7 @@ class StopTypeListTile extends StatelessWidget {
|
|||
showDialog<StopType>(
|
||||
context: context,
|
||||
builder: (_) => DialogPicker<StopType>(
|
||||
icon: Icons.straighten,
|
||||
title: S.of(context).showFractionalStops,
|
||||
selectedValue: context.read<StopType>(),
|
||||
values: StopType.values,
|
||||
|
|
|
@ -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<MeteringScreenLayoutFeaturesDialog> createState() =>
|
||||
_MeteringScreenLayoutFeaturesDialogState();
|
||||
}
|
||||
|
||||
class _MeteringScreenLayoutFeaturesDialogState extends State<MeteringScreenLayoutFeaturesDialog> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -3,12 +3,14 @@ import 'package:lightmeter/generated/l10n.dart';
|
|||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
class DialogPicker<T> extends StatefulWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final T selectedValue;
|
||||
final List<T> 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<T> extends State<DialogPicker<T>> {
|
|||
@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(
|
||||
|
|
|
@ -24,7 +24,8 @@ class _PrimaryColorDialogPickerState extends State<PrimaryColorDialogPicker> {
|
|||
@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,
|
||||
|
|
|
@ -18,6 +18,7 @@ class ThemeTypeListTile extends StatelessWidget {
|
|||
showDialog<ThemeType>(
|
||||
context: context,
|
||||
builder: (_) => DialogPicker<ThemeType>(
|
||||
icon: Icons.brightness_6,
|
||||
title: S.of(context).chooseTheme,
|
||||
selectedValue: context.read<ThemeType>(),
|
||||
values: ThemeType.values,
|
||||
|
|
Loading…
Reference in a new issue