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/49] 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/49] 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/49] 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/49] 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/49] 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/49] 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/49] 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/49] 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/49] 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/49] 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/49] 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/49] 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,
+ ),
)
],
);
From 1310b78a54d85ba031577e8473f5de66098a6565 Mon Sep 17 00:00:00 2001
From: Vadim <44135514+vodemn@users.noreply.github.com>
Date: Sat, 5 Aug 2023 21:11:23 +0200
Subject: [PATCH 13/49] ML-61 Delete artefacts after release creation (#96)
* Replaced "Build ..." flow with "Create new release"
* Renamed other flows
---
.../workflows/{cd_dev.yml => build_apk.yml} | 10 +--
.../{cd_prod.yml => create_release.yml} | 76 +++++++++++++++----
.github/workflows/{ci.yml => pr_check.yml} | 0
3 files changed, 62 insertions(+), 24 deletions(-)
rename .github/workflows/{cd_dev.yml => build_apk.yml} (91%)
rename .github/workflows/{cd_prod.yml => create_release.yml} (72%)
rename .github/workflows/{ci.yml => pr_check.yml} (100%)
diff --git a/.github/workflows/cd_dev.yml b/.github/workflows/build_apk.yml
similarity index 91%
rename from .github/workflows/cd_dev.yml
rename to .github/workflows/build_apk.yml
index e0e6a25..c972954 100644
--- a/.github/workflows/cd_dev.yml
+++ b/.github/workflows/build_apk.yml
@@ -19,17 +19,11 @@ on:
jobs:
build:
+ name: Build .apk
runs-on: macos-11
- timeout-minutes: 30
+ timeout-minutes: 15
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
diff --git a/.github/workflows/cd_prod.yml b/.github/workflows/create_release.yml
similarity index 72%
rename from .github/workflows/cd_prod.yml
rename to .github/workflows/create_release.yml
index 9e45644..6fe5d77 100644
--- a/.github/workflows/cd_prod.yml
+++ b/.github/workflows/create_release.yml
@@ -3,7 +3,9 @@
# separate terms of service, privacy policy, and support
# documentation.
-name: Build prod .aab & .apk
+name: Create new release
+
+run-name: Release v${{ inputs.version }}
on:
workflow_dispatch:
@@ -20,15 +22,9 @@ jobs:
build:
name: Build .apk & .aab
runs-on: macos-11
- timeout-minutes: 30
- 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
+ timeout-minutes: 15
+ steps:
- uses: actions/checkout@v3
with:
submodules: recursive
@@ -142,18 +138,66 @@ jobs:
with:
name: m3_lightmeter_apk
+ - name: Rename apk
+ run: mv app-prod-release.apk m3_lightmeter.apk
+
+ - uses: ncipollo/release-action@v1.12.0
+ with:
+ artifacts: "m3_lightmeter.apk"
+ skipIfReleaseExists: true
+ tag: "v${{ github.event.inputs.version }}"
+
+ - name: Delete no longer used apk artifact
+ uses: geekyeggo/delete-artifact@v2
+ with:
+ name: m3_lightmeter_apk
+
+ extract-merged-native-libs:
+ name: Extract merged native libraries
+ needs: [build]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: recursive
+
- name: Download app bundle
uses: actions/download-artifact@v3
with:
name: m3_lightmeter_bundle
- - name: Rename artifacts
+ - name: Extract & zip merged_native_libs
run: |
- mv app-prod-release.apk m3_lightmeter.apk
- mv app-prod-release.aab m3_lightmeter.aab
+ unzip app-prod-release.aab
+ (cd base/lib && zip -r "$OLDPWD/merged_native_libs.zip" .)
- - uses: ncipollo/release-action@v1.12.0
+ - name: Zip app bundle and merged_native_libs
+ run: zip m3_lightmeter_release.zip app-prod-release.aab merged_native_libs.zip
+
+ - name: Upload merged_native_libs.zip to artifacts
+ uses: actions/upload-artifact@v3
with:
- artifacts: "m3_lightmeter.apk, m3_lightmeter.aab"
- skipIfReleaseExists: true
- tag: "v${{ github.event.inputs.version }}"
+ name: m3_lightmeter_release
+ path: m3_lightmeter_release.zip
+
+ # TODO: this should be moved to `create-google-play-release` step when it is implemented
+ - name: Delete no longer used app bundle artifact
+ uses: geekyeggo/delete-artifact@v2
+ with:
+ name: m3_lightmeter_bundle
+
+ # TODO: Automate Google Play releases creation
+ create-google-play-release:
+ if: false
+ name: Create Google Play release
+ needs: [build, extract-merged-native-libs]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: recursive
+
+ - name: Download app bundle
+ uses: actions/download-artifact@v3
+ with:
+ name: m3_lightmeter_bundle
diff --git a/.github/workflows/ci.yml b/.github/workflows/pr_check.yml
similarity index 100%
rename from .github/workflows/ci.yml
rename to .github/workflows/pr_check.yml
From 886188bb9e7997bbf0595be6a1b3cbef545f278d Mon Sep 17 00:00:00 2001
From: Vadim <44135514+vodemn@users.noreply.github.com>
Date: Sun, 6 Aug 2023 16:28:20 +0200
Subject: [PATCH 14/49] ML-95 Live histogram (#97)
* Added histogram and separated camera view builder
* Added histogram to `MeteringScreenLayoutConfig`
* `ResolutionPreset.medium` -> `ResolutionPreset.low`
* Adjusted histogram paddings
---
.github/workflows/build_apk.yml | 2 +-
.github/workflows/create_release.yml | 2 +-
.vscode/launch.json | 4 +-
.vscode/tasks.json | 6 +-
README.md | 4 +-
.../models/metering_screen_layout_config.dart | 10 +-
lib/data/shared_prefs_service.dart | 1 +
lib/l10n/intl_en.arb | 1 +
lib/l10n/intl_fr.arb | 1 +
lib/l10n/intl_ru.arb | 1 +
lib/l10n/intl_zh.arb | 1 +
.../bloc_container_camera.dart | 2 +-
.../camera_view/widget_camera_view.dart | 10 +-
.../widget_placeholder_camera_view.dart | 0
.../histogram/widget_histogram.dart | 132 ++++++++++++++++++
.../camera_preview/widget_camera_preview.dart | 62 ++++++++
.../widget_container_camera.dart | 26 ++--
...ialog_metering_screen_layout_features.dart | 2 +
.../metering_screen_layout_config_test.dart | 60 +++++---
test/data/shared_prefs_service_test.dart | 7 +-
20 files changed, 280 insertions(+), 54 deletions(-)
rename lib/screens/metering/components/camera_container/components/{ => camera_preview/components}/camera_view/widget_camera_view.dart (86%)
rename lib/screens/metering/components/camera_container/components/{ => camera_preview/components}/camera_view_placeholder/widget_placeholder_camera_view.dart (100%)
create mode 100644 lib/screens/metering/components/camera_container/components/camera_preview/components/histogram/widget_histogram.dart
create mode 100644 lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart
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..a8f0a11 100644
--- a/lib/screens/metering/components/camera_container/widget_container_camera.dart
+++ b/lib/screens/metering/components/camera_container/widget_container_camera.dart
@@ -10,8 +10,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';
@@ -107,20 +106,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 +151,9 @@ class _CameraControlsBuilder extends StatelessWidget {
},
);
} else {
- child = const Column(children: [Expanded(child: SizedBox.shrink())],);
+ child = const Column(
+ children: [Expanded(child: SizedBox.shrink())],
+ );
}
return AnimatedSwitcher(
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 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);
});
From 737a9aa2c252202264118024475cbf82f16a663b Mon Sep 17 00:00:00 2001
From: Vadim <44135514+vodemn@users.noreply.github.com>
Date: Mon, 7 Aug 2023 12:56:29 +0200
Subject: [PATCH 15/49] ML-98 Metering top bar cutout doesn't pass through taps
(#99)
* replaced `OverflowBox` with `Stack`
---
.../widget_container_camera.dart | 155 +++++++++---------
1 file changed, 76 insertions(+), 79 deletions(-)
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 a8f0a11..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';
@@ -45,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;
}
}
@@ -165,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,
- ),
- ),
- ],
- ),
- ),
- );
- }
-}
From 9c11401175c344d5d69f5adfaf0e7eea4b25b558 Mon Sep 17 00:00:00 2001
From: vodemn
Date: Mon, 7 Aug 2023 14:59:47 +0000
Subject: [PATCH 16/49] Version bump
---
pubspec.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pubspec.yaml b/pubspec.yaml
index 315e72f..7a4c862 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,7 @@
name: lightmeter
description: A new Flutter project.
publish_to: "none"
-version: 0.12.4+35
+version: 0.13.0+36
environment:
sdk: ">=3.0.0 <4.0.0"
From d91441bac9ab5bed8abc073521a012b8a95e6ed7 Mon Sep 17 00:00:00 2001
From: Vadim <44135514+vodemn@users.noreply.github.com>
Date: Tue, 8 Aug 2023 11:46:43 +0200
Subject: [PATCH 17/49] ML-95 Live histogram (#100)
* Removed redundant `stopImageStream()`
---
.../camera_preview/components/histogram/widget_histogram.dart | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
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
index 6964e77..05dfa9d 100644
--- 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
@@ -28,7 +28,9 @@ class _CameraHistogramState extends State {
@override
void dispose() {
- widget.controller.stopImageStream();
+ /// There is no need to stop image stream here,
+ /// because this widget will be disposed when CameraController is disposed
+ /// widget.controller.stopImageStream();
super.dispose();
}
From b9412c7441f8bc68ef02071dc113bb1deaa470a6 Mon Sep 17 00:00:00 2001
From: Vadim <44135514+vodemn@users.noreply.github.com>
Date: Wed, 9 Aug 2023 12:24:12 +0200
Subject: [PATCH 18/49] ML-61 Create Google Play release from Github actions
(#101)
* Delete all artefacts after GP release
* Update create_release.yml
* Added release notes formatting
* Preserve release zip if GP release creation failed
* Create whatsnew folder
---
.github/workflows/create_release.yml | 118 ++++++++++++++++++++-------
1 file changed, 87 insertions(+), 31 deletions(-)
diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml
index 3851170..ca00c25 100644
--- a/.github/workflows/create_release.yml
+++ b/.github/workflows/create_release.yml
@@ -3,6 +3,11 @@
# separate terms of service, privacy policy, and support
# documentation.
+
+# This workflow uses perl regex. For better syntaxis understading see these docs:
+# https://perldoc.perl.org/perlrequick#Search-and-replace
+# https://perldoc.perl.org/perlre#Other-Modifiers
+
name: Create new release
run-name: Release v${{ inputs.version }}
@@ -14,6 +19,10 @@ on:
description: "Version"
required: true
type: string
+ release-notes:
+ description: "Release notes"
+ required: true
+ type: string
env:
BUILD_ARGS: --release --flavor prod --dart-define cameraPreviewAspectRatio=240/320 -t lib/main_prod.dart
@@ -23,7 +32,6 @@ jobs:
name: Build .apk & .aab
runs-on: macos-11
timeout-minutes: 15
-
steps:
- uses: actions/checkout@v3
with:
@@ -95,6 +103,21 @@ jobs:
name: m3_lightmeter_bundle
path: build/app/outputs/bundle/prodRelease/app-prod-release.aab
+ generate-release-notes:
+ name: Generate release notes
+ runs-on: ubuntu-latest
+ steps:
+ - name: Generate release notes
+ run: |
+ echo ${{ inputs.release-notes }} > whatsnew-en-US.md
+ perl -i -pe 's/\s{1}(-{1})/\n$1/g' whatsnew-en-US.md
+
+ - name: Upload merged_native_libs.zip to artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: whatsnew-en-US
+ path: whatsnew-en-US.md
+
update-version-in-repo:
name: Update repo version
needs: [build]
@@ -121,22 +144,20 @@ jobs:
branch: ${{ github.ref_name }}
unprotect_reviews: true
- create-release:
+ create-github-release:
name: Create Github release
- needs: [build, update-version-in-repo]
if: github.ref_name == 'main'
+ needs: [generate-release-notes, update-version-in-repo]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- - uses: actions/checkout@v3
- with:
- submodules: recursive
-
- - name: Download apk
+ - name: Download apk & release notes
uses: actions/download-artifact@v3
with:
- name: m3_lightmeter_apk
+ name: |
+ m3_lightmeter_apk
+ whatsnew-en-US
- name: Rename apk
run: mv app-prod-release.apk m3_lightmeter.apk
@@ -146,8 +167,9 @@ jobs:
artifacts: "m3_lightmeter.apk"
skipIfReleaseExists: true
tag: "v${{ github.event.inputs.version }}"
+ bodyFile: "whatsnew-en-US.md"
- - name: Delete no longer used apk artifact
+ - name: Delete apk artifact
uses: geekyeggo/delete-artifact@v2
with:
name: m3_lightmeter_apk
@@ -157,10 +179,6 @@ jobs:
needs: [build]
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- with:
- submodules: recursive
-
- name: Download app bundle
uses: actions/download-artifact@v3
with:
@@ -171,33 +189,71 @@ jobs:
unzip app-prod-release.aab
(cd base/lib && zip -r "$OLDPWD/merged_native_libs.zip" .)
+ - name: Upload merged native libs to artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: merged_native_libs
+ path: merged_native_libs.zip
+
+ create-google-play-release:
+ name: Create Google Play release
+ if: github.ref_name == 'main'
+ needs: [generate-release-notes, extract-merged-native-libs]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Download app bundle & merged native libs
+ uses: actions/download-artifact@v3
+ with:
+ name: |
+ m3_lightmeter_bundle
+ merged_native_libs
+ whatsnew-en-US
+
+ - name: Move release notes to a folder
+ run: |
+ mkdir whatsnew
+ mv whatsnew-en-US.md whatsnew
+
+ - name: Create Google Play release
+ id: create-google-play-release-step
+ uses: r0adkll/upload-google-play@v1.1.1
+ with:
+ serviceAccountJsonPlainText: ${{ secrets.GH_ACTIONS_SERVICE_ACCOUNT_JSON }}
+ packageName: com.vodemn.lightmeter
+ releaseFiles: app-prod-release.aab
+ track: production
+ status: draft
+ userFraction: 1.0
+ debugSymbols: merged_native_libs.zip
+ whatsNewDirectory: whatsnew
+
+ # https://docs.github.com/en/actions/learn-github-actions/expressions#failure-with-conditions
- name: Zip app bundle and merged_native_libs
+ if: ${{ failure() && steps.create-google-play-release-step.conclusion == 'failure' }}
run: zip m3_lightmeter_release.zip app-prod-release.aab merged_native_libs.zip
- - name: Upload merged_native_libs.zip to artifacts
+ - name: Upload release zip to artifacts
+ if: ${{ failure() && steps.create-google-play-release-step.conclusion == 'failure' }}
uses: actions/upload-artifact@v3
with:
name: m3_lightmeter_release
path: m3_lightmeter_release.zip
-
- # TODO: this should be moved to `create-google-play-release` step when it is implemented
- - name: Delete no longer used app bundle artifact
+
+ - name: Delete app bundle & merged native libs artifacts
+ if: ${{ always() }}
uses: geekyeggo/delete-artifact@v2
with:
- name: m3_lightmeter_bundle
+ name: |
+ m3_lightmeter_bundle
+ merged_native_libs
- # TODO: Automate Google Play releases creation
- create-google-play-release:
- if: false
- name: Create Google Play release
- needs: [build, extract-merged-native-libs]
+ cleanup:
+ name: Cleanup
+ if: ${{ always() }}
+ needs: [create-github-release, create-google-play-release]
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - name: Delete release notes artifact
+ uses: geekyeggo/delete-artifact@v2
with:
- submodules: recursive
-
- - name: Download app bundle
- uses: actions/download-artifact@v3
- with:
- name: m3_lightmeter_bundle
+ name: whatsnew-en-US
From d41fa6fa8494ecd016f511bfbf782e5c345d4c48 Mon Sep 17 00:00:00 2001
From: Vadim <44135514+vodemn@users.noreply.github.com>
Date: Wed, 9 Aug 2023 16:39:41 +0200
Subject: [PATCH 19/49] ML-61 Download each artifact separately (#102)
* Download each artifact separately
* typo
* Updated setup-java action
* Extract merged native libs directly in GP release job
---
.github/workflows/create_release.yml | 45 ++++++++++------------------
1 file changed, 16 insertions(+), 29 deletions(-)
diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml
index ca00c25..71248ac 100644
--- a/.github/workflows/create_release.yml
+++ b/.github/workflows/create_release.yml
@@ -37,7 +37,7 @@ jobs:
with:
submodules: recursive
- - uses: actions/setup-java@v2
+ - uses: actions/setup-java@v3
with:
distribution: "zulu"
java-version: "11"
@@ -152,16 +152,19 @@ jobs:
permissions:
contents: write
steps:
- - name: Download apk & release notes
+ - name: Download apk
uses: actions/download-artifact@v3
with:
- name: |
- m3_lightmeter_apk
- whatsnew-en-US
+ name: m3_lightmeter_apk
- name: Rename apk
run: mv app-prod-release.apk m3_lightmeter.apk
+ - name: Download release notes
+ uses: actions/download-artifact@v3
+ with:
+ name: whatsnew-en-US
+
- uses: ncipollo/release-action@v1.12.0
with:
artifacts: "m3_lightmeter.apk"
@@ -174,9 +177,10 @@ jobs:
with:
name: m3_lightmeter_apk
- extract-merged-native-libs:
- name: Extract merged native libraries
- needs: [build]
+ create-google-play-release:
+ name: Create Google Play release
+ if: github.ref_name == 'main'
+ needs: [generate-release-notes]
runs-on: ubuntu-latest
steps:
- name: Download app bundle
@@ -189,31 +193,16 @@ jobs:
unzip app-prod-release.aab
(cd base/lib && zip -r "$OLDPWD/merged_native_libs.zip" .)
- - name: Upload merged native libs to artifacts
- uses: actions/upload-artifact@v3
- with:
- name: merged_native_libs
- path: merged_native_libs.zip
-
- create-google-play-release:
- name: Create Google Play release
- if: github.ref_name == 'main'
- needs: [generate-release-notes, extract-merged-native-libs]
- runs-on: ubuntu-latest
- steps:
- - name: Download app bundle & merged native libs
+ - name: Download release notes
uses: actions/download-artifact@v3
with:
- name: |
- m3_lightmeter_bundle
- merged_native_libs
- whatsnew-en-US
+ name: whatsnew-en-US
- name: Move release notes to a folder
run: |
mkdir whatsnew
mv whatsnew-en-US.md whatsnew
-
+
- name: Create Google Play release
id: create-google-play-release-step
uses: r0adkll/upload-google-play@v1.1.1
@@ -243,9 +232,7 @@ jobs:
if: ${{ always() }}
uses: geekyeggo/delete-artifact@v2
with:
- name: |
- m3_lightmeter_bundle
- merged_native_libs
+ name: m3_lightmeter_bundle
cleanup:
name: Cleanup
From bdb0442dd104d59c996538e3d3b1fddc9c05e4e5 Mon Sep 17 00:00:00 2001
From: Vadim
Date: Wed, 9 Aug 2023 16:49:02 +0200
Subject: [PATCH 20/49] Fixed release jobs dependencies
---
.github/workflows/create_release.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml
index 71248ac..693b3fd 100644
--- a/.github/workflows/create_release.yml
+++ b/.github/workflows/create_release.yml
@@ -147,7 +147,7 @@ jobs:
create-github-release:
name: Create Github release
if: github.ref_name == 'main'
- needs: [generate-release-notes, update-version-in-repo]
+ needs: [build, generate-release-notes, update-version-in-repo]
runs-on: ubuntu-latest
permissions:
contents: write
@@ -180,7 +180,7 @@ jobs:
create-google-play-release:
name: Create Google Play release
if: github.ref_name == 'main'
- needs: [generate-release-notes]
+ needs: [build, generate-release-notes]
runs-on: ubuntu-latest
steps:
- name: Download app bundle
From dafbc682886ee8fac129fbdbb568af950f54cce9 Mon Sep 17 00:00:00 2001
From: Vadim
Date: Wed, 9 Aug 2023 17:06:47 +0200
Subject: [PATCH 21/49] Increased build job timeout
---
.github/workflows/create_release.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml
index 693b3fd..5f8191e 100644
--- a/.github/workflows/create_release.yml
+++ b/.github/workflows/create_release.yml
@@ -31,7 +31,7 @@ jobs:
build:
name: Build .apk & .aab
runs-on: macos-11
- timeout-minutes: 15
+ timeout-minutes: 30
steps:
- uses: actions/checkout@v3
with:
From 6e3588a72e9af0e48141f2b80f91285923063b2b Mon Sep 17 00:00:00 2001
From: vodemn
Date: Wed, 9 Aug 2023 15:20:00 +0000
Subject: [PATCH 22/49] Version bump
---
pubspec.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pubspec.yaml b/pubspec.yaml
index 7a4c862..7f4fcb5 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,7 @@
name: lightmeter
description: A new Flutter project.
publish_to: "none"
-version: 0.13.0+36
+version: 0.13.1+37
environment:
sdk: ">=3.0.0 <4.0.0"
From 4917ee8aefaef71bbfa6a69ab21ebeaa8b47725b Mon Sep 17 00:00:00 2001
From: Vadim <44135514+vodemn@users.noreply.github.com>
Date: Thu, 10 Aug 2023 12:42:31 +0200
Subject: [PATCH 23/49] ML-61 Try to automate GP & Github releases via Github
Actions (#103)
* Added option to create separate releases
* Removed branch condition
* Added default values to releases checkboxes
* Removed user fraction
* Remove .md extension for GP release
* More refined releases conditions
* Parse release name
* Create Google Play release name
* Checkout first
* Update create_release.yml
* Increment build number only for GH release
* Release with status `complete`
* typo
---
.github/workflows/create_release.yml | 40 ++++++++++++++++++++++++----
1 file changed, 35 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml
index 5f8191e..07f76c8 100644
--- a/.github/workflows/create_release.yml
+++ b/.github/workflows/create_release.yml
@@ -23,6 +23,14 @@ on:
description: "Release notes"
required: true
type: string
+ github-release:
+ type: boolean
+ description: Create Github release
+ default: true
+ google-play-release:
+ type: boolean
+ description: Create Google Play release
+ default: true
env:
BUILD_ARGS: --release --flavor prod --dart-define cameraPreviewAspectRatio=240/320 -t lib/main_prod.dart
@@ -30,6 +38,7 @@ env:
jobs:
build:
name: Build .apk & .aab
+ if: ${{ inputs.github-release }} || ${{ inputs.google-play-release }}
runs-on: macos-11
timeout-minutes: 30
steps:
@@ -70,7 +79,10 @@ jobs:
echo -n "$FIREBASE_OPTIONS" | base64 --decode --output $FIREBASE_OPTIONS_PATH
cp $FIREBASE_OPTIONS_PATH ./lib
+ # This step makes sense when Github release is enabled because this release increments the build number.
+ # Therefore here we have to increment it as well to build an apk with the same build number.
- name: Increment build number & replace version number
+ if: ${{ inputs.github-release }}
run: perl -i -pe 's/^(version:\s+)(\d+\.\d+\.\d+)(\+)(\d+)$/$1."${{ github.event.inputs.version }}".$3.($4+1)/e' pubspec.yaml
- name: Install Flutter
@@ -86,18 +98,22 @@ jobs:
flutter pub run intl_utils:generate
- name: Build apk
+ if: ${{ inputs.github-release }}
run: flutter build apk $BUILD_ARGS
- name: Upload apk to artifacts
+ if: ${{ inputs.github-release }}
uses: actions/upload-artifact@v3
with:
name: m3_lightmeter_apk
path: build/app/outputs/flutter-apk/app-prod-release.apk
- name: Build appbundle
+ if: ${{ inputs.google-play-release }}
run: flutter build appbundle $BUILD_ARGS
- name: Upload app bundle to artifacts
+ if: ${{ inputs.google-play-release }}
uses: actions/upload-artifact@v3
with:
name: m3_lightmeter_bundle
@@ -120,6 +136,7 @@ jobs:
update-version-in-repo:
name: Update repo version
+ if: ${{ inputs.github-release }}
needs: [build]
runs-on: ubuntu-latest
steps:
@@ -146,7 +163,7 @@ jobs:
create-github-release:
name: Create Github release
- if: github.ref_name == 'main'
+ if: ${{ inputs.github-release }}
needs: [build, generate-release-notes, update-version-in-repo]
runs-on: ubuntu-latest
permissions:
@@ -179,10 +196,14 @@ jobs:
create-google-play-release:
name: Create Google Play release
- if: github.ref_name == 'main'
+ if: ${{ inputs.google-play-release }}
needs: [build, generate-release-notes]
runs-on: ubuntu-latest
steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: recursive
+
- name: Download app bundle
uses: actions/download-artifact@v3
with:
@@ -200,8 +221,17 @@ jobs:
- name: Move release notes to a folder
run: |
+ mv whatsnew-en-US.md whatsnew-en-US
mkdir whatsnew
- mv whatsnew-en-US.md whatsnew
+ mv whatsnew-en-US whatsnew
+
+ # https://unix.stackexchange.com/questions/13466/can-grep-output-only-specified-groupings-that-match'
+ # https://stackoverflow.com/questions/74353311/github-workflow-unable-to-process-file-command-env-successfully
+ - name: Create Google Play release name
+ id: release-name
+ run: |
+ RELEASE_NAME=$(echo "$(cat pubspec.yaml)" | sed -n -r "s/^version:\s{1}(.*)[+](.*)$/700\2 (\1)/p")
+ echo "release_name=$RELEASE_NAME" >> $GITHUB_ENV
- name: Create Google Play release
id: create-google-play-release-step
@@ -210,9 +240,9 @@ jobs:
serviceAccountJsonPlainText: ${{ secrets.GH_ACTIONS_SERVICE_ACCOUNT_JSON }}
packageName: com.vodemn.lightmeter
releaseFiles: app-prod-release.aab
+ releaseName: ${{ env.release_name }}
track: production
- status: draft
- userFraction: 1.0
+ status: completed
debugSymbols: merged_native_libs.zip
whatsNewDirectory: whatsnew
From 5adcee00dd8b93af5c5f96315945259bd023dddc Mon Sep 17 00:00:00 2001
From: Vadim <44135514+vodemn@users.noreply.github.com>
Date: Mon, 14 Aug 2023 12:25:37 +0200
Subject: [PATCH 24/49] ML-105 Hide providers from the widget tree (#106)
* Added `ServiceProviders` widget
* Added `EnumProviders` widget for enum values
* Moved `ThemeProvider` functionality to `EnumProviders`
* Style
* `EnumProviders` -> `UserPreferencesProvider`
* `ServiceProviders` -> `ServiceProvider`
* Moved `MeteringScreenLayoutProvider` functionality to `UserPreferencesProvider`
* typo
* Removed `InheritedModelAspectListener`
* TODO
* Removed Inherited Generics
* Removed redundant `LightmeterProviders`
* Removed redundant methods from `ServicesProvider`
* `_inheritFrom` -> `_inheritFromEnumsModel`
* Fixed `MeteringScreenLayoutConfig` updates
* Separated `_ThemeModel`
* typo
* `_EnumsModel` -> `_UserPreferencesModel`
---
lib/application.dart | 124 +++++---
lib/providers.dart | 77 -----
lib/providers/equipment_profile_provider.dart | 69 +++-
lib/providers/ev_source_type_provider.dart | 63 ----
.../metering_screen_layout_provider.dart | 60 ----
lib/providers/services_provider.dart | 36 +++
lib/providers/stop_type_provider.dart | 42 ---
lib/providers/supported_locale_provider.dart | 53 ----
lib/providers/theme_provider.dart | 257 ---------------
lib/providers/user_preferences_provider.dart | 295 ++++++++++++++++++
lib/res/theme.dart | 97 ++++++
.../widget_bottom_controls.dart | 4 +-
.../camera_preview/widget_camera_preview.dart | 4 +-
.../provider_container_camera.dart | 5 +-
.../widget_container_camera.dart | 6 +-
.../provider_container_light_sensor.dart | 5 +-
.../widget_container_readings.dart | 19 +-
lib/screens/metering/flow_metering.dart | 64 ++--
lib/screens/metering/screen_metering.dart | 25 +-
.../listener_metering_layout_feature.dart | 52 +++
.../utils/listsner_equipment_profiles.dart | 30 ++
.../widget_list_tile_report_issue.dart | 5 +-
.../widget_list_tile_source_code.dart | 5 +-
.../widget_list_tile_write_email.dart | 5 +-
.../caffeine/provider_list_tile_caffeine.dart | 5 +-
.../haptics/provider_list_tile_haptics.dart | 5 +-
.../language/widget_list_tile_language.dart | 9 +-
.../provider_list_tile_volume_actions.dart | 5 +-
.../provider_dialog_calibration.dart | 5 +-
.../widget_dialog_calibration.dart | 5 +-
.../widget_list_tile_calibration.dart | 7 +-
.../screen_equipment_profile.dart | 19 +-
.../widget_list_tile_fractional_stops.dart | 9 +-
...ialog_metering_screen_layout_features.dart | 6 +-
.../widget_list_tile_dynamic_color.dart | 7 +-
.../widget_dialog_picker_primary_color.dart | 6 +-
.../widget_list_tile_primary_color.dart | 7 +-
.../widget_list_tile_theme_type.dart | 9 +-
.../theme/widget_settings_section_theme.dart | 4 +-
lib/screens/settings/flow_settings.dart | 33 +-
lib/screens/settings/screen_settings.dart | 7 +-
lib/utils/inherited_generics.dart | 171 ----------
test/data/shared_prefs_service_test.dart | 6 +-
43 files changed, 790 insertions(+), 937 deletions(-)
delete mode 100644 lib/providers.dart
delete mode 100644 lib/providers/ev_source_type_provider.dart
delete mode 100644 lib/providers/metering_screen_layout_provider.dart
create mode 100644 lib/providers/services_provider.dart
delete mode 100644 lib/providers/stop_type_provider.dart
delete mode 100644 lib/providers/supported_locale_provider.dart
delete mode 100644 lib/providers/theme_provider.dart
create mode 100644 lib/providers/user_preferences_provider.dart
create mode 100644 lib/res/theme.dart
create mode 100644 lib/screens/metering/utils/listener_metering_layout_feature.dart
create mode 100644 lib/screens/metering/utils/listsner_equipment_profiles.dart
delete mode 100644 lib/utils/inherited_generics.dart
diff --git a/lib/application.dart b/lib/application.dart
index 3880a08..1249ed9 100644
--- a/lib/application.dart
+++ b/lib/application.dart
@@ -1,13 +1,22 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.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/supported_locale.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/environment.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/providers.dart';
+import 'package:lightmeter/providers/equipment_profile_provider.dart';
+import 'package:lightmeter/providers/services_provider.dart';
+import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/screens/metering/flow_metering.dart';
import 'package:lightmeter/screens/settings/flow_settings.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
+import 'package:platform/platform.dart';
+import 'package:shared_preferences/shared_preferences.dart';
class Application extends StatelessWidget {
final Environment env;
@@ -16,56 +25,71 @@ class Application extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return LightmeterProviders(
- env: env,
- builder: (context, ready) => ready
- ? _AnnotatedRegionWrapper(
- child: MaterialApp(
- theme: context.listen(),
- locale: Locale(context.listen().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!,
+ return FutureBuilder(
+ future: Future.wait([
+ SharedPreferences.getInstance(),
+ const LightSensorService(LocalPlatform()).hasSensor(),
+ ]),
+ builder: (_, snapshot) {
+ if (snapshot.data != null) {
+ return ServicesProvider(
+ caffeineService: const CaffeineService(),
+ environment: env.copyWith(hasLightSensor: snapshot.data![1] as bool),
+ hapticsService: const HapticsService(),
+ lightSensorService: const LightSensorService(LocalPlatform()),
+ permissionsService: const PermissionsService(),
+ userPreferencesService: UserPreferencesService(snapshot.data![0] as SharedPreferences),
+ volumeEventsService: const VolumeEventsService(LocalPlatform()),
+ child: UserPreferencesProvider(
+ child: EquipmentProfileProvider(
+ child: Builder(
+ builder: (context) {
+ final theme = UserPreferencesProvider.themeOf(context);
+ final systemIconsBrightness =
+ ThemeData.estimateBrightnessForColor(theme.colorScheme.onSurface);
+ return AnnotatedRegion(
+ value: SystemUiOverlayStyle(
+ statusBarColor: Colors.transparent,
+ statusBarBrightness: systemIconsBrightness == Brightness.light
+ ? Brightness.dark
+ : Brightness.light,
+ statusBarIconBrightness: systemIconsBrightness,
+ systemNavigationBarColor: Colors.transparent,
+ systemNavigationBarIconBrightness: systemIconsBrightness,
+ ),
+ child: MaterialApp(
+ theme: theme,
+ locale: Locale(UserPreferencesProvider.localeOf(context).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(),
- },
),
- )
- : const SizedBox(),
- );
- }
-}
-
-class _AnnotatedRegionWrapper extends StatelessWidget {
- final Widget child;
-
- const _AnnotatedRegionWrapper({required this.child});
-
- @override
- Widget build(BuildContext context) {
- final systemIconsBrightness = ThemeData.estimateBrightnessForColor(
- context.listen().colorScheme.onSurface,
- );
- return AnnotatedRegion(
- value: SystemUiOverlayStyle(
- statusBarColor: Colors.transparent,
- statusBarBrightness:
- systemIconsBrightness == Brightness.light ? Brightness.dark : Brightness.light,
- statusBarIconBrightness: systemIconsBrightness,
- systemNavigationBarColor: Colors.transparent,
- systemNavigationBarIconBrightness: systemIconsBrightness,
- ),
- child: child,
+ ),
+ );
+ } else if (snapshot.error != null) {
+ return Center(child: Text(snapshot.error!.toString()));
+ }
+
+ // TODO(@vodemn): maybe user splashscreen instead
+ return const SizedBox();
+ },
);
}
}
diff --git a/lib/providers.dart b/lib/providers.dart
deleted file mode 100644
index d7907ab..0000000
--- a/lib/providers.dart
+++ /dev/null
@@ -1,77 +0,0 @@
-import 'package:flutter/material.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/permissions_service.dart';
-import 'package:lightmeter/data/shared_prefs_service.dart';
-import 'package:lightmeter/data/volume_events_service.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:lightmeter/providers/stop_type_provider.dart';
-import 'package:lightmeter/providers/supported_locale_provider.dart';
-import 'package:lightmeter/providers/theme_provider.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
-import 'package:platform/platform.dart';
-import 'package:shared_preferences/shared_preferences.dart';
-
-class LightmeterProviders extends StatelessWidget {
- final Environment env;
- final Widget Function(BuildContext context, bool ready) builder;
-
- const LightmeterProviders({required this.env, required this.builder, super.key});
-
- @override
- Widget build(BuildContext context) {
- return FutureBuilder(
- future: Future.wait([
- SharedPreferences.getInstance(),
- const LightSensorService(LocalPlatform()).hasSensor(),
- ]),
- builder: (_, snapshot) {
- if (snapshot.data != null) {
- return InheritedWidgetBase(
- data: env.copyWith(hasLightSensor: snapshot.data![1] as bool),
- child: InheritedWidgetBase(
- data: UserPreferencesService(snapshot.data![0] as SharedPreferences),
- child: InheritedWidgetBase(
- data: const LightSensorService(LocalPlatform()),
- child: InheritedWidgetBase(
- data: const CaffeineService(),
- child: InheritedWidgetBase(
- data: const HapticsService(),
- child: InheritedWidgetBase(
- data: const VolumeEventsService(LocalPlatform()),
- child: InheritedWidgetBase(
- data: const PermissionsService(),
- child: MeteringScreenLayoutProvider(
- child: StopTypeProvider(
- child: EquipmentProfileProvider(
- child: EvSourceTypeProvider(
- child: SupportedLocaleProvider(
- child: ThemeProvider(
- child: Builder(
- builder: (context) => builder(context, true),
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- );
- } else if (snapshot.error != null) {
- return Center(child: Text(snapshot.error!.toString()));
- }
- return builder(context, false);
- },
- );
- }
-}
diff --git a/lib/providers/equipment_profile_provider.dart b/lib/providers/equipment_profile_provider.dart
index 85c9377..c0294fa 100644
--- a/lib/providers/equipment_profile_provider.dart
+++ b/lib/providers/equipment_profile_provider.dart
@@ -1,11 +1,9 @@
import 'package:flutter/material.dart';
-import 'package:lightmeter/data/shared_prefs_service.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
+import 'package:lightmeter/providers/services_provider.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
import 'package:uuid/uuid.dart';
-typedef EquipmentProfiles = List;
-
+// TODO(@vodemn): This will be removed in #89
class EquipmentProfileProvider extends StatefulWidget {
final Widget child;
@@ -35,7 +33,8 @@ class EquipmentProfileProviderState extends State {
EquipmentProfile get _selectedProfile => _customProfiles.firstWhere(
(e) => e.id == _selectedId,
orElse: () {
- context.get().selectedEquipmentProfileId = _defaultProfile.id;
+ ServicesProvider.of(context).userPreferencesService.selectedEquipmentProfileId =
+ _defaultProfile.id;
return _defaultProfile;
},
);
@@ -43,18 +42,16 @@ class EquipmentProfileProviderState extends State {
@override
void initState() {
super.initState();
- _selectedId = context.get().selectedEquipmentProfileId;
- _customProfiles = context.get().equipmentProfiles;
+ _selectedId = ServicesProvider.of(context).userPreferencesService.selectedEquipmentProfileId;
+ _customProfiles = ServicesProvider.of(context).userPreferencesService.equipmentProfiles;
}
@override
Widget build(BuildContext context) {
- return InheritedWidgetBase>(
- data: [_defaultProfile] + _customProfiles,
- child: InheritedWidgetBase(
- data: _selectedProfile,
- child: widget.child,
- ),
+ return EquipmentProfiles(
+ profiles: [_defaultProfile] + _customProfiles,
+ selected: _selectedProfile,
+ child: widget.child,
);
}
@@ -62,7 +59,8 @@ class EquipmentProfileProviderState extends State {
setState(() {
_selectedId = data.id;
});
- context.get().selectedEquipmentProfileId = _selectedProfile.id;
+ ServicesProvider.of(context).userPreferencesService.selectedEquipmentProfileId =
+ _selectedProfile.id;
}
/// Creates a default equipment profile
@@ -94,7 +92,48 @@ class EquipmentProfileProviderState extends State {
}
void _refreshSavedProfiles() {
- context.get().equipmentProfiles = _customProfiles;
+ ServicesProvider.of(context).userPreferencesService.equipmentProfiles = _customProfiles;
setState(() {});
}
}
+
+// Copied from #89
+enum EquipmentProfilesAspect { list, selected }
+
+class EquipmentProfiles extends InheritedModel {
+ const EquipmentProfiles({
+ super.key,
+ required this.profiles,
+ required this.selected,
+ required super.child,
+ });
+
+ final List profiles;
+ final EquipmentProfile selected;
+
+ static List of(BuildContext context) {
+ return InheritedModel.inheritFrom(
+ context,
+ aspect: EquipmentProfilesAspect.list,
+ )!
+ .profiles;
+ }
+
+ static EquipmentProfile selectedOf(BuildContext context) {
+ return InheritedModel.inheritFrom(
+ context,
+ aspect: EquipmentProfilesAspect.selected,
+ )!
+ .selected;
+ }
+
+ @override
+ bool updateShouldNotify(EquipmentProfiles oldWidget) => false;
+
+ @override
+ bool updateShouldNotifyDependent(
+ EquipmentProfiles oldWidget,
+ Set dependencies,
+ ) =>
+ false;
+}
diff --git a/lib/providers/ev_source_type_provider.dart b/lib/providers/ev_source_type_provider.dart
deleted file mode 100644
index aed873b..0000000
--- a/lib/providers/ev_source_type_provider.dart
+++ /dev/null
@@ -1,63 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:lightmeter/data/models/ev_source_type.dart';
-import 'package:lightmeter/data/shared_prefs_service.dart';
-import 'package:lightmeter/environment.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
-
-class EvSourceTypeProvider extends StatefulWidget {
- final Widget child;
-
- const EvSourceTypeProvider({required this.child, super.key});
-
- static EvSourceTypeProviderState of(BuildContext context) {
- return context.findAncestorStateOfType()!;
- }
-
- @override
- State createState() => EvSourceTypeProviderState();
-}
-
-class EvSourceTypeProviderState extends State {
- late final ValueNotifier valueListenable;
-
- @override
- void initState() {
- super.initState();
- final evSourceType = context.get().evSourceType;
- valueListenable = ValueNotifier(
- evSourceType == EvSourceType.sensor && !context.get().hasLightSensor
- ? EvSourceType.camera
- : evSourceType,
- );
- }
-
- @override
- void dispose() {
- valueListenable.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return ValueListenableBuilder(
- valueListenable: valueListenable,
- builder: (_, value, child) => InheritedWidgetBase(
- data: value,
- child: child!,
- ),
- child: widget.child,
- );
- }
-
- void toggleType() {
- switch (valueListenable.value) {
- case EvSourceType.camera:
- if (context.get().hasLightSensor) {
- valueListenable.value = EvSourceType.sensor;
- }
- case EvSourceType.sensor:
- valueListenable.value = EvSourceType.camera;
- }
- context.get().evSourceType = valueListenable.value;
- }
-}
diff --git a/lib/providers/metering_screen_layout_provider.dart b/lib/providers/metering_screen_layout_provider.dart
deleted file mode 100644
index 405c1a7..0000000
--- a/lib/providers/metering_screen_layout_provider.dart
+++ /dev/null
@@ -1,60 +0,0 @@
-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:lightmeter/utils/inherited_generics.dart';
-
-class MeteringScreenLayoutProvider extends StatefulWidget {
- final Widget child;
-
- const MeteringScreenLayoutProvider({required this.child, super.key});
-
- static MeteringScreenLayoutProviderState of(BuildContext context) {
- return context.findAncestorStateOfType()!;
- }
-
- @override
- State createState() => MeteringScreenLayoutProviderState();
-}
-
-class MeteringScreenLayoutProviderState extends State {
- late final MeteringScreenLayoutConfig _config =
- context.get().meteringScreenLayout;
-
- @override
- Widget build(BuildContext context) {
- return InheritedModelBase(
- data: MeteringScreenLayoutConfig.from(_config),
- child: widget.child,
- );
- }
-
- void updateFeatures(MeteringScreenLayoutConfig config) {
- setState(() {
- config.forEach((key, value) {
- _config.update(
- key,
- (_) => value,
- ifAbsent: () => value,
- );
- });
- });
- context.get().meteringScreenLayout = _config;
- }
-}
-
-typedef _MeteringScreenLayoutModel = InheritedModelBase;
-
-extension MeteringScreenLayout on InheritedModelBase {
- static MeteringScreenLayoutConfig of(BuildContext context, {bool listen = true}) {
- if (listen) {
- return context.dependOnInheritedWidgetOfExactType<_MeteringScreenLayoutModel>()!.data;
- } else {
- return context.findAncestorWidgetOfExactType<_MeteringScreenLayoutModel>()!.data;
- }
- }
-
- static bool featureOf(BuildContext context, MeteringScreenLayoutFeature aspect) {
- return InheritedModel.inheritFrom<_MeteringScreenLayoutModel>(context, aspect: aspect)!
- .data[aspect]!;
- }
-}
diff --git a/lib/providers/services_provider.dart b/lib/providers/services_provider.dart
new file mode 100644
index 0000000..c2c548f
--- /dev/null
+++ b/lib/providers/services_provider.dart
@@ -0,0 +1,36 @@
+import 'package:flutter/material.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/permissions_service.dart';
+import 'package:lightmeter/data/shared_prefs_service.dart';
+import 'package:lightmeter/data/volume_events_service.dart';
+import 'package:lightmeter/environment.dart';
+
+class ServicesProvider extends InheritedWidget {
+ final CaffeineService caffeineService;
+ final Environment environment;
+ final HapticsService hapticsService;
+ final LightSensorService lightSensorService;
+ final PermissionsService permissionsService;
+ final UserPreferencesService userPreferencesService;
+ final VolumeEventsService volumeEventsService;
+
+ const ServicesProvider({
+ required this.caffeineService,
+ required this.environment,
+ required this.hapticsService,
+ required this.lightSensorService,
+ required this.permissionsService,
+ required this.userPreferencesService,
+ required this.volumeEventsService,
+ required super.child,
+ });
+
+ static ServicesProvider of(BuildContext context) {
+ return context.findAncestorWidgetOfExactType()!;
+ }
+
+ @override
+ bool updateShouldNotify(ServicesProvider oldWidget) => false;
+}
diff --git a/lib/providers/stop_type_provider.dart b/lib/providers/stop_type_provider.dart
deleted file mode 100644
index 3b20dd5..0000000
--- a/lib/providers/stop_type_provider.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:lightmeter/data/shared_prefs_service.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
-import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
-
-class StopTypeProvider extends StatefulWidget {
- final Widget child;
-
- const StopTypeProvider({required this.child, super.key});
-
- static StopTypeProviderState of(BuildContext context) {
- return context.findAncestorStateOfType()!;
- }
-
- @override
- State createState() => StopTypeProviderState();
-}
-
-class StopTypeProviderState extends State {
- late StopType _stopType;
-
- @override
- void initState() {
- super.initState();
- _stopType = context.get().stopType;
- }
-
- @override
- Widget build(BuildContext context) {
- return InheritedWidgetBase(
- data: _stopType,
- child: widget.child,
- );
- }
-
- void set(StopType type) {
- setState(() {
- _stopType = type;
- });
- context.get().stopType = type;
- }
-}
diff --git a/lib/providers/supported_locale_provider.dart b/lib/providers/supported_locale_provider.dart
deleted file mode 100644
index caa7ced..0000000
--- a/lib/providers/supported_locale_provider.dart
+++ /dev/null
@@ -1,53 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:lightmeter/data/models/supported_locale.dart';
-import 'package:lightmeter/data/shared_prefs_service.dart';
-import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
-
-class SupportedLocaleProvider extends StatefulWidget {
- final Widget child;
-
- const SupportedLocaleProvider({required this.child, super.key});
-
- static SupportedLocaleProviderState of(BuildContext context) {
- return context.findAncestorStateOfType()!;
- }
-
- @override
- State createState() => SupportedLocaleProviderState();
-}
-
-class SupportedLocaleProviderState extends State {
- late final ValueNotifier valueListenable;
-
- @override
- void initState() {
- super.initState();
- valueListenable = ValueNotifier(context.get().locale);
- }
-
- @override
- void dispose() {
- valueListenable.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return ValueListenableBuilder(
- valueListenable: valueListenable,
- builder: (_, value, child) => InheritedWidgetBase(
- data: value,
- child: child!,
- ),
- child: widget.child,
- );
- }
-
- void setLocale(SupportedLocale locale) {
- S.load(Locale(locale.intlName)).then((value) {
- valueListenable.value = locale;
- context.get().locale = locale;
- });
- }
-}
diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme_provider.dart
deleted file mode 100644
index 9773df1..0000000
--- a/lib/providers/theme_provider.dart
+++ /dev/null
@@ -1,257 +0,0 @@
-import 'package:dynamic_color/dynamic_color.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/scheduler.dart';
-import 'package:lightmeter/data/models/dynamic_colors_state.dart';
-import 'package:lightmeter/data/models/theme_type.dart';
-import 'package:lightmeter/data/shared_prefs_service.dart';
-import 'package:lightmeter/res/dimens.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
-import 'package:material_color_utilities/material_color_utilities.dart';
-
-class ThemeProvider extends StatefulWidget {
- final Widget child;
-
- const ThemeProvider({
- required this.child,
- super.key,
- });
-
- static ThemeProviderState of(BuildContext context) {
- return context.findAncestorStateOfType()!;
- }
-
- static const primaryColorsList = [
- Color(0xfff44336),
- Color(0xffe91e63),
- Color(0xff9c27b0),
- Color(0xff673ab7),
- Color(0xff3f51b5),
- Color(0xff2196f3),
- Color(0xff03a9f4),
- Color(0xff00bcd4),
- Color(0xff009688),
- Color(0xff4caf50),
- Color(0xff8bc34a),
- Color(0xffcddc39),
- Color(0xffffeb3b),
- Color(0xffffc107),
- Color(0xffff9800),
- Color(0xffff5722),
- ];
-
- @override
- State createState() => ThemeProviderState();
-}
-
-class ThemeProviderState extends State with WidgetsBindingObserver {
- UserPreferencesService get _prefs => context.get();
-
- late final _themeTypeNotifier = ValueNotifier(_prefs.themeType);
- late final _dynamicColorNotifier = ValueNotifier(_prefs.dynamicColor);
- late final _primaryColorNotifier = ValueNotifier(_prefs.primaryColor);
-
- @override
- void initState() {
- super.initState();
- WidgetsBinding.instance.addObserver(this);
- }
-
- @override
- void didChangePlatformBrightness() {
- super.didChangePlatformBrightness();
- setState(() {});
- }
-
- @override
- void dispose() {
- WidgetsBinding.instance.removeObserver(this);
- _themeTypeNotifier.dispose();
- _dynamicColorNotifier.dispose();
- _primaryColorNotifier.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return ValueListenableBuilder(
- valueListenable: _themeTypeNotifier,
- builder: (_, themeType, __) => InheritedWidgetBase(
- data: themeType,
- child: ValueListenableBuilder(
- valueListenable: _dynamicColorNotifier,
- builder: (_, useDynamicColor, __) => _DynamicColorProvider(
- useDynamicColor: useDynamicColor,
- themeBrightness: _themeBrightness,
- builder: (_, dynamicPrimaryColor) => ValueListenableBuilder(
- valueListenable: _primaryColorNotifier,
- builder: (_, primaryColor, __) => _ThemeDataProvider(
- primaryColor: dynamicPrimaryColor ?? primaryColor,
- brightness: _themeBrightness,
- child: widget.child,
- ),
- ),
- ),
- ),
- ),
- );
- }
-
- void setThemeType(ThemeType themeType) {
- _themeTypeNotifier.value = themeType;
- _prefs.themeType = themeType;
- }
-
- Brightness get _themeBrightness {
- switch (_themeTypeNotifier.value) {
- case ThemeType.light:
- return Brightness.light;
- case ThemeType.dark:
- return Brightness.dark;
- case ThemeType.systemDefault:
- return SchedulerBinding.instance.platformDispatcher.platformBrightness;
- }
- }
-
- void setPrimaryColor(Color color) {
- _primaryColorNotifier.value = color;
- _prefs.primaryColor = color;
- }
-
- void enableDynamicColor(bool enable) {
- _dynamicColorNotifier.value = enable;
- _prefs.dynamicColor = enable;
- }
-}
-
-class _DynamicColorProvider extends StatelessWidget {
- final bool useDynamicColor;
- final Brightness themeBrightness;
- final Widget Function(BuildContext context, Color? primaryColor) builder;
-
- const _DynamicColorProvider({
- required this.useDynamicColor,
- required this.themeBrightness,
- required this.builder,
- });
-
- @override
- Widget build(BuildContext context) {
- return DynamicColorBuilder(
- builder: (lightDynamic, darkDynamic) {
- late final DynamicColorState state;
- late final Color? dynamicPrimaryColor;
- if (lightDynamic != null && darkDynamic != null) {
- if (useDynamicColor) {
- dynamicPrimaryColor =
- (themeBrightness == Brightness.light ? lightDynamic : darkDynamic).primary;
- state = DynamicColorState.enabled;
- } else {
- dynamicPrimaryColor = null;
- state = DynamicColorState.disabled;
- }
- } else {
- dynamicPrimaryColor = null;
- state = DynamicColorState.unavailable;
- }
- return InheritedWidgetBase(
- data: state,
- child: builder(context, dynamicPrimaryColor),
- );
- },
- );
- }
-}
-
-class _ThemeDataProvider extends StatelessWidget {
- final Color primaryColor;
- final Brightness brightness;
- final Widget child;
-
- const _ThemeDataProvider({
- required this.primaryColor,
- required this.brightness,
- required this.child,
- });
-
- @override
- Widget build(BuildContext context) {
- return InheritedWidgetBase(
- data: _themeFromColorScheme(_colorSchemeFromColor()),
- child: child,
- );
- }
-
- ThemeData _themeFromColorScheme(ColorScheme scheme) {
- return ThemeData(
- useMaterial3: true,
- brightness: scheme.brightness,
- primaryColor: primaryColor,
- colorScheme: scheme,
- appBarTheme: AppBarTheme(
- elevation: 4,
- color: scheme.surface,
- surfaceTintColor: scheme.surfaceTint,
- ),
- cardTheme: CardTheme(
- clipBehavior: Clip.antiAlias,
- color: scheme.surface,
- elevation: 4,
- margin: EdgeInsets.zero,
- shadowColor: Colors.transparent,
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(Dimens.borderRadiusL)),
- surfaceTintColor: scheme.surfaceTint,
- ),
- dialogBackgroundColor: scheme.surface,
- dialogTheme: DialogTheme(
- backgroundColor: scheme.surface,
- surfaceTintColor: scheme.surfaceTint,
- elevation: 6,
- ),
- dividerColor: scheme.outlineVariant,
- dividerTheme: DividerThemeData(
- color: scheme.outlineVariant,
- space: 0,
- ),
- listTileTheme: ListTileThemeData(
- style: ListTileStyle.list,
- iconColor: scheme.onSurface,
- textColor: scheme.onSurface,
- ),
- scaffoldBackgroundColor: scheme.surface,
- );
- }
-
- ColorScheme _colorSchemeFromColor() {
- final scheme = brightness == Brightness.light
- ? Scheme.light(primaryColor.value)
- : Scheme.dark(primaryColor.value);
-
- return ColorScheme(
- brightness: brightness,
- background: Color(scheme.background),
- error: Color(scheme.error),
- errorContainer: Color(scheme.errorContainer),
- onBackground: Color(scheme.onBackground),
- onError: Color(scheme.onError),
- onErrorContainer: Color(scheme.onErrorContainer),
- primary: Color(scheme.primary),
- onPrimary: Color(scheme.onPrimary),
- primaryContainer: Color(scheme.primaryContainer),
- onPrimaryContainer: Color(scheme.onPrimaryContainer),
- secondary: Color(scheme.secondary),
- onSecondary: Color(scheme.onSecondary),
- surface: Color.alphaBlend(
- Color(scheme.primary).withOpacity(0.05),
- Color(scheme.background),
- ),
- onSurface: Color(scheme.onSurface),
- surfaceVariant: Color.alphaBlend(
- Color(scheme.primary).withOpacity(0.5),
- Color(scheme.background),
- ),
- onSurfaceVariant: Color(scheme.onSurfaceVariant),
- outline: Color(scheme.outline),
- outlineVariant: Color(scheme.outlineVariant),
- );
- }
-}
diff --git a/lib/providers/user_preferences_provider.dart b/lib/providers/user_preferences_provider.dart
new file mode 100644
index 0000000..af644d1
--- /dev/null
+++ b/lib/providers/user_preferences_provider.dart
@@ -0,0 +1,295 @@
+import 'package:dynamic_color/dynamic_color.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:lightmeter/data/models/dynamic_colors_state.dart';
+import 'package:lightmeter/data/models/ev_source_type.dart';
+import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
+import 'package:lightmeter/data/models/supported_locale.dart';
+import 'package:lightmeter/data/models/theme_type.dart';
+import 'package:lightmeter/data/shared_prefs_service.dart';
+import 'package:lightmeter/generated/l10n.dart';
+import 'package:lightmeter/providers/services_provider.dart';
+import 'package:lightmeter/res/theme.dart';
+import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
+
+class UserPreferencesProvider extends StatefulWidget {
+ final Widget child;
+
+ const UserPreferencesProvider({required this.child, super.key});
+
+ static _UserPreferencesProviderState of(BuildContext context) {
+ return context.findAncestorStateOfType<_UserPreferencesProviderState>()!;
+ }
+
+ static DynamicColorState dynamicColorStateOf(BuildContext context) {
+ return _inheritFromEnumsModel(context, _Aspect.dynamicColorState).dynamicColorState;
+ }
+
+ static EvSourceType evSourceTypeOf(BuildContext context) {
+ return _inheritFromEnumsModel(context, _Aspect.evSourceType).evSourceType;
+ }
+
+ static SupportedLocale localeOf(BuildContext context) {
+ return _inheritFromEnumsModel(context, _Aspect.locale).locale;
+ }
+
+ static MeteringScreenLayoutConfig meteringScreenConfigOf(BuildContext context) {
+ return context.findAncestorWidgetOfExactType<_MeteringScreenLayoutModel>()!.data;
+ }
+
+ static bool meteringScreenFeatureOf(BuildContext context, MeteringScreenLayoutFeature feature) {
+ return InheritedModel.inheritFrom<_MeteringScreenLayoutModel>(context, aspect: feature)!
+ .data[feature]!;
+ }
+
+ static StopType stopTypeOf(BuildContext context) {
+ return _inheritFromEnumsModel(context, _Aspect.stopType).stopType;
+ }
+
+ static ThemeData themeOf(BuildContext context) {
+ return _inheritFromEnumsModel(context, _Aspect.theme).theme;
+ }
+
+ static ThemeType themeTypeOf(BuildContext context) {
+ return _inheritFromEnumsModel(context, _Aspect.themeType).themeType;
+ }
+
+ static _UserPreferencesModel _inheritFromEnumsModel(
+ BuildContext context,
+ _Aspect aspect,
+ ) {
+ return InheritedModel.inheritFrom<_UserPreferencesModel>(context, aspect: aspect)!;
+ }
+
+ @override
+ State createState() => _UserPreferencesProviderState();
+}
+
+class _UserPreferencesProviderState extends State
+ with WidgetsBindingObserver {
+ UserPreferencesService get userPreferencesService =>
+ ServicesProvider.of(context).userPreferencesService;
+
+ late bool dynamicColor = userPreferencesService.dynamicColor;
+ late EvSourceType evSourceType;
+ late MeteringScreenLayoutConfig meteringScreenLayout =
+ userPreferencesService.meteringScreenLayout;
+ late Color primaryColor = userPreferencesService.primaryColor;
+ late StopType stopType = userPreferencesService.stopType;
+ late SupportedLocale locale = userPreferencesService.locale;
+ late ThemeType themeType = userPreferencesService.themeType;
+
+ @override
+ void initState() {
+ super.initState();
+ evSourceType = userPreferencesService.evSourceType;
+ evSourceType = evSourceType == EvSourceType.sensor &&
+ !ServicesProvider.of(context).environment.hasLightSensor
+ ? EvSourceType.camera
+ : evSourceType;
+ WidgetsBinding.instance.addObserver(this);
+ }
+
+ @override
+ void didChangePlatformBrightness() {
+ super.didChangePlatformBrightness();
+ setState(() {});
+ }
+
+ @override
+ void dispose() {
+ WidgetsBinding.instance.removeObserver(this);
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return DynamicColorBuilder(
+ builder: (lightDynamic, darkDynamic) {
+ late final DynamicColorState state;
+ late final Color? dynamicPrimaryColor;
+ if (lightDynamic != null && darkDynamic != null) {
+ if (dynamicColor) {
+ dynamicPrimaryColor =
+ (_themeBrightness == Brightness.light ? lightDynamic : darkDynamic).primary;
+ state = DynamicColorState.enabled;
+ } else {
+ dynamicPrimaryColor = null;
+ state = DynamicColorState.disabled;
+ }
+ } else {
+ dynamicPrimaryColor = null;
+ state = DynamicColorState.unavailable;
+ }
+ return _UserPreferencesModel(
+ brightness: _themeBrightness,
+ dynamicColorState: state,
+ evSourceType: evSourceType,
+ locale: locale,
+ primaryColor: dynamicPrimaryColor ?? primaryColor,
+ stopType: stopType,
+ themeType: themeType,
+ child: _MeteringScreenLayoutModel(
+ data: meteringScreenLayout,
+ child: widget.child,
+ ),
+ );
+ },
+ );
+ }
+
+ void enableDynamicColor(bool enable) {
+ setState(() {
+ dynamicColor = enable;
+ });
+ userPreferencesService.dynamicColor = enable;
+ }
+
+ void toggleEvSourceType() {
+ if (!ServicesProvider.of(context).environment.hasLightSensor) {
+ return;
+ }
+ setState(() {
+ switch (evSourceType) {
+ case EvSourceType.camera:
+ evSourceType = EvSourceType.sensor;
+ case EvSourceType.sensor:
+ evSourceType = EvSourceType.camera;
+ }
+ });
+ userPreferencesService.evSourceType = evSourceType;
+ }
+
+ void setLocale(SupportedLocale locale) {
+ S.load(Locale(locale.intlName)).then((value) {
+ setState(() {
+ this.locale = locale;
+ });
+ userPreferencesService.locale = locale;
+ });
+ }
+
+ void setMeteringScreenLayout(MeteringScreenLayoutConfig config) {
+ setState(() {
+ meteringScreenLayout = config;
+ });
+ userPreferencesService.meteringScreenLayout = meteringScreenLayout;
+ }
+
+ void setPrimaryColor(Color primaryColor) {
+ setState(() {
+ this.primaryColor = primaryColor;
+ });
+ userPreferencesService.primaryColor = primaryColor;
+ }
+
+ void setStopType(StopType stopType) {
+ setState(() {
+ this.stopType = stopType;
+ });
+ userPreferencesService.stopType = stopType;
+ }
+
+ void setThemeType(ThemeType themeType) {
+ setState(() {
+ this.themeType = themeType;
+ });
+ userPreferencesService.themeType = themeType;
+ }
+
+ Brightness get _themeBrightness {
+ switch (themeType) {
+ case ThemeType.light:
+ return Brightness.light;
+ case ThemeType.dark:
+ return Brightness.dark;
+ case ThemeType.systemDefault:
+ return SchedulerBinding.instance.platformDispatcher.platformBrightness;
+ }
+ }
+}
+
+enum _Aspect {
+ dynamicColorState,
+ evSourceType,
+ locale,
+ stopType,
+ theme,
+ themeType,
+}
+
+class _UserPreferencesModel extends InheritedModel<_Aspect> {
+ final DynamicColorState dynamicColorState;
+ final EvSourceType evSourceType;
+ final SupportedLocale locale;
+ final StopType stopType;
+ final ThemeType themeType;
+
+ final Brightness _brightness;
+ final Color _primaryColor;
+
+ const _UserPreferencesModel({
+ required Brightness brightness,
+ required this.dynamicColorState,
+ required this.evSourceType,
+ required this.locale,
+ required Color primaryColor,
+ required this.stopType,
+ required this.themeType,
+ required super.child,
+ }) : _brightness = brightness,
+ _primaryColor = primaryColor;
+
+ ThemeData get theme => themeFrom(_primaryColor, _brightness);
+
+ @override
+ bool updateShouldNotify(_UserPreferencesModel oldWidget) {
+ return _brightness != oldWidget._brightness ||
+ dynamicColorState != oldWidget.dynamicColorState ||
+ evSourceType != oldWidget.evSourceType ||
+ locale != oldWidget.locale ||
+ _primaryColor != oldWidget._primaryColor ||
+ stopType != oldWidget.stopType ||
+ themeType != oldWidget.themeType;
+ }
+
+ @override
+ bool updateShouldNotifyDependent(
+ _UserPreferencesModel oldWidget,
+ Set<_Aspect> dependencies,
+ ) {
+ return (dependencies.contains(_Aspect.dynamicColorState) &&
+ dynamicColorState != oldWidget.dynamicColorState) ||
+ (dependencies.contains(_Aspect.evSourceType) && evSourceType != oldWidget.evSourceType) ||
+ (dependencies.contains(_Aspect.locale) && locale != oldWidget.locale) ||
+ (dependencies.contains(_Aspect.stopType) && stopType != oldWidget.stopType) ||
+ (dependencies.contains(_Aspect.theme) &&
+ (_brightness != oldWidget._brightness || _primaryColor != oldWidget._primaryColor)) ||
+ (dependencies.contains(_Aspect.themeType) && themeType != oldWidget.themeType);
+ }
+}
+
+class _MeteringScreenLayoutModel extends InheritedModel {
+ final Map data;
+
+ const _MeteringScreenLayoutModel({
+ required this.data,
+ required super.child,
+ });
+
+ @override
+ bool updateShouldNotify(_MeteringScreenLayoutModel oldWidget) => oldWidget.data != data;
+
+ @override
+ bool updateShouldNotifyDependent(
+ _MeteringScreenLayoutModel oldWidget,
+ Set dependencies,
+ ) {
+ for (final dependecy in dependencies) {
+ if (oldWidget.data[dependecy] != data[dependecy]) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/lib/res/theme.dart b/lib/res/theme.dart
new file mode 100644
index 0000000..a6320c1
--- /dev/null
+++ b/lib/res/theme.dart
@@ -0,0 +1,97 @@
+import 'package:flutter/material.dart';
+import 'package:lightmeter/res/dimens.dart';
+import 'package:material_color_utilities/material_color_utilities.dart';
+
+const primaryColorsList = [
+ Color(0xfff44336),
+ Color(0xffe91e63),
+ Color(0xff9c27b0),
+ Color(0xff673ab7),
+ Color(0xff3f51b5),
+ Color(0xff2196f3),
+ Color(0xff03a9f4),
+ Color(0xff00bcd4),
+ Color(0xff009688),
+ Color(0xff4caf50),
+ Color(0xff8bc34a),
+ Color(0xffcddc39),
+ Color(0xffffeb3b),
+ Color(0xffffc107),
+ Color(0xffff9800),
+ Color(0xffff5722),
+];
+
+ThemeData themeFrom(Color primaryColor, Brightness brightness) {
+ final scheme = _colorSchemeFromColor(primaryColor, brightness);
+ return ThemeData(
+ useMaterial3: true,
+ brightness: scheme.brightness,
+ primaryColor: primaryColor,
+ colorScheme: scheme,
+ appBarTheme: AppBarTheme(
+ elevation: 4,
+ color: scheme.surface,
+ surfaceTintColor: scheme.surfaceTint,
+ ),
+ cardTheme: CardTheme(
+ clipBehavior: Clip.antiAlias,
+ color: scheme.surface,
+ elevation: 4,
+ margin: EdgeInsets.zero,
+ shadowColor: Colors.transparent,
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(Dimens.borderRadiusL)),
+ surfaceTintColor: scheme.surfaceTint,
+ ),
+ dialogBackgroundColor: scheme.surface,
+ dialogTheme: DialogTheme(
+ backgroundColor: scheme.surface,
+ surfaceTintColor: scheme.surfaceTint,
+ elevation: 6,
+ ),
+ dividerColor: scheme.outlineVariant,
+ dividerTheme: DividerThemeData(
+ color: scheme.outlineVariant,
+ space: 0,
+ ),
+ listTileTheme: ListTileThemeData(
+ style: ListTileStyle.list,
+ iconColor: scheme.onSurface,
+ textColor: scheme.onSurface,
+ ),
+ scaffoldBackgroundColor: scheme.surface,
+ );
+}
+
+ColorScheme _colorSchemeFromColor(Color primaryColor, Brightness brightness) {
+ final scheme = brightness == Brightness.light
+ ? Scheme.light(primaryColor.value)
+ : Scheme.dark(primaryColor.value);
+
+ return ColorScheme(
+ brightness: brightness,
+ background: Color(scheme.background),
+ error: Color(scheme.error),
+ errorContainer: Color(scheme.errorContainer),
+ onBackground: Color(scheme.onBackground),
+ onError: Color(scheme.onError),
+ onErrorContainer: Color(scheme.onErrorContainer),
+ primary: Color(scheme.primary),
+ onPrimary: Color(scheme.onPrimary),
+ primaryContainer: Color(scheme.primaryContainer),
+ onPrimaryContainer: Color(scheme.onPrimaryContainer),
+ secondary: Color(scheme.secondary),
+ onSecondary: Color(scheme.onSecondary),
+ surface: Color.alphaBlend(
+ Color(scheme.primary).withOpacity(0.05),
+ Color(scheme.background),
+ ),
+ onSurface: Color(scheme.onSurface),
+ surfaceVariant: Color.alphaBlend(
+ Color(scheme.primary).withOpacity(0.5),
+ Color(scheme.background),
+ ),
+ onSurfaceVariant: Color(scheme.onSurfaceVariant),
+ outline: Color(scheme.outline),
+ outlineVariant: Color(scheme.outlineVariant),
+ );
+}
diff --git a/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart b/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart
index 9989494..54ea810 100644
--- a/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart
+++ b/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/ev_source_type.dart';
+import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
class MeteringBottomControls extends StatelessWidget {
final double? ev;
@@ -42,7 +42,7 @@ class MeteringBottomControls extends StatelessWidget {
child: IconButton(
onPressed: onSwitchEvSourceType,
icon: Icon(
- context.listen() != EvSourceType.camera
+ UserPreferencesProvider.evSourceTypeOf(context) != EvSourceType.camera
? Icons.camera_rear
: Icons.wb_incandescent,
),
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
index 288c678..ecfcb42 100644
--- 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
@@ -2,7 +2,7 @@ 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/providers/user_preferences_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';
@@ -38,7 +38,7 @@ class _CameraPreviewState extends State {
alignment: Alignment.bottomCenter,
children: [
CameraView(controller: widget.controller!),
- if (MeteringScreenLayout.featureOf(
+ if (UserPreferencesProvider.meteringScreenFeatureOf(
context,
MeteringScreenLayoutFeature.histogram,
))
diff --git a/lib/screens/metering/components/camera_container/provider_container_camera.dart b/lib/screens/metering/components/camera_container/provider_container_camera.dart
index 344f573..0e27700 100644
--- a/lib/screens/metering/components/camera_container/provider_container_camera.dart
+++ b/lib/screens/metering/components/camera_container/provider_container_camera.dart
@@ -2,12 +2,11 @@ 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/interactors/metering_interactor.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart';
import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart';
import 'package:lightmeter/screens/metering/components/camera_container/widget_container_camera.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
+import 'package:lightmeter/screens/metering/flow_metering.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class CameraContainerProvider extends StatelessWidget {
@@ -39,7 +38,7 @@ class CameraContainerProvider extends StatelessWidget {
return BlocProvider(
lazy: false,
create: (context) => CameraContainerBloc(
- context.get(),
+ MeteringInteractorProvider.of(context),
context.read(),
)..add(const RequestPermissionEvent()),
child: CameraContainer(
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 e42991c..c510bc8 100644
--- a/lib/screens/metering/components/camera_container/widget_container_camera.dart
+++ b/lib/screens/metering/components/camera_container/widget_container_camera.dart
@@ -7,7 +7,7 @@ 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/providers/user_preferences_provider.dart';
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';
@@ -114,14 +114,14 @@ class CameraContainer extends StatelessWidget {
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
enabledFeaturesHeight += Dimens.paddingS;
}
- if (MeteringScreenLayout.featureOf(
+ if (UserPreferencesProvider.meteringScreenFeatureOf(
context,
MeteringScreenLayoutFeature.extremeExposurePairs,
)) {
enabledFeaturesHeight += Dimens.readingContainerDoubleValueHeight;
enabledFeaturesHeight += Dimens.paddingS;
}
- if (MeteringScreenLayout.featureOf(
+ if (UserPreferencesProvider.meteringScreenFeatureOf(
context,
MeteringScreenLayoutFeature.filmPicker,
)) {
diff --git a/lib/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart b/lib/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart
index c7423fc..aa27504 100644
--- a/lib/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart
+++ b/lib/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart
@@ -2,11 +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/interactors/metering_interactor.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
import 'package:lightmeter/screens/metering/components/light_sensor_container/bloc_container_light_sensor.dart';
import 'package:lightmeter/screens/metering/components/light_sensor_container/widget_container_light_sensor.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
+import 'package:lightmeter/screens/metering/flow_metering.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class LightSensorContainerProvider extends StatelessWidget {
@@ -38,7 +37,7 @@ class LightSensorContainerProvider extends StatelessWidget {
return BlocProvider(
lazy: false,
create: (context) => LightSensorContainerBloc(
- context.get(),
+ MeteringInteractorProvider.of(context),
context.read(),
),
child: LightSensorContainer(
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 0893305..d31380c 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
@@ -5,11 +5,10 @@ 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/providers/user_preferences_provider.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/animated_dialog_picker/widget_picker_dialog_animated.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/reading_value_container/widget_container_reading_value.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class ReadingsContainer extends StatelessWidget {
@@ -43,7 +42,7 @@ class ReadingsContainer extends StatelessWidget {
const _EquipmentProfilePicker(),
const _InnerPadding(),
],
- if (MeteringScreenLayout.featureOf(
+ if (UserPreferencesProvider.meteringScreenFeatureOf(
context,
MeteringScreenLayoutFeature.extremeExposurePairs,
)) ...[
@@ -61,7 +60,7 @@ class ReadingsContainer extends StatelessWidget {
),
const _InnerPadding(),
],
- if (MeteringScreenLayout.featureOf(
+ if (UserPreferencesProvider.meteringScreenFeatureOf(
context,
MeteringScreenLayoutFeature.filmPicker,
)) ...[
@@ -77,7 +76,7 @@ class ReadingsContainer extends StatelessWidget {
Expanded(
child: _IsoValuePicker(
selectedValue: iso,
- values: context.listen().isoValues,
+ values: EquipmentProfiles.selectedOf(context).isoValues,
onChanged: onIsoChanged,
),
),
@@ -85,7 +84,7 @@ class ReadingsContainer extends StatelessWidget {
Expanded(
child: _NdValuePicker(
selectedValue: nd,
- values: context.listen().ndValues,
+ values: EquipmentProfiles.selectedOf(context).ndValues,
onChanged: onNdChanged,
),
),
@@ -108,16 +107,16 @@ class _EquipmentProfilePicker extends StatelessWidget {
return AnimatedDialogPicker(
icon: Icons.camera,
title: S.of(context).equipmentProfile,
- selectedValue: context.listen(),
- values: context.listen(),
+ selectedValue: EquipmentProfiles.selectedOf(context),
+ values: EquipmentProfiles.of(context),
itemTitleBuilder: (_, value) => Text(value.id.isEmpty ? S.of(context).none : value.name),
onChanged: EquipmentProfileProvider.of(context).setProfile,
closedChild: ReadingValueContainer.singleValue(
value: ReadingValue(
label: S.of(context).equipmentProfile,
- value: context.listen().id.isEmpty
+ value: EquipmentProfiles.selectedOf(context).id.isEmpty
? S.of(context).none
- : context.listen().name,
+ : EquipmentProfiles.selectedOf(context).name,
),
),
);
diff --git a/lib/screens/metering/flow_metering.dart b/lib/screens/metering/flow_metering.dart
index 1caef02..cca5675 100644
--- a/lib/screens/metering/flow_metering.dart
+++ b/lib/screens/metering/flow_metering.dart
@@ -1,17 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/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:lightmeter/providers/services_provider.dart';
import 'package:lightmeter/screens/metering/bloc_metering.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
import 'package:lightmeter/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart';
import 'package:lightmeter/screens/metering/screen_metering.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
class MeteringFlow extends StatefulWidget {
const MeteringFlow({super.key});
@@ -23,31 +17,45 @@ class MeteringFlow extends StatefulWidget {
class _MeteringFlowState extends State {
@override
Widget build(BuildContext context) {
- return InheritedWidgetBase(
+ return MeteringInteractorProvider(
data: MeteringInteractor(
- context.get(),
- context.get(),
- context.get(),
- context.get(),
- context.get(),
- context.get(),
+ ServicesProvider.of(context).userPreferencesService,
+ ServicesProvider.of(context).caffeineService,
+ ServicesProvider.of(context).hapticsService,
+ ServicesProvider.of(context).permissionsService,
+ ServicesProvider.of(context).lightSensorService,
+ ServicesProvider.of(context).volumeEventsService,
)..initialize(),
- child: InheritedWidgetBase(
- data: VolumeKeysNotifier(context.get()),
- child: MultiBlocProvider(
- providers: [
- BlocProvider(create: (_) => MeteringCommunicationBloc()),
- BlocProvider(
- create: (context) => MeteringBloc(
- context.get(),
- context.get(),
- context.read(),
- ),
+ child: MultiBlocProvider(
+ providers: [
+ BlocProvider(create: (_) => MeteringCommunicationBloc()),
+ BlocProvider(
+ create: (context) => MeteringBloc(
+ MeteringInteractorProvider.of(context),
+ VolumeKeysNotifier(ServicesProvider.of(context).volumeEventsService),
+ context.read(),
),
- ],
- child: const MeteringScreen(),
- ),
+ ),
+ ],
+ child: const MeteringScreen(),
),
);
}
}
+
+class MeteringInteractorProvider extends InheritedWidget {
+ final MeteringInteractor data;
+
+ const MeteringInteractorProvider({
+ required this.data,
+ required super.child,
+ super.key,
+ });
+
+ static MeteringInteractor of(BuildContext context) {
+ return context.findAncestorWidgetOfExactType()!.data;
+ }
+
+ @override
+ bool updateShouldNotify(MeteringInteractorProvider oldWidget) => false;
+}
diff --git a/lib/screens/metering/screen_metering.dart b/lib/screens/metering/screen_metering.dart
index bbfebfc..ba4dbea 100644
--- a/lib/screens/metering/screen_metering.dart
+++ b/lib/screens/metering/screen_metering.dart
@@ -6,15 +6,17 @@ 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/ev_source_type_provider.dart';
+import 'package:lightmeter/providers/equipment_profile_provider.dart';
+import 'package:lightmeter/providers/services_provider.dart';
+import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/screens/metering/bloc_metering.dart';
import 'package:lightmeter/screens/metering/components/bottom_controls/provider_bottom_controls.dart';
import 'package:lightmeter/screens/metering/components/camera_container/provider_container_camera.dart';
import 'package:lightmeter/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart';
import 'package:lightmeter/screens/metering/event_metering.dart';
import 'package:lightmeter/screens/metering/state_metering.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
+import 'package:lightmeter/screens/metering/utils/listener_metering_layout_feature.dart';
+import 'package:lightmeter/screens/metering/utils/listsner_equipment_profiles.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class MeteringScreen extends StatelessWidget {
@@ -45,8 +47,8 @@ class MeteringScreen extends StatelessWidget {
builder: (context, state) => MeteringBottomControlsProvider(
ev: state is MeteringDataState ? state.ev : null,
isMetering: state.isMetering,
- onSwitchEvSourceType: context.get().hasLightSensor
- ? EvSourceTypeProvider.of(context).toggleType
+ onSwitchEvSourceType: ServicesProvider.of(context).environment.hasLightSensor
+ ? UserPreferencesProvider.of(context).toggleEvSourceType
: null,
onMeasure: () => context.read().add(const MeasureEvent()),
onSettings: () {
@@ -71,12 +73,12 @@ class _InheritedListeners extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return InheritedWidgetListener(
+ return EquipmentProfileListener(
onDidChangeDependencies: (value) {
context.read().add(EquipmentProfileChangedEvent(value));
},
- child: InheritedModelAspectListener(
- aspect: MeteringScreenLayoutFeature.filmPicker,
+ child: MeteringScreenLayoutFeatureListener(
+ feature: MeteringScreenLayoutFeature.filmPicker,
onDidChangeDependencies: (value) {
if (!value) context.read().add(const FilmChangedEvent(Film.other()));
},
@@ -110,7 +112,8 @@ class _MeteringContainerBuidler extends StatelessWidget {
final exposurePairs = ev != null ? buildExposureValues(context, ev!, film) : [];
final fastest = exposurePairs.isNotEmpty ? exposurePairs.first : null;
final slowest = exposurePairs.isNotEmpty ? exposurePairs.last : null;
- return context.listen() == EvSourceType.camera
+ // Doubled build here when switching evSourceType. As new source bloc fires a new state on init
+ return UserPreferencesProvider.evSourceTypeOf(context) == EvSourceType.camera
? CameraContainerProvider(
fastest: fastest,
slowest: slowest,
@@ -141,10 +144,10 @@ class _MeteringContainerBuidler extends StatelessWidget {
}
/// Depending on the `stopType` the exposure pairs list length is multiplied by 1,2 or 3
- final StopType stopType = context.listen();
+ final StopType stopType = UserPreferencesProvider.stopTypeOf(context);
final int evSteps = (ev * (stopType.index + 1)).round();
- final EquipmentProfile equipmentProfile = context.listen();
+ final EquipmentProfile equipmentProfile = EquipmentProfiles.selectedOf(context);
final List apertureValues =
equipmentProfile.apertureValues.whereStopType(stopType);
final List shutterSpeedValues =
diff --git a/lib/screens/metering/utils/listener_metering_layout_feature.dart b/lib/screens/metering/utils/listener_metering_layout_feature.dart
new file mode 100644
index 0000000..c245ec3
--- /dev/null
+++ b/lib/screens/metering/utils/listener_metering_layout_feature.dart
@@ -0,0 +1,52 @@
+import 'package:flutter/material.dart';
+import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
+import 'package:lightmeter/providers/user_preferences_provider.dart';
+
+/// Listening to multiple dependencies at the same time causes firing an event for all dependencies
+/// even though some of them didn't change:
+/// ```dart
+/// @override
+/// void didChangeDependencies() {
+/// super.didChangeDependencies();
+/// _bloc.add(EquipmentProfileChangedEvent(EquipmentProfile.of(context)));
+/// if (!MeteringScreenLayout.featureStatusOf(context, MeteringScreenLayoutFeature.filmPicker)) {
+/// _bloc.add(const FilmChangedEvent(Film.other()));
+/// }
+/// }
+/// ```
+/// To overcome this issue I've decided to create a generic listener,
+/// that will listen to each dependency separately.
+class MeteringScreenLayoutFeatureListener extends StatefulWidget {
+ final MeteringScreenLayoutFeature feature;
+ final ValueChanged onDidChangeDependencies;
+ final Widget child;
+
+ const MeteringScreenLayoutFeatureListener({
+ required this.feature,
+ required this.onDidChangeDependencies,
+ required this.child,
+ super.key,
+ });
+
+ @override
+ State createState() =>
+ _MeteringScreenLayoutFeatureListenerState();
+}
+
+class _MeteringScreenLayoutFeatureListenerState extends State {
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ widget.onDidChangeDependencies(
+ UserPreferencesProvider.meteringScreenFeatureOf(
+ context,
+ widget.feature,
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return widget.child;
+ }
+}
diff --git a/lib/screens/metering/utils/listsner_equipment_profiles.dart b/lib/screens/metering/utils/listsner_equipment_profiles.dart
new file mode 100644
index 0000000..ec604ce
--- /dev/null
+++ b/lib/screens/metering/utils/listsner_equipment_profiles.dart
@@ -0,0 +1,30 @@
+import 'package:flutter/material.dart';
+import 'package:lightmeter/providers/equipment_profile_provider.dart';
+import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
+
+class EquipmentProfileListener extends StatefulWidget {
+ final ValueChanged onDidChangeDependencies;
+ final Widget child;
+
+ const EquipmentProfileListener({
+ required this.onDidChangeDependencies,
+ required this.child,
+ super.key,
+ });
+
+ @override
+ State createState() => _EquipmentProfileListenerState();
+}
+
+class _EquipmentProfileListenerState extends State {
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ widget.onDidChangeDependencies(EquipmentProfiles.selectedOf(context));
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return widget.child;
+ }
+}
diff --git a/lib/screens/settings/components/about/components/report_issue/widget_list_tile_report_issue.dart b/lib/screens/settings/components/about/components/report_issue/widget_list_tile_report_issue.dart
index 4c477f8..72bc1b5 100644
--- a/lib/screens/settings/components/about/components/report_issue/widget_list_tile_report_issue.dart
+++ b/lib/screens/settings/components/about/components/report_issue/widget_list_tile_report_issue.dart
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
-import 'package:lightmeter/environment.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
+import 'package:lightmeter/providers/services_provider.dart';
import 'package:url_launcher/url_launcher.dart';
class ReportIssueListTile extends StatelessWidget {
@@ -14,7 +13,7 @@ class ReportIssueListTile extends StatelessWidget {
title: Text(S.of(context).reportIssue),
onTap: () {
launchUrl(
- Uri.parse(context.get().issuesReportUrl),
+ Uri.parse(ServicesProvider.of(context).environment.issuesReportUrl),
mode: LaunchMode.externalApplication,
);
},
diff --git a/lib/screens/settings/components/about/components/source_code/widget_list_tile_source_code.dart b/lib/screens/settings/components/about/components/source_code/widget_list_tile_source_code.dart
index 42a73e8..4327332 100644
--- a/lib/screens/settings/components/about/components/source_code/widget_list_tile_source_code.dart
+++ b/lib/screens/settings/components/about/components/source_code/widget_list_tile_source_code.dart
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
-import 'package:lightmeter/environment.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
+import 'package:lightmeter/providers/services_provider.dart';
import 'package:url_launcher/url_launcher.dart';
class SourceCodeListTile extends StatelessWidget {
@@ -14,7 +13,7 @@ class SourceCodeListTile extends StatelessWidget {
title: Text(S.of(context).sourceCode),
onTap: () {
launchUrl(
- Uri.parse(context.get().sourceCodeUrl),
+ Uri.parse(ServicesProvider.of(context).environment.sourceCodeUrl),
mode: LaunchMode.externalApplication,
);
},
diff --git a/lib/screens/settings/components/about/components/write_email/widget_list_tile_write_email.dart b/lib/screens/settings/components/about/components/write_email/widget_list_tile_write_email.dart
index b12d0d4..b0a4391 100644
--- a/lib/screens/settings/components/about/components/write_email/widget_list_tile_write_email.dart
+++ b/lib/screens/settings/components/about/components/write_email/widget_list_tile_write_email.dart
@@ -1,8 +1,7 @@
import 'package:clipboard/clipboard.dart';
import 'package:flutter/material.dart';
-import 'package:lightmeter/environment.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
+import 'package:lightmeter/providers/services_provider.dart';
import 'package:url_launcher/url_launcher.dart';
class WriteEmailListTile extends StatelessWidget {
@@ -14,7 +13,7 @@ class WriteEmailListTile extends StatelessWidget {
leading: const Icon(Icons.email),
title: Text(S.of(context).writeEmail),
onTap: () {
- final email = context.get().contactEmail;
+ final email = ServicesProvider.of(context).environment.contactEmail;
final mailToUrl = Uri.parse('mailto:$email?subject=M3 Lightmeter');
canLaunchUrl(mailToUrl).then((canLaunch) {
if (canLaunch) {
diff --git a/lib/screens/settings/components/general/components/caffeine/provider_list_tile_caffeine.dart b/lib/screens/settings/components/general/components/caffeine/provider_list_tile_caffeine.dart
index 4509d20..8d9bcfb 100644
--- a/lib/screens/settings/components/general/components/caffeine/provider_list_tile_caffeine.dart
+++ b/lib/screens/settings/components/general/components/caffeine/provider_list_tile_caffeine.dart
@@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:lightmeter/interactors/settings_interactor.dart';
import 'package:lightmeter/screens/settings/components/general/components/caffeine/bloc_list_tile_caffeine.dart';
import 'package:lightmeter/screens/settings/components/general/components/caffeine/widget_list_tile_caffeine.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
+import 'package:lightmeter/screens/settings/flow_settings.dart';
class CaffeineListTileProvider extends StatelessWidget {
const CaffeineListTileProvider({super.key});
@@ -12,7 +11,7 @@ class CaffeineListTileProvider extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
- create: (context) => CaffeineListTileBloc(context.get()),
+ create: (context) => CaffeineListTileBloc(SettingsInteractorProvider.of(context)),
child: const CaffeineListTile(),
);
}
diff --git a/lib/screens/settings/components/general/components/haptics/provider_list_tile_haptics.dart b/lib/screens/settings/components/general/components/haptics/provider_list_tile_haptics.dart
index beb0462..34cdeab 100644
--- a/lib/screens/settings/components/general/components/haptics/provider_list_tile_haptics.dart
+++ b/lib/screens/settings/components/general/components/haptics/provider_list_tile_haptics.dart
@@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:lightmeter/interactors/settings_interactor.dart';
import 'package:lightmeter/screens/settings/components/general/components/haptics/bloc_list_tile_haptics.dart';
import 'package:lightmeter/screens/settings/components/general/components/haptics/widget_list_tile_haptics.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
+import 'package:lightmeter/screens/settings/flow_settings.dart';
class HapticsListTileProvider extends StatelessWidget {
const HapticsListTileProvider({super.key});
@@ -12,7 +11,7 @@ class HapticsListTileProvider extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
- create: (context) => HapticsListTileBloc(context.get()),
+ create: (context) => HapticsListTileBloc(SettingsInteractorProvider.of(context)),
child: const HapticsListTile(),
);
}
diff --git a/lib/screens/settings/components/general/components/language/widget_list_tile_language.dart b/lib/screens/settings/components/general/components/language/widget_list_tile_language.dart
index 381f286..663c634 100644
--- a/lib/screens/settings/components/general/components/language/widget_list_tile_language.dart
+++ b/lib/screens/settings/components/general/components/language/widget_list_tile_language.dart
@@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/supported_locale.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/providers/supported_locale_provider.dart';
+import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
class LanguageListTile extends StatelessWidget {
const LanguageListTile({super.key});
@@ -13,20 +12,20 @@ class LanguageListTile extends StatelessWidget {
return ListTile(
leading: const Icon(Icons.language),
title: Text(S.of(context).language),
- trailing: Text(context.listen().localizedName),
+ trailing: Text(UserPreferencesProvider.localeOf(context).localizedName),
onTap: () {
showDialog(
context: context,
builder: (_) => DialogPicker(
icon: Icons.language,
title: S.of(context).chooseLanguage,
- selectedValue: context.get(),
+ selectedValue: UserPreferencesProvider.localeOf(context),
values: SupportedLocale.values,
titleAdapter: (context, value) => value.localizedName,
),
).then((value) {
if (value != null) {
- SupportedLocaleProvider.of(context).setLocale(value);
+ UserPreferencesProvider.of(context).setLocale(value);
}
});
},
diff --git a/lib/screens/settings/components/general/components/volume_actions/provider_list_tile_volume_actions.dart b/lib/screens/settings/components/general/components/volume_actions/provider_list_tile_volume_actions.dart
index 790ad4f..f99505f 100644
--- a/lib/screens/settings/components/general/components/volume_actions/provider_list_tile_volume_actions.dart
+++ b/lib/screens/settings/components/general/components/volume_actions/provider_list_tile_volume_actions.dart
@@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:lightmeter/interactors/settings_interactor.dart';
import 'package:lightmeter/screens/settings/components/general/components/volume_actions/bloc_list_tile_volume_actions.dart';
import 'package:lightmeter/screens/settings/components/general/components/volume_actions/widget_list_tile_volume_actions.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
+import 'package:lightmeter/screens/settings/flow_settings.dart';
class VolumeActionsListTileProvider extends StatelessWidget {
const VolumeActionsListTileProvider({super.key});
@@ -12,7 +11,7 @@ class VolumeActionsListTileProvider extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
- create: (context) => VolumeActionsListTileBloc(context.get()),
+ create: (context) => VolumeActionsListTileBloc(SettingsInteractorProvider.of(context)),
child: const VolumeActionsListTile(),
);
}
diff --git a/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/provider_dialog_calibration.dart b/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/provider_dialog_calibration.dart
index a95ec1b..a26a6cf 100644
--- a/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/provider_dialog_calibration.dart
+++ b/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/provider_dialog_calibration.dart
@@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:lightmeter/interactors/settings_interactor.dart';
import 'package:lightmeter/screens/settings/components/metering/components/calibration/components/calibration_dialog/bloc_dialog_calibration.dart';
import 'package:lightmeter/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
+import 'package:lightmeter/screens/settings/flow_settings.dart';
class CalibrationDialogProvider extends StatelessWidget {
const CalibrationDialogProvider({super.key});
@@ -12,7 +11,7 @@ class CalibrationDialogProvider extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
- create: (context) => CalibrationDialogBloc(context.get()),
+ create: (context) => CalibrationDialogBloc(SettingsInteractorProvider.of(context)),
child: const CalibrationDialog(),
);
}
diff --git a/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart b/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart
index 4c91c87..69a4f00 100644
--- a/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart
+++ b/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart
@@ -1,13 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:lightmeter/environment.dart';
import 'package:lightmeter/generated/l10n.dart';
+import 'package:lightmeter/providers/services_provider.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/settings/components/metering/components/calibration/components/calibration_dialog/bloc_dialog_calibration.dart';
import 'package:lightmeter/screens/settings/components/metering/components/calibration/components/calibration_dialog/event_dialog_calibration.dart';
import 'package:lightmeter/screens/settings/components/metering/components/calibration/components/calibration_dialog/state_dialog_calibration.dart';
import 'package:lightmeter/screens/shared/centered_slider/widget_slider_centered.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
import 'package:lightmeter/utils/to_string_signed.dart';
class CalibrationDialog extends StatelessWidget {
@@ -15,7 +14,7 @@ class CalibrationDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final bool hasLightSensor = context.get().hasLightSensor;
+ final bool hasLightSensor = ServicesProvider.of(context).environment.hasLightSensor;
return AlertDialog(
icon: const Icon(Icons.settings_brightness),
titlePadding: Dimens.dialogIconTitlePadding,
diff --git a/lib/screens/settings/components/metering/components/calibration/widget_list_tile_calibration.dart b/lib/screens/settings/components/metering/components/calibration/widget_list_tile_calibration.dart
index e591e4b..61690c4 100644
--- a/lib/screens/settings/components/metering/components/calibration/widget_list_tile_calibration.dart
+++ b/lib/screens/settings/components/metering/components/calibration/widget_list_tile_calibration.dart
@@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/interactors/settings_interactor.dart';
import 'package:lightmeter/screens/settings/components/metering/components/calibration/components/calibration_dialog/provider_dialog_calibration.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
+import 'package:lightmeter/screens/settings/flow_settings.dart';
class CalibrationListTile extends StatelessWidget {
const CalibrationListTile({super.key});
@@ -15,8 +14,8 @@ class CalibrationListTile extends StatelessWidget {
onTap: () {
showDialog(
context: context,
- builder: (_) => InheritedWidgetBase(
- data: context.get(),
+ builder: (_) => SettingsInteractorProvider(
+ data: SettingsInteractorProvider.of(context),
child: const CalibrationDialogProvider(),
),
);
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 4872d79..3120b38 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
@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/providers/equipment_profile_provider.dart';
+
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.dart';
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart';
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class EquipmentProfilesScreen extends StatefulWidget {
@@ -19,13 +19,12 @@ class _EquipmentProfilesScreenState extends State {
static const maxProfiles = 5 + 1; // replace with a constant from iap
late List> profileContainersKeys = [];
- int get profilesCount => context.listen().length;
+ int get profilesCount => EquipmentProfiles.of(context).length;
@override
- void initState() {
- super.initState();
- profileContainersKeys = context
- .get()
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ profileContainersKeys = EquipmentProfiles.of(context)
.map((e) => GlobalKey(debugLabel: e.id))
.toList();
}
@@ -58,14 +57,14 @@ class _EquipmentProfilesScreenState extends State {
),
child: EquipmentProfileContainer(
key: profileContainersKeys[index],
- data: context.listen()[index],
+ data: EquipmentProfiles.of(context)[index],
onExpand: () => _keepExpandedAt(index),
onUpdate: (profileData) => _updateProfileAt(profileData, index),
onDelete: () => _removeProfileAt(index),
),
)
: const SizedBox.shrink(),
- childCount: profileContainersKeys.length,
+ childCount: profilesCount,
),
),
],
@@ -79,7 +78,6 @@ class _EquipmentProfilesScreenState extends State {
).then((value) {
if (value != null) {
EquipmentProfileProvider.of(context).addProfile(value);
- profileContainersKeys.add(GlobalKey());
}
});
}
@@ -89,8 +87,7 @@ class _EquipmentProfilesScreenState extends State {
}
void _removeProfileAt(int index) {
- EquipmentProfileProvider.of(context).deleteProfile(context.listen()[index]);
- profileContainersKeys.removeAt(index);
+ EquipmentProfileProvider.of(context).deleteProfile(EquipmentProfiles.of(context)[index]);
}
void _keepExpandedAt(int index) {
diff --git a/lib/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart b/lib/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart
index 2a27a56..1bcf6bc 100644
--- a/lib/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart
+++ b/lib/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart
@@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/providers/stop_type_provider.dart';
+import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class StopTypeListTile extends StatelessWidget {
@@ -13,20 +12,20 @@ class StopTypeListTile extends StatelessWidget {
return ListTile(
leading: const Icon(Icons.straighten),
title: Text(S.of(context).fractionalStops),
- trailing: Text(_typeToString(context, context.listen())),
+ trailing: Text(_typeToString(context, UserPreferencesProvider.stopTypeOf(context))),
onTap: () {
showDialog(
context: context,
builder: (_) => DialogPicker(
icon: Icons.straighten,
title: S.of(context).showFractionalStops,
- selectedValue: context.get(),
+ selectedValue: UserPreferencesProvider.stopTypeOf(context),
values: StopType.values,
titleAdapter: _typeToString,
),
).then((value) {
if (value != null) {
- StopTypeProvider.of(context).set(value);
+ UserPreferencesProvider.of(context).setStopType(value);
}
});
},
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 d43f011..c60abab 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
@@ -1,7 +1,7 @@
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/providers/user_preferences_provider.dart';
import 'package:lightmeter/res/dimens.dart';
class MeteringScreenLayoutFeaturesDialog extends StatefulWidget {
@@ -14,7 +14,7 @@ class MeteringScreenLayoutFeaturesDialog extends StatefulWidget {
class _MeteringScreenLayoutFeaturesDialogState extends State {
late final _features =
- MeteringScreenLayoutConfig.from(MeteringScreenLayout.of(context, listen: false));
+ MeteringScreenLayoutConfig.from(UserPreferencesProvider.meteringScreenConfigOf(context));
@override
Widget build(BuildContext context) {
@@ -56,7 +56,7 @@ class _MeteringScreenLayoutFeaturesDialogState extends State() == DynamicColorState.enabled,
- onChanged: ThemeProvider.of(context).enableDynamicColor,
+ value: UserPreferencesProvider.dynamicColorStateOf(context) == DynamicColorState.enabled,
+ onChanged: UserPreferencesProvider.of(context).enableDynamicColor,
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
);
}
diff --git a/lib/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart b/lib/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart
index 434336a..380faa1 100644
--- a/lib/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart
+++ b/lib/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/providers/theme_provider.dart';
import 'package:lightmeter/res/dimens.dart';
+import 'package:lightmeter/res/theme.dart';
import 'package:lightmeter/screens/shared/filled_circle/widget_circle_filled.dart';
class PrimaryColorDialogPicker extends StatefulWidget {
@@ -38,9 +38,9 @@ class _PrimaryColorDialogPickerState extends State {
padding: EdgeInsets.zero,
child: Row(
children: List.generate(
- ThemeProvider.primaryColorsList.length,
+ primaryColorsList.length,
(index) {
- final color = ThemeProvider.primaryColorsList[index];
+ final color = primaryColorsList[index];
return Padding(
padding: EdgeInsets.only(left: index == 0 ? 0 : Dimens.paddingS),
child: _SelectableColorItem(
diff --git a/lib/screens/settings/components/theme/components/primary_color/widget_list_tile_primary_color.dart b/lib/screens/settings/components/theme/components/primary_color/widget_list_tile_primary_color.dart
index afc1a7b..44de22e 100644
--- a/lib/screens/settings/components/theme/components/primary_color/widget_list_tile_primary_color.dart
+++ b/lib/screens/settings/components/theme/components/primary_color/widget_list_tile_primary_color.dart
@@ -1,17 +1,16 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/dynamic_colors_state.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/providers/theme_provider.dart';
+import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
class PrimaryColorListTile extends StatelessWidget {
const PrimaryColorListTile({super.key});
@override
Widget build(BuildContext context) {
- if (context.listen() == DynamicColorState.enabled) {
+ if (UserPreferencesProvider.dynamicColorStateOf(context) == DynamicColorState.enabled) {
return Opacity(
opacity: Dimens.disabledOpacity,
child: IgnorePointer(
@@ -31,7 +30,7 @@ class PrimaryColorListTile extends StatelessWidget {
builder: (_) => const PrimaryColorDialogPicker(),
).then((value) {
if (value != null) {
- ThemeProvider.of(context).setPrimaryColor(value);
+ UserPreferencesProvider.of(context).setPrimaryColor(value);
}
});
},
diff --git a/lib/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart b/lib/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart
index 46e1b86..8a95161 100644
--- a/lib/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart
+++ b/lib/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart
@@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/theme_type.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/providers/theme_provider.dart';
+import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
class ThemeTypeListTile extends StatelessWidget {
const ThemeTypeListTile({super.key});
@@ -13,20 +12,20 @@ class ThemeTypeListTile extends StatelessWidget {
return ListTile(
leading: const Icon(Icons.brightness_6),
title: Text(S.of(context).theme),
- trailing: Text(_typeToString(context, context.listen())),
+ trailing: Text(_typeToString(context, UserPreferencesProvider.themeTypeOf(context))),
onTap: () {
showDialog(
context: context,
builder: (_) => DialogPicker(
icon: Icons.brightness_6,
title: S.of(context).chooseTheme,
- selectedValue: context.get(),
+ selectedValue: UserPreferencesProvider.themeTypeOf(context),
values: ThemeType.values,
titleAdapter: _typeToString,
),
).then((value) {
if (value != null) {
- ThemeProvider.of(context).setThemeType(value);
+ UserPreferencesProvider.of(context).setThemeType(value);
}
});
},
diff --git a/lib/screens/settings/components/theme/widget_settings_section_theme.dart b/lib/screens/settings/components/theme/widget_settings_section_theme.dart
index d137469..ee03c54 100644
--- a/lib/screens/settings/components/theme/widget_settings_section_theme.dart
+++ b/lib/screens/settings/components/theme/widget_settings_section_theme.dart
@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/dynamic_colors_state.dart';
import 'package:lightmeter/generated/l10n.dart';
+import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart';
import 'package:lightmeter/screens/settings/components/theme/components/dynamic_color/widget_list_tile_dynamic_color.dart';
import 'package:lightmeter/screens/settings/components/theme/components/primary_color/widget_list_tile_primary_color.dart';
import 'package:lightmeter/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
class ThemeSettingsSection extends StatelessWidget {
const ThemeSettingsSection({super.key});
@@ -17,7 +17,7 @@ class ThemeSettingsSection extends StatelessWidget {
children: [
const ThemeTypeListTile(),
const PrimaryColorListTile(),
- if (context.get() != DynamicColorState.unavailable)
+ if (UserPreferencesProvider.dynamicColorStateOf(context) != DynamicColorState.unavailable)
const DynamicColorListTile(),
],
);
diff --git a/lib/screens/settings/flow_settings.dart b/lib/screens/settings/flow_settings.dart
index 3195c25..f3c8156 100644
--- a/lib/screens/settings/flow_settings.dart
+++ b/lib/screens/settings/flow_settings.dart
@@ -1,25 +1,38 @@
import 'package:flutter/material.dart';
-import 'package:lightmeter/data/caffeine_service.dart';
-import 'package:lightmeter/data/haptics_service.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:lightmeter/providers/services_provider.dart';
import 'package:lightmeter/screens/settings/screen_settings.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
class SettingsFlow extends StatelessWidget {
const SettingsFlow({super.key});
@override
Widget build(BuildContext context) {
- return InheritedWidgetBase(
+ return SettingsInteractorProvider(
data: SettingsInteractor(
- context.get(),
- context.get(),
- context.get(),
- context.get(),
+ ServicesProvider.of(context).userPreferencesService,
+ ServicesProvider.of(context).caffeineService,
+ ServicesProvider.of(context).hapticsService,
+ ServicesProvider.of(context).volumeEventsService,
),
child: const SettingsScreen(),
);
}
}
+
+class SettingsInteractorProvider extends InheritedWidget {
+ final SettingsInteractor data;
+
+ const SettingsInteractorProvider({
+ required this.data,
+ required super.child,
+ super.key,
+ });
+
+ static SettingsInteractor of(BuildContext context) {
+ return context.findAncestorWidgetOfExactType()!.data;
+ }
+
+ @override
+ bool updateShouldNotify(SettingsInteractorProvider oldWidget) => false;
+}
diff --git a/lib/screens/settings/screen_settings.dart b/lib/screens/settings/screen_settings.dart
index 3c745bd..9332d3f 100644
--- a/lib/screens/settings/screen_settings.dart
+++ b/lib/screens/settings/screen_settings.dart
@@ -1,12 +1,11 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/interactors/settings_interactor.dart';
import 'package:lightmeter/screens/settings/components/about/widget_settings_section_about.dart';
import 'package:lightmeter/screens/settings/components/general/widget_settings_section_general.dart';
import 'package:lightmeter/screens/settings/components/metering/widget_settings_section_metering.dart';
import 'package:lightmeter/screens/settings/components/theme/widget_settings_section_theme.dart';
+import 'package:lightmeter/screens/settings/flow_settings.dart';
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
-import 'package:lightmeter/utils/inherited_generics.dart';
class SettingsScreen extends StatefulWidget {
const SettingsScreen({super.key});
@@ -19,12 +18,12 @@ class _SettingsScreenState extends State {
@override
void didChangeDependencies() {
super.didChangeDependencies();
- context.get().disableVolumeHandling();
+ SettingsInteractorProvider.of(context).disableVolumeHandling();
}
@override
void deactivate() {
- context.get().restoreVolumeHandling();
+ SettingsInteractorProvider.of(context).restoreVolumeHandling();
super.deactivate();
}
diff --git a/lib/utils/inherited_generics.dart b/lib/utils/inherited_generics.dart
deleted file mode 100644
index 5f71ec6..0000000
--- a/lib/utils/inherited_generics.dart
+++ /dev/null
@@ -1,171 +0,0 @@
-import 'package:flutter/widgets.dart';
-
-/// Listening to multiple dependencies at the same time causes firing an event for all dependencies
-/// even though some of them didn't change:
-/// ```dart
-/// @override
-/// void didChangeDependencies() {
-/// super.didChangeDependencies();
-/// _bloc.add(EquipmentProfileChangedEvent(EquipmentProfile.of(context)));
-/// if (!MeteringScreenLayout.featureStatusOf(context, MeteringScreenLayoutFeature.filmPicker)) {
-/// _bloc.add(const FilmChangedEvent(Film.other()));
-/// }
-/// }
-/// ```
-/// To overcome this issue I've decided to create a generic listener,
-/// that will listen to each dependency separately.
-class InheritedWidgetListener extends StatefulWidget {
- final ValueChanged onDidChangeDependencies;
- final Widget child;
-
- const InheritedWidgetListener({
- required this.onDidChangeDependencies,
- required this.child,
- super.key,
- });
-
- @override
- State> createState() => _InheritedWidgetListenerState();
-}
-
-class _InheritedWidgetListenerState extends State> {
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- widget.onDidChangeDependencies(context.listen());
- }
-
- @override
- Widget build(BuildContext context) {
- return widget.child;
- }
-}
-
-class InheritedWidgetBase extends InheritedWidget {
- final T data;
-
- const InheritedWidgetBase({
- required this.data,
- required super.child,
- super.key,
- });
-
- static T of(BuildContext context, {bool listen = true}) {
- if (listen) {
- return context.dependOnInheritedWidgetOfExactType>()!.data;
- } else {
- return context.findAncestorWidgetOfExactType>()!.data;
- }
- }
-
- @override
- bool updateShouldNotify(InheritedWidgetBase oldWidget) => true;
-}
-
-extension InheritedWidgetBaseContext on BuildContext {
- T get() {
- return InheritedWidgetBase.of(this, listen: false);
- }
-
- T listen() {
- return InheritedWidgetBase.of(this);
- }
-}
-
-/// Listening to multiple dependencies at the same time causes firing an event for all dependencies
-/// even though some of them didn't change:
-/// ```dart
-/// @override
-/// void didChangeDependencies() {
-/// super.didChangeDependencies();
-/// _bloc.add(EquipmentProfileChangedEvent(EquipmentProfile.of(context)));
-/// if (!MeteringScreenLayout.featureStatusOf(context, MeteringScreenLayoutFeature.filmPicker)) {
-/// _bloc.add(const FilmChangedEvent(Film.other()));
-/// }
-/// }
-/// ```
-/// To overcome this issue I've decided to create a generic listener,
-/// that will listen to each dependency separately.
-class InheritedModelAspectListener extends StatefulWidget {
- final A aspect;
- final ValueChanged onDidChangeDependencies;
- final Widget child;
-
- const InheritedModelAspectListener({
- required this.aspect,
- required this.onDidChangeDependencies,
- required this.child,
- super.key,
- });
-
- @override
- State> createState() =>
- _InheritedModelAspectListenerState();
-}
-
-class _InheritedModelAspectListenerState
- extends State> {
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- widget.onDidChangeDependencies(context.listenModelFeature(widget.aspect));
- }
-
- @override
- Widget build(BuildContext context) {
- return widget.child;
- }
-}
-
-class InheritedModelBase extends InheritedModel {
- final Map data;
-
- const InheritedModelBase({
- required this.data,
- required super.child,
- super.key,
- });
-
- static Map of(BuildContext context, {bool listen = true}) {
- if (listen) {
- return context.dependOnInheritedWidgetOfExactType>()!.data;
- } else {
- return context.findAncestorWidgetOfExactType>()!.data;
- }
- }
-
- static T featureOf(BuildContext context, A aspect) {
- return InheritedModel.inheritFrom>(context, aspect: aspect)!
- .data[aspect]!;
- }
-
- @override
- bool updateShouldNotify(InheritedModelBase oldWidget) => true;
-
- @override
- bool updateShouldNotifyDependent(
- InheritedModelBase oldWidget,
- Set dependencies,
- ) {
- for (final dependecy in dependencies) {
- if (oldWidget.data[dependecy] != data[dependecy]) {
- return true;
- }
- }
- return false;
- }
-}
-
-extension InheritedModelBaseContext on BuildContext {
- Map getModel() {
- return InheritedModelBase.of(this, listen: false);
- }
-
- Map listenModel() {
- return InheritedModelBase.of(this);
- }
-
- T listenModelFeature(A aspect) {
- return InheritedModelBase.featureOf(this, aspect);
- }
-}
diff --git a/test/data/shared_prefs_service_test.dart b/test/data/shared_prefs_service_test.dart
index 514629f..692cb81 100644
--- a/test/data/shared_prefs_service_test.dart
+++ b/test/data/shared_prefs_service_test.dart
@@ -6,7 +6,7 @@ import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/data/models/supported_locale.dart';
import 'package:lightmeter/data/models/theme_type.dart';
import 'package:lightmeter/data/shared_prefs_service.dart';
-import 'package:lightmeter/providers/theme_provider.dart';
+import 'package:lightmeter/res/theme.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
import 'package:mocktail/mocktail.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -348,13 +348,13 @@ void main() {
group('primaryColor', () {
test('get default', () {
when(() => sharedPreferences.getInt(UserPreferencesService.primaryColorKey)).thenReturn(null);
- expect(service.primaryColor, ThemeProvider.primaryColorsList[5]);
+ expect(service.primaryColor, primaryColorsList[5]);
});
test('get', () {
when(() => sharedPreferences.getInt(UserPreferencesService.primaryColorKey))
.thenReturn(0xff9c27b0);
- expect(service.primaryColor, ThemeProvider.primaryColorsList[2]);
+ expect(service.primaryColor, primaryColorsList[2]);
});
test('set', () {
From aee527dccaac9cf8b632c1a53247c38cfccf123e Mon Sep 17 00:00:00 2001
From: Vadim <44135514+vodemn@users.noreply.github.com>
Date: Sun, 20 Aug 2023 11:14:37 +0200
Subject: [PATCH 25/49] Create FUNDING.yml
---
.github/FUNDING.yml | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 .github/FUNDING.yml
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..9375169
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+# These are supported funding model platforms
+
+github: [vodemn]
From a9f8223e4a1f7548b45e1a24e74d9d41ed6542be Mon Sep 17 00:00:00 2001
From: Vadim
Date: Mon, 28 Aug 2023 11:39:20 +0200
Subject: [PATCH 26/49] Added Firebase Analytics
---
android/app/build.gradle | 1 +
1 file changed, 1 insertion(+)
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 53774dc..5ccd388 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -109,4 +109,5 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "com.android.billingclient:billing-ktx:6.0.0"
+ implementation "com.google.firebase:firebase-analytics:17.4.1"
}
From e1c320b8047c9dd01383aa36af3cba287ad0a902 Mon Sep 17 00:00:00 2001
From: Vadim
Date: Mon, 28 Aug 2023 16:01:04 +0200
Subject: [PATCH 27/49] Replaced user config with github-actions[bot]
---
.github/workflows/create_release.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml
index 07f76c8..3d238b4 100644
--- a/.github/workflows/create_release.yml
+++ b/.github/workflows/create_release.yml
@@ -149,8 +149,8 @@ jobs:
- name: Commit changes
run: |
- git config --global user.name "vodemn"
- git config --global user.email "vadim.turko@gmail.com"
+ git config --local user.email "github-actions[bot]@users.noreply.github.com"
+ git config --local user.name "github-actions[bot]"
git add -A
git commit -m "Version bump"
From d364de44864a836f422544565e334ddc6fb55ce5 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Mon, 28 Aug 2023 15:55:12 +0000
Subject: [PATCH 28/49] Version bump
---
pubspec.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pubspec.yaml b/pubspec.yaml
index 7f4fcb5..f8ba217 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,7 @@
name: lightmeter
description: A new Flutter project.
publish_to: "none"
-version: 0.13.1+37
+version: 0.13.2+38
environment:
sdk: ">=3.0.0 <4.0.0"
From 4bb080a144e9233bd40ac4a5eeac6d3126256ae4 Mon Sep 17 00:00:00 2001
From: Vadim <44135514+vodemn@users.noreply.github.com>
Date: Sat, 2 Sep 2023 10:32:08 +0200
Subject: [PATCH 29/49] Implemented IAP & Equipment profiles (#89)
* added equipment profiles to layout config
* calculate layout height based on `MeteringScreenLayoutFeature`
* Update cd_dev.yml
* Fixed equipment profile tile padding
* import
* `webfactory/ssh-agent`
* Update pubspec.yaml
* fixed `MeteringScreenLayoutConfigJson` tests
* fixed `UserPreferencesService` tests
* reset selected equipment profile when layout feature is disabled
* `IAPProductType.equipment` -> `IAPProductType.paidFeatures`
* updated packages versions
* Update shared_prefs_service.dart
* Fixed & tested exposure pairs list builder
* typo
* typo
* added iap repo stub
* Renamed `EquipmentProfileData` ->`EquipmentProfile`
* Moved `EquipmentProfileProvider` to iap repo
* Update README.md
* Fixed `EquipmentProfileListener`
* Improved `EquipmentProfilesListTile` statuses visualization
* Update README.md
* Update ci.yml
* Post-merge fixes
* typo
* Added workflow checks
* more sophisticated iap icons
* Include IAP by default
* added loader for `IAPProductStatus.pending`
* typo
* Added equipment profiles list placeholder
* typo
* separated `IconPlaceholder`
* improved `buildExposureValues` testing
* cleanup
---
.github/workflows/build_apk.yml | 16 +-
.github/workflows/create_release.yml | 17 +-
.github/workflows/pr_check.yml | 20 +-
.vscode/launch.json | 2 +
README.md | 19 +-
iap/.gitignore | 36 +
iap/.metadata | 10 +
iap/LICENSE | 1 +
iap/analysis_options.yaml | 4 +
iap/lib/m3_lightmeter_iap.dart | 30 +
iap/lib/src/data/models/iap_product.dart | 5 +
.../providers/equipment_profile_provider.dart | 79 ++
.../src/providers/iap_products_provider.dart | 47 +
iap/pubspec.yaml | 25 +
ios/Runner.xcodeproj/project.pbxproj | 16 +-
lib/application.dart | 24 +-
lib/data/models/exposure_pair.dart | 12 +
lib/data/models/film.dart | 2 +
.../models/metering_screen_layout_config.dart | 9 +-
lib/data/shared_prefs_service.dart | 7 +-
lib/features.dart | 3 -
lib/l10n/intl_en.arb | 3 +-
lib/l10n/intl_fr.arb | 3 +-
lib/l10n/intl_ru.arb | 3 +-
lib/l10n/intl_zh.arb | 1 +
lib/providers/equipment_profile_provider.dart | 139 ---
lib/res/dimens.dart | 3 +-
.../bloc_container_camera.dart | 2 +-
.../widget_container_camera.dart | 8 +-
.../bloc_container_light_sensor.dart | 2 +-
.../widget_list_exposure_pairs.dart | 8 +-
.../widget_container_readings.dart | 8 +-
lib/screens/metering/screen_metering.dart | 94 +-
.../utils/equipment_profile_listener.dart | 30 +
.../utils/listsner_equipment_profiles.dart | 2 +-
.../widget_container_equipment_profile.dart | 3 +-
.../screen_equipment_profile.dart | 88 +-
.../widget_list_tile_equipment_profiles.dart | 30 +-
...ialog_metering_screen_layout_features.dart | 31 +-
.../widget_settings_section_metering.dart | 3 +-
lib/screens/settings/screen_settings.dart | 1 +
.../widget_icon_placeholder.dart} | 18 +-
.../shared/sliver_screen/screen_sliver.dart | 3 +-
lib/utils/log_2.dart | 3 -
pubspec.yaml | 24 +-
.../metering_screen_layout_config_test.dart | 35 +-
test/data/shared_prefs_service_test.dart | 7 +-
.../metering/screen_metering_test.dart | 966 ++++++++++++++++++
48 files changed, 1608 insertions(+), 294 deletions(-)
create mode 100644 iap/.gitignore
create mode 100644 iap/.metadata
create mode 100644 iap/LICENSE
create mode 100644 iap/analysis_options.yaml
create mode 100644 iap/lib/m3_lightmeter_iap.dart
create mode 100644 iap/lib/src/data/models/iap_product.dart
create mode 100644 iap/lib/src/providers/equipment_profile_provider.dart
create mode 100644 iap/lib/src/providers/iap_products_provider.dart
create mode 100644 iap/pubspec.yaml
delete mode 100644 lib/features.dart
delete mode 100644 lib/providers/equipment_profile_provider.dart
create mode 100644 lib/screens/metering/utils/equipment_profile_listener.dart
rename lib/screens/{metering/components/shared/exposure_pairs_list/components/empty_exposure_pairs_list/widget_list_exposure_pairs_empty.dart => shared/icon_placeholder/widget_icon_placeholder.dart} (68%)
delete mode 100644 lib/utils/log_2.dart
create mode 100644 test/screens/metering/screen_metering_test.dart
diff --git a/.github/workflows/build_apk.yml b/.github/workflows/build_apk.yml
index 9157f90..e61b38b 100644
--- a/.github/workflows/build_apk.yml
+++ b/.github/workflows/build_apk.yml
@@ -16,14 +16,28 @@ on:
- dev
- prod
default: 'dev'
+ include-iap:
+ type: boolean
+ description: Include IAP package
+ default: true
jobs:
build:
name: Build .apk
runs-on: macos-11
timeout-minutes: 15
-
steps:
+ - name: Connect private iap package
+ uses: webfactory/ssh-agent@v0.8.0
+ if: ${{ inputs.include-iap }}
+ with:
+ ssh-private-key: ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
+
+ - name: Override iap package with stub
+ if: ${{ !inputs.include-iap }}
+ run: |
+ echo "\ndependency_overrides:\n m3_lightmeter_iap:\n path: iap" >> pubspec.yaml
+
- uses: actions/checkout@v3
with:
submodules: recursive
diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml
index 3d238b4..e0281e3 100644
--- a/.github/workflows/create_release.yml
+++ b/.github/workflows/create_release.yml
@@ -31,6 +31,10 @@ on:
type: boolean
description: Create Google Play release
default: true
+ include-iap:
+ type: boolean
+ description: Include IAP package
+ default: true
env:
BUILD_ARGS: --release --flavor prod --dart-define cameraPreviewAspectRatio=240/320 -t lib/main_prod.dart
@@ -38,10 +42,21 @@ env:
jobs:
build:
name: Build .apk & .aab
- if: ${{ inputs.github-release }} || ${{ inputs.google-play-release }}
+ if: ${{ inputs.github-release || inputs.google-play-release }}
runs-on: macos-11
timeout-minutes: 30
steps:
+ - name: Connect private iap package
+ uses: webfactory/ssh-agent@v0.8.0
+ if: ${{ inputs.include-iap }}
+ with:
+ ssh-private-key: ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
+
+ - name: Override iap package with stub
+ if: ${{ !inputs.include-iap }}
+ run: |
+ echo "\ndependency_overrides:\n m3_lightmeter_iap:\n path: iap" >> pubspec.yaml
+
- uses: actions/checkout@v3
with:
submodules: recursive
diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml
index 9022170..e6f0294 100644
--- a/.github/workflows/pr_check.yml
+++ b/.github/workflows/pr_check.yml
@@ -11,13 +11,27 @@ on:
pull_request:
branches: ["main"]
+env:
+ # Stub iap package if this worlflow is running from the PR from a fork
+ STUB_IAP: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
+
jobs:
analyze_and_test:
name: Analyze & test
runs-on: macos-11
timeout-minutes: 10
-
steps:
+ - name: Connect private iap package
+ uses: webfactory/ssh-agent@v0.8.0
+ if: !env.STUB_IAP
+ with:
+ ssh-private-key: ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
+
+ - name: Override iap package with stub
+ if: env.STUB_IAP
+ run: |
+ echo "\ndependency_overrides:\n m3_lightmeter_iap:\n path: iap" >> pubspec.yaml
+
- uses: actions/checkout@v3
with:
submodules: recursive
@@ -25,7 +39,7 @@ jobs:
- uses: subosito/flutter-action@v2
with:
channel: "stable"
- flutter-version: '3.10.0'
+ flutter-version: "3.10.0"
- name: Prepare flutter project
run: |
@@ -37,4 +51,4 @@ jobs:
run: flutter analyze lib --fatal-infos
- name: Run tests
- run: flutter test
\ No newline at end of file
+ run: flutter test
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 822d34d..6cbc2bc 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -31,6 +31,7 @@
{
"name": "prod (android)",
"request": "launch",
+ //"flutterMode": "release",
"type": "dart",
"args": [
"--flavor",
@@ -43,6 +44,7 @@
{
"name": "prod (ios)",
"request": "launch",
+ //"flutterMode": "release",
"type": "dart",
"args": [
"--flavor",
diff --git a/README.md b/README.md
index f629aa2..c4af94a 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,22 @@ Out of the box Firebase Crashlytics won't work. If you want to add Crashlytics t
### 3. Get packages
-Fetch all the neccessary dependencies and generate translation files by running the following commands:
+As part of the app's functionallity is in the private repo, you have to replace these lines in _pubspec.yaml_:
+
+```yaml
+m3_lightmeter_iap:
+ git:
+ url: "https://github.com/vodemn/m3_lightmeter_iap"
+ ref: main
+```
+with these:
+```yaml
+m3_lightmeter_iap:
+ path: iap
+```
+and run `flutter pub get` from the _iap/_ folder.
+
+Then you can fetch all the neccessary dependencies and generate translation files by running the following commands:
```console
flutter pub get
flutter pub run intl_utils:generate
@@ -69,4 +84,4 @@ Apple does not provide API for reading Lux stream form the ambient light sensor.
## 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
+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)
diff --git a/iap/.gitignore b/iap/.gitignore
new file mode 100644
index 0000000..205884d
--- /dev/null
+++ b/iap/.gitignore
@@ -0,0 +1,36 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+.packages
+build/
+
+.fvm/
+*.properties
+ios/Flutter/
+.flutter-plugins
+.flutter-plugins-dependencies
\ No newline at end of file
diff --git a/iap/.metadata b/iap/.metadata
new file mode 100644
index 0000000..acbef51
--- /dev/null
+++ b/iap/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: 9944297138845a94256f1cf37beb88ff9a8e811a
+ channel: stable
+
+project_type: package
diff --git a/iap/LICENSE b/iap/LICENSE
new file mode 100644
index 0000000..ba75c69
--- /dev/null
+++ b/iap/LICENSE
@@ -0,0 +1 @@
+TODO: Add your license here.
diff --git a/iap/analysis_options.yaml b/iap/analysis_options.yaml
new file mode 100644
index 0000000..a5744c1
--- /dev/null
+++ b/iap/analysis_options.yaml
@@ -0,0 +1,4 @@
+include: package:flutter_lints/flutter.yaml
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/iap/lib/m3_lightmeter_iap.dart b/iap/lib/m3_lightmeter_iap.dart
new file mode 100644
index 0000000..8fe8aa5
--- /dev/null
+++ b/iap/lib/m3_lightmeter_iap.dart
@@ -0,0 +1,30 @@
+library m3_lightmeter_iap;
+
+import 'package:flutter/material.dart';
+import 'package:m3_lightmeter_iap/src/providers/equipment_profile_provider.dart';
+import 'package:m3_lightmeter_iap/src/providers/iap_products_provider.dart';
+
+export 'src/data/models/iap_product.dart';
+
+export 'src/providers/equipment_profile_provider.dart' hide EquipmentProfilesAspect;
+export 'src/providers/iap_products_provider.dart';
+
+class IAPProviders extends StatelessWidget {
+ final Object sharedPreferences;
+ final Widget child;
+
+ const IAPProviders({
+ required this.sharedPreferences,
+ required this.child,
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return IAPProductsProvider(
+ child: EquipmentProfileProvider(
+ child: child,
+ ),
+ );
+ }
+}
diff --git a/iap/lib/src/data/models/iap_product.dart b/iap/lib/src/data/models/iap_product.dart
new file mode 100644
index 0000000..706e3f1
--- /dev/null
+++ b/iap/lib/src/data/models/iap_product.dart
@@ -0,0 +1,5 @@
+enum IAPProductType { paidFeatures }
+
+class IAPProduct {
+ IAPProduct();
+}
diff --git a/iap/lib/src/providers/equipment_profile_provider.dart b/iap/lib/src/providers/equipment_profile_provider.dart
new file mode 100644
index 0000000..4f7aa0f
--- /dev/null
+++ b/iap/lib/src/providers/equipment_profile_provider.dart
@@ -0,0 +1,79 @@
+import 'package:flutter/material.dart';
+import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
+
+class EquipmentProfileProvider extends StatefulWidget {
+ final Widget child;
+
+ const EquipmentProfileProvider({required this.child, super.key});
+
+ static EquipmentProfileProviderState of(BuildContext context) {
+ return context.findAncestorStateOfType()!;
+ }
+
+ @override
+ State createState() => EquipmentProfileProviderState();
+}
+
+class EquipmentProfileProviderState extends State {
+ static const EquipmentProfile _defaultProfile = EquipmentProfile(
+ id: '',
+ name: '',
+ apertureValues: ApertureValue.values,
+ ndValues: NdValue.values,
+ shutterSpeedValues: ShutterSpeedValue.values,
+ isoValues: IsoValue.values,
+ );
+
+ @override
+ Widget build(BuildContext context) {
+ return EquipmentProfiles(
+ profiles: const [_defaultProfile],
+ selected: _defaultProfile,
+ child: widget.child,
+ );
+ }
+
+ void setProfile(EquipmentProfile data) {}
+
+ void addProfile(String name) {}
+
+ void updateProdile(EquipmentProfile data) {}
+
+ void deleteProfile(EquipmentProfile data) {}
+}
+
+enum EquipmentProfilesAspect { list, selected }
+
+class EquipmentProfiles extends InheritedModel {
+ const EquipmentProfiles({
+ super.key,
+ required this.profiles,
+ required this.selected,
+ required super.child,
+ });
+
+ final List profiles;
+ final EquipmentProfile selected;
+
+ static List of(BuildContext context) {
+ return InheritedModel.inheritFrom(
+ context,
+ aspect: EquipmentProfilesAspect.list,
+ )!
+ .profiles;
+ }
+
+ static EquipmentProfile selectedOf(BuildContext context) {
+ return InheritedModel.inheritFrom(
+ context,
+ aspect: EquipmentProfilesAspect.selected,
+ )!
+ .selected;
+ }
+
+ @override
+ bool updateShouldNotify(EquipmentProfiles oldWidget) => false;
+
+ @override
+ bool updateShouldNotifyDependent(EquipmentProfiles oldWidget, Set dependencies) => false;
+}
diff --git a/iap/lib/src/providers/iap_products_provider.dart b/iap/lib/src/providers/iap_products_provider.dart
new file mode 100644
index 0000000..4ea3c98
--- /dev/null
+++ b/iap/lib/src/providers/iap_products_provider.dart
@@ -0,0 +1,47 @@
+import 'package:flutter/material.dart';
+import 'package:m3_lightmeter_iap/src/data/models/iap_product.dart';
+
+class IAPProductsProvider extends StatefulWidget {
+ final Widget child;
+
+ const IAPProductsProvider({required this.child, super.key});
+
+ static IAPProductsProviderState of(BuildContext context) {
+ return context.findAncestorStateOfType()!;
+ }
+
+ @override
+ State createState() => IAPProductsProviderState();
+}
+
+class IAPProductsProviderState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return IAPProducts(
+ products: const [],
+ child: widget.child,
+ );
+ }
+
+ Future buy(IAPProductType type) async {}
+}
+
+class IAPProducts extends InheritedModel {
+ final List products;
+
+ const IAPProducts({
+ required this.products,
+ required super.child,
+ super.key,
+ });
+
+ static IAPProduct? of(BuildContext context, IAPProductType type) => null;
+
+ static bool isPurchased(BuildContext context, IAPProductType type) => false;
+
+ @override
+ bool updateShouldNotify(IAPProducts oldWidget) => false;
+
+ @override
+ bool updateShouldNotifyDependent(covariant IAPProducts oldWidget, Set dependencies) => false;
+}
diff --git a/iap/pubspec.yaml b/iap/pubspec.yaml
new file mode 100644
index 0000000..6aed37e
--- /dev/null
+++ b/iap/pubspec.yaml
@@ -0,0 +1,25 @@
+name: m3_lightmeter_iap
+description: IAP stubs for the M3 Lightmeter app.
+version: 0.2.0
+publish_to: 'none'
+
+environment:
+ sdk: '>=2.19.2 <3.0.0'
+ flutter: ">=1.17.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+ m3_lightmeter_resources:
+ git:
+ url: "https://github.com/vodemn/m3_lightmeter_resources"
+ ref: main
+ shared_preferences: 2.2.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ flutter_lints: ^2.0.0
+
+flutter:
+ uses-material-design: true
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index a858962..73ce339 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 51;
+ objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -237,6 +237,7 @@
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -268,6 +269,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -371,7 +373,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = 74JQ9DBXY6;
+ DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -500,7 +502,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = 74JQ9DBXY6;
+ DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -523,7 +525,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = 74JQ9DBXY6;
+ DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -600,7 +602,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = 74JQ9DBXY6;
+ DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -675,7 +677,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = 74JQ9DBXY6;
+ DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -747,7 +749,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- DEVELOPMENT_TEAM = 74JQ9DBXY6;
+ DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
diff --git a/lib/application.dart b/lib/application.dart
index 1249ed9..38ef0bf 100644
--- a/lib/application.dart
+++ b/lib/application.dart
@@ -10,11 +10,11 @@ import 'package:lightmeter/data/shared_prefs_service.dart';
import 'package:lightmeter/data/volume_events_service.dart';
import 'package:lightmeter/environment.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/providers/equipment_profile_provider.dart';
import 'package:lightmeter/providers/services_provider.dart';
import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/screens/metering/flow_metering.dart';
import 'package:lightmeter/screens/settings/flow_settings.dart';
+import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:platform/platform.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -32,16 +32,18 @@ class Application extends StatelessWidget {
]),
builder: (_, snapshot) {
if (snapshot.data != null) {
- return ServicesProvider(
- caffeineService: const CaffeineService(),
- environment: env.copyWith(hasLightSensor: snapshot.data![1] as bool),
- hapticsService: const HapticsService(),
- lightSensorService: const LightSensorService(LocalPlatform()),
- permissionsService: const PermissionsService(),
- userPreferencesService: UserPreferencesService(snapshot.data![0] as SharedPreferences),
- volumeEventsService: const VolumeEventsService(LocalPlatform()),
- child: UserPreferencesProvider(
- child: EquipmentProfileProvider(
+ return IAPProviders(
+ sharedPreferences: snapshot.data![0] as SharedPreferences,
+ child: ServicesProvider(
+ caffeineService: const CaffeineService(),
+ environment: env.copyWith(hasLightSensor: snapshot.data![1] as bool),
+ hapticsService: const HapticsService(),
+ lightSensorService: const LightSensorService(LocalPlatform()),
+ permissionsService: const PermissionsService(),
+ userPreferencesService:
+ UserPreferencesService(snapshot.data![0] as SharedPreferences),
+ volumeEventsService: const VolumeEventsService(LocalPlatform()),
+ child: UserPreferencesProvider(
child: Builder(
builder: (context) {
final theme = UserPreferencesProvider.themeOf(context);
diff --git a/lib/data/models/exposure_pair.dart b/lib/data/models/exposure_pair.dart
index 9df3ada..d3aa823 100644
--- a/lib/data/models/exposure_pair.dart
+++ b/lib/data/models/exposure_pair.dart
@@ -8,4 +8,16 @@ class ExposurePair {
@override
String toString() => '$aperture - $shutterSpeed';
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+ if (other.runtimeType != runtimeType) return false;
+ return other is ExposurePair &&
+ other.aperture == aperture &&
+ other.shutterSpeed == shutterSpeed;
+ }
+
+ @override
+ int get hashCode => Object.hash(aperture, shutterSpeed, runtimeType);
}
diff --git a/lib/data/models/film.dart b/lib/data/models/film.dart
index 2da1c9b..ab651e8 100644
--- a/lib/data/models/film.dart
+++ b/lib/data/models/film.dart
@@ -13,6 +13,8 @@ double log10polynomian(
) =>
a * pow(log10(x), 2) + b * log10(x) + c;
+typedef ReciprocityFailureBuilder = ShutterSpeedValue Function(ShutterSpeedValue shutterSpeed);
+
/// Only Ilford films have reciprocity formulas provided by the manufacturer:
/// https://www.ilfordphoto.com/wp/wp-content/uploads/2017/06/Reciprocity-Failure-Compensation.pdf
///
diff --git a/lib/data/models/metering_screen_layout_config.dart b/lib/data/models/metering_screen_layout_config.dart
index 0aae055..7802195 100644
--- a/lib/data/models/metering_screen_layout_config.dart
+++ b/lib/data/models/metering_screen_layout_config.dart
@@ -1,9 +1,14 @@
-enum MeteringScreenLayoutFeature { extremeExposurePairs, filmPicker, histogram }
+enum MeteringScreenLayoutFeature {
+ extremeExposurePairs,
+ filmPicker,
+ histogram,
+ equipmentProfiles,
+}
typedef MeteringScreenLayoutConfig = Map;
extension MeteringScreenLayoutConfigJson on MeteringScreenLayoutConfig {
- static MeteringScreenLayoutConfig fromJson(Map data) =>
+ static MeteringScreenLayoutConfig fromJson(Map data) =>
{
for (final f in MeteringScreenLayoutFeature.values)
f: data[f.index.toString()] as bool? ?? true
diff --git a/lib/data/shared_prefs_service.dart b/lib/data/shared_prefs_service.dart
index 8ae5487..812f0e6 100644
--- a/lib/data/shared_prefs_service.dart
+++ b/lib/data/shared_prefs_service.dart
@@ -95,6 +95,7 @@ class UserPreferencesService {
);
} else {
return {
+ MeteringScreenLayoutFeature.equipmentProfiles: true,
MeteringScreenLayoutFeature.extremeExposurePairs: true,
MeteringScreenLayoutFeature.filmPicker: true,
MeteringScreenLayoutFeature.histogram: true,
@@ -147,10 +148,4 @@ class UserPreferencesService {
orElse: () => Film.values.first,
);
set film(Film value) => _sharedPreferences.setString(filmKey, value.name);
-
- 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
}
diff --git a/lib/features.dart b/lib/features.dart
deleted file mode 100644
index deede30..0000000
--- a/lib/features.dart
+++ /dev/null
@@ -1,3 +0,0 @@
-class FeaturesConfig {
- static const bool equipmentProfilesEnabled = false;
-}
diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb
index 1bfe8ed..925d280 100644
--- a/lib/l10n/intl_en.arb
+++ b/lib/l10n/intl_en.arb
@@ -54,6 +54,7 @@
"isoValuesFilterDescription": "Select the ISO values to display. These may be your most commonly used values or those supported by your camera.",
"equipmentProfile": "Equipment profile",
"equipmentProfiles": "Equipment profiles",
+ "tapToAdd": "Tap to add",
"general": "General",
"keepScreenOn": "Keep screen on",
"haptics": "Haptics",
@@ -86,4 +87,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb
index f75d76b..c199d1e 100644
--- a/lib/l10n/intl_fr.arb
+++ b/lib/l10n/intl_fr.arb
@@ -42,6 +42,7 @@
"film": "Pellicule",
"equipment": "Équipement",
"equipmentProfileName": "Nom du profil de l'équipement",
+ "tapToAdd": "Appuie pour ajouter",
"equipmentProfileNameHint": "Praktica MTL5B",
"equipmentProfileAllValues": "Tout",
"apertureValues": "Valeurs Aperture",
@@ -86,4 +87,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb
index 3a67fc2..d20c4bd 100644
--- a/lib/l10n/intl_ru.arb
+++ b/lib/l10n/intl_ru.arb
@@ -54,6 +54,7 @@
"isoValuesFilterDescription": "Выберите значения ISO для отображения. Это может быть наиболее часто используемые значения или значения, поддерживаемые вашей камерой.",
"equipmentProfile": "Оборудование",
"equipmentProfiles": "Профили оборудования",
+ "tapToAdd": "Нажмите, чтобы добавить",
"general": "Общие",
"keepScreenOn": "Запрет блокировки",
"haptics": "Вибрация",
@@ -86,4 +87,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb
index 5d020cf..02bdf6c 100644
--- a/lib/l10n/intl_zh.arb
+++ b/lib/l10n/intl_zh.arb
@@ -54,6 +54,7 @@
"isoValuesFilterDescription": "选择要显示的 ISO。这些值可能是您最常用的值,也可能是相机支持的值。",
"equipmentProfile": "设备配置",
"equipmentProfiles": "设备配置",
+ "tapToAdd": "點擊添加",
"general": "通用",
"keepScreenOn": "保持屏幕常亮",
"haptics": "震动",
diff --git a/lib/providers/equipment_profile_provider.dart b/lib/providers/equipment_profile_provider.dart
deleted file mode 100644
index c0294fa..0000000
--- a/lib/providers/equipment_profile_provider.dart
+++ /dev/null
@@ -1,139 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:lightmeter/providers/services_provider.dart';
-import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
-import 'package:uuid/uuid.dart';
-
-// TODO(@vodemn): This will be removed in #89
-class EquipmentProfileProvider extends StatefulWidget {
- final Widget child;
-
- const EquipmentProfileProvider({required this.child, super.key});
-
- static EquipmentProfileProviderState of(BuildContext context) {
- return context.findAncestorStateOfType()!;
- }
-
- @override
- State createState() => EquipmentProfileProviderState();
-}
-
-class EquipmentProfileProviderState extends State