mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2025-08-01 10:46:44 +00:00

* typos * added `LogbookPhotosProvider` * implemented `LogbookScreen` * implemented `LogbookPhotoEditScreen` * added photo update * save geolocation * added `CameraSettingsSection` * adjusted logbook grid * added hero animation * fixed logbook list updates * added empty logbook state * added `saveLogbookPhotos` option * fixed updating photos * made `DialogPicker` content scrollable * added tests for `LogbookPhotosProvider` * made image preview full-width * made note field multiline * wip * migrated to new iap service * fixed unit tests * typo * fixed arb formatting * stub logbook photos for tests * implemented integration test for logbook * moved date to title * redundant bottom padding * added logbook photo screen to screenshots generator * Update settings.gradle * aligned iap stub with iap release * sync * made logbook iap * debug screenshots * Update runner.dart * fixed dialog picker of optional values * added bottom padding to logbook edit screen * fixed tests * Create camera_stub_image.jpg * Update films_provider_test.dart * rename * Update pubspec.yaml * added logbook to pro features
261 lines
7.6 KiB
Dart
261 lines
7.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:lightmeter/data/geolocation_service.dart';
|
|
import 'package:lightmeter/providers/logbook_photos_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 _MockLogbookPhotosStorageService extends Mock implements IapStorageService {}
|
|
|
|
class _MockGeolocationService extends Mock implements GeolocationService {}
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
late _MockLogbookPhotosStorageService storageService;
|
|
late _MockGeolocationService geolocationService;
|
|
|
|
setUpAll(() {
|
|
storageService = _MockLogbookPhotosStorageService();
|
|
geolocationService = _MockGeolocationService();
|
|
registerFallbackValue(ApertureValue.values.first);
|
|
registerFallbackValue(_customPhotos.first);
|
|
registerFallbackValue(ShutterSpeedValue.values.first);
|
|
});
|
|
|
|
setUp(() {
|
|
when(() => storageService.addPhoto(any<LogbookPhoto>())).thenAnswer((_) async {});
|
|
when(
|
|
() => storageService.updatePhoto(
|
|
id: any<String>(named: 'id'),
|
|
note: any<String>(named: 'note'),
|
|
apertureValue: any<Optional<ApertureValue>?>(named: 'apertureValue'),
|
|
shutterSpeedValue: any<Optional<ShutterSpeedValue>?>(named: 'shutterSpeedValue'),
|
|
),
|
|
).thenAnswer((_) async {});
|
|
when(() => storageService.deletePhoto(any<String>())).thenAnswer((_) async {});
|
|
when(() => storageService.getPhotos()).thenAnswer((_) => Future.value(_customPhotos));
|
|
|
|
when(() => geolocationService.getCurrentPosition()).thenAnswer((_) => Future.value());
|
|
});
|
|
|
|
tearDown(() {
|
|
reset(storageService);
|
|
});
|
|
|
|
Future<void> pumpTestWidget(WidgetTester tester, IAPProductStatus productStatus) async {
|
|
await tester.pumpWidget(
|
|
IAPProducts(
|
|
products: [
|
|
IAPProduct(
|
|
storeId: IAPProductType.paidFeatures.storeId,
|
|
status: productStatus,
|
|
price: '0.0\$',
|
|
),
|
|
],
|
|
child: LogbookPhotosProvider(
|
|
storageService: storageService,
|
|
geolocationService: geolocationService,
|
|
child: const _Application(),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
void expectLogbookPhotosCount(int count) {
|
|
expect(find.text(_LogbookPhotosCount.text(count)), findsOneWidget);
|
|
}
|
|
|
|
void expectLogbookPhotosEnabled(bool enabled) {
|
|
expect(find.text(_LogbookPhotosEnabled.text(enabled)), findsOneWidget);
|
|
}
|
|
|
|
group(
|
|
'LogbookPhotosProvider dependency on IAPProductStatus',
|
|
() {
|
|
setUp(() {
|
|
when(() => storageService.getPhotos()).thenAnswer((_) => Future.value(_customPhotos));
|
|
});
|
|
|
|
testWidgets(
|
|
'IAPProductStatus.purchased - show all saved photos',
|
|
(tester) async {
|
|
await pumpTestWidget(tester, IAPProductStatus.purchased);
|
|
expectLogbookPhotosCount(_customPhotos.length);
|
|
expectLogbookPhotosEnabled(true);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'IAPProductStatus.purchasable - show empty list',
|
|
(tester) async {
|
|
await pumpTestWidget(tester, IAPProductStatus.purchasable);
|
|
expectLogbookPhotosCount(0);
|
|
expectLogbookPhotosEnabled(true);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'IAPProductStatus.pending - show empty list',
|
|
(tester) async {
|
|
await pumpTestWidget(tester, IAPProductStatus.pending);
|
|
expectLogbookPhotosCount(0);
|
|
expectLogbookPhotosEnabled(true);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'saveLogbookPhotos',
|
|
(tester) async {
|
|
when(() => storageService.getPhotos()).thenAnswer((_) => Future.value(_customPhotos));
|
|
await pumpTestWidget(tester, IAPProductStatus.purchased);
|
|
expectLogbookPhotosCount(_customPhotos.length);
|
|
expectLogbookPhotosEnabled(true);
|
|
|
|
tester.logbookPhotosProvider.saveLogbookPhotos(false);
|
|
await tester.pump();
|
|
expectLogbookPhotosCount(_customPhotos.length);
|
|
expectLogbookPhotosEnabled(false);
|
|
|
|
tester.logbookPhotosProvider.saveLogbookPhotos(true);
|
|
await tester.pump();
|
|
expectLogbookPhotosCount(_customPhotos.length);
|
|
expectLogbookPhotosEnabled(true);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'LogbookPhotosProvider CRUD',
|
|
(tester) async {
|
|
when(() => storageService.getPhotos()).thenAnswer((_) async => []);
|
|
|
|
await pumpTestWidget(tester, IAPProductStatus.purchased);
|
|
expectLogbookPhotosCount(0);
|
|
expectLogbookPhotosEnabled(true);
|
|
|
|
/// Create a photo
|
|
final photo = await tester.logbookPhotosProvider.addPhotoIfPossible(
|
|
_customPhotos.first.name,
|
|
ev100: _customPhotos.first.ev,
|
|
iso: _customPhotos.first.iso,
|
|
nd: _customPhotos.first.nd,
|
|
);
|
|
|
|
/// Update a photo
|
|
final updatedPhoto = photo!.copyWith(note: 'Updated note');
|
|
await tester.logbookPhotosProvider.updateLogbookPhoto(updatedPhoto);
|
|
await tester.pump();
|
|
expectLogbookPhotosCount(1); // No photos loaded initially
|
|
verify(
|
|
() => storageService.updatePhoto(
|
|
id: updatedPhoto.id,
|
|
note: 'Updated note',
|
|
),
|
|
).called(1);
|
|
|
|
/// Delete a photo
|
|
await tester.logbookPhotosProvider.deleteLogbookPhoto(updatedPhoto);
|
|
await tester.pump();
|
|
expectLogbookPhotosCount(0);
|
|
verify(() => storageService.deletePhoto(updatedPhoto.id)).called(1);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'addPhotoIfPossible when disabled',
|
|
(tester) async {
|
|
when(() => storageService.getPhotos()).thenAnswer((_) async => []);
|
|
await pumpTestWidget(tester, IAPProductStatus.purchased);
|
|
|
|
// Disable logbook photos
|
|
tester.logbookPhotosProvider.saveLogbookPhotos(false);
|
|
await tester.pump();
|
|
|
|
// Try to add photo when disabled
|
|
await tester.logbookPhotosProvider.addPhotoIfPossible(
|
|
'assets/camera_stub_image.jpg',
|
|
ev100: 12.0,
|
|
iso: 100,
|
|
nd: 0,
|
|
);
|
|
await tester.pump();
|
|
|
|
// Should not add photo when disabled
|
|
expectLogbookPhotosCount(0);
|
|
verifyNever(() => storageService.addPhoto(any<LogbookPhoto>()));
|
|
},
|
|
);
|
|
}
|
|
|
|
extension on WidgetTester {
|
|
LogbookPhotosProviderState get logbookPhotosProvider {
|
|
final BuildContext context = element(find.byType(_Application));
|
|
return LogbookPhotosProvider.of(context);
|
|
}
|
|
}
|
|
|
|
class _Application extends StatelessWidget {
|
|
const _Application();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: Column(
|
|
children: [
|
|
_LogbookPhotosCount(),
|
|
_LogbookPhotosEnabled(),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _LogbookPhotosCount extends StatelessWidget {
|
|
static String text(int count) => "Photos count: $count";
|
|
|
|
const _LogbookPhotosCount();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Text(text(LogbookPhotos.of(context).length));
|
|
}
|
|
}
|
|
|
|
class _LogbookPhotosEnabled extends StatelessWidget {
|
|
static String text(bool enabled) => "Photos enabled: $enabled";
|
|
|
|
const _LogbookPhotosEnabled();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Text(text(LogbookPhotos.isEnabledOf(context)));
|
|
}
|
|
}
|
|
|
|
final List<LogbookPhoto> _customPhotos = [
|
|
LogbookPhoto(
|
|
id: '1',
|
|
name: 'assets/camera_stub_image.jpg',
|
|
timestamp: DateTime(2024, 1, 1, 12),
|
|
ev: 12.0,
|
|
iso: 100,
|
|
nd: 0,
|
|
note: 'Test photo 1',
|
|
),
|
|
LogbookPhoto(
|
|
id: '2',
|
|
name: 'assets/camera_stub_image.jpg',
|
|
timestamp: DateTime(2024, 1, 2, 12),
|
|
ev: 13.0,
|
|
iso: 200,
|
|
nd: 1,
|
|
note: 'Test photo 2',
|
|
),
|
|
];
|