Compare commits

...

5 commits

Author SHA1 Message Date
Vadim
2e929d3630 Pass availableFilms to FilmsProvider 2023-10-09 17:39:40 +02:00
Vadim
12f222e334 EquipmentProfileProvider tests 2023-10-09 17:34:47 +02:00
Vadim
9d1c6534ca FilmsProvider tests 2023-10-09 17:05:19 +02:00
Vadim
68ccc5f01e Synced _iap_ stub with repo 2023-10-09 17:04:24 +02:00
Vadim
e06ee35265 Moved EquipmentProfileProvider & FilmsProvider to the main repo 2023-10-07 22:25:04 +02:00
25 changed files with 1038 additions and 287 deletions

View file

@ -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<Film> films = [];

View file

@ -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<EquipmentProfile> get equipmentProfiles => [];
set equipmentProfiles(List<EquipmentProfile> profiles) {}
Film get selectedFilm => const Film.other();
set selectedFilm(Film value) {}
List<Film> get filmsInUse => [];
set filmsInUse(List<Film> profiles) {}
}

View file

@ -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<EquipmentProfileProviderState>()!;
}
@override
State<EquipmentProfileProvider> createState() => EquipmentProfileProviderState();
}
class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
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<EquipmentProfile> {
const EquipmentProfiles({
super.key,
required super.values,
required super.selected,
required super.child,
});
static List<EquipmentProfile> of(BuildContext context) {
return InheritedModel.inheritFrom<EquipmentProfiles>(context, aspect: SelectableAspect.list)!.values;
}
static EquipmentProfile selectedOf(BuildContext context) {
return InheritedModel.inheritFrom<EquipmentProfiles>(context, aspect: SelectableAspect.selected)!.selected;
}
}

View file

@ -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<FilmsProviderState>()!;
}
@override
State<FilmsProvider> createState() => FilmsProviderState();
}
class FilmsProviderState extends State<FilmsProvider> {
@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<Film> films) {}
}
class Films extends SelectableInheritedModel<Film> {
final List<Film> 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<Film> of(BuildContext context) {
return InheritedModel.inheritFrom<Films>(context)!.values;
}
/// [Film.other()] + films in use selected by user
static List<Film> inUseOf<T>(BuildContext context) {
return InheritedModel.inheritFrom<Films>(
context,
aspect: SelectableAspect.list,
)!
.filmsInUse;
}
static Film selectedOf(BuildContext context) {
return InheritedModel.inheritFrom<Films>(context, aspect: SelectableAspect.selected)!.selected;
}
}

View file

@ -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<void> 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()),
),
),
);

View file

@ -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<MockIAPProviders> createState() => _MockIAPProvidersState();
}
class _MockIAPProvidersState extends State<MockIAPProviders> {
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)];

View file

@ -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,
),
),
),
),
);

View file

@ -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<EquipmentProfileProviderState>()!;
}
@override
State<EquipmentProfileProvider> createState() => EquipmentProfileProviderState();
}
class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
static const EquipmentProfile _defaultProfile = EquipmentProfile(
id: '',
name: '',
apertureValues: ApertureValue.values,
ndValues: NdValue.values,
shutterSpeedValues: ShutterSpeedValue.values,
isoValues: IsoValue.values,
);
List<EquipmentProfile> _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<EquipmentProfile> {
const EquipmentProfiles({
super.key,
required super.values,
required super.selected,
required super.child,
});
/// [_defaultProfile] + profiles created by the user
static List<EquipmentProfile> of(BuildContext context) {
return InheritedModel.inheritFrom<EquipmentProfiles>(context, aspect: SelectableAspect.list)!
.values;
}
static EquipmentProfile selectedOf(BuildContext context) {
return InheritedModel.inheritFrom<EquipmentProfiles>(context,
aspect: SelectableAspect.selected,)!
.selected;
}
}

View file

@ -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<Film>? availableFilms;
final Widget child;
const FilmsProvider({
required this.storageService,
this.availableFilms,
required this.child,
super.key,
});
static FilmsProviderState of(BuildContext context) {
return context.findAncestorStateOfType<FilmsProviderState>()!;
}
@override
State<FilmsProvider> createState() => FilmsProviderState();
}
class FilmsProviderState extends State<FilmsProvider> {
late List<Film> _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<Film> 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<Film> {
final List<Film> 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<Film> of(BuildContext context) {
return InheritedModel.inheritFrom<Films>(context)!.values;
}
/// [Film.other()] + films in use selected by user
static List<Film> inUseOf<T>(BuildContext context) {
return InheritedModel.inheritFrom<Films>(
context,
aspect: SelectableAspect.list,
)!
.filmsInUse;
}
static Film selectedOf(BuildContext context) {
return InheritedModel.inheritFrom<Films>(context, aspect: SelectableAspect.selected)!.selected;
}
}

View file

@ -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<ExposurePair> exposurePairs;

View file

@ -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 {

View file

@ -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;

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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});

View file

@ -17,7 +17,10 @@ class SelectableInheritedModel<T> extends InheritedModel<SelectableAspect> {
bool updateShouldNotify(SelectableInheritedModel oldWidget) => true;
@override
bool updateShouldNotifyDependent(SelectableInheritedModel oldWidget, Set<SelectableAspect> dependencies) {
bool updateShouldNotifyDependent(
SelectableInheritedModel oldWidget,
Set<SelectableAspect> dependencies,
) {
if (dependencies.contains(SelectableAspect.list)) {
return true;
} else if (dependencies.contains(SelectableAspect.selected)) {

View file

@ -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"

View file

@ -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<void> 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<List<EquipmentProfile>>()).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<List<EquipmentProfile>>()).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<List<EquipmentProfile>>()).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<List<EquipmentProfile>>()).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<List<EquipmentProfile>>()).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<List<EquipmentProfile>>());
},
);
});
}
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<EquipmentProfile> _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),
],
),
];

View file

@ -0,0 +1,264 @@
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<void> 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('');
},
);
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);
},
);
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('');
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.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('');
},
);
},
);
}
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;
}

View file

@ -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';

View file

@ -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<void> pumpApplication(Film selectedFilm) async {
await pumpWidget(
FilmsProvider(
child: Films(
values: _films,
filmsInUse: _films.sublist(0, _films.length - 1),
selected: selectedFilm,
Future<void> 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),