diff --git a/integration_test/generate_screenshots.dart b/integration_test/generate_screenshots.dart index 2c28d42..8c9b8c4 100644 --- a/integration_test/generate_screenshots.dart +++ b/integration_test/generate_screenshots.dart @@ -27,13 +27,11 @@ import 'package:lightmeter/screens/metering/components/shared/readings_container 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 {} +import 'mocks/paid_features_mock.dart'; class _MockUserPreferencesService extends Mock implements UserPreferencesService {} @@ -112,28 +110,17 @@ void main() { 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(), - ), - ), - ), + MockIAPProviders.purchased( + selectedFilm: mockFilms.first, + 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()), ), ), ); diff --git a/integration_test/mocks/paid_features_mock.dart b/integration_test/mocks/paid_features_mock.dart index 433216b..27850b4 100644 --- a/integration_test/mocks/paid_features_mock.dart +++ b/integration_test/mocks/paid_features_mock.dart @@ -1,65 +1,81 @@ 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'; -import 'package:shared_preferences/shared_preferences.dart'; -class _MockSharedPreferences extends Mock implements SharedPreferences {} +class _MockIAPStorageService extends Mock implements IAPStorageService {} -class MockIAPProviders extends StatelessWidget { +class MockIAPProviders extends StatefulWidget { final IAPProductStatus purchaseStatus; + final String selectedEquipmentProfileId; + final Film selectedFilm; final Widget child; const MockIAPProviders({ + this.selectedEquipmentProfileId = '', + this.selectedFilm = const Film.other(), required this.purchaseStatus, required this.child, super.key, }); const MockIAPProviders.purchasable({ + this.selectedEquipmentProfileId = '', + this.selectedFilm = const Film.other(), required this.child, super.key, }) : purchaseStatus = IAPProductStatus.purchasable; const MockIAPProviders.purchased({ + this.selectedEquipmentProfileId = '', + this.selectedFilm = const Film.other(), required this.child, super.key, }) : purchaseStatus = IAPProductStatus.purchased; + @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) { - if (purchaseStatus == IAPProductStatus.purchased) { - return 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: child, + return IAPProductsProvider( + child: IAPProducts( + products: [ + IAPProduct( + storeId: IAPProductType.paidFeatures.storeId, + status: widget.purchaseStatus, + ) + ], + child: EquipmentProfileProvider( + storageService: mockIAPStorageService, + child: FilmsProvider( + storageService: mockIAPStorageService, + child: widget.child, ), ), - ); - } - return IAPProviders( - sharedPreferences: _MockSharedPreferences(), - child: EquipmentProfiles( - selected: _defaultEquipmentProfile, - values: const [_defaultEquipmentProfile], - child: Films( - selected: const Film.other(), - values: const [Film.other()], - filmsInUse: const [Film.other()], - child: child, - ), ), ); } } -const _defaultEquipmentProfile = EquipmentProfile( +const defaultEquipmentProfile = EquipmentProfile( id: '', name: '', apertureValues: ApertureValue.values, @@ -68,8 +84,7 @@ const _defaultEquipmentProfile = EquipmentProfile( isoValues: IsoValue.values, ); -final _mockEquipmentProfiles = [ - _defaultEquipmentProfile, +final mockEquipmentProfiles = [ EquipmentProfile( id: '1', name: 'Praktica + Zenitar', @@ -103,3 +118,5 @@ final _mockEquipmentProfiles = [ isoValues: IsoValue.values, ), ]; + +const mockFilms = [Film('Ilford HP5+', 400)]; diff --git a/lib/application_wrapper.dart b/lib/application_wrapper.dart index f8627f9..fe53aec 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,8 +29,8 @@ class ApplicationWrapper extends StatelessWidget { ]), builder: (_, snapshot) { if (snapshot.data != null) { - return IAPProviders( - sharedPreferences: snapshot.data![0] as SharedPreferences, + final iapService = IAPStorageService(snapshot.data![0] as SharedPreferences); + return IAPProductsProvider( child: ServicesProvider( caffeineService: const CaffeineService(), environment: env.copyWith(hasLightSensor: snapshot.data![1] as bool), @@ -38,8 +40,14 @@ class ApplicationWrapper extends StatelessWidget { userPreferencesService: UserPreferencesService(snapshot.data![0] as SharedPreferences), volumeEventsService: const VolumeEventsService(LocalPlatform()), - child: UserPreferencesProvider( - child: child, + child: EquipmentProfileProvider( + storageService: iapService, + child: FilmsProvider( + storageService: iapService, + child: UserPreferencesProvider( + child: child, + ), + ), ), ), ); 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..8707d55 --- /dev/null +++ b/lib/providers/films_provider.dart @@ -0,0 +1,102 @@ +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 Widget child; + + const FilmsProvider({ + required this.storageService, + 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: 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/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 efd7b6d..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; 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/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..9591cae 100644 --- a/lib/screens/metering/screen_metering.dart +++ b/lib/screens/metering/screen_metering.dart @@ -5,6 +5,8 @@ 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/films_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'; @@ -15,7 +17,6 @@ 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 { 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..2d2fe72 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,9 +1,9 @@ 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/user_preferences_provider.dart'; import 'package:lightmeter/res/dimens.dart'; -import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; class MeteringScreenLayoutFeaturesDialog extends StatefulWidget { const MeteringScreenLayoutFeaturesDialog({super.key}); diff --git a/lib/utils/selectable_provider.dart b/lib/utils/selectable_provider.dart new file mode 100644 index 0000000..76c9493 --- /dev/null +++ b/lib/utils/selectable_provider.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +enum SelectableAspect { list, selected } + +class SelectableInheritedModel extends InheritedModel { + const SelectableInheritedModel({ + super.key, + required this.values, + required this.selected, + required super.child, + }); + + final List values; + final T selected; + + @override + bool updateShouldNotify(SelectableInheritedModel oldWidget) => true; + + @override + bool updateShouldNotifyDependent( + SelectableInheritedModel oldWidget, + Set dependencies, + ) { + if (dependencies.contains(SelectableAspect.list)) { + return true; + } else if (dependencies.contains(SelectableAspect.selected)) { + return selected != oldWidget.selected; + } else { + return false; + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 090b8ce..934bfbd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: m3_lightmeter_iap: git: url: "https://github.com/vodemn/m3_lightmeter_iap" - ref: v0.4.0 + ref: v0.5.0 m3_lightmeter_resources: git: url: "https://github.com/vodemn/m3_lightmeter_resources" 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 index 4692ee6..819a9eb 100644 --- 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 @@ -2,8 +2,8 @@ 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_iap/m3_lightmeter_iap.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import '../../../../../application_mock.dart'; 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 index 5d44bcb..8cc4d38 100644 --- a/test/screens/metering/components/shared/readings_container/film_picker_test.dart +++ b/test/screens/metering/components/shared/readings_container/film_picker_test.dart @@ -1,75 +1,37 @@ 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/res/dimens.dart'; 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/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'; +import 'package:mocktail/mocktail.dart'; import '../../../../../application_mock.dart'; +class _MockIAPStorageService extends Mock implements IAPStorageService {} + void main() { - group('Film push/pull label', () { - testWidgets( - 'Film.other()', - (tester) async { - await tester.pumpApplication(_films[0]); - _expectReadingValueContainerText(S.current.film); - _expectReadingValueContainerText(S.current.none); - }, - ); + late final _MockIAPStorageService mockIAPStorageService; - testWidgets( - 'Film with the same ISO', - (tester) async { - await tester.pumpApplication(_films[2]); - _expectReadingValueContainerText(S.current.film); - _expectReadingValueContainerText(_films[2].name); - }, - ); - - testWidgets( - 'Film with greater ISO', - (tester) async { - await tester.pumpApplication(_films[3]); - _expectReadingValueContainerText(S.current.filmPull); - _expectReadingValueContainerText(_films[3].name); - }, - ); - - testWidgets( - 'Film with lower ISO', - (tester) async { - await tester.pumpApplication(_films[1]); - _expectReadingValueContainerText(S.current.filmPush); - _expectReadingValueContainerText(_films[1].name); - }, - ); + setUpAll(() { + mockIAPStorageService = _MockIAPStorageService(); + when(() => mockIAPStorageService.filmsInUse).thenReturn(_films); }); - testWidgets( - 'Film picker shows only films in use', - (tester) async { - await tester.pumpApplication(_films[1]); - await tester.tap(find.byType(FilmPicker)); - await tester.pumpAndSettle(Dimens.durationL); - _expectRadioListTile(S.current.none); - _expectRadioListTile(_films[1].name); - _expectRadioListTile(_films[2].name); - _expectRadioListTile(_films[3].name); - }, - ); -} - -extension WidgetTesterActions on WidgetTester { - Future pumpApplication(Film selectedFilm) async { - await pumpWidget( - FilmsProvider( - child: Films( - values: _films, - filmsInUse: _films.sublist(0, _films.length - 1), - selected: selectedFilm, + 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: [ @@ -82,12 +44,67 @@ extension WidgetTesterActions on WidgetTester { ), ), ); - await pumpAndSettle(); + 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.tap(find.byType(FilmPicker)); + await tester.pumpAndSettle(Dimens.durationL); + _expectRadioListTile(S.current.none); + _expectRadioListTile(_films[1].name); + _expectRadioListTile(_films[2].name); + _expectRadioListTile(_films[3].name); + }, + ); } const _films = [ - Film.other(), Film('ISO 100 Film', 100), Film('ISO 400 Film', 400), Film('ISO 800 Film', 800),