Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ML-191 Add an ability to add a generic film, that will accept a formula #195

Merged
merged 38 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4498167
sync with resources
vodemn Oct 6, 2024
12abf99
separated `ExpandableSectionList` as widget
vodemn Oct 6, 2024
c05d4e8
fixed generic type
vodemn Oct 7, 2024
dbdc057
implemented `FilmsScreen` (wip)
vodemn Oct 7, 2024
cc2ab3b
made `SliverScreen` title a widget
vodemn Oct 21, 2024
b7f0305
[`FilmEditScreen`] wip
vodemn Oct 21, 2024
1eebc70
[`FilmEditScreen`] added validation
vodemn Oct 22, 2024
e781bb3
fixed title overflow for `SliverScreen`
vodemn Oct 23, 2024
a6f319b
[`FilmEditScreen`] separated add and edit blocs
vodemn Oct 23, 2024
756c179
[`FilmEditScreen`] split into separate components
vodemn Oct 23, 2024
3870dfe
added bottom widget to `SliverScreen`
vodemn Oct 24, 2024
ba5bbb7
implemented films list tabs fo `FilmsScreen`
vodemn Oct 24, 2024
0243598
added films screen to navigation
vodemn Oct 24, 2024
9884a61
replaced explicit routes names with enum values
vodemn Oct 24, 2024
68cecf5
implemented CRUD for custom films
vodemn Oct 25, 2024
45d7728
added placeholder for empty custom films list
vodemn Oct 26, 2024
09d04c6
added `FilmsStorageService`
vodemn Oct 28, 2024
7db43fb
fixed unit tests
vodemn Oct 29, 2024
274cfc3
fixed integration tests
vodemn Oct 29, 2024
b4fffb8
lint
vodemn Oct 29, 2024
7b9f03f
fixed golden tests
vodemn Oct 29, 2024
815f180
added iap stub methods
vodemn Oct 29, 2024
2dde8ce
added custom films to features list
vodemn Oct 30, 2024
f51b076
use 2.0.0 resouces
vodemn Oct 30, 2024
f5f237a
Merge branch 'main' into feature/ML-191
vodemn Oct 30, 2024
7979bbd
fixed film picket tests
vodemn Oct 30, 2024
beeb678
migrated to iap 1.0.1
vodemn Oct 31, 2024
ca03de9
autofocus film name field
vodemn Oct 31, 2024
02d81ca
wait for the film to edited
vodemn Oct 31, 2024
dff3eef
migrated to iap 1.1.0
vodemn Oct 31, 2024
bddeede
typo
vodemn Nov 1, 2024
8bc3563
wait for storage initialization
vodemn Nov 1, 2024
f22086e
migrated to iap 1.1.1
vodemn Nov 3, 2024
e29920b
fixed films initialization
vodemn Nov 3, 2024
e7512e4
added conditions to films model `updateShouldNotifyDependent`
vodemn Nov 3, 2024
ae2216a
typo
vodemn Nov 3, 2024
d1b9639
fixed select film discard notify
vodemn Nov 3, 2024
2accf9f
covered films model `updateShouldNotifyDependent`
vodemn Nov 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions iap/lib/m3_lightmeter_iap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
export 'src/data/models/iap_product.dart';
export 'src/providers/iap_products_provider.dart';
export 'src/data/iap_storage_service.dart';
export 'src/data/films_storage_service.dart';

const List<Film> films = [];
32 changes: 32 additions & 0 deletions iap/lib/src/data/films_storage_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:flutter/foundation.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';

typedef SelectableFilm<T extends Film> = ({T film, bool isUsed});

class FilmsStorageService {
FilmsStorageService();

Future<void> init() async {}

@visibleForTesting
Future<void> createTable(dynamic _) async {}

String get selectedFilmId => '';
set selectedFilmId(String id) {}

Future<void> addFilm(FilmExponential _, {bool isUsed = true}) async {}

Future<void> updateFilm(FilmExponential _) async {}

Future<void> toggleFilm(Film _, bool __) async {}

Future<void> deleteFilm(FilmExponential _) async {}

Future<Map<String, SelectableFilm<Film>>> getPredefinedFilms() async {
return const {};
}

Future<Map<String, SelectableFilm<FilmExponential>>> getCustomFilms() async {
return const {};
}
}
6 changes: 0 additions & 6 deletions iap/lib/src/data/iap_storage_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,4 @@ class IAPStorageService {

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) {}
}
7 changes: 3 additions & 4 deletions iap/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
name: m3_lightmeter_iap
description: IAP stubs for the M3 Lightmeter app.
version: 0.2.0
version: 1.0.0
publish_to: 'none'

environment:
sdk: '>=2.19.2 <3.0.0'
flutter: ">=1.17.0"
sdk: ">=3.0.0 <4.0.0"

dependencies:
flutter:
sdk: flutter
m3_lightmeter_resources:
git:
url: "https://github.com/vodemn/m3_lightmeter_resources"
ref: v1.4.0
ref: v2.0.0
shared_preferences: 2.2.0

