From b13acedebd693d3e9c82e036e143a5019c658e9d Mon Sep 17 00:00:00 2001
From: Vadim <44135514+vodemn@users.noreply.github.com>
Date: Mon, 10 Jul 2023 17:49:34 +0200
Subject: [PATCH 01/12] ML-62 Interactors tests (#87)
* removed redundant `UserPreferencesService` from `MeteringBloc`
* wip
* post-merge fixes
* `MeasureEvent` tests
* `MeasureEvent` tests revision
* `MeasureEvent` tests added timeout
* added stubs for other `MeteringBloc` events
* rewritten `MeteringBloc` logic
* wip
* `IsoChangedEvent` tests
* refined `IsoChangedEvent` tests
* `NdChangedEvent` tests
* `FilmChangedEvent` tests
* `MeteringCommunicationBloc` tests
* added test run to ci
* overriden `==` for `MeasuredState`
* `LuxMeteringEvent` tests
* refined `LuxMeteringEvent` tests
* rename
* wip
* wip
* `InitializeEvent`/`DeinitializeEvent` tests
* clamp minZoomLevel
* fixed `MeteringCommunicationBloc` tests
* wip
* `ZoomChangedEvent` tests
* `ExposureOffsetChangedEvent`/`ExposureOffsetResetEvent` tests
* renamed test groups
* added test coverage script
* improved `CameraContainerBloc` test coverage
* `EquipmentProfileChangedEvent` tests
* verify response vibration
* fixed running all tests
* `MeteringCommunicationBloc` equality tests
* `CameraContainerBloc` equality tests
* removed generated code from coverage
* `MeteringScreenLayoutFeature` tests
* `SupportedLocale` tests
* `Film` tests
* `CaffeineService` tests
* `UserPreferencesService` tests (wip)
* `LightSensorService` tests (wip)
* `migrateOldKeys()` tests
* ignore currently unused getters & setters
* gradle upgrade
* `reset(sharedPreferences);` calls count
* typo
* `MeteringInteractor` tests
* `SettingsInteractor` tests (wip)
* `MeteringInteractor` tests (wip)
* `SettingsInteractor` tests
---
analysis_options.yaml | 1 -
lib/interactors/metering_interactor.dart | 6 +-
lib/interactors/settings_interactor.dart | 2 +-
.../bloc_container_camera.dart | 2 +-
lib/screens/metering/flow_metering.dart | 2 +-
.../interactors/metering_interactor_test.dart | 274 ++++++++++++++++++
.../interactors/settings_interactor_test.dart | 205 +++++++++++++
.../camera/bloc_container_camera_test.dart | 12 +-
8 files changed, 492 insertions(+), 12 deletions(-)
create mode 100644 test/interactors/metering_interactor_test.dart
create mode 100644 test/interactors/settings_interactor_test.dart
diff --git a/analysis_options.yaml b/analysis_options.yaml
index c8e8d30..7f980c4 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,6 +1,5 @@
include: package:lint/strict.yaml
-
linter:
rules:
use_setters_to_change_properties: false
diff --git a/lib/interactors/metering_interactor.dart b/lib/interactors/metering_interactor.dart
index 34b03ec..7ea25ac 100644
--- a/lib/interactors/metering_interactor.dart
+++ b/lib/interactors/metering_interactor.dart
@@ -25,7 +25,9 @@ class MeteringInteractor {
this._permissionsService,
this._lightSensorService,
this._volumeEventsService,
- ) {
+ );
+
+ void initialize() {
if (_userPreferencesService.caffeine) {
_caffeineService.keepScreenOn(true);
}
@@ -69,7 +71,7 @@ class MeteringInteractor {
.then((value) => value == PermissionStatus.granted);
}
- Future requestPermission() async {
+ Future requestCameraPermission() async {
return _permissionsService
.requestCameraPermission()
.then((value) => value == PermissionStatus.granted);
diff --git a/lib/interactors/settings_interactor.dart b/lib/interactors/settings_interactor.dart
index 4eeb8b9..8f74dd2 100644
--- a/lib/interactors/settings_interactor.dart
+++ b/lib/interactors/settings_interactor.dart
@@ -41,8 +41,8 @@ class SettingsInteractor {
VolumeAction get volumeAction => _userPreferencesService.volumeAction;
Future setVolumeAction(VolumeAction value) async {
- await _volumeEventsService.setVolumeHandling(value != VolumeAction.none);
_userPreferencesService.volumeAction = value;
+ await _volumeEventsService.setVolumeHandling(value != VolumeAction.none);
}
bool get isHapticsEnabled => _userPreferencesService.haptics;
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 7f00256..cafce13 100644
--- a/lib/screens/metering/components/camera_container/bloc_container_camera.dart
+++ b/lib/screens/metering/components/camera_container/bloc_container_camera.dart
@@ -92,7 +92,7 @@ class CameraContainerBloc extends EvSourceBlocBase _onRequestPermission(_, Emitter emit) async {
- final hasPermission = await _meteringInteractor.requestPermission();
+ final hasPermission = await _meteringInteractor.requestCameraPermission();
if (!hasPermission) {
emit(const CameraErrorState(CameraErrorType.permissionNotGranted));
} else {
diff --git a/lib/screens/metering/flow_metering.dart b/lib/screens/metering/flow_metering.dart
index 780f537..1caef02 100644
--- a/lib/screens/metering/flow_metering.dart
+++ b/lib/screens/metering/flow_metering.dart
@@ -31,7 +31,7 @@ class _MeteringFlowState extends State {
context.get(),
context.get(),
context.get(),
- ),
+ )..initialize(),
child: InheritedWidgetBase(
data: VolumeKeysNotifier(context.get()),
child: MultiBlocProvider(
diff --git a/test/interactors/metering_interactor_test.dart b/test/interactors/metering_interactor_test.dart
new file mode 100644
index 0000000..bd6a169
--- /dev/null
+++ b/test/interactors/metering_interactor_test.dart
@@ -0,0 +1,274 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:lightmeter/data/caffeine_service.dart';
+import 'package:lightmeter/data/haptics_service.dart';
+import 'package:lightmeter/data/light_sensor_service.dart';
+import 'package:lightmeter/data/models/film.dart';
+import 'package:lightmeter/data/models/volume_action.dart';
+import 'package:lightmeter/data/permissions_service.dart';
+import 'package:lightmeter/data/shared_prefs_service.dart';
+import 'package:lightmeter/data/volume_events_service.dart';
+import 'package:lightmeter/interactors/metering_interactor.dart';
+import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
+import 'package:mocktail/mocktail.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+class _MockUserPreferencesService extends Mock implements UserPreferencesService {}
+
+class _MockCaffeineService extends Mock implements CaffeineService {}
+
+class _MockHapticsService extends Mock implements HapticsService {}
+
+class _MockPermissionsService extends Mock implements PermissionsService {}
+
+class _MockLightSensorService extends Mock implements LightSensorService {}
+
+class _MockVolumeEventsService extends Mock implements VolumeEventsService {}
+
+void main() {
+ late _MockUserPreferencesService mockUserPreferencesService;
+ late _MockCaffeineService mockCaffeineService;
+ late _MockHapticsService mockHapticsService;
+ late _MockPermissionsService mockPermissionsService;
+ late _MockLightSensorService mockLightSensorService;
+ late _MockVolumeEventsService mockVolumeEventsService;
+
+ late MeteringInteractor interactor;
+
+ setUp(() {
+ mockUserPreferencesService = _MockUserPreferencesService();
+ mockCaffeineService = _MockCaffeineService();
+ mockHapticsService = _MockHapticsService();
+ mockPermissionsService = _MockPermissionsService();
+ mockLightSensorService = _MockLightSensorService();
+ mockVolumeEventsService = _MockVolumeEventsService();
+
+ interactor = MeteringInteractor(
+ mockUserPreferencesService,
+ mockCaffeineService,
+ mockHapticsService,
+ mockPermissionsService,
+ mockLightSensorService,
+ mockVolumeEventsService,
+ );
+ });
+
+ group(
+ 'Initalization',
+ () {
+ test('caffeine - true', () async {
+ when(() => mockUserPreferencesService.caffeine).thenReturn(true);
+ when(() => mockCaffeineService.keepScreenOn(true)).thenAnswer((_) async => true);
+ when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.shutter);
+ when(() => mockVolumeEventsService.setVolumeHandling(true)).thenAnswer((_) async => true);
+ interactor.initialize();
+ verify(() => mockUserPreferencesService.caffeine).called(1);
+ verify(() => mockCaffeineService.keepScreenOn(true)).called(1);
+ verify(() => mockVolumeEventsService.setVolumeHandling(true)).called(1);
+ });
+
+ test('caffeine - false', () async {
+ when(() => mockUserPreferencesService.caffeine).thenReturn(false);
+ when(() => mockCaffeineService.keepScreenOn(false)).thenAnswer((_) async => false);
+ when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.shutter);
+ when(() => mockVolumeEventsService.setVolumeHandling(true)).thenAnswer((_) async => true);
+ interactor.initialize();
+ verify(() => mockUserPreferencesService.caffeine).called(1);
+ verifyNever(() => mockCaffeineService.keepScreenOn(false));
+ verify(() => mockVolumeEventsService.setVolumeHandling(true)).called(1);
+ });
+ },
+ );
+
+ group(
+ 'Calibration',
+ () {
+ test('cameraEvCalibration', () async {
+ when(() => mockUserPreferencesService.cameraEvCalibration).thenReturn(0.0);
+ expect(interactor.cameraEvCalibration, 0.0);
+ verify(() => mockUserPreferencesService.cameraEvCalibration).called(1);
+ });
+
+ test('lightSensorEvCalibration', () async {
+ when(() => mockUserPreferencesService.lightSensorEvCalibration).thenReturn(0.0);
+ expect(interactor.lightSensorEvCalibration, 0.0);
+ verify(() => mockUserPreferencesService.lightSensorEvCalibration).called(1);
+ });
+ },
+ );
+
+ group(
+ 'Equipment',
+ () {
+ test('iso - get', () async {
+ when(() => mockUserPreferencesService.iso).thenReturn(IsoValue.values.first);
+ expect(interactor.iso, IsoValue.values.first);
+ verify(() => mockUserPreferencesService.iso).called(1);
+ });
+
+ test('iso - set', () async {
+ when(() => mockUserPreferencesService.iso = IsoValue.values.first)
+ .thenReturn(IsoValue.values.first);
+ interactor.iso = IsoValue.values.first;
+ verify(() => mockUserPreferencesService.iso = IsoValue.values.first).called(1);
+ });
+
+ test('ndFilter - get', () async {
+ when(() => mockUserPreferencesService.ndFilter).thenReturn(NdValue.values.first);
+ expect(interactor.ndFilter, NdValue.values.first);
+ verify(() => mockUserPreferencesService.ndFilter).called(1);
+ });
+
+ test('ndFilter - set', () async {
+ when(() => mockUserPreferencesService.ndFilter = NdValue.values.first)
+ .thenReturn(NdValue.values.first);
+ interactor.ndFilter = NdValue.values.first;
+ verify(() => mockUserPreferencesService.ndFilter = NdValue.values.first).called(1);
+ });
+
+ test('film - get', () async {
+ when(() => mockUserPreferencesService.film).thenReturn(Film.values.first);
+ expect(interactor.film, Film.values.first);
+ verify(() => mockUserPreferencesService.film).called(1);
+ });
+
+ test('film - set', () async {
+ when(() => mockUserPreferencesService.film = Film.values.first)
+ .thenReturn(Film.values.first);
+ interactor.film = Film.values.first;
+ verify(() => mockUserPreferencesService.film = Film.values.first).called(1);
+ });
+ },
+ );
+
+ group(
+ 'Volume action',
+ () {
+ test('volumeAction - VolumeAction.shutter', () async {
+ when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.shutter);
+ expect(interactor.volumeAction, VolumeAction.shutter);
+ verify(() => mockUserPreferencesService.volumeAction).called(1);
+ });
+
+ test('volumeAction - VolumeAction.none', () async {
+ when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.none);
+ expect(interactor.volumeAction, VolumeAction.none);
+ verify(() => mockUserPreferencesService.volumeAction).called(1);
+ });
+ },
+ );
+
+ group(
+ 'Haptics',
+ () {
+ test('isHapticsEnabled', () async {
+ when(() => mockUserPreferencesService.haptics).thenReturn(true);
+ expect(interactor.isHapticsEnabled, true);
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ });
+
+ test('quickVibration() - true', () async {
+ when(() => mockUserPreferencesService.haptics).thenReturn(true);
+ when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
+ interactor.quickVibration();
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ verify(() => mockHapticsService.quickVibration()).called(1);
+ });
+
+ test('quickVibration() - false', () async {
+ when(() => mockUserPreferencesService.haptics).thenReturn(false);
+ when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
+ interactor.quickVibration();
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ verifyNever(() => mockHapticsService.quickVibration());
+ });
+
+ test('responseVibration() - true', () async {
+ when(() => mockUserPreferencesService.haptics).thenReturn(true);
+ when(() => mockHapticsService.responseVibration()).thenAnswer((_) async {});
+ interactor.responseVibration();
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ verify(() => mockHapticsService.responseVibration()).called(1);
+ });
+
+ test('responseVibration() - false', () async {
+ when(() => mockUserPreferencesService.haptics).thenReturn(false);
+ when(() => mockHapticsService.responseVibration()).thenAnswer((_) async {});
+ interactor.responseVibration();
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ verifyNever(() => mockHapticsService.responseVibration());
+ });
+
+ test('errorVibration() - true', () async {
+ when(() => mockUserPreferencesService.haptics).thenReturn(true);
+ when(() => mockHapticsService.errorVibration()).thenAnswer((_) async {});
+ interactor.errorVibration();
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ verify(() => mockHapticsService.errorVibration()).called(1);
+ });
+
+ test('errorVibration() - false', () async {
+ when(() => mockUserPreferencesService.haptics).thenReturn(false);
+ when(() => mockHapticsService.errorVibration()).thenAnswer((_) async {});
+ interactor.errorVibration();
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ verifyNever(() => mockHapticsService.errorVibration());
+ });
+ },
+ );
+
+ group(
+ 'Permissions',
+ () {
+ test('checkCameraPermission() - granted', () async {
+ when(() => mockPermissionsService.checkCameraPermission())
+ .thenAnswer((_) async => PermissionStatus.granted);
+ expectLater(interactor.checkCameraPermission(), completion(true));
+ verify(() => mockPermissionsService.checkCameraPermission()).called(1);
+ });
+
+ test('checkCameraPermission() - denied', () async {
+ when(() => mockPermissionsService.checkCameraPermission())
+ .thenAnswer((_) async => PermissionStatus.denied);
+ expectLater(interactor.checkCameraPermission(), completion(false));
+ verify(() => mockPermissionsService.checkCameraPermission()).called(1);
+ });
+
+ test('requestCameraPermission() - granted', () async {
+ when(() => mockPermissionsService.requestCameraPermission())
+ .thenAnswer((_) async => PermissionStatus.granted);
+ expectLater(interactor.requestCameraPermission(), completion(true));
+ verify(() => mockPermissionsService.requestCameraPermission()).called(1);
+ });
+
+ test('requestCameraPermission() - denied', () async {
+ when(() => mockPermissionsService.requestCameraPermission())
+ .thenAnswer((_) async => PermissionStatus.denied);
+ expectLater(interactor.requestCameraPermission(), completion(false));
+ verify(() => mockPermissionsService.requestCameraPermission()).called(1);
+ });
+ },
+ );
+
+ group(
+ 'Haptics',
+ () {
+ test('hasAmbientLightSensor() - true', () async {
+ when(() => mockLightSensorService.hasSensor()).thenAnswer((_) async => true);
+ expectLater(interactor.hasAmbientLightSensor(), completion(true));
+ verify(() => mockLightSensorService.hasSensor()).called(1);
+ });
+
+ test('hasAmbientLightSensor() - false', () async {
+ when(() => mockLightSensorService.hasSensor()).thenAnswer((_) async => false);
+ expectLater(interactor.hasAmbientLightSensor(), completion(false));
+ verify(() => mockLightSensorService.hasSensor()).called(1);
+ });
+
+ test('luxStream()', () async {
+ when(() => mockLightSensorService.luxStream()).thenAnswer((_) => const Stream.empty());
+ expect(interactor.luxStream(), const Stream.empty());
+ verify(() => mockLightSensorService.luxStream()).called(1);
+ });
+ },
+ );
+}
diff --git a/test/interactors/settings_interactor_test.dart b/test/interactors/settings_interactor_test.dart
new file mode 100644
index 0000000..03437dc
--- /dev/null
+++ b/test/interactors/settings_interactor_test.dart
@@ -0,0 +1,205 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:lightmeter/data/caffeine_service.dart';
+import 'package:lightmeter/data/haptics_service.dart';
+import 'package:lightmeter/data/models/volume_action.dart';
+import 'package:lightmeter/data/shared_prefs_service.dart';
+import 'package:lightmeter/data/volume_events_service.dart';
+import 'package:lightmeter/interactors/settings_interactor.dart';
+import 'package:mocktail/mocktail.dart';
+
+class _MockUserPreferencesService extends Mock implements UserPreferencesService {}
+
+class _MockCaffeineService extends Mock implements CaffeineService {}
+
+class _MockHapticsService extends Mock implements HapticsService {}
+
+class _MockVolumeEventsService extends Mock implements VolumeEventsService {}
+
+void main() {
+ late _MockUserPreferencesService mockUserPreferencesService;
+ late _MockCaffeineService mockCaffeineService;
+ late _MockHapticsService mockHapticsService;
+ late _MockVolumeEventsService mockVolumeEventsService;
+
+ late SettingsInteractor interactor;
+
+ setUp(() {
+ mockUserPreferencesService = _MockUserPreferencesService();
+ mockCaffeineService = _MockCaffeineService();
+ mockHapticsService = _MockHapticsService();
+ mockVolumeEventsService = _MockVolumeEventsService();
+
+ interactor = SettingsInteractor(
+ mockUserPreferencesService,
+ mockCaffeineService,
+ mockHapticsService,
+ mockVolumeEventsService,
+ );
+ });
+
+ group(
+ 'Calibration',
+ () {
+ test('cameraEvCalibration - get', () async {
+ when(() => mockUserPreferencesService.cameraEvCalibration).thenReturn(0.0);
+ expect(interactor.cameraEvCalibration, 0.0);
+ verify(() => mockUserPreferencesService.cameraEvCalibration).called(1);
+ });
+
+ test('cameraEvCalibration - set', () async {
+ when(() => mockUserPreferencesService.cameraEvCalibration = 0.0).thenReturn(0.0);
+ interactor.setCameraEvCalibration(0.0);
+ verify(() => mockUserPreferencesService.cameraEvCalibration = 0.0).called(1);
+ });
+
+ test('lightSensorEvCalibration - get', () async {
+ when(() => mockUserPreferencesService.lightSensorEvCalibration).thenReturn(0.0);
+ expect(interactor.lightSensorEvCalibration, 0.0);
+ verify(() => mockUserPreferencesService.lightSensorEvCalibration).called(1);
+ });
+
+ test('lightSensorEvCalibration - set', () async {
+ when(() => mockUserPreferencesService.lightSensorEvCalibration = 0.0).thenReturn(0.0);
+ interactor.setLightSensorEvCalibration(0.0);
+ verify(() => mockUserPreferencesService.lightSensorEvCalibration = 0.0).called(1);
+ });
+ },
+ );
+
+ group(
+ 'Caffeine',
+ () {
+ test('isCaffeineEnabled', () async {
+ when(() => mockUserPreferencesService.caffeine).thenReturn(true);
+ expect(interactor.isCaffeineEnabled, true);
+ verify(() => mockUserPreferencesService.caffeine).called(1);
+ });
+
+ test('enableCaffeine(true)', () async {
+ when(() => mockCaffeineService.keepScreenOn(true)).thenAnswer((_) async => true);
+ when(() => mockUserPreferencesService.caffeine = true).thenReturn(true);
+ await interactor.enableCaffeine(true);
+ verify(() => mockCaffeineService.keepScreenOn(true)).called(1);
+ verify(() => mockUserPreferencesService.caffeine = true).called(1);
+ });
+ },
+ );
+
+ group(
+ 'Volume action',
+ () {
+ test('disableVolumeHandling()', () async {
+ when(() => mockVolumeEventsService.setVolumeHandling(false)).thenAnswer((_) async => false);
+ expectLater(interactor.disableVolumeHandling(), isA>());
+ verify(() => mockVolumeEventsService.setVolumeHandling(false)).called(1);
+ });
+
+ test('restoreVolumeHandling() - VolumeAction.shutter', () async {
+ when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.shutter);
+ when(() => mockVolumeEventsService.setVolumeHandling(true)).thenAnswer((_) async => true);
+ expectLater(interactor.restoreVolumeHandling(), isA>());
+ verify(() => mockUserPreferencesService.volumeAction).called(1);
+ verify(() => mockVolumeEventsService.setVolumeHandling(true)).called(1);
+ });
+
+ test('restoreVolumeHandling() - VolumeAction.none', () async {
+ when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.none);
+ when(() => mockVolumeEventsService.setVolumeHandling(false)).thenAnswer((_) async => false);
+ expectLater(interactor.restoreVolumeHandling(), isA>());
+ verify(() => mockUserPreferencesService.volumeAction).called(1);
+ verify(() => mockVolumeEventsService.setVolumeHandling(false)).called(1);
+ });
+
+ test('volumeAction - VolumeAction.shutter', () async {
+ when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.shutter);
+ expect(interactor.volumeAction, VolumeAction.shutter);
+ verify(() => mockUserPreferencesService.volumeAction).called(1);
+ });
+
+ test('volumeAction - VolumeAction.none', () async {
+ when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.none);
+ expect(interactor.volumeAction, VolumeAction.none);
+ verify(() => mockUserPreferencesService.volumeAction).called(1);
+ });
+
+ test('setVolumeAction(VolumeAction.shutter)', () async {
+ when(() => mockUserPreferencesService.volumeAction = VolumeAction.shutter)
+ .thenReturn(VolumeAction.shutter);
+ when(() => mockVolumeEventsService.setVolumeHandling(true)).thenAnswer((_) async => true);
+ expectLater(interactor.setVolumeAction(VolumeAction.shutter), isA>());
+ verify(() => mockVolumeEventsService.setVolumeHandling(true)).called(1);
+ verify(() => mockUserPreferencesService.volumeAction = VolumeAction.shutter).called(1);
+ });
+
+ test('setVolumeAction(VolumeAction.none)', () async {
+ when(() => mockUserPreferencesService.volumeAction = VolumeAction.none)
+ .thenReturn(VolumeAction.none);
+ when(() => mockVolumeEventsService.setVolumeHandling(false)).thenAnswer((_) async => false);
+ expectLater(interactor.setVolumeAction(VolumeAction.none), isA>());
+ verify(() => mockVolumeEventsService.setVolumeHandling(false)).called(1);
+ verify(() => mockUserPreferencesService.volumeAction = VolumeAction.none).called(1);
+ });
+ },
+ );
+
+ group(
+ 'Haptics',
+ () {
+ test('isHapticsEnabled', () async {
+ when(() => mockUserPreferencesService.haptics).thenReturn(true);
+ expect(interactor.isHapticsEnabled, true);
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ });
+
+ test('enableHaptics() - true', () async {
+ when(() => mockUserPreferencesService.haptics = true).thenReturn(true);
+ when(() => mockUserPreferencesService.haptics).thenReturn(true);
+ when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
+ interactor.enableHaptics(true);
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ verify(() => mockHapticsService.quickVibration()).called(1);
+ });
+
+ test('enableHaptics() - false', () async {
+ when(() => mockUserPreferencesService.haptics = false).thenReturn(false);
+ when(() => mockUserPreferencesService.haptics).thenReturn(false);
+ when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
+ interactor.enableHaptics(false);
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ verifyNever(() => mockHapticsService.quickVibration());
+ });
+
+ test('quickVibration() - true', () async {
+ when(() => mockUserPreferencesService.haptics).thenReturn(true);
+ when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
+ interactor.quickVibration();
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ verify(() => mockHapticsService.quickVibration()).called(1);
+ });
+
+ test('quickVibration() - false', () async {
+ when(() => mockUserPreferencesService.haptics).thenReturn(false);
+ when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
+ interactor.quickVibration();
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ verifyNever(() => mockHapticsService.quickVibration());
+ });
+
+ test('responseVibration() - true', () async {
+ when(() => mockUserPreferencesService.haptics).thenReturn(true);
+ when(() => mockHapticsService.responseVibration()).thenAnswer((_) async {});
+ interactor.responseVibration();
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ verify(() => mockHapticsService.responseVibration()).called(1);
+ });
+
+ test('responseVibration() - false', () async {
+ when(() => mockUserPreferencesService.haptics).thenReturn(false);
+ when(() => mockHapticsService.responseVibration()).thenAnswer((_) async {});
+ interactor.responseVibration();
+ verify(() => mockUserPreferencesService.haptics).called(1);
+ verifyNever(() => mockHapticsService.responseVibration());
+ });
+ },
+ );
+}
diff --git a/test/screens/metering/components/camera/bloc_container_camera_test.dart b/test/screens/metering/components/camera/bloc_container_camera_test.dart
index fa08edf..d54c0a5 100644
--- a/test/screens/metering/components/camera/bloc_container_camera_test.dart
+++ b/test/screens/metering/components/camera/bloc_container_camera_test.dart
@@ -140,11 +140,11 @@ void main() {
'Request denied',
build: () => bloc,
setUp: () {
- when(() => meteringInteractor.requestPermission()).thenAnswer((_) async => false);
+ when(() => meteringInteractor.requestCameraPermission()).thenAnswer((_) async => false);
},
act: (bloc) => bloc.add(const RequestPermissionEvent()),
verify: (_) {
- verify(() => meteringInteractor.requestPermission()).called(1);
+ verify(() => meteringInteractor.requestCameraPermission()).called(1);
},
expect: () => [
isA()
@@ -156,12 +156,12 @@ void main() {
'Request granted -> check denied',
build: () => bloc,
setUp: () {
- when(() => meteringInteractor.requestPermission()).thenAnswer((_) async => true);
+ when(() => meteringInteractor.requestCameraPermission()).thenAnswer((_) async => true);
when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => false);
},
act: (bloc) => bloc.add(const RequestPermissionEvent()),
verify: (_) {
- verify(() => meteringInteractor.requestPermission()).called(1);
+ verify(() => meteringInteractor.requestCameraPermission()).called(1);
verify(() => meteringInteractor.checkCameraPermission()).called(1);
},
expect: () => [
@@ -175,12 +175,12 @@ void main() {
'Request granted -> check granted',
build: () => bloc,
setUp: () {
- when(() => meteringInteractor.requestPermission()).thenAnswer((_) async => true);
+ when(() => meteringInteractor.requestCameraPermission()).thenAnswer((_) async => true);
when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => true);
},
act: (bloc) => bloc.add(const RequestPermissionEvent()),
verify: (_) {
- verify(() => meteringInteractor.requestPermission()).called(1);
+ verify(() => meteringInteractor.requestCameraPermission()).called(1);
verify(() => meteringInteractor.checkCameraPermission()).called(1);
},
expect: () => initializedStateSequence,
From dbf1f09eb68e1b0e9ec817a500f4695f186288bb Mon Sep 17 00:00:00 2001
From: Vadim
Date: Mon, 24 Jul 2023 09:08:37 +0200
Subject: [PATCH 02/12] Renamed `EquipmentProfileData` -> `EquipmentProfile`
---
lib/data/models/supported_locale.dart | 4 +++-
lib/data/shared_prefs_service.dart | 4 ++--
lib/providers/equipment_profile_provider.dart | 21 +++++++++----------
.../widget_container_readings.dart | 2 +-
lib/screens/metering/event_metering.dart | 2 +-
lib/screens/metering/screen_metering.dart | 1 -
.../widget_container_equipment_profile.dart | 10 ++++-----
.../screen_equipment_profile.dart | 2 +-
.../widget_list_tile_equipment_profiles.dart | 2 +-
test/screens/metering/bloc_metering_test.dart | 2 +-
10 files changed, 25 insertions(+), 25 deletions(-)
diff --git a/lib/data/models/supported_locale.dart b/lib/data/models/supported_locale.dart
index e5f6dcb..445a30c 100644
--- a/lib/data/models/supported_locale.dart
+++ b/lib/data/models/supported_locale.dart
@@ -1,4 +1,4 @@
-enum SupportedLocale { en, fr, ru }
+enum SupportedLocale { en, fr, ru, cn }
extension SupportedLocaleExtension on SupportedLocale {
String get intlName => toString().replaceAll("SupportedLocale.", "");
@@ -11,6 +11,8 @@ extension SupportedLocaleExtension on SupportedLocale {
return 'Français';
case SupportedLocale.ru:
return 'Русский';
+ case SupportedLocale.cn:
+ return '<--->';
}
}
}
diff --git a/lib/data/shared_prefs_service.dart b/lib/data/shared_prefs_service.dart
index 76f9045..8105d5c 100644
--- a/lib/data/shared_prefs_service.dart
+++ b/lib/data/shared_prefs_service.dart
@@ -150,6 +150,6 @@ class UserPreferencesService {
String get selectedEquipmentProfileId => ''; // coverage:ignore-line
set selectedEquipmentProfileId(String id) {} // coverage:ignore-line
- List get equipmentProfiles => []; // coverage:ignore-line
- set equipmentProfiles(List profiles) {} // coverage:ignore-line
+ List get equipmentProfiles => []; // coverage:ignore-line
+ set equipmentProfiles(List profiles) {} // coverage:ignore-line
}
diff --git a/lib/providers/equipment_profile_provider.dart b/lib/providers/equipment_profile_provider.dart
index c5ef82d..85c9377 100644
--- a/lib/providers/equipment_profile_provider.dart
+++ b/lib/providers/equipment_profile_provider.dart
@@ -4,8 +4,7 @@ import 'package:lightmeter/utils/inherited_generics.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
import 'package:uuid/uuid.dart';
-typedef EquipmentProfiles = List;
-typedef EquipmentProfile = EquipmentProfileData;
+typedef EquipmentProfiles = List;
class EquipmentProfileProvider extends StatefulWidget {
final Widget child;
@@ -21,7 +20,7 @@ class EquipmentProfileProvider extends StatefulWidget {
}
class EquipmentProfileProviderState extends State {
- static const EquipmentProfileData _defaultProfile = EquipmentProfileData(
+ static const EquipmentProfile _defaultProfile = EquipmentProfile(
id: '',
name: '',
apertureValues: ApertureValue.values,
@@ -30,10 +29,10 @@ class EquipmentProfileProviderState extends State {
isoValues: IsoValue.values,
);
- List _customProfiles = [];
+ List _customProfiles = [];
String _selectedId = '';
- EquipmentProfileData get _selectedProfile => _customProfiles.firstWhere(
+ EquipmentProfile get _selectedProfile => _customProfiles.firstWhere(
(e) => e.id == _selectedId,
orElse: () {
context.get().selectedEquipmentProfileId = _defaultProfile.id;
@@ -50,16 +49,16 @@ class EquipmentProfileProviderState extends State {
@override
Widget build(BuildContext context) {
- return InheritedWidgetBase>(
+ return InheritedWidgetBase>(
data: [_defaultProfile] + _customProfiles,
- child: InheritedWidgetBase(
+ child: InheritedWidgetBase(
data: _selectedProfile,
child: widget.child,
),
);
}
- void setProfile(EquipmentProfileData data) {
+ void setProfile(EquipmentProfile data) {
setState(() {
_selectedId = data.id;
});
@@ -69,7 +68,7 @@ class EquipmentProfileProviderState extends State {
/// Creates a default equipment profile
void addProfile(String name) {
_customProfiles.add(
- EquipmentProfileData(
+ EquipmentProfile(
id: const Uuid().v1(),
name: name,
apertureValues: ApertureValue.values,
@@ -81,7 +80,7 @@ class EquipmentProfileProviderState extends State {
_refreshSavedProfiles();
}
- void updateProdile(EquipmentProfileData data) {
+ void updateProdile(EquipmentProfile data) {
final indexToUpdate = _customProfiles.indexWhere((element) => element.id == data.id);
if (indexToUpdate >= 0) {
_customProfiles[indexToUpdate] = data;
@@ -89,7 +88,7 @@ class EquipmentProfileProviderState extends State {
}
}
- void deleteProfile(EquipmentProfileData data) {
+ void deleteProfile(EquipmentProfile data) {
_customProfiles.remove(data);
_refreshSavedProfiles();
}
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 09f8bce..0893305 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
@@ -105,7 +105,7 @@ class _EquipmentProfilePicker extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return AnimatedDialogPicker(
+ return AnimatedDialogPicker(
icon: Icons.camera,
title: S.of(context).equipmentProfile,
selectedValue: context.listen(),
diff --git a/lib/screens/metering/event_metering.dart b/lib/screens/metering/event_metering.dart
index 852b5e4..bf3a22d 100644
--- a/lib/screens/metering/event_metering.dart
+++ b/lib/screens/metering/event_metering.dart
@@ -6,7 +6,7 @@ sealed class MeteringEvent {
}
class EquipmentProfileChangedEvent extends MeteringEvent {
- final EquipmentProfileData equipmentProfileData;
+ final EquipmentProfile equipmentProfileData;
const EquipmentProfileChangedEvent(this.equipmentProfileData);
}
diff --git a/lib/screens/metering/screen_metering.dart b/lib/screens/metering/screen_metering.dart
index 4fb1e85..bbfebfc 100644
--- a/lib/screens/metering/screen_metering.dart
+++ b/lib/screens/metering/screen_metering.dart
@@ -7,7 +7,6 @@ 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/screens/metering/bloc_metering.dart';
import 'package:lightmeter/screens/metering/components/bottom_controls/provider_bottom_controls.dart';
diff --git a/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.dart b/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.dart
index b240611..1d9b7bf 100644
--- a/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.dart
+++ b/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.dart
@@ -8,8 +8,8 @@ import 'package:lightmeter/screens/settings/components/metering/components/equip
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class EquipmentProfileContainer extends StatefulWidget {
- final EquipmentProfileData data;
- final ValueChanged onUpdate;
+ final EquipmentProfile data;
+ final ValueChanged onUpdate;
final VoidCallback onDelete;
final VoidCallback onExpand;
@@ -27,7 +27,7 @@ class EquipmentProfileContainer extends StatefulWidget {
class EquipmentProfileContainerState extends State
with TickerProviderStateMixin {
- late EquipmentProfileData _equipmentData = EquipmentProfileData(
+ late EquipmentProfile _equipmentData = EquipmentProfile(
id: widget.data.id,
name: widget.data.name,
apertureValues: widget.data.apertureValues,
@@ -45,7 +45,7 @@ class EquipmentProfileContainerState extends State
@override
void didUpdateWidget(EquipmentProfileContainer oldWidget) {
super.didUpdateWidget(oldWidget);
- _equipmentData = EquipmentProfileData(
+ _equipmentData = EquipmentProfile(
id: widget.data.id,
name: widget.data.name,
apertureValues: widget.data.apertureValues,
@@ -195,7 +195,7 @@ class _AnimatedArrowButton extends AnimatedWidget {
}
class _AnimatedEquipmentListTiles extends AnimatedWidget {
- final EquipmentProfileData equipmentData;
+ final EquipmentProfile equipmentData;
final ValueChanged> onApertureValuesSelected;
final ValueChanged> onIsoValuesSelecred;
final ValueChanged> onNdValuesSelected;
diff --git a/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart b/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart
index 25ca59a..4872d79 100644
--- a/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart
+++ b/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart
@@ -84,7 +84,7 @@ class _EquipmentProfilesScreenState extends State {
});
}
- void _updateProfileAt(EquipmentProfileData data, int index) {
+ void _updateProfileAt(EquipmentProfile data, int index) {
EquipmentProfileProvider.of(context).updateProdile(data);
}
diff --git a/lib/screens/settings/components/metering/components/equipment_profiles/widget_list_tile_equipment_profiles.dart b/lib/screens/settings/components/metering/components/equipment_profiles/widget_list_tile_equipment_profiles.dart
index 13cb6f0..d1a6ef3 100644
--- a/lib/screens/settings/components/metering/components/equipment_profiles/widget_list_tile_equipment_profiles.dart
+++ b/lib/screens/settings/components/metering/components/equipment_profiles/widget_list_tile_equipment_profiles.dart
@@ -12,7 +12,7 @@ class EquipmentProfilesListTile extends StatelessWidget {
leading: const Icon(Icons.camera),
title: Text(S.of(context).equipmentProfiles),
onTap: () {
- Navigator.of(context).push(
+ Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const EquipmentProfilesScreen()),
);
},
diff --git a/test/screens/metering/bloc_metering_test.dart b/test/screens/metering/bloc_metering_test.dart
index 3593c91..45b9291 100644
--- a/test/screens/metering/bloc_metering_test.dart
+++ b/test/screens/metering/bloc_metering_test.dart
@@ -495,7 +495,7 @@ void main() {
group(
'`EquipmentProfileChangedEvent`',
() {
- final reducedProfile = EquipmentProfileData(
+ final reducedProfile = EquipmentProfile(
id: '0',
name: 'Reduced',
apertureValues: ApertureValue.values,
From bb9b023fa7c7d3e788f1b87cac39921de99c01e6 Mon Sep 17 00:00:00 2001
From: ScaredCube <74418739+ScaredCube@users.noreply.github.com>
Date: Mon, 24 Jul 2023 15:35:30 +0800
Subject: [PATCH 03/12] Add Chinese language support (#91)
* Add Chinese language support
* Update intl_cn.arb
* Fixed some bugs
* Add Chinese language support
* renamed `cn` to `zh`
---------
Co-authored-by: Vadim <44135514+vodemn@users.noreply.github.com>
Co-authored-by: Vadim
---
lib/data/models/supported_locale.dart | 6 +-
lib/l10n/intl_zh.arb | 88 +++++++++++++++++++++++++++
2 files changed, 91 insertions(+), 3 deletions(-)
create mode 100644 lib/l10n/intl_zh.arb
diff --git a/lib/data/models/supported_locale.dart b/lib/data/models/supported_locale.dart
index 445a30c..0d93a50 100644
--- a/lib/data/models/supported_locale.dart
+++ b/lib/data/models/supported_locale.dart
@@ -1,4 +1,4 @@
-enum SupportedLocale { en, fr, ru, cn }
+enum SupportedLocale { en, fr, ru, zh }
extension SupportedLocaleExtension on SupportedLocale {
String get intlName => toString().replaceAll("SupportedLocale.", "");
@@ -11,8 +11,8 @@ extension SupportedLocaleExtension on SupportedLocale {
return 'Français';
case SupportedLocale.ru:
return 'Русский';
- case SupportedLocale.cn:
- return '<--->';
+ case SupportedLocale.zh:
+ return '简体中文';
}
}
}
diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb
new file mode 100644
index 0000000..b011254
--- /dev/null
+++ b/lib/l10n/intl_zh.arb
@@ -0,0 +1,88 @@
+{
+ "@@locale": "zh",
+ "fastestExposurePair": "最快曝光组合",
+ "slowestExposurePair": "最慢曝光组合",
+ "ev": "EV",
+ "evValue": "{value} EV",
+ "@evValue": {
+ "placeholders": {
+ "value": {
+ "type": "String"
+ }
+ }
+ },
+ "iso": "ISO",
+ "filmSpeed": "胶片感光度",
+ "nd": "ND",
+ "ndFilterFactor": "ND 滤镜系数",
+ "noExposurePairs": "所选设置没有曝光配对",
+ "noCamerasDetected": "您的设备似乎没有连接到任何摄像头",
+ "noCameraPermission": "未获得摄像头权限",
+ "otherCameraError": "连接摄像头时发生错误",
+ "none": "无",
+ "cancel": "取消",
+ "select": "选择",
+ "save": "保存",
+ "settings": "设置",
+ "metering": "测量",
+ "fractionalStops": "分数位",
+ "showFractionalStops": "显示分数位",
+ "halfStops": "1/2",
+ "thirdStops": "1/3",
+ "calibration": "校准",
+ "calibrationMessage": "此应用测量读数的准确性完全取决于设备的硬件。因此,请考虑测试此应用并手动设置 EV 校准,以获得准确的测量结果。",
+ "calibrationMessageCameraOnly": "此应用程序测量读数的准确性完全取决于设备的后置摄像头。因此,请考虑测试此应用并手动设置 EV 校准,以获得准确的测量结果。",
+ "camera": "摄像头",
+ "lightSensor": "光传感器",
+ "meteringScreenLayout": "布局",
+ "meteringScreenLayoutHint": "隐藏不需要的元素,以免浪费曝光列表空间",
+ "meteringScreenFeatureExtremeExposurePairs": "最快 & 最慢曝光组合",
+ "meteringScreenFeatureFilmPicker": "胶片选择",
+ "film": "胶片",
+ "equipment": "设备",
+ "equipmentProfileName": "设备配置名称",
+ "equipmentProfileNameHint": "Praktica MTL5B",
+ "equipmentProfileAllValues": "全部",
+ "apertureValues": "光圈值",
+ "apertureValuesFilterDescription": "选择要显示的光圈值范围。这通常由您使用的镜头决定。",
+ "ndFilters": "ND 滤镜",
+ "ndFiltersFilterDescription": "选择要显示的 ND 滤镜。这些可能是您最常用的 ND 滤镜,也可能是适合您镜头的滤光镜。",
+ "shutterSpeedValues": "快门速度",
+ "shutterSpeedValuesFilterDescription": "选择要显示的快门速度范围。这通常由您使用的相机机身决定。",
+ "isoValues": "ISO",
+ "isoValuesFilterDescription": "选择要显示的 ISO。这些值可能是您最常用的值,也可能是相机支持的值。",
+ "equipmentProfile": "设备配置",
+ "equipmentProfiles": "设备配置",
+ "general": "通用",
+ "keepScreenOn": "保持屏幕常亮",
+ "haptics": "震动",
+ "volumeKeysAction": "音量键快门",
+ "language": "语言",
+ "chooseLanguage": "选择语言",
+ "theme": "主题",
+ "chooseTheme": "选择主题",
+ "themeLight": "亮色",
+ "themeDark": "暗色",
+ "themeSystemDefault": "跟随系统",
+ "dynamicColor": "动态颜色",
+ "primaryColor": "主题颜色",
+ "choosePrimaryColor": "选择主题颜色",
+ "about": "关于",
+ "sourceCode": "源代码",
+ "reportIssue": "报告问题",
+ "writeEmail": "Email",
+ "youDontHaveMailApp": "您没有安装任何邮件App。",
+ "copyEmail": "复制电子邮件",
+ "version": "Version",
+ "versionNumber": "{version} ({buildNumber})",
+ "@versionNumber": {
+ "placeholders": {
+ "version": {
+ "type": "String"
+ },
+ "buildNumber": {
+ "type": "String"
+ }
+ }
+ }
+}
From dd5f551fd2d35ba2c9c192a969caff43894a675e Mon Sep 17 00:00:00 2001
From: vodemn
Date: Mon, 24 Jul 2023 07:54:38 +0000
Subject: [PATCH 04/12] Version bump
---
pubspec.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pubspec.yaml b/pubspec.yaml
index 222ac3c..cad178c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,7 @@
name: lightmeter
description: A new Flutter project.
publish_to: "none"
-version: 0.12.0+31
+version: 0.12.1+32
environment:
sdk: ">=3.0.0 <4.0.0"
From b02b50bac349f2fa1c23e15b9739040948198edc Mon Sep 17 00:00:00 2001
From: ScaredCube <74418739+ScaredCube@users.noreply.github.com>
Date: Mon, 24 Jul 2023 18:16:35 +0800
Subject: [PATCH 05/12] Fixed Chinese translation (#93)
---
lib/l10n/intl_zh.arb | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb
index b011254..b931906 100644
--- a/lib/l10n/intl_zh.arb
+++ b/lib/l10n/intl_zh.arb
@@ -25,8 +25,8 @@
"save": "保存",
"settings": "设置",
"metering": "测量",
- "fractionalStops": "分数位",
- "showFractionalStops": "显示分数位",
+ "fractionalStops": "EV 步进值",
+ "showFractionalStops": "显示 EV 步进值",
"halfStops": "1/2",
"thirdStops": "1/3",
"calibration": "校准",
@@ -46,7 +46,7 @@
"apertureValues": "光圈值",
"apertureValuesFilterDescription": "选择要显示的光圈值范围。这通常由您使用的镜头决定。",
"ndFilters": "ND 滤镜",
- "ndFiltersFilterDescription": "选择要显示的 ND 滤镜。这些可能是您最常用的 ND 滤镜,也可能是适合您镜头的滤光镜。",
+ "ndFiltersFilterDescription": "选择要显示的 ND 滤镜系数。这些可能是您最常用的 ND 滤镜,也可能是适合您镜头的滤光镜。",
"shutterSpeedValues": "快门速度",
"shutterSpeedValuesFilterDescription": "选择要显示的快门速度范围。这通常由您使用的相机机身决定。",
"isoValues": "ISO",
From 119e079554016718e53a6b87a86d1fce5d238506 Mon Sep 17 00:00:00 2001
From: vodemn
Date: Mon, 24 Jul 2023 11:04:23 +0000
Subject: [PATCH 06/12] Version bump
---
pubspec.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pubspec.yaml b/pubspec.yaml
index cad178c..1f8ed64 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,7 @@
name: lightmeter
description: A new Flutter project.
publish_to: "none"
-version: 0.12.1+32
+version: 0.12.2+33
environment:
sdk: ">=3.0.0 <4.0.0"
From 40c670ad3033809ca996456c353998aa6cab672f Mon Sep 17 00:00:00 2001
From: Vadim <44135514+vodemn@users.noreply.github.com>
Date: Tue, 25 Jul 2023 17:31:01 +0200
Subject: [PATCH 07/12] Updated README Build section (#94)
* Update README.md
* Set exact Flutter version for workflows
* Added stub `DefaultFirebaseOptions`
* Fixed `rm`
* Removed `rm`
* Update .gitignore
* Added readable name to ci workflow
* Build -> Development
* Update ci.yml
---
.github/workflows/cd_dev.yml | 1 +
.github/workflows/cd_prod.yml | 1 +
.github/workflows/ci.yml | 30 ++++------------
README.md | 32 ++++++++++++++---
lib/firebase_options.dart | 68 +++++++++++++++++++++++++++++++++++
lib/main_prod.dart | 8 ++++-
6 files changed, 112 insertions(+), 28 deletions(-)
create mode 100644 lib/firebase_options.dart
diff --git a/.github/workflows/cd_dev.yml b/.github/workflows/cd_dev.yml
index d90d0fb..e0e6a25 100644
--- a/.github/workflows/cd_dev.yml
+++ b/.github/workflows/cd_dev.yml
@@ -71,6 +71,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: "stable"
+ flutter-version: '3.10.0'
- name: Prepare flutter project
run: |
diff --git a/.github/workflows/cd_prod.yml b/.github/workflows/cd_prod.yml
index 9ff3d6e..9e45644 100644
--- a/.github/workflows/cd_prod.yml
+++ b/.github/workflows/cd_prod.yml
@@ -73,6 +73,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: "stable"
+ flutter-version: '3.10.0'
- name: Prepare flutter project
run: |
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e8956f6..9022170 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,18 +12,12 @@ on:
branches: ["main"]
jobs:
- build:
+ analyze_and_test:
+ name: Analyze & test
runs-on: macos-11
timeout-minutes: 10
steps:
- - uses: shaunco/ssh-agent@git-repo-mapping
- with:
- ssh-private-key: |
- ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
- repo-mappings: |
- github.com/vodemn/m3_lightmeter_iap
-
- uses: actions/checkout@v3
with:
submodules: recursive
@@ -31,23 +25,13 @@ jobs:
- uses: subosito/flutter-action@v2
with:
channel: "stable"
+ flutter-version: '3.10.0'
- - name: Check flutter version
- run: flutter --version
-
- - name: Install dependencies
- run: flutter pub get
-
- - name: Generate intl
- run: flutter pub run intl_utils:generate
-
- - name: Restore firebase_options.dart
- env:
- FIREBASE_OPTIONS: ${{ secrets.FIREBASE_OPTIONS }}
+ - name: Prepare flutter project
run: |
- FIREBASE_OPTIONS_PATH=$RUNNER_TEMP/firebase_options.dart
- echo -n "$FIREBASE_OPTIONS" | base64 --decode --output $FIREBASE_OPTIONS_PATH
- cp $FIREBASE_OPTIONS_PATH ./lib
+ flutter --version
+ flutter pub get
+ flutter pub run intl_utils:generate
- name: Analyze project source
run: flutter analyze lib --fatal-infos
diff --git a/README.md b/README.md
index 0b4dae8..2fef498 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
- [Table of contents](#table-of-contents)
- [Backstory](#backstory)
- [Screenshots](#screenshots)
-- [Build](#build)
+- [Development](#development)
- [Contribution](#contribution)
- [iOS Limitations](#ios-limitations)
@@ -27,22 +27,46 @@ Without further delay behold my new Lightmeter app inspired by Material You (a.k
-# Build
+# Development
-As part of this project is private, you will be able to run this app from the _main_dev.dart_ file (i.e. --flavor dev). Also to avoid fatal errors the _main_prod.dart_ file is excluded from analysis.
+### 1. Install Flutter
+
+To build this app you need to install Flutter 3.10.0 stable. [How to install](https://docs.flutter.dev/get-started/install).
+
+### 2. (Optional) Install Firebase
+
+Out of the box Firebase Crashlytics won't work. If you want to add Crashlytics to your local build please follow [this guide](https://firebase.google.com/docs/flutter/setup).
+
+### 3. Get packages
+
+Fetch all the neccessary dependencies and generate translation files by running the following commands:
+```console
+flutter pub get
+flutter pub run intl_utils:generate
+```
+
+### 4. Build
+
+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
+```
+Just replace `$FLAVOR` with `dev` or `prod`.
# Contribution
To report a bug or suggest a new feature open a new [issue](https://github.com/vodemn/m3_lightmeter/issues).
-In case you want to help develop this project you need to follow this [style guide](doc/style_guide.md).
+In case you want to help develop this project feel free to open a Pull Request, but you need to follow this [style guide](doc/style_guide.md).
# iOS Limitations
A list of features, that Android version of the app has and that iOS does not.
## Incident light metering
+
Apple does not provide API for reading Lux stream form the ambient light sensor. Lux can be calculated based on front camera image stream, but this would be a reflected light. So there is no way incident light metering can be implemented on iOS.
## Volume buttons action
+
This can be [implemented](https://stackoverflow.com/questions/70161271/ios-override-hardware-volume-buttons-same-as-zello) but the app will be rejected due to [2.5.9](https://developer.apple.com/app-store/review/guidelines/#software-requirements)
\ No newline at end of file
diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart
new file mode 100644
index 0000000..a8a32c7
--- /dev/null
+++ b/lib/firebase_options.dart
@@ -0,0 +1,68 @@
+// File generated by FlutterFire CLI.
+// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
+import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
+import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform;
+
+/// Default [FirebaseOptions] for use with your Firebase apps.
+///
+/// Example:
+/// ```dart
+/// import 'firebase_options.dart';
+/// // ...
+/// await Firebase.initializeApp(
+/// options: DefaultFirebaseOptions.currentPlatform,
+/// );
+/// ```
+class DefaultFirebaseOptions {
+ static FirebaseOptions get currentPlatform {
+ if (kIsWeb) {
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions have not been configured for web - '
+ 'you can reconfigure this by running the FlutterFire CLI again.',
+ );
+ }
+ switch (defaultTargetPlatform) {
+ case TargetPlatform.android:
+ return android;
+ case TargetPlatform.iOS:
+ return ios;
+ case TargetPlatform.macOS:
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions have not been configured for macos - '
+ 'you can reconfigure this by running the FlutterFire CLI again.',
+ );
+ case TargetPlatform.windows:
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions have not been configured for windows - '
+ 'you can reconfigure this by running the FlutterFire CLI again.',
+ );
+ case TargetPlatform.linux:
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions have not been configured for linux - '
+ 'you can reconfigure this by running the FlutterFire CLI again.',
+ );
+ default:
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions are not supported for this platform.',
+ );
+ }
+ }
+
+ static const FirebaseOptions android = FirebaseOptions(
+ apiKey: '',
+ appId: '',
+ messagingSenderId: '',
+ projectId: '',
+ storageBucket: '',
+ );
+
+ static const FirebaseOptions ios = FirebaseOptions(
+ apiKey: '',
+ appId: '',
+ messagingSenderId: '',
+ projectId: '',
+ storageBucket: '',
+ iosClientId: '',
+ iosBundleId: '',
+ );
+}
diff --git a/lib/main_prod.dart b/lib/main_prod.dart
index a47d421..fc1700a 100644
--- a/lib/main_prod.dart
+++ b/lib/main_prod.dart
@@ -1,3 +1,5 @@
+import 'dart:developer';
+
import 'package:flutter/material.dart';
import 'package:lightmeter/application.dart';
import 'package:lightmeter/environment.dart';
@@ -5,6 +7,10 @@ import 'package:lightmeter/firebase.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
- await initializeFirebase();
+ try {
+ await initializeFirebase();
+ } catch (e) {
+ log(e.toString());
+ }
runApp(const Application(Environment.prod()));
}
From 6a9036ce5eed82cd312334ff698411bb85be6333 Mon Sep 17 00:00:00 2001
From: Vadim
Date: Tue, 1 Aug 2023 12:58:43 +0200
Subject: [PATCH 08/12] Camera is taking too long to take a picture
---
.../components/camera_container/bloc_container_camera.dart | 1 +
pubspec.yaml | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
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 cafce13..fee9899 100644
--- a/lib/screens/metering/components/camera_container/bloc_container_camera.dart
+++ b/lib/screens/metering/components/camera_container/bloc_container_camera.dart
@@ -129,6 +129,7 @@ class CameraContainerBloc extends EvSourceBlocBase([
_cameraController!.getMinZoomLevel(),
diff --git a/pubspec.yaml b/pubspec.yaml
index 1f8ed64..0015829 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -9,7 +9,7 @@ environment:
dependencies:
app_settings: 4.2.0
bloc_concurrency: 0.2.2
- camera: 0.10.5
+ camera: 0.10.5+2
clipboard: 0.1.3
dynamic_color: 1.6.5
exif: 3.1.4
From 50c2460f16494b69fc60a9c2eea632ca2273cdd8 Mon Sep 17 00:00:00 2001
From: vodemn
Date: Tue, 1 Aug 2023 11:08:55 +0000
Subject: [PATCH 09/12] Version bump
---
pubspec.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pubspec.yaml b/pubspec.yaml
index 0015829..e2c7b6d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,7 @@
name: lightmeter
description: A new Flutter project.
publish_to: "none"
-version: 0.12.2+33
+version: 0.12.3+34
environment:
sdk: ">=3.0.0 <4.0.0"
From c12cfb16976b69c19affc4f802852c936741f845 Mon Sep 17 00:00:00 2001
From: Vadim
Date: Thu, 3 Aug 2023 22:46:01 +0200
Subject: [PATCH 10/12] Lock & release focus when taking a picture
---
.../components/camera_container/bloc_container_camera.dart | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
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 fee9899..9c8658d 100644
--- a/lib/screens/metering/components/camera_container/bloc_container_camera.dart
+++ b/lib/screens/metering/components/camera_container/bloc_container_camera.dart
@@ -129,7 +129,6 @@ class CameraContainerBloc extends EvSourceBlocBase([
_cameraController!.getMinZoomLevel(),
@@ -206,7 +205,13 @@ class CameraContainerBloc extends EvSourceBlocBase _takePhoto() async {
try {
+ // https://github.com/flutter/flutter/issues/84957#issuecomment-1661155095
+ await _cameraController!.setFocusMode(FocusMode.locked);
+ await _cameraController!.setExposureMode(ExposureMode.locked);
final file = await _cameraController!.takePicture();
+ await _cameraController!.setFocusMode(FocusMode.auto);
+ await _cameraController!.setExposureMode(ExposureMode.auto);
+
final Uint8List bytes = await file.readAsBytes();
Directory(file.path).deleteSync(recursive: true);
From 6e1aaf5acf0d707d6853048d07cda87a037457c2 Mon Sep 17 00:00:00 2001
From: vodemn
Date: Thu, 3 Aug 2023 20:54:33 +0000
Subject: [PATCH 11/12] Version bump
---
pubspec.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pubspec.yaml b/pubspec.yaml
index e2c7b6d..315e72f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,7 @@
name: lightmeter
description: A new Flutter project.
publish_to: "none"
-version: 0.12.3+34
+version: 0.12.4+35
environment:
sdk: ">=3.0.0 <4.0.0"
From 8a71c8db13dbbd6b380b36091ec8d27bd15bd0d9 Mon Sep 17 00:00:00 2001
From: Vadim
Date: Fri, 4 Aug 2023 16:17:40 +0200
Subject: [PATCH 12/12] Added switch animations to `MeteringScreen`
---
lib/res/dimens.dart | 1 +
.../widget_placeholder_camera_view.dart | 5 +-
.../widget_container_camera.dart | 26 ++--
.../widget_list_exposure_pairs.dart | 112 +++++++++---------
.../widget_container_reading_value.dart | 15 ++-
5 files changed, 82 insertions(+), 77 deletions(-)
diff --git a/lib/res/dimens.dart b/lib/res/dimens.dart
index 1eefdc1..a1bb780 100644
--- a/lib/res/dimens.dart
+++ b/lib/res/dimens.dart
@@ -25,6 +25,7 @@ class Dimens {
static const Duration durationM = Duration(milliseconds: 200);
static const Duration durationML = Duration(milliseconds: 250);
static const Duration durationL = Duration(milliseconds: 300);
+ static const Duration switchDuration = Duration(milliseconds: 100);
static const double enabledOpacity = 1.0;
static const double disabledOpacity = 0.38;
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_view_placeholder/widget_placeholder_camera_view.dart
index 058da74..a2914d2 100644
--- 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_view_placeholder/widget_placeholder_camera_view.dart
@@ -10,10 +10,9 @@ class CameraViewPlaceholder extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
+ color: error != null ? null : Colors.black,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(Dimens.borderRadiusM)),
- child: Center(
- child: error != null ? const Icon(Icons.no_photography) : const CircularProgressIndicator(),
- ),
+ child: Center(child: error != null ? const Icon(Icons.no_photography) : null),
);
}
}
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 2f8ef45..7ac43eb 100644
--- a/lib/screens/metering/components/camera_container/widget_container_camera.dart
+++ b/lib/screens/metering/components/camera_container/widget_container_camera.dart
@@ -110,17 +110,17 @@ class _CameraViewBuilder extends StatelessWidget {
return AspectRatio(
aspectRatio: PlatformConfig.cameraPreviewAspectRatio,
child: BlocBuilder(
- buildWhen: (previous, current) =>
- current is CameraLoadingState ||
- current is CameraInitializedState ||
- current is CameraErrorState,
- builder: (context, state) {
- if (state is CameraInitializedState) {
- return Center(child: CameraView(controller: state.controller));
- } else {
- return CameraViewPlaceholder(error: state is CameraErrorState ? state.error : null);
- }
- },
+ 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),
+ },
+ ),
+ ),
),
);
}
@@ -161,11 +161,11 @@ class _CameraControlsBuilder extends StatelessWidget {
},
);
} else {
- child = const SizedBox.shrink();
+ child = const Column(children: [Expanded(child: SizedBox.shrink())],);
}
return AnimatedSwitcher(
- duration: Dimens.durationS,
+ duration: Dimens.switchDuration,
child: child,
);
},
diff --git a/lib/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart b/lib/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart
index f2496b2..71b293e 100644
--- a/lib/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart
+++ b/lib/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart
@@ -12,70 +12,72 @@ class ExposurePairsList extends StatelessWidget {
@override
Widget build(BuildContext context) {
- if (exposurePairs.isEmpty) {
- return const EmptyExposurePairsList();
- }
- return Stack(
- alignment: Alignment.center,
- children: [
- Positioned.fill(
- child: ListView.builder(
- key: ValueKey(exposurePairs.hashCode),
- padding: const EdgeInsets.symmetric(vertical: Dimens.paddingL),
- itemCount: exposurePairs.length,
- itemBuilder: (_, index) => Stack(
+ return AnimatedSwitcher(
+ duration: Dimens.switchDuration,
+ child: exposurePairs.isEmpty
+ ? const EmptyExposurePairsList()
+ : Stack(
alignment: Alignment.center,
children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Expanded(
- child: Align(
- alignment: Alignment.centerLeft,
- child: ExposurePairsListItem(
- exposurePairs[index].aperture,
- tickOnTheLeft: false,
+ Positioned.fill(
+ child: ListView.builder(
+ key: ValueKey(exposurePairs.hashCode),
+ padding: const EdgeInsets.symmetric(vertical: Dimens.paddingL),
+ itemCount: exposurePairs.length,
+ itemBuilder: (_, index) => Stack(
+ alignment: Alignment.center,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Expanded(
+ child: Align(
+ alignment: Alignment.centerLeft,
+ child: ExposurePairsListItem(
+ exposurePairs[index].aperture,
+ tickOnTheLeft: false,
+ ),
+ ),
+ ),
+ Expanded(
+ child: Align(
+ alignment: Alignment.centerLeft,
+ child: ExposurePairsListItem(
+ exposurePairs[index].shutterSpeed,
+ tickOnTheLeft: true,
+ ),
+ ),
+ ),
+ ],
),
- ),
- ),
- Expanded(
- child: Align(
- alignment: Alignment.centerLeft,
- child: ExposurePairsListItem(
- exposurePairs[index].shutterSpeed,
- tickOnTheLeft: true,
+ Positioned(
+ top: 0,
+ bottom: 0,
+ child: LayoutBuilder(
+ builder: (context, constraints) => Align(
+ alignment: index == 0
+ ? Alignment.bottomCenter
+ : (index == exposurePairs.length - 1
+ ? Alignment.topCenter
+ : Alignment.center),
+ child: SizedBox(
+ height: index == 0 || index == exposurePairs.length - 1
+ ? constraints.maxHeight / 2
+ : constraints.maxHeight,
+ child: ColoredBox(
+ color: Theme.of(context).colorScheme.onBackground,
+ child: const SizedBox(width: 1),
+ ),
+ ),
+ ),
+ ),
),
- ),
- ),
- ],
- ),
- Positioned(
- top: 0,
- bottom: 0,
- child: LayoutBuilder(
- builder: (context, constraints) => Align(
- alignment: index == 0
- ? Alignment.bottomCenter
- : (index == exposurePairs.length - 1
- ? Alignment.topCenter
- : Alignment.center),
- child: SizedBox(
- height: index == 0 || index == exposurePairs.length - 1
- ? constraints.maxHeight / 2
- : constraints.maxHeight,
- child: ColoredBox(
- color: Theme.of(context).colorScheme.onBackground,
- child: const SizedBox(width: 1),
- ),
- ),
+ ],
),
),
),
],
),
- ),
- ),
- ],
);
}
}
diff --git a/lib/screens/metering/components/shared/readings_container/components/reading_value_container/widget_container_reading_value.dart b/lib/screens/metering/components/shared/readings_container/components/reading_value_container/widget_container_reading_value.dart
index b40f666..3254456 100644
--- a/lib/screens/metering/components/shared/readings_container/components/reading_value_container/widget_container_reading_value.dart
+++ b/lib/screens/metering/components/shared/readings_container/components/reading_value_container/widget_container_reading_value.dart
@@ -72,12 +72,15 @@ class _ReadingValueBuilder extends StatelessWidget {
softWrap: false,
),
const SizedBox(height: Dimens.grid4),
- Text(
- reading.value,
- style: textTheme.titleMedium?.copyWith(color: textColor),
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- softWrap: false,
+ AnimatedSwitcher(
+ duration: Dimens.switchDuration,
+ child: Text(
+ reading.value,
+ style: textTheme.titleMedium?.copyWith(color: textColor),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ softWrap: false,
+ ),
)
],
);