diff --git a/.github/workflows/build_apk.yml b/.github/workflows/build_apk.yml index c972954..9157f90 100644 --- a/.github/workflows/build_apk.yml +++ b/.github/workflows/build_apk.yml @@ -76,7 +76,7 @@ jobs: - name: Build Apk env: FLAVOR: ${{ github.event.inputs.flavor }} - run: flutter build apk --release --flavor $FLAVOR --dart-define cameraPreviewAspectRatio=2/3 -t lib/main_$FLAVOR.dart + run: flutter build apk --release --flavor $FLAVOR --dart-define c -t lib/main_$FLAVOR.dart - name: Upload artifact uses: actions/upload-artifact@v3 diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 6fe5d77..3851170 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -16,7 +16,7 @@ on: type: string env: - BUILD_ARGS: --release --flavor prod --dart-define cameraPreviewAspectRatio=2/3 -t lib/main_prod.dart + BUILD_ARGS: --release --flavor prod --dart-define cameraPreviewAspectRatio=240/320 -t lib/main_prod.dart jobs: build: diff --git a/.vscode/launch.json b/.vscode/launch.json index fb960be..822d34d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,7 @@ "--flavor", "dev", "--dart-define", - "cameraPreviewAspectRatio=2/3", + "cameraPreviewAspectRatio=240/320", ], "program": "${workspaceFolder}/lib/main_dev.dart", }, @@ -36,7 +36,7 @@ "--flavor", "prod", "--dart-define", - "cameraPreviewAspectRatio=2/3", + "cameraPreviewAspectRatio=240/320", ], "program": "${workspaceFolder}/lib/main_prod.dart", }, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8615437..adaac0f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -12,7 +12,7 @@ "dev", "--release", "--dart-define", - "cameraPreviewAspectRatio=2/3", + "cameraPreviewAspectRatio=240/320", "-t", "lib/main_dev.dart", ], @@ -28,7 +28,7 @@ "prod", "--release", "--dart-define", - "cameraPreviewAspectRatio=2/3", + "cameraPreviewAspectRatio=240/320", "-t", "lib/main_prod.dart", ], @@ -44,7 +44,7 @@ "prod", "--release", "--dart-define", - "cameraPreviewAspectRatio=2/3", + "cameraPreviewAspectRatio=240/320", "-t", "lib/main_prod.dart", ], diff --git a/README.md b/README.md index 2fef498..f629aa2 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,11 @@ flutter pub get flutter pub run intl_utils:generate ``` -### 4. Build +### 4. Build (Android) You can build an apk by running the following command from the root of the repository: ```console -flutter build apk --release --flavor $FLAVOR --dart-define cameraPreviewAspectRatio=2/3 -t lib/main_$FLAVOR.dart +flutter build apk --release --flavor $FLAVOR --dart-define cameraPreviewAspectRatio=240/320 -t lib/main_$FLAVOR.dart ``` Just replace `$FLAVOR` with `dev` or `prod`. diff --git a/lib/data/models/metering_screen_layout_config.dart b/lib/data/models/metering_screen_layout_config.dart index 3410632..0aae055 100644 --- a/lib/data/models/metering_screen_layout_config.dart +++ b/lib/data/models/metering_screen_layout_config.dart @@ -1,11 +1,13 @@ -enum MeteringScreenLayoutFeature { extremeExposurePairs, filmPicker } +enum MeteringScreenLayoutFeature { extremeExposurePairs, filmPicker, histogram } 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), - ); + static MeteringScreenLayoutConfig fromJson(Map data) => + { + for (final f in MeteringScreenLayoutFeature.values) + f: data[f.index.toString()] as bool? ?? true + }; 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 8105d5c..8ae5487 100644 --- a/lib/data/shared_prefs_service.dart +++ b/lib/data/shared_prefs_service.dart @@ -97,6 +97,7 @@ class UserPreferencesService { return { MeteringScreenLayoutFeature.extremeExposurePairs: true, MeteringScreenLayoutFeature.filmPicker: true, + MeteringScreenLayoutFeature.histogram: true, }; } } diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 39700b8..1bfe8ed 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -38,6 +38,7 @@ "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", + "meteringScreenFeatureHistogram": "Histogram", "film": "Film", "equipment": "Equipment", "equipmentProfileName": "Equipment profile name", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 5957d0d..f75d76b 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -38,6 +38,7 @@ "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", + "meteringScreenFeatureHistogram": "Histogramme", "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 f6b3833..3a67fc2 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -38,6 +38,7 @@ "meteringScreenLayoutHint": "Здесь вы можете скрыть некоторые ненужные или неиспользуемые элементы с главного экрана.", "meteringScreenFeatureExtremeExposurePairs": "Длинная и короткая выдержки", "meteringScreenFeatureFilmPicker": "Выбор пленки", + "meteringScreenFeatureHistogram": "Гистограмма", "film": "Пленка", "equipment": "Оборудование", "equipmentProfileName": "Название профиля", diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index b931906..5d020cf 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -38,6 +38,7 @@ "meteringScreenLayoutHint": "隐藏不需要的元素,以免浪费曝光列表空间", "meteringScreenFeatureExtremeExposurePairs": "最快 & 最慢曝光组合", "meteringScreenFeatureFilmPicker": "胶片选择", + "meteringScreenFeatureHistogram": "直方图", "film": "胶片", "equipment": "设备", "equipmentProfileName": "设备配置名称", diff --git a/lib/screens/metering/components/camera_container/bloc_container_camera.dart b/lib/screens/metering/components/camera_container/bloc_container_camera.dart index 9c8658d..991bdf6 100644 --- a/lib/screens/metering/components/camera_container/bloc_container_camera.dart +++ b/lib/screens/metering/components/camera_container/bloc_container_camera.dart @@ -123,7 +123,7 @@ class CameraContainerBloc extends EvSourceBlocBase camera.lensDirection == CameraLensDirection.back, orElse: () => cameras.last, ), - ResolutionPreset.medium, + ResolutionPreset.low, enableAudio: false, ); diff --git a/lib/screens/metering/components/camera_container/components/camera_view/widget_camera_view.dart b/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart similarity index 86% rename from lib/screens/metering/components/camera_container/components/camera_view/widget_camera_view.dart rename to lib/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart index e443ad1..7c06062 100644 --- a/lib/screens/metering/components/camera_container/components/camera_view/widget_camera_view.dart +++ b/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart @@ -14,10 +14,12 @@ class CameraView extends StatelessWidget { valueListenable: controller, builder: (_, __, ___) => AspectRatio( aspectRatio: _isLandscape(value) ? value.aspectRatio : (1 / value.aspectRatio), - child: RotatedBox( - quarterTurns: _getQuarterTurns(value), - child: controller.buildPreview(), - ), + child: value.isInitialized + ? RotatedBox( + quarterTurns: _getQuarterTurns(value), + child: controller.buildPreview(), + ) + : const SizedBox.shrink(), ), ); } diff --git a/lib/screens/metering/components/camera_container/components/camera_view_placeholder/widget_placeholder_camera_view.dart b/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_view_placeholder/widget_placeholder_camera_view.dart similarity index 100% rename from lib/screens/metering/components/camera_container/components/camera_view_placeholder/widget_placeholder_camera_view.dart rename to lib/screens/metering/components/camera_container/components/camera_preview/components/camera_view_placeholder/widget_placeholder_camera_view.dart diff --git a/lib/screens/metering/components/camera_container/components/camera_preview/components/histogram/widget_histogram.dart b/lib/screens/metering/components/camera_container/components/camera_preview/components/histogram/widget_histogram.dart new file mode 100644 index 0000000..6964e77 --- /dev/null +++ b/lib/screens/metering/components/camera_container/components/camera_preview/components/histogram/widget_histogram.dart @@ -0,0 +1,132 @@ +import 'dart:math'; + +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; +import 'package:lightmeter/res/dimens.dart'; + +class CameraHistogram extends StatefulWidget { + final CameraController controller; + + const CameraHistogram({required this.controller, super.key}); + + @override + _CameraHistogramState createState() => _CameraHistogramState(); +} + +class _CameraHistogramState extends State { + List histogramR = List.filled(256, 0); + List histogramG = List.filled(256, 0); + List histogramB = List.filled(256, 0); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _startImageStream(); + }); + } + + @override + void dispose() { + widget.controller.stopImageStream(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + HistogramChannel( + color: Colors.red, + values: histogramR, + ), + const SizedBox(height: Dimens.grid4), + HistogramChannel( + color: Colors.green, + values: histogramG, + ), + const SizedBox(height: Dimens.grid4), + HistogramChannel( + color: Colors.blue, + values: histogramB, + ), + ], + ); + } + + void _startImageStream() { + widget.controller.startImageStream((CameraImage image) { + histogramR = List.filled(256, 0); + histogramG = List.filled(256, 0); + histogramB = List.filled(256, 0); + + final int uvRowStride = image.planes[1].bytesPerRow; + final int uvPixelStride = image.planes[1].bytesPerPixel!; + + for (int x = 0; x < image.width; x++) { + for (int y = 0; y < image.height; y++) { + final int uvIndex = uvPixelStride * (x / 2).floor() + uvRowStride * (y / 2).floor(); + final int index = y * image.width + x; + + final yp = image.planes[0].bytes[index]; + final up = image.planes[1].bytes[uvIndex]; + final vp = image.planes[2].bytes[uvIndex]; + + final r = yp + vp * 1436 / 1024 - 179; + final g = yp - up * 46549 / 131072 + 44 - vp * 93604 / 131072 + 91; + final b = yp + up * 1814 / 1024 - 227; + + histogramR[r.round().clamp(0, 255)]++; + histogramG[g.round().clamp(0, 255)]++; + histogramB[b.round().clamp(0, 255)]++; + } + } + + if (mounted) setState(() {}); + }); + } +} + +class HistogramChannel extends StatelessWidget { + final List values; + final Color color; + + final int _maxOccurences; + + HistogramChannel({ + required this.values, + required this.color, + super.key, + }) : _maxOccurences = values.reduce((value, element) => max(value, element)); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final pixelWidth = constraints.maxWidth / values.length; + return Column( + children: [ + SizedBox( + height: Dimens.grid16, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: values + .map( + (e) => SizedBox( + height: _maxOccurences == 0 ? 0 : Dimens.grid16 * (e / _maxOccurences), + width: pixelWidth, + child: ColoredBox(color: color), + ), + ) + .toList(), + ), + ), + const Divider(), + ], + ); + }, + ); + } +} diff --git a/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart b/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart new file mode 100644 index 0000000..288c678 --- /dev/null +++ b/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart @@ -0,0 +1,62 @@ +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; +import 'package:lightmeter/data/models/metering_screen_layout_config.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_preview/components/camera_view/widget_camera_view.dart'; +import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/camera_view_placeholder/widget_placeholder_camera_view.dart'; +import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/histogram/widget_histogram.dart'; +import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart'; + +class CameraPreview extends StatefulWidget { + final CameraController? controller; + final CameraErrorType? error; + + const CameraPreview({this.controller, this.error, super.key}); + + @override + State createState() => _CameraPreviewState(); +} + +class _CameraPreviewState extends State { + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: PlatformConfig.cameraPreviewAspectRatio, + child: Center( + child: Stack( + children: [ + const CameraViewPlaceholder(error: null), + AnimatedSwitcher( + duration: Dimens.switchDuration, + child: widget.controller != null + ? ValueListenableBuilder( + valueListenable: widget.controller!, + builder: (_, __, ___) => widget.controller!.value.isInitialized + ? Stack( + alignment: Alignment.bottomCenter, + children: [ + CameraView(controller: widget.controller!), + if (MeteringScreenLayout.featureOf( + context, + MeteringScreenLayoutFeature.histogram, + )) + Positioned( + left: Dimens.grid8, + right: Dimens.grid8, + bottom: Dimens.grid16, + child: CameraHistogram(controller: widget.controller!), + ), + ], + ) + : const SizedBox.shrink(), + ) + : CameraViewPlaceholder(error: widget.error), + ), + ], + ), + ), + ); + } +} 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 7ac43eb..e42991c 100644 --- a/lib/screens/metering/components/camera_container/widget_container_camera.dart +++ b/lib/screens/metering/components/camera_container/widget_container_camera.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lightmeter/data/models/exposure_pair.dart'; @@ -10,8 +12,7 @@ import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart'; import 'package:lightmeter/screens/metering/components/camera_container/components/camera_controls/widget_camera_controls.dart'; import 'package:lightmeter/screens/metering/components/camera_container/components/camera_controls_placeholder/widget_placeholder_camera_controls.dart'; -import 'package:lightmeter/screens/metering/components/camera_container/components/camera_view/widget_camera_view.dart'; -import 'package:lightmeter/screens/metering/components/camera_container/components/camera_view_placeholder/widget_placeholder_camera_view.dart'; +import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart'; import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart'; import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart'; import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart'; @@ -46,59 +47,94 @@ class CameraContainer extends StatelessWidget { @override Widget build(BuildContext context) { - final double cameraViewHeight = - ((MediaQuery.of(context).size.width - Dimens.grid8 - 2 * Dimens.paddingM) / 2) / - PlatformConfig.cameraPreviewAspectRatio; + final double meteringContainerHeight = _meteringContainerHeight(context); + final double cameraPreviewHeight = _cameraPreviewHeight(context); + final double topBarOverflow = meteringContainerHeight - cameraPreviewHeight; - double topBarOverflow = Dimens.readingContainerSingleValueHeight + // ISO & ND - -cameraViewHeight; + return Stack( + children: [ + Positioned( + left: 0, + top: 0, + right: 0, + child: MeteringTopBar( + readingsContainer: ReadingsContainer( + fastest: fastest, + slowest: slowest, + film: film, + iso: iso, + nd: nd, + onFilmChanged: onFilmChanged, + onIsoChanged: onIsoChanged, + onNdChanged: onNdChanged, + ), + appendixHeight: topBarOverflow, + preview: const _CameraViewBuilder(), + ), + ), + SafeArea( + bottom: false, + child: Column( + children: [ + SizedBox( + height: min(meteringContainerHeight, cameraPreviewHeight) + Dimens.paddingM * 2, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM), + child: Row( + children: [ + Expanded( + child: Padding( + padding: EdgeInsets.only(top: topBarOverflow >= 0 ? topBarOverflow : 0), + child: ExposurePairsList(exposurePairs), + ), + ), + const SizedBox(width: Dimens.grid8), + Expanded( + child: Padding( + padding: EdgeInsets.only(top: topBarOverflow <= 0 ? -topBarOverflow : 0), + child: const _CameraControlsBuilder(), + ), + ), + ], + ), + ), + ), + ], + ), + ) + ], + ); + } + + double _meteringContainerHeight(BuildContext context) { + double enabledFeaturesHeight = 0; if (FeaturesConfig.equipmentProfilesEnabled) { - topBarOverflow += Dimens.readingContainerSingleValueHeight; - topBarOverflow += Dimens.paddingS; + enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight; + enabledFeaturesHeight += Dimens.paddingS; } if (MeteringScreenLayout.featureOf( context, MeteringScreenLayoutFeature.extremeExposurePairs, )) { - topBarOverflow += Dimens.readingContainerDoubleValueHeight; - topBarOverflow += Dimens.paddingS; + enabledFeaturesHeight += Dimens.readingContainerDoubleValueHeight; + enabledFeaturesHeight += Dimens.paddingS; } if (MeteringScreenLayout.featureOf( context, MeteringScreenLayoutFeature.filmPicker, )) { - topBarOverflow += Dimens.readingContainerSingleValueHeight; - topBarOverflow += Dimens.paddingS; + enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight; + enabledFeaturesHeight += Dimens.paddingS; } - return Column( - children: [ - MeteringTopBar( - readingsContainer: ReadingsContainer( - fastest: fastest, - slowest: slowest, - film: film, - iso: iso, - nd: nd, - onFilmChanged: onFilmChanged, - onIsoChanged: onIsoChanged, - onNdChanged: onNdChanged, - ), - appendixHeight: topBarOverflow, - preview: const _CameraViewBuilder(), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM), - child: _MiddleContentWrapper( - topBarOverflow: topBarOverflow, - leftContent: ExposurePairsList(exposurePairs), - rightContent: const _CameraControlsBuilder(), - ), - ), - ), - ], - ); + return enabledFeaturesHeight + Dimens.readingContainerSingleValueHeight; // ISO & ND + } + + double _cameraPreviewHeight(BuildContext context) { + return ((MediaQuery.of(context).size.width - Dimens.grid8 - 2 * Dimens.paddingM) / 2) / + PlatformConfig.cameraPreviewAspectRatio; } } @@ -107,20 +143,11 @@ class _CameraViewBuilder extends StatelessWidget { @override Widget build(BuildContext context) { - return AspectRatio( - aspectRatio: PlatformConfig.cameraPreviewAspectRatio, - child: BlocBuilder( - buildWhen: (previous, current) => current is! CameraActiveState, - builder: (context, state) => Center( - child: AnimatedSwitcher( - duration: Dimens.durationM, - child: switch (state) { - CameraInitializedState() => CameraView(controller: state.controller), - CameraErrorState() => CameraViewPlaceholder(error: state.error), - _ => const CameraViewPlaceholder(error: null), - }, - ), - ), + return BlocBuilder( + buildWhen: (previous, current) => current is! CameraActiveState, + builder: (context, state) => CameraPreview( + controller: state is CameraInitializedState ? state.controller : null, + error: state is CameraErrorState ? state.error : null, ), ); } @@ -161,7 +188,9 @@ class _CameraControlsBuilder extends StatelessWidget { }, ); } else { - child = const Column(children: [Expanded(child: SizedBox.shrink())],); + child = const Column( + children: [Expanded(child: SizedBox.shrink())], + ); } return AnimatedSwitcher( @@ -173,43 +202,3 @@ class _CameraControlsBuilder extends StatelessWidget { ); } } - -class _MiddleContentWrapper extends StatelessWidget { - final double topBarOverflow; - final Widget leftContent; - final Widget rightContent; - - const _MiddleContentWrapper({ - required this.topBarOverflow, - required this.leftContent, - required this.rightContent, - }); - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (context, constraints) => OverflowBox( - alignment: Alignment.bottomCenter, - maxHeight: constraints.maxHeight + topBarOverflow.abs(), - maxWidth: constraints.maxWidth, - child: Row( - children: [ - Expanded( - child: Padding( - padding: EdgeInsets.only(top: topBarOverflow >= 0 ? topBarOverflow : 0), - child: leftContent, - ), - ), - const SizedBox(width: Dimens.grid8), - Expanded( - child: Padding( - padding: EdgeInsets.only(top: topBarOverflow <= 0 ? -topBarOverflow : 0), - child: rightContent, - ), - ), - ], - ), - ), - ); - } -} 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 index 654a6c4..d43f011 100644 --- 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 @@ -71,6 +71,8 @@ class _MeteringScreenLayoutFeaturesDialogState extends State=3.0.0 <4.0.0" diff --git a/test/data/models/metering_screen_layout_config_test.dart b/test/data/models/metering_screen_layout_config_test.dart index 53922ae..d763b08 100644 --- a/test/data/models/metering_screen_layout_config_test.dart +++ b/test/data/models/metering_screen_layout_config_test.dart @@ -2,30 +2,56 @@ import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:test/test.dart'; void main() { - test('fromJson', () { - expect( - MeteringScreenLayoutConfigJson.fromJson({'0': true, '1': true}), - { - MeteringScreenLayoutFeature.extremeExposurePairs: true, - MeteringScreenLayoutFeature.filmPicker: true, - }, - ); - expect( - MeteringScreenLayoutConfigJson.fromJson({'0': false, '1': false}), - { - MeteringScreenLayoutFeature.extremeExposurePairs: false, - MeteringScreenLayoutFeature.filmPicker: false, - }, - ); - }); + group( + 'fromJson()', + () { + test('All keys', () { + expect( + MeteringScreenLayoutConfigJson.fromJson( + { + '0': true, + '1': true, + '2': true, + }, + ), + { + MeteringScreenLayoutFeature.extremeExposurePairs: true, + MeteringScreenLayoutFeature.filmPicker: true, + MeteringScreenLayoutFeature.histogram: true, + }, + ); + }); + + test('Legacy (no equipment profiles)', () { + expect( + MeteringScreenLayoutConfigJson.fromJson( + { + '0': true, + '1': true, + }, + ), + { + MeteringScreenLayoutFeature.extremeExposurePairs: true, + MeteringScreenLayoutFeature.filmPicker: true, + MeteringScreenLayoutFeature.histogram: true, + }, + ); + }); + }, + ); test('toJson', () { expect( { MeteringScreenLayoutFeature.extremeExposurePairs: true, MeteringScreenLayoutFeature.filmPicker: true, + MeteringScreenLayoutFeature.histogram: true, }.toJson(), - {'0': true, '1': true}, + { + '0': true, + '1': true, + '2': true, + }, ); }); } diff --git a/test/data/shared_prefs_service_test.dart b/test/data/shared_prefs_service_test.dart index 1a2d05b..514629f 100644 --- a/test/data/shared_prefs_service_test.dart +++ b/test/data/shared_prefs_service_test.dart @@ -193,6 +193,7 @@ void main() { { MeteringScreenLayoutFeature.extremeExposurePairs: true, MeteringScreenLayoutFeature.filmPicker: true, + MeteringScreenLayoutFeature.histogram: true, }, ); }); @@ -206,6 +207,7 @@ void main() { { MeteringScreenLayoutFeature.extremeExposurePairs: false, MeteringScreenLayoutFeature.filmPicker: true, + MeteringScreenLayoutFeature.histogram: true, }, ); }); @@ -214,17 +216,18 @@ void main() { when( () => sharedPreferences.setString( UserPreferencesService.meteringScreenLayoutKey, - """{"0":false,"1":true}""", + """{"0":false,"1":true,"2":true}""", ), ).thenAnswer((_) => Future.value(true)); service.meteringScreenLayout = { MeteringScreenLayoutFeature.extremeExposurePairs: false, MeteringScreenLayoutFeature.filmPicker: true, + MeteringScreenLayoutFeature.histogram: true, }; verify( () => sharedPreferences.setString( UserPreferencesService.meteringScreenLayoutKey, - """{"0":false,"1":true}""", + """{"0":false,"1":true,"2":true}""", ), ).called(1); });