From f3b08868be5f32be78a2418bdefd1d6e9c3183ee Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Fri, 20 Oct 2023 16:12:43 +0200 Subject: [PATCH 01/13] ML-62 Providers tests + Platform & Application mocks (#131) - Fixed test coverage calculation - Removed `mockito` from the application mock - Implemented platform channel mocks to mimic incident light metering - Covered providers with unit tests - Covered metering screen pickers with widget tests - Laid foundation for integration tests --- .github/workflows/pr_check.yml | 2 +- .gitignore | 3 +- .vscode/settings.json | 3 +- README.md | 3 + iap/lib/m3_lightmeter_iap.dart | 31 +- iap/lib/src/data/iap_storage_service.dart | 17 + .../providers/equipment_profile_provider.dart | 61 --- iap/lib/src/providers/films_provider.dart | 65 --- integration_test/generate_screenshots.dart | 294 -------------- .../mocks/paid_features_mock.dart | 105 +++++ .../utils/platform_channel_mock.dart | 59 +++ .../utils/widget_tester_actions.dart | 84 ++++ lib/application_wrapper.dart | 35 +- lib/data/light_sensor_service.dart | 4 +- lib/main_dev.dart | 11 +- lib/main_prod.dart | 10 +- lib/main_release.dart | 10 +- lib/providers/equipment_profile_provider.dart | 130 ++++++ lib/providers/films_provider.dart | 107 +++++ lib/providers/services_provider.dart | 2 + lib/providers/user_preferences_provider.dart | 97 +++-- .../widget_list_exposure_pairs.dart | 4 +- .../widget_picker_equipment_profiles.dart | 2 +- ...dget_container_extreme_exposure_pairs.dart | 2 +- .../film_picker/widget_picker_film.dart | 2 +- .../nd_picker/widget_picker_nd.dart | 7 +- .../widget_container_readings.dart | 2 +- lib/screens/metering/screen_metering.dart | 14 +- .../listener_metering_layout_feature.dart | 52 --- .../utils/listsner_equipment_profiles.dart | 2 +- .../screen_equipment_profile.dart | 3 +- .../films/widget_list_tile_films.dart | 2 +- ...ialog_metering_screen_layout_features.dart | 13 +- .../utils}/selectable_provider.dart | 0 pubspec.yaml | 4 +- screenshots/README.md | 37 ++ screenshots/generate_screenshots.dart | 136 +++++++ .../generate_screenshots.sh | 2 +- test/application_mock.dart | 31 ++ test/data/light_sensor_service_test.dart | 90 ++--- test/data/models/exposure_pair_test.dart | 38 ++ test/data/models/supported_locale_test.dart | 2 + test/data/shared_prefs_service_test.dart | 57 +-- test/data/volume_events_service_test.dart | 18 +- test/event_channel_mock.dart | 10 + .../equipment_profile_provider_test.dart | 353 ++++++++++++++++ test/providers/films_provider_test.dart | 275 +++++++++++++ .../user_preferences_provider_test.dart | 376 ++++++++++++++++++ .../equipment_profile_picker_test.dart | 116 ++++++ ...extreme_exposure_pairs_container_test.dart | 72 ++++ .../readings_container/film_picker_test.dart | 110 +++++ .../readings_container/iso_picker_test.dart | 61 +++ .../readings_container/nd_picker_test.dart | 71 ++++ .../shared/animated_dialog_test.dart | 122 ++++++ .../shared/dialog_picker_test.dart | 95 +++++ .../shared/readings_container/utils.dart | 27 ++ test_coverage.sh | 8 + test_driver/integration_driver.dart | 8 + test_driver/screenshot_driver.dart | 47 +-- .../utils/grant_camera_permission.dart | 34 ++ 60 files changed, 2714 insertions(+), 724 deletions(-) create mode 100644 iap/lib/src/data/iap_storage_service.dart delete mode 100644 iap/lib/src/providers/equipment_profile_provider.dart delete mode 100644 iap/lib/src/providers/films_provider.dart delete mode 100644 integration_test/generate_screenshots.dart create mode 100644 integration_test/mocks/paid_features_mock.dart create mode 100644 integration_test/utils/platform_channel_mock.dart create mode 100644 integration_test/utils/widget_tester_actions.dart create mode 100644 lib/providers/equipment_profile_provider.dart create mode 100644 lib/providers/films_provider.dart delete mode 100644 lib/screens/metering/utils/listener_metering_layout_feature.dart rename {iap/lib/src/providers => lib/utils}/selectable_provider.dart (100%) create mode 100644 screenshots/README.md create mode 100644 screenshots/generate_screenshots.dart rename {integration_test => screenshots}/generate_screenshots.sh (83%) create mode 100644 test/application_mock.dart create mode 100644 test/data/models/exposure_pair_test.dart create mode 100644 test/event_channel_mock.dart create mode 100644 test/providers/equipment_profile_provider_test.dart create mode 100644 test/providers/films_provider_test.dart create mode 100644 test/providers/user_preferences_provider_test.dart create mode 100644 test/screens/metering/components/shared/readings_container/equipment_profile_picker_test.dart create mode 100644 test/screens/metering/components/shared/readings_container/extreme_exposure_pairs_container_test.dart create mode 100644 test/screens/metering/components/shared/readings_container/film_picker_test.dart create mode 100644 test/screens/metering/components/shared/readings_container/iso_picker_test.dart create mode 100644 test/screens/metering/components/shared/readings_container/nd_picker_test.dart create mode 100644 test/screens/metering/components/shared/readings_container/shared/animated_dialog_test.dart create mode 100644 test/screens/metering/components/shared/readings_container/shared/dialog_picker_test.dart create mode 100644 test/screens/metering/components/shared/readings_container/utils.dart create mode 100644 test_driver/integration_driver.dart create mode 100644 test_driver/utils/grant_camera_permission.dart diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml index caf7a97..113bba0 100644 --- a/.github/workflows/pr_check.yml +++ b/.github/workflows/pr_check.yml @@ -15,7 +15,7 @@ jobs: analyze_and_test: name: Analyze & test runs-on: macos-11 - timeout-minutes: 5 + timeout-minutes: 10 steps: - uses: 8BitJonny/gh-get-current-pr@2.2.0 id: PR diff --git a/.gitignore b/.gitignore index 703a8b6..4904e66 100644 --- a/.gitignore +++ b/.gitignore @@ -60,4 +60,5 @@ ios/Runner/GoogleService-Info.plist /lib/firebase_options.dart coverage/ -screenshots/ \ No newline at end of file +test/coverage_helper_test.dart +screenshots/*.png \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 97cf9fc..0ddf5c0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,10 +7,9 @@ "files.watcherExclude": { "**/.fvm": true }, - "dart.lineLength": 100, + "dart.lineLength": 120, "[dart]": { "editor.rulers": [ - 100, 120, ], "editor.selectionHighlight": true, diff --git a/README.md b/README.md index dae7291..ef38b93 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ +![](https://github.com/vodemn/m3_lightmeter/actions/workflows/pr_check.yml/badge.svg) +![](https://github.com/vodemn/m3_lightmeter/actions/workflows/create_release.yml/badge.svg) + # Table of contents - [Table of contents](#table-of-contents) diff --git a/iap/lib/m3_lightmeter_iap.dart b/iap/lib/m3_lightmeter_iap.dart index 171fe47..43e69aa 100644 --- a/iap/lib/m3_lightmeter_iap.dart +++ b/iap/lib/m3_lightmeter_iap.dart @@ -1,34 +1,9 @@ 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/films_provider.dart'; -import 'package:m3_lightmeter_iap/src/providers/iap_products_provider.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; export 'src/data/models/iap_product.dart'; - -export 'src/providers/equipment_profile_provider.dart'; -export 'src/providers/films_provider.dart'; export 'src/providers/iap_products_provider.dart'; +export 'src/data/iap_storage_service.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: FilmsProvider( - child: EquipmentProfileProvider( - child: child, - ), - ), - ); - } -} +const List films = []; diff --git a/iap/lib/src/data/iap_storage_service.dart b/iap/lib/src/data/iap_storage_service.dart new file mode 100644 index 0000000..f62f622 --- /dev/null +++ b/iap/lib/src/data/iap_storage_service.dart @@ -0,0 +1,17 @@ +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; + +class IAPStorageService { + const IAPStorageService(Object _); + + String get selectedEquipmentProfileId => ''; + set selectedEquipmentProfileId(String id) {} + + List get equipmentProfiles => []; + set equipmentProfiles(List profiles) {} + + Film get selectedFilm => const Film.other(); + set selectedFilm(Film value) {} + + List get filmsInUse => []; + set filmsInUse(List profiles) {} +} diff --git a/iap/lib/src/providers/equipment_profile_provider.dart b/iap/lib/src/providers/equipment_profile_provider.dart deleted file mode 100644 index 0a037a9..0000000 --- a/iap/lib/src/providers/equipment_profile_provider.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:m3_lightmeter_iap/src/providers/selectable_provider.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( - values: const [_defaultProfile], - selected: _defaultProfile, - child: widget.child, - ); - } - - void setProfile(EquipmentProfile data) {} - - void addProfile(String name, [EquipmentProfile? copyFrom]) {} - - void updateProdile(EquipmentProfile data) {} - - void deleteProfile(EquipmentProfile data) {} -} - -class EquipmentProfiles extends SelectableInheritedModel { - const EquipmentProfiles({ - super.key, - required super.values, - required super.selected, - required super.child, - }); - - static List of(BuildContext context) { - return InheritedModel.inheritFrom(context, aspect: SelectableAspect.list)!.values; - } - - static EquipmentProfile selectedOf(BuildContext context) { - return InheritedModel.inheritFrom(context, aspect: SelectableAspect.selected)!.selected; - } -} diff --git a/iap/lib/src/providers/films_provider.dart b/iap/lib/src/providers/films_provider.dart deleted file mode 100644 index e75ccd3..0000000 --- a/iap/lib/src/providers/films_provider.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:m3_lightmeter_iap/src/providers/selectable_provider.dart'; -import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; - -class FilmsProvider extends StatefulWidget { - final Widget child; - - const FilmsProvider({ - required this.child, - super.key, - }); - - static FilmsProviderState of(BuildContext context) { - return context.findAncestorStateOfType()!; - } - - @override - State createState() => FilmsProviderState(); -} - -class FilmsProviderState extends State { - @override - Widget build(BuildContext context) { - return Films( - values: const [Film.other()], - filmsInUse: const [Film.other()], - selected: const Film.other(), - child: widget.child, - ); - } - - void setFilm(Film film) {} - - void saveFilms(List films) {} -} - -class Films extends SelectableInheritedModel { - final List filmsInUse; - - const Films({ - super.key, - required super.values, - required this.filmsInUse, - required super.selected, - required super.child, - }); - - /// [Film.other()] + all the custom fields with actual reciprocity formulas - static List of(BuildContext context) { - return InheritedModel.inheritFrom(context)!.values; - } - - /// [Film.other()] + films in use selected by user - static List inUseOf(BuildContext context) { - return InheritedModel.inheritFrom( - context, - aspect: SelectableAspect.list, - )! - .filmsInUse; - } - - static Film selectedOf(BuildContext context) { - return InheritedModel.inheritFrom(context, aspect: SelectableAspect.selected)!.selected; - } -} diff --git a/integration_test/generate_screenshots.dart b/integration_test/generate_screenshots.dart deleted file mode 100644 index 2c28d42..0000000 --- a/integration_test/generate_screenshots.dart +++ /dev/null @@ -1,294 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; -import 'package:lightmeter/application.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/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/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/environment.dart'; -import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/providers/services_provider.dart'; -import 'package:lightmeter/providers/user_preferences_provider.dart'; -import 'package:lightmeter/res/dimens.dart'; -import 'package:lightmeter/res/theme.dart'; -import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart'; -import 'package:lightmeter/screens/metering/components/shared/readings_container/components/iso_picker/widget_picker_iso.dart'; -import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.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/screen_equipment_profile.dart'; -import 'package:lightmeter/screens/settings/screen_settings.dart'; -import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; -import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -class _MockSharedPreferences extends Mock implements SharedPreferences {} - -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 {} - -//https://stackoverflow.com/a/67186625/13167574 -void main() { - late _MockUserPreferencesService mockUserPreferencesService; - late _MockCaffeineService mockCaffeineService; - late _MockHapticsService mockHapticsService; - late _MockPermissionsService mockPermissionsService; - late _MockLightSensorService mockLightSensorService; - late _MockVolumeEventsService mockVolumeEventsService; - - final binding = IntegrationTestWidgetsFlutterBinding(); - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - setUpAll(() { - mockUserPreferencesService = _MockUserPreferencesService(); - when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.camera); - when(() => mockUserPreferencesService.stopType).thenReturn(StopType.third); - when(() => mockUserPreferencesService.locale).thenReturn(SupportedLocale.en); - when(() => mockUserPreferencesService.caffeine).thenReturn(true); - when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.shutter); - when(() => mockUserPreferencesService.cameraEvCalibration).thenReturn(0.0); - when(() => mockUserPreferencesService.lightSensorEvCalibration).thenReturn(0.0); - when(() => mockUserPreferencesService.iso).thenReturn(const IsoValue(400, StopType.full)); - when(() => mockUserPreferencesService.ndFilter).thenReturn(NdValue.values.first); - when(() => mockUserPreferencesService.haptics).thenReturn(true); - when(() => mockUserPreferencesService.meteringScreenLayout).thenReturn({ - MeteringScreenLayoutFeature.equipmentProfiles: true, - MeteringScreenLayoutFeature.extremeExposurePairs: true, - MeteringScreenLayoutFeature.filmPicker: true, - MeteringScreenLayoutFeature.histogram: false, - }); - when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light); - when(() => mockUserPreferencesService.dynamicColor).thenReturn(false); - - mockCaffeineService = _MockCaffeineService(); - when(() => mockCaffeineService.isKeepScreenOn()).thenAnswer((_) async => false); - when(() => mockCaffeineService.keepScreenOn(true)).thenAnswer((_) async => true); - when(() => mockCaffeineService.keepScreenOn(false)).thenAnswer((_) async => false); - - mockHapticsService = _MockHapticsService(); - when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {}); - when(() => mockHapticsService.responseVibration()).thenAnswer((_) async {}); - when(() => mockHapticsService.errorVibration()).thenAnswer((_) async {}); - - mockPermissionsService = _MockPermissionsService(); - when(() => mockPermissionsService.requestCameraPermission()) - .thenAnswer((_) async => PermissionStatus.granted); - when(() => mockPermissionsService.checkCameraPermission()) - .thenAnswer((_) async => PermissionStatus.granted); - - mockLightSensorService = _MockLightSensorService(); - when(() => mockLightSensorService.hasSensor()).thenAnswer((_) async => true); - when(() => mockLightSensorService.luxStream()).thenAnswer((_) => Stream.fromIterable([100])); - - mockVolumeEventsService = _MockVolumeEventsService(); - when(() => mockVolumeEventsService.setVolumeHandling(true)).thenAnswer((_) async => true); - when(() => mockVolumeEventsService.setVolumeHandling(false)).thenAnswer((_) async => false); - when(() => mockVolumeEventsService.volumeButtonsEventStream()) - .thenAnswer((_) => const Stream.empty()); - - when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {}); - when(() => mockHapticsService.responseVibration()).thenAnswer((_) async {}); - }); - - Future pumpApplication(WidgetTester tester) async { - await tester.pumpWidget( - IAPProviders( - sharedPreferences: _MockSharedPreferences(), - child: EquipmentProfiles( - selected: _mockEquipmentProfiles[0], - values: _mockEquipmentProfiles, - child: Films( - selected: const Film('Ilford HP5+', 400), - values: const [Film.other(), Film('Ilford HP5+', 400)], - filmsInUse: const [Film.other(), Film('Ilford HP5+', 400)], - child: ServicesProvider( - environment: const Environment.prod().copyWith(hasLightSensor: true), - userPreferencesService: mockUserPreferencesService, - caffeineService: mockCaffeineService, - hapticsService: mockHapticsService, - permissionsService: mockPermissionsService, - lightSensorService: mockLightSensorService, - volumeEventsService: mockVolumeEventsService, - child: const UserPreferencesProvider( - child: Application(), - ), - ), - ), - ), - ), - ); - await tester.pumpAndSettle(); - } - - /// Generates several screenshots with the light theme - /// and the additionally the first one with the dark theme - void generateScreenshots(Color color) { - testWidgets('${color.value}_light', (tester) async { - when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light); - when(() => mockUserPreferencesService.primaryColor).thenReturn(color); - await pumpApplication(tester); - - await tester.takePhoto(); - await tester.takeScreenshot(binding, '${color.value}_metering_reflected'); - - await tester.tap(find.byTooltip(S.current.tooltipUseLightSensor)); - await tester.pumpAndSettle(); - await tester.tap(find.byType(MeteringMeasureButton)); - await tester.tap(find.byType(MeteringMeasureButton)); - await tester.takeScreenshot(binding, '${color.value}_metering_incident'); - - expect(find.byType(IsoValuePicker), findsOneWidget); - await tester.tap(find.byType(IsoValuePicker)); - await tester.pumpAndSettle(Dimens.durationL); - expect(find.byType(DialogPicker), findsOneWidget); - await tester.takeScreenshot(binding, '${color.value}_metering_iso_picker'); - - await tester.tapCancelButton(); - expect(find.byType(DialogPicker), findsNothing); - expect(find.byTooltip(S.current.tooltipOpenSettings), findsOneWidget); - await tester.tap(find.byTooltip(S.current.tooltipOpenSettings)); - await tester.pumpAndSettle(); - expect(find.byType(SettingsScreen), findsOneWidget); - await tester.takeScreenshot(binding, '${color.value}_settings'); - - await tester.tapListTile(S.current.meteringScreenLayout); - await tester.takeScreenshot(binding, '${color.value}_settings_metering_screen_layout'); - - await tester.tapCancelButton(); - await tester.tapListTile(S.current.equipmentProfiles); - expect(find.byType(EquipmentProfilesScreen), findsOneWidget); - await tester.tap(find.byType(EquipmentProfileContainer).first); - await tester.pumpAndSettle(); - await tester.takeScreenshot(binding, '${color.value}-equipment_profiles'); - - await tester.tap(find.byIcon(Icons.iso).first); - await tester.pumpAndSettle(); - await tester.takeScreenshot(binding, '${color.value}_equipment_profiles_iso_picker'); - }); - - testWidgets( - '${color.value}_dark', - (tester) async { - when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.dark); - when(() => mockUserPreferencesService.primaryColor).thenReturn(color); - await pumpApplication(tester); - - await tester.takePhoto(); - await tester.takeScreenshot(binding, '${color.value}_metering_reflected_dark'); - - await tester.tap(find.byTooltip(S.current.tooltipUseLightSensor)); - await tester.pumpAndSettle(); - await tester.tap(find.byType(MeteringMeasureButton)); - await tester.tap(find.byType(MeteringMeasureButton)); - await tester.takeScreenshot(binding, '${color.value}_metering_incident_dark'); - }, - ); - } - - generateScreenshots(primaryColorsList[5]); - generateScreenshots(primaryColorsList[3]); - generateScreenshots(primaryColorsList[9]); -} - -extension on WidgetTester { - Future takeScreenshot(IntegrationTestWidgetsFlutterBinding binding, String name) async { - if (Platform.isAndroid) { - await binding.convertFlutterSurfaceToImage(); - await pumpAndSettle(); - } - await binding.takeScreenshot(name); - await pumpAndSettle(); - } - - Future takePhoto() async { - await tap(find.byType(MeteringMeasureButton)); - await pump(const Duration(seconds: 2)); // wait for circular progress indicator - await pump(const Duration(seconds: 1)); // wait for circular progress indicator - await pumpAndSettle(); - } - - Future tapCancelButton() async { - final cancelButton = find.byWidgetPredicate( - (widget) => - widget is TextButton && - widget.child is Text && - (widget.child as Text?)?.data == S.current.cancel, - ); - expect(cancelButton, findsOneWidget); - await tap(cancelButton); - await pumpAndSettle(); - } - - Future tapListTile(String title) async { - final listTile = find.byWidgetPredicate( - (widget) => - widget is ListTile && widget.title is Text && (widget.title as Text?)?.data == title, - ); - expect(listTile, findsOneWidget); - await tap(listTile); - await pumpAndSettle(); - } -} - -final _mockEquipmentProfiles = [ - const EquipmentProfile( - id: '', - name: '', - apertureValues: ApertureValue.values, - ndValues: NdValue.values, - shutterSpeedValues: ShutterSpeedValue.values, - isoValues: IsoValue.values, - ), - EquipmentProfile( - id: '1', - name: 'Praktica + Zenitar', - apertureValues: ApertureValue.values.sublist( - ApertureValue.values.indexOf(const ApertureValue(1.7, StopType.half)), - ApertureValue.values.indexOf(const ApertureValue(16, StopType.full)) + 1, - ), - ndValues: NdValue.values.sublist(0, 3), - shutterSpeedValues: ShutterSpeedValue.values.sublist( - ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(1000, true, StopType.full)), - ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(16, false, StopType.full)) + 1, - ), - isoValues: const [ - IsoValue(50, StopType.full), - IsoValue(100, StopType.full), - IsoValue(200, StopType.full), - IsoValue(250, StopType.third), - IsoValue(400, StopType.full), - IsoValue(500, StopType.third), - IsoValue(800, StopType.full), - IsoValue(1600, StopType.full), - IsoValue(3200, StopType.full), - ], - ), - const EquipmentProfile( - id: '2', - name: 'Praktica + Jupiter', - apertureValues: ApertureValue.values, - ndValues: NdValue.values, - shutterSpeedValues: ShutterSpeedValue.values, - isoValues: IsoValue.values, - ), -]; diff --git a/integration_test/mocks/paid_features_mock.dart b/integration_test/mocks/paid_features_mock.dart new file mode 100644 index 0000000..5d38c52 --- /dev/null +++ b/integration_test/mocks/paid_features_mock.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/providers/equipment_profile_provider.dart'; +import 'package:lightmeter/providers/films_provider.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; +import 'package:mocktail/mocktail.dart'; + +class _MockIAPStorageService extends Mock implements IAPStorageService {} + +class MockIAPProviders extends StatefulWidget { + final String selectedEquipmentProfileId; + final Film selectedFilm; + final Widget child; + + const MockIAPProviders({ + this.selectedEquipmentProfileId = '', + this.selectedFilm = const Film.other(), + required this.child, + super.key, + }); + + @override + State createState() => _MockIAPProvidersState(); +} + +class _MockIAPProvidersState extends State { + late final _MockIAPStorageService mockIAPStorageService; + + @override + void initState() { + super.initState(); + mockIAPStorageService = _MockIAPStorageService(); + when(() => mockIAPStorageService.equipmentProfiles).thenReturn(mockEquipmentProfiles); + when(() => mockIAPStorageService.selectedEquipmentProfileId).thenReturn(widget.selectedEquipmentProfileId); + when(() => mockIAPStorageService.filmsInUse).thenReturn(mockFilms); + when(() => mockIAPStorageService.selectedFilm).thenReturn(widget.selectedFilm); + } + + @override + Widget build(BuildContext context) { + return EquipmentProfileProvider( + storageService: mockIAPStorageService, + child: FilmsProvider( + storageService: mockIAPStorageService, + availableFilms: mockFilms, + child: widget.child, + ), + ); + } +} + +const defaultEquipmentProfile = EquipmentProfile( + id: '', + name: '', + apertureValues: ApertureValue.values, + ndValues: NdValue.values, + shutterSpeedValues: ShutterSpeedValue.values, + isoValues: IsoValue.values, +); + +final mockEquipmentProfiles = [ + EquipmentProfile( + id: '1', + name: 'Praktica + Zenitar', + apertureValues: ApertureValue.values.sublist( + ApertureValue.values.indexOf(const ApertureValue(1.7, StopType.half)), + ApertureValue.values.indexOf(const ApertureValue(16, StopType.full)) + 1, + ), + ndValues: NdValue.values.sublist(0, 3), + shutterSpeedValues: ShutterSpeedValue.values.sublist( + ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(1000, true, StopType.full)), + ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(16, false, StopType.full)) + 1, + ), + isoValues: const [ + IsoValue(50, StopType.full), + IsoValue(100, StopType.full), + IsoValue(200, StopType.full), + IsoValue(250, StopType.third), + IsoValue(400, StopType.full), + IsoValue(500, StopType.third), + IsoValue(800, StopType.full), + IsoValue(1600, StopType.full), + IsoValue(3200, StopType.full), + ], + ), + const EquipmentProfile( + id: '2', + name: 'Praktica + Jupiter', + apertureValues: ApertureValue.values, + ndValues: NdValue.values, + shutterSpeedValues: ShutterSpeedValue.values, + isoValues: IsoValue.values, + ), +]; + +const mockFilms = [_MockFilm(100, 2), _MockFilm(400, 2), _MockFilm(3, 800), _MockFilm(400, 1.5)]; + +class _MockFilm extends Film { + final double reciprocityMultiplier; + + const _MockFilm(int iso, this.reciprocityMultiplier) : super('Mock film $iso x$reciprocityMultiplier', iso); + + @override + double reciprocityFormula(double t) => t * reciprocityMultiplier; +} diff --git a/integration_test/utils/platform_channel_mock.dart b/integration_test/utils/platform_channel_mock.dart new file mode 100644 index 0000000..383f243 --- /dev/null +++ b/integration_test/utils/platform_channel_mock.dart @@ -0,0 +1,59 @@ +import 'dart:math'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:light_sensor/light_sensor.dart'; + +void setLightSensorAvilability({required bool hasSensor}) { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + LightSensor.methodChannel, + (methodCall) async { + switch (methodCall.method) { + case "sensor": + return hasSensor; + default: + return null; + } + }, + ); +} + +void resetLightSensorAvilability() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + LightSensor.methodChannel, + null, + ); +} + +Future sendMockIncidentEv(double ev) => sendMockLux((2.5 * pow(2, ev)).toInt()); + +Future sendMockLux([int lux = 100]) async { + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + LightSensor.eventChannel.name, + const StandardMethodCodec().encodeSuccessEnvelope(lux), + (ByteData? data) {}, + ); +} + +void setupLightSensorStreamHandler() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + MethodChannel(LightSensor.eventChannel.name), + (methodCall) async { + switch (methodCall.method) { + case "listen": + return; + case "cancel": + return; + default: + return null; + } + }, + ); +} + +void resetLightSensorStreamHandler() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + MethodChannel(LightSensor.eventChannel.name), + null, + ); +} diff --git a/integration_test/utils/widget_tester_actions.dart b/integration_test/utils/widget_tester_actions.dart new file mode 100644 index 0000000..8411634 --- /dev/null +++ b/integration_test/utils/widget_tester_actions.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/application.dart'; +import 'package:lightmeter/application_wrapper.dart'; +import 'package:lightmeter/environment.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/res/dimens.dart'; +import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; + +import '../mocks/paid_features_mock.dart'; +import 'platform_channel_mock.dart'; + +extension WidgetTesterCommonActions on WidgetTester { + Future pumpApplication({ + IAPProductStatus productStatus = IAPProductStatus.purchased, + String selectedEquipmentProfileId = '', + Film selectedFilm = const Film.other(), + }) async { + await pumpWidget( + IAPProducts( + products: [ + IAPProduct( + storeId: IAPProductType.paidFeatures.storeId, + status: productStatus, + ), + ], + child: ApplicationWrapper( + const Environment.dev(), + child: MockIAPProviders( + selectedEquipmentProfileId: selectedEquipmentProfileId, + selectedFilm: selectedFilm, + child: const Application(), + ), + ), + ), + ); + await pumpAndSettle(); + } + + Future takePhoto() async { + await tap(find.byType(MeteringMeasureButton)); + await pump(const Duration(seconds: 2)); // wait for circular progress indicator + await pump(const Duration(seconds: 1)); // wait for circular progress indicator + await pumpAndSettle(); + } + + Future toggleIncidentMetering(double ev) async { + await tap(find.byType(MeteringMeasureButton)); + await sendMockIncidentEv(ev); + await tap(find.byType(MeteringMeasureButton)); + await pumpAndSettle(); + } + + Future openAnimatedPicker() async { + await tap(find.byType(T)); + await pumpAndSettle(Dimens.durationL); + } +} + +extension WidgetTesterListTileActions on WidgetTester { + /// Useful for tapping a specific [ListTile] inside a specific screen or dialog + Future tapDescendantTextOf(String text) async { + await tap(find.descendant(of: find.byType(T), matching: find.text(text))); + } +} + +extension WidgetTesterTextButtonActions on WidgetTester { + Future tapSelectButton() => _tapTextButton(S.current.select); + + Future tapCancelButton() => _tapTextButton(S.current.cancel); + + Future tapSaveButton() => _tapTextButton(S.current.save); + + Future _tapTextButton(String text) async { + final button = find.byWidgetPredicate( + (widget) => widget is TextButton && widget.child is Text && (widget.child as Text?)?.data == text, + ); + expect(button, findsOneWidget); + await tap(button); + await pumpAndSettle(); + } +} diff --git a/lib/application_wrapper.dart b/lib/application_wrapper.dart index f8627f9..d2975d6 100644 --- a/lib/application_wrapper.dart +++ b/lib/application_wrapper.dart @@ -6,6 +6,8 @@ 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/films_provider.dart'; import 'package:lightmeter/providers/services_provider.dart'; import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; @@ -27,19 +29,26 @@ class ApplicationWrapper extends StatelessWidget { ]), builder: (_, snapshot) { if (snapshot.data != null) { - 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: child, + final iapService = IAPStorageService(snapshot.data![0] as SharedPreferences); + final userPreferencesService = UserPreferencesService(snapshot.data![0] as SharedPreferences); + final hasLightSensor = snapshot.data![1] as bool; + return ServicesProvider( + caffeineService: const CaffeineService(), + environment: env.copyWith(hasLightSensor: hasLightSensor), + hapticsService: const HapticsService(), + lightSensorService: const LightSensorService(LocalPlatform()), + permissionsService: const PermissionsService(), + userPreferencesService: userPreferencesService, + volumeEventsService: const VolumeEventsService(LocalPlatform()), + child: EquipmentProfileProvider( + storageService: iapService, + child: FilmsProvider( + storageService: iapService, + child: UserPreferencesProvider( + hasLightSensor: hasLightSensor, + userPreferencesService: userPreferencesService, + child: child, + ), ), ), ); diff --git a/lib/data/light_sensor_service.dart b/lib/data/light_sensor_service.dart index c837dde..a4b95d7 100644 --- a/lib/data/light_sensor_service.dart +++ b/lib/data/light_sensor_service.dart @@ -11,7 +11,7 @@ class LightSensorService { return false; } try { - return await LightSensor.hasSensor ?? false; + return await LightSensor.hasSensor(); } catch (_) { return false; } @@ -21,6 +21,6 @@ class LightSensorService { if (!localPlatform.isAndroid) { return const Stream.empty(); } - return LightSensor.lightSensorStream; + return LightSensor.luxStream(); } } diff --git a/lib/main_dev.dart b/lib/main_dev.dart index 9ef3faf..b43352f 100644 --- a/lib/main_dev.dart +++ b/lib/main_dev.dart @@ -2,8 +2,17 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/application.dart'; import 'package:lightmeter/application_wrapper.dart'; import 'package:lightmeter/environment.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - runApp(const ApplicationWrapper(Environment.dev(), child: Application())); + runApp( + IAPProducts( + products: [IAPProduct(storeId: IAPProductType.paidFeatures.storeId)], + child: const ApplicationWrapper( + Environment.dev(), + child: Application(), + ), + ), + ); } diff --git a/lib/main_prod.dart b/lib/main_prod.dart index b75513e..3460f32 100644 --- a/lib/main_prod.dart +++ b/lib/main_prod.dart @@ -3,9 +3,17 @@ import 'package:lightmeter/application.dart'; import 'package:lightmeter/application_wrapper.dart'; import 'package:lightmeter/environment.dart'; import 'package:lightmeter/firebase.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await initializeFirebase(handleErrors: true); - runApp(const ApplicationWrapper(Environment.prod(), child: Application())); + runApp( + const IAPProductsProvider( + child: ApplicationWrapper( + Environment.prod(), + child: Application(), + ), + ), + ); } diff --git a/lib/main_release.dart b/lib/main_release.dart index bb6384a..eea83e2 100644 --- a/lib/main_release.dart +++ b/lib/main_release.dart @@ -3,9 +3,17 @@ import 'package:lightmeter/application.dart'; import 'package:lightmeter/application_wrapper.dart'; import 'package:lightmeter/environment.dart'; import 'package:lightmeter/firebase.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await initializeFirebase(handleErrors: false); - runApp(const ApplicationWrapper(Environment.prod(), child: Application())); + runApp( + const IAPProductsProvider( + child: ApplicationWrapper( + Environment.prod(), + child: Application(), + ), + ), + ); } diff --git a/lib/providers/equipment_profile_provider.dart b/lib/providers/equipment_profile_provider.dart new file mode 100644 index 0000000..a5e0999 --- /dev/null +++ b/lib/providers/equipment_profile_provider.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/utils/selectable_provider.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; +import 'package:uuid/uuid.dart'; + +class EquipmentProfileProvider extends StatefulWidget { + final IAPStorageService storageService; + final Widget child; + + const EquipmentProfileProvider({ + required this.storageService, + 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, + ); + + List _customProfiles = []; + String _selectedId = ''; + + EquipmentProfile get _selectedProfile => _customProfiles.firstWhere( + (e) => e.id == _selectedId, + orElse: () => _defaultProfile, + ); + + @override + void initState() { + super.initState(); + _selectedId = widget.storageService.selectedEquipmentProfileId; + _customProfiles = widget.storageService.equipmentProfiles; + } + + @override + Widget build(BuildContext context) { + return EquipmentProfiles( + values: [ + _defaultProfile, + if (IAPProducts.isPurchased(context, IAPProductType.paidFeatures)) ..._customProfiles, + ], + selected: IAPProducts.isPurchased(context, IAPProductType.paidFeatures) + ? _selectedProfile + : _defaultProfile, + child: widget.child, + ); + } + + void setProfile(EquipmentProfile data) { + if (_selectedId != data.id) { + setState(() { + _selectedId = data.id; + }); + widget.storageService.selectedEquipmentProfileId = _selectedProfile.id; + } + } + + /// Creates a default equipment profile + void addProfile(String name, [EquipmentProfile? copyFrom]) { + _customProfiles.add( + EquipmentProfile( + id: const Uuid().v1(), + name: name, + apertureValues: copyFrom?.apertureValues ?? ApertureValue.values, + ndValues: copyFrom?.ndValues ?? NdValue.values, + shutterSpeedValues: copyFrom?.shutterSpeedValues ?? ShutterSpeedValue.values, + isoValues: copyFrom?.isoValues ?? IsoValue.values, + ), + ); + _refreshSavedProfiles(); + } + + void updateProdile(EquipmentProfile data) { + final indexToUpdate = _customProfiles.indexWhere((element) => element.id == data.id); + if (indexToUpdate >= 0) { + _customProfiles[indexToUpdate] = data; + _refreshSavedProfiles(); + } + } + + void deleteProfile(EquipmentProfile data) { + if (data.id == _selectedId) { + _selectedId = _defaultProfile.id; + widget.storageService.selectedEquipmentProfileId = _defaultProfile.id; + } + _customProfiles.remove(data); + _refreshSavedProfiles(); + } + + void _refreshSavedProfiles() { + widget.storageService.equipmentProfiles = _customProfiles; + setState(() {}); + } +} + +class EquipmentProfiles extends SelectableInheritedModel { + const EquipmentProfiles({ + super.key, + required super.values, + required super.selected, + required super.child, + }); + + /// [_defaultProfile] + profiles created by the user + static List of(BuildContext context) { + return InheritedModel.inheritFrom(context, aspect: SelectableAspect.list)! + .values; + } + + static EquipmentProfile selectedOf(BuildContext context) { + return InheritedModel.inheritFrom(context, + aspect: SelectableAspect.selected,)! + .selected; + } +} diff --git a/lib/providers/films_provider.dart b/lib/providers/films_provider.dart new file mode 100644 index 0000000..aff6d01 --- /dev/null +++ b/lib/providers/films_provider.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/utils/selectable_provider.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; + +class FilmsProvider extends StatefulWidget { + final IAPStorageService storageService; + final List? availableFilms; + final Widget child; + + const FilmsProvider({ + required this.storageService, + this.availableFilms, + required this.child, + super.key, + }); + + static FilmsProviderState of(BuildContext context) { + return context.findAncestorStateOfType()!; + } + + @override + State createState() => FilmsProviderState(); +} + +class FilmsProviderState extends State { + late List _filmsInUse; + late Film _selected; + + @override + void initState() { + super.initState(); + _filmsInUse = widget.storageService.filmsInUse; + _selected = widget.storageService.selectedFilm; + _discardSelectedIfNotIncluded(); + } + + @override + Widget build(BuildContext context) { + return Films( + values: [ + const Film.other(), + ...widget.availableFilms ?? films, + ], + filmsInUse: [ + const Film.other(), + if (IAPProducts.isPurchased(context, IAPProductType.paidFeatures)) ..._filmsInUse, + ], + selected: IAPProducts.isPurchased(context, IAPProductType.paidFeatures) + ? _selected + : const Film.other(), + child: widget.child, + ); + } + + void setFilm(Film film) { + if (_selected != film) { + _selected = film; + widget.storageService.selectedFilm = film; + setState(() {}); + } + } + + void saveFilms(List films) { + _filmsInUse = films; + widget.storageService.filmsInUse = films; + _discardSelectedIfNotIncluded(); + setState(() {}); + } + + void _discardSelectedIfNotIncluded() { + if (_selected != const Film.other() && !_filmsInUse.contains(_selected)) { + _selected = const Film.other(); + widget.storageService.selectedFilm = const Film.other(); + } + } +} + +class Films extends SelectableInheritedModel { + final List filmsInUse; + + const Films({ + super.key, + required super.values, + required this.filmsInUse, + required super.selected, + required super.child, + }); + + /// [Film.other()] + all the custom fields with actual reciprocity formulas + static List of(BuildContext context) { + return InheritedModel.inheritFrom(context)!.values; + } + + /// [Film.other()] + films in use selected by user + static List inUseOf(BuildContext context) { + return InheritedModel.inheritFrom( + context, + aspect: SelectableAspect.list, + )! + .filmsInUse; + } + + static Film selectedOf(BuildContext context) { + return InheritedModel.inheritFrom(context, aspect: SelectableAspect.selected)!.selected; + } +} diff --git a/lib/providers/services_provider.dart b/lib/providers/services_provider.dart index c2c548f..e65aa96 100644 --- a/lib/providers/services_provider.dart +++ b/lib/providers/services_provider.dart @@ -7,6 +7,7 @@ import 'package:lightmeter/data/shared_prefs_service.dart'; import 'package:lightmeter/data/volume_events_service.dart'; import 'package:lightmeter/environment.dart'; +// coverage:ignore-start class ServicesProvider extends InheritedWidget { final CaffeineService caffeineService; final Environment environment; @@ -34,3 +35,4 @@ class ServicesProvider extends InheritedWidget { @override bool updateShouldNotify(ServicesProvider oldWidget) => false; } +// coverage:ignore-end diff --git a/lib/providers/user_preferences_provider.dart b/lib/providers/user_preferences_provider.dart index af644d1..3a4111c 100644 --- a/lib/providers/user_preferences_provider.dart +++ b/lib/providers/user_preferences_provider.dart @@ -8,14 +8,20 @@ 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 bool hasLightSensor; + final UserPreferencesService userPreferencesService; final Widget child; - const UserPreferencesProvider({required this.child, super.key}); + const UserPreferencesProvider({ + required this.hasLightSensor, + required this.userPreferencesService, + required this.child, + super.key, + }); static _UserPreferencesProviderState of(BuildContext context) { return context.findAncestorStateOfType<_UserPreferencesProviderState>()!; @@ -38,8 +44,7 @@ class UserPreferencesProvider extends StatefulWidget { } static bool meteringScreenFeatureOf(BuildContext context, MeteringScreenLayoutFeature feature) { - return InheritedModel.inheritFrom<_MeteringScreenLayoutModel>(context, aspect: feature)! - .data[feature]!; + return InheritedModel.inheritFrom<_MeteringScreenLayoutModel>(context, aspect: feature)!.data[feature]!; } static StopType stopTypeOf(BuildContext context) { @@ -65,28 +70,20 @@ class UserPreferencesProvider extends StatefulWidget { 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; +class _UserPreferencesProviderState extends State with WidgetsBindingObserver { + late EvSourceType _evSourceType; + late StopType _stopType = widget.userPreferencesService.stopType; + late MeteringScreenLayoutConfig _meteringScreenLayout = widget.userPreferencesService.meteringScreenLayout; + late SupportedLocale _locale = widget.userPreferencesService.locale; + late ThemeType _themeType = widget.userPreferencesService.themeType; + late Color _primaryColor = widget.userPreferencesService.primaryColor; + late bool _dynamicColor = widget.userPreferencesService.dynamicColor; @override void initState() { super.initState(); - evSourceType = userPreferencesService.evSourceType; - evSourceType = evSourceType == EvSourceType.sensor && - !ServicesProvider.of(context).environment.hasLightSensor - ? EvSourceType.camera - : evSourceType; + _evSourceType = widget.userPreferencesService.evSourceType; + _evSourceType = _evSourceType == EvSourceType.sensor && !widget.hasLightSensor ? EvSourceType.camera : _evSourceType; WidgetsBinding.instance.addObserver(this); } @@ -109,9 +106,8 @@ class _UserPreferencesProviderState extends State late final DynamicColorState state; late final Color? dynamicPrimaryColor; if (lightDynamic != null && darkDynamic != null) { - if (dynamicColor) { - dynamicPrimaryColor = - (_themeBrightness == Brightness.light ? lightDynamic : darkDynamic).primary; + if (_dynamicColor) { + dynamicPrimaryColor = (_themeBrightness == Brightness.light ? lightDynamic : darkDynamic).primary; state = DynamicColorState.enabled; } else { dynamicPrimaryColor = null; @@ -124,13 +120,13 @@ class _UserPreferencesProviderState extends State return _UserPreferencesModel( brightness: _themeBrightness, dynamicColorState: state, - evSourceType: evSourceType, - locale: locale, - primaryColor: dynamicPrimaryColor ?? primaryColor, - stopType: stopType, - themeType: themeType, + evSourceType: _evSourceType, + locale: _locale, + primaryColor: dynamicPrimaryColor ?? _primaryColor, + stopType: _stopType, + themeType: _themeType, child: _MeteringScreenLayoutModel( - data: meteringScreenLayout, + data: _meteringScreenLayout, child: widget.child, ), ); @@ -140,65 +136,65 @@ class _UserPreferencesProviderState extends State void enableDynamicColor(bool enable) { setState(() { - dynamicColor = enable; + _dynamicColor = enable; }); - userPreferencesService.dynamicColor = enable; + widget.userPreferencesService.dynamicColor = enable; } void toggleEvSourceType() { - if (!ServicesProvider.of(context).environment.hasLightSensor) { + if (!widget.hasLightSensor) { return; } setState(() { - switch (evSourceType) { + switch (_evSourceType) { case EvSourceType.camera: - evSourceType = EvSourceType.sensor; + _evSourceType = EvSourceType.sensor; case EvSourceType.sensor: - evSourceType = EvSourceType.camera; + _evSourceType = EvSourceType.camera; } }); - userPreferencesService.evSourceType = evSourceType; + widget.userPreferencesService.evSourceType = _evSourceType; } void setLocale(SupportedLocale locale) { S.load(Locale(locale.intlName)).then((value) { setState(() { - this.locale = locale; + _locale = locale; }); - userPreferencesService.locale = locale; + widget.userPreferencesService.locale = locale; }); } void setMeteringScreenLayout(MeteringScreenLayoutConfig config) { setState(() { - meteringScreenLayout = config; + _meteringScreenLayout = config; }); - userPreferencesService.meteringScreenLayout = meteringScreenLayout; + widget.userPreferencesService.meteringScreenLayout = _meteringScreenLayout; } void setPrimaryColor(Color primaryColor) { setState(() { - this.primaryColor = primaryColor; + _primaryColor = primaryColor; }); - userPreferencesService.primaryColor = primaryColor; + widget.userPreferencesService.primaryColor = primaryColor; } void setStopType(StopType stopType) { setState(() { - this.stopType = stopType; + _stopType = stopType; }); - userPreferencesService.stopType = stopType; + widget.userPreferencesService.stopType = stopType; } void setThemeType(ThemeType themeType) { setState(() { - this.themeType = themeType; + _themeType = themeType; }); - userPreferencesService.themeType = themeType; + widget.userPreferencesService.themeType = themeType; } Brightness get _themeBrightness { - switch (themeType) { + switch (_themeType) { case ThemeType.light: return Brightness.light; case ThemeType.dark: @@ -258,8 +254,7 @@ class _UserPreferencesModel extends InheritedModel<_Aspect> { _UserPreferencesModel oldWidget, Set<_Aspect> dependencies, ) { - return (dependencies.contains(_Aspect.dynamicColorState) && - dynamicColorState != oldWidget.dynamicColorState) || + 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) || 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 4051c5e..cb78275 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 @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/exposure_pair.dart'; import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/films_provider.dart'; import 'package:lightmeter/res/dimens.dart'; - import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/components/exposure_pairs_list_item/widget_item_list_exposure_pairs.dart'; import 'package:lightmeter/screens/shared/icon_placeholder/widget_icon_placeholder.dart'; -import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; class ExposurePairsList extends StatelessWidget { final List exposurePairs; @@ -33,6 +32,7 @@ class ExposurePairsList extends StatelessWidget { alignment: Alignment.center, children: [ Row( + key: ValueKey(index), mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( diff --git a/lib/screens/metering/components/shared/readings_container/components/equipment_profile_picker/widget_picker_equipment_profiles.dart b/lib/screens/metering/components/shared/readings_container/components/equipment_profile_picker/widget_picker_equipment_profiles.dart index f47cd53..adbe1d2 100644 --- a/lib/screens/metering/components/shared/readings_container/components/equipment_profile_picker/widget_picker_equipment_profiles.dart +++ b/lib/screens/metering/components/shared/readings_container/components/equipment_profile_picker/widget_picker_equipment_profiles.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/equipment_profile_provider.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart'; -import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class EquipmentProfilePicker extends StatelessWidget { diff --git a/lib/screens/metering/components/shared/readings_container/components/extreme_exposure_pairs_container/widget_container_extreme_exposure_pairs.dart b/lib/screens/metering/components/shared/readings_container/components/extreme_exposure_pairs_container/widget_container_extreme_exposure_pairs.dart index 54c786c..5c701e2 100644 --- a/lib/screens/metering/components/shared/readings_container/components/extreme_exposure_pairs_container/widget_container_extreme_exposure_pairs.dart +++ b/lib/screens/metering/components/shared/readings_container/components/extreme_exposure_pairs_container/widget_container_extreme_exposure_pairs.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/exposure_pair.dart'; import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/films_provider.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart'; -import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; class ExtremeExposurePairsContainer extends StatelessWidget { final ExposurePair? fastest; diff --git a/lib/screens/metering/components/shared/readings_container/components/film_picker/widget_picker_film.dart b/lib/screens/metering/components/shared/readings_container/components/film_picker/widget_picker_film.dart index ae1e6fe..13a9366 100644 --- a/lib/screens/metering/components/shared/readings_container/components/film_picker/widget_picker_film.dart +++ b/lib/screens/metering/components/shared/readings_container/components/film_picker/widget_picker_film.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/films_provider.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart'; -import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class FilmPicker extends StatelessWidget { diff --git a/lib/screens/metering/components/shared/readings_container/components/nd_picker/widget_picker_nd.dart b/lib/screens/metering/components/shared/readings_container/components/nd_picker/widget_picker_nd.dart index eda016c..fdd8f0a 100644 --- a/lib/screens/metering/components/shared/readings_container/components/nd_picker/widget_picker_nd.dart +++ b/lib/screens/metering/components/shared/readings_container/components/nd_picker/widget_picker_nd.dart @@ -27,14 +27,13 @@ class NdValuePicker extends StatelessWidget { value.value == 0 ? S.of(context).none : value.value.toString(), ), // using descending order, because ND filter darkens image & lowers EV - itemTrailingBuilder: (selected, value) => value.value != selected.value - ? Text(S.of(context).evValue(value.toStringDifference(selected))) - : null, + itemTrailingBuilder: (selected, value) => + value.value != selected.value ? Text(S.of(context).evValue(value.toStringDifference(selected))) : null, onChanged: onChanged, closedChild: ReadingValueContainer.singleValue( value: ReadingValue( label: S.of(context).nd, - value: selectedValue.value.toString(), + value: selectedValue.value == 0 ? S.of(context).none : selectedValue.value.toString(), ), ), ); 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 f10546d..cb8af05 100644 --- a/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart +++ b/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/exposure_pair.dart'; import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; +import 'package:lightmeter/providers/equipment_profile_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/equipment_profile_picker/widget_picker_equipment_profiles.dart'; @@ -8,7 +9,6 @@ import 'package:lightmeter/screens/metering/components/shared/readings_container import 'package:lightmeter/screens/metering/components/shared/readings_container/components/film_picker/widget_picker_film.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/iso_picker/widget_picker_iso.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/nd_picker/widget_picker_nd.dart'; -import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class ReadingsContainer extends StatelessWidget { diff --git a/lib/screens/metering/screen_metering.dart b/lib/screens/metering/screen_metering.dart index f1d11fb..6900159 100644 --- a/lib/screens/metering/screen_metering.dart +++ b/lib/screens/metering/screen_metering.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lightmeter/data/models/ev_source_type.dart'; import 'package:lightmeter/data/models/exposure_pair.dart'; -import 'package:lightmeter/data/models/metering_screen_layout_config.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'; @@ -13,9 +13,7 @@ import 'package:lightmeter/screens/metering/components/camera_container/provider 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/screens/metering/utils/listener_metering_layout_feature.dart'; import 'package:lightmeter/screens/metering/utils/listsner_equipment_profiles.dart'; -import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class MeteringScreen extends StatelessWidget { @@ -73,15 +71,7 @@ class _InheritedListeners extends StatelessWidget { onDidChangeDependencies: (value) { context.read().add(EquipmentProfileChangedEvent(value)); }, - child: MeteringScreenLayoutFeatureListener( - feature: MeteringScreenLayoutFeature.filmPicker, - onDidChangeDependencies: (value) { - if (!value) { - FilmsProvider.of(context).setFilm(const Film.other()); - } - }, - child: child, - ), + child: child, ); } } diff --git a/lib/screens/metering/utils/listener_metering_layout_feature.dart b/lib/screens/metering/utils/listener_metering_layout_feature.dart deleted file mode 100644 index c245ec3..0000000 --- a/lib/screens/metering/utils/listener_metering_layout_feature.dart +++ /dev/null @@ -1,52 +0,0 @@ -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 index 68d03dc..ec604ce 100644 --- a/lib/screens/metering/utils/listsner_equipment_profiles.dart +++ b/lib/screens/metering/utils/listsner_equipment_profiles.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; +import 'package:lightmeter/providers/equipment_profile_provider.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class EquipmentProfileListener extends StatefulWidget { 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 3c72918..b10190f 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,12 +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/icon_placeholder/widget_icon_placeholder.dart'; import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart'; -import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class EquipmentProfilesScreen extends StatefulWidget { diff --git a/lib/screens/settings/components/metering/components/films/widget_list_tile_films.dart b/lib/screens/settings/components/metering/components/films/widget_list_tile_films.dart index c343e2b..72ff433 100644 --- a/lib/screens/settings/components/metering/components/films/widget_list_tile_films.dart +++ b/lib/screens/settings/components/metering/components/films/widget_list_tile_films.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/films_provider.dart'; import 'package:lightmeter/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart'; import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart'; -import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class FilmsListTile extends StatelessWidget { 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 57aaf24..2529e2e 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,21 +1,21 @@ 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/equipment_profile_provider.dart'; +import 'package:lightmeter/providers/films_provider.dart'; import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/res/dimens.dart'; -import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class MeteringScreenLayoutFeaturesDialog extends StatefulWidget { const MeteringScreenLayoutFeaturesDialog({super.key}); @override - State createState() => - _MeteringScreenLayoutFeaturesDialogState(); + State createState() => _MeteringScreenLayoutFeaturesDialogState(); } class _MeteringScreenLayoutFeaturesDialogState extends State { - late final _features = - MeteringScreenLayoutConfig.from(UserPreferencesProvider.meteringScreenConfigOf(context)); + late final _features = MeteringScreenLayoutConfig.from(UserPreferencesProvider.meteringScreenConfigOf(context)); @override Widget build(BuildContext context) { @@ -57,6 +57,9 @@ class _MeteringScreenLayoutFeaturesDialogState extends State *also in dark mode + +> **Android only + +## Run the generator + +```console +sh screenshots/generate_screenshots.sh +``` + +Screenshots will be stored in the _screenshots/_ folder. diff --git a/screenshots/generate_screenshots.dart b/screenshots/generate_screenshots.dart new file mode 100644 index 0000000..8c8e7e9 --- /dev/null +++ b/screenshots/generate_screenshots.dart @@ -0,0 +1,136 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.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/theme_type.dart'; +import 'package:lightmeter/data/models/volume_action.dart'; +import 'package:lightmeter/data/shared_prefs_service.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/res/theme.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/iso_picker/widget_picker_iso.dart'; +import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart'; +import 'package:lightmeter/screens/settings/screen_settings.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../integration_test/mocks/paid_features_mock.dart'; +import '../integration_test/utils/widget_tester_actions.dart'; + +//https://stackoverflow.com/a/67186625/13167574 + +/// Just a screenshot generator. No expectations here. +void main() { + final binding = IntegrationTestWidgetsFlutterBinding(); + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + final Color lightThemeColor = primaryColorsList[5]; + final Color darkThemeColor = primaryColorsList[3]; + + void mockSharedPrefs(ThemeType theme, Color color) { + // ignore: invalid_use_of_visible_for_testing_member + SharedPreferences.setMockInitialValues({ + /// Metering values + UserPreferencesService.evSourceTypeKey: EvSourceType.camera.index, + UserPreferencesService.isoKey: 400, + UserPreferencesService.ndFilterKey: 0, + + /// Metering settings + UserPreferencesService.stopTypeKey: StopType.third.index, + UserPreferencesService.cameraEvCalibrationKey: 0.0, + UserPreferencesService.lightSensorEvCalibrationKey: 0.0, + UserPreferencesService.meteringScreenLayoutKey: json.encode( + { + MeteringScreenLayoutFeature.equipmentProfiles: true, + MeteringScreenLayoutFeature.extremeExposurePairs: true, + MeteringScreenLayoutFeature.filmPicker: true, + MeteringScreenLayoutFeature.histogram: false, + }.toJson(), + ), + + /// General settings + UserPreferencesService.caffeineKey: true, + UserPreferencesService.hapticsKey: true, + UserPreferencesService.volumeActionKey: VolumeAction.shutter.toString(), + UserPreferencesService.localeKey: 'en', + + /// Theme settings + UserPreferencesService.themeTypeKey: theme.index, + UserPreferencesService.primaryColorKey: color.value, + UserPreferencesService.dynamicColorKey: false, + }); + } + + /// Generates several screenshots with the light theme + testWidgets( + 'Generate light theme screenshots', + (tester) async { + mockSharedPrefs(ThemeType.light, lightThemeColor); + await tester.pumpApplication(); + + await tester.takePhoto(); + await tester.takeScreenshot(binding, '${lightThemeColor.value}_metering_reflected'); + + if (Platform.isAndroid) { + await tester.tap(find.byTooltip(S.current.tooltipUseLightSensor)); + await tester.pumpAndSettle(); + await tester.toggleIncidentMetering(7.3); + await tester.takeScreenshot(binding, '${lightThemeColor.value}_metering_incident'); + } + + await tester.openAnimatedPicker(); + await tester.takeScreenshot(binding, '${lightThemeColor.value}_metering_iso_picker'); + + await tester.tapCancelButton(); + await tester.tap(find.byTooltip(S.current.tooltipOpenSettings)); + await tester.pumpAndSettle(); + await tester.takeScreenshot(binding, '${lightThemeColor.value}_settings'); + + await tester.tapDescendantTextOf(S.current.meteringScreenLayout); + await tester.takeScreenshot(binding, '${lightThemeColor.value}_settings_metering_screen_layout'); + + await tester.tapCancelButton(); + await tester.tapDescendantTextOf(S.current.equipmentProfiles); + await tester.pumpAndSettle(); + await tester.tapDescendantTextOf(mockEquipmentProfiles.first.name); + await tester.pumpAndSettle(); + await tester.takeScreenshot(binding, '${lightThemeColor.value}-equipment_profiles'); + + await tester.tap(find.byIcon(Icons.iso).first); + await tester.pumpAndSettle(); + await tester.takeScreenshot(binding, '${lightThemeColor.value}_equipment_profiles_iso_picker'); + }, + ); + + /// and the additionally the first one with the dark theme + testWidgets( + 'Generate dark theme screenshots', + (tester) async { + mockSharedPrefs(ThemeType.dark, darkThemeColor); + await tester.pumpApplication(); + + await tester.takePhoto(); + await tester.takeScreenshot(binding, '${darkThemeColor.value}_metering_reflected'); + + if (Platform.isAndroid) { + await tester.tap(find.byTooltip(S.current.tooltipUseLightSensor)); + await tester.pumpAndSettle(); + await tester.toggleIncidentMetering(7.3); + await tester.takeScreenshot(binding, '${darkThemeColor.value}_metering_incident'); + } + }, + ); +} + +extension on WidgetTester { + Future takeScreenshot(IntegrationTestWidgetsFlutterBinding binding, String name) async { + if (Platform.isAndroid) { + await binding.convertFlutterSurfaceToImage(); + await pumpAndSettle(); + } + await binding.takeScreenshot(name); + await pumpAndSettle(); + } +} diff --git a/integration_test/generate_screenshots.sh b/screenshots/generate_screenshots.sh similarity index 83% rename from integration_test/generate_screenshots.sh rename to screenshots/generate_screenshots.sh index 8296e96..c95568e 100644 --- a/integration_test/generate_screenshots.sh +++ b/screenshots/generate_screenshots.sh @@ -2,7 +2,7 @@ flutter drive \ --dart-define="cameraPreviewAspectRatio=240/320" \ --dart-define="cameraStubImage=assets/camera_stub_image.jpg" \ --driver=test_driver/screenshot_driver.dart \ - --target=integration_test/generate_screenshots.dart \ + --target=screenshots/generate_screenshots.dart \ --profile \ --flavor=dev \ --no-dds \ diff --git a/test/application_mock.dart b/test/application_mock.dart new file mode 100644 index 0000000..dbcf260 --- /dev/null +++ b/test/application_mock.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/res/theme.dart'; + +/// Provides [MaterialApp] with default theme and "en" localization +class WidgetTestApplicationMock extends StatelessWidget { + final Widget child; + + const WidgetTestApplicationMock({required this.child, super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: themeFrom(primaryColorsList[5], Brightness.light), + locale: const Locale('en'), + 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!, + ), + home: Scaffold(body: child), + ); + } +} diff --git a/test/data/light_sensor_service_test.dart b/test/data/light_sensor_service_test.dart index d29b50f..f8be45b 100644 --- a/test/data/light_sensor_service_test.dart +++ b/test/data/light_sensor_service_test.dart @@ -1,9 +1,11 @@ -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:light_sensor/light_sensor.dart'; import 'package:lightmeter/data/light_sensor_service.dart'; import 'package:mocktail/mocktail.dart'; import 'package:platform/platform.dart'; +import '../event_channel_mock.dart'; + class _MockLocalPlatform extends Mock implements LocalPlatform {} void main() { @@ -12,68 +14,44 @@ void main() { late _MockLocalPlatform localPlatform; late LightSensorService service; - const methodChannel = MethodChannel('system_feature'); - // TODO: add event channel mock - //const eventChannel = EventChannel('light.eventChannel'); - setUp(() { localPlatform = _MockLocalPlatform(); service = LightSensorService(localPlatform); }); - tearDown(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, null); - }); - group( 'hasSensor()', () { + void setMockSensorAvailability({required bool hasSensor}) { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + LightSensor.methodChannel, + (methodCall) async { + switch (methodCall.method) { + case "sensor": + return hasSensor; + default: + return null; + } + }, + ); + } + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + LightSensor.methodChannel, + null, + ); + }); + test('true - Android', () async { when(() => localPlatform.isAndroid).thenReturn(true); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, null); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, (methodCall) async { - switch (methodCall.method) { - case "sensor": - return true; - default: - return null; - } - }); + setMockSensorAvailability(hasSensor: true); expectLater(service.hasSensor(), completion(true)); }); test('false - Android', () async { when(() => localPlatform.isAndroid).thenReturn(true); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, null); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, (methodCall) async { - switch (methodCall.method) { - case "sensor": - return false; - default: - return null; - } - }); - expectLater(service.hasSensor(), completion(false)); - }); - - test('null - Android', () async { - when(() => localPlatform.isAndroid).thenReturn(true); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, null); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(methodChannel, (methodCall) async { - switch (methodCall.method) { - case "sensor": - return null; - default: - return null; - } - }); + setMockSensorAvailability(hasSensor: false); expectLater(service.hasSensor(), completion(false)); }); @@ -85,10 +63,18 @@ void main() { ); group('luxStream', () { - // test('Android', () async { - // when(() => localPlatform.isAndroid).thenReturn(true); - // expect(service.luxStream(), const Stream.empty()); - // }); + test('Android', () async { + when(() => localPlatform.isAndroid).thenReturn(true); + final stream = service.luxStream(); + final List result = []; + final subscription = stream.listen(result.add); + await sendMockVolumeAction(LightSensor.eventChannel.name, 100); + await sendMockVolumeAction(LightSensor.eventChannel.name, 150); + await sendMockVolumeAction(LightSensor.eventChannel.name, 150); + await sendMockVolumeAction(LightSensor.eventChannel.name, 200); + expect(result, [100, 150, 150, 200]); + subscription.cancel(); + }); test('iOS', () async { when(() => localPlatform.isAndroid).thenReturn(false); diff --git a/test/data/models/exposure_pair_test.dart b/test/data/models/exposure_pair_test.dart new file mode 100644 index 0000000..9143750 --- /dev/null +++ b/test/data/models/exposure_pair_test.dart @@ -0,0 +1,38 @@ +import 'package:lightmeter/data/models/exposure_pair.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; +import 'package:test/test.dart'; + +void main() { + test('toString()', () { + expect( + ExposurePair(ApertureValue.values.first, ShutterSpeedValue.values.first).toString(), + '${ApertureValue.values.first} - ${ShutterSpeedValue.values.first}', + ); + }); + + test('==', () { + expect( + ExposurePair(ApertureValue.values.first, ShutterSpeedValue.values.first) == + ExposurePair(ApertureValue.values.first, ShutterSpeedValue.values.first), + true, + ); + expect( + ExposurePair(ApertureValue.values.first, ShutterSpeedValue.values.first) == + ExposurePair(ApertureValue.values.first, ShutterSpeedValue.values.last), + false, + ); + }); + + test('hashCode', () { + expect( + ExposurePair(ApertureValue.values.first, ShutterSpeedValue.values.first).hashCode == + ExposurePair(ApertureValue.values.first, ShutterSpeedValue.values.first).hashCode, + true, + ); + expect( + ExposurePair(ApertureValue.values.first, ShutterSpeedValue.values.first).hashCode == + ExposurePair(ApertureValue.values.first, ShutterSpeedValue.values.last).hashCode, + false, + ); + }); +} diff --git a/test/data/models/supported_locale_test.dart b/test/data/models/supported_locale_test.dart index 6d92154..83f7489 100644 --- a/test/data/models/supported_locale_test.dart +++ b/test/data/models/supported_locale_test.dart @@ -6,11 +6,13 @@ void main() { expect(SupportedLocale.en.intlName, 'en'); expect(SupportedLocale.fr.intlName, 'fr'); expect(SupportedLocale.ru.intlName, 'ru'); + expect(SupportedLocale.zh.intlName, 'zh'); }); test('localizedName', () { expect(SupportedLocale.en.localizedName, 'English'); expect(SupportedLocale.fr.localizedName, 'Français'); expect(SupportedLocale.ru.localizedName, 'Русский'); + expect(SupportedLocale.zh.localizedName, '简体中文'); }); } diff --git a/test/data/shared_prefs_service_test.dart b/test/data/shared_prefs_service_test.dart index 0896626..8ec42e1 100644 --- a/test/data/shared_prefs_service_test.dart +++ b/test/data/shared_prefs_service_test.dart @@ -4,6 +4,7 @@ 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/models/volume_action.dart'; import 'package:lightmeter/data/shared_prefs_service.dart'; import 'package:lightmeter/res/theme.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; @@ -99,8 +100,7 @@ void main() { }); test('set', () { - when(() => sharedPreferences.setInt(UserPreferencesService.isoKey, 200)) - .thenAnswer((_) => Future.value(true)); + when(() => sharedPreferences.setInt(UserPreferencesService.isoKey, 200)).thenAnswer((_) => Future.value(true)); service.iso = const IsoValue(200, StopType.full); verify(() => sharedPreferences.setInt(UserPreferencesService.isoKey, 200)).called(1); }); @@ -118,8 +118,7 @@ void main() { }); test('set', () { - when(() => sharedPreferences.setInt(UserPreferencesService.ndFilterKey, 0)) - .thenAnswer((_) => Future.value(true)); + when(() => sharedPreferences.setInt(UserPreferencesService.ndFilterKey, 0)).thenAnswer((_) => Future.value(true)); service.ndFilter = const NdValue(0); verify(() => sharedPreferences.setInt(UserPreferencesService.ndFilterKey, 0)).called(1); }); @@ -175,8 +174,7 @@ void main() { }); test('set', () { - when(() => sharedPreferences.setInt(UserPreferencesService.stopTypeKey, 0)) - .thenAnswer((_) => Future.value(true)); + when(() => sharedPreferences.setInt(UserPreferencesService.stopTypeKey, 0)).thenAnswer((_) => Future.value(true)); service.stopType = StopType.full; verify(() => sharedPreferences.setInt(UserPreferencesService.stopTypeKey, 0)).called(1); }); @@ -253,6 +251,26 @@ void main() { verify(() => sharedPreferences.setBool(UserPreferencesService.hapticsKey, false)).called(1); }); }); + group('volumeAction', () { + test('get default', () { + when(() => sharedPreferences.getBool(UserPreferencesService.volumeActionKey)).thenReturn(null); + expect(service.volumeAction, VolumeAction.shutter); + }); + + test('get', () { + when(() => sharedPreferences.getString(UserPreferencesService.volumeActionKey)) + .thenReturn(VolumeAction.shutter.toString()); + expect(service.volumeAction, VolumeAction.shutter); + }); + + test('set', () { + when(() => sharedPreferences.setString(UserPreferencesService.volumeActionKey, VolumeAction.shutter.toString())) + .thenAnswer((_) => Future.value(true)); + service.volumeAction = VolumeAction.shutter; + verify(() => sharedPreferences.setString(UserPreferencesService.volumeActionKey, VolumeAction.shutter.toString())) + .called(1); + }); + }); group('locale', () { test('get default', () { @@ -261,8 +279,7 @@ void main() { }); test('get', () { - when(() => sharedPreferences.getString(UserPreferencesService.localeKey)) - .thenReturn('SupportedLocale.ru'); + when(() => sharedPreferences.getString(UserPreferencesService.localeKey)).thenReturn('SupportedLocale.ru'); expect(service.locale, SupportedLocale.ru); }); @@ -279,14 +296,12 @@ void main() { group('cameraEvCalibration', () { test('get default', () { - when(() => sharedPreferences.getDouble(UserPreferencesService.cameraEvCalibrationKey)) - .thenReturn(null); + when(() => sharedPreferences.getDouble(UserPreferencesService.cameraEvCalibrationKey)).thenReturn(null); expect(service.cameraEvCalibration, 0.0); }); test('get', () { - when(() => sharedPreferences.getDouble(UserPreferencesService.cameraEvCalibrationKey)) - .thenReturn(2.0); + when(() => sharedPreferences.getDouble(UserPreferencesService.cameraEvCalibrationKey)).thenReturn(2.0); expect(service.cameraEvCalibration, 2.0); }); @@ -303,14 +318,12 @@ void main() { group('lightSensorEvCalibration', () { test('get default', () { - when(() => sharedPreferences.getDouble(UserPreferencesService.lightSensorEvCalibrationKey)) - .thenReturn(null); + when(() => sharedPreferences.getDouble(UserPreferencesService.lightSensorEvCalibrationKey)).thenReturn(null); expect(service.lightSensorEvCalibration, 0.0); }); test('get', () { - when(() => sharedPreferences.getDouble(UserPreferencesService.lightSensorEvCalibrationKey)) - .thenReturn(2.0); + when(() => sharedPreferences.getDouble(UserPreferencesService.lightSensorEvCalibrationKey)).thenReturn(2.0); expect(service.lightSensorEvCalibration, 2.0); }); @@ -354,8 +367,7 @@ void main() { }); test('get', () { - when(() => sharedPreferences.getInt(UserPreferencesService.primaryColorKey)) - .thenReturn(0xff9c27b0); + when(() => sharedPreferences.getInt(UserPreferencesService.primaryColorKey)).thenReturn(0xff9c27b0); expect(service.primaryColor, primaryColorsList[2]); }); @@ -372,14 +384,12 @@ void main() { group('dynamicColor', () { test('get default', () { - when(() => sharedPreferences.getBool(UserPreferencesService.dynamicColorKey)) - .thenReturn(null); + when(() => sharedPreferences.getBool(UserPreferencesService.dynamicColorKey)).thenReturn(null); expect(service.dynamicColor, false); }); test('get', () { - when(() => sharedPreferences.getBool(UserPreferencesService.dynamicColorKey)) - .thenReturn(true); + when(() => sharedPreferences.getBool(UserPreferencesService.dynamicColorKey)).thenReturn(true); expect(service.dynamicColor, true); }); @@ -387,8 +397,7 @@ void main() { when(() => sharedPreferences.setBool(UserPreferencesService.dynamicColorKey, false)) .thenAnswer((_) => Future.value(true)); service.dynamicColor = false; - verify(() => sharedPreferences.setBool(UserPreferencesService.dynamicColorKey, false)) - .called(1); + verify(() => sharedPreferences.setBool(UserPreferencesService.dynamicColorKey, false)).called(1); }); }); } diff --git a/test/data/volume_events_service_test.dart b/test/data/volume_events_service_test.dart index f9ef3d6..f574e50 100644 --- a/test/data/volume_events_service_test.dart +++ b/test/data/volume_events_service_test.dart @@ -4,6 +4,8 @@ import 'package:lightmeter/data/volume_events_service.dart'; import 'package:mocktail/mocktail.dart'; import 'package:platform/platform.dart'; +import '../event_channel_mock.dart'; + class _MockLocalPlatform extends Mock implements LocalPlatform {} void main() { @@ -60,10 +62,18 @@ void main() { }); group('volumeButtonsEventStream', () { - // test('Android', () async { - // when(() => localPlatform.isAndroid).thenReturn(true); - // expect(service.volumeButtonsEventStream(), const Stream.empty()); - // }); + test('Android', () async { + when(() => localPlatform.isAndroid).thenReturn(true); + final stream = service.volumeButtonsEventStream(); + final List result = []; + final subscription = stream.listen(result.add); + await sendMockVolumeAction(VolumeEventsService.volumeEventsChannel.name, 24); + await sendMockVolumeAction(VolumeEventsService.volumeEventsChannel.name, 25); + await sendMockVolumeAction(VolumeEventsService.volumeEventsChannel.name, 20); + await sendMockVolumeAction(VolumeEventsService.volumeEventsChannel.name, 24); + expect(result, [24, 25, 24]); + subscription.cancel(); + }); test('iOS', () async { when(() => localPlatform.isAndroid).thenReturn(false); diff --git a/test/event_channel_mock.dart b/test/event_channel_mock.dart new file mode 100644 index 0000000..ee8cbc7 --- /dev/null +++ b/test/event_channel_mock.dart @@ -0,0 +1,10 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Future sendMockVolumeAction(String channelName, int keyCode) async { + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + channelName, + const StandardMethodCodec().encodeSuccessEnvelope(keyCode), + (ByteData? data) {}, + ); +} diff --git a/test/providers/equipment_profile_provider_test.dart b/test/providers/equipment_profile_provider_test.dart new file mode 100644 index 0000000..83f04f7 --- /dev/null +++ b/test/providers/equipment_profile_provider_test.dart @@ -0,0 +1,353 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/providers/equipment_profile_provider.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; +import 'package:mocktail/mocktail.dart'; + +class _MockIAPStorageService extends Mock implements IAPStorageService {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + late _MockIAPStorageService storageService; + + setUpAll(() { + storageService = _MockIAPStorageService(); + }); + + tearDown(() { + reset(storageService); + }); + + Future pumpTestWidget(WidgetTester tester, IAPProductStatus productStatus) async { + await tester.pumpWidget( + IAPProducts( + products: [ + IAPProduct( + storeId: IAPProductType.paidFeatures.storeId, + status: productStatus, + ), + ], + child: EquipmentProfileProvider( + storageService: storageService, + child: const _Application(), + ), + ), + ); + } + + void expectEquipmentProfilesCount(int count) { + expect(find.text('Equipment profiles count: $count'), findsOneWidget); + } + + void expectSelectedEquipmentProfileName(String name) { + expect(find.text('Selected equipment profile: $name'), findsOneWidget); + } + + group( + 'EquipmentProfileProvider dependency on IAPProductStatus', + () { + setUp(() { + when(() => storageService.selectedEquipmentProfileId).thenReturn(_customProfiles.first.id); + when(() => storageService.equipmentProfiles).thenReturn(_customProfiles); + }); + + testWidgets( + 'IAPProductStatus.purchased - show all saved profiles', + (tester) async { + await pumpTestWidget(tester, IAPProductStatus.purchased); + expectEquipmentProfilesCount(3); + expectSelectedEquipmentProfileName(_customProfiles.first.name); + }, + ); + + testWidgets( + 'IAPProductStatus.purchasable - show only default', + (tester) async { + await pumpTestWidget(tester, IAPProductStatus.purchasable); + expectEquipmentProfilesCount(1); + expectSelectedEquipmentProfileName(''); + }, + ); + + testWidgets( + 'IAPProductStatus.pending - show only default', + (tester) async { + await pumpTestWidget(tester, IAPProductStatus.pending); + expectEquipmentProfilesCount(1); + expectSelectedEquipmentProfileName(''); + }, + ); + }, + ); + + group('EquipmentProfileProvider CRUD', () { + testWidgets( + 'Add', + (tester) async { + when(() => storageService.equipmentProfiles).thenReturn([]); + when(() => storageService.selectedEquipmentProfileId).thenReturn(''); + + await pumpTestWidget(tester, IAPProductStatus.purchased); + expectEquipmentProfilesCount(1); + expectSelectedEquipmentProfileName(''); + + await tester.tap(find.byKey(_Application.addProfileButtonKey)); + await tester.pump(); + expectEquipmentProfilesCount(2); + expectSelectedEquipmentProfileName(''); + + verifyNever(() => storageService.selectedEquipmentProfileId = ''); + verify(() => storageService.equipmentProfiles = any>()).called(1); + }, + ); + + testWidgets( + 'Add from', + (tester) async { + when(() => storageService.equipmentProfiles).thenReturn(List.from(_customProfiles)); + when(() => storageService.selectedEquipmentProfileId).thenReturn(''); + + await pumpTestWidget(tester, IAPProductStatus.purchased); + expectEquipmentProfilesCount(3); + expectSelectedEquipmentProfileName(''); + + await tester.tap(find.byKey(_Application.addFromProfileButtonKey(_customProfiles[0].id))); + await tester.pump(); + expectEquipmentProfilesCount(4); + expectSelectedEquipmentProfileName(''); + + verifyNever(() => storageService.selectedEquipmentProfileId = ''); + verify(() => storageService.equipmentProfiles = any>()).called(1); + }, + ); + + testWidgets( + 'Edit selected', + (tester) async { + when(() => storageService.equipmentProfiles).thenReturn(List.from(_customProfiles)); + when(() => storageService.selectedEquipmentProfileId).thenReturn(_customProfiles[0].id); + + await pumpTestWidget(tester, IAPProductStatus.purchased); + + /// Change the name & limit ISO values of the both added profiles + await tester.tap(find.byKey(_Application.updateProfileButtonKey(_customProfiles[0].id))); + await tester.pumpAndSettle(); + expectEquipmentProfilesCount(3); + expectSelectedEquipmentProfileName("${_customProfiles[0].name} updated"); + + verifyNever(() => storageService.selectedEquipmentProfileId = _customProfiles[0].id); + verify(() => storageService.equipmentProfiles = any>()).called(1); + }, + ); + + testWidgets( + 'Delete selected', + (tester) async { + when(() => storageService.equipmentProfiles).thenReturn(List.from(_customProfiles)); + when(() => storageService.selectedEquipmentProfileId).thenReturn(_customProfiles[0].id); + + await pumpTestWidget(tester, IAPProductStatus.purchased); + expectEquipmentProfilesCount(3); + expectSelectedEquipmentProfileName(_customProfiles[0].name); + + /// Delete the selected profile + await tester.tap(find.byKey(_Application.deleteProfileButtonKey(_customProfiles[0].id))); + await tester.pumpAndSettle(); + expectEquipmentProfilesCount(2); + expectSelectedEquipmentProfileName(''); + + verify(() => storageService.selectedEquipmentProfileId = '').called(1); + verify(() => storageService.equipmentProfiles = any>()).called(1); + }, + ); + + testWidgets( + 'Delete not selected', + (tester) async { + when(() => storageService.equipmentProfiles).thenReturn(List.from(_customProfiles)); + when(() => storageService.selectedEquipmentProfileId).thenReturn(_customProfiles[0].id); + + await pumpTestWidget(tester, IAPProductStatus.purchased); + expectEquipmentProfilesCount(3); + expectSelectedEquipmentProfileName(_customProfiles[0].name); + + /// Delete the not selected profile + await tester.tap(find.byKey(_Application.deleteProfileButtonKey(_customProfiles[1].id))); + await tester.pumpAndSettle(); + expectEquipmentProfilesCount(2); + expectSelectedEquipmentProfileName(_customProfiles[0].name); + + verifyNever(() => storageService.selectedEquipmentProfileId = ''); + verify(() => storageService.equipmentProfiles = any>()).called(1); + }, + ); + + testWidgets( + 'Select', + (tester) async { + when(() => storageService.equipmentProfiles).thenReturn(List.from(_customProfiles)); + when(() => storageService.selectedEquipmentProfileId).thenReturn(''); + + await pumpTestWidget(tester, IAPProductStatus.purchased); + expectEquipmentProfilesCount(3); + expectSelectedEquipmentProfileName(''); + + /// Select the 1st custom profile + await tester.tap(find.byKey(_Application.setProfileButtonKey(_customProfiles[0].id))); + await tester.pumpAndSettle(); + expectEquipmentProfilesCount(3); + expectSelectedEquipmentProfileName(_customProfiles[0].name); + + verify(() => storageService.selectedEquipmentProfileId = _customProfiles[0].id).called(1); + verifyNever(() => storageService.equipmentProfiles = any>()); + }, + ); + }); +} + +class _Application extends StatelessWidget { + const _Application(); + + static ValueKey get addProfileButtonKey => const ValueKey('addProfileButtonKey'); + static ValueKey addFromProfileButtonKey(String id) => ValueKey('addFromProfileButtonKey$id'); + static ValueKey setProfileButtonKey(String id) => ValueKey('setProfileButtonKey$id'); + static ValueKey updateProfileButtonKey(String id) => ValueKey('updateProfileButtonKey$id'); + static ValueKey deleteProfileButtonKey(String id) => ValueKey('deleteProfileButtonKey$id'); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('IAPProviders test')), + body: Center( + child: Column( + children: [ + Text("Equipment profiles count: ${EquipmentProfiles.of(context).length}"), + Text("Selected equipment profile: ${EquipmentProfiles.selectedOf(context).name}"), + ElevatedButton( + key: addProfileButtonKey, + onPressed: () { + EquipmentProfileProvider.of(context).addProfile('Test added'); + }, + child: const Text("Add"), + ), + ...EquipmentProfiles.of(context).map((e) => _equipmentProfilesCrudRow(context, e)), + ], + ), + ), + ), + ); + } + + Widget _equipmentProfilesCrudRow(BuildContext context, EquipmentProfile profile) { + return Row( + children: [ + ElevatedButton( + key: setProfileButtonKey(profile.id), + onPressed: () { + EquipmentProfileProvider.of(context).setProfile(profile); + }, + child: const Text("Set"), + ), + ElevatedButton( + key: addFromProfileButtonKey(profile.id), + onPressed: () { + EquipmentProfileProvider.of(context).addProfile('Test from ${profile.name}', profile); + }, + child: const Text("Add from"), + ), + ElevatedButton( + key: updateProfileButtonKey(profile.id), + onPressed: () { + EquipmentProfileProvider.of(context).updateProdile( + profile.copyWith( + name: '${profile.name} updated', + isoValues: _customProfiles.first.isoValues, + ), + ); + }, + child: const Text("Update"), + ), + ElevatedButton( + key: deleteProfileButtonKey(profile.id), + onPressed: () { + EquipmentProfileProvider.of(context).deleteProfile(profile); + }, + child: const Text("Delete"), + ), + ], + ); + } +} + +final List _customProfiles = [ + const EquipmentProfile( + id: '1', + name: 'Test 1', + apertureValues: [ + ApertureValue(4.0, StopType.full), + ApertureValue(4.5, StopType.third), + ApertureValue(4.8, StopType.half), + ApertureValue(5.0, StopType.third), + ApertureValue(5.6, StopType.full), + ApertureValue(6.3, StopType.third), + ApertureValue(6.7, StopType.half), + ApertureValue(7.1, StopType.third), + ApertureValue(8, StopType.full), + ], + ndValues: [ + NdValue(0), + NdValue(2), + NdValue(4), + NdValue(8), + NdValue(16), + NdValue(32), + NdValue(64), + ], + shutterSpeedValues: ShutterSpeedValue.values, + isoValues: [ + IsoValue(100, StopType.full), + IsoValue(125, StopType.third), + IsoValue(160, StopType.third), + IsoValue(200, StopType.full), + IsoValue(250, StopType.third), + IsoValue(320, StopType.third), + IsoValue(400, StopType.full), + ], + ), + const EquipmentProfile( + id: '2', + name: 'Test 2', + apertureValues: [ + ApertureValue(4.0, StopType.full), + ApertureValue(4.5, StopType.third), + ApertureValue(4.8, StopType.half), + ApertureValue(5.0, StopType.third), + ApertureValue(5.6, StopType.full), + ApertureValue(6.3, StopType.third), + ApertureValue(6.7, StopType.half), + ApertureValue(7.1, StopType.third), + ApertureValue(8, StopType.full), + ], + ndValues: [ + NdValue(0), + NdValue(2), + NdValue(4), + NdValue(8), + NdValue(16), + NdValue(32), + NdValue(64), + ], + shutterSpeedValues: ShutterSpeedValue.values, + isoValues: [ + IsoValue(100, StopType.full), + IsoValue(125, StopType.third), + IsoValue(160, StopType.third), + IsoValue(200, StopType.full), + IsoValue(250, StopType.third), + IsoValue(320, StopType.third), + IsoValue(400, StopType.full), + ], + ), +]; diff --git a/test/providers/films_provider_test.dart b/test/providers/films_provider_test.dart new file mode 100644 index 0000000..760ca43 --- /dev/null +++ b/test/providers/films_provider_test.dart @@ -0,0 +1,275 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/providers/films_provider.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; +import 'package:mocktail/mocktail.dart'; + +class _MockIAPStorageService extends Mock implements IAPStorageService {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + late _MockIAPStorageService mockIAPStorageService; + + setUpAll(() { + mockIAPStorageService = _MockIAPStorageService(); + }); + + tearDown(() { + reset(mockIAPStorageService); + }); + + Future pumpTestWidget(WidgetTester tester, IAPProductStatus productStatus) async { + await tester.pumpWidget( + IAPProducts( + products: [ + IAPProduct( + storeId: IAPProductType.paidFeatures.storeId, + status: productStatus, + ) + ], + child: FilmsProvider( + storageService: mockIAPStorageService, + availableFilms: mockFilms, + child: const _Application(), + ), + ), + ); + } + + void expectFilmsCount(int count) { + expect(find.text('Films count: $count'), findsOneWidget); + } + + void expectFilmsInUseCount(int count) { + expect(find.text('Films in use count: $count'), findsOneWidget); + } + + void expectSelectedFilmName(String name) { + expect(find.text('Selected film: $name'), findsOneWidget); + } + + group( + 'FilmsProvider dependency on IAPProductStatus', + () { + setUp(() { + when(() => mockIAPStorageService.selectedFilm).thenReturn(mockFilms.first); + when(() => mockIAPStorageService.filmsInUse).thenReturn(mockFilms); + }); + + testWidgets( + 'IAPProductStatus.purchased - show all saved films', + (tester) async { + await pumpTestWidget(tester, IAPProductStatus.purchased); + expectFilmsCount(mockFilms.length + 1); + expectFilmsInUseCount(mockFilms.length + 1); + expectSelectedFilmName(mockFilms.first.name); + }, + ); + + testWidgets( + 'IAPProductStatus.purchasable - show only default', + (tester) async { + await pumpTestWidget(tester, IAPProductStatus.purchasable); + expectFilmsCount(mockFilms.length + 1); + expectFilmsInUseCount(1); + expectSelectedFilmName(''); + }, + ); + + testWidgets( + 'IAPProductStatus.pending - show only default', + (tester) async { + await pumpTestWidget(tester, IAPProductStatus.pending); + expectFilmsCount(mockFilms.length + 1); + expectFilmsInUseCount(1); + expectSelectedFilmName(''); + }, + ); + }, + ); + + group( + 'FilmsProvider CRUD', + () { + testWidgets( + 'Select films in use', + (tester) async { + when(() => mockIAPStorageService.selectedFilm).thenReturn(const Film.other()); + when(() => mockIAPStorageService.filmsInUse).thenReturn([]); + + /// Init + await pumpTestWidget(tester, IAPProductStatus.purchased); + expectFilmsCount(mockFilms.length + 1); + expectFilmsInUseCount(1); + expectSelectedFilmName(''); + + /// Select all filmsInUse + await tester.tap(find.byKey(_Application.saveFilmsButtonKey(0))); + await tester.pumpAndSettle(); + expectFilmsCount(mockFilms.length + 1); + expectFilmsInUseCount(mockFilms.length + 1); + expectSelectedFilmName(''); + + verify(() => mockIAPStorageService.filmsInUse = mockFilms.skip(0).toList()).called(1); + verifyNever(() => mockIAPStorageService.selectedFilm = const Film.other()); + }, + ); + + testWidgets( + 'Select film', + (tester) async { + when(() => mockIAPStorageService.selectedFilm).thenReturn(const Film.other()); + when(() => mockIAPStorageService.filmsInUse).thenReturn(mockFilms); + + /// Init + await pumpTestWidget(tester, IAPProductStatus.purchased); + expectFilmsCount(mockFilms.length + 1); + expectFilmsInUseCount(mockFilms.length + 1); + expectSelectedFilmName(''); + + /// Select all filmsInUse + await tester.tap(find.byKey(_Application.setFilmButtonKey(0))); + await tester.pumpAndSettle(); + expectFilmsCount(mockFilms.length + 1); + expectFilmsInUseCount(mockFilms.length + 1); + expectSelectedFilmName(mockFilms.first.name); + + verifyNever(() => mockIAPStorageService.filmsInUse = any>()); + verify(() => mockIAPStorageService.selectedFilm = mockFilms.first).called(1); + }, + ); + + group( + 'Coming from free app', + () { + testWidgets( + 'Has selected film', + (tester) async { + when(() => mockIAPStorageService.selectedFilm).thenReturn(mockFilms[2]); + when(() => mockIAPStorageService.filmsInUse).thenReturn([]); + + /// Init + await pumpTestWidget(tester, IAPProductStatus.purchased); + expectFilmsInUseCount(1); + expectSelectedFilmName(''); + + verifyNever(() => mockIAPStorageService.filmsInUse = any>()); + verify(() => mockIAPStorageService.selectedFilm = const Film.other()).called(1); + }, + ); + + testWidgets( + 'None film selected', + (tester) async { + when(() => mockIAPStorageService.selectedFilm).thenReturn(const Film.other()); + when(() => mockIAPStorageService.filmsInUse).thenReturn([]); + + /// Init + await pumpTestWidget(tester, IAPProductStatus.purchased); + expectFilmsInUseCount(1); + expectSelectedFilmName(''); + + verifyNever(() => mockIAPStorageService.filmsInUse = any>()); + verifyNever(() => mockIAPStorageService.selectedFilm = const Film.other()); + }, + ); + }, + ); + + testWidgets( + 'Discard selected (by filmsInUse list update)', + (tester) async { + when(() => mockIAPStorageService.selectedFilm).thenReturn(mockFilms.first); + when(() => mockIAPStorageService.filmsInUse).thenReturn(mockFilms); + + /// Init + await pumpTestWidget(tester, IAPProductStatus.purchased); + expectFilmsCount(mockFilms.length + 1); + expectFilmsInUseCount(mockFilms.length + 1); + expectSelectedFilmName(mockFilms.first.name); + + /// Select all filmsInUse except the first one + await tester.tap(find.byKey(_Application.saveFilmsButtonKey(1))); + await tester.pumpAndSettle(); + expectFilmsCount(mockFilms.length + 1); + expectFilmsInUseCount((mockFilms.length - 1) + 1); + expectSelectedFilmName(''); + + verify(() => mockIAPStorageService.filmsInUse = mockFilms.skip(1).toList()).called(1); + verify(() => mockIAPStorageService.selectedFilm = const Film.other()).called(1); + }, + ); + }, + ); +} + +class _Application extends StatelessWidget { + const _Application(); + + static ValueKey saveFilmsButtonKey(int index) => ValueKey('saveFilmsButtonKey$index'); + static ValueKey setFilmButtonKey(int index) => ValueKey('setFilmButtonKey$index'); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: Column( + children: [ + Text("Films count: ${Films.of(context).length}"), + Text("Films in use count: ${Films.inUseOf(context).length}"), + Text("Selected film: ${Films.selectedOf(context).name}"), + _filmRow(context, 0), + _filmRow(context, 1), + ], + ), + ), + ), + ); + } + + Widget _filmRow(BuildContext context, int index) { + return Row( + children: [ + ElevatedButton( + key: saveFilmsButtonKey(index), + onPressed: () { + FilmsProvider.of(context).saveFilms(mockFilms.skip(index).toList()); + }, + child: const Text("Save filmsInUse"), + ), + ElevatedButton( + key: setFilmButtonKey(index), + onPressed: () { + FilmsProvider.of(context).setFilm(mockFilms[index]); + }, + child: const Text("Set film"), + ), + ], + ); + } +} + +const mockFilms = [_MockFilm2x(), _MockFilm3x(), _MockFilm4x()]; + +class _MockFilm2x extends Film { + const _MockFilm2x() : super('Mock film 2x', 400); + + @override + double reciprocityFormula(double t) => t * 2; +} + +class _MockFilm3x extends Film { + const _MockFilm3x() : super('Mock film 3x', 800); + + @override + double reciprocityFormula(double t) => t * 3; +} + +class _MockFilm4x extends Film { + const _MockFilm4x() : super('Mock film 4x', 1600); + + @override + double reciprocityFormula(double t) => t * 4; +} diff --git a/test/providers/user_preferences_provider_test.dart b/test/providers/user_preferences_provider_test.dart new file mode 100644 index 0000000..ffa7993 --- /dev/null +++ b/test/providers/user_preferences_provider_test.dart @@ -0,0 +1,376 @@ +import 'package:dynamic_color/test_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.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/providers/user_preferences_provider.dart'; +import 'package:lightmeter/res/theme.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; +import 'package:material_color_utilities/material_color_utilities.dart'; +import 'package:mocktail/mocktail.dart'; + +class _MockUserPreferencesService extends Mock implements UserPreferencesService {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late _MockUserPreferencesService mockUserPreferencesService; + + setUpAll(() { + mockUserPreferencesService = _MockUserPreferencesService(); + }); + + setUp(() { + when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.camera); + when(() => mockUserPreferencesService.stopType).thenReturn(StopType.third); + when(() => mockUserPreferencesService.meteringScreenLayout).thenReturn({ + MeteringScreenLayoutFeature.extremeExposurePairs: true, + MeteringScreenLayoutFeature.filmPicker: true, + MeteringScreenLayoutFeature.equipmentProfiles: true, + MeteringScreenLayoutFeature.histogram: true, + }); + when(() => mockUserPreferencesService.locale).thenReturn(SupportedLocale.en); + when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light); + when(() => mockUserPreferencesService.primaryColor).thenReturn(primaryColorsList[5]); + when(() => mockUserPreferencesService.dynamicColor).thenReturn(false); + }); + + tearDown(() { + reset(mockUserPreferencesService); + }); + + Future pumpTestWidget( + WidgetTester tester, { + bool hasLightSensor = true, + required WidgetBuilder builder, + }) async { + await tester.pumpWidget( + UserPreferencesProvider( + hasLightSensor: hasLightSensor, + userPreferencesService: mockUserPreferencesService, + child: _Application(builder: builder), + ), + ); + } + + group('[evSourceType]', () { + Future pumpEvTestApplication(WidgetTester tester, {required bool hasLightSensor}) async { + await pumpTestWidget( + tester, + hasLightSensor: hasLightSensor, + builder: (context) => Column( + children: [ + Text('EV source type: ${UserPreferencesProvider.evSourceTypeOf(context)}'), + ElevatedButton( + onPressed: UserPreferencesProvider.of(context).toggleEvSourceType, + child: const Text('toggleEvSourceType'), + ), + ], + ), + ); + } + + void expectEvSource(EvSourceType evSourceType) { + expect(find.text("EV source type: $evSourceType"), findsOneWidget); + } + + testWidgets( + 'Init evSourceType when has sensor & stored sensor', + (tester) async { + when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.sensor); + await pumpEvTestApplication(tester, hasLightSensor: true); + expectEvSource(EvSourceType.sensor); + }, + ); + + testWidgets( + 'Init evSourceType when has no sensor & stored camera', + (tester) async { + when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.camera); + await pumpEvTestApplication(tester, hasLightSensor: false); + expectEvSource(EvSourceType.camera); + }, + ); + + testWidgets( + 'Init evSourceType when has no sensor & stored sensor -> Reset to camera', + (tester) async { + when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.sensor); + await pumpEvTestApplication(tester, hasLightSensor: false); + expectEvSource(EvSourceType.camera); + }, + ); + + testWidgets( + 'Try toggleEvSourceType() when has no sensor', + (tester) async { + when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.camera); + await pumpEvTestApplication(tester, hasLightSensor: false); + await tester.tap(find.text('toggleEvSourceType')); + await tester.pumpAndSettle(); + verifyNever(() => mockUserPreferencesService.evSourceType = EvSourceType.sensor); + }, + ); + + testWidgets( + 'Try toggleEvSourceType() when has sensor', + (tester) async { + when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.camera); + await pumpEvTestApplication(tester, hasLightSensor: true); + + await tester.tap(find.text('toggleEvSourceType')); + await tester.pumpAndSettle(); + expectEvSource(EvSourceType.sensor); + verify(() => mockUserPreferencesService.evSourceType = EvSourceType.sensor).called(1); + + await tester.tap(find.text('toggleEvSourceType')); + await tester.pumpAndSettle(); + expectEvSource(EvSourceType.camera); + verify(() => mockUserPreferencesService.evSourceType = EvSourceType.camera).called(1); + }, + ); + }); + + testWidgets( + 'Set different stop type', + (tester) async { + when(() => mockUserPreferencesService.stopType).thenReturn(StopType.third); + await pumpTestWidget( + tester, + builder: (context) => Column( + children: [ + Text('Stop type: ${UserPreferencesProvider.stopTypeOf(context)}'), + ElevatedButton( + onPressed: () => UserPreferencesProvider.of(context).setStopType(StopType.full), + child: const Text('setStopType'), + ), + ], + ), + ); + expect(find.text("Stop type: ${StopType.third}"), findsOneWidget); + + await tester.tap(find.text('setStopType')); + await tester.pumpAndSettle(); + expect(find.text("Stop type: ${StopType.full}"), findsOneWidget); + verify(() => mockUserPreferencesService.stopType = StopType.full).called(1); + }, + ); + + testWidgets( + 'Set metering screen layout config', + (tester) async { + await pumpTestWidget( + tester, + builder: (context) { + final config = UserPreferencesProvider.meteringScreenConfigOf(context); + return Column( + children: [ + ...List.generate( + config.length, + (index) => Text('${config.keys.toList()[index]}: ${config.values.toList()[index]}'), + ), + ...List.generate( + MeteringScreenLayoutFeature.values.length, + (index) => Text( + '${MeteringScreenLayoutFeature.values[index]}: ${UserPreferencesProvider.meteringScreenFeatureOf(context, MeteringScreenLayoutFeature.values[index])}', + ), + ), + ElevatedButton( + onPressed: () => UserPreferencesProvider.of(context).setMeteringScreenLayout({ + MeteringScreenLayoutFeature.equipmentProfiles: true, + MeteringScreenLayoutFeature.extremeExposurePairs: false, + MeteringScreenLayoutFeature.filmPicker: false, + MeteringScreenLayoutFeature.histogram: true, + }), + child: const Text(''), + ), + ], + ); + }, + ); + // Match `findsNWidgets(2)` to verify that `meteringScreenFeatureOf` specific results are the same as the whole config + expect(find.text("${MeteringScreenLayoutFeature.equipmentProfiles}: true"), findsNWidgets(2)); + expect(find.text("${MeteringScreenLayoutFeature.extremeExposurePairs}: true"), findsNWidgets(2)); + expect(find.text("${MeteringScreenLayoutFeature.filmPicker}: true"), findsNWidgets(2)); + expect(find.text("${MeteringScreenLayoutFeature.histogram}: true"), findsNWidgets(2)); + + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + expect(find.text("${MeteringScreenLayoutFeature.equipmentProfiles}: true"), findsNWidgets(2)); + expect(find.text("${MeteringScreenLayoutFeature.extremeExposurePairs}: false"), findsNWidgets(2)); + expect(find.text("${MeteringScreenLayoutFeature.filmPicker}: false"), findsNWidgets(2)); + expect(find.text("${MeteringScreenLayoutFeature.histogram}: true"), findsNWidgets(2)); + verify( + () => mockUserPreferencesService.meteringScreenLayout = { + MeteringScreenLayoutFeature.extremeExposurePairs: false, + MeteringScreenLayoutFeature.filmPicker: false, + MeteringScreenLayoutFeature.equipmentProfiles: true, + MeteringScreenLayoutFeature.histogram: true, + }, + ).called(1); + }, + ); + + testWidgets( + 'Set different locale', + (tester) async { + when(() => mockUserPreferencesService.locale).thenReturn(SupportedLocale.en); + await pumpTestWidget( + tester, + builder: (context) => ElevatedButton( + onPressed: () => UserPreferencesProvider.of(context).setLocale(SupportedLocale.fr), + child: Text('${UserPreferencesProvider.localeOf(context)}'), + ), + ); + expect(find.text("${SupportedLocale.en}"), findsOneWidget); + + await tester.tap(find.text("${SupportedLocale.en}")); + await tester.pumpAndSettle(); + expect(find.text("${SupportedLocale.fr}"), findsOneWidget); + verify(() => mockUserPreferencesService.locale = SupportedLocale.fr).called(1); + }, + ); + + group('[theme]', () { + testWidgets( + 'Set dark theme type', + (tester) async { + when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light); + await pumpTestWidget( + tester, + builder: (context) => Column( + children: [ + ElevatedButton( + onPressed: () => UserPreferencesProvider.of(context).setThemeType(ThemeType.dark), + child: Text('${UserPreferencesProvider.themeTypeOf(context)}'), + ), + Text('${Theme.of(context).colorScheme.brightness}') + ], + ), + ); + expect(find.text("${ThemeType.light}"), findsOneWidget); + expect(find.text("${Brightness.light}"), findsOneWidget); + + await tester.tap(find.text("${ThemeType.light}")); + await tester.pumpAndSettle(); + expect(find.text("${ThemeType.dark}"), findsOneWidget); + expect(find.text("${Brightness.dark}"), findsOneWidget); + verify(() => mockUserPreferencesService.themeType = ThemeType.dark).called(1); + }, + ); + + testWidgets( + 'Set systemDefault theme type and toggle platform brightness', + (tester) async { + when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light); + await pumpTestWidget( + tester, + builder: (context) => Column( + children: [ + ElevatedButton( + onPressed: () => UserPreferencesProvider.of(context).setThemeType(ThemeType.systemDefault), + child: Text('${UserPreferencesProvider.themeTypeOf(context)}'), + ), + Text('${Theme.of(context).colorScheme.brightness}') + ], + ), + ); + TestWidgetsFlutterBinding.instance.platformDispatcher.platformBrightnessTestValue = Brightness.dark; + expect(find.text("${ThemeType.light}"), findsOneWidget); + expect(find.text("${Brightness.light}"), findsOneWidget); + + await tester.tap(find.text("${ThemeType.light}")); + await tester.pumpAndSettle(); + expect(find.text("${ThemeType.systemDefault}"), findsOneWidget); + expect(find.text("${Brightness.dark}"), findsOneWidget); + verify(() => mockUserPreferencesService.themeType = ThemeType.systemDefault).called(1); + + TestWidgetsFlutterBinding.instance.platformDispatcher.platformBrightnessTestValue = Brightness.light; + await tester.pumpAndSettle(); + expect(find.text("${ThemeType.systemDefault}"), findsOneWidget); + expect(find.text("${Brightness.light}"), findsOneWidget); + }, + ); + + testWidgets( + 'Set primary color', + (tester) async { + when(() => mockUserPreferencesService.primaryColor).thenReturn(primaryColorsList[5]); + await pumpTestWidget( + tester, + builder: (context) => ElevatedButton( + onPressed: () => UserPreferencesProvider.of(context).setPrimaryColor(primaryColorsList[7]), + child: Text('${UserPreferencesProvider.themeOf(context).primaryColor}'), + ), + ); + expect(find.text("${primaryColorsList[5]}"), findsOneWidget); + + await tester.tap(find.text("${primaryColorsList[5]}")); + await tester.pumpAndSettle(); + expect(find.text("${primaryColorsList[7]}"), findsOneWidget); + verify(() => mockUserPreferencesService.primaryColor = primaryColorsList[7]).called(1); + }, + ); + + testWidgets( + 'Dynamic colors not available', + (tester) async { + when(() => mockUserPreferencesService.dynamicColor).thenReturn(true); + await pumpTestWidget( + tester, + builder: (context) => ElevatedButton( + onPressed: () => UserPreferencesProvider.of(context).enableDynamicColor(false), + child: Text('${UserPreferencesProvider.dynamicColorStateOf(context)}'), + ), + ); + await tester.pumpAndSettle(); + expect( + find.text("${DynamicColorState.unavailable}"), + findsOneWidget, + reason: + "Even though dynamic colors usage is enabled, the core palette can be unavailable. Therefore `DynamicColorState` is also unavailable.", + ); + }, + ); + + testWidgets( + 'Toggle dynamic color state', + (tester) async { + DynamicColorTestingUtils.setMockDynamicColors(corePalette: CorePalette.of(0xffffffff)); + when(() => mockUserPreferencesService.dynamicColor).thenReturn(true); + await pumpTestWidget( + tester, + builder: (context) => ElevatedButton( + onPressed: () => UserPreferencesProvider.of(context).enableDynamicColor(false), + child: Text('${UserPreferencesProvider.dynamicColorStateOf(context)}'), + ), + ); + await tester.pumpAndSettle(); + expect(find.text("${DynamicColorState.enabled}"), findsOneWidget); + + await tester.tap(find.text("${DynamicColorState.enabled}")); + await tester.pumpAndSettle(); + expect(find.text("${DynamicColorState.disabled}"), findsOneWidget); + verify(() => mockUserPreferencesService.dynamicColor = false).called(1); + }, + ); + }); +} + +class _Application extends StatelessWidget { + final WidgetBuilder builder; + + const _Application({required this.builder}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: UserPreferencesProvider.themeOf(context), + home: Scaffold(body: Center(child: Builder(builder: builder))), + ); + } +} diff --git a/test/screens/metering/components/shared/readings_container/equipment_profile_picker_test.dart b/test/screens/metering/components/shared/readings_container/equipment_profile_picker_test.dart new file mode 100644 index 0000000..6853168 --- /dev/null +++ b/test/screens/metering/components/shared/readings_container/equipment_profile_picker_test.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/equipment_profile_provider.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/equipment_profile_picker/widget_picker_equipment_profiles.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../../../../application_mock.dart'; +import 'utils.dart'; + +class _MockIAPStorageService extends Mock implements IAPStorageService {} + +void main() { + late final _MockIAPStorageService mockIAPStorageService; + + setUpAll(() { + mockIAPStorageService = _MockIAPStorageService(); + when(() => mockIAPStorageService.equipmentProfiles).thenReturn(_mockEquipmentProfiles); + when(() => mockIAPStorageService.selectedEquipmentProfileId).thenReturn(''); + }); + + Future pumpApplication(WidgetTester tester) async { + await tester.pumpWidget( + IAPProducts( + products: [ + IAPProduct( + storeId: IAPProductType.paidFeatures.storeId, + status: IAPProductStatus.purchased, + ) + ], + child: EquipmentProfileProvider( + storageService: mockIAPStorageService, + child: const WidgetTestApplicationMock( + child: Row(children: [Expanded(child: EquipmentProfilePicker())]), + ), + ), + ), + ); + await tester.pumpAndSettle(); + } + + testWidgets( + 'Check dialog icon and title consistency', + (tester) async { + await pumpApplication(tester); + expectReadingValueContainerText(S.current.equipmentProfile); + await tester.openAnimatedPicker(); + expect(find.byIcon(Icons.camera), findsOneWidget); + expectDialogPickerText(S.current.equipmentProfile); + }, + ); + + group( + 'Display selected value', + () { + testWidgets( + 'None', + (tester) async { + when(() => mockIAPStorageService.selectedEquipmentProfileId).thenReturn(''); + await pumpApplication(tester); + expectReadingValueContainerText(S.current.none); + await tester.openAnimatedPicker(); + expectRadioListTile(S.current.none, isSelected: true); + }, + ); + + testWidgets( + 'Praktica + Zenitar', + (tester) async { + when(() => mockIAPStorageService.selectedEquipmentProfileId).thenReturn(_mockEquipmentProfiles.first.id); + await pumpApplication(tester); + expectReadingValueContainerText(_mockEquipmentProfiles.first.name); + await tester.openAnimatedPicker(); + expectRadioListTile(_mockEquipmentProfiles.first.name, isSelected: true); + }, + ); + }, + ); +} + +final _mockEquipmentProfiles = [ + EquipmentProfile( + id: '1', + name: 'Praktica + Zenitar', + apertureValues: ApertureValue.values.sublist( + ApertureValue.values.indexOf(const ApertureValue(1.7, StopType.half)), + ApertureValue.values.indexOf(const ApertureValue(16, StopType.full)) + 1, + ), + ndValues: NdValue.values.sublist(0, 3), + shutterSpeedValues: ShutterSpeedValue.values.sublist( + ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(1000, true, StopType.full)), + ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(16, false, StopType.full)) + 1, + ), + isoValues: const [ + IsoValue(50, StopType.full), + IsoValue(100, StopType.full), + IsoValue(200, StopType.full), + IsoValue(250, StopType.third), + IsoValue(400, StopType.full), + IsoValue(500, StopType.third), + IsoValue(800, StopType.full), + IsoValue(1600, StopType.full), + IsoValue(3200, StopType.full), + ], + ), + const EquipmentProfile( + id: '2', + name: 'Praktica + Jupiter', + apertureValues: ApertureValue.values, + ndValues: NdValue.values, + shutterSpeedValues: ShutterSpeedValue.values, + isoValues: IsoValue.values, + ), +]; diff --git a/test/screens/metering/components/shared/readings_container/extreme_exposure_pairs_container_test.dart b/test/screens/metering/components/shared/readings_container/extreme_exposure_pairs_container_test.dart new file mode 100644 index 0000000..819a9eb --- /dev/null +++ b/test/screens/metering/components/shared/readings_container/extreme_exposure_pairs_container_test.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/data/models/exposure_pair.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/films_provider.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/extreme_exposure_pairs_container/widget_container_extreme_exposure_pairs.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; + +import '../../../../../application_mock.dart'; + +void main() { + testWidgets( + 'No exposure pairs', + (tester) async { + await tester.pumpApplication( + fastest: null, + slowest: null, + ); + + final pickerFinder = find.byType(ExtremeExposurePairsContainer); + expect(pickerFinder, findsOneWidget); + expect(find.descendant(of: pickerFinder, matching: find.text(S.current.fastestExposurePair)), findsOneWidget); + expect(find.descendant(of: pickerFinder, matching: find.text(S.current.slowestExposurePair)), findsOneWidget); + expect(find.descendant(of: pickerFinder, matching: find.text('-')), findsNWidgets(2)); + }, + ); + + testWidgets( + 'Has pairs', + (tester) async { + await tester.pumpApplication( + fastest: ExposurePair(ApertureValue.values.first, ShutterSpeedValue.values.first), + slowest: ExposurePair(ApertureValue.values.last, ShutterSpeedValue.values.last), + ); + + final pickerFinder = find.byType(ExtremeExposurePairsContainer); + expect(pickerFinder, findsOneWidget); + expect(find.descendant(of: pickerFinder, matching: find.text(S.current.fastestExposurePair)), findsOneWidget); + expect(find.descendant(of: pickerFinder, matching: find.text(S.current.slowestExposurePair)), findsOneWidget); + expect(find.descendant(of: pickerFinder, matching: find.text('f/1.0 - 1/2000')), findsOneWidget); + expect(find.descendant(of: pickerFinder, matching: find.text('f/45 - 16"')), findsOneWidget); + }, + ); +} + +extension WidgetTesterActions on WidgetTester { + Future pumpApplication({ + required ExposurePair? fastest, + required ExposurePair? slowest, + }) async { + await pumpWidget( + Films( + values: const [Film.other()], + filmsInUse: const [Film.other()], + selected: const Film.other(), + child: WidgetTestApplicationMock( + child: Row( + children: [ + Expanded( + child: ExtremeExposurePairsContainer( + fastest: fastest, + slowest: slowest, + ), + ), + ], + ), + ), + ), + ); + await pumpAndSettle(); + } +} diff --git a/test/screens/metering/components/shared/readings_container/film_picker_test.dart b/test/screens/metering/components/shared/readings_container/film_picker_test.dart new file mode 100644 index 0000000..19f4840 --- /dev/null +++ b/test/screens/metering/components/shared/readings_container/film_picker_test.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/films_provider.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/film_picker/widget_picker_film.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../../../../application_mock.dart'; +import 'utils.dart'; + +class _MockIAPStorageService extends Mock implements IAPStorageService {} + +void main() { + late final _MockIAPStorageService mockIAPStorageService; + + setUpAll(() { + mockIAPStorageService = _MockIAPStorageService(); + when(() => mockIAPStorageService.filmsInUse).thenReturn(_films); + }); + + Future pumpApplication(WidgetTester tester) async { + await tester.pumpWidget( + IAPProducts( + products: [ + IAPProduct( + storeId: IAPProductType.paidFeatures.storeId, + status: IAPProductStatus.purchased, + ) + ], + child: FilmsProvider( + storageService: mockIAPStorageService, + child: const WidgetTestApplicationMock( + child: Row( + children: [ + Expanded( + child: FilmPicker(selectedIso: IsoValue(400, StopType.full)), + ), + ], + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + } + + group('Film push/pull label', () { + testWidgets( + 'Film.other()', + (tester) async { + when(() => mockIAPStorageService.selectedFilm).thenReturn(const Film.other()); + await pumpApplication(tester); + expectReadingValueContainerText(S.current.film); + expectReadingValueContainerText(S.current.none); + }, + ); + + testWidgets( + 'Film with the same ISO', + (tester) async { + when(() => mockIAPStorageService.selectedFilm).thenReturn(_films[1]); + await pumpApplication(tester); + expectReadingValueContainerText(S.current.film); + expectReadingValueContainerText(_films[1].name); + }, + ); + + testWidgets( + 'Film with greater ISO', + (tester) async { + when(() => mockIAPStorageService.selectedFilm).thenReturn(_films[2]); + await pumpApplication(tester); + expectReadingValueContainerText(S.current.filmPull); + expectReadingValueContainerText(_films[2].name); + }, + ); + + testWidgets( + 'Film with lower ISO', + (tester) async { + when(() => mockIAPStorageService.selectedFilm).thenReturn(_films[0]); + await pumpApplication(tester); + expectReadingValueContainerText(S.current.filmPush); + expectReadingValueContainerText(_films[0].name); + }, + ); + }); + + testWidgets( + 'Film picker shows only films in use', + (tester) async { + when(() => mockIAPStorageService.selectedFilm).thenReturn(_films[0]); + await pumpApplication(tester); + await tester.openAnimatedPicker(); + expectRadioListTile(S.current.none, isSelected: true); + expectRadioListTile(_films[1].name); + expectRadioListTile(_films[2].name); + expectRadioListTile(_films[3].name); + }, + ); +} + +const _films = [ + Film('ISO 100 Film', 100), + Film('ISO 400 Film', 400), + Film('ISO 800 Film', 800), + Film('ISO 1600 Film', 1600), +]; diff --git a/test/screens/metering/components/shared/readings_container/iso_picker_test.dart b/test/screens/metering/components/shared/readings_container/iso_picker_test.dart new file mode 100644 index 0000000..9068df4 --- /dev/null +++ b/test/screens/metering/components/shared/readings_container/iso_picker_test.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/iso_picker/widget_picker_iso.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; + +import '../../../../../application_mock.dart'; +import 'utils.dart'; + +void main() { + Future pumpApplication( + WidgetTester tester, { + List values = IsoValue.values, + IsoValue selectedValue = const IsoValue(100, StopType.full), + }) async { + assert(values.contains(selectedValue)); + await tester.pumpWidget( + WidgetTestApplicationMock( + child: Row( + children: [ + Expanded( + child: IsoValuePicker( + selectedValue: selectedValue, + values: values, + onChanged: (_) {}, + ), + ), + ], + ), + ), + ); + await tester.pumpAndSettle(); + } + + testWidgets( + 'Check dialog icon and title consistency', + (tester) async { + await pumpApplication(tester); + expectReadingValueContainerText(S.current.iso); + await tester.openAnimatedPicker(); + expect(find.byIcon(Icons.iso), findsOneWidget); + expectDialogPickerText(S.current.iso); + expectDialogPickerText(S.current.filmSpeed); + }, + ); + + group( + 'Display selected value', + () { + testWidgets( + 'Any', + (tester) async { + await pumpApplication(tester); + expectReadingValueContainerText('100'); + await tester.openAnimatedPicker(); + expectRadioListTile('100', isSelected: true); + }, + ); + }, + ); +} diff --git a/test/screens/metering/components/shared/readings_container/nd_picker_test.dart b/test/screens/metering/components/shared/readings_container/nd_picker_test.dart new file mode 100644 index 0000000..3deb824 --- /dev/null +++ b/test/screens/metering/components/shared/readings_container/nd_picker_test.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/nd_picker/widget_picker_nd.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; + +import '../../../../../application_mock.dart'; +import 'utils.dart'; + +void main() { + Future pumpApplication( + WidgetTester tester, { + List values = NdValue.values, + NdValue selectedValue = const NdValue(0), + }) async { + assert(values.contains(selectedValue)); + await tester.pumpWidget( + WidgetTestApplicationMock( + child: Row( + children: [ + Expanded( + child: NdValuePicker( + selectedValue: selectedValue, + values: values, + onChanged: (_) {}, + ), + ), + ], + ), + ), + ); + await tester.pumpAndSettle(); + } + + testWidgets( + 'Check dialog icon and title consistency', + (tester) async { + await pumpApplication(tester); + expectReadingValueContainerText(S.current.nd); + await tester.openAnimatedPicker(); + expect(find.byIcon(Icons.filter_b_and_w), findsOneWidget); + expectDialogPickerText(S.current.nd); + expectDialogPickerText(S.current.ndFilterFactor); + }, + ); + + group( + 'Display selected value', + () { + testWidgets( + 'None', + (tester) async { + await pumpApplication(tester); + expectReadingValueContainerText(S.current.none); + await tester.openAnimatedPicker(); + expectRadioListTile(S.current.none, isSelected: true); + }, + ); + + testWidgets( + 'ND2', + (tester) async { + await pumpApplication(tester, selectedValue: const NdValue(2)); + expectReadingValueContainerText('2'); + await tester.openAnimatedPicker(); + expectRadioListTile('2', isSelected: true); + }, + ); + }, + ); +} diff --git a/test/screens/metering/components/shared/readings_container/shared/animated_dialog_test.dart b/test/screens/metering/components/shared/readings_container/shared/animated_dialog_test.dart new file mode 100644 index 0000000..d4b83c6 --- /dev/null +++ b/test/screens/metering/components/shared/readings_container/shared/animated_dialog_test.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/res/dimens.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart'; + +import '../../../../../../application_mock.dart'; +import '../utils.dart'; + +void main() { + group( + 'Open & close tests', + () { + testWidgets( + 'Open & close with select', + (tester) async { + await tester.pumpApplication(); + await tester.openAnimatedPicker>(); + expect(find.byType(DialogPicker), findsOneWidget); + await tester.tapSelectButton(); + expect(find.byType(DialogPicker), findsNothing); + }, + ); + + testWidgets( + 'Open & close with cancel', + (tester) async { + await tester.pumpApplication(); + await tester.openAnimatedPicker>(); + expect(find.byType(DialogPicker), findsOneWidget); + await tester.tapCancelButton(); + expect(find.byType(DialogPicker), findsNothing); + }, + ); + + testWidgets( + 'Open & close with tap outside', + (tester) async { + await tester.pumpApplication(); + await tester.openAnimatedPicker>(); + expect(find.byType(DialogPicker), findsOneWidget); + + /// tester taps the center of the found widget, + /// which results in tap on the dialog instead of the underlying barrier + /// therefore just tap at offset outside the dialog + await tester.longPressAt(const Offset(16, 16)); + await tester.pumpAndSettle(Dimens.durationML); + expect(find.byType(DialogPicker), findsNothing); + }, + ); + + testWidgets( + 'Open & close with back gesture', + (tester) async { + await tester.pumpApplication(); + await tester.openAnimatedPicker>(); + expect(find.byType(DialogPicker), findsOneWidget); + + //// https://github.com/flutter/flutter/blob/master/packages/flutter/test/widgets/router_test.dart#L970-L971 + //// final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute')); + //// await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) {}); + /// https://github.com/flutter/packages/blob/main/packages/animations/test/open_container_test.dart#L234 + (tester.state(find.byType(Navigator)) as NavigatorState).pop(); + await tester.pumpAndSettle(Dimens.durationML); + expect(find.byType(DialogPicker), findsNothing); + }, + ); + }, + ); +} + +extension WidgetTesterActions on WidgetTester { + Future pumpApplication() async { + await pumpWidget( + WidgetTestApplicationMock( + child: Row( + children: [ + Expanded( + child: AnimatedDialogPicker( + icon: Icons.iso, + title: '', + subtitle: '', + selectedValue: 0, + values: List.generate(10, (index) => index), + itemTitleBuilder: (_, value) => Text(value.toString()), + itemTrailingBuilder: (selected, value) => null, + onChanged: (_) {}, + closedChild: ReadingValueContainer.singleValue( + value: ReadingValue( + label: '', + value: 0.toString(), + ), + ), + ), + ), + ], + ), + ), + ); + await pumpAndSettle(); + } + + Future tapSelectButton() async { + final cancelButton = find.byWidgetPredicate( + (widget) => widget is TextButton && widget.child is Text && (widget.child as Text?)?.data == S.current.select, + ); + expect(cancelButton, findsOneWidget); + await tap(cancelButton); + await pumpAndSettle(Dimens.durationML); + } + + Future tapCancelButton() async { + final cancelButton = find.byWidgetPredicate( + (widget) => widget is TextButton && widget.child is Text && (widget.child as Text?)?.data == S.current.cancel, + ); + expect(cancelButton, findsOneWidget); + await tap(cancelButton); + await pumpAndSettle(Dimens.durationML); + } +} diff --git a/test/screens/metering/components/shared/readings_container/shared/dialog_picker_test.dart b/test/screens/metering/components/shared/readings_container/shared/dialog_picker_test.dart new file mode 100644 index 0000000..39c38ba --- /dev/null +++ b/test/screens/metering/components/shared/readings_container/shared/dialog_picker_test.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../../../../../application_mock.dart'; +import '../utils.dart'; + +class _ValueChanged { + void onChanged(T value) {} +} + +class _MockValueChanged extends Mock implements _ValueChanged {} + +void main() { + final functions = _MockValueChanged(); + + group( + 'onChanged', + () { + testWidgets( + 'other', + (tester) async { + await tester.pumpApplication(functions.onChanged); + await tester.openAnimatedPicker>(); + expect(find.byType(DialogPicker), findsOneWidget); + await tester.tapListTile(1); + await tester.tapSelectButton(); + verify(() => functions.onChanged(1)).called(1); + }, + ); + + testWidgets( + 'same', + (tester) async { + await tester.pumpApplication(functions.onChanged); + await tester.openAnimatedPicker>(); + expect(find.byType(DialogPicker), findsOneWidget); + await tester.tapListTile(0); + await tester.tapSelectButton(); + verify(() => functions.onChanged(0)).called(1); + }, + ); + }, + ); +} + +extension WidgetTesterActions on WidgetTester { + Future pumpApplication(ValueChanged onChanged) async { + await pumpWidget( + WidgetTestApplicationMock( + child: Row( + children: [ + Expanded( + child: AnimatedDialogPicker( + icon: Icons.iso, + title: '', + subtitle: '', + selectedValue: 0, + values: List.generate(10, (index) => index), + itemTitleBuilder: (_, value) => Text(value.toString()), + itemTrailingBuilder: (selected, value) => null, + onChanged: onChanged, + closedChild: ReadingValueContainer.singleValue( + value: ReadingValue( + label: '', + value: 0.toString(), + ), + ), + ), + ), + ], + ), + ), + ); + await pumpAndSettle(); + } + + Future tapListTile(int iso) async { + expect(find.descendant(of: find.byType(RadioListTile), matching: find.text('$iso')), findsOneWidget); + await tap(find.descendant(of: find.byType(RadioListTile), matching: find.text('$iso'))); + } + + Future tapSelectButton() async { + final cancelButton = find.byWidgetPredicate( + (widget) => widget is TextButton && widget.child is Text && (widget.child as Text?)?.data == S.current.select, + ); + expect(cancelButton, findsOneWidget); + await tap(cancelButton); + await pumpAndSettle(); + } +} diff --git a/test/screens/metering/components/shared/readings_container/utils.dart b/test/screens/metering/components/shared/readings_container/utils.dart new file mode 100644 index 0000000..0ffba27 --- /dev/null +++ b/test/screens/metering/components/shared/readings_container/utils.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/res/dimens.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart'; +import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart'; + +extension WidgetTesterActions on WidgetTester { + Future openAnimatedPicker() async { + await tap(find.byType(T)); + await pumpAndSettle(Dimens.durationL); + } +} + +void expectReadingValueContainerText(String text) => _expectTextDescendantOf(text); + +void expectDialogPickerText(String text) => _expectTextDescendantOf>(text); + +void _expectTextDescendantOf(String text) { + expect(find.descendant(of: find.byType(T), matching: find.text(text)), findsOneWidget); +} + +void expectRadioListTile(String text, {bool isSelected = false}) { + expect( + find.descendant(of: find.byType(RadioListTile), matching: find.text(text)), + findsOneWidget, + ); +} diff --git a/test_coverage.sh b/test_coverage.sh index c88e397..12014e8 100644 --- a/test_coverage.sh +++ b/test_coverage.sh @@ -1,4 +1,12 @@ flutter test --coverage +flutter test integration_test --flavor=dev --coverage + +file=test/coverage_helper_test.dart +echo "// Helper file to make coverage work for all dart files\n" > $file +echo "// ignore_for_file: unused_import, directives_ordering" >> $file +find lib '!' -path '*generated*/*' '!' -name '*.g.dart' '!' -name '*.part.dart' -name '*.dart' | cut -c4- | awk -v package=$1 '{printf "import '\''package:lightmeter%s%s'\'';\n", package, $1}' >> $file +echo "void main() {}" >> $file + lcov --remove coverage/lcov.info 'lib/generated/*' 'lib/l10n/*' -o coverage/new_lcov.info genhtml coverage/new_lcov.info -o coverage/html open coverage/html/index.html \ No newline at end of file diff --git a/test_driver/integration_driver.dart b/test_driver/integration_driver.dart new file mode 100644 index 0000000..3d79aac --- /dev/null +++ b/test_driver/integration_driver.dart @@ -0,0 +1,8 @@ +import 'package:integration_test/integration_test_driver_extended.dart'; + +import 'utils/grant_camera_permission.dart'; + +Future main() async { + await grantCameraPermission(); + await integrationDriver(); +} diff --git a/test_driver/screenshot_driver.dart b/test_driver/screenshot_driver.dart index 3b11ee3..4348142 100644 --- a/test_driver/screenshot_driver.dart +++ b/test_driver/screenshot_driver.dart @@ -1,42 +1,15 @@ -import 'dart:developer'; import 'dart:io'; import 'package:integration_test/integration_test_driver_extended.dart'; +import 'utils/grant_camera_permission.dart'; + Future main() async { - try { - final bool adbExists = Process.runSync('which', ['adb']).exitCode == 0; - if (!adbExists) { - log(r'This test needs ADB to exist on the $PATH. Skipping...'); - exit(0); - } - final deviceId = await Process.run('adb', ["-s", 'shell', 'devices']).then((value) { - if (value.stdout is String) { - return RegExp(r"(?:List of devices attached\n)([A-Z0-9]*)(?:\sdevice\n)") - .firstMatch(value.stdout as String)! - .group(1); - } - }); - if (deviceId == null) { - log('This test needs at least one device connected'); - exit(0); - } - await Process.run('adb', [ - "-s", - deviceId, // https://github.com/flutter/flutter/issues/86295#issuecomment-1192766368 - 'shell', - 'pm', - 'grant', - 'com.vodemn.lightmeter.dev', - 'android.permission.CAMERA' - ]); - await integrationDriver( - onScreenshot: (name, bytes, [args]) async { - final File image = await File('screenshots/$name.png').create(recursive: true); - image.writeAsBytesSync(bytes); - return true; - }, - ); - } catch (e) { - log('Error occured: $e'); - } + await grantCameraPermission(); + await integrationDriver( + onScreenshot: (name, bytes, [args]) async { + final File image = await File('screenshots/$name.png').create(recursive: true); + image.writeAsBytesSync(bytes); + return true; + }, + ); } diff --git a/test_driver/utils/grant_camera_permission.dart b/test_driver/utils/grant_camera_permission.dart new file mode 100644 index 0000000..c6f56d7 --- /dev/null +++ b/test_driver/utils/grant_camera_permission.dart @@ -0,0 +1,34 @@ +import 'dart:developer'; +import 'dart:io'; + +Future grantCameraPermission() async { + try { + final bool adbExists = Process.runSync('which', ['adb']).exitCode == 0; + if (!adbExists) { + log(r'This test needs ADB to exist on the $PATH. Skipping...'); + exit(0); + } + final deviceId = await Process.run('adb', ["-s", 'shell', 'devices']).then((value) { + if (value.stdout is String) { + return RegExp(r"(?:List of devices attached\n)([A-Z0-9]*)(?:\sdevice\n)") + .firstMatch(value.stdout as String)! + .group(1); + } + }); + if (deviceId == null) { + log('This test needs at least one device connected'); + exit(0); + } + await Process.run('adb', [ + "-s", + deviceId, // https://github.com/flutter/flutter/issues/86295#issuecomment-1192766368 + 'shell', + 'pm', + 'grant', + 'com.vodemn.lightmeter.dev', + 'android.permission.CAMERA' + ]); + } catch (e) { + log('Error occured: $e'); + } +} From a52efcd3413b09b373e5e6aadc16302d0692d62c Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Tue, 31 Oct 2023 18:42:25 +0100 Subject: [PATCH 02/13] ML-130 Integrate Firebase Remote Config (#132) * implemented `RemoteConfigService` * added alternative translations * typo * added `firebase_analytics` * dim paid features list tiles * log list tile tap instead of dialog * implemented `RemoteConfigProvider` * typo --- lib/application_wrapper.dart | 25 +++-- lib/data/analytics/analytics.dart | 34 ++++++ .../api/analytics_api_interface.dart | 8 ++ .../analytics/api/analytics_firebase.dart | 26 +++++ .../analytics/entity/analytics_event.dart | 3 + lib/data/models/feature.dart | 5 + lib/data/remote_config_service.dart | 81 ++++++++++++++ lib/l10n/intl_en.arb | 6 +- lib/l10n/intl_fr.arb | 4 + lib/l10n/intl_ru.arb | 4 + lib/l10n/intl_zh.arb | 4 + lib/providers/remote_config_provider.dart | 78 +++++++++++++ lib/providers/services_provider.dart | 3 + .../buy_pro/widget_list_tile_buy_pro.dart | 24 +++- ...idget_settings_section_lightmeter_pro.dart | 6 +- .../iap_list_tile/widget_list_tile_iap.dart | 27 ++--- .../components/utils/show_buy_pro_dialog.dart | 13 ++- pubspec.yaml | 10 +- .../remote_config_provider_test.dart | 104 ++++++++++++++++++ 19 files changed, 425 insertions(+), 40 deletions(-) create mode 100644 lib/data/analytics/analytics.dart create mode 100644 lib/data/analytics/api/analytics_api_interface.dart create mode 100644 lib/data/analytics/api/analytics_firebase.dart create mode 100644 lib/data/analytics/entity/analytics_event.dart create mode 100644 lib/data/models/feature.dart create mode 100644 lib/data/remote_config_service.dart create mode 100644 lib/providers/remote_config_provider.dart create mode 100644 test/providers/remote_config_provider_test.dart diff --git a/lib/application_wrapper.dart b/lib/application_wrapper.dart index d2975d6..d28fbcf 100644 --- a/lib/application_wrapper.dart +++ b/lib/application_wrapper.dart @@ -1,13 +1,17 @@ import 'package:flutter/material.dart'; +import 'package:lightmeter/data/analytics/analytics.dart'; +import 'package:lightmeter/data/analytics/api/analytics_firebase.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/remote_config_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/films_provider.dart'; +import 'package:lightmeter/providers/remote_config_provider.dart'; import 'package:lightmeter/providers/services_provider.dart'; import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; @@ -23,9 +27,10 @@ class ApplicationWrapper extends StatelessWidget { @override Widget build(BuildContext context) { return FutureBuilder( - future: Future.wait([ + future: Future.wait([ SharedPreferences.getInstance(), const LightSensorService(LocalPlatform()).hasSensor(), + const RemoteConfigService().activeAndFetchFeatures(), ]), builder: (_, snapshot) { if (snapshot.data != null) { @@ -33,6 +38,7 @@ class ApplicationWrapper extends StatelessWidget { final userPreferencesService = UserPreferencesService(snapshot.data![0] as SharedPreferences); final hasLightSensor = snapshot.data![1] as bool; return ServicesProvider( + analytics: const LightmeterAnalytics(api: LightmeterAnalyticsFirebase()), caffeineService: const CaffeineService(), environment: env.copyWith(hasLightSensor: hasLightSensor), hapticsService: const HapticsService(), @@ -40,14 +46,17 @@ class ApplicationWrapper extends StatelessWidget { permissionsService: const PermissionsService(), userPreferencesService: userPreferencesService, volumeEventsService: const VolumeEventsService(LocalPlatform()), - child: EquipmentProfileProvider( - storageService: iapService, - child: FilmsProvider( + child: RemoteConfigProvider( + remoteConfigService: const RemoteConfigService(), + child: EquipmentProfileProvider( storageService: iapService, - child: UserPreferencesProvider( - hasLightSensor: hasLightSensor, - userPreferencesService: userPreferencesService, - child: child, + child: FilmsProvider( + storageService: iapService, + child: UserPreferencesProvider( + hasLightSensor: hasLightSensor, + userPreferencesService: userPreferencesService, + child: child, + ), ), ), ), diff --git a/lib/data/analytics/analytics.dart b/lib/data/analytics/analytics.dart new file mode 100644 index 0000000..1bd6496 --- /dev/null +++ b/lib/data/analytics/analytics.dart @@ -0,0 +1,34 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:flutter/foundation.dart'; +import 'package:lightmeter/data/analytics/api/analytics_api_interface.dart'; +import 'package:lightmeter/data/analytics/entity/analytics_event.dart'; + +class LightmeterAnalytics { + final ILightmeterAnalyticsApi _api; + + const LightmeterAnalytics({required ILightmeterAnalyticsApi api}) : _api = api; + + Future logEvent( + LightmeterAnalyticsEvent event, { + Map? parameters, + }) async { + if (kDebugMode) { + log(' logEvent: ${event.name} / $parameters'); + return; + } + + return _api.logEvent( + event: event, + parameters: parameters, + ); + } + + Future logUnlockProFeatures(String listTileTitle) async { + return logEvent( + LightmeterAnalyticsEvent.unlockProFeatures, + parameters: {"listTileTitle": listTileTitle}, + ); + } +} diff --git a/lib/data/analytics/api/analytics_api_interface.dart b/lib/data/analytics/api/analytics_api_interface.dart new file mode 100644 index 0000000..1aa007f --- /dev/null +++ b/lib/data/analytics/api/analytics_api_interface.dart @@ -0,0 +1,8 @@ +import 'package:lightmeter/data/analytics/entity/analytics_event.dart'; + +abstract class ILightmeterAnalyticsApi { + Future logEvent({ + required LightmeterAnalyticsEvent event, + Map? parameters, + }); +} diff --git a/lib/data/analytics/api/analytics_firebase.dart b/lib/data/analytics/api/analytics_firebase.dart new file mode 100644 index 0000000..fb11d02 --- /dev/null +++ b/lib/data/analytics/api/analytics_firebase.dart @@ -0,0 +1,26 @@ +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/foundation.dart'; +import 'package:lightmeter/data/analytics/api/analytics_api_interface.dart'; +import 'package:lightmeter/data/analytics/entity/analytics_event.dart'; + +class LightmeterAnalyticsFirebase implements ILightmeterAnalyticsApi { + const LightmeterAnalyticsFirebase(); + + @override + Future logEvent({ + required LightmeterAnalyticsEvent event, + Map? parameters, + }) async { + try { + await FirebaseAnalytics.instance.logEvent( + name: event.name, + parameters: parameters, + ); + } on FirebaseException catch (e) { + debugPrint('Firebase Analytics Exception: $e'); + } catch (e) { + debugPrint(e.toString()); + } + } +} diff --git a/lib/data/analytics/entity/analytics_event.dart b/lib/data/analytics/entity/analytics_event.dart new file mode 100644 index 0000000..8275869 --- /dev/null +++ b/lib/data/analytics/entity/analytics_event.dart @@ -0,0 +1,3 @@ +enum LightmeterAnalyticsEvent { + unlockProFeatures, +} diff --git a/lib/data/models/feature.dart b/lib/data/models/feature.dart new file mode 100644 index 0000000..e4dc30e --- /dev/null +++ b/lib/data/models/feature.dart @@ -0,0 +1,5 @@ +enum Feature { unlockProFeaturesText } + +const featuresDefaultValues = { + Feature.unlockProFeaturesText: false, +}; diff --git a/lib/data/remote_config_service.dart b/lib/data/remote_config_service.dart new file mode 100644 index 0000000..9fc83fc --- /dev/null +++ b/lib/data/remote_config_service.dart @@ -0,0 +1,81 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:flutter/foundation.dart'; +import 'package:lightmeter/data/models/feature.dart'; + +class RemoteConfigService { + const RemoteConfigService(); + + Future activeAndFetchFeatures() async { + final FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instance; + const cacheStaleDuration = kDebugMode ? Duration(minutes: 1) : Duration(hours: 12); + + try { + await remoteConfig.setConfigSettings( + RemoteConfigSettings( + fetchTimeout: const Duration(seconds: 15), + minimumFetchInterval: cacheStaleDuration, + ), + ); + await remoteConfig.setDefaults(featuresDefaultValues.map((key, value) => MapEntry(key.name, value))); + await remoteConfig.activate(); + await remoteConfig.ensureInitialized(); + unawaited(remoteConfig.fetch()); + + log('Firebase remote config initialized successfully'); + } on FirebaseException catch (e) { + _logError('Firebase exception during Firebase Remote Config initialization: $e'); + } on Exception catch (e) { + _logError('Error during Firebase Remote Config initialization: $e'); + } + } + + dynamic getValue(Feature feature) => FirebaseRemoteConfig.instance.getValue(feature.name).toValue(feature); + + Map getAll() { + final Map result = {}; + for (final value in FirebaseRemoteConfig.instance.getAll().entries) { + try { + final feature = Feature.values.firstWhere((f) => f.name == value.key); + result[feature] = value.value.toValue(feature); + } catch (e) { + log(e.toString()); + } + } + return result; + } + + Stream> onConfigUpdated() => FirebaseRemoteConfig.instance.onConfigUpdated.asyncMap( + (event) async { + await FirebaseRemoteConfig.instance.activate(); + final Set updatedFeatures = {}; + for (final key in event.updatedKeys) { + try { + updatedFeatures.add(Feature.values.firstWhere((element) => element.name == key)); + } catch (e) { + log(e.toString()); + } + } + return updatedFeatures; + }, + ); + + bool isEnabled(Feature feature) => FirebaseRemoteConfig.instance.getBool(feature.name); + + void _logError(dynamic throwable, {StackTrace? stackTrace}) { + FirebaseCrashlytics.instance.recordError(throwable, stackTrace); + } +} + +extension on RemoteConfigValue { + dynamic toValue(Feature feature) { + switch (feature) { + case Feature.unlockProFeaturesText: + return asBool(); + } + } +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 2f2fa27..57e91d2 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -92,10 +92,14 @@ } } }, - "buyLightmeterPro": "Buy Lightmeter Pro", "lightmeterPro": "Lightmeter Pro", + "buyLightmeterPro": "Buy Lightmeter Pro", "lightmeterProDescription": "Unlocks extra features, such as equipment profiles containing filters for aperture, shutter speed, and more; and a list of films with compensation for what's known as reciprocity failure.\n\nThe source code of Lightmeter is available on GitHub. You are welcome to compile it yourself. However, if you want to support the development and receive new features and updates, consider purchasing Lightmeter Pro.", "buy": "Buy", + "proFeatures": "Pro features", + "unlockProFeatures": "Unlock Pro features", + "unlockProFeaturesDescription": "Unlock professional features, such as equipment profiles containing filters for aperture, shutter speed, and more; and a list of films with compensation for what's known as reciprocity failure.\n\nBy unlocking Pro features you support the development and make it possible to add new features to the app.", + "unlock": "Unlock", "tooltipAdd": "Add", "tooltipClose": "Close", "tooltipExpand": "Expand", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 895b8e2..b1cd7fb 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -96,6 +96,10 @@ "lightmeterPro": "Lightmeter Pro", "lightmeterProDescription": "Déverrouille des fonctionnalités supplémentaires, telles que des profils d'équipement contenant des filtres pour l'ouverture, la vitesse d'obturation et plus encore, ainsi qu'une liste de films avec une compensation pour ce que l'on appelle l'échec de réciprocité.\n\nLe code source du Lightmeter est disponible sur GitHub. Vous pouvez le compiler vous-même. Cependant, si vous souhaitez soutenir le développement et recevoir de nouvelles fonctionnalités et mises à jour, envisagez d'acheter Lightmeter Pro.", "buy": "Acheter", + "proFeatures": "Fonctionnalités professionnelles", + "unlockProFeatures": "Déverrouiller les fonctionnalités professionnelles", + "unlockProFeaturesDescription": "Déverrouillez des fonctions professionnelles, telles que des profils d'équipement contenant des filtres pour l'ouverture, la vitesse d'obturation et plus encore, ainsi qu'une liste de films avec compensation pour ce que l'on appelle l'échec de réciprocité.\n\nEn débloquant les fonctionnalités Pro, vous soutenez le développement et permettez d'ajouter de nouvelles fonctionnalités à l'application.", + "unlock": "Déverrouiller", "tooltipAdd": "Ajouter", "tooltipClose": "Fermer", "tooltipExpand": "Élargir", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index f4c7b0b..a18cc9c 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -96,6 +96,10 @@ "lightmeterPro": "Lightmeter Pro", "lightmeterProDescription": "Даёт доступ к таким функциям как профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений, а также набору пленок с компенсацией эффекта Шварцшильда.\n\nИсходный код Lightmeter доступен на GitHub. Вы можете собрать его самостоятельно. Однако если вы хотите поддержать разработку и получать новые функции и обновления, то приобретите Lightmeter Pro.", "buy": "Купить", + "proFeatures": "Профессиональные настройки", + "unlockProFeatures": "Разблокировать профессиональные настройки", + "unlockProFeaturesDescription": "Вы можете разблокировать профессиональные настройки, такие как профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений, а также набору пленок с компенсацией эффекта Шварцшильда.\n\nПолучая доступ к профессиональным настройкам, вы поддерживаете разработку и делаете возможным появление новых функций в приложении.", + "unlock": "Разблокировать", "tooltipAdd": "Добавить", "tooltipClose": "Закрыть", "tooltipExpand": "Развернуть", diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 0701781..5788ea9 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -96,6 +96,10 @@ "lightmeterPro": "Lightmeter Pro", "lightmeterProDescription": "购买以解锁额外功能。例如包含光圈、快门速度等参数的配置文件;以及一个胶卷预设列表来提供倒易率失效时的曝光补偿。\n\n您可以在 GitHub 上获取 Lightmeter 的源代码,欢迎自行编译。不过,如果您想支持开发并获得新功能和更新,请考虑购买 Lightmeter Pro。", "buy": "购买", + "proFeatures": "专业功能", + "unlockProFeatures": "解锁专业功能", + "unlockProFeaturesDescription": "解锁专业功能。例如包含光圈、快门速度等参数的配置文件;以及一个胶卷预设列表来提供倒易率失效时的曝光补偿。\n\n通过解锁专业版功能,您可以支持开发工作,帮助为应用程序添加新功能。", + "unlock": "解锁", "tooltipAdd": "添加", "tooltipClose": "关闭", "tooltipExpand": "展开", diff --git a/lib/providers/remote_config_provider.dart b/lib/providers/remote_config_provider.dart new file mode 100644 index 0000000..9736e1d --- /dev/null +++ b/lib/providers/remote_config_provider.dart @@ -0,0 +1,78 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:lightmeter/data/models/feature.dart'; +import 'package:lightmeter/data/remote_config_service.dart'; + +class RemoteConfigProvider extends StatefulWidget { + final RemoteConfigService remoteConfigService; + final Widget child; + + const RemoteConfigProvider({ + required this.remoteConfigService, + required this.child, + super.key, + }); + + @override + State createState() => RemoteConfigProviderState(); +} + +class RemoteConfigProviderState extends State { + late final Map _config = widget.remoteConfigService.getAll(); + late final StreamSubscription> _updatesSubscription; + + @override + void initState() { + super.initState(); + _updatesSubscription = widget.remoteConfigService.onConfigUpdated().listen(_updateFeatures); + } + + @override + void dispose() { + _updatesSubscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return RemoteConfig( + config: _config, + child: widget.child, + ); + } + + void _updateFeatures(Set updatedFeatures) { + for (final feature in updatedFeatures) { + _config[feature] = widget.remoteConfigService.getValue(feature); + } + setState(() {}); + } +} + +class RemoteConfig extends InheritedModel { + final Map _config; + + const RemoteConfig({ + super.key, + required Map config, + required super.child, + }) : _config = config; + + static bool isEnabled(BuildContext context, Feature feature) { + return InheritedModel.inheritFrom(context)!._config[feature] as bool; + } + + @override + bool updateShouldNotify(RemoteConfig oldWidget) => true; + + @override + bool updateShouldNotifyDependent(RemoteConfig oldWidget, Set features) { + for (final feature in features) { + if (oldWidget._config[feature] != _config[feature]) { + return true; + } + } + return false; + } +} diff --git a/lib/providers/services_provider.dart b/lib/providers/services_provider.dart index e65aa96..36f5674 100644 --- a/lib/providers/services_provider.dart +++ b/lib/providers/services_provider.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:lightmeter/data/analytics/analytics.dart'; import 'package:lightmeter/data/caffeine_service.dart'; import 'package:lightmeter/data/haptics_service.dart'; import 'package:lightmeter/data/light_sensor_service.dart'; @@ -9,6 +10,7 @@ import 'package:lightmeter/environment.dart'; // coverage:ignore-start class ServicesProvider extends InheritedWidget { + final LightmeterAnalytics analytics; final CaffeineService caffeineService; final Environment environment; final HapticsService hapticsService; @@ -18,6 +20,7 @@ class ServicesProvider extends InheritedWidget { final VolumeEventsService volumeEventsService; const ServicesProvider({ + required this.analytics, required this.caffeineService, required this.environment, required this.hapticsService, diff --git a/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart b/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart index 11e8a4f..5f8adcd 100644 --- a/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart +++ b/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart @@ -1,20 +1,36 @@ import 'package:flutter/material.dart'; +import 'package:lightmeter/data/models/feature.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart'; +import 'package:lightmeter/providers/remote_config_provider.dart'; +import 'package:lightmeter/providers/services_provider.dart'; +import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/settings/components/utils/show_buy_pro_dialog.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; class BuyProListTile extends StatelessWidget { const BuyProListTile({super.key}); @override Widget build(BuildContext context) { - return IAPListTile( + final unlockFeaturesEnabled = RemoteConfig.isEnabled(context, Feature.unlockProFeaturesText); + final status = IAPProducts.productOf(context, IAPProductType.paidFeatures)?.status; + final isPending = status == IAPProductStatus.purchased || status == null; + return ListTile( leading: const Icon(Icons.star), - title: Text(S.of(context).buyLightmeterPro), + title: Text(unlockFeaturesEnabled ? S.of(context).unlockProFeatures : S.of(context).buyLightmeterPro), onTap: () { showBuyProDialog(context); + ServicesProvider.of(context) + .analytics + .logUnlockProFeatures(unlockFeaturesEnabled ? 'Unlock Pro features' : 'Buy Lightmeter Pro'); }, - showPendingTrailing: true, + trailing: isPending + ? const SizedBox( + height: Dimens.grid24, + width: Dimens.grid24, + child: CircularProgressIndicator(), + ) + : null, ); } } diff --git a/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart b/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart index c060dc1..7050ae2 100644 --- a/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart +++ b/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:lightmeter/data/models/feature.dart'; import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/remote_config_provider.dart'; import 'package:lightmeter/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart'; import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart'; @@ -9,7 +11,9 @@ class LightmeterProSettingsSection extends StatelessWidget { @override Widget build(BuildContext context) { return SettingsSection( - title: S.of(context).lightmeterPro, + title: RemoteConfig.isEnabled(context, Feature.unlockProFeaturesText) + ? S.of(context).proFeatures + : S.of(context).lightmeterPro, children: const [BuyProListTile()], ); } diff --git a/lib/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart b/lib/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart index cf65ade..a2b980f 100644 --- a/lib/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart +++ b/lib/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/res/dimens.dart'; -import 'package:lightmeter/screens/settings/components/utils/show_buy_pro_dialog.dart'; import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; /// Depends on the product status and replaces [onTap] with purchase callback @@ -23,24 +22,14 @@ class IAPListTile extends StatelessWidget { @override Widget build(BuildContext context) { - final status = IAPProducts.productOf(context, IAPProductType.paidFeatures)?.status; - final isPending = status == IAPProductStatus.purchased || status == null; - return ListTile( - leading: leading, - title: title, - onTap: switch (status) { - IAPProductStatus.purchasable => () => showBuyProDialog(context), - IAPProductStatus.pending => null, - IAPProductStatus.purchased => onTap, - null => null, - }, - trailing: showPendingTrailing && isPending - ? const SizedBox( - height: Dimens.grid24, - width: Dimens.grid24, - child: CircularProgressIndicator(), - ) - : null, + final isPurchased = IAPProducts.isPurchased(context, IAPProductType.paidFeatures); + return Opacity( + opacity: isPurchased ? Dimens.enabledOpacity : Dimens.disabledOpacity, + child: ListTile( + leading: leading, + title: title, + onTap: isPurchased ? onTap : null, + ), ); } } diff --git a/lib/screens/settings/components/utils/show_buy_pro_dialog.dart b/lib/screens/settings/components/utils/show_buy_pro_dialog.dart index 0333570..50edbd1 100644 --- a/lib/screens/settings/components/utils/show_buy_pro_dialog.dart +++ b/lib/screens/settings/components/utils/show_buy_pro_dialog.dart @@ -1,17 +1,24 @@ import 'package:flutter/material.dart'; +import 'package:lightmeter/data/models/feature.dart'; import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/remote_config_provider.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; Future showBuyProDialog(BuildContext context) { + final unlockFeaturesEnabled = RemoteConfig.isEnabled(context, Feature.unlockProFeaturesText); return showDialog( context: context, builder: (_) => AlertDialog( icon: const Icon(Icons.star), titlePadding: Dimens.dialogIconTitlePadding, - title: Text(S.of(context).lightmeterPro), + title: Text(unlockFeaturesEnabled ? S.of(context).proFeatures : S.of(context).lightmeterPro), contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL), - content: SingleChildScrollView(child: Text(S.of(context).lightmeterProDescription)), + content: SingleChildScrollView( + child: Text( + unlockFeaturesEnabled ? S.of(context).unlockProFeaturesDescription : S.of(context).lightmeterProDescription, + ), + ), actionsPadding: Dimens.dialogActionsPadding, actions: [ TextButton( @@ -23,7 +30,7 @@ Future showBuyProDialog(BuildContext context) { Navigator.of(context).pop(); IAPProductsProvider.of(context).buy(IAPProductType.paidFeatures); }, - child: Text(S.of(context).buy), + child: Text(unlockFeaturesEnabled ? S.of(context).unlock : S.of(context).buy), ), ], ), diff --git a/pubspec.yaml b/pubspec.yaml index 215f802..a7eb3a2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,8 +13,10 @@ dependencies: clipboard: 0.1.3 dynamic_color: 1.6.6 exif: 3.1.4 - firebase_core: 2.14.0 - firebase_crashlytics: 3.3.3 + firebase_analytics: 10.6.2 + firebase_core: 2.20.0 + firebase_crashlytics: 3.4.2 + firebase_remote_config: 4.3.2 flutter: sdk: flutter flutter_bloc: 8.1.3 @@ -26,7 +28,7 @@ dependencies: m3_lightmeter_iap: git: url: "https://github.com/vodemn/m3_lightmeter_iap" - ref: v0.5.0 + ref: v0.6.2 m3_lightmeter_resources: git: url: "https://github.com/vodemn/m3_lightmeter_resources" @@ -56,7 +58,7 @@ dev_dependencies: flutter: uses-material-design: true - assets: + assets: - assets/camera_stub_image.jpg flutter_intl: diff --git a/test/providers/remote_config_provider_test.dart b/test/providers/remote_config_provider_test.dart new file mode 100644 index 0000000..aa6815d --- /dev/null +++ b/test/providers/remote_config_provider_test.dart @@ -0,0 +1,104 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/data/models/feature.dart'; +import 'package:lightmeter/data/remote_config_service.dart'; +import 'package:lightmeter/providers/remote_config_provider.dart'; +import 'package:mocktail/mocktail.dart'; + +class _MockRemoteConfigService extends Mock implements RemoteConfigService {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + late _MockRemoteConfigService mockRemoteConfigService; + + setUpAll(() { + mockRemoteConfigService = _MockRemoteConfigService(); + }); + + setUp(() { + when(() => mockRemoteConfigService.getValue(Feature.unlockProFeaturesText)).thenReturn(false); + when(() => mockRemoteConfigService.getAll()).thenReturn({Feature.unlockProFeaturesText: false}); + }); + + tearDown(() { + reset(mockRemoteConfigService); + }); + + Future pumpTestWidget(WidgetTester tester) async { + await tester.pumpWidget( + RemoteConfigProvider( + remoteConfigService: mockRemoteConfigService, + child: const _Application(), + ), + ); + } + + testWidgets( + 'RemoteConfigProvider init', + (tester) async { + when(() => mockRemoteConfigService.onConfigUpdated()).thenAnswer((_) => const Stream.empty()); + + await pumpTestWidget(tester); + expect(find.text('unlockProFeaturesText: false'), findsOneWidget); + }, + ); + + testWidgets( + 'RemoteConfigProvider updates stream', + (tester) async { + final StreamController> remoteConfigUpdateController = StreamController>(); + when(() => mockRemoteConfigService.onConfigUpdated()).thenAnswer((_) => remoteConfigUpdateController.stream); + + await pumpTestWidget(tester); + expect(find.text('unlockProFeaturesText: false'), findsOneWidget); + + when(() => mockRemoteConfigService.getValue(Feature.unlockProFeaturesText)).thenReturn(true); + remoteConfigUpdateController.add({Feature.unlockProFeaturesText}); + await tester.pumpAndSettle(); + expect(find.text('unlockProFeaturesText: true'), findsOneWidget); + + await remoteConfigUpdateController.close(); + }, + ); + + test('RemoteConfig.updateShouldNotifyDependent', () { + const config = RemoteConfig(config: {Feature.unlockProFeaturesText: false}, child: SizedBox()); + expect( + config.updateShouldNotifyDependent(config, {}), + false, + ); + expect( + config.updateShouldNotifyDependent( + const RemoteConfig(config: {Feature.unlockProFeaturesText: false}, child: SizedBox()), + {Feature.unlockProFeaturesText}, + ), + false, + ); + expect( + config.updateShouldNotifyDependent( + const RemoteConfig(config: {Feature.unlockProFeaturesText: true}, child: SizedBox()), + {Feature.unlockProFeaturesText}, + ), + true, + ); + }); +} + +class _Application extends StatelessWidget { + const _Application(); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: Text( + "${Feature.unlockProFeaturesText.name}: ${RemoteConfig.isEnabled(context, Feature.unlockProFeaturesText)}", + ), + ), + ), + ); + } +} From d36db97959ad8a337166ff53ff9ddfb34cee9711 Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Tue, 31 Oct 2023 22:32:02 +0100 Subject: [PATCH 03/13] Updated IAP version to fix network issue https://github.com/flutter/flutter/issues/135540 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index a7eb3a2..2d7730e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,7 +28,7 @@ dependencies: m3_lightmeter_iap: git: url: "https://github.com/vodemn/m3_lightmeter_iap" - ref: v0.6.2 + ref: v0.6.3 m3_lightmeter_resources: git: url: "https://github.com/vodemn/m3_lightmeter_resources" From 37a3b79f04f4323ea26b9c3e7d0821b4daca5354 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 1 Nov 2023 10:16:41 +0000 Subject: [PATCH 04/13] Version bump --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2d7730e..6fd97bc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: lightmeter description: Lightmeter app inspired by Material 3 design system. publish_to: "none" -version: 0.15.1+42 +version: 0.15.2+43 environment: sdk: ">=3.0.0 <4.0.0" From 3bb3f12641398dc9274cc6f1fc9374f786ed1fce Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Thu, 2 Nov 2023 17:40:47 +0100 Subject: [PATCH 05/13] ML-62 Utils tests (#133) * 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 * AnimatedDialog picker standalone tests * Moved Animated dialog picker to widget tests * `ExtremeExposurePairsContainer` widget test * dialog picker test * Match extreme exposure pairs & pairs list edge values * `FilmPicker` widget tests * fixed animated dialog picker tests * add not hit files to coverage percentage * Moved `EquipmentProfileProvider` & `FilmsProvider` to the main repo * Synced _iap_ stub with repo * `FilmsProvider` tests * `EquipmentProfileProvider` tests * Pass `availableFilms` to `FilmsProvider` * `FilmPicker` tests * removed unnecessary imports * Metering layout features tests * split integration tests by screens * Films in use test * mock light meter lux stream * removed mockito mocks for integration tests From no on these are the only mocks in use: - Mock shared prefs initial values - Mock platform responses (camera/light sensor) * set sharedprefs mock without redundant group * unified granting camera permission on Android * fixed metering screen tests * extracted common values * `FilmPicker` integration tests * fixed light sensor platform mocks * wip * removed integration tests for now * moved screenshots generator to screenshots folder * typo * removed `MockIAPProductsProvider` * implemented platform mocks for unit tests * data/models/ 100% coverage * `IsoValuePicker` tests * `EquipmentProfileProvider` tests * extended PR check timeout * typo * added storage action verification for `FilmsProvider` tests * `UserPreferencesProvider` tests * Update README.md * added //coverage:ignore to `ServicesProvider` * typo * typo * `toStringSignedAsFixed` tests * `SelectableInheritedModel` tests * removed unused `TextLineHeight` util * `VolumeKeysNotifier` tests * import * `EquipmentProfileListener` tests * typo * split `EquipmentProfileListener` tests * `showBuyProDialog` tests * added `maybeOf` getter for iap stub --- .../src/providers/iap_products_provider.dart | 9 +- lib/data/volume_events_service.dart | 8 +- lib/providers/equipment_profile_provider.dart | 15 +- lib/screens/metering/bloc_metering.dart | 2 +- .../widget_slider_exposure_offset.dart | 2 +- lib/screens/metering/flow_metering.dart | 2 +- lib/screens/metering/screen_metering.dart | 2 +- ....dart => listener_equipment_profiles.dart} | 0 .../notifier_volume_keys.dart | 8 +- .../buy_pro/widget_list_tile_buy_pro.dart | 2 +- .../screen_equipment_profile.dart | 2 +- .../utils/show_buy_pro_dialog.dart | 2 +- lib/utils/text_line_height.dart | 5 - lib/utils/to_string_signed.dart | 11 +- pubspec.yaml | 2 +- test/function_mock.dart | 7 + .../interactors/metering_interactor_test.dart | 2 +- .../equipment_profile_provider_test.dart | 2 +- test/screens/metering/bloc_metering_test.dart | 2 +- .../shared/dialog_picker_test.dart | 9 +- .../listener_equipment_profiles_test.dart | 138 ++++++++++++++++++ .../utils/notifier_volume_keys_test.dart | 46 ++++++ .../utils/show_buy_pro_dialog_test.dart | 61 ++++++++ test/utils/selectable_provider_test.dart | 76 ++++++++++ test/utils/to_string_signed_test.dart | 16 ++ 25 files changed, 379 insertions(+), 52 deletions(-) rename lib/screens/metering/utils/{listsner_equipment_profiles.dart => listener_equipment_profiles.dart} (100%) rename lib/screens/metering/{components/shared/volume_keys_notifier => utils}/notifier_volume_keys.dart (82%) rename lib/screens/settings/{components => }/utils/show_buy_pro_dialog.dart (94%) delete mode 100644 lib/utils/text_line_height.dart create mode 100644 test/function_mock.dart create mode 100644 test/screens/metering/utils/listener_equipment_profiles_test.dart create mode 100644 test/screens/metering/utils/notifier_volume_keys_test.dart create mode 100644 test/screens/settings/utils/show_buy_pro_dialog_test.dart create mode 100644 test/utils/selectable_provider_test.dart create mode 100644 test/utils/to_string_signed_test.dart diff --git a/iap/lib/src/providers/iap_products_provider.dart b/iap/lib/src/providers/iap_products_provider.dart index 4895fdf..9d381ae 100644 --- a/iap/lib/src/providers/iap_products_provider.dart +++ b/iap/lib/src/providers/iap_products_provider.dart @@ -6,8 +6,10 @@ class IAPProductsProvider extends StatefulWidget { const IAPProductsProvider({required this.child, super.key}); - static IAPProductsProviderState of(BuildContext context) { - return context.findAncestorStateOfType()!; + static IAPProductsProviderState of(BuildContext context) => IAPProductsProvider.maybeOf(context)!; + + static IAPProductsProviderState? maybeOf(BuildContext context) { + return context.findAncestorStateOfType(); } @override @@ -54,8 +56,7 @@ class IAPProducts extends InheritedModel { bool updateShouldNotify(IAPProducts oldWidget) => false; @override - bool updateShouldNotifyDependent(IAPProducts oldWidget, Set dependencies) => - false; + bool updateShouldNotifyDependent(IAPProducts oldWidget, Set dependencies) => false; IAPProduct? _findProduct(IAPProductType type) { try { diff --git a/lib/data/volume_events_service.dart b/lib/data/volume_events_service.dart index d57936a..360de75 100644 --- a/lib/data/volume_events_service.dart +++ b/lib/data/volume_events_service.dart @@ -3,7 +3,7 @@ import 'package:flutter/services.dart'; import 'package:platform/platform.dart'; class VolumeEventsService { - final LocalPlatform localPlatform; + final LocalPlatform _localPlatform; @visibleForTesting static const volumeHandlingChannel = MethodChannel("com.vodemn.lightmeter/volumeHandling"); @@ -11,12 +11,12 @@ class VolumeEventsService { @visibleForTesting static const volumeEventsChannel = EventChannel("com.vodemn.lightmeter/volumeEvents"); - const VolumeEventsService(this.localPlatform); + const VolumeEventsService(this._localPlatform); /// If set to `false` we allow system to handle key events. /// Returns current status of volume handling. Future setVolumeHandling(bool enableHandling) async { - if (!localPlatform.isAndroid) { + if (!_localPlatform.isAndroid) { return false; } return volumeHandlingChannel @@ -29,7 +29,7 @@ class VolumeEventsService { /// KEYCODE_VOLUME_DOWN = 25; /// pressed Stream volumeButtonsEventStream() { - if (!localPlatform.isAndroid) { + if (!_localPlatform.isAndroid) { return const Stream.empty(); } return volumeEventsChannel diff --git a/lib/providers/equipment_profile_provider.dart b/lib/providers/equipment_profile_provider.dart index a5e0999..564c5ef 100644 --- a/lib/providers/equipment_profile_provider.dart +++ b/lib/providers/equipment_profile_provider.dart @@ -54,9 +54,7 @@ class EquipmentProfileProviderState extends State { _defaultProfile, if (IAPProducts.isPurchased(context, IAPProductType.paidFeatures)) ..._customProfiles, ], - selected: IAPProducts.isPurchased(context, IAPProductType.paidFeatures) - ? _selectedProfile - : _defaultProfile, + selected: IAPProducts.isPurchased(context, IAPProductType.paidFeatures) ? _selectedProfile : _defaultProfile, child: widget.child, ); } @@ -85,7 +83,7 @@ class EquipmentProfileProviderState extends State { _refreshSavedProfiles(); } - void updateProdile(EquipmentProfile data) { + void updateProfile(EquipmentProfile data) { final indexToUpdate = _customProfiles.indexWhere((element) => element.id == data.id); if (indexToUpdate >= 0) { _customProfiles[indexToUpdate] = data; @@ -118,13 +116,14 @@ class EquipmentProfiles extends SelectableInheritedModel { /// [_defaultProfile] + profiles created by the user static List of(BuildContext context) { - return InheritedModel.inheritFrom(context, aspect: SelectableAspect.list)! - .values; + return InheritedModel.inheritFrom(context, aspect: SelectableAspect.list)!.values; } static EquipmentProfile selectedOf(BuildContext context) { - return InheritedModel.inheritFrom(context, - aspect: SelectableAspect.selected,)! + return InheritedModel.inheritFrom( + context, + aspect: SelectableAspect.selected, + )! .selected; } } diff --git a/lib/screens/metering/bloc_metering.dart b/lib/screens/metering/bloc_metering.dart index cbb0356..753a7e7 100644 --- a/lib/screens/metering/bloc_metering.dart +++ b/lib/screens/metering/bloc_metering.dart @@ -10,9 +10,9 @@ import 'package:lightmeter/screens/metering/communication/event_communication_me as communication_events; import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' as communication_states; -import 'package:lightmeter/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart'; import 'package:lightmeter/screens/metering/event_metering.dart'; import 'package:lightmeter/screens/metering/state_metering.dart'; +import 'package:lightmeter/screens/metering/utils/notifier_volume_keys.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class MeteringBloc extends Bloc { diff --git a/lib/screens/metering/components/camera_container/components/camera_controls/components/exposure_offset_slider/widget_slider_exposure_offset.dart b/lib/screens/metering/components/camera_container/components/camera_controls/components/exposure_offset_slider/widget_slider_exposure_offset.dart index 484ee4b..40ec1f0 100644 --- a/lib/screens/metering/components/camera_container/components/camera_controls/components/exposure_offset_slider/widget_slider_exposure_offset.dart +++ b/lib/screens/metering/components/camera_container/components/camera_controls/components/exposure_offset_slider/widget_slider_exposure_offset.dart @@ -72,7 +72,7 @@ class _Ruler extends StatelessWidget { children: [ if (showValue) Text( - (index + min).toStringSigned(), + (index + min).toStringSignedAsFixed(0), style: Theme.of(context).textTheme.bodyLarge, ), const SizedBox(width: Dimens.grid8), diff --git a/lib/screens/metering/flow_metering.dart b/lib/screens/metering/flow_metering.dart index cca5675..f78482d 100644 --- a/lib/screens/metering/flow_metering.dart +++ b/lib/screens/metering/flow_metering.dart @@ -4,8 +4,8 @@ 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/screens/metering/utils/notifier_volume_keys.dart'; class MeteringFlow extends StatefulWidget { const MeteringFlow({super.key}); diff --git a/lib/screens/metering/screen_metering.dart b/lib/screens/metering/screen_metering.dart index 6900159..380411f 100644 --- a/lib/screens/metering/screen_metering.dart +++ b/lib/screens/metering/screen_metering.dart @@ -13,7 +13,7 @@ import 'package:lightmeter/screens/metering/components/camera_container/provider 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/screens/metering/utils/listsner_equipment_profiles.dart'; +import 'package:lightmeter/screens/metering/utils/listener_equipment_profiles.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class MeteringScreen extends StatelessWidget { diff --git a/lib/screens/metering/utils/listsner_equipment_profiles.dart b/lib/screens/metering/utils/listener_equipment_profiles.dart similarity index 100% rename from lib/screens/metering/utils/listsner_equipment_profiles.dart rename to lib/screens/metering/utils/listener_equipment_profiles.dart diff --git a/lib/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart b/lib/screens/metering/utils/notifier_volume_keys.dart similarity index 82% rename from lib/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart rename to lib/screens/metering/utils/notifier_volume_keys.dart index df64fdf..0c19955 100644 --- a/lib/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart +++ b/lib/screens/metering/utils/notifier_volume_keys.dart @@ -5,12 +5,12 @@ import 'package:lightmeter/data/models/volume_action.dart'; import 'package:lightmeter/data/volume_events_service.dart'; class VolumeKeysNotifier extends ChangeNotifier with RouteAware { - final VolumeEventsService volumeEventsService; + final VolumeEventsService _volumeEventsService; late final StreamSubscription _volumeKeysSubscription; VolumeKey _value = VolumeKey.up; - VolumeKeysNotifier(this.volumeEventsService) { - _volumeKeysSubscription = volumeEventsService + VolumeKeysNotifier(this._volumeEventsService) { + _volumeKeysSubscription = _volumeEventsService .volumeButtonsEventStream() .map((event) => event == 24 ? VolumeKey.up : VolumeKey.down) .listen((event) { @@ -19,6 +19,8 @@ class VolumeKeysNotifier extends ChangeNotifier with RouteAware { } VolumeKey get value => _value; + + @protected set value(VolumeKey newValue) { _value = newValue; notifyListeners(); diff --git a/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart b/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart index 5f8adcd..854a914 100644 --- a/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart +++ b/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart @@ -4,7 +4,7 @@ import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/providers/remote_config_provider.dart'; import 'package:lightmeter/providers/services_provider.dart'; import 'package:lightmeter/res/dimens.dart'; -import 'package:lightmeter/screens/settings/components/utils/show_buy_pro_dialog.dart'; +import 'package:lightmeter/screens/settings/utils/show_buy_pro_dialog.dart'; import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; class BuyProListTile extends StatelessWidget { 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 b10190f..6879a2c 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 @@ -90,7 +90,7 @@ class _EquipmentProfilesScreenState extends State { } void _updateProfileAt(EquipmentProfile data) { - EquipmentProfileProvider.of(context).updateProdile(data); + EquipmentProfileProvider.of(context).updateProfile(data); } void _removeProfileAt(EquipmentProfile data) { diff --git a/lib/screens/settings/components/utils/show_buy_pro_dialog.dart b/lib/screens/settings/utils/show_buy_pro_dialog.dart similarity index 94% rename from lib/screens/settings/components/utils/show_buy_pro_dialog.dart rename to lib/screens/settings/utils/show_buy_pro_dialog.dart index 50edbd1..7ce3ff6 100644 --- a/lib/screens/settings/components/utils/show_buy_pro_dialog.dart +++ b/lib/screens/settings/utils/show_buy_pro_dialog.dart @@ -28,7 +28,7 @@ Future showBuyProDialog(BuildContext context) { FilledButton( onPressed: () { Navigator.of(context).pop(); - IAPProductsProvider.of(context).buy(IAPProductType.paidFeatures); + IAPProductsProvider.maybeOf(context)?.buy(IAPProductType.paidFeatures); }, child: Text(unlockFeaturesEnabled ? S.of(context).unlock : S.of(context).buy), ), diff --git a/lib/utils/text_line_height.dart b/lib/utils/text_line_height.dart deleted file mode 100644 index a531074..0000000 --- a/lib/utils/text_line_height.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:flutter/widgets.dart'; - -extension TextLineHeight on TextStyle { - double get lineHeight => fontSize! * height!; -} diff --git a/lib/utils/to_string_signed.dart b/lib/utils/to_string_signed.dart index 4122893..fcced8d 100644 --- a/lib/utils/to_string_signed.dart +++ b/lib/utils/to_string_signed.dart @@ -1,13 +1,4 @@ -extension SignedString on num { - String toStringSigned() { - if (this > 0) { - return "+${toString()}"; - } else { - return toString(); - } - } -} - +/// Returns value in form -1 or + 1. The only exception - 0. extension SignedStringDouble on double { String toStringSignedAsFixed(int fractionDigits) { if (this > 0) { diff --git a/pubspec.yaml b/pubspec.yaml index 6fd97bc..ed5fc90 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,7 +28,7 @@ dependencies: m3_lightmeter_iap: git: url: "https://github.com/vodemn/m3_lightmeter_iap" - ref: v0.6.3 + ref: v0.7.0 m3_lightmeter_resources: git: url: "https://github.com/vodemn/m3_lightmeter_resources" diff --git a/test/function_mock.dart b/test/function_mock.dart new file mode 100644 index 0000000..8a4abfe --- /dev/null +++ b/test/function_mock.dart @@ -0,0 +1,7 @@ +import 'package:mocktail/mocktail.dart'; + +class _ValueChanged { + void onChanged(T value) {} +} + +class MockValueChanged extends Mock implements _ValueChanged {} diff --git a/test/interactors/metering_interactor_test.dart b/test/interactors/metering_interactor_test.dart index 1f0b05a..083a116 100644 --- a/test/interactors/metering_interactor_test.dart +++ b/test/interactors/metering_interactor_test.dart @@ -236,7 +236,7 @@ void main() { ); group( - 'Haptics', + 'Light sensor', () { test('hasAmbientLightSensor() - true', () async { when(() => mockLightSensorService.hasSensor()).thenAnswer((_) async => true); diff --git a/test/providers/equipment_profile_provider_test.dart b/test/providers/equipment_profile_provider_test.dart index 83f04f7..fb329cd 100644 --- a/test/providers/equipment_profile_provider_test.dart +++ b/test/providers/equipment_profile_provider_test.dart @@ -260,7 +260,7 @@ class _Application extends StatelessWidget { ElevatedButton( key: updateProfileButtonKey(profile.id), onPressed: () { - EquipmentProfileProvider.of(context).updateProdile( + EquipmentProfileProvider.of(context).updateProfile( profile.copyWith( name: '${profile.name} updated', isoValues: _customProfiles.first.isoValues, diff --git a/test/screens/metering/bloc_metering_test.dart b/test/screens/metering/bloc_metering_test.dart index fbffbbc..f07d35c 100644 --- a/test/screens/metering/bloc_metering_test.dart +++ b/test/screens/metering/bloc_metering_test.dart @@ -7,9 +7,9 @@ import 'package:lightmeter/screens/metering/communication/event_communication_me as communication_events; import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' as communication_states; -import 'package:lightmeter/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart'; import 'package:lightmeter/screens/metering/event_metering.dart'; import 'package:lightmeter/screens/metering/state_metering.dart'; +import 'package:lightmeter/screens/metering/utils/notifier_volume_keys.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; diff --git a/test/screens/metering/components/shared/readings_container/shared/dialog_picker_test.dart b/test/screens/metering/components/shared/readings_container/shared/dialog_picker_test.dart index 39c38ba..bd037af 100644 --- a/test/screens/metering/components/shared/readings_container/shared/dialog_picker_test.dart +++ b/test/screens/metering/components/shared/readings_container/shared/dialog_picker_test.dart @@ -7,16 +7,11 @@ import 'package:lightmeter/screens/metering/components/shared/readings_container import 'package:mocktail/mocktail.dart'; import '../../../../../../application_mock.dart'; +import '../../../../../../function_mock.dart'; import '../utils.dart'; -class _ValueChanged { - void onChanged(T value) {} -} - -class _MockValueChanged extends Mock implements _ValueChanged {} - void main() { - final functions = _MockValueChanged(); + final functions = MockValueChanged(); group( 'onChanged', diff --git a/test/screens/metering/utils/listener_equipment_profiles_test.dart b/test/screens/metering/utils/listener_equipment_profiles_test.dart new file mode 100644 index 0000000..ae6cb87 --- /dev/null +++ b/test/screens/metering/utils/listener_equipment_profiles_test.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/providers/equipment_profile_provider.dart'; +import 'package:lightmeter/screens/metering/utils/listener_equipment_profiles.dart'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../../function_mock.dart'; + +class _MockIAPStorageService extends Mock implements IAPStorageService {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final storageService = _MockIAPStorageService(); + final equipmentProfileProviderKey = GlobalKey(); + final onDidChangeDependencies = MockValueChanged(); + + tearDown(() { + reset(onDidChangeDependencies); + reset(storageService); + }); + + Future pumpTestWidget(WidgetTester tester) async { + await tester.pumpWidget( + IAPProducts( + products: [ + IAPProduct( + storeId: IAPProductType.paidFeatures.storeId, + status: IAPProductStatus.purchased, + ), + ], + child: EquipmentProfileProvider( + key: equipmentProfileProviderKey, + storageService: storageService, + child: MaterialApp( + home: EquipmentProfileListener( + onDidChangeDependencies: onDidChangeDependencies.onChanged, + child: Builder(builder: (context) => Text(EquipmentProfiles.selectedOf(context).name)), + ), + ), + ), + ), + ); + } + + testWidgets( + 'Trigger `onDidChangeDependencies` by selecting a new profile', + (tester) async { + when(() => storageService.equipmentProfiles).thenReturn(List.from(_customProfiles)); + when(() => storageService.selectedEquipmentProfileId).thenReturn(''); + await pumpTestWidget(tester); + + equipmentProfileProviderKey.currentState!.setProfile(_customProfiles[0]); + await tester.pump(); + verify(() => onDidChangeDependencies.onChanged(_customProfiles[0])).called(1); + }, + ); + + testWidgets( + 'Trigger `onDidChangeDependencies` by updating the selected profile', + (tester) async { + when(() => storageService.equipmentProfiles).thenReturn(List.from(_customProfiles)); + when(() => storageService.selectedEquipmentProfileId).thenReturn(_customProfiles[0].id); + await pumpTestWidget(tester); + + final updatedProfile1 = _customProfiles[0].copyWith(name: 'Test 1 updated'); + equipmentProfileProviderKey.currentState!.updateProfile(updatedProfile1); + await tester.pump(); + verify(() => onDidChangeDependencies.onChanged(updatedProfile1)).called(1); + + /// Verify that updating the not selected profile doesn't trigger the callback + final updatedProfile2 = _customProfiles[1].copyWith(name: 'Test 2 updated'); + equipmentProfileProviderKey.currentState!.updateProfile(updatedProfile2); + await tester.pump(); + verifyNever(() => onDidChangeDependencies.onChanged(updatedProfile2)); + }, + ); + + testWidgets( + "Don't trigger `onDidChangeDependencies` by updating the unselected profile", + (tester) async { + when(() => storageService.equipmentProfiles).thenReturn(List.from(_customProfiles)); + when(() => storageService.selectedEquipmentProfileId).thenReturn(_customProfiles[0].id); + await pumpTestWidget(tester); + + final updatedProfile2 = _customProfiles[1].copyWith(name: 'Test 2 updated'); + equipmentProfileProviderKey.currentState!.updateProfile(updatedProfile2); + await tester.pump(); + verifyNever(() => onDidChangeDependencies.onChanged(updatedProfile2)); + }, + ); +} + +final List _customProfiles = [ + const EquipmentProfile( + id: '1', + name: 'Test 1', + apertureValues: [ + ApertureValue(4.0, StopType.full), + ApertureValue(4.5, StopType.third), + ApertureValue(4.8, StopType.half), + ApertureValue(5.0, StopType.third), + ApertureValue(5.6, StopType.full), + ApertureValue(6.3, StopType.third), + ApertureValue(6.7, StopType.half), + ApertureValue(7.1, StopType.third), + ApertureValue(8, StopType.full), + ], + ndValues: [ + NdValue(0), + NdValue(2), + NdValue(4), + NdValue(8), + NdValue(16), + NdValue(32), + NdValue(64), + ], + shutterSpeedValues: ShutterSpeedValue.values, + isoValues: [ + IsoValue(100, StopType.full), + IsoValue(125, StopType.third), + IsoValue(160, StopType.third), + IsoValue(200, StopType.full), + IsoValue(250, StopType.third), + IsoValue(320, StopType.third), + IsoValue(400, StopType.full), + ], + ), + const EquipmentProfile( + id: '2', + name: 'Test 2', + apertureValues: ApertureValue.values, + ndValues: NdValue.values, + shutterSpeedValues: ShutterSpeedValue.values, + isoValues: IsoValue.values, + ), +]; diff --git a/test/screens/metering/utils/notifier_volume_keys_test.dart b/test/screens/metering/utils/notifier_volume_keys_test.dart new file mode 100644 index 0000000..7bb6458 --- /dev/null +++ b/test/screens/metering/utils/notifier_volume_keys_test.dart @@ -0,0 +1,46 @@ +import 'dart:async'; + +import 'package:lightmeter/data/models/volume_action.dart'; +import 'package:lightmeter/data/volume_events_service.dart'; +import 'package:lightmeter/screens/metering/utils/notifier_volume_keys.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +import '../../../function_mock.dart'; + +class _MockVolumeEventsService extends Mock implements VolumeEventsService {} + +void main() { + late _MockVolumeEventsService mockVolumeEventsService; + + setUp(() { + mockVolumeEventsService = _MockVolumeEventsService(); + }); + + test( + 'Listen to `volumeButtonsEventStream()`', + () async { + final StreamController volumeButtonsEvents = StreamController(); + when(() => mockVolumeEventsService.volumeButtonsEventStream()).thenAnswer((_) => volumeButtonsEvents.stream); + + final volumeKeysNotifier = VolumeKeysNotifier(mockVolumeEventsService); + final functions = MockValueChanged(); + volumeKeysNotifier.addListener(() => functions.onChanged(volumeKeysNotifier.value)); + expect(volumeKeysNotifier.value, VolumeKey.up); + + volumeButtonsEvents.add(25); + volumeButtonsEvents.add(25); + volumeButtonsEvents.add(25); + volumeButtonsEvents.add(24); + volumeButtonsEvents.add(24); + volumeButtonsEvents.add(25); + await Future.delayed(Duration.zero); + verify(() => functions.onChanged(VolumeKey.up)).called(2); + verify(() => functions.onChanged(VolumeKey.down)).called(4); + + volumeKeysNotifier.removeListener(() => functions.onChanged(volumeKeysNotifier.value)); + await volumeKeysNotifier.dispose(); + await volumeButtonsEvents.close(); + }, + ); +} diff --git a/test/screens/settings/utils/show_buy_pro_dialog_test.dart b/test/screens/settings/utils/show_buy_pro_dialog_test.dart new file mode 100644 index 0000000..16a8e19 --- /dev/null +++ b/test/screens/settings/utils/show_buy_pro_dialog_test.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/data/models/feature.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/remote_config_provider.dart'; +import 'package:lightmeter/screens/settings/utils/show_buy_pro_dialog.dart'; + +import '../../../application_mock.dart'; + +void main() { + Future pumpApplication(WidgetTester tester) async { + await tester.pumpWidget( + RemoteConfig( + config: const {Feature.unlockProFeaturesText: false}, + child: WidgetTestApplicationMock( + child: Builder( + builder: (context) => ElevatedButton( + onPressed: () => showBuyProDialog(context), + child: const SizedBox(), + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + } + + testWidgets( + '`showBuyProDialog` and buy', + (tester) async { + await pumpApplication(tester); + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text(S.current.lightmeterPro), findsOneWidget); + expect(find.text(S.current.cancel), findsOneWidget); + expect(find.text(S.current.buy), findsOneWidget); + + await tester.tap(find.text(S.current.buy)); + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsNothing); + }, + ); + + testWidgets( + '`showBuyProDialog` and cancel', + (tester) async { + await pumpApplication(tester); + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text(S.current.lightmeterPro), findsOneWidget); + expect(find.text(S.current.cancel), findsOneWidget); + expect(find.text(S.current.buy), findsOneWidget); + + await tester.tap(find.text(S.current.cancel)); + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsNothing); + }, + ); +} diff --git a/test/utils/selectable_provider_test.dart b/test/utils/selectable_provider_test.dart new file mode 100644 index 0000000..4ef2b96 --- /dev/null +++ b/test/utils/selectable_provider_test.dart @@ -0,0 +1,76 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/utils/selectable_provider.dart'; + +void main() { + group('SelectableInheritedModel.updateShouldNotifyDependent', () { + final model = SelectableInheritedModel( + values: List.generate(25, (index) => index), + selected: 1, + child: const SizedBox(), + ); + + test( + '`{}`', + () { + expect( + model.updateShouldNotifyDependent( + SelectableInheritedModel( + values: List.generate(25, (index) => index), + selected: 1, + child: const SizedBox(), + ), + {}, + ), + false, + ); + }, + ); + + test( + '`{SelectableAspect.list}`', + () { + expect( + model.updateShouldNotifyDependent( + SelectableInheritedModel( + values: List.generate(25, (index) => index), + selected: 1, + child: const SizedBox(), + ), + {SelectableAspect.list}, + ), + true, + ); + }, + ); + + test( + '`{SelectableAspect.selected}`', + () { + expect( + model.updateShouldNotifyDependent( + SelectableInheritedModel( + values: List.generate(25, (index) => index), + selected: 1, + child: const SizedBox(), + ), + {SelectableAspect.selected}, + ), + false, + ); + expect( + model.updateShouldNotifyDependent( + SelectableInheritedModel( + values: List.generate(25, (index) => index), + selected: 2, + child: const SizedBox(), + ), + {SelectableAspect.selected}, + ), + true, + ); + }, + ); + }); +} diff --git a/test/utils/to_string_signed_test.dart b/test/utils/to_string_signed_test.dart new file mode 100644 index 0000000..8611204 --- /dev/null +++ b/test/utils/to_string_signed_test.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/utils/to_string_signed.dart'; + +void main() { + test('toStringSignedAsFixed(0)', () { + expect(1.5.toStringSignedAsFixed(0), '+2'); + expect((-1.5).toStringSignedAsFixed(0), '-2'); + expect(0.0.toStringSignedAsFixed(0), '0'); + }); + + test('toStringSignedAsFixed(1)', () { + expect(1.5.toStringSignedAsFixed(1), '+1.5'); + expect((-1.5).toStringSignedAsFixed(1), '-1.5'); + expect(0.0.toStringSignedAsFixed(1), '0.0'); + }); +} From 068834bfe540040bd05a51582617246284ab885c Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:57:36 +0100 Subject: [PATCH 06/13] ML-134 Firebase Remote Config issues (#135) * Unable to connect to the server * internal remote config fetch error * fixed tests --- lib/data/remote_config_service.dart | 7 ++++++- lib/providers/remote_config_provider.dart | 7 ++++++- test/providers/remote_config_provider_test.dart | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/data/remote_config_service.dart b/lib/data/remote_config_service.dart index 9fc83fc..8243dd2 100644 --- a/lib/data/remote_config_service.dart +++ b/lib/data/remote_config_service.dart @@ -24,7 +24,6 @@ class RemoteConfigService { await remoteConfig.setDefaults(featuresDefaultValues.map((key, value) => MapEntry(key.name, value))); await remoteConfig.activate(); await remoteConfig.ensureInitialized(); - unawaited(remoteConfig.fetch()); log('Firebase remote config initialized successfully'); } on FirebaseException catch (e) { @@ -34,6 +33,12 @@ class RemoteConfigService { } } + Future fetchConfig() async { + // https://github.com/firebase/flutterfire/issues/6196#issuecomment-927751667 + await Future.delayed(const Duration(seconds: 1)); + await FirebaseRemoteConfig.instance.fetch(); + } + dynamic getValue(Feature feature) => FirebaseRemoteConfig.instance.getValue(feature.name).toValue(feature); Map getAll() { diff --git a/lib/providers/remote_config_provider.dart b/lib/providers/remote_config_provider.dart index 9736e1d..a00b385 100644 --- a/lib/providers/remote_config_provider.dart +++ b/lib/providers/remote_config_provider.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/feature.dart'; @@ -25,7 +26,11 @@ class RemoteConfigProviderState extends State { @override void initState() { super.initState(); - _updatesSubscription = widget.remoteConfigService.onConfigUpdated().listen(_updateFeatures); + widget.remoteConfigService.fetchConfig(); + _updatesSubscription = widget.remoteConfigService.onConfigUpdated().listen( + _updateFeatures, + onError: (e) => log(e.toString()), + ); } @override diff --git a/test/providers/remote_config_provider_test.dart b/test/providers/remote_config_provider_test.dart index aa6815d..a215cbe 100644 --- a/test/providers/remote_config_provider_test.dart +++ b/test/providers/remote_config_provider_test.dart @@ -18,6 +18,7 @@ void main() { }); setUp(() { + when(() => mockRemoteConfigService.fetchConfig()).thenAnswer((_) async {}); when(() => mockRemoteConfigService.getValue(Feature.unlockProFeaturesText)).thenReturn(false); when(() => mockRemoteConfigService.getAll()).thenReturn({Feature.unlockProFeaturesText: false}); }); From 434327a7d0fbcc238580eeaf90f04ce01d8aaf0b Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Tue, 7 Nov 2023 12:03:38 +0100 Subject: [PATCH 07/13] Disable list tile `onTap` if IAP is pending --- .../buy_pro/widget_list_tile_buy_pro.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart b/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart index 854a914..9854a14 100644 --- a/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart +++ b/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart @@ -18,12 +18,14 @@ class BuyProListTile extends StatelessWidget { return ListTile( leading: const Icon(Icons.star), title: Text(unlockFeaturesEnabled ? S.of(context).unlockProFeatures : S.of(context).buyLightmeterPro), - onTap: () { - showBuyProDialog(context); - ServicesProvider.of(context) - .analytics - .logUnlockProFeatures(unlockFeaturesEnabled ? 'Unlock Pro features' : 'Buy Lightmeter Pro'); - }, + onTap: !isPending + ? () { + showBuyProDialog(context); + ServicesProvider.of(context) + .analytics + .logUnlockProFeatures(unlockFeaturesEnabled ? 'Unlock Pro features' : 'Buy Lightmeter Pro'); + } + : null, trailing: isPending ? const SizedBox( height: Dimens.grid24, From ddc7ec8c8b2be7f0fb38f7ea5833599da1d26985 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Nov 2023 11:15:17 +0000 Subject: [PATCH 08/13] Version bump --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index ed5fc90..eb53e59 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: lightmeter description: Lightmeter app inspired by Material 3 design system. publish_to: "none" -version: 0.15.2+43 +version: 0.15.3+44 environment: sdk: ">=3.0.0 <4.0.0" From 6566108994371f149720b330447422605227356b Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Sat, 11 Nov 2023 21:05:11 +0100 Subject: [PATCH 09/13] ML-129 Spot metering (#136) * imlemented `CameraSpotDetector` * separated generic `DialogSwitch` * added `CameraFeature` model * added `CameraFeaturesListTile` to metering section * added features dialog subtitles * added long press to remove metering spot * translations * hide camera features for purchasable status * hide `CameraHistogram` & `CameraSpotDetector` if purchasable * bumped iap version * fixed tests * removed redundant camera state emission * tests * Fixed remote config initalization * updated pro features description --- lib/application_wrapper.dart | 5 +- lib/data/models/camera_feature.dart | 13 +++ lib/data/models/feature.dart | 2 +- .../models/metering_screen_layout_config.dart | 33 +++++-- lib/data/remote_config_service.dart | 49 +++++++++- lib/data/shared_prefs_service.dart | 39 +++++--- lib/l10n/intl_en.arb | 10 +- lib/l10n/intl_fr.arb | 10 +- lib/l10n/intl_ru.arb | 10 +- lib/l10n/intl_zh.arb | 10 +- lib/providers/remote_config_provider.dart | 2 +- lib/providers/user_preferences_provider.dart | 55 ++++++----- lib/res/theme.dart | 14 ++- .../bloc_container_camera.dart | 18 ++-- .../widget_camera_spot_detector.dart | 71 ++++++++++++++ .../camera_view/widget_camera_view.dart | 21 ++-- .../camera_preview/widget_camera_preview.dart | 53 +++++++--- .../event_container_camera.dart | 18 ++++ .../widget_container_camera.dart | 3 + .../widget_list_tile_camera_features.dart | 45 +++++++++ ...ialog_metering_screen_layout_features.dart | 97 ------------------- ...dget_list_tile_metering_screen_layout.dart | 36 ++++++- .../widget_settings_section_metering.dart | 2 + .../dialog_switch/widget_dialog_switch.dart | 95 ++++++++++++++++++ .../settings/utils/show_buy_pro_dialog.dart | 31 +++++- lib/utils/map_model.dart | 26 +++++ pubspec.yaml | 2 +- screenshots/generate_screenshots.dart | 1 - .../models/camera_features_config_test.dart | 47 +++++++++ .../metering_screen_layout_config_test.dart | 11 +-- test/data/shared_prefs_service_test.dart | 68 +++++++++++-- .../user_preferences_provider_test.dart | 57 ++++++++++- .../camera/bloc_container_camera_test.dart | 59 ++++++----- .../camera/event_container_camera_test.dart | 21 ++++ 34 files changed, 783 insertions(+), 251 deletions(-) create mode 100644 lib/data/models/camera_feature.dart create mode 100644 lib/screens/metering/components/camera_container/components/camera_preview/components/camera_spot_detector/widget_camera_spot_detector.dart create mode 100644 lib/screens/settings/components/metering/components/camera_features/widget_list_tile_camera_features.dart delete mode 100644 lib/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart create mode 100644 lib/screens/settings/components/shared/dialog_switch/widget_dialog_switch.dart create mode 100644 lib/utils/map_model.dart create mode 100644 test/data/models/camera_features_config_test.dart diff --git a/lib/application_wrapper.dart b/lib/application_wrapper.dart index d28fbcf..ae7d1ae 100644 --- a/lib/application_wrapper.dart +++ b/lib/application_wrapper.dart @@ -30,7 +30,7 @@ class ApplicationWrapper extends StatelessWidget { future: Future.wait([ SharedPreferences.getInstance(), const LightSensorService(LocalPlatform()).hasSensor(), - const RemoteConfigService().activeAndFetchFeatures(), + if (env.buildType != BuildType.dev) const RemoteConfigService().activeAndFetchFeatures(), ]), builder: (_, snapshot) { if (snapshot.data != null) { @@ -47,7 +47,8 @@ class ApplicationWrapper extends StatelessWidget { userPreferencesService: userPreferencesService, volumeEventsService: const VolumeEventsService(LocalPlatform()), child: RemoteConfigProvider( - remoteConfigService: const RemoteConfigService(), + remoteConfigService: + env.buildType != BuildType.dev ? const RemoteConfigService() : const MockRemoteConfigService(), child: EquipmentProfileProvider( storageService: iapService, child: FilmsProvider( diff --git a/lib/data/models/camera_feature.dart b/lib/data/models/camera_feature.dart new file mode 100644 index 0000000..20193bf --- /dev/null +++ b/lib/data/models/camera_feature.dart @@ -0,0 +1,13 @@ +enum CameraFeature { + spotMetering, + histogram, +} + +typedef CameraFeaturesConfig = Map; + +extension CameraFeaturesConfigJson on CameraFeaturesConfig { + static CameraFeaturesConfig fromJson(Map data) => + {for (final f in CameraFeature.values) f: data[f.name] as bool? ?? false}; + + Map toJson() => map((key, value) => MapEntry(key.name, value)); +} diff --git a/lib/data/models/feature.dart b/lib/data/models/feature.dart index e4dc30e..b022db7 100644 --- a/lib/data/models/feature.dart +++ b/lib/data/models/feature.dart @@ -1,5 +1,5 @@ enum Feature { unlockProFeaturesText } const featuresDefaultValues = { - Feature.unlockProFeaturesText: false, + Feature.unlockProFeaturesText: true, }; diff --git a/lib/data/models/metering_screen_layout_config.dart b/lib/data/models/metering_screen_layout_config.dart index 7802195..7d550e4 100644 --- a/lib/data/models/metering_screen_layout_config.dart +++ b/lib/data/models/metering_screen_layout_config.dart @@ -1,18 +1,31 @@ enum MeteringScreenLayoutFeature { - extremeExposurePairs, - filmPicker, - histogram, - equipmentProfiles, + extremeExposurePairs, // 0 + filmPicker, // 1 + equipmentProfiles, // 3 } typedef MeteringScreenLayoutConfig = Map; extension MeteringScreenLayoutConfigJson on MeteringScreenLayoutConfig { - static MeteringScreenLayoutConfig fromJson(Map data) => - { - for (final f in MeteringScreenLayoutFeature.values) - f: data[f.index.toString()] as bool? ?? true - }; + static MeteringScreenLayoutConfig fromJson(Map data) { + int? migratedIndex(MeteringScreenLayoutFeature feature) { + switch (feature) { + case MeteringScreenLayoutFeature.extremeExposurePairs: + return 0; + case MeteringScreenLayoutFeature.filmPicker: + return 1; + case MeteringScreenLayoutFeature.equipmentProfiles: + return 3; + default: + return null; + } + } - Map toJson() => map((key, value) => MapEntry(key.index.toString(), value)); + return { + for (final f in MeteringScreenLayoutFeature.values) + f: (data[migratedIndex(f).toString()] ?? data[f.name]) as bool? ?? true + }; + } + + Map toJson() => map((key, value) => MapEntry(key.name, value)); } diff --git a/lib/data/remote_config_service.dart b/lib/data/remote_config_service.dart index 8243dd2..f8c123b 100644 --- a/lib/data/remote_config_service.dart +++ b/lib/data/remote_config_service.dart @@ -7,9 +7,26 @@ import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:flutter/foundation.dart'; import 'package:lightmeter/data/models/feature.dart'; -class RemoteConfigService { +abstract class IRemoteConfigService { + const IRemoteConfigService(); + + Future activeAndFetchFeatures(); + + Future fetchConfig(); + + dynamic getValue(Feature feature); + + Map getAll(); + + Stream> onConfigUpdated(); + + bool isEnabled(Feature feature); +} + +class RemoteConfigService implements IRemoteConfigService { const RemoteConfigService(); + @override Future activeAndFetchFeatures() async { final FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instance; const cacheStaleDuration = kDebugMode ? Duration(minutes: 1) : Duration(hours: 12); @@ -28,19 +45,22 @@ class RemoteConfigService { log('Firebase remote config initialized successfully'); } on FirebaseException catch (e) { _logError('Firebase exception during Firebase Remote Config initialization: $e'); - } on Exception catch (e) { + } catch (e) { _logError('Error during Firebase Remote Config initialization: $e'); } } + @override Future fetchConfig() async { // https://github.com/firebase/flutterfire/issues/6196#issuecomment-927751667 await Future.delayed(const Duration(seconds: 1)); await FirebaseRemoteConfig.instance.fetch(); } + @override dynamic getValue(Feature feature) => FirebaseRemoteConfig.instance.getValue(feature.name).toValue(feature); + @override Map getAll() { final Map result = {}; for (final value in FirebaseRemoteConfig.instance.getAll().entries) { @@ -54,6 +74,7 @@ class RemoteConfigService { return result; } + @override Stream> onConfigUpdated() => FirebaseRemoteConfig.instance.onConfigUpdated.asyncMap( (event) async { await FirebaseRemoteConfig.instance.activate(); @@ -69,6 +90,7 @@ class RemoteConfigService { }, ); + @override bool isEnabled(Feature feature) => FirebaseRemoteConfig.instance.getBool(feature.name); void _logError(dynamic throwable, {StackTrace? stackTrace}) { @@ -76,6 +98,29 @@ class RemoteConfigService { } } +class MockRemoteConfigService implements IRemoteConfigService { + const MockRemoteConfigService(); + + @override + Future activeAndFetchFeatures() async {} + + @override + Future fetchConfig() async {} + + @override + Map getAll() => featuresDefaultValues; + + @override + dynamic getValue(Feature feature) => featuresDefaultValues[feature]; + + @override + // ignore: cast_nullable_to_non_nullable + bool isEnabled(Feature feature) => featuresDefaultValues[feature] as bool; + + @override + Stream> onConfigUpdated() => const Stream.empty(); +} + extension on RemoteConfigValue { dynamic toValue(Feature feature) { switch (feature) { diff --git a/lib/data/shared_prefs_service.dart b/lib/data/shared_prefs_service.dart index 96d7d9d..443f2e5 100644 --- a/lib/data/shared_prefs_service.dart +++ b/lib/data/shared_prefs_service.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:lightmeter/data/models/camera_feature.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'; @@ -18,6 +19,7 @@ class UserPreferencesService { static const cameraEvCalibrationKey = "cameraEvCalibration"; static const lightSensorEvCalibrationKey = "lightSensorEvCalibration"; static const meteringScreenLayoutKey = "meteringScreenLayout"; + static const cameraFeaturesKey = "cameraFeatures"; static const caffeineKey = "caffeine"; static const hapticsKey = "haptics"; @@ -70,16 +72,13 @@ class UserPreferencesService { } } - IsoValue get iso => - IsoValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(isoKey) ?? 100)); + IsoValue get iso => IsoValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(isoKey) ?? 100)); set iso(IsoValue value) => _sharedPreferences.setInt(isoKey, value.value); - NdValue get ndFilter => - NdValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(ndFilterKey) ?? 0)); + NdValue get ndFilter => NdValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(ndFilterKey) ?? 0)); set ndFilter(NdValue value) => _sharedPreferences.setInt(ndFilterKey, value.value); - EvSourceType get evSourceType => - EvSourceType.values[_sharedPreferences.getInt(evSourceTypeKey) ?? 0]; + EvSourceType get evSourceType => EvSourceType.values[_sharedPreferences.getInt(evSourceTypeKey) ?? 0]; set evSourceType(EvSourceType value) => _sharedPreferences.setInt(evSourceTypeKey, value.index); StopType get stopType => StopType.values[_sharedPreferences.getInt(stopTypeKey) ?? 2]; @@ -96,7 +95,6 @@ class UserPreferencesService { MeteringScreenLayoutFeature.equipmentProfiles: true, MeteringScreenLayoutFeature.extremeExposurePairs: true, MeteringScreenLayoutFeature.filmPicker: true, - MeteringScreenLayoutFeature.histogram: true, }; } } @@ -104,6 +102,21 @@ class UserPreferencesService { set meteringScreenLayout(MeteringScreenLayoutConfig value) => _sharedPreferences.setString(meteringScreenLayoutKey, json.encode(value.toJson())); + CameraFeaturesConfig get cameraFeatures { + final configJson = _sharedPreferences.getString(cameraFeaturesKey); + if (configJson != null) { + return CameraFeaturesConfigJson.fromJson(json.decode(configJson) as Map); + } else { + return { + CameraFeature.spotMetering: false, + CameraFeature.histogram: false, + }; + } + } + + set cameraFeatures(CameraFeaturesConfig value) => + _sharedPreferences.setString(cameraFeaturesKey, json.encode(value.toJson())); + bool get caffeine => _sharedPreferences.getBool(caffeineKey) ?? false; set caffeine(bool value) => _sharedPreferences.setBool(caffeineKey, value); @@ -114,8 +127,7 @@ class UserPreferencesService { (e) => e.toString() == _sharedPreferences.getString(volumeActionKey), orElse: () => VolumeAction.shutter, ); - set volumeAction(VolumeAction value) => - _sharedPreferences.setString(volumeActionKey, value.toString()); + set volumeAction(VolumeAction value) => _sharedPreferences.setString(volumeActionKey, value.toString()); SupportedLocale get locale => SupportedLocale.values.firstWhere( (e) => e.toString() == _sharedPreferences.getString(localeKey), @@ -124,13 +136,10 @@ class UserPreferencesService { set locale(SupportedLocale value) => _sharedPreferences.setString(localeKey, value.toString()); double get cameraEvCalibration => _sharedPreferences.getDouble(cameraEvCalibrationKey) ?? 0.0; - set cameraEvCalibration(double value) => - _sharedPreferences.setDouble(cameraEvCalibrationKey, value); + set cameraEvCalibration(double value) => _sharedPreferences.setDouble(cameraEvCalibrationKey, value); - double get lightSensorEvCalibration => - _sharedPreferences.getDouble(lightSensorEvCalibrationKey) ?? 0.0; - set lightSensorEvCalibration(double value) => - _sharedPreferences.setDouble(lightSensorEvCalibrationKey, value); + double get lightSensorEvCalibration => _sharedPreferences.getDouble(lightSensorEvCalibrationKey) ?? 0.0; + set lightSensorEvCalibration(double value) => _sharedPreferences.setDouble(lightSensorEvCalibrationKey, value); ThemeType get themeType => ThemeType.values[_sharedPreferences.getInt(themeTypeKey) ?? 0]; set themeType(ThemeType value) => _sharedPreferences.setInt(themeTypeKey, value.index); diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 57e91d2..02bb95a 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -39,7 +39,11 @@ "meteringScreenLayoutHintEquipmentProfiles": "Equipment profile picker", "meteringScreenFeatureExtremeExposurePairs": "Fastest & shortest exposure pairs", "meteringScreenFeatureFilmPicker": "Film picker", - "meteringScreenFeatureHistogram": "Histogram", + "cameraFeatures": "Camera features", + "cameraFeatureSpotMetering": "Spot metering", + "cameraFeatureSpotMeteringHint": "Long press the camera view to remove metering spot", + "cameraFeatureHistogram": "Histogram", + "cameraFeatureHistogramHint": "Enabling histogram can encrease battery drain", "film": "Film", "filmPush": "Film (push)", "filmPull": "Film (pull)", @@ -94,11 +98,11 @@ }, "lightmeterPro": "Lightmeter Pro", "buyLightmeterPro": "Buy Lightmeter Pro", - "lightmeterProDescription": "Unlocks extra features, such as equipment profiles containing filters for aperture, shutter speed, and more; and a list of films with compensation for what's known as reciprocity failure.\n\nThe source code of Lightmeter is available on GitHub. You are welcome to compile it yourself. However, if you want to support the development and receive new features and updates, consider purchasing Lightmeter Pro.", + "lightmeterProDescription": "Unlocks extra features:\n \u2022 Equipment profiles containing filters for aperture, shutter speed, and more\n \u2022 List of films with compensation for what's known as reciprocity failure\n \u2022 Spot metering\n \u2022 Histogram\n\nThe source code of Lightmeter is available on GitHub. You are welcome to compile it yourself. However, if you want to support the development and receive new features and updates, consider purchasing Lightmeter Pro.", "buy": "Buy", "proFeatures": "Pro features", "unlockProFeatures": "Unlock Pro features", - "unlockProFeaturesDescription": "Unlock professional features, such as equipment profiles containing filters for aperture, shutter speed, and more; and a list of films with compensation for what's known as reciprocity failure.\n\nBy unlocking Pro features you support the development and make it possible to add new features to the app.", + "unlockProFeaturesDescription": "Unlock professional features:\n \u2022 Equipment profiles containing filters for aperture, shutter speed, and more\n \u2022 List of films with compensation for what's known as reciprocity failure\n \u2022 Spot metering\n \u2022 Histogram\n\nBy unlocking Pro features you support the development and make it possible to add new features to the app.", "unlock": "Unlock", "tooltipAdd": "Add", "tooltipClose": "Close", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index b1cd7fb..f8b52c3 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -39,7 +39,11 @@ "meteringScreenLayoutHintEquipmentProfiles": "Sélecteur de profil de l'équipement", "meteringScreenFeatureExtremeExposurePairs": "Paires d'exposition les plus rapides et les plus courtes", "meteringScreenFeatureFilmPicker": "Sélecteur de film", - "meteringScreenFeatureHistogram": "Histogramme", + "cameraFeatures": "Fonctionnalités de la caméra", + "cameraFeatureSpotMetering": "Mesure spot", + "cameraFeatureSpotMeteringHint": "Appuyez longuement sur la vue de l'appareil photo pour supprimer le spot de mesure", + "cameraFeatureHistogram": "Histogramme", + "cameraFeatureHistogramHint": "L'activation de l'histogramme peut augmenter la consommation de la batterie", "film": "Pellicule", "filmPush": "Pellicule (push)", "filmPull": "Pellicule (pull)", @@ -94,11 +98,11 @@ }, "buyLightmeterPro": "Acheter Lightmeter Pro", "lightmeterPro": "Lightmeter Pro", - "lightmeterProDescription": "Déverrouille des fonctionnalités supplémentaires, telles que des profils d'équipement contenant des filtres pour l'ouverture, la vitesse d'obturation et plus encore, ainsi qu'une liste de films avec une compensation pour ce que l'on appelle l'échec de réciprocité.\n\nLe code source du Lightmeter est disponible sur GitHub. Vous pouvez le compiler vous-même. Cependant, si vous souhaitez soutenir le développement et recevoir de nouvelles fonctionnalités et mises à jour, envisagez d'acheter Lightmeter Pro.", + "lightmeterProDescription": "Débloque des fonctionnalités supplémentaires:\n \u2022 Profils d'équipement contenant des filtres pour l'ouverture, la vitesse d'obturation et plus encore\n \u2022 Liste de films avec une compensation pour ce que l'on appelle l'échec de réciprocité\n \u2022 Mesure spot\n \u2022 Histogramme\n\nLe code source du Lightmeter est disponible sur GitHub. Vous pouvez le compiler vous-même. Cependant, si vous souhaitez soutenir le développement et recevoir de nouvelles fonctionnalités et mises à jour, envisagez d'acheter Lightmeter Pro.", "buy": "Acheter", "proFeatures": "Fonctionnalités professionnelles", "unlockProFeatures": "Déverrouiller les fonctionnalités professionnelles", - "unlockProFeaturesDescription": "Déverrouillez des fonctions professionnelles, telles que des profils d'équipement contenant des filtres pour l'ouverture, la vitesse d'obturation et plus encore, ainsi qu'une liste de films avec compensation pour ce que l'on appelle l'échec de réciprocité.\n\nEn débloquant les fonctionnalités Pro, vous soutenez le développement et permettez d'ajouter de nouvelles fonctionnalités à l'application.", + "unlockProFeaturesDescription": "Déverrouillez des fonctions professionnelles:\n \u2022 Profils d'équipement contenant des filtres pour l'ouverture, la vitesse d'obturation et plus encore, ainsi qu'une liste de films avec compensation pour ce que l'on appelle l'échec de réciprocité\n \u2022 Mesure spot\n \u2022 Histogramme\n\nEn débloquant les fonctionnalités Pro, vous soutenez le développement et permettez d'ajouter de nouvelles fonctionnalités à l'application.", "unlock": "Déverrouiller", "tooltipAdd": "Ajouter", "tooltipClose": "Fermer", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index a18cc9c..f25dc8e 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -39,7 +39,11 @@ "meteringScreenLayoutHintEquipmentProfiles": "Выбор профиля оборудования", "meteringScreenFeatureExtremeExposurePairs": "Длинная и короткая выдержки", "meteringScreenFeatureFilmPicker": "Выбор пленки", - "meteringScreenFeatureHistogram": "Гистограмма", + "cameraFeatures": "Возможности камеры", + "cameraFeatureSpotMetering": "Точечный замер", + "cameraFeatureSpotMeteringHint": "Используйте долгое нажатие, чтобы удалить точку замера", + "cameraFeatureHistogram": "Гистограмма", + "cameraFeatureHistogramHint": "Использование гистограммы может увеличить расход аккумулятора", "film": "Пленка", "filmPush": "Пленка (push)", "filmPull": "Пленка (pull)", @@ -94,11 +98,11 @@ }, "buyLightmeterPro": "Купить Lightmeter Pro", "lightmeterPro": "Lightmeter Pro", - "lightmeterProDescription": "Даёт доступ к таким функциям как профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений, а также набору пленок с компенсацией эффекта Шварцшильда.\n\nИсходный код Lightmeter доступен на GitHub. Вы можете собрать его самостоятельно. Однако если вы хотите поддержать разработку и получать новые функции и обновления, то приобретите Lightmeter Pro.", + "lightmeterProDescription": "Даёт доступ к различным функциям:\n \u2022 Профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений\n \u2022 Список пленок с компенсацией эффекта Шварцшильда\n \u2022 Точечный замер\n \u2022 Гистограмма\n\nИсходный код Lightmeter доступен на GitHub. Вы можете собрать его самостоятельно. Однако если вы хотите поддержать разработку и получать новые функции и обновления, то приобретите Lightmeter Pro.", "buy": "Купить", "proFeatures": "Профессиональные настройки", "unlockProFeatures": "Разблокировать профессиональные настройки", - "unlockProFeaturesDescription": "Вы можете разблокировать профессиональные настройки, такие как профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений, а также набору пленок с компенсацией эффекта Шварцшильда.\n\nПолучая доступ к профессиональным настройкам, вы поддерживаете разработку и делаете возможным появление новых функций в приложении.", + "unlockProFeaturesDescription": "Вы можете разблокировать профессиональные настройки:\n \u2022 Профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений\n \u2022 Список пленок с компенсацией эффекта Шварцшильда\n \u2022 Точечный замер\n \u2022 Гистограмма\n\nПолучая доступ к профессиональным настройкам, вы поддерживаете разработку и делаете возможным появление новых функций в приложении.", "unlock": "Разблокировать", "tooltipAdd": "Добавить", "tooltipClose": "Закрыть", diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 5788ea9..714c4b3 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -39,7 +39,11 @@ "meteringScreenLayoutHintEquipmentProfiles": "设备配置选择", "meteringScreenFeatureExtremeExposurePairs": "最快 & 最慢曝光组合", "meteringScreenFeatureFilmPicker": "胶片选择", - "meteringScreenFeatureHistogram": "直方图", + "cameraFeatures": "相机功能", + "cameraFeatureSpotMetering": "点测光", + "cameraFeatureSpotMeteringHint": "长按相机视图可移除测光点", + "cameraFeatureHistogram": "直方图", + "cameraFeatureHistogramHint": "启用直方图会增加电池消耗", "film": "胶片", "filmPush": "胶片 (push)", "filmPull": "胶片 (pull)", @@ -94,11 +98,11 @@ }, "buyLightmeterPro": "购买 Lightmeter Pro", "lightmeterPro": "Lightmeter Pro", - "lightmeterProDescription": "购买以解锁额外功能。例如包含光圈、快门速度等参数的配置文件;以及一个胶卷预设列表来提供倒易率失效时的曝光补偿。\n\n您可以在 GitHub 上获取 Lightmeter 的源代码,欢迎自行编译。不过,如果您想支持开发并获得新功能和更新,请考虑购买 Lightmeter Pro。", + "lightmeterProDescription": "解锁额外功能:\n \u2022 配置文件,其中包含光圈、快门速度等参数\n \u2022 胶片预设列表,用于在反转率发生故障时提供曝光补偿\n \u2022 点测光\n \u2022 直方图\n\n您可以在 GitHub 上获取 Lightmeter 的源代码,欢迎自行编译。不过,如果您想支持开发并获得新功能和更新,请考虑购买 Lightmeter Pro。", "buy": "购买", "proFeatures": "专业功能", "unlockProFeatures": "解锁专业功能", - "unlockProFeaturesDescription": "解锁专业功能。例如包含光圈、快门速度等参数的配置文件;以及一个胶卷预设列表来提供倒易率失效时的曝光补偿。\n\n通过解锁专业版功能,您可以支持开发工作,帮助为应用程序添加新功能。", + "unlockProFeaturesDescription": "\n \u2022 配置文件,其中包含光圈、快门速度等参数\n \u2022 胶片预设列表,用于在反转率发生故障时提供曝光补偿\n \u2022 点测光\n \u2022 直方图\n\n通过解锁专业版功能,您可以支持开发工作,帮助为应用程序添加新功能。", "unlock": "解锁", "tooltipAdd": "添加", "tooltipClose": "关闭", diff --git a/lib/providers/remote_config_provider.dart b/lib/providers/remote_config_provider.dart index a00b385..4557ed6 100644 --- a/lib/providers/remote_config_provider.dart +++ b/lib/providers/remote_config_provider.dart @@ -6,7 +6,7 @@ import 'package:lightmeter/data/models/feature.dart'; import 'package:lightmeter/data/remote_config_service.dart'; class RemoteConfigProvider extends StatefulWidget { - final RemoteConfigService remoteConfigService; + final IRemoteConfigService remoteConfigService; final Widget child; const RemoteConfigProvider({ diff --git a/lib/providers/user_preferences_provider.dart b/lib/providers/user_preferences_provider.dart index 3a4111c..764f282 100644 --- a/lib/providers/user_preferences_provider.dart +++ b/lib/providers/user_preferences_provider.dart @@ -1,6 +1,7 @@ import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; +import 'package:lightmeter/data/models/camera_feature.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'; @@ -9,6 +10,7 @@ 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/res/theme.dart'; +import 'package:lightmeter/utils/map_model.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class UserPreferencesProvider extends StatefulWidget { @@ -51,6 +53,14 @@ class UserPreferencesProvider extends StatefulWidget { return _inheritFromEnumsModel(context, _Aspect.stopType).stopType; } + static CameraFeaturesConfig cameraConfigOf(BuildContext context) { + return context.findAncestorWidgetOfExactType<_CameraFeaturesModel>()!.data; + } + + static bool cameraFeatureOf(BuildContext context, CameraFeature feature) { + return InheritedModel.inheritFrom<_CameraFeaturesModel>(context, aspect: feature)!.data[feature]!; + } + static ThemeData themeOf(BuildContext context) { return _inheritFromEnumsModel(context, _Aspect.theme).theme; } @@ -74,6 +84,7 @@ class _UserPreferencesProviderState extends State with late EvSourceType _evSourceType; late StopType _stopType = widget.userPreferencesService.stopType; late MeteringScreenLayoutConfig _meteringScreenLayout = widget.userPreferencesService.meteringScreenLayout; + late CameraFeaturesConfig _cameraFeatures = widget.userPreferencesService.cameraFeatures; late SupportedLocale _locale = widget.userPreferencesService.locale; late ThemeType _themeType = widget.userPreferencesService.themeType; late Color _primaryColor = widget.userPreferencesService.primaryColor; @@ -83,7 +94,8 @@ class _UserPreferencesProviderState extends State with void initState() { super.initState(); _evSourceType = widget.userPreferencesService.evSourceType; - _evSourceType = _evSourceType == EvSourceType.sensor && !widget.hasLightSensor ? EvSourceType.camera : _evSourceType; + _evSourceType = + _evSourceType == EvSourceType.sensor && !widget.hasLightSensor ? EvSourceType.camera : _evSourceType; WidgetsBinding.instance.addObserver(this); } @@ -127,7 +139,10 @@ class _UserPreferencesProviderState extends State with themeType: _themeType, child: _MeteringScreenLayoutModel( data: _meteringScreenLayout, - child: widget.child, + child: _CameraFeaturesModel( + data: _cameraFeatures, + child: widget.child, + ), ), ); }, @@ -172,6 +187,13 @@ class _UserPreferencesProviderState extends State with widget.userPreferencesService.meteringScreenLayout = _meteringScreenLayout; } + void setCameraFeature(CameraFeaturesConfig config) { + setState(() { + _cameraFeatures = config; + }); + widget.userPreferencesService.cameraFeatures = _cameraFeatures; + } + void setPrimaryColor(Color primaryColor) { setState(() { _primaryColor = primaryColor; @@ -264,27 +286,16 @@ class _UserPreferencesModel extends InheritedModel<_Aspect> { } } -class _MeteringScreenLayoutModel extends InheritedModel { - final Map data; - +class _MeteringScreenLayoutModel extends MapModel { const _MeteringScreenLayoutModel({ - required this.data, + required super.data, + required super.child, + }); +} + +class _CameraFeaturesModel extends MapModel { + const _CameraFeaturesModel({ + required super.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 index a6320c1..52e0e92 100644 --- a/lib/res/theme.dart +++ b/lib/res/theme.dart @@ -23,7 +23,7 @@ const primaryColorsList = [ ThemeData themeFrom(Color primaryColor, Brightness brightness) { final scheme = _colorSchemeFromColor(primaryColor, brightness); - return ThemeData( + final theme = ThemeData( useMaterial3: true, brightness: scheme.brightness, primaryColor: primaryColor, @@ -60,12 +60,18 @@ ThemeData themeFrom(Color primaryColor, Brightness brightness) { ), scaffoldBackgroundColor: scheme.surface, ); + return theme.copyWith( + listTileTheme: ListTileThemeData( + style: ListTileStyle.list, + iconColor: scheme.onSurface, + textColor: scheme.onSurface, + subtitleTextStyle: theme.textTheme.bodyMedium!.copyWith(color: scheme.onSurfaceVariant), + ), + ); } ColorScheme _colorSchemeFromColor(Color primaryColor, Brightness brightness) { - final scheme = brightness == Brightness.light - ? Scheme.light(primaryColor.value) - : Scheme.dark(primaryColor.value); + final scheme = brightness == Brightness.light ? Scheme.light(primaryColor.value) : Scheme.dark(primaryColor.value); return ColorScheme( brightness: brightness, 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 1d7d4b5..2e6b051 100644 --- a/lib/screens/metering/components/camera_container/bloc_container_camera.dart +++ b/lib/screens/metering/components/camera_container/bloc_container_camera.dart @@ -11,10 +11,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lightmeter/interactors/metering_interactor.dart'; import 'package:lightmeter/platform_config.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; -import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' - as communication_event; -import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' - as communication_states; +import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' as communication_event; +import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' as communication_states; 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'; @@ -57,6 +55,7 @@ class CameraContainerBloc extends EvSourceBlocBase(_onZoomChanged); on(_onExposureOffsetChanged); on(_onExposureOffsetResetEvent); + on(_onExposureSpotChangedEvent); } @override @@ -166,9 +165,7 @@ class CameraContainerBloc extends EvSourceBlocBase _onZoomChanged(ZoomChangedEvent event, Emitter emit) async { - if (_cameraController != null && - event.value >= _zoomRange!.start && - event.value <= _zoomRange!.end) { + if (_cameraController != null && event.value >= _zoomRange!.start && event.value <= _zoomRange!.end) { _cameraController!.setZoomLevel(event.value); _currentZoom = event.value; _emitActiveState(emit); @@ -188,6 +185,13 @@ class CameraContainerBloc extends EvSourceBlocBase _onExposureSpotChangedEvent(ExposureSpotChangedEvent event, Emitter emit) async { + if (_cameraController != null) { + _cameraController!.setExposurePoint(event.offset); + _cameraController!.setFocusPoint(event.offset); + } + } + void _emitActiveState(Emitter emit) { emit( CameraActiveState( diff --git a/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_spot_detector/widget_camera_spot_detector.dart b/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_spot_detector/widget_camera_spot_detector.dart new file mode 100644 index 0000000..0ec7f17 --- /dev/null +++ b/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_spot_detector/widget_camera_spot_detector.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/res/dimens.dart'; + +class CameraSpotDetector extends StatefulWidget { + final ValueChanged onSpotTap; + + const CameraSpotDetector({ + required this.onSpotTap, + super.key, + }); + + @override + State createState() => _CameraSpotDetectorState(); +} + +class _CameraSpotDetectorState extends State { + Offset? spot; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (_, constraints) => GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: (TapDownDetails details) => onViewFinderTap(details, constraints), + onLongPress: () => onViewFinderTap(null, constraints), + child: Stack( + children: [ + if (spot != null) + AnimatedPositioned( + duration: Dimens.durationS, + left: spot!.dx - Dimens.grid16 / 2, + top: spot!.dy - Dimens.grid16 / 2, + height: Dimens.grid16, + width: Dimens.grid16, + child: const _Spot(), + ), + ], + ), + ), + ); + } + + void onViewFinderTap(TapDownDetails? details, BoxConstraints constraints) { + setState(() { + spot = details?.localPosition; + }); + + widget.onSpotTap( + details != null + ? Offset( + details.localPosition.dx / constraints.maxWidth, + details.localPosition.dy / constraints.maxHeight, + ) + : null, + ); + } +} + +class _Spot extends StatelessWidget { + const _Spot(); + + @override + Widget build(BuildContext context) { + return const DecoratedBox( + decoration: BoxDecoration( + color: Colors.white70, + shape: BoxShape.circle, + ), + ); + } +} diff --git a/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart b/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart index 7c06062..c054f3d 100644 --- a/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart +++ b/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart @@ -12,14 +12,17 @@ class CameraView extends StatelessWidget { final value = controller.value; return ValueListenableBuilder( valueListenable: controller, - builder: (_, __, ___) => AspectRatio( + builder: (_, __, Widget? child) => AspectRatio( aspectRatio: _isLandscape(value) ? value.aspectRatio : (1 / value.aspectRatio), - child: value.isInitialized - ? RotatedBox( - quarterTurns: _getQuarterTurns(value), - child: controller.buildPreview(), - ) - : const SizedBox.shrink(), + child: Stack( + children: [ + RotatedBox( + quarterTurns: _getQuarterTurns(value), + child: controller.buildPreview(), + ), + child ?? const SizedBox(), + ], + ), ), ); } @@ -42,8 +45,6 @@ class CameraView extends StatelessWidget { DeviceOrientation _getApplicableOrientation(CameraValue value) { return value.isRecordingVideo ? value.recordingOrientation! - : (value.previewPauseOrientation ?? - value.lockedCaptureOrientation ?? - value.deviceOrientation); + : (value.previewPauseOrientation ?? value.lockedCaptureOrientation ?? value.deviceOrientation); } } 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 3e9538f..e6f1699 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 @@ -1,19 +1,27 @@ import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; -import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; +import 'package:lightmeter/data/models/camera_feature.dart'; import 'package:lightmeter/platform_config.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_spot_detector/widget_camera_spot_detector.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'; +import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; class CameraPreview extends StatefulWidget { final CameraController? controller; final CameraErrorType? error; + final ValueChanged onSpotTap; - const CameraPreview({this.controller, this.error, super.key}); + const CameraPreview({ + this.controller, + this.error, + required this.onSpotTap, + super.key, + }); @override State createState() => _CameraPreviewState(); @@ -31,7 +39,10 @@ class _CameraPreviewState extends State { AnimatedSwitcher( duration: Dimens.switchDuration, child: widget.controller != null - ? _CameraPreviewBuilder(controller: widget.controller!) + ? _CameraPreviewBuilder( + controller: widget.controller!, + onSpotTap: widget.onSpotTap, + ) : CameraViewPlaceholder(error: widget.error), ), ], @@ -43,16 +54,19 @@ class _CameraPreviewState extends State { class _CameraPreviewBuilder extends StatefulWidget { final CameraController controller; + final ValueChanged onSpotTap; - const _CameraPreviewBuilder({required this.controller}); + const _CameraPreviewBuilder({ + required this.controller, + required this.onSpotTap, + }); @override State<_CameraPreviewBuilder> createState() => _CameraPreviewBuilderState(); } class _CameraPreviewBuilderState extends State<_CameraPreviewBuilder> { - late final ValueNotifier _initializedNotifier = - ValueNotifier(widget.controller.value.isInitialized); + late final ValueNotifier _initializedNotifier = ValueNotifier(widget.controller.value.isInitialized); @override void initState() { @@ -79,16 +93,23 @@ class _CameraPreviewBuilderState extends State<_CameraPreviewBuilder> { alignment: Alignment.bottomCenter, children: [ CameraView(controller: widget.controller), - if (UserPreferencesProvider.meteringScreenFeatureOf( - context, - MeteringScreenLayoutFeature.histogram, - )) - Positioned( - left: Dimens.grid8, - right: Dimens.grid8, - bottom: Dimens.grid16, - child: CameraHistogram(controller: widget.controller), - ), + if (IAPProducts.isPurchased(context, IAPProductType.paidFeatures)) ...[ + if (UserPreferencesProvider.cameraFeatureOf( + context, + CameraFeature.histogram, + )) + Positioned( + left: Dimens.grid8, + right: Dimens.grid8, + bottom: Dimens.grid16, + child: CameraHistogram(controller: widget.controller), + ), + if (UserPreferencesProvider.cameraFeatureOf( + context, + CameraFeature.spotMetering, + )) + CameraSpotDetector(onSpotTap: widget.onSpotTap) + ], ], ) : const SizedBox.shrink(), diff --git a/lib/screens/metering/components/camera_container/event_container_camera.dart b/lib/screens/metering/components/camera_container/event_container_camera.dart index d3e5995..fe0713d 100644 --- a/lib/screens/metering/components/camera_container/event_container_camera.dart +++ b/lib/screens/metering/components/camera_container/event_container_camera.dart @@ -1,3 +1,5 @@ +import 'package:flutter/gestures.dart'; + abstract class CameraContainerEvent { const CameraContainerEvent(); } @@ -53,3 +55,19 @@ class ExposureOffsetChangedEvent extends CameraContainerEvent { class ExposureOffsetResetEvent extends CameraContainerEvent { const ExposureOffsetResetEvent(); } + +class ExposureSpotChangedEvent extends CameraContainerEvent { + final Offset? offset; + + const ExposureSpotChangedEvent(this.offset); + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + return other is ExposureSpotChangedEvent && other.offset == offset; + } + + @override + int get hashCode => Object.hash(offset, runtimeType); +} 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 944aa34..a23545e 100644 --- a/lib/screens/metering/components/camera_container/widget_container_camera.dart +++ b/lib/screens/metering/components/camera_container/widget_container_camera.dart @@ -143,6 +143,9 @@ class _CameraViewBuilder extends StatelessWidget { builder: (context, state) => CameraPreview( controller: state is CameraInitializedState ? state.controller : null, error: state is CameraErrorState ? state.error : null, + onSpotTap: (value) { + context.read().add(ExposureSpotChangedEvent(value)); + }, ), ); } diff --git a/lib/screens/settings/components/metering/components/camera_features/widget_list_tile_camera_features.dart b/lib/screens/settings/components/metering/components/camera_features/widget_list_tile_camera_features.dart new file mode 100644 index 0000000..1446be3 --- /dev/null +++ b/lib/screens/settings/components/metering/components/camera_features/widget_list_tile_camera_features.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/data/models/camera_feature.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; +import 'package:lightmeter/screens/settings/components/shared/dialog_switch/widget_dialog_switch.dart'; +import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart'; + +class CameraFeaturesListTile extends StatelessWidget { + const CameraFeaturesListTile({super.key}); + + @override + Widget build(BuildContext context) { + return IAPListTile( + leading: const Icon(Icons.camera_alt), + title: Text(S.of(context).cameraFeatures), + onTap: () { + showDialog( + context: context, + builder: (_) => DialogSwitch( + icon: Icons.layers_outlined, + title: S.of(context).cameraFeatures, + values: UserPreferencesProvider.cameraConfigOf(context), + titleAdapter: (context, feature) { + switch (feature) { + case CameraFeature.spotMetering: + return S.of(context).cameraFeatureSpotMetering; + case CameraFeature.histogram: + return S.of(context).cameraFeatureHistogram; + } + }, + subtitleAdapter: (context, feature) { + switch (feature) { + case CameraFeature.spotMetering: + return S.of(context).cameraFeatureSpotMeteringHint; + case CameraFeature.histogram: + return S.of(context).cameraFeatureHistogramHint; + } + }, + onSave: UserPreferencesProvider.of(context).setCameraFeature, + ), + ); + }, + ); + } +} 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 deleted file mode 100644 index 2529e2e..0000000 --- a/lib/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart +++ /dev/null @@ -1,97 +0,0 @@ -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/equipment_profile_provider.dart'; -import 'package:lightmeter/providers/films_provider.dart'; -import 'package:lightmeter/providers/user_preferences_provider.dart'; -import 'package:lightmeter/res/dimens.dart'; -import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; - -class MeteringScreenLayoutFeaturesDialog extends StatefulWidget { - const MeteringScreenLayoutFeaturesDialog({super.key}); - - @override - State createState() => _MeteringScreenLayoutFeaturesDialogState(); -} - -class _MeteringScreenLayoutFeaturesDialogState extends State { - late final _features = MeteringScreenLayoutConfig.from(UserPreferencesProvider.meteringScreenConfigOf(context)); - - @override - Widget build(BuildContext context) { - return AlertDialog( - icon: const Icon(Icons.layers_outlined), - titlePadding: Dimens.dialogIconTitlePadding, - title: Text(S.of(context).meteringScreenLayout), - contentPadding: EdgeInsets.zero, - content: SizedBox( - width: double.maxFinite, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL), - child: Text(S.of(context).meteringScreenLayoutHint), - ), - const SizedBox(height: Dimens.grid16), - ListView( - shrinkWrap: true, - children: [ - _featureListTile(MeteringScreenLayoutFeature.equipmentProfiles), - _featureListTile(MeteringScreenLayoutFeature.extremeExposurePairs), - _featureListTile(MeteringScreenLayoutFeature.filmPicker), - _featureListTile(MeteringScreenLayoutFeature.histogram), - ], - ), - ], - ), - ), - actionsPadding: Dimens.dialogActionsPadding, - actions: [ - TextButton( - onPressed: Navigator.of(context).pop, - child: Text(S.of(context).cancel), - ), - TextButton( - onPressed: () { - if (!_features[MeteringScreenLayoutFeature.equipmentProfiles]!) { - EquipmentProfileProvider.of(context).setProfile(EquipmentProfiles.of(context).first); - } - if (!_features[MeteringScreenLayoutFeature.filmPicker]!) { - FilmsProvider.of(context).setFilm(const Film.other()); - } - UserPreferencesProvider.of(context).setMeteringScreenLayout(_features); - Navigator.of(context).pop(); - }, - child: Text(S.of(context).save), - ), - ], - ); - } - - Widget _featureListTile(MeteringScreenLayoutFeature f) { - return SwitchListTile( - contentPadding: EdgeInsets.symmetric(horizontal: Dimens.dialogTitlePadding.left), - title: Text(_toStringLocalized(context, f)), - value: _features[f]!, - onChanged: (value) { - setState(() { - _features.update(f, (_) => value); - }); - }, - ); - } - - String _toStringLocalized(BuildContext context, MeteringScreenLayoutFeature feature) { - switch (feature) { - case MeteringScreenLayoutFeature.equipmentProfiles: - return S.of(context).meteringScreenLayoutHintEquipmentProfiles; - case MeteringScreenLayoutFeature.extremeExposurePairs: - return S.of(context).meteringScreenFeatureExtremeExposurePairs; - case MeteringScreenLayoutFeature.filmPicker: - return S.of(context).meteringScreenFeatureFilmPicker; - case MeteringScreenLayoutFeature.histogram: - return S.of(context).meteringScreenFeatureHistogram; - } - } -} diff --git a/lib/screens/settings/components/metering/components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart b/lib/screens/settings/components/metering/components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart index a540926..1f89b4b 100644 --- a/lib/screens/settings/components/metering/components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart +++ b/lib/screens/settings/components/metering/components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:lightmeter/generated/l10n.dart'; - -import 'package:lightmeter/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart'; +import 'package:lightmeter/providers/equipment_profile_provider.dart'; +import 'package:lightmeter/providers/films_provider.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; +import 'package:lightmeter/screens/settings/components/shared/dialog_switch/widget_dialog_switch.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class MeteringScreenLayoutListTile extends StatelessWidget { const MeteringScreenLayoutListTile({super.key}); @@ -14,9 +18,35 @@ class MeteringScreenLayoutListTile extends StatelessWidget { onTap: () { showDialog( context: context, - builder: (_) => const MeteringScreenLayoutFeaturesDialog(), + builder: (_) => DialogSwitch( + icon: Icons.layers_outlined, + title: S.of(context).meteringScreenLayout, + description: S.of(context).meteringScreenLayoutHint, + values: UserPreferencesProvider.meteringScreenConfigOf(context), + titleAdapter: _toStringLocalized, + onSave: (value) { + if (!value[MeteringScreenLayoutFeature.equipmentProfiles]!) { + EquipmentProfileProvider.of(context).setProfile(EquipmentProfiles.of(context).first); + } + if (!value[MeteringScreenLayoutFeature.filmPicker]!) { + FilmsProvider.of(context).setFilm(const Film.other()); + } + UserPreferencesProvider.of(context).setMeteringScreenLayout(value); + }, + ), ); }, ); } + + String _toStringLocalized(BuildContext context, MeteringScreenLayoutFeature feature) { + switch (feature) { + case MeteringScreenLayoutFeature.equipmentProfiles: + return S.of(context).meteringScreenLayoutHintEquipmentProfiles; + case MeteringScreenLayoutFeature.extremeExposurePairs: + return S.of(context).meteringScreenFeatureExtremeExposurePairs; + case MeteringScreenLayoutFeature.filmPicker: + return S.of(context).meteringScreenFeatureFilmPicker; + } + } } diff --git a/lib/screens/settings/components/metering/widget_settings_section_metering.dart b/lib/screens/settings/components/metering/widget_settings_section_metering.dart index 90de68d..0c6c07d 100644 --- a/lib/screens/settings/components/metering/widget_settings_section_metering.dart +++ b/lib/screens/settings/components/metering/widget_settings_section_metering.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/screens/settings/components/metering/components/calibration/widget_list_tile_calibration.dart'; +import 'package:lightmeter/screens/settings/components/metering/components/camera_features/widget_list_tile_camera_features.dart'; import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/widget_list_tile_equipment_profiles.dart'; import 'package:lightmeter/screens/settings/components/metering/components/films/widget_list_tile_films.dart'; import 'package:lightmeter/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart'; @@ -20,6 +21,7 @@ class MeteringSettingsSection extends StatelessWidget { MeteringScreenLayoutListTile(), EquipmentProfilesListTile(), FilmsListTile(), + CameraFeaturesListTile(), ], ); } diff --git a/lib/screens/settings/components/shared/dialog_switch/widget_dialog_switch.dart b/lib/screens/settings/components/shared/dialog_switch/widget_dialog_switch.dart new file mode 100644 index 0000000..aa3a370 --- /dev/null +++ b/lib/screens/settings/components/shared/dialog_switch/widget_dialog_switch.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/res/dimens.dart'; + +typedef StringAdapter = String Function(BuildContext context, T value); + +class DialogSwitch extends StatefulWidget { + final IconData icon; + final String title; + final String? description; + final Map values; + final StringAdapter titleAdapter; + final StringAdapter? subtitleAdapter; + final ValueChanged> onSave; + + const DialogSwitch({ + required this.icon, + required this.title, + this.description, + required this.values, + required this.titleAdapter, + this.subtitleAdapter, + required this.onSave, + super.key, + }); + + @override + State> createState() => _DialogSwitchState(); +} + +class _DialogSwitchState extends State> { + late final Map _features = Map.from(widget.values); + + @override + Widget build(BuildContext context) { + return AlertDialog( + icon: Icon(widget.icon), + titlePadding: Dimens.dialogIconTitlePadding, + title: Text(widget.title), + contentPadding: EdgeInsets.zero, + content: SizedBox( + width: double.maxFinite, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.description != null) ...[ + Padding( + padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL), + child: Text(widget.description!), + ), + const SizedBox(height: Dimens.grid16) + ], + ListView( + shrinkWrap: true, + children: _features.entries + .map( + (entry) => SwitchListTile( + contentPadding: EdgeInsets.symmetric(horizontal: Dimens.dialogTitlePadding.left), + title: Text(widget.titleAdapter(context, entry.key)), + subtitle: widget.subtitleAdapter != null + ? Text( + widget.subtitleAdapter!.call(context, entry.key), + style: Theme.of(context).listTileTheme.subtitleTextStyle, + ) + : null, + value: _features[entry.key]!, + onChanged: (value) { + setState(() { + _features.update(entry.key, (_) => value); + }); + }, + ), + ) + .toList(), + ), + ], + ), + ), + actionsPadding: Dimens.dialogActionsPadding, + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: Text(S.of(context).cancel), + ), + TextButton( + onPressed: () { + widget.onSave(_features); + Navigator.of(context).pop(); + }, + child: Text(S.of(context).save), + ), + ], + ); + } +} diff --git a/lib/screens/settings/utils/show_buy_pro_dialog.dart b/lib/screens/settings/utils/show_buy_pro_dialog.dart index 7ce3ff6..181259f 100644 --- a/lib/screens/settings/utils/show_buy_pro_dialog.dart +++ b/lib/screens/settings/utils/show_buy_pro_dialog.dart @@ -7,6 +7,31 @@ import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; Future showBuyProDialog(BuildContext context) { final unlockFeaturesEnabled = RemoteConfig.isEnabled(context, Feature.unlockProFeaturesText); + + Widget splitDescription() { + final description = + unlockFeaturesEnabled ? S.of(context).unlockProFeaturesDescription : S.of(context).lightmeterProDescription; + final paragraphs = description.split('\n\n'); + final features = paragraphs.first.split('\n \u2022 ').sublist(1); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(paragraphs.first.split('\n \u2022 ').first), + ...features.map( + (f) => Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('\u2022 '), + Flexible(child: Text(f)), + ], + ), + ), + Text('\n${paragraphs.last}'), + ], + ); + } + return showDialog( context: context, builder: (_) => AlertDialog( @@ -14,11 +39,7 @@ Future showBuyProDialog(BuildContext context) { titlePadding: Dimens.dialogIconTitlePadding, title: Text(unlockFeaturesEnabled ? S.of(context).proFeatures : S.of(context).lightmeterPro), contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL), - content: SingleChildScrollView( - child: Text( - unlockFeaturesEnabled ? S.of(context).unlockProFeaturesDescription : S.of(context).lightmeterProDescription, - ), - ), + content: SingleChildScrollView(child: splitDescription()), actionsPadding: Dimens.dialogActionsPadding, actions: [ TextButton( diff --git a/lib/utils/map_model.dart b/lib/utils/map_model.dart new file mode 100644 index 0000000..1f5ea09 --- /dev/null +++ b/lib/utils/map_model.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +class MapModel extends InheritedModel { + final Map data; + + const MapModel({ + required this.data, + required super.child, + }); + + @override + bool updateShouldNotify(MapModel oldWidget) => oldWidget.data != data; + + @override + bool updateShouldNotifyDependent( + MapModel oldWidget, + Set dependencies, + ) { + for (final dependecy in dependencies) { + if (oldWidget.data[dependecy] != data[dependecy]) { + return true; + } + } + return false; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index eb53e59..d88de9f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,7 +28,7 @@ dependencies: m3_lightmeter_iap: git: url: "https://github.com/vodemn/m3_lightmeter_iap" - ref: v0.7.0 + ref: v0.7.1 m3_lightmeter_resources: git: url: "https://github.com/vodemn/m3_lightmeter_resources" diff --git a/screenshots/generate_screenshots.dart b/screenshots/generate_screenshots.dart index 8c8e7e9..814cfd1 100644 --- a/screenshots/generate_screenshots.dart +++ b/screenshots/generate_screenshots.dart @@ -46,7 +46,6 @@ void main() { MeteringScreenLayoutFeature.equipmentProfiles: true, MeteringScreenLayoutFeature.extremeExposurePairs: true, MeteringScreenLayoutFeature.filmPicker: true, - MeteringScreenLayoutFeature.histogram: false, }.toJson(), ), diff --git a/test/data/models/camera_features_config_test.dart b/test/data/models/camera_features_config_test.dart new file mode 100644 index 0000000..d7261a8 --- /dev/null +++ b/test/data/models/camera_features_config_test.dart @@ -0,0 +1,47 @@ +import 'package:lightmeter/data/models/camera_feature.dart'; +import 'package:test/test.dart'; + +void main() { + group( + 'fromJson()', + () { + test('All keys', () { + expect( + CameraFeaturesConfigJson.fromJson( + { + 'spotMetering': true, + 'histogram': true, + }, + ), + { + CameraFeature.spotMetering: true, + CameraFeature.histogram: true, + }, + ); + }); + + test('Legacy (no spotMetering & histogram)', () { + expect( + CameraFeaturesConfigJson.fromJson({}), + { + CameraFeature.spotMetering: false, + CameraFeature.histogram: false, + }, + ); + }); + }, + ); + + test('toJson()', () { + expect( + { + CameraFeature.spotMetering: true, + CameraFeature.histogram: true, + }.toJson(), + { + 'spotMetering': true, + 'histogram': true, + }, + ); + }); +} diff --git a/test/data/models/metering_screen_layout_config_test.dart b/test/data/models/metering_screen_layout_config_test.dart index 9e9393e..f3216ba 100644 --- a/test/data/models/metering_screen_layout_config_test.dart +++ b/test/data/models/metering_screen_layout_config_test.dart @@ -18,7 +18,6 @@ void main() { { MeteringScreenLayoutFeature.extremeExposurePairs: true, MeteringScreenLayoutFeature.filmPicker: true, - MeteringScreenLayoutFeature.histogram: true, MeteringScreenLayoutFeature.equipmentProfiles: true, }, ); @@ -35,7 +34,6 @@ void main() { { MeteringScreenLayoutFeature.extremeExposurePairs: false, MeteringScreenLayoutFeature.filmPicker: false, - MeteringScreenLayoutFeature.histogram: true, MeteringScreenLayoutFeature.equipmentProfiles: true, }, ); @@ -53,7 +51,6 @@ void main() { { MeteringScreenLayoutFeature.extremeExposurePairs: false, MeteringScreenLayoutFeature.filmPicker: false, - MeteringScreenLayoutFeature.histogram: false, MeteringScreenLayoutFeature.equipmentProfiles: true, }, ); @@ -67,13 +64,11 @@ void main() { MeteringScreenLayoutFeature.equipmentProfiles: true, MeteringScreenLayoutFeature.extremeExposurePairs: true, MeteringScreenLayoutFeature.filmPicker: true, - MeteringScreenLayoutFeature.histogram: true, }.toJson(), { - '3': true, - '0': true, - '1': true, - '2': true, + 'equipmentProfiles': true, + 'extremeExposurePairs': true, + 'filmPicker': true, }, ); }); diff --git a/test/data/shared_prefs_service_test.dart b/test/data/shared_prefs_service_test.dart index 8ec42e1..96f4e1e 100644 --- a/test/data/shared_prefs_service_test.dart +++ b/test/data/shared_prefs_service_test.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/data/models/camera_feature.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'; @@ -191,12 +192,11 @@ void main() { MeteringScreenLayoutFeature.extremeExposurePairs: true, MeteringScreenLayoutFeature.filmPicker: true, MeteringScreenLayoutFeature.equipmentProfiles: true, - MeteringScreenLayoutFeature.histogram: true, }, ); }); - test('get', () { + test('get (legacy)', () { when( () => sharedPreferences.getString(UserPreferencesService.meteringScreenLayoutKey), ).thenReturn("""{"0":false,"1":true}"""); @@ -206,7 +206,20 @@ void main() { MeteringScreenLayoutFeature.extremeExposurePairs: false, MeteringScreenLayoutFeature.filmPicker: true, MeteringScreenLayoutFeature.equipmentProfiles: true, - MeteringScreenLayoutFeature.histogram: true, + }, + ); + }); + + test('get', () { + when( + () => sharedPreferences.getString(UserPreferencesService.meteringScreenLayoutKey), + ).thenReturn("""{"extremeExposurePairs":false,"filmPicker":true}"""); + expect( + service.meteringScreenLayout, + { + MeteringScreenLayoutFeature.extremeExposurePairs: false, + MeteringScreenLayoutFeature.filmPicker: true, + MeteringScreenLayoutFeature.equipmentProfiles: true, }, ); }); @@ -215,19 +228,62 @@ void main() { when( () => sharedPreferences.setString( UserPreferencesService.meteringScreenLayoutKey, - """{"0":false,"1":true,"2":true,"3":true}""", + """{"extremeExposurePairs":false,"filmPicker":true,"equipmentProfiles":true}""", ), ).thenAnswer((_) => Future.value(true)); service.meteringScreenLayout = { MeteringScreenLayoutFeature.extremeExposurePairs: false, MeteringScreenLayoutFeature.filmPicker: true, - MeteringScreenLayoutFeature.histogram: true, MeteringScreenLayoutFeature.equipmentProfiles: true, }; verify( () => sharedPreferences.setString( UserPreferencesService.meteringScreenLayoutKey, - """{"0":false,"1":true,"2":true,"3":true}""", + """{"extremeExposurePairs":false,"filmPicker":true,"equipmentProfiles":true}""", + ), + ).called(1); + }); + }); + + group('cameraFeatures', () { + test('get default', () { + when(() => sharedPreferences.getString(UserPreferencesService.cameraFeaturesKey)).thenReturn(null); + expect( + service.cameraFeatures, + { + CameraFeature.spotMetering: false, + CameraFeature.histogram: false, + }, + ); + }); + + test('get', () { + when(() => sharedPreferences.getString(UserPreferencesService.cameraFeaturesKey)) + .thenReturn("""{"spotMetering":false,"histogram":true}"""); + expect( + service.cameraFeatures, + { + CameraFeature.spotMetering: false, + CameraFeature.histogram: true, + }, + ); + }); + + test('set', () { + when( + () => sharedPreferences.setString( + UserPreferencesService.cameraFeaturesKey, + """{"spotMetering":false,"histogram":true}""", + ), + ).thenAnswer((_) => Future.value(true)); + service.cameraFeatures = { + CameraFeature.spotMetering: false, + CameraFeature.histogram: true, + }; + verify( + () => sharedPreferences.setString( + UserPreferencesService.cameraFeaturesKey, + """{"spotMetering":false,"histogram":true}""", ), ).called(1); }); diff --git a/test/providers/user_preferences_provider_test.dart b/test/providers/user_preferences_provider_test.dart index ffa7993..b6dc2ae 100644 --- a/test/providers/user_preferences_provider_test.dart +++ b/test/providers/user_preferences_provider_test.dart @@ -1,6 +1,7 @@ import 'package:dynamic_color/test_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/data/models/camera_feature.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'; @@ -31,7 +32,10 @@ void main() { MeteringScreenLayoutFeature.extremeExposurePairs: true, MeteringScreenLayoutFeature.filmPicker: true, MeteringScreenLayoutFeature.equipmentProfiles: true, - MeteringScreenLayoutFeature.histogram: true, + }); + when(() => mockUserPreferencesService.cameraFeatures).thenReturn({ + CameraFeature.spotMetering: true, + CameraFeature.histogram: true, }); when(() => mockUserPreferencesService.locale).thenReturn(SupportedLocale.en); when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light); @@ -184,7 +188,6 @@ void main() { MeteringScreenLayoutFeature.equipmentProfiles: true, MeteringScreenLayoutFeature.extremeExposurePairs: false, MeteringScreenLayoutFeature.filmPicker: false, - MeteringScreenLayoutFeature.histogram: true, }), child: const Text(''), ), @@ -196,20 +199,64 @@ void main() { expect(find.text("${MeteringScreenLayoutFeature.equipmentProfiles}: true"), findsNWidgets(2)); expect(find.text("${MeteringScreenLayoutFeature.extremeExposurePairs}: true"), findsNWidgets(2)); expect(find.text("${MeteringScreenLayoutFeature.filmPicker}: true"), findsNWidgets(2)); - expect(find.text("${MeteringScreenLayoutFeature.histogram}: true"), findsNWidgets(2)); await tester.tap(find.byType(ElevatedButton)); await tester.pumpAndSettle(); expect(find.text("${MeteringScreenLayoutFeature.equipmentProfiles}: true"), findsNWidgets(2)); expect(find.text("${MeteringScreenLayoutFeature.extremeExposurePairs}: false"), findsNWidgets(2)); expect(find.text("${MeteringScreenLayoutFeature.filmPicker}: false"), findsNWidgets(2)); - expect(find.text("${MeteringScreenLayoutFeature.histogram}: true"), findsNWidgets(2)); verify( () => mockUserPreferencesService.meteringScreenLayout = { MeteringScreenLayoutFeature.extremeExposurePairs: false, MeteringScreenLayoutFeature.filmPicker: false, MeteringScreenLayoutFeature.equipmentProfiles: true, - MeteringScreenLayoutFeature.histogram: true, + }, + ).called(1); + }, + ); + + testWidgets( + 'Set camera features config', + (tester) async { + await pumpTestWidget( + tester, + builder: (context) { + final config = UserPreferencesProvider.cameraConfigOf(context); + return Column( + children: [ + ...List.generate( + config.length, + (index) => Text('${config.keys.toList()[index]}: ${config.values.toList()[index]}'), + ), + ...List.generate( + CameraFeature.values.length, + (index) => Text( + '${CameraFeature.values[index]}: ${UserPreferencesProvider.cameraFeatureOf(context, CameraFeature.values[index])}', + ), + ), + ElevatedButton( + onPressed: () => UserPreferencesProvider.of(context).setCameraFeature({ + CameraFeature.spotMetering: true, + CameraFeature.histogram: false, + }), + child: const Text(''), + ), + ], + ); + }, + ); + // Match `findsNWidgets(2)` to verify that `cameraFeatureOf` specific results are the same as the whole config + expect(find.text("${CameraFeature.spotMetering}: true"), findsNWidgets(2)); + expect(find.text("${CameraFeature.histogram}: true"), findsNWidgets(2)); + + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + expect(find.text("${CameraFeature.spotMetering}: true"), findsNWidgets(2)); + expect(find.text("${CameraFeature.histogram}: false"), findsNWidgets(2)); + verify( + () => mockUserPreferencesService.cameraFeatures = { + CameraFeature.spotMetering: true, + CameraFeature.histogram: false, }, ).called(1); }, 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 d54c0a5..52c57c8 100644 --- a/test/screens/metering/components/camera/bloc_container_camera_test.dart +++ b/test/screens/metering/components/camera/bloc_container_camera_test.dart @@ -4,10 +4,8 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:lightmeter/interactors/metering_interactor.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; -import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' - as communication_events; -import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' - as communication_states; +import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' as communication_events; +import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' as communication_states; 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/models/camera_error_type.dart'; @@ -16,9 +14,9 @@ import 'package:mocktail/mocktail.dart'; class _MockMeteringInteractor extends Mock implements MeteringInteractor {} -class _MockMeteringCommunicationBloc extends MockBloc< - communication_events.MeteringCommunicationEvent, - communication_states.MeteringCommunicationState> implements MeteringCommunicationBloc {} +class _MockMeteringCommunicationBloc + extends MockBloc + implements MeteringCommunicationBloc {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -147,8 +145,7 @@ void main() { verify(() => meteringInteractor.requestCameraPermission()).called(1); }, expect: () => [ - isA() - .having((state) => state.error, "error", CameraErrorType.permissionNotGranted), + isA().having((state) => state.error, "error", CameraErrorType.permissionNotGranted), ], ); @@ -166,8 +163,7 @@ void main() { }, expect: () => [ isA(), - isA() - .having((state) => state.error, "error", CameraErrorType.permissionNotGranted), + isA().having((state) => state.error, "error", CameraErrorType.permissionNotGranted), ], ); @@ -215,8 +211,7 @@ void main() { 'No cameras detected error', setUp: () { when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => true); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( cameraMethodChannel, (methodCall) async => cameraMethodCallSuccessHandler(methodCall, cameras: const []), ); @@ -232,8 +227,7 @@ void main() { }, expect: () => [ isA(), - isA() - .having((state) => state.error, "error", CameraErrorType.noCamerasDetected), + isA().having((state) => state.error, "error", CameraErrorType.noCamerasDetected), ], ); @@ -241,8 +235,7 @@ void main() { 'No back facing cameras available', setUp: () { when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => true); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( cameraMethodChannel, (methodCall) async => cameraMethodCallSuccessHandler(methodCall, cameras: frontCameras), ); @@ -263,8 +256,7 @@ void main() { 'Catch other initialization errors', setUp: () { when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => true); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( cameraMethodChannel, (methodCall) async { switch (methodCall.method) { @@ -300,10 +292,8 @@ void main() { act: (bloc) async { bloc.add(const InitializeEvent()); await Future.delayed(Duration.zero); - TestWidgetsFlutterBinding.instance - .handleAppLifecycleStateChanged(AppLifecycleState.detached); - TestWidgetsFlutterBinding.instance - .handleAppLifecycleStateChanged(AppLifecycleState.resumed); + TestWidgetsFlutterBinding.instance.handleAppLifecycleStateChanged(AppLifecycleState.detached); + TestWidgetsFlutterBinding.instance.handleAppLifecycleStateChanged(AppLifecycleState.resumed); }, verify: (_) { verify(() => meteringInteractor.checkCameraPermission()).called(2); @@ -500,6 +490,29 @@ void main() { ); }, ); + + group( + '`ExposureSpotChangedEvent`', + () { + blocTest( + 'Set exposure spot multiple times', + setUp: () { + when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => true); + }, + build: () => bloc, + act: (bloc) async { + bloc.add(const InitializeEvent()); + await Future.delayed(Duration.zero); + bloc.add(const ExposureSpotChangedEvent(Offset(0.1, 0.1))); + bloc.add(const ExposureSpotChangedEvent(Offset(1.0, 0.5))); + }, + verify: (_) { + verify(() => meteringInteractor.checkCameraPermission()).called(1); + }, + expect: () => [...initializedStateSequence], + ); + }, + ); } extension _MethodChannelMock on MethodChannel { diff --git a/test/screens/metering/components/camera/event_container_camera_test.dart b/test/screens/metering/components/camera/event_container_camera_test.dart index f136b5f..3751f10 100644 --- a/test/screens/metering/components/camera/event_container_camera_test.dart +++ b/test/screens/metering/components/camera/event_container_camera_test.dart @@ -1,5 +1,7 @@ // ignore_for_file: prefer_const_constructors +import 'dart:ui'; + import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart'; import 'package:test/test.dart'; @@ -41,4 +43,23 @@ void main() { }); }, ); + + group( + '`ExposureSpotChangedEvent`', + () { + final a = ExposureSpotChangedEvent(Offset(0.0, 0.0)); + final b = ExposureSpotChangedEvent(Offset(0.0, 0.0)); + final c = ExposureSpotChangedEvent(Offset(2.0, 2.0)); + test('==', () { + expect(a == b && b == a, true); + expect(a != c && c != a, true); + expect(b != c && c != b, true); + }); + test('hashCode', () { + expect(a.hashCode == b.hashCode, true); + expect(a.hashCode != c.hashCode, true); + expect(b.hashCode != c.hashCode, true); + }); + }, + ); } From 19fc039723bf87eb34f82cc06b2e3aab7d59bf78 Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Tue, 14 Nov 2023 12:26:34 +0100 Subject: [PATCH 10/13] ML-137 Dialogs improvements (#138) * Force dialogs to have the same width * Fix `DialogPicker` bouncing when the first selected item is near the end --- .../dialog_filter/widget_dialog_filter.dart | 148 +++++++++--------- .../dialog_picker/widget_dialog_picker.dart | 40 ++--- .../widget_dialog_picker_range.dart | 79 +++++----- 3 files changed, 139 insertions(+), 128 deletions(-) diff --git a/lib/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart b/lib/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart index 76cd729..eb03e23 100644 --- a/lib/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart +++ b/lib/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/res/dimens.dart'; @@ -34,18 +35,20 @@ class _DialogFilterState extends State> { bool get _hasAnySelected => checkboxValues.contains(true); bool get _hasAnyUnselected => checkboxValues.contains(false); - late final ScrollController _scrollController; + final ScrollController _scrollController = ScrollController(); @override void initState() { super.initState(); - int i = 0; - for (; i < checkboxValues.length; i++) { - if (checkboxValues[i]) { - break; + SchedulerBinding.instance.addPostFrameCallback((_) { + int i = 0; + for (; i < checkboxValues.length; i++) { + if (checkboxValues[i]) { + break; + } } - } - _scrollController = ScrollController(initialScrollOffset: Dimens.grid56 * i); + _scrollController.jumpTo((Dimens.grid56 * i).clamp(0, _scrollController.position.maxScrollExtent)); + }); } @override @@ -61,79 +64,80 @@ class _DialogFilterState extends State> { titlePadding: Dimens.dialogIconTitlePadding, title: Text(widget.title), contentPadding: EdgeInsets.zero, - content: Column( - children: [ - Padding( - padding: Dimens.dialogIconTitlePadding, - child: Text(widget.description), - ), - const Divider(), - Expanded( - child: SingleChildScrollView( - controller: _scrollController, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: List.generate( - widget.values.length, - (index) => CheckboxListTile( - value: checkboxValues[index], - controlAffinity: ListTileControlAffinity.leading, - title: Text( - widget.titleAdapter(context, widget.values[index]), - style: Theme.of(context).textTheme.bodyLarge, + content: SizedBox( + width: double.maxFinite, + child: Column( + children: [ + Padding( + padding: Dimens.dialogIconTitlePadding, + child: Text(widget.description), + ), + const Divider(), + Expanded( + child: SingleChildScrollView( + controller: _scrollController, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: List.generate( + widget.values.length, + (index) => CheckboxListTile( + value: checkboxValues[index], + controlAffinity: ListTileControlAffinity.leading, + title: Text( + widget.titleAdapter(context, widget.values[index]), + style: Theme.of(context).textTheme.bodyLarge, + ), + onChanged: (value) { + if (value != null) { + setState(() { + checkboxValues[index] = value; + }); + } + }, ), - onChanged: (value) { - if (value != null) { - setState(() { - checkboxValues[index] = value; - }); - } - }, ), ), ), ), - ), - const Divider(), - Padding( - padding: Dimens.dialogActionsPadding, - child: Row( - children: [ - SizedBox( - width: 40, - child: IconButton( - padding: EdgeInsets.zero, - icon: Icon(_hasAnyUnselected ? Icons.select_all : Icons.deselect), - onPressed: _toggleAll, - tooltip: _hasAnyUnselected - ? S.of(context).tooltipSelectAll - : S.of(context).tooltipDesecelectAll, + const Divider(), + Padding( + padding: Dimens.dialogActionsPadding, + child: Row( + children: [ + SizedBox( + width: 40, + child: IconButton( + padding: EdgeInsets.zero, + icon: Icon(_hasAnyUnselected ? Icons.select_all : Icons.deselect), + onPressed: _toggleAll, + tooltip: _hasAnyUnselected ? S.of(context).tooltipSelectAll : S.of(context).tooltipDesecelectAll, + ), ), - ), - const Spacer(), - TextButton( - onPressed: Navigator.of(context).pop, - child: Text(S.of(context).cancel), - ), - TextButton( - onPressed: _hasAnySelected - ? () { - final List selectedValues = []; - for (int i = 0; i < widget.values.length; i++) { - if (checkboxValues[i]) { - selectedValues.add(widget.values[i]); + const Spacer(), + TextButton( + onPressed: Navigator.of(context).pop, + child: Text(S.of(context).cancel), + ), + TextButton( + onPressed: _hasAnySelected + ? () { + final List selectedValues = []; + for (int i = 0; i < widget.values.length; i++) { + if (checkboxValues[i]) { + selectedValues.add(widget.values[i]); + } } + Navigator.of(context).pop(selectedValues); } - Navigator.of(context).pop(selectedValues); - } - : null, - child: Text(S.of(context).save), - ), - ], - ), - ) - ], + : null, + child: Text(S.of(context).save), + ), + ], + ), + ) + ], + ), ), ); } diff --git a/lib/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart b/lib/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart index c893027..dc31b5c 100644 --- a/lib/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart +++ b/lib/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart @@ -32,24 +32,28 @@ class _DialogPickerState extends State> { titlePadding: Dimens.dialogIconTitlePadding, title: Text(widget.title), contentPadding: EdgeInsets.zero, - content: Column( - mainAxisSize: MainAxisSize.min, - children: widget.values - .map( - (e) => RadioListTile( - value: e, - groupValue: _selected, - title: Text(widget.titleAdapter(context, e)), - onChanged: (T? value) { - if (value != null) { - setState(() { - _selected = value; - }); - } - }, - ), - ) - .toList(), + content: SizedBox( + width: double.maxFinite, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: widget.values + .map( + (e) => RadioListTile( + value: e, + groupValue: _selected, + title: Text(widget.titleAdapter(context, e)), + onChanged: (T? value) { + if (value != null) { + setState(() { + _selected = value; + }); + } + }, + ), + ) + .toList(), + ), ), actionsPadding: Dimens.dialogActionsPadding, actions: [ diff --git a/lib/screens/settings/components/shared/dialog_range_picker/widget_dialog_picker_range.dart b/lib/screens/settings/components/shared/dialog_range_picker/widget_dialog_picker_range.dart index cc3f4ca..56f7967 100644 --- a/lib/screens/settings/components/shared/dialog_range_picker/widget_dialog_picker_range.dart +++ b/lib/screens/settings/components/shared/dialog_range_picker/widget_dialog_picker_range.dart @@ -36,47 +36,50 @@ class _DialogRangePickerState extends State Date: Tue, 14 Nov 2023 12:30:33 +0000 Subject: [PATCH 11/13] Version bump --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index d88de9f..3cfdf6d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: lightmeter description: Lightmeter app inspired by Material 3 design system. publish_to: "none" -version: 0.15.3+44 +version: 0.16.0+45 environment: sdk: ">=3.0.0 <4.0.0" From 561f849eea2adbe261dc618326d0b6e17b547c8a Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Tue, 21 Nov 2023 21:37:23 +0100 Subject: [PATCH 12/13] Add app icon indicating dev build (#139) * [Android] added (DEV) to dev flavor app name * [Android] added `applicationIdSuffix` instead of explicit `applicationId` * [Android] removed main/res for app to be able to see flavored resources * replaced `flutter_launcher_icons` with `icons_launcher` * [Android] generated icon for dev & prod flavors * Create README.md * [iOS] generated icons for dev & prod flavors * [iOS] added (DEV) to dev flavor app name * [iOS] cleanup --- android/app/build.gradle | 5 +- android/app/src/dev/ic_launcher-playstore.png | Bin 0 -> 24066 bytes .../res/mipmap-anydpi-v26/ic_launcher.xml | 0 .../mipmap-anydpi-v26/ic_launcher_round.xml | 0 .../src/dev/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2055 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 2882 bytes .../dev/res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3788 bytes .../src/dev/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1326 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 1721 bytes .../dev/res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2169 bytes .../src/dev/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 2878 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 4283 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5486 bytes .../src/dev/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 4462 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 7398 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 8763 bytes .../dev/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 6355 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 11041 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 12788 bytes .../dev/res/values/ic_launcher_background.xml | 4 + android/app/src/main/AndroidManifest.xml | 2 +- .../res/mipmap-anydpi-v26/launcher_icon.xml | 5 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 1451 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 2865 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 3101 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 784 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 1382 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 1587 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 2087 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 4471 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 4436 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 3268 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 8485 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 7072 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 4669 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 13009 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 10473 -> 0 bytes .../app/src/prod/ic_launcher-playstore.png | Bin 0 -> 15132 bytes .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/prod/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 1450 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 1866 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3048 bytes .../src/prod/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 787 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 1105 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 1610 bytes .../src/prod/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 2058 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 2835 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 4404 bytes .../prod/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 3151 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 4858 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 6937 bytes .../prod/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 4468 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 7224 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 10193 bytes .../res/values/ic_launcher_background.xml | 4 + assets/README.md | 11 ++ assets/launcher_icon_circle.png | Bin 3977 -> 0 bytes assets/launcher_icon_dev_1024.png | Bin 0 -> 21938 bytes assets/launcher_icon_dev_512.png | Bin 0 -> 14568 bytes assets/launcher_icon_prod_1024.png | Bin 0 -> 14418 bytes assets/launcher_icon_prod_512.png | Bin 0 -> 6166 bytes assets/launcher_icon_square.png | Bin 2256 -> 0 bytes ios/Podfile | 6 + ios/Runner.xcodeproj/project.pbxproj | 85 +++++++++--- .../AppIcon-dev.appiconset/Contents.json | 14 ++ .../Icon square (dev).png | Bin 0 -> 21938 bytes .../AppIcon-prod.appiconset/Contents.json | 14 ++ .../AppIcon-prod.appiconset/Icon square.png | Bin 0 -> 14418 bytes .../AppIcon.appiconset/Contents.json | 122 ------------------ .../Icon-App-1024x1024@1x.png | Bin 34665 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 323 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 626 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 1008 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 476 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 964 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 1420 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 626 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 1268 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 1786 -> 0 bytes .../AppIcon.appiconset/Icon-App-50x50@1x.png | Bin 785 -> 0 bytes .../AppIcon.appiconset/Icon-App-50x50@2x.png | Bin 1532 -> 0 bytes .../AppIcon.appiconset/Icon-App-57x57@1x.png | Bin 942 -> 0 bytes .../AppIcon.appiconset/Icon-App-57x57@2x.png | Bin 1771 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 1786 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 2305 -> 0 bytes .../AppIcon.appiconset/Icon-App-72x72@1x.png | Bin 1184 -> 0 bytes .../AppIcon.appiconset/Icon-App-72x72@2x.png | Bin 2041 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 1269 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 2113 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 2241 -> 0 bytes ios/Runner/Assets.xcassets/Contents.json | 6 + ios/Runner/Info.plist | 96 +++++++------- pubspec.yaml | 3 +- 94 files changed, 192 insertions(+), 195 deletions(-) create mode 100644 android/app/src/dev/ic_launcher-playstore.png rename android/app/src/{main => dev}/res/mipmap-anydpi-v26/ic_launcher.xml (100%) rename android/app/src/{main => dev}/res/mipmap-anydpi-v26/ic_launcher_round.xml (100%) create mode 100644 android/app/src/dev/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/dev/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 android/app/src/dev/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 android/app/src/dev/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/dev/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 android/app/src/dev/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 android/app/src/dev/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/dev/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/dev/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 android/app/src/dev/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 android/app/src/dev/res/values/ic_launcher_background.xml delete mode 100644 android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml delete mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 android/app/src/prod/ic_launcher-playstore.png create mode 100644 android/app/src/prod/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 android/app/src/prod/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 android/app/src/prod/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/prod/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 android/app/src/prod/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 android/app/src/prod/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/prod/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 android/app/src/prod/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 android/app/src/prod/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/prod/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/prod/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 android/app/src/prod/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 android/app/src/prod/res/values/ic_launcher_background.xml create mode 100644 assets/README.md delete mode 100644 assets/launcher_icon_circle.png create mode 100644 assets/launcher_icon_dev_1024.png create mode 100644 assets/launcher_icon_dev_512.png create mode 100755 assets/launcher_icon_prod_1024.png create mode 100644 assets/launcher_icon_prod_512.png delete mode 100644 assets/launcher_icon_square.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon square (dev).png create mode 100644 ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Icon square.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 ios/Runner/Assets.xcassets/Contents.json diff --git a/android/app/build.gradle b/android/app/build.gradle index 5ccd388..be85756 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -75,12 +75,13 @@ android { flavorDimensions "app" productFlavors { dev { - applicationId "com.vodemn.lightmeter.dev" + resValue "string", "app_name", "Lightmeter (DEV)" dimension "app" signingConfig signingConfigs.release + applicationIdSuffix ".dev" } prod { - applicationId "com.vodemn.lightmeter" + resValue "string", "app_name", "Lightmeter" dimension "app" signingConfig signingConfigs.release } diff --git a/android/app/src/dev/ic_launcher-playstore.png b/android/app/src/dev/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..00039fd87d913bced7dae9ef768ae517fa2fb696 GIT binary patch literal 24066 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 literal 0 HcmV?d00001 diff --git a/android/app/src/dev/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/dev/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..c7d106b343f6d287ff481ed4602901a4edbd792d GIT binary patch literal 1721 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz1|<8_!p|}=ur+wPIEGZjy`6J1TO?EB*x&FF zmcs6iM}3A`UEEg1pC8FD5I6s_L_F|d^9#p|3QZj=G%jv_&{6!!pqS(4aUEgVWU;B8 ziy~w0y=a}cu(}~#;Af_KD)3JYJ&-~?z zAOj3?1&>@?8~yak%HU5Q9y({{=1$$X(QxLRIaB2A>sTJ;>m2?sXHy|yBfkD3$Hd=D z`NU^TdwqTVbnoeUp7HVV0$!?LzI|hR-}wE}cYp79)2(6_WY3;8>&eB%?nb7jNB^#$ z@$zLR5KKHf+g!YMUiQxTR|SphVm768#_X@Vn;F1!Z}-lfmN(vie7<`U$nY13YJY$8 ztgpBKZf?rP#@2NHiB-%Akl)u_6ISN;ZK7S;=*!Jovu;8(7as49sY`&}qyHeHc4wZ>3 zxcd}rjgGqv^HpmLu_wDG?<)KE=M78SW}Zwrk=;zDk8-%L%c~#ymHYA%P&C+iUqf(m zO#PIf8*WKSZ4#95+xMtuo_>O^ch#XC25Zi+v>89!*Es8E^#++RHKxO+nlaBl@Wx#0 z+%+#uYmE;}8+WH&Q+C0Mx%>HDBK*Burgk1E_%X_uj$rD*-33rYZGSFh4rgfO>Vj+Ew1Y&1C)Q5lE2h= z-mAuw;p?A#7uD`^>I8*qyU^0}jlO&9bnjZ1iLeT(1EZZ!)hph-GkB-z>PZ{+@Si#E z=x&-=DKGIQGa^1WVkQR<`+U2Amv-Gx>sB6aJo57JO3m;@M_}yAc+9bAj5@FEIvvK9 zsCW?MYWv7a2NZnFo=LWG2Yi&**h?D;N$73Aewk&?jty?>Vke*To!vFf{paybsh>_a zvEJn1NlNmzosjb4-@>J?ZtLQ7qwHpLyuKb2@naq^YAiaIB)yvPEoz#I&W+94(|!2) zy^r78>iR#K`|zod1cAvWTRXV3x9p6(cYn9~-P_yQ`|sc1ukS3bCt|Cw1$KwChPzMm zx~IL>m5;tfZqP3OVOaKgS&gjh)bjH8UiTZBjb^UiT6=ZXqUYzt#GU}X$T}^1ZGKw^ z*Xr7*rxMQox^qkGect_P=X#}?+x6r5;=cdAtp4-)mzRsKpM|OqOIn*}-o?eeZ+Ef! z4W4iB?(6@zt5gz>jlJ~m^RMvZudaT2c1H5&?+Xi$fWs*!Vd=73XZd#1f)5LNVs;8; z)fsF!!FqXb?rfhuHJZ)s{NA@eJvCmqu@I`-beX7BJ8N~>`)Mz?=Qk&>`D65Eah$BZ zp77qi|J$cVn^iq-s8#@m^atw%NLgO+BLEN7R<=jR|S_Htr8tOc3jlYdeO&t?8e&YPfOS8O8Bs3 zN9BEP|FEz2kH+Kk^G;rQuz+W-eY{B6owN-B;0&&9_@CMQPiR$vtf-iafxMhO-#35b z>|5tARDF9h@nG`tCF#%4tvb{ME~KtMI%a>zJM7$B&%(rm{`zqz9!yj=?)QFsc@-}= zxG=kH^#5@}Vfj9nx0|_+CM5lNc{!o?l>Xr;^SRam|FYl5sY8S0me{{Pa?f6hgGgRA zPVTb28w!(aYd_h2my?JoF*Q3i{oJ_(kN1%H=e{DKnBXL2=2#u!E2A0K>T@N%U4Zs%H#G4jyG#=g?R1K@eK;#B9^xn z>f6}Y#Sq(HTrxRSR2eYG<=yl|i8d_g%C!FsA?G`N=DqsD0<4l4JYD@<);T3K0RXqy BM0EfF literal 0 HcmV?d00001 diff --git a/android/app/src/dev/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/dev/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..89c0f9f918905bce32453a1f41537a51bc497156 GIT binary patch literal 2169 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 literal 0 HcmV?d00001 diff --git a/android/app/src/dev/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/dev/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..79cc56fdba89d5c62e6c6de75e270ecf5eb285ac GIT binary patch literal 4283 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 literal 0 HcmV?d00001 diff --git a/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..f1c2f64bac8bcf23d8191dae7fb8d4ff261186f8 GIT binary patch literal 4462 zcmZ`dbyO2xxNJ0|8%B(-NvhO@(Ip)s;-m$nq>=6oQ5sRYM@x5i35fhax=W2=7E-~l8Ha! zXEvz^gDGPSejGu}BhqJKiG-Sva1yct?g}JN`?z4LWLyMaoDnyPpgrl>NP(1DSs0^@ z5TkRr%zJxTW1xloMjs^?oCKl+sRSg$&uNdd?n>l4mO~Oc8eeoDdzE*MT_=*f z@^iM?)@I@VU$>2Ns?Rrx_zQZ7Pt{D!vjO--%gn|P93Q@V`g~IQKl+r1EzUm{YHeMd zot(nb+ciJ2!L_x={F?!7cocQXs|b7Ugk8|VhwWD( zZzfh^OENjg@*SF$xa`W~URFk5(qMHP+}A?T?P8PnKaT!BoU7oVaHzTI$w0%e%T;12 z1AZ0^7c;LQzONmKs z(K_=nf6O#JISS1~tICt)ITW=M&CuBgJzkyNeg(!t>L$omC*i^I$qw|XFI)J( znipb);-i#?sJ;jK&`t_&Ck%zs zzX-t=Ge3IMP#iS?JXcm%kI8@q)Gc%bc#Wi^t-=%avgP>9sx5`Cf1ik@`1&k6^aG9H zHZZ#p>RR8Q79ZEk^%}^qSgg%ZJPVC}%Tu^pQEBNojMy~kP+TO!`6Sc5D=E#YsDK8f ziQoDqjzNSweUCr|fyl`aw~mO)=KTmh*MqOcCC4uE7I534c+{t$;U@+X)fSD~yIzNDU`jR-h$=@rN`shN zxooY>SfGE6-I+}YvJLZn+SCD{+3?h9QQMXQT0sEe2%WA_%KnCR+ zUX43qE#mUNN9O&OQDnm;BzMsm_xPdU94qYA{0p^QgSr4;h!gk zY>GZ>fiw3DMr{Z;>R>fwZ;I2?W;BxIvks#mS$y0oYduXmVCes+s{ zlJ+R@7jm^T5WFj7AUX09B&3FMpRfK!M_n+Le(xE1%D&y{)a7AC4ePEjwWQEFR8zmZ zty6>QOi`X}zjZIi%kDb@ywvPY)kdQm7t1}Be!Nn1Tvr%oP*(szWW!2~=$V+_qUZ!z zcPE~-CHemaoF#hr;>d5gBJ$Hmrwp5fFU0^4Y@+<_w!{68ppa!k=b ze6^gaHvc1Gh-FicZs=i=#5`Q`9hhmZ^+KJAma4ppb+fi2%)|XU8pK4NGW5;v>ToWm z^+J`k_VcSJwYm%m&j~L#--=o6B1AS=5oh=x`TXa%tg0WxPe114LbST5-GNZbzqyhPRgt78vbc6mxD{>1W(hI!-_%W1{?X^UD? z-|+%0n@zeoeCeq5R7?d>XO9UPz#4m#bn2%nZZd_o?Nx3)gv+1*`|hkyPh% zs`q3BAH`!O|Ce$+4$A_)BD%7cAX)FZbm=3(yymr#rL#11wSa&*g{etta5jhbkst&n zIXWs$KQ@AYhW`PC5+y)gC6n|VvmnfUSlB6;G9tdoCt(R@t9_jpJ{}#J38DN4l zgyoCvB6}$GpOBvbHnc+h%`@d@e0g1P7wi0a0!O@%FfYJQ#5=4=PF8Q^U~c`(SW%6y z1P%N={d1FU0;wBegs=nsZ148mA-%Z~hEB+Uw=d=iCGk^XsMd$zL6m_WlOafF-4B6f`efmW(Z4k2I|u)1_ob znUoV7x&Azy)yTTpAZCg7jHQqYTH$=?V2>jH`n71vMFg^37eSnW)s@ws6HKt#n#-NQ^9418@BGss3A+^2cJoDEjfTUx%eQLb_j=@9&5{ZRxttZ_d(k!sp z3nR@N&JgpXB6P!De(d&Mf*9hc9@6_pJ7d^!vv*le8IJcs`ZUa9PGTm^Mdhk~@$2`w z_T~|=A|fl_21rwSbm=5i)G^FK_tHs0lv4rfyY@PE!zagF-%I8{U09Cey};5=PL`!Q4>W=-eP~QBGOzXm6(*U#$H5iR@>u z=oisyIVSdc?0gWYy?>hohe3thSls$~lwxMo!cuF4PX)ckL07QncVT%SZTgOz>V>)H znp+N})5%DZry}~k;tP{D%gw$L-8wnr&>YHN0hd`C*~S{ABv}=&N{i=iTD@hVp^vE`zU=x7g z(QF(Hndz3>>kw6!i;Vv#I%Z0V%f}Uhb2H}UV_&jpG!OyB*Ir0Xq~xHyUtFi{5HrFU z+=wEUF@ocD&i*SkvXGY!;wp`5G@F=YzJiFMXDv3-T$1cT*G?>r=K}iaoG0uLwE0H{ zWo6HUgWRU3^F6Uf(VHlW2A4U0j&*YY00z9kCG@5VPqSxWVcKmSnXZ}w<*eJ&epYVf z;^?TG0b)*MXQ9U1abl{m9q8|_oAR#MbZF|O`R=#eiioUXrZYSZ5Xt%*7D_whWfi=u zDSy|1uk5X+I=ArbS%3>G;`WbU>+N$P!Nkq*^~-rKl;~=$O4i>)k^U)fjhd!!0p~GW zgai<7w#U)4OQyr0!b57>nnwzgr9ARl8tirEJexf2eF&p>KxtrwyS*&gxwQn%Xl-)Y zwOT3qajMF~*N_6oW4#y>T*9K* zf5n5UVvy9R4pmkL-U-K15))V$;WobGbgqh)P|js+Y-3r(DKYOvc-9mTVQLDG_OG88 zn8oFd{+;uZJ=HfpyA;r8`@AN1W}(&qtP-xI^gkaJ*m?94y>RyP&G?M3MS35On-=)F}YJq5dn5Bf#G zO|KU=%(c2dJY?7w(wP4y5)taSc%NkyQp8i!$uCw=kW0)bCoF1W6*KR&?}|ReM1~X6 z+fZkJE{#ICkbq+Sx7c>`1`Aab_tsN|Jeygnn9b`FpX8?w?@+jBCLQB`Wc>S~eu}}% z!s9&UT3v;U3{B^6F7!oj9yPCxQL1dKtgt6ij@H!Bd2L26uLh8H1e# ztpU-1ZqASN{2h(?F|>>Z$l5sn>13K}`*C(g1Cp5!Rhn;y=N ^Z?0d{NYvlIHgPF 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 H>+t^pL)HY6 literal 0 HcmV?d00001 diff --git a/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..92032302daecb5fc9491d9cc1391c6534dae522f GIT binary patch literal 11041 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 literal 0 HcmV?d00001 diff --git a/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..5711a6656d3a57fcd9620408fd6e0813673606d1 GIT binary patch literal 12788 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 + + #212121 + \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6dba1a5..a7d420e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,7 +3,7 @@ package="com.vodemn.lightmeter"> - - - - diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index de34bbe32f92571a164d5ba711e34bd35f965864..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1451 zcmV;c1yuTpP)(K=3Vi@ioeq`Zv=XQ`OG&v@=!RLx1o?5T^S3|EH?D>iaY)$qhH$ zaKjD9$Fs)9#uteMH1Q&>#&iA`2^3G(_F;g6f`Tve^Ya6Fd3isupKlo7JB&A>+yl^J zq%qpiKIrrLQrV#I8I)kW_jo+154A=UAJ!)u=&#U#!32ZYjQTt2KXc6QNuf;)%jzo( zqz{;21ermUlaurHL(Qf_-{si=-|2%HJizH_26?^SAEDKXLC=}HKk0)RJizH_24!bw z{{pR640^!~`dJ^$-~moYH|SSrwQA6mKA6D+oQ`hLZ_sMhptt&91`lvLxp|PrM|vCs;#Y!wORHv$nW>l=;$bIY;4fY%?;h( z-}3Z%F1YDWJH@KcXxM!_-KH!tE;Qj*4Fk^qtqLO<<#8V zOlxavQO}rgz+}v*@$qqO|Kgu(614^u6%|o$Z!hic?g|29#r5?yEiNunU0t2^m(<_iFXZ(I@(sePJeF@OE9~s- zXs2>MHv{AvghL?CIefw@n3$L_b4HS95H{vOAn@cvV5~SiJk;)Te4ZTg48q+C*9SgP zl?)6FXkQ$ks}h57@l?I{@GLJcYn1{%SLp^}t>aeCC#ahJ{e7(hz~?L7Abd+GS?~#} z1~a<7yl*X`TNs1_J8H;$a&};9YD&mgxuD&)x_&mhp**=d^_J<7f&YSVHJ!mS?d7u7|xtE(&O>gp2mGeE9E z0PSFvdl9}IEOnEA(d{*(@+QH~}@*=H*SNX!iLNpt)5%w@h2c=nbJkiwS z1)BxS6^B5ab4X3rKqU~xF4S{SvcRp};yx1&`x;~uT!SJI6NB9U)i4To21SSm22JRL z89c!0Xa;#ap106y$sp$LH~L@(4{$oc+}vDBPfvdht$x4%CGSJHc*gjMk>&Mz|AEsO zFp&r?uMMWr8`28RQ3XEqDmgj10RzEp44Mc)3)8g8w0!}M4D-v2i;o!|Mnzg$+P4`Q z8NX*`W&N3%nfZ?Khr@V>+@S$lpedy7lm97>GBE8vWn?mn8KsQR9S$@=O9tb!BnwN+ zC|<%BE$ylYs_kfhJyLECOyP0&ckBh8vEEe*p+x4mqW2JwgBg002ovPDHLk FV1jyhyAl8Z diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index ae5b4264af3bc3b9d0ab8ecff867cb694050f4f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2865 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 diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index d49e5da819114701a6b2af2174565f3c8301e458..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 784 zcmV+r1MmEaP)Oix7+7B0S+RmDE8b#TjB9WKC!S-Nxev)uxi9peQ!G3|kTK7Yw`-7#4LsQj@;qrqCO7VC66tlRCn zSNz6(JQsZ)!pQ{G>vc954A^8cVYAtc&F6Er*=*Q$yLGSljr(}s@AuvIVvHat7eI>* z>-BnUI-RoBYUL7s49n%xZ4W>Ai!pg97cd+S*pfzJ z0RD(0##Sl--98$P?#KACfC%(N&~Q>B0Au%mhfNp|4{^m@(gmP;U;>H+WB_r+>LgtN zN+7C;m|O-B7x9$|z#@b?E+&=<#OD_~sRFQ)Vlfhv$^_ygj}igc4q!zUlgb3*BaadR z*we@=onb(HDv|l&{2q@p~b_=-nuQ zeLME=#~x;UocuiC!`KrU52Me-0RL7Z>Nz1+ZzX)7dY}Xv_g+XuEZ)pjOn$j*SytUS zq6cI{=z%Mqt7z^=gKRcivTggf7a%qtILhVnO(v76dRzkq!0`DdMZ@SjIq&@&x&SUT zGJX)ZmmbGZ!^5*=GI>D`KS}j1Isc^|xx@98@NGT(!APyaH@i;w7-ZwN%5?&MDXa+cdOJwrAoj04!7dinBBB>{fll=oII&z+D8FfSe O0000P~m=#is~_>|Bn5xDP`X zI%14dsgrI4xmHhg*Yr$PwdaFFg4$i@-udb3s$10)6NC^#2qA zH+Yn$A0;LkFHMi_NDbGC7`6KF2(QxrV`9=I;KLNesTP2M9ItE;Q}?CebDcVwU~v?+a5P(L5WN{@UX_uLYc zQ@E~dY;4r}^;Z_OiMEHhww)(AV63uHcNvUR?RH!5?Ckv4+Z#7%8y?_A`YWkznUAQ! zT$1vbBEh?QZ*NcE-QCF?jvaWwi{|-B`;pXq8YTcOK{$o4qL7~~3B16Q^jSv8fy_&k z*7WuDwai&?a82{eW?+aYM{WVVWZ~;JM;_S|7QQ`3g?HFhQCv6ebdV=CeWr&+sne86?wF z{3!Dk9F?sid0@6knEg!^c$e`wbSU!`9F;BSkwy!JDf?7`cNvdEUuC|6qp~$v(rBSj zXOSxKF5_|NLgp(t%D$McP$-T`6?m8NIMkE*3Xa9(K*cmFUln+l@wg=O6&z{MrPPt} zxFqux9BI&{)RFPHB=Z# zDGYF0j24Q6zk%WON}-~iUf~3$U2#re0#&g@0BgNM)X)3Us1gC7ScGF!yhS)Hb9nj|G;+%#a&1WR6|R(=jZ1i_%5NiOSOUTVhAiah_M6G_{9)_%Z(CRZUlx| z(1%5IT+kN)#U-<8!A0o_(n@UGhfb&C+rhY3y4ybFaATF!Xw|%RQJu?^xOGths>aR1 zF!;*N!A8%d3Fw_AP;YVjy)mG;!DVuA3)uwWCe10LvTNGV9gzTfblafcGpYEXf%L$= zOMY|hJt8s&@lhTEJ}f6i@&RK$jw&_yz_&!+#fO%r4N9L;uinZQVDPcPgb+dq oA%qY@2qA6b8!3uObi_hTnON6zi-Sf*LoHkhpQ<#swiPj0+oMQ===I7!!?)8rE9lLKn6X z7Ay=K6Jts=hM2}Bq>!!^L#Wz~O~L2)-8tv=_P+au0p3ifC;9ST-`so7ckVss+#lM| zaC&J3&P+{BW%zV|^oZS-eIJ=L&CJX+yB{gVVzF~;iWLeCM|tY$I*Y+B$0Dh)lf^jwlE%0N(?5f$*4MANF0yX0shW z8)=G(bA8X}^A}+FmP6=2d_SxNZ0q#BMV(?tXCt-zh!PVP8GHt%*=IA&|Ik4i70uX=yO+T5+)`l!s+ShaA9E~Tv}QRmzS5rwY4<^ zzVjP@V;i=0``fVX;rWHd>FC7P8jp^bTxC8?PELk%b93R!%1XGqyBi)H9E695hhFeE zwxL7p!@jOQ+X*~rn=|pA%`H*12BYKic`IRE`NWgt{%UcIuh>@T zyu}XUy|>ydE-r@q`};9MCk1s;r)x(*4kna_hlk&>x!Php8XO#K4`hY))p<@eM=MS8 z3hJb8UpOfjII-Q+)AN+g*@$`+l=e1>anE?&+S-a4PYVnu+Hma(G@;+LIaBPi$;}lT z$6}uqgGqOqm_G{CO&bZ-NpsxSIt6@ih_1uF^LHEPJdRuFi&qfb)Y=+Sk{|(-n_= zUI)03ag9_PwCVD&^5Aw?S66rB01|izKm3v80AtcMP;Jnr%R{;0xr;>3M-CvS3i!C6 zZ_!6OWq%Rw*@Gh|!>Q_2G4(4nyeA{{H?#uZ&vp`qWvZCE(^uNlR&mfqgp*WmFzcJzf3QHzT$wT%NVv-ujWB+jlqw}vAG{(|!#3F8V(FSaNN}FQ)#N<^NPEP=X-QC@D*!11B zWqw&jQ-_9s;rY-9JK z4(dYH`xv^PqYbfDal%$CQiD0I1DQ3X@NqvI8V@BGH!qz&7$Pw7-uW8$tbR+|~O61zG({QnP6 l3i!@%8McwpCh=3p`TvSTQb`1z**yRN002ovPDHLkV1hM@{v!YY diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index afaeb3c945d83314d0ffb65425b29145d690325f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2087 zcmV+?2-x?DP)Nkl z&vQF-=9n;H!h{JECQO(xp;dgqd}y$M78wiYrY8O$>DOh=nl&Fe|6EsB_i;rg_lNQhvja0dHLswMB+#0w|G3>$Gl<&?Z3H%lXD-mfF{t!tyO5@ zxXO-K@(mv{Wbou+Sy@??TeC32ax)f-74i)p**6+x#@X=+L-_=>ur(q6_diNXN+NFU zQVr`OeDR-b@yD2dJlxN9pvf2;K>QZ`?A9)ou!dLM(ahtE|Bf4R=L5&Zn;YW)X~Ru* zXCxBIcWW3lECDDl4;6I)ljYg~Z!b9$x7-Sr6 z_zf5?Dk^G#cD9Cr!0o8G1z>K&I7-9pyg?6@vfy0{JEn2jwHf{`<5%(KVSXkKT z=qG6ED~tg?Fa~59V?dUf)duifgsQ5lX!YvVv~}xN+O=yJ?cTlnJ#B4mv}w~OYG`Pn z<;#~-B9V};k#1(40oB#jv}MZ{I&$O)UAlCMIy*b5r>BPo1_o$kWQ5+ldGnsvuU}JN zUmrbw{FrXtx<&2n?X+*-K5A}mw#*Z+oepM&0T2tK9XfP~u3x`SPoF-e(a}+woSgK< z#KeSUN_TfRojrS&T3TAHHz=KNhSml^n8wCNI(FdAcQB&dN%a?TJ$`#tV zb0@7>u|m3k7-(z2%9ShW;K74*@7}#YPrD!b`}^tq`SY}H-8x#jbgA@PQb9Wd@YJtb zwTjN2J4Zu9Ly}nO1WnWa{rf}hAG9-I^XAQT_wHRuoJ=u1JWQugpQg&nO6j)+K^p_` z=rY7p@>XH&v9lQm{k01Bd7bs}}RyD(3Sj!Yx{`S)sC}#lH^Rc8X zCZ-D*mSXTA<~0?{8Gy|cm1ewt!Jfi}3m1}}AuDG9CN;Wsz9J7EJg`P_Vs2h2WdKG$ z*hUl+(hY3n?AWnG%Fhd>48ZmuIx#UJ-M|3i@ZrN!eqJbL0ICk@E9G zDFaYCg6ah^86CNH?V6OI7fKm`x*ZJT#AI~j_U+qJeqJbL0E(qhuOlX-BX{oHk@EAx z7?3860a->%15m4p$}}+<9l3GihLoQdN*RDcS=4HZ$>_+%ix;K*yim#jR4JoSR!l}m zPMkO)<>!S`2B1V7Rmx&Ay7BhyTRL#yfRvvXN*RES43vmtMNv#jCwhB(Y5n^3Qhr`2 zWdJ~ZzV00{Ht3VBnp4gI)cIp?RZL13uq}D=L50D$HQ<%>8LBnp#m&<~-*MVp^Wv*SiP0{}W^oXNtfW2TxUG&vY*1|988g_Z^Y zJb|dg$FU&H71Ct{mKQL?#Uv}pIDaOzHNXLH1C9mZWD4p^(LbcRp#w9qs)>a)tnK(+ zUhsqV1~@ox1P(`_J|89GczTmPX@hM<>|~)?!+JiJl+*FVi_97W++ehX`h1j#qe>Zt zvUr+NE{J*^2Xv^YUcd%A_HeO{h~aKn9c(hpN&|Gl7?5R*0a?ZvkY$VkS;l3+P^bn( zB9U+6#()_S-hl5M{RB;Yg>?aZjDRoR@4ksa#?hv%tZbmTxVX;IPtep?SpWa~{QUd~ zU%U$!H=i(>8b_O0EcR&r{Q0HO&L*Y6N6HFHOH13?1l$N8sxcq&5wJBGV-tv5j9;Lc ztzF82i{vj@u%Mn_t-l--;_p6VHEFP#9el>h`hpTas6G7jqXYho`9#n%P|UTz2vk~7 zP|(V+R}WkkpMYEkny~o23l}c@0a|e%00Zz1nNR{>g)3$%^78V&k4B>%Y{Z}ZMhr3| z==Je8mvH6$3R))xrO#6Jp`Pb$IZ}}bKc#chF`NLej{4g@ix2}xrm*6 z3v1B~O`wgf(L7tTPy-{PPnjqaXTD;-X7IhNTm~9I3uppupixR6Z<{XQDmvvnridwF zzF^>bE`r>l0knW7&?fXJ-z%ICl%bu&O>-G&04*{iya^K~OqeiX!o=)|e*u5VNw=1{ RnH~TD002ovPDHLkV1hQd=j;Fg diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index f75d3c23664f601d00258791816e1b580e8b1e48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4471 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 KMNUMnLSTXzPjkEg diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 0110c49ca101a303c49762c5e6cb52540e9299cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4669 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 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index a2162bf63f4ae853b4953c4328c881b6b158864e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10473 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 + + + + \ No newline at end of file diff --git a/android/app/src/prod/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/prod/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/android/app/src/prod/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/prod/res/mipmap-hdpi/ic_launcher.png b/android/app/src/prod/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b86b175594280640de188e03ee1636ce63e7af9b GIT binary patch literal 1450 zcmV;b1y%ZqP)(cIMFK}Bylcu-b7DX1)X&;>OxhImS%kSLPV zCg{O~ptv!54j9jZ2tH6m1yOO7FZFMxH?vi()6+9ncMtu+4?&pf|M#D&>Z4IV zaKZ^EY#+~RYinOb6Ud2A(r7&Ae-T9yc5WXA$jZuU$jr=qn~{<61N-S^Oxg^uU;A6+ zqLpK)p?okeFE5@A`kp~i#(S5`6(5*uDDh!^vVs2c59o_Bh|Q?KWBxZs|DNRE#IUTs z#X#zSQHGBhL}_Vht${h~3U!xf1AMOzrttu~qZs6NyQh$=5rdvHcYjg`(|Ca0Q4C5= zP5l+Q8ZqbvGw5e^FpUS;9o3-UkgHLHeo+V0c!1qe4Vp!+Mh%))2h(_f-BAsiL#~!J zh@G>jq@;vA9uHMjRVh?dR8U!285I^5Qg(K>@RdBLX=#bJ zwzg=0f1fTcF6i>|l1@)gX=i7LHa9nEZf=fxdwZ$8yqt1!a)j~1z@$%g`rU0GS7hK7c4 zjk1P8_4W12Qn|go6_O1HEW?S32`VZo5}v6Oa}C1g+SS!XySuwWa*^WZ=0;g|*x32! z>%csNU`}6O9~~bb3rR&9EV;F{HL9+z7IXx`JcBTEWSFC(?n^it@LC?h9D}guJms=_ z2<&k<66EIQ3Ys23zCn0fZES1^;$?AlbwxcrJ%X+WkZ%xP<&l0{h05OE9u*fC3)(c0 zYY;>o9v%vU&0unJQqLJloz3X z7tc`dR!`0H^0M;9@mftW2)A-xpxIzQtgNgQv`RM!^&FHec!6ev{jj;YSHwXoG6t8%3R$zL1TF@)qAe>lHVCThIfw8eML9cX!Q1V1cl^16PIy*ZBz0wUr z^$>L!UYyxnUtd#QU7es;x+;BHu2S}JHY#UM2G(6{16n#s(}Ot7x3JcH2j zM0bG~X%;BjH8nK}IyvMS1X^2LEpww^Utd@5h`dG)xdx$Vwz|41h?Nb#q_(y;L7xV4 z4FYHfn|Wx&4Sr!^A=uudBHtj)BD90i@#MwJ00-8Zni@gZ1IRZB;NXZ2TlRi*a&kf) z9UX$6M=-}AfD0_z!Lm#`J3FJHp&>n;j6lpY2%uMvcJNd8$uM(Zj)6a}GS?u0i!81o z=y>vpg$p)h>^WFgAlqXXntG^bqct-y zFrXCJQA0+V6qP^}yHL+T$pW|X2+SBBYa3(%9D{-o5rdrn)zAuO1_g-#gML&8(|Ca0 zQ4DgqT+_(akU`Ad@6^FG9$FMc|n3(t)xq3XFm%I;;#WThyj1;%q{STbRfYF41 zd2O*A-TqvWb4Y;?y^4*E{Tc(oZ44R>AQzU?zbv(a_!q6^Z)<=07*qoM6N<$ Eg6+7T#sB~S literal 0 HcmV?d00001 diff --git a/android/app/src/prod/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/prod/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..1392a3b65e2b17154578efd1b77c21b879fcd618 GIT binary patch literal 1866 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 literal 0 HcmV?d00001 diff --git a/android/app/src/prod/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/prod/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..e22fb7829ad74c3ffa3a4030ef82175f75681ad0 GIT binary patch literal 1610 zcmV-Q2DSN#P)6b8x&GoT0rhT(S}BWRsfAyMN7R&Lz5F@%sVj0+p1(P-E-F(w)pmA%%u(1k55 zSQ%NE7*nD##568R3h7!4hN|7z6nuW)opWAq@4Igp=7&yC^5wn0x%Zs!+w7+*zW~EG976x$`%xueTc__0>J&RV8+FT%C^2E=c^KPL!$RhsJlzp4i-f*g zwBfT|t=Qz>1rF6X64Sib1JtFC&<1V!IGj{$@}F4-?*;J?s0XN%y3M~~^Rkair4m&G zD7qpFHGW;gUZBc^g+jrA@9ytOuSC$`pjbbQBE=EL;#bhxmv5U#GShI@N^ z;nC4iS+ETqVjuQ(_1R9~N!y%>_iS#7qBR&DpUYbb>nbN08c-Onudj#u`};AX$ANv= zcW!RZ=yLVjw#fxfyl8Xui5>S^Dx;)wvADPxZf|dghlhtTa+QL8se`&){T2*|@$vCr zEspUO+X|gG*g?GaR-5JJLLTPk#^evmKEykna;o(dmE3B{1 zbE-L7^(3#LPU`lBlX8I*JAHk9PuQG|s7FC*Z<84Jj8{&%YrI~dZswJ1SD*>~y3Ltl zmrZW2*f%%Ie-Kn!4H4<9NAu(Ww^_7 z+{(SSG~`y|f$apBbuRC&p+4kr4{Rl{n4Lp|zim%#G|OfV&wp?x zc%c;erPWxs)4WmsQ93m3b{g@&39dcDxx`)b0Oj;;X&eh9f`^SpA82@Ja_wT&qwqob zWcakZ$4YZRNT<_dXtcvl6HR!e#OUvKc6R1%&XI^mow;1@B0Z(Yf|$aGaH!YO|BxKr z(N|HAa`5!JbdKeM;%$1EOu zMQQkoI8pX~l&JURuGkF0VU1jb)A5(PBFqO)RR9IwiW7<9uN*De+Lee!o?r= zWrQ6%fC3a7!W5954Z|@>Deb=qoP)AI{x#Y?~3qwSlI;k5rmS{_Co;Ylhq_tQq zQXy{w&SHcL$(-f02mCp%}Cs`(VU3se`(xle&+s^K}s2ho^xo@N_1Vc^S3(1126wZGqe_@H^u9 zPlE6K#^2b6ZP^EXd9y$rY0(+~!$kw7O%@g!=AAu>z>% literal 0 HcmV?d00001 diff --git a/android/app/src/prod/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/prod/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..a2ace48c5aae2e56e8a69784e3232599f02c2fac GIT binary patch literal 2058 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 literal 0 HcmV?d00001 diff --git a/android/app/src/prod/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/prod/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..c6e5d43456c9400048e87f0ed3f74e0f8d560826 GIT binary patch literal 2835 zcmdT`dsI^S7WS4m%SqFP=BTNelaIP;N%la&%+ws;l~#mW(>SK0R-%-K_A)CfGgGf7 z+4Ws0X$S;_*0nUlB9Jg8pvH_>WC{w2=HS8A+;#t&KksENV4ZXJ{`TJA-upYhi%}85 zCdM|#1_lNuAv?D3)cwDi`z~Ij`@Oi*U14BgJQT9sfA{ftg}^TqvESk8m^Sv8M;nIR z#v+|AoIha6flF<_to~^vJca&K*_kT^KUx?joWjL+J1=pCU%@r<8GMV*m!I$bJ}xMHSMZgB`iom?(QWoGtkmDAI{I2>-` z+qdDKBB_9mX5=^K&#I*TLY7iA7WF&0;1Sb?=bJaZ8Jau!4@TY$O>Ovfd?a-0+f42n ze?PH&;2xtI?;=o&S-G7ohpyJ{B;(WN-eGL~bF7tT&b^J*e)$oi4vbP9$%W;sedjg;}SzB9&gocJD zXJ�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 Iv`^B10Sr^W_y7O^ literal 0 HcmV?d00001 diff --git a/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..9a5a827fc6ade34aca0f9f159de8600d697b5984 GIT binary patch literal 4858 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 dC8eO+npM*x9`LjNT{WNKvs~kL;xt99 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 literal 0 HcmV?d00001 diff --git a/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..9d3e572ce22078f812f546f1d719b49846923ba5 GIT binary patch literal 4468 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 literal 0 HcmV?d00001 diff --git a/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..fe2a50c74b2c8f17babbd0542ddead2b67d9abe3 GIT binary patch literal 7224 zcmeHM`(M&$-*@wAZR_Cu)RuNo@7k7I9#@%p0M_iV%Cu>Ug@-j#OG`~s0u^R0eYFm3 zX&RoWyQU#2*+E1FGB?cwUwBF(r}z8) z;r#xi4OUjGO}n>mJ8(Mc)xbb|Xv|#8 zq?ET&_iM1OGw<*=d(W5!&#kwAzjDKePj{bQ_t7$k4_Dpj*&Vk^w9Wpg$F3~99SeWU zJGbYz)PEl7JzMs_xALPH|pGQ`vIBmPXX8msHUjOb+mz>>K>Rj;4Hn$(Xa?7o~s+>aBMZjKWalegfN{dAH^N;Y_EBoe6;Icb~)JLeKp|2a-k$;$uK zIV|sCf+T;*>MeQpzG-=qsPxJPO%EoRv!u7nWIlUyB%xQS@@`CJH9Ia$_P9r>c??~&2kk8yP>^vVHUCE3fyS)W& zt~P$JzaflLEHygxs=ZJ#A9GXgyB*S61xwxucS(nmckI!=|J^ioaQZA=K zE%23HaOZyM6jbVB>lX1uUcjgzAyBign?@Ar+5x;%v}%}wib~`eXIiD7nG#n8vU9H= zT$-!L7^&S$y!!VmIoiQ{G41)Sj3RwaG)|+yIPg6luRRehFK$c14Lni|h8Y_B=d*9Pv~_D~Y#tKoEaxLQ^vG@e-yqk+UR!cv4x z!Q=^yd{u_w6-&nVlDbe3Grc2*eCb|(0~{axV#xV?c_}e|icqoE6rtF2Tgs#$^|$X} zSOVvJE2H4cGo-Gp{@|L#0UyFc&+b6Hsz`5SIMp2QudVJ+>pKx5O^7tY`$d*OvV7>w zKm$QNvS$Tvc51Kk$GM-0SN3MEOINbxZTq|iHfxL$F&r(yTGvAlZr_RKan~R-YFV^r z&;`BCS?h&M1GrMJ!PCPc>=-v`(tQ08F)Vku@fqTU52jt^MY*bjxc^jU z1T>sWp;jG-qoOK6Lplg-FgB8RI>}ZLpb;7L>S|egLH*#5IkLbrvEBi7vtqJXVn`6D z$9%08Gc*!3R+AE`3x}o(d_O9bd5lk`2@%?Wz^?0Lh#@1Y`56JJ1o$DR2o)07{s+fG zyfgKulskyGkR*+Nr)ILFCA^><)~xgz1u1y>uWNA71##5U-fT#e62+- zzH#3agu#xzz#LpzJ2*9{S=4f~G!(kr`PUt6(HxHn29xW<_V*b6-cd#Ln)qUGegOYx zw<`FOuUsSR9!w0>OE`5qE09XV%&Ve~ZG!CQ7I*ak3;XFZ3UWQ0E0J?5kY7g}Qg(_g z$C?JDb2F@|Rwvqs8}<*#PsDz5@Tm!QVHcShMSY zFgCdwI#>}{ZIZ&i)T1^26&f5e@Txpm#HQ1h-*HS6KI`IH7Ez#>Gtku)8Q)P+Hw8hQ z;0x`6MmZeo#^J{ennQRun?Ggy<+r1pyBGXqs+~Xo*n}7pBVt@?k{xiLj!=aSg}HRY zr(v0cs#e$UYnMS0VqF_`b^3hFbzfuP`zyU%e|G*|P~hupvW$ioc_j^w)JzJtj_j#W zhsy5=FoL0Q*uu@vn=h?y2bH1N@PS*sPh8QK6NQD1)%o-Bcd45As;;{{NM4%dTqfv4 zX!|#LUckZtz6!I**3HGBRSAvnqa!%MEieDPP#&9d+;YG8nTcmIot!4*ykA16c@_KO zm7?R9>F~1xT%)-S#eRg1_>WJGn!)0?T@KVj{?}LGQ-9a@@UyHJrqI5TLzrGH$@%Mf z&#l_4sq~k4A6dT@z|;{3+|HKZ`RLo6t>hY%DYI`T`#(z-XQ=sJGJ&tExKR9c-u!+` zINrF?yn%Xr0l4wV(kw=q09(@TL-W4x4~N7mq77;`S-JGu*I`u=-kyICr-0W0qR zq{KbC_&sWM&C#d-qCc}wfo=UOhDYKg*l)ZkUhV@GUmhPk43F<;uu}2Q{W6o^ynd0= zcY?K#|9U@c>+I(^A;ODI|7XW3P2?GVkyp^$sc@rYq9B??O@BZ3&dptvd`u!cCO$tW z9Dd;+P-QSATyfnUs$YO%C6*M**1{fIQklTy^f26s8RUJgLEfcf2t(%-Psd;HT~9| z7=LcG8Fc>A|YV`m)%}58-&rOadS(dowvqnDAEtUUGb+)LWeiyBA8=oX7_pXSCXe zgJ#OvvHU_hRR!lp2(1W_o<@*eMNie{G~Q04NW6v5RJeIhhS2I?WUp?sjn4S3#c%OW zjz?;Vy4Z+>OQ(iyP}aLs@OF8`V{{+6w;>ZQI}pyj;q|u;Dvzz0n&Z^MzdwJdsH(4O zFwXQ5?lcVna+0)BMOZxja6TXC%mI?&l?;^0Hmv;z<)wjSKuZGbtutXPh7u^Kxzpa{ zxeNz|$JSHegbDmO0+&&g3EL@d=CoXN&Yeb1y9V&Mp|=ajSC%%-&s~l(vPRSczn*U z`S4w+0j2KP6w>moBOKlO-o5UeycVy;gMg~}rM}%%Y?Jo57nFEB6h4N9Ei0{v`_bMz z<#Wmjy9VN6>DuD+*XC1IQwh)^4`4IWJhR*>Fb|W&NrkSBVJzzoRsXm{H`(H-?#*sw z;G*Ry%6TqaH8hoGTDmDtFjBqbELzSLkR6t1!t?;b2$6<~5r^F#TT+!?$9p|e1EwqC z7bkH53nfG}9i0HY_3b&dy3xXfHgig~Y`)kGQf!O&(y89D5KCNCl>dVVI4>cf>8MDj zGSH_j@NA3N*~!(t|>!?+C-pV{Llf-roWGU zkaA5g>SyEHb!xf#TK3c1>S)SYzb*O$^JyX~hq4x7|6#QDW+N3fV=S&aWzCH~gA>I(el_+7E))k&{< zt3NY82&B}>x5kz}zkAqUr;$XfYt2qI0g(`B|JHX2k}6jqPFk_6KVS+nOH6+2^)UR4 zSYdaKpjIpKKIP^QVKjcL8|MM%qbsl^pxh;wg1h<1Pt zRZo8VjDtGIg^Q`Cnv<>_1>wU=l5*+T8x%{1O~?wO1?I1RFNk_KiAsSmna>99d^Q-T zyWh0KV*ZAdcO5XVc^3UB8>vhB?(3g7gxD-`nhjsNt#xb)dtr zN4Sb>NK~n;XDkU2-N(&LH;-#pU4N&3ASVAu3QBzOqhH&codl95VAL;;C;Y2w1KA@3 zK}_iPSCt^rYy5O0xgndRS{PTjUZCcccW?Txh2lu-zzZ^@798yawSTHNXOMv_xsUuA z{&?*HX{-*2y{R`jgB9wAcyR^&pyRjmiP`;+L$%3=y-$tpS6Yo#o?U3OR5{UPiH7Vh zKoM!aR>Xw9niKDU)+tFO{wffcK3c130ZU54QTOP`piUYq!mX! zT9lLzYD8LmbB=|P+zMkxz3K`!Ioycc_i zh!9>{?G?zM`iI3#y=Jv_*#S}L3rK|?Qq=S%7QJN-4*>xwP|Jn#(w|nAb1|>`al&_~ z$|1l3_xOsZCgBp^i8hq8n@Axv+ zK@riT>!8{bVdL1<0d3yyCC$k7{hzAB;TDRG{ui|guHnaA#D;U;H_lS8N8M!!u!6C0 z*xJoqfRRRy6|8*mD9bvR(6Q2fFTB|`A~1bBJ)|5E%s7yt2lL&dk)B1^4X|rNaB_jG zZ`#C@62M%O>;hcX%HVYC6D%al%J2X!&s$)}CXQKuXXwXC*aEW;^%bc8GNqxGmYM zLu)MTn+@6qoQyXwa@e?`dvrkDUE-2D;LO3Q<_s!#kb#j(`#0VHE&_VZqV8aQ+N+VW zGs)hSQ5@pNXm1?WB7aay`j_}Dq=hP_EwaV1kbp^Ksd~T+p?aCK>%VPJMVEc9PSf|E= z(a_M92JK*auCA5Z#e*r+u{yvY*%?5tv1vo73U#o2ecWz~p3x5jA;-H4bcmn!lK?s{ zSpG%acVswXZkZxDX#?dnQla7FIl3SxVNF!TyeD(#(o?+&Uo<9J0C24}MWy zgV=e~gp`NUu0!lcNmI=&1VmX~gJpKsHKO*epJ`St0|bx#88s`g#K+T{acc7ApCOdr z-N>+Ms{%lPxV0C!yDwb2=5?cdB?QWJF1nrKH}xIWEatGcokQjm`j#`sh|bLPOalVU zjE4O;A{J;)z5zvXJ>>#AasYNdy$BT0Fp%FR^?8iz$ffu}f?R~Xl?dmmC=xf?@s(MH z>BeqSG7|4#HLo5MuQaz^b#|ioBey`7Zoz<7!kc?&ZuDdTjLU8RE(X>Jt(Rpm`M_ed6EI-EQ`_VgY>J!+O^>`z3CEfFhby7lVUj_J3L zZl|EiNha=`UU4r~(Ua2d$_mJ2rfT5G+Kj6}lfzkw10!)q0y(^`8bfH%c`~Tl5j(Ki zmJlC(_(S8FsW-!v0-njJl7R%~NAgu7GzMS=wSVPxl5{GaPPKF#s2Gmv0K!ND$RitS zJSzhs-eyGX(HN4)I&ZYfYn62hH)bu-((E9eYFgrz2c*$HqT$Z}N{31(Qsftv|LE4#ic8pcyyc z6v}Z(oe(5kwy)ZGxL1p)1mAotJm|$MqDCC6Bcx! z5VqQGUZ^|k0PP4QERNTF+Mgsg#KiDSDxR@WuZXPKZGoo${@U^X^CzD7{v*GMSuO50 aS<8OvSwuROonpSh?j3>KtG@l=%>M!Zo%JIC literal 0 HcmV?d00001 diff --git a/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..74f9d28d3114a7398478dfff3f08257150c8b11a GIT binary patch literal 10193 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 + + #212121 + \ No newline at end of file diff --git a/assets/README.md b/assets/README.md new file mode 100644 index 0000000..decb62c --- /dev/null +++ b/assets/README.md @@ -0,0 +1,11 @@ +# Assets + +## Launcher icons + +### Android + +Resources for Android are generated in Android Studio from the 512x512 source image as described in this [guide](https://developer.android.com/studio/write/create-app-icons). + +### iOS + +Resources for iOS are generated in XCode from the 1024x1024 source image as described in this [guide](https://developer.apple.com/documentation/xcode/configuring-your-app-icon). diff --git a/assets/launcher_icon_circle.png b/assets/launcher_icon_circle.png deleted file mode 100644 index ab23e263336398852de145c031b7617fb07be821..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3977 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 diff --git a/assets/launcher_icon_dev_1024.png b/assets/launcher_icon_dev_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..0923f36f7beb4f33fa854a0773f04999205e928a GIT binary patch literal 21938 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 literal 0 HcmV?d00001 diff --git a/assets/launcher_icon_dev_512.png b/assets/launcher_icon_dev_512.png new file mode 100644 index 0000000000000000000000000000000000000000..1c234a09396daf84f20deb51fa245316d837b820 GIT binary patch literal 14568 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 BS=#^r literal 0 HcmV?d00001 diff --git a/assets/launcher_icon_prod_1024.png b/assets/launcher_icon_prod_1024.png new file mode 100755 index 0000000000000000000000000000000000000000..a54026f2259f373ddd90df20c04997588fc35042 GIT binary patch literal 14418 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 C%duzx literal 0 HcmV?d00001 diff --git a/assets/launcher_icon_square.png b/assets/launcher_icon_square.png deleted file mode 100644 index e8ac5f8bf740c2796d616aa577746064cf112bed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2256 zcmcImdo&Y#AOFqVHkU{z3n3~hm%`lM#gc|c0?>V3I{hrVFu7|sm5?lig08nyq zcJz|O_FpD1EAd{VSL7rC7US$s0Kn+CUj|7`iE{%${+5g537@3G1un%)%OBCbMAh-F zs(>pUjE#YY-hrOfLhMKUbC-3ESEgf^4AcY>(?NuqkAgfhB!>HUGTVCu z%f)NkFU#(@4n*6h+6m)ILdKetmz^O07Up&8n@bVd^1w?UINt}l$iJfy9T&k6^*kPr z|9Ny2o4h5Ae^ykqAl@O|nj3uCDsIrRPkTH+KhJMl5sB7*OwY~LjE;>Z+&GH5lDp)v zyu8f47t|(4|4{N0=0qOgWjVAhU6&3;Gn!0|jq|#?y7-wt&?l_z?M=hO!{4bu^*}1l zM3|SCXJ&4GG4+*|mDTjrRK`)kU4Sa3&&D@AaHcq@$)a)qiga^cC`|DW3NrTg_V)W@ z6`PotIK8;|R!c`GQI5SX2WcF}u4`&)>g#-{KLwplPfw3R$ajYH%jv$)+*UcU?lcFg z9<))Z-fm~ZJ?1nP`20$3{R&0mrTEp=)hm_9IF%7>3Wf5Ris9uuTwh--#bU8BOKU7T z%chV1=Rr3GQj=ILZrwtqaRgfD2?TSu%eiGTtj9Av-XZlHI&nF1uj80z>ycNl{=C?G zgDgwua5!NXE_6=dF0knR@9leYt1tUd@lEK%V|{(9iCgMkPjHcul&`%0!%gUbJeMco z)&(Y>)K^WGgEHvM{v!MNQL*7HcCK&;ajQm(h28Wop3Qm0jJUtA>L4hftqWIj zM(4DKEl&D$>7I+en7Z+^Yr^?w(zbYO{7ocgcdLxYqgQfR#G3%d$xcjLYDfpwjlZg?EK#rXn%lY&rMSZG7ng1Jz6gV&#OO%(mI$ho_So zUc<+=VC7tKTAM)&p5EN7g+CNuwVQb2O~WM*EtjWYHu8XQOYBqb9G#yzD^)%3En*11 zHeXvaaXf42s6g`9`F-b$OREqT*){3raqYp=G(tF6re6)yD_m?paxgW~nd-ode&pW( z3Ib;)AGp%Im5RR91BNIhIM{4qPhqi=k&)3zy;Qw1k*|f_T&m7uQix#+?c6q#|0X2l z0qo|AhjGv_%wM)Wbt0pKKEyTw1^>7-L$<4_{ziZ_fwydWa1M=8GAM9GcK+l*)8M zCMIczs)8v;fB@tf577`*C7B@8oPBa3a0h@+NEi*7c3(ob;UFxH0HTO@bZW}u8Uy`* z4xO?BaLdvXn50)sYASbiAL)QHjjWKt{$CHTS5}_gI2(5B3cv;h(Ng7ccYh6QUs#Cb z+$QE*v&Zzn>CBn(0yW|_#f;_HYWgW)Ta?q`8Cr4b z9KeyD*xVeQFg&XKi<9;*U!F;O|?|O-fGwQ`xM8$8UT4 zHdGUEK9{lJ?2Zk7$Y`5FMJ+IQh^$|-#Kx=N|ReeXmEKEOrG69)B_ThP9#+d%; z5X2Down}yhlg>;Pm|nX*zW)sj()}{)UK_j4wE|w-(h?c7-)0s&Tt_}R;!pachsn$@ zFV_z)+w(AhalEDGtHIM_9kcADr6(FzAEO-Fckvc2yo-=gVZM@`JN#I?XR((1>4 z?i-sM1h*SQ$c5L;6k2^sbN`M>=bmm_ zBS%0|s*xic&JhBEU?(?Nb|rBu_z;wJE+AlTHJ)5qiBiV(SXlPE8a=Z*=(3l1bsN^b zva(WFTl;VZRwqkOi4CHnke?R?`l&!;&#m^%?GWh+h|Kx;c$#Kb00Yx&0AKm2r!H<& zB^uze-`3T6?N(Qhe6V!r>mygr&f1NM&#FW_+)x!PT||&J4w8saHq|y=4?yvqB)^U@ z4c~#M3=W}hM-FJt%|B--FQlxgVvZAs(0&R7wngr1ox{;69SFjVrXut-v>^Eze%$(G zyOmW=|L|$+qn{EszIL9^+?&=Y5o=fKZuuld2pTU9s#QS+2IOk|4MlREmsq-c{yYm8 TR{l-$q5zju?vAe=LT~*Cv`8kQ diff --git a/ios/Podfile b/ios/Podfile index e9b0728..462df98 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -40,6 +40,12 @@ post_install do |installer| # Start of the permission_handler configuration target.build_configurations.each do |config| + # https://github.com/CocoaPods/CocoaPods/issues/12012 + xcconfig_path = config.base_configuration_reference.real_path + xcconfig = File.read(xcconfig_path) + xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR") + File.open(xcconfig_path, "w") { |file| file << xcconfig_mod } + # Preprocessor definitions can be found in: https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 73ce339..0b4c62e 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -44,7 +44,7 @@ 8C539F8FF42AB22E298D5A5E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146EE1CF9000F007C117D /* Lightmeter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Lightmeter.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; @@ -95,7 +95,7 @@ 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, + 97C146EE1CF9000F007C117D /* Lightmeter.app */, ); name = Products; sourceTree = ""; @@ -154,6 +154,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 45F53C083F2EA48EF231DA16 /* [CP] Embed Pods Frameworks */, + FF00F85CE432774850A0EDB7 /* [firebase_crashlytics] Crashlytics Upload Symbols */, ); buildRules = ( ); @@ -161,7 +162,7 @@ ); name = Runner; productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productReference = 97C146EE1CF9000F007C117D /* Lightmeter.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -242,6 +243,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -282,6 +284,29 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + FF00F85CE432774850A0EDB7 /* [firebase_crashlytics] Crashlytics Upload Symbols */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}\"", + "\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/\"", + "\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"", + "\"$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)\"", + "\"$(PROJECT_DIR)/firebase_app_id_file.json\"", + ); + name = "[firebase_crashlytics] Crashlytics Upload Symbols"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$PODS_ROOT/FirebaseCrashlytics/upload-symbols\" --flutter-project \"$PROJECT_DIR/firebase_app_id_file.json\" "; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -370,18 +395,23 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-prod"; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 489Z6UQMGN; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Lightmeter; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = Lightmeter; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -499,18 +529,23 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-prod"; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 489Z6UQMGN; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Lightmeter; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = Lightmeter; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -522,18 +557,23 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-prod"; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 489Z6UQMGN; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Lightmeter; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = Lightmeter; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -599,18 +639,23 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev"; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 489Z6UQMGN; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Lightmeter (DEV)"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter.dev; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "Lightmeter (DEV)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -674,18 +719,23 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev"; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 489Z6UQMGN; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Lightmeter (DEV)"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter.dev; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "Lightmeter (DEV)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -746,18 +796,23 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev"; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 489Z6UQMGN; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Lightmeter (DEV)"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter.dev; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "Lightmeter (DEV)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json new file mode 100644 index 0000000..c89c0fc --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "Icon square (dev).png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon square (dev).png b/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon square (dev).png new file mode 100644 index 0000000000000000000000000000000000000000..0923f36f7beb4f33fa854a0773f04999205e928a GIT binary patch literal 21938 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Contents.json new file mode 100644 index 0000000..e85412b --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "Icon square.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Icon square.png b/ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Icon square.png new file mode 100644 index 0000000000000000000000000000000000000000..a54026f2259f373ddd90df20c04997588fc35042 GIT binary patch literal 14418 zcmeHud0dR^`|$P5Qc+APQxP*|*EY!#(u_6xPN`@L)j854Dq7~rRtiN2AsHOYL6Op; z)X-*0P54rxjS|{7(>C*7&p7Az{yy*f&-?p)-apSDs{6UG`@XOHy087Z99z5EcG%ES zLjhpevZagG12FJU2B@jvFVPs|L-=cm&(bY^0P5P*KLReq>)}VjZ@ujTNGdV-M!~z! zUo{^fIa+#{}jHwF^TpYHf2*j{L>?J2RF@oc$%rTWGwqg2CPhT-{3wz;bB zU*>t*B)M$p>`fc$xx-G!9JQanWKYbgSBL+cFywUE)iVn6)Vi45$JNELlWiK?#%A^B zBuzczSxV%zzRA7W-?HN6%a^Sb;?jqrsrXCqlR_Pq3aFnX%Tb5 zdr7WB{xkPnx_7m_r}~)UQ@=>0P^g1_{NGB7XPLKPptF?E8O-r-{@|_NYhP1To!6E% zYtnYXKz{W&o&45_A$hsSn80)N2ap_gtqtAd%i-re4UJE4dz`tn+{)V8;0UQk*J7?- za!^(ZWP1ex@ReuMvBs>$Zu90xA-kOK7;5IXs?qN`=dx9yCLT4QEWCDDs7Y6NLC)gP zxkj}7lVK6Gg>IuYNAo3 zy6y3S?n*5hc$`7)ykWDQovmZ9cUAVcW*fg*O(XOUO62lTg4~STZG3g`@`WKZNL>UV zo|=4w&+carq&Du*)N>mG0#m#Rdn#Lo2hJL7uRM4;A%B1@AIW173z>xdcml}Gr~*0f zFTPx3OZ;)dUln-Xh>4}HclY+W3OnbJP4C{l%dQXaDEdUS(;;Od08cybojfa`bE{!7t8k<}a4@ky?d&ab5f05_Hc2#wf-JV#f6H%0(HF6_bk zdN_#H1h6_6>)vPD=hA*uPt%X?WUeOJk5`JH%I?pr3_Ua;UY(VdRbRvr87wA1ri0kB z@~fs{N!h*)AXf|EG~Vu?TQrBS zo^1pIJa$CnkxOho#iM3Mw#FayYv*_Et#x44m- zc0*~>SpYG7|bfbgVitwP4P}zJnh#QA217rbFo)#dH!X)mK6e=Zh~6^Pi4{MjP%+o+RF zH3Hrk0<4@L->GQHYQ^Jj6bA$O4Ige|O*~%`yhtr0pf%wWfHp2G)b#G#{y^mUQ&kvR ztO|OUk(uTvH%1raHp?NZ)*d8*_#}P~TeeJz=J3-#al|7|GQsIMf~iyv65rWbgulQ4 zi}rS3qUH@kZpg2cpEz-XdH|Z>v4J{RT?A;;YJbOP%hSC+O@-9F7y-Ga|IHa=JNbH$ zt@7+vR94Gp&go$4m29D)4#)kQ-g6b+PyZYvuLM z^6L;2dyz)oMacCLwV_A}vt7G(F+l}IY3Ht8FFk?FB%pCyeE;$JqU3H4ROv=3^fdJj z(TD?XYFz2V)|~z%>!6q}n?jm)L?_`jjk)Pma%O}6_(0FawnH58l`9g;)b5t&y-W##CQ_mEnm1>!Bq^@``Q zlj`}3PX6v*RpE`CfsPfnG_qkf4W|E=@!+|m2pXQB0`bn10I#+G*6O6b;{Wac%*dAFmS@Hm|bf%UZMf zFi-Db&#Q~G)CH#ae77|Z#ipgDEeSBD3s`k?o2wOr)wZIUx^OEDz$D&91YMq1?A^ZeyG^Ot>n2WH#Ym_Za5C0@&Y+_0rI?!^rKnySY^UTVO8swoh*;CnrL z5x~8_lmUVcRUvyd33AyGkQ~`E zK$p1Y2i#MG%`gwl0F2s|=`FX|dNMD5w%) zr&=z8Ssm~dRol%yKY8$W6$46eBb(kgIH(4hLm;Mnu>U8; zH~3Lks|Go+zOC&(%--s-ZCj`vxwp=cdxK7g%#T?C_nKN;b-A2{V0a~C)U%C3cs3=0 zaOzc`jk_EV3M07bfhrO1gcfvY0awde27Z0}p2Rntzd%x1UUd%^hs^W19DAW1zxg%N@}MLS1KkiSK_N1-L5b0Tj4EsF9}gcsq@}*n6nNNszKpseTCrjU6NG2$Hj{co1NZLTNX|sZ^hD%h zMT&bV3MiS`)ur1v*R3XI^_jX(xk3+DEzaPgwm^I z8j9J8V;wo^>2~M=OrS{~2mO3eTkAr6s8xgMJFJ3K;r`P0l@A_2-aQtkF9GtX)lU|@ zHA+}E)+X4V^rx0*1pV~df;JrBA-;ooF`Yb$pNB;}l_Rw)n^37Of{c)U|JifqOci`G zAV`%AKh#S9;j!Qr8s#&7EgZPD)oGYa*8Z9Hb`_A3iAhN((XA9TkF^fu9q=cSxp{EmRtUwoZCf9hd3$-G%zQi-IMxq`we{p(kmSw{@SJ+^s0P zX3GR_RA5hXrxqJc8}nGALW=-+;G=^o1;_W%0zdoyF4578e~_sK!jh}Om3=JO!Oj8$ zCfTcvUtDD;sH?fPo9Gl7hV?%Br7-=poc)7>f|x+A_o~j>I-JE)=W+Z&VlXeL=Pvp& zcZ#Apy24Nf#Kr0jxc}gTq}5eVXb{b4SP10oh$FdAL;##f_wjVX#Rj1c>FxR4j8NgO z?cQ8&aX7kjY;^_2u^=g$7uY7;Yk)eG0Jq{AK9DIDOv)jYXy^~9f{#ryottoptoU1( zZ_rg&1ok8QKzu2BTbaI}3ma$gLJ+y*f&DD-v9QpTJ|uZ_@q1a3-M2-wLBgk7mZ%5a>y&lxOs?Kp5O^UGE@Jep;EV0&($U&hSEL zu<{gjr8l*IPcesds!0kQNk*_w|zL5;IUvu1z3-U6i7C9QjARqlW{+HL3}KAseQ@nV1@$kw;qN0yWiK6OSJgX>-F{Z z0Zgk+*@M-UmBs*XzZRYtt$c%uzA?b6p!>&rTXgBkN60kpe(DG_5hHwn z-B#$PDEjUx9LQ)q&8D{HQx=*!7>18N%e%@%QZxkGhNQ(1kXmGE5PSjoRqK#q^XAP+ zzTL;ZS}lyP{fwbs#N2=u9e@vCM!CzZ2&mHjD*r{|-H~G;iE)^PyNoT+196BOAqi(d zV6^3SfaWL=>cYS&fJqrBaAH=nEZTi872L-Q@92Sm?^Tt(g{;Q4tR$5B#wcT=HX2V; zwZj{CeZ6xpXR72L112jcH(7tyS5eCy0W}Jz+|b9ESiNjNaIur2{9;Wxg&>`j2eo!I z_*Nj)Q*L3bGHSQAMyBF&bPd>9d?X^n8+Y!0`$t6f|)e2ztT$;+&?d;~!5MUDvnhgSi!c_cL~_dX{`){@?&9YQaE9|8AUG0FF$C#6 z3e4)ne;-6`g&ka+;WVJ0DLA!mnkF<>VT^&y5%0EhXMqJgngr4r6oXnt+Yi_>$b=!V zcNOwoO?FP-_dB$C+R%96n*zb#fB4`KM$HtN6*%J;iKU(+i>x+Otj`s{-<9XqtWS=s{EgWZ4=9IrX>SO zK`zVJfYR8mr68VLqCg!oB?}r)VJJnV-X~4~DN?${N`W-{v(A1>*AfsLx9rBt}bDrYKQL{k_YjYTV zf8EjqYOuxPZ~+Ja8Va}q2jmMAilO3WWq}7iYf7*T0|;-gD_3_SJ`X5-_IHkmJUaxY zzwAY=*wBnQC#D0wi%IuLoU@F$K+RF^Jh?bo`^;u)&^8hst^$*(6xjH=_>T5En4zO) z!p#7pI!YA-rA&wMIQcb+nl!(L$vr_V#)BYSmwo)|COkSB{Rv^&BA~zwekRf2zQ?xZ zWkv+qVC9GOzM6UV0vmTM@<*iDt3bS%T0^0`*l#4)bOd;u)W?HOE}UDRimWwT1rNIK zaq{|W+~EesDzH{vi+qQKE$Bg>_`SAXCs7MLB7eJsiodR-{w34F2&6id=sw&!;=aKF z`9+8G0Oy{jOi==k!_EI|U`SGf;h+SI-Av$`a~o2TRrPI9ncP}tefhwW>k|;c`vpUR z3UV<+iE~akcVgUa<>1C$8mzsDF{`e}5fE5Xq!7LjCP34kl ziO66_$54nAu9!;UtAVS?4{?M5BeV;KI00xy#rof;qWju@d^z>C1|1uC*l~BcF*R6- zPw23J^YxK5Sats);^r^}QWsDQ<46!tAU?ev^~ldN9v$fZJ&cX?n!kyOv&TW8jd3}M zH*ecqHWZHqA&mwULI+u!ke)lYjZ_YBLXfCL))doL09=9~CuTUH#&xJjq;Nolhaker zzy7Ye#gXGEB*a7BCvC;YU5g4}Gp5uqAxTm{VaPuNfp+{I8lsS}={KaAcJM7Ep>gT$ zUl;iQQn$L`gOL~Ba>@VO5f^Hlkni{$)n8!gJVxGiK`9MknPDD;*P|xfz2E8heFlr@_ zumy^1w4D+$@0r;2>pk19p!!!HMxpWcg$6jF_4u{+?;>x;i~QBNXn>&>pDukLW%x= z=@shfq8j|CD%BJR-_+!SKBQ6_#~)|zd$@rVHO|Ci)SAcwej+NS)2ywOdPGNsQjeTR z)XcecsmnpU%w{iv>Tuy-zftNixDl1!|R%s)KqSMq_R}uRTw|s@B2O1UwWtCQS8fxIOfuABpKp1si^WW!LDc1bJjodVG zmrl*nc>l|kA!_L5qP>4-?e`juV}m{evp9EWA6U*p$n@M?XVesDv-e(AMRYc7N4qn_ z{8D2W2s&N4QO8i*7B7!i%nH1KCU5T(v=!i1k0}`D0hA5qhAjww2Cd#mYJqMO&I&hY zdtTK=o9|<$j)pVBeI-F|qYS+>)m9{n!L*P=Ob_@yc@IJj_@WVx3AIt$D2b$^*!x zCk6*Y{BWbV3xR3J-B&gq0fC*;`a4)Vz8+1GNmkd;*rfjBSuDX*{_!kahvT|8H3R&4 zBcSnQiUV%?!g9j@Jz(uZKW3kg&ss4$C!a7;Fb!FiOfKmBa~yObqpa{pdE=RRE56~Q zpIhmP@9gx;Klj>_6M-}2A>>${Lw97`|}>9Y=fEcA%Iu%ZD@f4DDIx@nX% zmZA1uQ8pJZFi#|>oTL4L{cBp$KWWg=#mrrO#i=TE(@fKRW@C$nWJ}%oc1%Yzsnri< z+<=pK&6INa8Wlv?lEU_%L8r@b3R-1K<0b5rsM8}^doh;mT*CadsVS)o64ID%#4_;o$fY-{wPn0iA+WuxmA<^G7Lg^A4pb@VbU#WPG z&iBY7yq6wgG7~*&C6uIP?E@`XiY_g{!DfZhB}Z`rgjs_Ad%gi#jQ~v>#GHEZWvb0$fWJhKGlv9keO9D5_Lo(nKo@uhEBDhD|`g z62wD;&cNhdx}wdPfJhfy^LW|-D-<&ge2NlMj`@BnL>pM>IV@$K28cNpjTN0^u>3-5 zH=$N)ETYOFRMh6Pz*EZ-X&?^qZ4m_oq(d%-$cHh#cmfYhRSL6c?{E9}_iiOZQ!tuU zW*R||<@GVcrH`LoG#mogG|_k_Dj?pPBtI05tH)t79?7(OPd6tx9%Iqk}M}THJ zV!Spzu)np5ACEWKkY87g(ogn{F)+Z8v_z-^@rG{D)ZBcYiGaygm{O5^Yrp}g#VbxYdtY!J+vLK)&1e)M-xc}hTP$kUrwy08M#V4R-8P48{PFMJZBJ2hy zN>ll#1rJjrZ zvQnf>Ox#DEBJ@Q-%ti22lv8;)br};H52z_QH8liyH_8c3<|rV0+j=+?{eBU>kSe=9 z_?1<8JbD%uCV0eL^rzaHJMef()Bb)|ECvP>Q&Ur?e_(3nq81$Ht#H|c36;ZC)~=*v zH3kMV6Kq0$Z)a`bf<$K#M4+=E*<0{b5zYynEct>-EgFTC+qi^e#N-=~4mZZTL3Uuf z>t4!C$3`6y#Sg09$QurEr^}i5DJF!t;ia2-TQLz-*3#ltQC*Fp?#hW(@}iWHym|Qb z{Q6P(uY1_pTT$KUXL-UmE$k zXLk_@I6_^t| z>ZFdz9-E}WLlxQ}EL4ututG9Yn1**uE+>RsB*4vrOp&9)rH9Jp#kuXGK83s1a!Qv= zn(z%&lVt&zB&k`bqIfcZ2TfXY*=B~OCe$zvDTFz=X54$^&ViB|ew;bxq8i)Uqyl8` zjvvRz{M${wTvsc`C-!60iT6~tT(H?fRyu4MPecO~fd)Oe`}-!$B{cMQ ze#N5>I{hjkWI8%HOvQBNwQ2zU{C5hyfv;BMP0)|WENQc88b0q1tx_Ad;y!x%Cek-I z*Vq;z{~o}+rAY!-vGA;6PY5``E}i46J>tGS0@-^W!bB`Ip5GO7WjI#pBt48dgb%3zl{#;TBNa&pFpK2|qEETEJt)=-%so{`*L?Wgf}8Ia3E6oWrs%*Hs$ zQE|l!v3T)tHkGNaC{Wi>X0M}06Dv!Z;GpR(|K!Hlm-Q=myD6x>Zt)hX+66Nr)8(_g zK28>VLcvxE?r%;{Le9CZQ#u(d2gZUyEKUkGc4w?s#mGoSci~+5-uf43a&^Q9_=8;= zwjfcRgWDHlNc9A1_OhqPpZFkDf$8IEf?|4xf3pL6R(}Tm5cT)$F4DPTcoGB)>(t*_ zBc)V)TdFOGq#`K_vEFt8O_s^q*hy>R`@>bRIwH36PS476L~qD6VIgH5q+>0y4$**b z(%JBlpZaPzc)a!bnq)2n;aKY+0amFz>^I6Xf^?O^dS0f;qw=3Bh4E2Zp)YBkBfE-@ z&eMj58-5BYfxBs!Cd@NdR$PS_>UJ)S?^pfs6&)AXkdRMl)*!rHS5a5#oog!Kpg}ln z?t!_9x{9ajDvjX)V~&{~IEw80rtQn~*s>I+l(i79SRzGYuaF>F_lfVx403>=ae>7|t)CnmOj|!!QRkj8`=r7bI)~N z*Bb2az><6xhSXM@GzNouJ9ZL%4QyB{c?BqL`E~qIQjR&F5x%HRd|qRf^Tn*{;0>e! z1D1~WCs5B^b;|NjM8jIJUy;#LDiJ=*(}1PurUZBc-S$I5dGBru{ct5uJSSMKTpLRe6_UswP9q%6)Xw}#3hMe#0)SZs~24psZ zw$#)JyBrxnh89q0eMAp<35%g**Tyx$MUDgX+F#|5Hc`U7Ah$`_hV%tXv-KCfbEd$P z;aFp|7#SFcYA4k+=Z)e=uKW&YP?g6PrV443AFx08W>LUgi2y(@z(dloKzi8-O`zUb zgGB%yQLR2T(`nGQZY&qXra4(*Dd_)B+8=}FQC6Z-vJhZ(y@4UTeO1S?LhWJ|iy_m# z$kv98!uR+E>tt5vGFI5*#s@TJ{mc)TPIUoEGLNCrQ;iY{4QSwFh^LvHE#ULa2*}`R zp%Kcf(mJxQfSV-x`E0k86!ED$ojG(IuX}Nz5CI(J5_s z!T`6t02AISWzBf)|E?Kt_&lEpH}d{2CvQ5cLr8Yi&7GdJ!c-oH?M>ugqm!l_P-?dl zZ;?pUHD*-xb)bsPmRc;c>fp{*6`wwtf~2BuE-LuA>-?>md+N=Z!X-zyf|AW;8C18- z;LmDZatmU59?P+MgKPqIGNzt69opaQ(a4cxqv0vuWZjtYL5(avYV3y?@29qlQoS6v$AktkCt3x< zQ&=iCJetnQ!zzw+ZdMJs*pi`XABWZa>y%CGa-YQjX$sW?WE(y9L{2}jkzVYQ8DY@X zgnbv-JaPvEJx3sa|A>`aPfyPbLu}uO>tbP~47VwJ-qBQ%Qs#|KYc3+e2@QgAdzv+G z%-FGP@HmBE5*Z+#lQ)dGc<~}?#1VONqa8&(BJu;`zR~z7JqP3Kj~Tw#uu7=WPygLr z1=c=3bLI?HsYI`6Q{<&x7EPt?GsONU0sa%EI@J%g-n#goKgTes+{XnW0zK3(=^x#r zn)(zy!zmWJ%0f2PEmkU$`w`^+C_+SafHVe<609i3B|4oz2?H+lo8e^lBatsP!V=5G zVYW1-LtLEK($dnHRaD3EI5Q5TN~&iCJ(jPx1>iO{Z2+5^{-DZ_*ILJtn`hH7bUTH< z#*>jE!5WmlM>R_^P-1V8f6gkX<1`+@4Q3r3M_|k%9Fcccfn95k@~|Ydc6+g71q!vi z`?tT$oi7qZQ)_itUKV;jetsz!!e}_k@in($Wq7amzJ2>r&%E)%=wl*#;Kz?2J4K>g z=qkqB2DJ-+e*e#%2k6Nv>mwTe0Rgs-a>ZAX{3djj3T&jI2BlZ)erZ!V$oO72$`~HF*u2_C}cSx1BB#SUoq5SzDoIIBRGz9_-Ibs|P zm=Q#n#bixXIF5?)4)3S@nDZ#FO)AiMCS4A>mUOIz6k0Tv>!rrYE=$yuk}VS87iVFJ|^g0)~yo7cUDtUy%JeG z?lguEry!jPE6vz)-$H&UwxY!iM>q5Zwxh+Z^z3xTqB%CUV&fZXD7PK^_B})?v0{VI z>XKf1@mbVN+Z*T*Z&uZXeR9PDpkFgY8t|=`A)UE zQSwWd#%vfQMxpAR?x99t>Yp>HFC2?LWt3cYj8B)M?h?Bs%bd4vHO160A-Nk@HmODE6AcXbJgtl)UPA-H&Eg?o_H?F* zlp{t%tj0p{^6dI(Q@O=|s-JSi*pVtQI;jdS8HMI-d0H?1iy>wXzx2SfZ*6Tpsg3Ec zINV7DaA#-H#NJgsbRLpMPv4&zHxfd8(m3Q#+%#A+j3&L|a$51^Nm%+I1fwad*?3Y- z@X57|pZzVpQI#@a@RbrEuTPIQwS3I9jHF3i3tUB3mEKW&WiyYDOI|iuK*P(-P?(4) z@U$$z?d4St`5QiW+$ak)9yr*7r^g*LRQZ^_->-`v>|oNtVzDlL!+tdeKCF?7+7dON zhundw?B2YCe71r;2*p_0lzdFQd-wPP0fVq*J{zCb!!a;89_mz~D#=FVO*T52TN#YJ zKbNe^cN`4NgBWq2lmgBEf0bSYmdz+m_w^0oE%_pZ` za%#&B9sH0M3b#=rq&oA-#Sdy!#h4z0RZ(i1d@V-Ow#Re&ELX1Ztc?x}Np9qX(gk-A z-$&ClMjqcvvsfW9BT9A!W^ufG!lw)#?IEQ2-&x3(#f*qbrjgnrJx!)nW;v&-Wpuf! z05dMIK9Z|xC{&}JWOe=cGEr?NN1tCU%6CCcoW1tj4u&RmgybdcVsl zY#MNk3dT?meN#7+1%gFkv8RNxbSx@F%dH0<+vYtDu`j>u)X0kJudN0W<0%juwHA>w zzeNXQMI0|)L|mI((Xw6PtlOS^MD|=1#q4kUT3fwF{&kYBjNJ`ub#Z6x8J68dYQbJ) wV2sfjc*lSL!_2=l_?Hj-!T(J* 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 DpNolF diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 8836ffd4c8fa2b76f838f1461dd5af1d43a2e3b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmV-J0lfZ+P)p_mOQBuY_Ofq!EXCOm({&%f1VW{!8~ zy~}hQM|&&tHTfUdw(T$sUDwrheH_Q8Y5Klb!nmSuHa=XpNQ^DPXr zsw&_2PhUj+D2mY9BuSQKp<&d#lDj<5E6b8GM!)!9WauBc`EDU8UZ6iNCNuth3vYnZCe6{kRc>NZi%d6 z7}7Ld*Oi8mK4b_oZ=SP_^dUn?0{jaL;>Unnxh4$@@lE8B8h)ss;13ih VYS*BZ!D|2j002ovPDHLkV1gR=g(Cm} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 826dc2b8c224a3fb999101b84185c304247467d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 626 zcmV-&0*(ENP)lKp^09x%hoJ9Co|i>2&&w zV4u&I&1Tc-bSxIL*=(QBuh;8xxg3o~{eFMD-HHUe-R^R^Tr3u?R_oWdY{1WlN~O|x zJg(R4o6SZ5I1-7p+ikz!|8k_sWXfbRiA17OsSE}KL*RHk-tBgsPUlPNryLGPv)R1e zZo}bFCpa37YNbkI>5WEXzu(X2bIoARwbg3rph~ja?VV01pUqR0YOgaXLy&6nMQ}s{-Td z_$xLigvxRki-jsM!qr8AXS3P)d{zs_O>uQh#yFT*BKuq(OoSjy;OYjc(swj~5ebGY zfvfAL-tYHnwR*W+w1Jr{35G18Z^NN7^~q%NY=BY&;iPHqWC>hdD-|d1^?C|pUUV*U z6A6Yap}u=dYgMMc1|xD4p2`Z;SQZDwxhm|i1~9@~NHAmxW`Muqbbkz>OK@MgIn?m8 zYSIIiG<^tH$6s+e$#X!`5$R2`T|x|466LmfNb4Vsf<@^K{{O+h0KVz?ga8p?BLDyZ M07*qoM6N<$f7>A>Oq*Me#A3{q(5YZA5I5|k+;2;zjB3KfkQ*8}`xM*%d1kDZxx(O|AE>2P6 zBofq+L{J({g`oNUFo>W!=RM~h@Ann=8S6dwKAg^d&+lz;dV0F=r!n}i1!OR!^}>+W z3qx8j3~9YkCOtntzq-1*u&^*YJ3Bc!IWjVWAHKf6K0G|!+}xB(r9z=_adFXZx3#}Z ztMu5|*xK6K#>U3n+}!8i_V@Sy>9V}M91I2EaiAG^D| zvQ~V2J|DVxE|;Selcdqz&?_{4d0b+#ShWkck|d3OEZ5zHFq_Rd+%A`^)9J7)`O>4K zqqsp7iE+TS>U28u`8>OfFTK9LPQUZC69@zpNF!G$@*#ngVzpZD?(P^h9BE{?FOyJD zvD@w0Y?e{Lkw)57>O%r;Taz^Mu~HurGcz;n0**8iy*e2x$WrVAjx=Tvbuv^YCMMVg z9O<{INoR(KhuH^pIpSnX|JqN+mUW6pKZCk#4tJgB6QvwHlAd ziNyke2@kc|Y`wT3A0Hogc6MG~UWoMq()D`Xu5kI~(Tz2YE|=5k3BrxpY24~`I{7YEtJQ9| z;{(O?k2?sH>D!!#`vQp``50*v*$qhuxq^F3_g4BH>|eHvlBaR`jf3s~i6N~QhO}N7 e(t7bDrT+p$##{=U>e{^k0000!^YCM09%aMXSA?r5%6vZW^?DrkalKNheBb2z{%khe?RGi1jYh+^?Pjx?aw1*V z9mg3ChwJrPh|5Vcp^<8}nq^so!CTQTDAF{oUayB?_@2w~-{&;1+U<5C7e^I?zScf*v1ws$toDhE z_pvFCD!`2y?}0jr#*Jxw~AG S^DImN0000%F(@XN&ha@A-J%bDr}&v|+Vcb^i_I&>{NlVz;3r+U0pRAjYJ|b91gk0a_eG_ z^Ye4wQD1Sp-Nj-t7z~cbWA;&ey4ZiQSd3Fl5r@N3DwV?FFuO5_F4h&UT$`1O%jK$8 ztI=o_DUT(dRTpa@Wehp$j5`2BvQJZdnLF7iMsl~OIH2#h@*4-zvql1UdiOE?cO z5|790^*S|C&~*6hPkCzfiJNbNmG#U|ND7vTzj*pL} zS}9^So4sCdrBWfpP;^nd5b6Qx_X()}={IUEgE@M$A7xJVe zUG&`et*g0#7d$^d6P}WE2ZI56ZZxs7l}Oy*-xHovj#{hLTBYvW+nf060+h@sl%y(A zrXvSXbi3Vdr_({TFHcF4$z+0to*YBZyXA6uHM+H0?e+DQ97ET|Td>AY}9%27{$i$>nmXaQhuxxu>V6kB^Ux@f;P|csve=!_{h4 zR-(YVa=Gew3rHA8qtQa4pxzi?AA5kyRVPsI;`90aevii!kH;+*3-55Cuq@ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index 61789674e2f3ccadf5ba32111875d7f94d28e58c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1420 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 T_i9%0)08~XzNlumh}{1GcOGxh diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png deleted file mode 100644 index 37d3359909a36fc4045088bdeebba87ad9cd3bd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 785 zcmV+s1Md8ZP)_zxtmL^h$)Dl{4z4Fx?K3MvtaM70~y?P`@0k!YyI zM?oPGUvZynHmh;Y%rSHB?k1j2&THm3$DH?l9{11B&v!ph_umb0SvM>e-LP16!(x$K zEVMnV)oL^vH5v^*?smJ`Y&IASdcEG^aQNR?4u`|*^?E!WyBtyZT}sbDZD{VR}Gt4*iVe!o8& zjgH4-g|SpBRk>VtyWLDoUvfH~jYb1-lgUIeEHLGAxvQB3>2$hEr4kB-q>zbY;fR$= zC7DcuOeWLo>uVqo7!HSWv69K8B20oFA0G>ag3ssMZnxKC;oPylFoQ|+PATC$F=N3O@WPRB3nCW03LluW34Wsnh9@mWWu$EyxJ` z<_~_sZnsNXrJY3N7JgW*R*MokXjsTnc=3@D6lVfZy_=uh-spa7O2oTK7N$O!P` z@V@Yw!v4XlfCe)d^A59x>CYp%SYH~QB{y}Dts=!V6jABy!CLKiWMof+Cz P00000NkvXXu0mjfpzc6hD@Q6Rk-(kkJR=sVo4IMzL%#yn5CGUVue5kl`?}3E*HvpDqg) zDv#aXFQ7?-Q&Lj)zK=4_YrJ*qWJ1E|@ut-V=`{ZQ_wp@7ciz*n(s{^5D!RzGrf)(s zQ2x=O(>9X9fPn9nt}rmbZrP%Uoh4pX9I1M01Rk22ni?5ly*`zkwp!#~*zsnYi;L>T zi%QdeOPDS=^L_}9b7`%jSpLt;mn{+r5JZPtM3gE&oVcUbXf(HP-?p%@$TGNg2n~(! zvq!k#@nP{9y&XU|>qj;b*%MZ%@Y&Y|7;D~itUz^Vy9RLd#m8eLLs}efoo$d0K;rQIZ8#i=p;Rj4WWJXmm6(z& zQDxZI`Q*{Vp{-PqB?;` zes7cYw1ZZz%6>q1%!3iH+duE=e9GMghPj#N%1FLD4LwEl^yPDSJEP(Du$zPhs(_|2 zTGs_gi+7r_AWdSiAz1q~qg@U(0Mhk|Vq|NMG5%E&K6h0Pdn67w_M*HXo$gqjYR{Il z%=wlvLs--0i?N*$cF;urnKMfU_ua2ojf)-vJ=quMMjwJ0(Am0-G;+W$$)R77_1)%T_em2*pXyLJO{x+K~i_0Mk~CkrqoUJ~`jF;rG9jn6XW zpBYoatbWc5n&I@oB$<{j7V#bP4T2sMiqe(CM)eR6Pjg`+I& zX30)o-ypZSxw)*od{^!c+ck8j%RO(oTr0F<6vTV~ zoDh}HUM>0O=1+4K869u1e`l+Gk};|tcV_n|!zlSA-BA||dYp0~D5#DZ&%l(l8m#p4 z9PH`#$cdyi2C|-M@9(5Lk~OM;=TU^8VVIStlBdX(P*?x@yBYWBb*gd8N1>PsZZ?TK zphMw5+ZX;ceE8Ej9C9EELF37W>H-Cb3ygeQTe^VIv0tc+M0%xMc^8f9$F;ScGE`)^ z={jlH0}}NOB!K5;o*twS$8HwqqC+=8gAm%k$O1_9+RO&2-ozW*>A=5W C^v(DH diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png deleted file mode 100644 index 687010b82e72780df3a4dedfe9d89c98e1d5a9c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 942 zcmV;f15x~mP)KoXi#T9iaIH$f2Etf5qBa8sNl36 znj;8DLl6t4hk|=A`1(EPyzhCv_jY{-QTQIu$Mc@^JO1`yDcRUb`R z_0g17AN{YaOG`^DD=T<47!3IM`uh6({QUIvG#n14e7{!K<>lp_ot^FNZHL2QwOU6D zy4`NG*?f3-C>D$LdR@z0>RH#;){c&jc6WDYXJ?5rlgaesQwIkJtyb&o?5tX?s+&*3+y@$oTDIjW6~jZ7wUb#;|UB(UD1lM1uK>%`-6r_(7q@Jsl49*<{#e?Jfil*?tI zv0|*NtE)FRH;aplLZe27^OsJigTY`no8=!Rz-qVKbGh8Y!h$%Pg0R4`Sj=cNVoPui z;bjHr_4TzZ(4Pv0LY+>hP$=*e^Ri+Mv_JY$p%X_Uky@?RZnv4mT&y_tst*%WX0th! zO1a(c_xE?YlFf=fiuNQ4Oj)=`(0!B1Bwfa4_4$0FeN=_KUhl=lMX%Q*Y8b4zLeUGQ zQYp(~vAA5Wd_GUqFj#TdPhe5Mw70jXi4~?qDvzeQ7_8X!@?aR6pP%2{+-x)&;|eHN z+=PlYPd{yKZK-61an^x{5cb$Lk2!hXBGy`^i|{WTm8XEGVRo#Ex><;%+pUBhOr zR;!4`9*;*7m4eW4qR}X`jO*S?BoasmI@tp}{nOLae!tJG8AZp-@Pc{{pDr?;i{Xob_Tp3x}-J z=|mzCv)L@p_Dk5Kc=T9Ne$W{ep0H3T)M~X-h&_InhVao_J3Es8o7w_>5#SiRb8YxsdA-mytq=V%+uEow%8p4WE#vT+r+F z@}I(c#AT$=(f?s!^WdInQ!)wG-_`h(tZ!4)cgb6So2IP#Xv(UOrmXtt1JhYf;lkyT Q!vFvP07*qoM6N<$f|~2$WdHyG diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png deleted file mode 100644 index 284f7ce43ec1003f128a2dbc70ef1a6cc8552a54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1771 zcmb7_`8OMg7RPBj)Y9o#+CwLVZiuC79a~!-$_$~Xm!d)vB%&|2s5FYf)S(kzqqY!1 z8dMg8WF&*6RB9|KN)U$7Dr)N%)Y_Lh?=P5hKKI_A@ACcLJb&=@6RKxa6%-Usczd}A z$ZO)?ar}rpD+n|}3JOXs-tMkJsfz1lKlfy)PA}E)qK8IQ?W@KbcdMt5?NAy*vjCG< zwZo13ev=RRWs zp9YzVTGDVNY4d@7_yj5CZEq|u~ zI#f622LN9nXj?1GcoxkZUfSN9o}R8CkyO>xzOmwlSzweFZiR;8akxBBYFEUEm6iN` zweID;8v5Jon2A!y_(WN406EpNN#Sf zxw(08LD}|up`wzqvbibuG)hQp;c}6kqeI7z{9II|)xn00b9-SEBnm~q<2l&a^tcRX zWPy)9L_ncHRgJr@5fhGAuQp|ko&o!K zNOU_a%<@DR%@)7R;c$F*E_6Bm-rnBMX6F!&UQf4R%7@+Wcn|@HpVKc)3Oag^389X4 zY-K1Xoh}{g>2W`=StIgk!lKdWh=@#Qb1g}1CaWaH$dhsE0QIedDC-3phto|eu{3H9 zmhH1um^E~|n)&+dto}_mD<+o|C#fLLCGso{iG2(2-kJEi5zQ1rO99wj#lhO5`}gk` z`*~1L1!9wvAD=HKR8PFia?m0oNQxbqw~P9$UPYC?cFyx_$xwFy!`H6^DVmc{aw$S z2kPKhJU+CpyFd0022(m%Z9t>ZCN27XASO%*gG3@Btc-s`qv>}-rl+QQs>=N-8yg!l zGq>mCO_-aTn^yCnbGX=mkkyqHME5D>m>5zWgzD z6mWwnLq>oKtyE|Ic}N12OeS|XJGf%8oOlLzyrsozezNk}dWu?aKKf2voOXdorGH@H z7r2m0!IZBapq$S9XpNai_z>=#e@v z$9MPl%>bq>Tg^NlFjzI0UN~r-6X0P$1`o&v^FPaE=K-b`7s`ERXJ_wqhR>_#YZdnX z0;esKP^53iL1`zQ`~pl@GNtcJ`>M^jr-AzVInQbA2hZm@9NTjB{;a?vmC{%F98D4V z9Ht{23LVH9V7(bZvIPRDRbJLy2ckQQqm=EsgdzxGL}1z@@jWbd^UD|G&;KE1<+vI4 zsUH*V_xJy3@Ll{Me{OxQj(3yYGH5<5G)$K=*D@Mj1mRz)fMV``WvHWN)5Lg{ZmcWY3Zgi+5Jiw9u@HU#6KE?*>;c9UQnUnprsF1qRAwlMwBxLzxq!;tC#* z??Y%t-$9`+UAmMoueCu%OLh=1UC8V7N&A*pZ-Z|3gjs!n7l z?%v*>yfX&8hM1T(G9!%`I+LZP4+LV6NVyz7?j6YQp1lKah8Snt7R&C&g_5LFDM4NC zjp0pga))Sd4_!$#J;~B!#p`YGb2K1!0{i^-+Hsb3~N}#6RH&0otl=hjo zZ{I>4t#hhZ)6&x)@ayZ1;v9WO*=X3I#vUiVYcOK_#-Fv7!7*}i{8K)ti2=!#|F#_1 Q`umA_dw|_pZV_4k0~{2JUjP6A diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 7863936769aad68485f34171246768d5362f01f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1786 zcmbVN`9B*77RRZ^$Dt?RdJWNvSp_v8tR(VwIy{eQsS(bEXK?*NFph*TcP8q z1d*xH*;03g-Tgskmd)ANCK}eV^}p@AE$Im-l_2cToI&uY*;gs!B>q zU{4GRtMH^RdPYTI+W@usN=kqio~Ua9B;~cLaMX6V{;;8rhzw(uQyaTAg=hlw5J!h~ zk+nuyMIqB@RJ!KRR5;WVwH%eM$<`z?d~h_=A*-l6tFR2sZ;qr6Cc^E*wdLh)=bDWg z!AF>1roee-BO$KluIq=xkk{bI+j2EADBY#?(PCrl-tFUiHQk` zkd~=cT)wx-O;ZTSc|;h26j?F(AGTj4D^h-be$mmsO#&~A+9bilIiJ863Kr& z=sTJwUC_r@98A_qEqPk;=--GPAM;9DBg$^UeAUczAvDTtL>-s1C_` zh(&?3;RX!C!!P74je7NZT+qO9#tI7yX++zi^M;0wSNmflGct7Ux(@lif_HH^z-{Lt zRd8KPi?&?}oepkRokB&Mb@6z~BM&NJB~=^i>uI=$k=pbQlg?fXB_!BmaCv!oclX=O zL;}l_p)9vNUkvHw|8UrdYpv@9aAOzK-t-R`)NPh zVy9dKv{mkPI`3MP3yAgXw)J6^N<-hqZg-f~+~Qq;sIB5plaVPYuOmczjcW|m381^n z6=M#yEj3+!<`}_c9UdN1>_b#8T9@}>*MsV*a`jZUs$d)=l2s>XpNf0nRo@#{c@dh* zmLKyoRBH3El^2m3XZ)$5YIFYvmEzmQKJX;VX|WhS=*bdKPn!<@nm@K&2f0jJbe1fqetd?;f-_;_GeB3DQH6=5UiRZnf!kezyrp}`(9JTOV$BZmO?-BMDd!9dIAA{K3gO zCh~PhN5^L7Ga3;(s8|H4^n7&w%Sn)ky=DaEUf5_hc*(M>@V=;ZHs7r*2dXZr_ypfH zJ3_wZ8mJc%+gC}MnGY%|B6*~`x|zh}WItcuTc33>`aRSe>A&>8FDfefM0$4%4gx%v zNO0(%pK!J46wNKBg1FHn5D3(bfCM(DjP#vLiHV6xO!UEN(-yY(6^$hBJgU-rpFqmV zxz`xC^z18*E3cW6`-_WcocX^lN7iHSBqgc;rR+}2-GLaQCc9vOaITrt&``!?zLLFE zi%r{WgG)+V3;b{WXrUGzZJ}c)d}9AZ>yO)uC+>*v`zx)he1!smR?QSUum9_glAd~> z00dGrJ8QI*O`aaXFR&fdmDqz)X*E@H{bTfVDvEeX*g_;?xwCce~C=gdZfI?7F_aPoC{Qz}nJCXCOnpo!kOAD)?`W{*rHiW2GzNnayIUR4S8Y zaK`ozk6&O`sDn}5zWI4$YVeFac3}2IS(_rq)T+dM4{C|Tie&CZI&S|v=E8G^3$Oj= T_i9%0)08~XzNlumh}{1GcOGxh diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 2ea76d6259c597d32c3fc6a3b0c93d42187721ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2305 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index 16a1ae1616a355cceb9105d0cf6606ec003e5610..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1269 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 - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Lightmeter - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - lightmeter - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UIApplicationSupportsIndirectInputEvents - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - NSCameraUsageDescription - Provide camera permissions in order to make measurements - - \ No newline at end of file + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + $(PRODUCT_NAME) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + lightmeter + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSCameraUsageDescription + Provide camera permissions in order to make measurements + + diff --git a/pubspec.yaml b/pubspec.yaml index 3cfdf6d..93635ef 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,8 +45,7 @@ dependencies: dev_dependencies: bloc_test: 9.1.3 build_runner: 2.4.6 - flutter_launcher_icons: 0.11.0 - flutter_native_splash: 2.2.16 + flutter_native_splash: 2.3.5 flutter_test: sdk: flutter google_fonts: 3.0.1 From 55b0e52d7f8cec83806e7fa6a03c8376bc65d9b3 Mon Sep 17 00:00:00 2001 From: ScaredCube <74418739+ScaredCube@users.noreply.github.com> Date: Fri, 1 Dec 2023 21:54:44 +0800 Subject: [PATCH 13/13] Fix Chinese Translation Errors (#140) --- lib/l10n/intl_zh.arb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 714c4b3..3844a76 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -98,11 +98,11 @@ }, "buyLightmeterPro": "购买 Lightmeter Pro", "lightmeterPro": "Lightmeter Pro", - "lightmeterProDescription": "解锁额外功能:\n \u2022 配置文件,其中包含光圈、快门速度等参数\n \u2022 胶片预设列表,用于在反转率发生故障时提供曝光补偿\n \u2022 点测光\n \u2022 直方图\n\n您可以在 GitHub 上获取 Lightmeter 的源代码,欢迎自行编译。不过,如果您想支持开发并获得新功能和更新,请考虑购买 Lightmeter Pro。", + "lightmeterProDescription": "解锁额外功能:\n \u2022 配置文件,其中包含光圈、快门速度等参数\n \u2022 胶卷列表,对胶片倒易率失效进行曝光补偿\n \u2022 点测光\n \u2022 直方图\n\n您可以在 GitHub 上获取 Lightmeter 的源代码,欢迎自行编译。不过,如果您想支持开发并获得新功能和更新,请考虑购买 Lightmeter Pro。", "buy": "购买", "proFeatures": "专业功能", "unlockProFeatures": "解锁专业功能", - "unlockProFeaturesDescription": "\n \u2022 配置文件,其中包含光圈、快门速度等参数\n \u2022 胶片预设列表,用于在反转率发生故障时提供曝光补偿\n \u2022 点测光\n \u2022 直方图\n\n通过解锁专业版功能,您可以支持开发工作,帮助为应用程序添加新功能。", + "unlockProFeaturesDescription": "\n \u2022 配置文件,其中包含光圈、快门速度等参数\n \u2022 胶卷列表,对胶片倒易率失效进行曝光补偿\n \u2022 点测光\n \u2022 直方图\n\n通过解锁专业版功能,您可以支持开发工作,帮助为应用程序添加新功能。", "unlock": "解锁", "tooltipAdd": "添加", "tooltipClose": "关闭",