dev_dependencies:
Expand Down
11 changes: 5 additions & 6 deletions integration_test/e2e_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ void testE2E(String description) {
testWidgets(
description,
(tester) async {
await tester.pumpApplication(equipmentProfiles: [], filmsInUse: []);
await tester.pumpApplication(
equipmentProfiles: [],
predefinedFilms: mockFilms.toFilmsMap(isUsed: true),
customFilms: {},
);

/// Create Praktica + Zenitar profile from scratch
await tester.openSettings();
Expand Down Expand Up @@ -76,11 +80,6 @@ void testE2E(String description) {
expect(find.text('f/3.5 - f/22'), findsOneWidget);
expect(find.text('1/1000 - B'), findsNWidgets(2));
await tester.navigatorPop();

/// Select some films
await tester.tap(find.text(S.current.filmsInUse));
await tester.pumpAndSettle();
await tester.setDialogFilterValues<Film>([mockFilms[0], mockFilms[1]], deselectAll: false);
await tester.navigatorPop();

/// Select some initial settings according to the selected gear and film
Expand Down
2 changes: 1 addition & 1 deletion integration_test/metering_screen_layout_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ void testToggleLayoutFeatures(String description) {
testWidgets(
'Film picker',
(tester) async {
await tester.pumpApplication(selectedFilm: mockFilms.first);
await tester.pumpApplication(selectedFilmId: mockFilms.first.id);
await tester.takePhoto();
expectPickerTitle<FilmPicker>(mockFilms.first.name);
expectExtremeExposurePairs('f/1.0 - 1/320', 'f/45 - 12"');
Expand Down
80 changes: 63 additions & 17 deletions integration_test/mocks/paid_features_mock.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,56 @@ import 'package:mocktail/mocktail.dart';

class _MockIAPStorageService extends Mock implements IAPStorageService {}

class _MockFilmsStorageService extends Mock implements FilmsStorageService {}

class MockIAPProviders extends StatefulWidget {
final List<EquipmentProfile>? equipmentProfiles;
final String selectedEquipmentProfileId;
final List<Film> availableFilms;
final List<Film> filmsInUse;
final Film selectedFilm;
final Map<String, SelectableFilm<Film>> predefinedFilms;
final Map<String, SelectableFilm<FilmExponential>> customFilms;
final String selectedFilmId;
final Widget child;

const MockIAPProviders({
MockIAPProviders({
this.equipmentProfiles = const [],
this.selectedEquipmentProfileId = '',
List<Film>? availableFilms,
List<Film>? filmsInUse,
this.selectedFilm = const Film.other(),
Map<String, SelectableFilm<Film>>? predefinedFilms,
Map<String, SelectableFilm<FilmExponential>>? customFilms,
String? selectedFilmId,
required this.child,
super.key,
}) : availableFilms = availableFilms ?? mockFilms,
filmsInUse = filmsInUse ?? mockFilms;
}) : predefinedFilms = predefinedFilms ?? mockFilms.toFilmsMap(),
customFilms = customFilms ?? mockFilms.toFilmsMap(),
selectedFilmId = selectedFilmId ?? const FilmStub().id;

@override
State<MockIAPProviders> createState() => _MockIAPProvidersState();
}

class _MockIAPProvidersState extends State<MockIAPProviders> {
late final _MockIAPStorageService mockIAPStorageService;
late final _MockFilmsStorageService mockFilmsStorageService;

@override
void initState() {
super.initState();
mockIAPStorageService = _MockIAPStorageService();
when(() => mockIAPStorageService.equipmentProfiles).thenReturn(widget.equipmentProfiles ?? mockEquipmentProfiles);
when(() => mockIAPStorageService.selectedEquipmentProfileId).thenReturn(widget.selectedEquipmentProfileId);
when(() => mockIAPStorageService.filmsInUse).thenReturn(widget.filmsInUse);
when(() => mockIAPStorageService.selectedFilm).thenReturn(widget.selectedFilm);

mockFilmsStorageService = _MockFilmsStorageService();
when(() => mockFilmsStorageService.init()).thenAnswer((_) async {});
when(() => mockFilmsStorageService.getPredefinedFilms()).thenAnswer((_) => Future.value(widget.predefinedFilms));
when(() => mockFilmsStorageService.getCustomFilms()).thenAnswer((_) => Future.value(widget.customFilms));
when(() => mockFilmsStorageService.selectedFilmId).thenReturn(widget.selectedFilmId);
}

@override
Widget build(BuildContext context) {
return EquipmentProfileProvider(
storageService: mockIAPStorageService,
child: FilmsProvider(
storageService: mockIAPStorageService,
availableFilms: widget.availableFilms,
filmsStorageService: mockFilmsStorageService,
child: widget.child,
),
);
Expand Down Expand Up @@ -128,13 +135,52 @@ final mockEquipmentProfiles = [
),
];

const mockFilms = [_MockFilm(100, 2), _MockFilm(400, 2), _MockFilm(3, 800), _MockFilm(400, 1.5)];
const mockFilms = [
_FilmMultiplying(id: '1', name: 'Mock film 1', iso: 100, reciprocityMultiplier: 2),
_FilmMultiplying(id: '2', name: 'Mock film 2', iso: 400, reciprocityMultiplier: 2),
_FilmMultiplying(id: '3', name: 'Mock film 3', iso: 800, reciprocityMultiplier: 3),
_FilmMultiplying(id: '4', name: 'Mock film 4', iso: 1200, reciprocityMultiplier: 1.5),
];

extension FilmMapper on List<Film> {
Map<String, ({T film, bool isUsed})> toFilmsMap<T extends Film>({bool isUsed = true}) =>
Map.fromEntries(map((e) => MapEntry(e.id, (film: e as T, isUsed: isUsed))));
}

class _MockFilm extends Film {
class _FilmMultiplying extends FilmExponential {
final double reciprocityMultiplier;

const _MockFilm(int iso, this.reciprocityMultiplier) : super('Mock film $iso x$reciprocityMultiplier', iso);
const _FilmMultiplying({
String? id,
required String name,
required super.iso,
required this.reciprocityMultiplier,
}) : super(id: id ?? name, name: 'Mock film $iso x$reciprocityMultiplier', exponent: 1);

@override
ShutterSpeedValue reciprocityFailure(ShutterSpeedValue shutterSpeed) {
if (shutterSpeed.isFraction) {
return shutterSpeed;
} else {
return ShutterSpeedValue(
shutterSpeed.rawValue * reciprocityMultiplier,
shutterSpeed.isFraction,
shutterSpeed.stopType,
);
}
}

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other.runtimeType != runtimeType) return false;
return other is _FilmMultiplying &&
other.id == id &&
other.name == name &&
other.iso == iso &&
other.reciprocityMultiplier == reciprocityMultiplier;
}

@override
double reciprocityFormula(double t) => t * reciprocityMultiplier;
int get hashCode => Object.hash(id, name, iso, reciprocityMultiplier, runtimeType);
}
12 changes: 6 additions & 6 deletions integration_test/utils/widget_tester_actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ extension WidgetTesterCommonActions on WidgetTester {
IAPProductStatus productStatus = IAPProductStatus.purchased,
List<EquipmentProfile>? equipmentProfiles,
String selectedEquipmentProfileId = '',
List<Film>? availableFilms,
List<Film>? filmsInUse,
Film selectedFilm = const Film.other(),
Map<String, SelectableFilm<Film>>? predefinedFilms,
Map<String, SelectableFilm<FilmExponential>>? customFilms,
String selectedFilmId = '',
}) async {
await pumpWidget(
MockIAPProductsProvider(
Expand All @@ -34,9 +34,9 @@ extension WidgetTesterCommonActions on WidgetTester {
child: MockIAPProviders(
equipmentProfiles: equipmentProfiles,
selectedEquipmentProfileId: selectedEquipmentProfileId,
availableFilms: availableFilms,
filmsInUse: filmsInUse,
selectedFilm: selectedFilm,
predefinedFilms: predefinedFilms,
customFilms: customFilms,
selectedFilmId: selectedFilmId,
child: const Application(),
),
),
Expand Down
17 changes: 12 additions & 5 deletions lib/application.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:lightmeter/data/models/supported_locale.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/navigation/modal_route_args_parser.dart';
import 'package:lightmeter/navigation/routes.dart';
import 'package:lightmeter/platform_config.dart';
import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/screens/film_edit/flow_film_edit.dart';
import 'package:lightmeter/screens/films/screen_films.dart';
import 'package:lightmeter/screens/lightmeter_pro/screen_lightmeter_pro.dart';
import 'package:lightmeter/screens/metering/flow_metering.dart';
import 'package:lightmeter/screens/settings/flow_settings.dart';
Expand Down Expand Up @@ -41,12 +45,15 @@ class Application extends StatelessWidget {
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: child!,
),
initialRoute: "metering",
initialRoute: NavigationRoutes.meteringScreen.name,
routes: {
"metering": (_) => const ReleaseNotesFlow(child: MeteringFlow()),
"settings": (_) => const SettingsFlow(),
"lightmeterPro": (_) => LightmeterProScreen(),
"timer": (context) => TimerFlow(args: ModalRoute.of(context)!.settings.arguments! as TimerFlowArgs),
NavigationRoutes.meteringScreen.name: (_) => const ReleaseNotesFlow(child: MeteringFlow()),
NavigationRoutes.settingsScreen.name: (_) => const SettingsFlow(),
NavigationRoutes.filmsListScreen.name: (_) => const FilmsScreen(),
NavigationRoutes.filmAddScreen.name: (_) => const FilmEditFlow(args: FilmEditArgs()),
NavigationRoutes.filmEditScreen.name: (context) => FilmEditFlow(args: context.routeArgs<FilmEditArgs>()),
NavigationRoutes.proFeaturesScreen.name: (_) => LightmeterProScreen(),
NavigationRoutes.timerScreen.name: (context) => TimerFlow(args: context.routeArgs<TimerFlowArgs>()),
},
),
);
Expand Down
Loading
Loading