mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2025-07-07 14:40:40 +00:00
Compare commits
No commits in common. "2e929d363072d0c374c0c3f542f3618f62704e64" and "069a07214c7d6590a87b2d757a306202053dad34" have entirely different histories.
2e929d3630
...
069a07214c
25 changed files with 288 additions and 1039 deletions
|
@ -1,9 +1,34 @@
|
||||||
library m3_lightmeter_iap;
|
library m3_lightmeter_iap;
|
||||||
|
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
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';
|
||||||
|
|
||||||
export 'src/data/models/iap_product.dart';
|
export 'src/data/models/iap_product.dart';
|
||||||
export 'src/providers/iap_products_provider.dart';
|
|
||||||
export 'src/data/iap_storage_service.dart';
|
|
||||||
|
|
||||||
const List<Film> films = [];
|
export 'src/providers/equipment_profile_provider.dart';
|
||||||
|
export 'src/providers/films_provider.dart';
|
||||||
|
export 'src/providers/iap_products_provider.dart';
|
||||||
|
|
||||||
|
class IAPProviders extends StatelessWidget {
|
||||||
|
final Object sharedPreferences;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const IAPProviders({
|
||||||
|
required this.sharedPreferences,
|
||||||
|
required this.child,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IAPProductsProvider(
|
||||||
|
child: FilmsProvider(
|
||||||
|
child: EquipmentProfileProvider(
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
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) {}
|
|
||||||
}
|
|
61
iap/lib/src/providers/equipment_profile_provider.dart
Normal file
61
iap/lib/src/providers/equipment_profile_provider.dart
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
65
iap/lib/src/providers/films_provider.dart
Normal file
65
iap/lib/src/providers/films_provider.dart
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,10 +17,7 @@ class SelectableInheritedModel<T> extends InheritedModel<SelectableAspect> {
|
||||||
bool updateShouldNotify(SelectableInheritedModel oldWidget) => true;
|
bool updateShouldNotify(SelectableInheritedModel oldWidget) => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotifyDependent(
|
bool updateShouldNotifyDependent(SelectableInheritedModel oldWidget, Set<SelectableAspect> dependencies) {
|
||||||
SelectableInheritedModel oldWidget,
|
|
||||||
Set<SelectableAspect> dependencies,
|
|
||||||
) {
|
|
||||||
if (dependencies.contains(SelectableAspect.list)) {
|
if (dependencies.contains(SelectableAspect.list)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (dependencies.contains(SelectableAspect.selected)) {
|
} else if (dependencies.contains(SelectableAspect.selected)) {
|
|
@ -27,11 +27,13 @@ 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/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/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart';
|
||||||
import 'package:lightmeter/screens/settings/screen_settings.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:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import 'mocks/paid_features_mock.dart';
|
class _MockSharedPreferences extends Mock implements SharedPreferences {}
|
||||||
|
|
||||||
class _MockUserPreferencesService extends Mock implements UserPreferencesService {}
|
class _MockUserPreferencesService extends Mock implements UserPreferencesService {}
|
||||||
|
|
||||||
|
@ -110,17 +112,28 @@ void main() {
|
||||||
|
|
||||||
Future<void> pumpApplication(WidgetTester tester) async {
|
Future<void> pumpApplication(WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MockIAPProviders.purchased(
|
IAPProviders(
|
||||||
selectedFilm: mockFilms.first,
|
sharedPreferences: _MockSharedPreferences(),
|
||||||
child: ServicesProvider(
|
child: EquipmentProfiles(
|
||||||
environment: const Environment.prod().copyWith(hasLightSensor: true),
|
selected: _mockEquipmentProfiles[0],
|
||||||
userPreferencesService: mockUserPreferencesService,
|
values: _mockEquipmentProfiles,
|
||||||
caffeineService: mockCaffeineService,
|
child: Films(
|
||||||
hapticsService: mockHapticsService,
|
selected: const Film('Ilford HP5+', 400),
|
||||||
permissionsService: mockPermissionsService,
|
values: const [Film.other(), Film('Ilford HP5+', 400)],
|
||||||
lightSensorService: mockLightSensorService,
|
filmsInUse: const [Film.other(), Film('Ilford HP5+', 400)],
|
||||||
volumeEventsService: mockVolumeEventsService,
|
child: ServicesProvider(
|
||||||
child: const UserPreferencesProvider(child: Application()),
|
environment: const Environment.prod().copyWith(hasLightSensor: true),
|
||||||
|
userPreferencesService: mockUserPreferencesService,
|
||||||
|
caffeineService: mockCaffeineService,
|
||||||
|
hapticsService: mockHapticsService,
|
||||||
|
permissionsService: mockPermissionsService,
|
||||||
|
lightSensorService: mockLightSensorService,
|
||||||
|
volumeEventsService: mockVolumeEventsService,
|
||||||
|
child: const UserPreferencesProvider(
|
||||||
|
child: Application(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,81 +1,65 @@
|
||||||
import 'package:flutter/material.dart';
|
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_iap/m3_lightmeter_iap.dart';
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
class _MockIAPStorageService extends Mock implements IAPStorageService {}
|
class _MockSharedPreferences extends Mock implements SharedPreferences {}
|
||||||
|
|
||||||
class MockIAPProviders extends StatefulWidget {
|
class MockIAPProviders extends StatelessWidget {
|
||||||
final IAPProductStatus purchaseStatus;
|
final IAPProductStatus purchaseStatus;
|
||||||
final String selectedEquipmentProfileId;
|
|
||||||
final Film selectedFilm;
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const MockIAPProviders({
|
const MockIAPProviders({
|
||||||
this.selectedEquipmentProfileId = '',
|
|
||||||
this.selectedFilm = const Film.other(),
|
|
||||||
required this.purchaseStatus,
|
required this.purchaseStatus,
|
||||||
required this.child,
|
required this.child,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
const MockIAPProviders.purchasable({
|
const MockIAPProviders.purchasable({
|
||||||
this.selectedEquipmentProfileId = '',
|
|
||||||
this.selectedFilm = const Film.other(),
|
|
||||||
required this.child,
|
required this.child,
|
||||||
super.key,
|
super.key,
|
||||||
}) : purchaseStatus = IAPProductStatus.purchasable;
|
}) : purchaseStatus = IAPProductStatus.purchasable;
|
||||||
|
|
||||||
const MockIAPProviders.purchased({
|
const MockIAPProviders.purchased({
|
||||||
this.selectedEquipmentProfileId = '',
|
|
||||||
this.selectedFilm = const Film.other(),
|
|
||||||
required this.child,
|
required this.child,
|
||||||
super.key,
|
super.key,
|
||||||
}) : purchaseStatus = IAPProductStatus.purchased;
|
}) : 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return IAPProductsProvider(
|
if (purchaseStatus == IAPProductStatus.purchased) {
|
||||||
child: IAPProducts(
|
return IAPProviders(
|
||||||
products: [
|
sharedPreferences: _MockSharedPreferences(),
|
||||||
IAPProduct(
|
child: EquipmentProfiles(
|
||||||
storeId: IAPProductType.paidFeatures.storeId,
|
selected: _mockEquipmentProfiles[0],
|
||||||
status: widget.purchaseStatus,
|
values: _mockEquipmentProfiles,
|
||||||
)
|
child: Films(
|
||||||
],
|
selected: const Film('Ilford HP5+', 400),
|
||||||
child: EquipmentProfileProvider(
|
values: const [Film.other(), Film('Ilford HP5+', 400)],
|
||||||
storageService: mockIAPStorageService,
|
filmsInUse: const [Film.other(), Film('Ilford HP5+', 400)],
|
||||||
child: FilmsProvider(
|
child: child,
|
||||||
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: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
apertureValues: ApertureValue.values,
|
apertureValues: ApertureValue.values,
|
||||||
|
@ -84,7 +68,8 @@ const defaultEquipmentProfile = EquipmentProfile(
|
||||||
isoValues: IsoValue.values,
|
isoValues: IsoValue.values,
|
||||||
);
|
);
|
||||||
|
|
||||||
final mockEquipmentProfiles = [
|
final _mockEquipmentProfiles = [
|
||||||
|
_defaultEquipmentProfile,
|
||||||
EquipmentProfile(
|
EquipmentProfile(
|
||||||
id: '1',
|
id: '1',
|
||||||
name: 'Praktica + Zenitar',
|
name: 'Praktica + Zenitar',
|
||||||
|
@ -118,5 +103,3 @@ final mockEquipmentProfiles = [
|
||||||
isoValues: IsoValue.values,
|
isoValues: IsoValue.values,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
const mockFilms = [Film('Ilford HP5+', 400)];
|
|
||||||
|
|
|
@ -6,8 +6,6 @@ import 'package:lightmeter/data/permissions_service.dart';
|
||||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||||
import 'package:lightmeter/data/volume_events_service.dart';
|
import 'package:lightmeter/data/volume_events_service.dart';
|
||||||
import 'package:lightmeter/environment.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/services_provider.dart';
|
||||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
|
@ -29,8 +27,8 @@ class ApplicationWrapper extends StatelessWidget {
|
||||||
]),
|
]),
|
||||||
builder: (_, snapshot) {
|
builder: (_, snapshot) {
|
||||||
if (snapshot.data != null) {
|
if (snapshot.data != null) {
|
||||||
final iapService = IAPStorageService(snapshot.data![0] as SharedPreferences);
|
return IAPProviders(
|
||||||
return IAPProductsProvider(
|
sharedPreferences: snapshot.data![0] as SharedPreferences,
|
||||||
child: ServicesProvider(
|
child: ServicesProvider(
|
||||||
caffeineService: const CaffeineService(),
|
caffeineService: const CaffeineService(),
|
||||||
environment: env.copyWith(hasLightSensor: snapshot.data![1] as bool),
|
environment: env.copyWith(hasLightSensor: snapshot.data![1] as bool),
|
||||||
|
@ -40,14 +38,8 @@ class ApplicationWrapper extends StatelessWidget {
|
||||||
userPreferencesService:
|
userPreferencesService:
|
||||||
UserPreferencesService(snapshot.data![0] as SharedPreferences),
|
UserPreferencesService(snapshot.data![0] as SharedPreferences),
|
||||||
volumeEventsService: const VolumeEventsService(LocalPlatform()),
|
volumeEventsService: const VolumeEventsService(LocalPlatform()),
|
||||||
child: EquipmentProfileProvider(
|
child: UserPreferencesProvider(
|
||||||
storageService: iapService,
|
child: child,
|
||||||
child: FilmsProvider(
|
|
||||||
storageService: iapService,
|
|
||||||
child: UserPreferencesProvider(
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/providers/films_provider.dart';
|
|
||||||
import 'package:lightmeter/res/dimens.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/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:lightmeter/screens/shared/icon_placeholder/widget_icon_placeholder.dart';
|
||||||
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
|
|
||||||
class ExposurePairsList extends StatelessWidget {
|
class ExposurePairsList extends StatelessWidget {
|
||||||
final List<ExposurePair> exposurePairs;
|
final List<ExposurePair> exposurePairs;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/generated/l10n.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/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: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:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class EquipmentProfilePicker extends StatelessWidget {
|
class EquipmentProfilePicker extends StatelessWidget {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||||
import 'package:lightmeter/generated/l10n.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: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 {
|
class ExtremeExposurePairsContainer extends StatelessWidget {
|
||||||
final ExposurePair? fastest;
|
final ExposurePair? fastest;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/generated/l10n.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/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: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:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class FilmPicker extends StatelessWidget {
|
class FilmPicker extends StatelessWidget {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||||
import 'package:lightmeter/data/models/metering_screen_layout_config.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/providers/user_preferences_provider.dart';
|
||||||
import 'package:lightmeter/res/dimens.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';
|
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/equipment_profile_picker/widget_picker_equipment_profiles.dart';
|
||||||
|
@ -9,6 +8,7 @@ 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/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/iso_picker/widget_picker_iso.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/nd_picker/widget_picker_nd.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';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class ReadingsContainer extends StatelessWidget {
|
class ReadingsContainer extends StatelessWidget {
|
||||||
|
|
|
@ -5,8 +5,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||||
import 'package:lightmeter/data/models/metering_screen_layout_config.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/services_provider.dart';
|
||||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||||
import 'package:lightmeter/screens/metering/bloc_metering.dart';
|
import 'package:lightmeter/screens/metering/bloc_metering.dart';
|
||||||
|
@ -17,6 +15,7 @@ import 'package:lightmeter/screens/metering/event_metering.dart';
|
||||||
import 'package:lightmeter/screens/metering/state_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/listener_metering_layout_feature.dart';
|
||||||
import 'package:lightmeter/screens/metering/utils/listsner_equipment_profiles.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';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class MeteringScreen extends StatelessWidget {
|
class MeteringScreen extends StatelessWidget {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class EquipmentProfileListener extends StatefulWidget {
|
class EquipmentProfileListener extends StatefulWidget {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
|
||||||
import 'package:lightmeter/res/dimens.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_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/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/icon_placeholder/widget_icon_placeholder.dart';
|
||||||
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.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';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class EquipmentProfilesScreen extends StatefulWidget {
|
class EquipmentProfilesScreen extends StatefulWidget {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/generated/l10n.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/dialog_filter/widget_dialog_filter.dart';
|
||||||
import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.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';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class FilmsListTile extends StatelessWidget {
|
class FilmsListTile extends StatelessWidget {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
||||||
import 'package:lightmeter/generated/l10n.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/providers/user_preferences_provider.dart';
|
||||||
import 'package:lightmeter/res/dimens.dart';
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
|
|
||||||
class MeteringScreenLayoutFeaturesDialog extends StatefulWidget {
|
class MeteringScreenLayoutFeaturesDialog extends StatefulWidget {
|
||||||
const MeteringScreenLayoutFeaturesDialog({super.key});
|
const MeteringScreenLayoutFeaturesDialog({super.key});
|
||||||
|
|
|
@ -26,7 +26,7 @@ dependencies:
|
||||||
m3_lightmeter_iap:
|
m3_lightmeter_iap:
|
||||||
git:
|
git:
|
||||||
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
||||||
ref: v0.5.0
|
ref: v0.4.0
|
||||||
m3_lightmeter_resources:
|
m3_lightmeter_resources:
|
||||||
git:
|
git:
|
||||||
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
||||||
|
|
|
@ -1,353 +0,0 @@
|
||||||
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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
];
|
|
|
@ -1,264 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||||
import 'package:lightmeter/generated/l10n.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: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 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
import '../../../../../application_mock.dart';
|
import '../../../../../application_mock.dart';
|
||||||
|
|
|
@ -1,37 +1,75 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/providers/films_provider.dart';
|
|
||||||
import 'package:lightmeter/res/dimens.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/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: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_iap/m3_lightmeter_iap.dart';
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
|
||||||
|
|
||||||
import '../../../../../application_mock.dart';
|
import '../../../../../application_mock.dart';
|
||||||
|
|
||||||
class _MockIAPStorageService extends Mock implements IAPStorageService {}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
late final _MockIAPStorageService mockIAPStorageService;
|
group('Film push/pull label', () {
|
||||||
|
testWidgets(
|
||||||
|
'Film.other()',
|
||||||
|
(tester) async {
|
||||||
|
await tester.pumpApplication(_films[0]);
|
||||||
|
_expectReadingValueContainerText(S.current.film);
|
||||||
|
_expectReadingValueContainerText(S.current.none);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
setUpAll(() {
|
testWidgets(
|
||||||
mockIAPStorageService = _MockIAPStorageService();
|
'Film with the same ISO',
|
||||||
when(() => mockIAPStorageService.filmsInUse).thenReturn(_films);
|
(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);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<void> pumpApplication(WidgetTester tester) async {
|
testWidgets(
|
||||||
await tester.pumpWidget(
|
'Film picker shows only films in use',
|
||||||
IAPProducts(
|
(tester) async {
|
||||||
products: [
|
await tester.pumpApplication(_films[1]);
|
||||||
IAPProduct(
|
await tester.tap(find.byType(FilmPicker));
|
||||||
storeId: IAPProductType.paidFeatures.storeId,
|
await tester.pumpAndSettle(Dimens.durationL);
|
||||||
status: IAPProductStatus.purchased,
|
_expectRadioListTile(S.current.none);
|
||||||
)
|
_expectRadioListTile(_films[1].name);
|
||||||
],
|
_expectRadioListTile(_films[2].name);
|
||||||
child: FilmsProvider(
|
_expectRadioListTile(_films[3].name);
|
||||||
storageService: mockIAPStorageService,
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
child: const WidgetTestApplicationMock(
|
child: const WidgetTestApplicationMock(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
@ -44,67 +82,12 @@ void main() {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await tester.pumpAndSettle();
|
await 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 = [
|
const _films = [
|
||||||
|
Film.other(),
|
||||||
Film('ISO 100 Film', 100),
|
Film('ISO 100 Film', 100),
|
||||||
Film('ISO 400 Film', 400),
|
Film('ISO 400 Film', 400),
|
||||||
Film('ISO 800 Film', 800),
|
Film('ISO 800 Film', 800),
|
||||||
|
|
Loading…
Reference in a new issue