ML-107 Films filter (#118)

* added stub `FilmsProvider`

* moved dialogs to the shared folder

* typo

* separated `EquipmentSettingsSection`

* copy

* `IAPBuilder` -> `IAPListTile`

* moved `Film` to resources repo

* fixed films selection

* untied iso and selected film

* removed film from exposure pairs building

* indicate push/pull

* copy

* Update .gitignore

* fixed extreme exposure pairs reciprocity display

* sync with iap changes

* sync iap stub with iap changes

* added reciprocity description

* added workspace file

* Update .gitignore
This commit is contained in:
Vadim 2023-09-14 16:59:16 +02:00 committed by GitHub
parent 1be7c3be48
commit cc9f162933
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 592 additions and 978 deletions

View file

@ -15,7 +15,7 @@ jobs:
analyze_and_test: analyze_and_test:
name: Analyze & test name: Analyze & test
runs-on: macos-11 runs-on: macos-11
timeout-minutes: 10 timeout-minutes: 5
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
@ -47,3 +47,10 @@ jobs:
- name: Run tests - name: Run tests
run: flutter test run: flutter test
- name: Analyze project source with stub
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
run: |
bash ./.github/scripts/stub_iap.sh
flutter pub get
flutter analyze lib --fatal-infos

View file

@ -33,11 +33,7 @@ Without further delay behold my new Lightmeter app inspired by Material You (a.k
To build this app you need to install Flutter 3.10.0 stable. [How to install](https://docs.flutter.dev/get-started/install). To build this app you need to install Flutter 3.10.0 stable. [How to install](https://docs.flutter.dev/get-started/install).
### 2. (Optional) Install Firebase ### 3. Project setup
Out of the box Firebase Crashlytics won't work. If you want to add Crashlytics to your local build please follow [this guide](https://firebase.google.com/docs/flutter/setup).
### 3. Get packages
As part of the app's functionallity is in the private repo, you have to replace these lines in _pubspec.yaml_: As part of the app's functionallity is in the private repo, you have to replace these lines in _pubspec.yaml_:
@ -47,24 +43,39 @@ m3_lightmeter_iap:
url: "https://github.com/vodemn/m3_lightmeter_iap" url: "https://github.com/vodemn/m3_lightmeter_iap"
ref: main ref: main
``` ```
with these: with these:
```yaml ```yaml
m3_lightmeter_iap: m3_lightmeter_iap:
path: iap path: iap
``` ```
and run `flutter pub get` from the _iap/_ folder.
You can do it simply by running the script:
```console
sh .github/scripts/stub_iap.sh
```
> If you are using VSCode, you can open the workspace like so: _File -> Open Workspace from File -> m3_lightmeter.code-workspace_. Otherwise you have to run `flutter pub get` command from the iap folder.
Then you can fetch all the neccessary dependencies and generate translation files by running the following commands: Then you can fetch all the neccessary dependencies and generate translation files by running the following commands:
```console ```console
flutter pub get flutter pub get
flutter pub run intl_utils:generate flutter pub run intl_utils:generate
``` ```
### 4. Build ### 4. (Optional) Install Firebase
Out of the box Firebase Crashlytics won't work. If you want to add Crashlytics to your local build please follow [this guide](https://firebase.google.com/docs/flutter/setup).
### 5. Build
#### Android #### Android
You can build an apk by running the following command from the root of the repository: You can build an apk by running the following command from the root of the repository:
```console ```console
flutter build apk --release --flavor dev --dart-define cameraPreviewAspectRatio=240/320 -t lib/main_dev.dart flutter build apk --release --flavor dev --dart-define cameraPreviewAspectRatio=240/320 -t lib/main_dev.dart
``` ```

View file

@ -2,11 +2,13 @@ library m3_lightmeter_iap;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:m3_lightmeter_iap/src/providers/equipment_profile_provider.dart'; import 'package:m3_lightmeter_iap/src/providers/equipment_profile_provider.dart';
import 'package:m3_lightmeter_iap/src/providers/films_provider.dart';
import 'package:m3_lightmeter_iap/src/providers/iap_products_provider.dart'; import 'package:m3_lightmeter_iap/src/providers/iap_products_provider.dart';
export 'src/data/models/iap_product.dart'; export 'src/data/models/iap_product.dart';
export 'src/providers/equipment_profile_provider.dart' hide EquipmentProfilesAspect; export 'src/providers/equipment_profile_provider.dart';
export 'src/providers/films_provider.dart';
export 'src/providers/iap_products_provider.dart'; export 'src/providers/iap_products_provider.dart';
class IAPProviders extends StatelessWidget { class IAPProviders extends StatelessWidget {
@ -22,9 +24,11 @@ class IAPProviders extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return IAPProductsProvider( return IAPProductsProvider(
child: FilmsProvider(
child: EquipmentProfileProvider( child: EquipmentProfileProvider(
child: child, child: child,
), ),
),
); );
} }
} }

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:m3_lightmeter_iap/src/providers/selectable_provider.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class EquipmentProfileProvider extends StatefulWidget { class EquipmentProfileProvider extends StatefulWidget {
@ -27,7 +28,7 @@ class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return EquipmentProfiles( return EquipmentProfiles(
profiles: const [_defaultProfile], values: const [_defaultProfile],
selected: _defaultProfile, selected: _defaultProfile,
child: widget.child, child: widget.child,
); );
@ -42,38 +43,19 @@ class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
void deleteProfile(EquipmentProfile data) {} void deleteProfile(EquipmentProfile data) {}
} }
enum EquipmentProfilesAspect { list, selected } class EquipmentProfiles extends SelectableInheritedModel<EquipmentProfile> {
class EquipmentProfiles extends InheritedModel<EquipmentProfilesAspect> {
const EquipmentProfiles({ const EquipmentProfiles({
super.key, super.key,
required this.profiles, required super.values,
required this.selected, required super.selected,
required super.child, required super.child,
}); });
final List<EquipmentProfile> profiles;
final EquipmentProfile selected;
static List<EquipmentProfile> of(BuildContext context) { static List<EquipmentProfile> of(BuildContext context) {
return InheritedModel.inheritFrom<EquipmentProfiles>( return InheritedModel.inheritFrom<EquipmentProfiles>(context, aspect: SelectableAspect.list)!.values;
context,
aspect: EquipmentProfilesAspect.list,
)!
.profiles;
} }
static EquipmentProfile selectedOf(BuildContext context) { static EquipmentProfile selectedOf(BuildContext context) {
return InheritedModel.inheritFrom<EquipmentProfiles>( return InheritedModel.inheritFrom<EquipmentProfiles>(context, aspect: SelectableAspect.selected)!.selected;
context,
aspect: EquipmentProfilesAspect.selected,
)!
.selected;
} }
@override
bool updateShouldNotify(EquipmentProfiles oldWidget) => false;
@override
bool updateShouldNotifyDependent(EquipmentProfiles oldWidget, Set<EquipmentProfilesAspect> dependencies) => false;
} }

View 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;
}
}

View file

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
enum SelectableAspect { list, selected }
class SelectableInheritedModel<T> extends InheritedModel<SelectableAspect> {
const SelectableInheritedModel({
super.key,
required this.values,
required this.selected,
required super.child,
});
final List<T> values;
final T selected;
@override
bool updateShouldNotify(SelectableInheritedModel oldWidget) => true;
@override
bool updateShouldNotifyDependent(SelectableInheritedModel oldWidget, Set<SelectableAspect> dependencies) {
if (dependencies.contains(SelectableAspect.list)) {
return true;
} else if (dependencies.contains(SelectableAspect.selected)) {
return selected != oldWidget.selected;
} else {
return false;
}
}
}

View file

@ -1,235 +0,0 @@
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
double log10(double x) => log(x) / log(10);
double log10polynomian(
double x,
double a,
double b,
double c,
) =>
a * pow(log10(x), 2) + b * log10(x) + c;
typedef ReciprocityFailureBuilder = ShutterSpeedValue Function(ShutterSpeedValue shutterSpeed);
/// Only Ilford films have reciprocity formulas provided by the manufacturer:
/// https://www.ilfordphoto.com/wp/wp-content/uploads/2017/06/Reciprocity-Failure-Compensation.pdf
///
/// Reciprocity formulas for Fomapan films and Kodak films are from here:
/// https://www.flickr.com/groups/86738082@N00/discuss/72157626050157470/
///
/// Cinema films like Kodak 5222/7222 Double-X and respective CineStill films (cause they are basically a modification of Kodak)
/// do not have any reciprocity failure information, as these films are ment to be used in cinema
/// with appropriate light and pretty short shutter speeds.
///
/// Because of this: https://github.com/dart-lang/sdk/issues/38934#issuecomment-803938315
/// `super` calls are ignored in test coverage
class Film {
final String name;
final int iso;
const Film(this.name, this.iso);
const Film.other()
: name = '',
iso = 0;
@override
String toString() => name;
ShutterSpeedValue reciprocityFailure(ShutterSpeedValue shutterSpeed) {
if (shutterSpeed.isFraction) {
return shutterSpeed;
} else {
return ShutterSpeedValue(
reciprocityFormula(shutterSpeed.rawValue),
shutterSpeed.isFraction,
shutterSpeed.stopType,
);
}
}
@protected
double reciprocityFormula(double t) => t;
static const List<Film> values = [
Film.other(),
FomapanFilm.creative100(),
FomapanFilm.creative200(),
FomapanFilm.action400(),
IlfordFilm.ortho(),
IlfordFilm.delta100(),
IlfordFilm.delta400(),
IlfordFilm.delta3200(),
IlfordFilm.fp4(),
IlfordFilm.hp5(),
IlfordFilm.panf(),
IlfordFilm.sfx200(),
IlfordFilm.xp2super(),
IlfordFilm.pan100(),
IlfordFilm.pan400(),
KodakFilm.tmax100(),
KodakFilm.tmax400(),
KodakFilm.tmax3200(),
KodakFilm.trix320(),
KodakFilm.trix400(),
];
}
/// https://www.tate.org.uk/documents/598/page_6_7_agfa_stocks_0.pdf
/// https://www.filmwasters.com/forum/index.php?topic=5298.0
// {{1,1.87},{2,3.73},{3,8.06},{4,13.93},{5,21.28},{6,23.00},{7,30.12},{8,38.05},{9,44.75},{10,50.12},{20,117},{30,202},{40,293},{50,413},{60,547},{70,694},{80,853},{90,1022},{100,1202}};
// class AgfaFilm extends Film {
// final double a;
// final double b;
// final double c;
// const AgfaFilm.apx100()
// : a = 1,
// b = 5,
// c = 2,
// super('Agfa APX 100', 100); // coverage:ignore-line
// const AgfaFilm.apx400()
// : a = 1.5,
// b = 4.5,
// c = 3,
// super('Agfa APX 400', 400); // coverage:ignore-line
// @override
// double reciprocityFormula(double t) => t * log10polynomian(t, a, b, c);
// }
class FomapanFilm extends Film {
final double a;
final double b;
final double c;
/// https://www.foma.cz/en/fomapan-100
const FomapanFilm.creative100()
: a = 1,
b = 5,
c = 2,
super('Fomapan CREATIVE 100', 100); // coverage:ignore-line
/// https://www.foma.cz/en/fomapan-200
const FomapanFilm.creative200()
: a = 1.5,
b = 4.5,
c = 3,
super('Fomapan CREATIVE 200', 200); // coverage:ignore-line
/// https://www.foma.cz/en/fomapan-100
const FomapanFilm.action400()
: a = -1.25, // coverage:ignore-line
b = 5.75,
c = 1.5,
super('Fomapan ACTION 400', 400); // coverage:ignore-line
@override
double reciprocityFormula(double t) => t * log10polynomian(t, a, b, c);
}
class IlfordFilm extends Film {
final double reciprocityPower;
/// https://www.ilfordphoto.com/amfile/file/download/file/1948/product/1650/
const IlfordFilm.ortho()
: reciprocityPower = 1.25,
super('Ilford ORTHO+', 80); // coverage:ignore-line
/// https://www.ilfordphoto.com/amfile/file/download/file/1919/product/686/
const IlfordFilm.fp4()
: reciprocityPower = 1.26,
super('Ilford FP4+', 125); // coverage:ignore-line
/// https://www.ilfordphoto.com/amfile/file/download/file/1903/product/691/
const IlfordFilm.hp5()
: reciprocityPower = 1.31,
super('Ilford HP5+', 400); // coverage:ignore-line
/// https://www.ilfordphoto.com/amfile/file/download/file/3/product/679/
const IlfordFilm.delta100()
: reciprocityPower = 1.26,
super('Ilford DELTA 100', 100); // coverage:ignore-line
/// https://www.ilfordphoto.com/amfile/file/download/file/1915/product/684/
const IlfordFilm.delta400()
: reciprocityPower = 1.41,
super('Ilford DELTA 400', 400); // coverage:ignore-line
/// https://www.ilfordphoto.com/amfile/file/download/file/1913/product/682/
const IlfordFilm.delta3200()
: reciprocityPower = 1.33,
super('Ilford DELTA 3200', 3200); // coverage:ignore-line
/// https://www.ilfordphoto.com/amfile/file/download/file/1905/product/699/
const IlfordFilm.panf()
: reciprocityPower = 1.33,
super('Ilford Pan F+', 50); // coverage:ignore-line
/// https://www.ilfordphoto.com/amfile/file/download/file/1907/product/701/
const IlfordFilm.sfx200()
: reciprocityPower = 1.31,
super('Ilford SFX 200', 200); // coverage:ignore-line
/// https://www.ilfordphoto.com/amfile/file/download/file/1909/product/703/
const IlfordFilm.xp2super()
: reciprocityPower = 1.31,
super('Ilford XP2 SUPER', 400); // coverage:ignore-line
/// https://www.ilfordphoto.com/amfile/file/download/file/1958/product/696/
const IlfordFilm.pan100()
: reciprocityPower = 1.26,
super('Kentemere 100', 100); // coverage:ignore-line
/// https://www.ilfordphoto.com/amfile/file/download/file/1959/product/697/
const IlfordFilm.pan400()
: reciprocityPower = 1.30,
super('Kentemere 400', 400); // coverage:ignore-line
@override
double reciprocityFormula(double t) => pow(t, reciprocityPower).toDouble();
}
class KodakFilm extends Film {
final double a;
final double b;
final double c;
const KodakFilm.tmax100()
: a = 1 / 6, // coverage:ignore-line
b = 0, // coverage:ignore-line
c = 4 / 3, // coverage:ignore-line
super('Kodak T-MAX 100', 100); // coverage:ignore-line
const KodakFilm.tmax400()
: a = 2 / 3, // coverage:ignore-line
b = -1 / 2, // coverage:ignore-line
c = 4 / 3, // coverage:ignore-line
super('Kodak T-MAX 400', 400); // coverage:ignore-line
const KodakFilm.tmax3200()
: a = 7 / 6, // coverage:ignore-line
b = -1, // coverage:ignore-line
c = 4 / 3, // coverage:ignore-line
super('Kodak T-MAX 3200', 3200); // coverage:ignore-line
const KodakFilm.trix320()
: a = 2,
b = 1,
c = 2,
super('Kodak TRI-X 320', 320); // coverage:ignore-line
const KodakFilm.trix400()
: a = 2,
b = 1,
c = 2,
super('Kodak TRI-X 400', 400); // coverage:ignore-line
@override
double reciprocityFormula(double t) => t * log10polynomian(t, a, b, c);
}

View file

@ -2,7 +2,6 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/ev_source_type.dart'; import 'package:lightmeter/data/models/ev_source_type.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/data/models/supported_locale.dart'; import 'package:lightmeter/data/models/supported_locale.dart';
import 'package:lightmeter/data/models/theme_type.dart'; import 'package:lightmeter/data/models/theme_type.dart';
@ -19,7 +18,6 @@ class UserPreferencesService {
static const cameraEvCalibrationKey = "cameraEvCalibration"; static const cameraEvCalibrationKey = "cameraEvCalibration";
static const lightSensorEvCalibrationKey = "lightSensorEvCalibration"; static const lightSensorEvCalibrationKey = "lightSensorEvCalibration";
static const meteringScreenLayoutKey = "meteringScreenLayout"; static const meteringScreenLayoutKey = "meteringScreenLayout";
static const filmKey = "film";
static const caffeineKey = "caffeine"; static const caffeineKey = "caffeine";
static const hapticsKey = "haptics"; static const hapticsKey = "haptics";
@ -142,10 +140,4 @@ class UserPreferencesService {
bool get dynamicColor => _sharedPreferences.getBool(dynamicColorKey) ?? false; bool get dynamicColor => _sharedPreferences.getBool(dynamicColorKey) ?? false;
set dynamicColor(bool value) => _sharedPreferences.setBool(dynamicColorKey, value); set dynamicColor(bool value) => _sharedPreferences.setBool(dynamicColorKey, value);
Film get film => Film.values.firstWhere(
(e) => e.name == _sharedPreferences.getString(filmKey),
orElse: () => Film.values.first,
);
set film(Film value) => _sharedPreferences.setString(filmKey, value.name);
} }

View file

@ -2,7 +2,6 @@ import 'package:app_settings/app_settings.dart';
import 'package:lightmeter/data/caffeine_service.dart'; import 'package:lightmeter/data/caffeine_service.dart';
import 'package:lightmeter/data/haptics_service.dart'; import 'package:lightmeter/data/haptics_service.dart';
import 'package:lightmeter/data/light_sensor_service.dart'; import 'package:lightmeter/data/light_sensor_service.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/data/models/volume_action.dart'; import 'package:lightmeter/data/models/volume_action.dart';
import 'package:lightmeter/data/permissions_service.dart'; import 'package:lightmeter/data/permissions_service.dart';
import 'package:lightmeter/data/shared_prefs_service.dart'; import 'package:lightmeter/data/shared_prefs_service.dart';
@ -45,9 +44,6 @@ class MeteringInteractor {
NdValue get ndFilter => _userPreferencesService.ndFilter; NdValue get ndFilter => _userPreferencesService.ndFilter;
set ndFilter(NdValue value) => _userPreferencesService.ndFilter = value; set ndFilter(NdValue value) => _userPreferencesService.ndFilter = value;
Film get film => _userPreferencesService.film;
set film(Film value) => _userPreferencesService.film = value;
VolumeAction get volumeAction => _userPreferencesService.volumeAction; VolumeAction get volumeAction => _userPreferencesService.volumeAction;
/// Executes vibration if haptics are enabled in settings /// Executes vibration if haptics are enabled in settings

View file

@ -41,6 +41,9 @@
"meteringScreenFeatureFilmPicker": "Film picker", "meteringScreenFeatureFilmPicker": "Film picker",
"meteringScreenFeatureHistogram": "Histogram", "meteringScreenFeatureHistogram": "Histogram",
"film": "Film", "film": "Film",
"filmPush": "Film (push)",
"filmPull": "Film (pull)",
"filmReciprocityHint": "Applies correction for shutter speeds grater than 1 second",
"equipment": "Equipment", "equipment": "Equipment",
"equipmentProfileName": "Equipment profile name", "equipmentProfileName": "Equipment profile name",
"equipmentProfileNameHint": "Praktica MTL5B", "equipmentProfileNameHint": "Praktica MTL5B",
@ -56,6 +59,8 @@
"equipmentProfile": "Equipment profile", "equipmentProfile": "Equipment profile",
"equipmentProfiles": "Equipment profiles", "equipmentProfiles": "Equipment profiles",
"tapToAdd": "Tap to add", "tapToAdd": "Tap to add",
"filmsInUse": "Films in use",
"filmsInUseDescription": "Select films which you use.",
"general": "General", "general": "General",
"keepScreenOn": "Keep screen on", "keepScreenOn": "Keep screen on",
"haptics": "Haptics", "haptics": "Haptics",

View file

@ -41,9 +41,11 @@
"meteringScreenFeatureFilmPicker": "Sélecteur de film", "meteringScreenFeatureFilmPicker": "Sélecteur de film",
"meteringScreenFeatureHistogram": "Histogramme", "meteringScreenFeatureHistogram": "Histogramme",
"film": "Pellicule", "film": "Pellicule",
"filmPush": "Pellicule (push)",
"filmPull": "Pellicule (pull)",
"filmReciprocityHint": "La correction s'applique aux vitesses d'obturation supérieures à 1 seconde",
"equipment": "Équipement", "equipment": "Équipement",
"equipmentProfileName": "Nom du profil de l'équipement", "equipmentProfileName": "Nom du profil de l'équipement",
"tapToAdd": "Appuie pour ajouter",
"equipmentProfileNameHint": "Praktica MTL5B", "equipmentProfileNameHint": "Praktica MTL5B",
"equipmentProfileAllValues": "Tout", "equipmentProfileAllValues": "Tout",
"apertureValues": "Valeurs Aperture", "apertureValues": "Valeurs Aperture",
@ -56,6 +58,9 @@
"isoValuesFilterDescription": "Sélectionnez les valeurs ISO à afficher. Ce sont peut-être vos valeurs les plus couramment utilisées ou celles prises en charge par votre caméra.", "isoValuesFilterDescription": "Sélectionnez les valeurs ISO à afficher. Ce sont peut-être vos valeurs les plus couramment utilisées ou celles prises en charge par votre caméra.",
"equipmentProfile": "Profil de l'équipement", "equipmentProfile": "Profil de l'équipement",
"equipmentProfiles": "Profils de l'équipement", "equipmentProfiles": "Profils de l'équipement",
"tapToAdd": "Appuie pour ajouter",
"filmsInUse": "Films en usage",
"filmsInUseDescription": "Sélectionnez les films que vous utilisez.",
"general": "Général", "general": "Général",
"keepScreenOn": "Garder l'écran allumé", "keepScreenOn": "Garder l'écran allumé",
"haptics": "Haptiques", "haptics": "Haptiques",

View file

@ -41,6 +41,9 @@
"meteringScreenFeatureFilmPicker": "Выбор пленки", "meteringScreenFeatureFilmPicker": "Выбор пленки",
"meteringScreenFeatureHistogram": "Гистограмма", "meteringScreenFeatureHistogram": "Гистограмма",
"film": "Пленка", "film": "Пленка",
"filmPush": "Пленка (push)",
"filmPull": "Пленка (pull)",
"filmReciprocityHint": "Применяет коррекцию для выдержек длиннее 1 секунды",
"equipment": "Оборудование", "equipment": "Оборудование",
"equipmentProfileName": "Название профиля", "equipmentProfileName": "Название профиля",
"equipmentProfileNameHint": "Praktica MTL5B", "equipmentProfileNameHint": "Praktica MTL5B",
@ -56,6 +59,8 @@
"equipmentProfile": "Оборудование", "equipmentProfile": "Оборудование",
"equipmentProfiles": "Профили оборудования", "equipmentProfiles": "Профили оборудования",
"tapToAdd": "Нажмите, чтобы добавить", "tapToAdd": "Нажмите, чтобы добавить",
"filmsInUse": "Используемые пленки",
"filmsInUseDescription": "Выберите пленки, которыми вы пользуетесь.",
"general": "Общие", "general": "Общие",
"keepScreenOn": "Запрет блокировки", "keepScreenOn": "Запрет блокировки",
"haptics": "Вибрация", "haptics": "Вибрация",

View file

@ -31,7 +31,7 @@
"thirdStops": "1/3", "thirdStops": "1/3",
"calibration": "校准", "calibration": "校准",
"calibrationMessage": "此应用测量读数的准确性完全取决于设备的硬件。因此,请考虑测试此应用并手动设置 EV 校准,以获得准确的测量结果。", "calibrationMessage": "此应用测量读数的准确性完全取决于设备的硬件。因此,请考虑测试此应用并手动设置 EV 校准,以获得准确的测量结果。",
"calibrationMessageCameraOnly": "此应用程序测量读数的准确性完全取决于设备的后置摄像头。因此,请考虑测试此应用并手动设置 EV 校准,以获得准确的测量结果。", "calibrationMessageCameraOnly": "此应用程序测量读数的准确s性完全取决于设备的后置摄像头。因此,请考虑测试此应用并手动设置 EV 校准,以获得准确的测量结果。",
"camera": "摄像头", "camera": "摄像头",
"lightSensor": "光传感器", "lightSensor": "光传感器",
"meteringScreenLayout": "布局", "meteringScreenLayout": "布局",
@ -41,6 +41,9 @@
"meteringScreenFeatureFilmPicker": "胶片选择", "meteringScreenFeatureFilmPicker": "胶片选择",
"meteringScreenFeatureHistogram": "直方图", "meteringScreenFeatureHistogram": "直方图",
"film": "胶片", "film": "胶片",
"filmPush": "胶片 (push)",
"filmPull": "胶片 (pull)",
"filmReciprocityHint": "Applies correction for shutter speeds grater than 1 second",
"equipment": "设备", "equipment": "设备",
"equipmentProfileName": "设备配置名称", "equipmentProfileName": "设备配置名称",
"equipmentProfileNameHint": "Praktica MTL5B", "equipmentProfileNameHint": "Praktica MTL5B",
@ -56,6 +59,8 @@
"equipmentProfile": "设备配置", "equipmentProfile": "设备配置",
"equipmentProfiles": "设备配置", "equipmentProfiles": "设备配置",
"tapToAdd": "點擊添加", "tapToAdd": "點擊添加",
"filmsInUse": "Films in use",
"filmsInUseDescription": "Select films which you use.",
"general": "通用", "general": "通用",
"keepScreenOn": "保持屏幕常亮", "keepScreenOn": "保持屏幕常亮",
"haptics": "震动", "haptics": "震动",

View file

@ -3,7 +3,6 @@ import 'dart:async';
import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:bloc_concurrency/bloc_concurrency.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/data/models/volume_action.dart'; import 'package:lightmeter/data/models/volume_action.dart';
import 'package:lightmeter/interactors/metering_interactor.dart'; import 'package:lightmeter/interactors/metering_interactor.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
@ -29,7 +28,6 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
) : super( ) : super(
MeteringDataState( MeteringDataState(
ev100: null, ev100: null,
film: _meteringInteractor.film,
iso: _meteringInteractor.iso, iso: _meteringInteractor.iso,
nd: _meteringInteractor.ndFilter, nd: _meteringInteractor.ndFilter,
isMetering: false, isMetering: false,
@ -42,7 +40,6 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
.listen(onCommunicationState); .listen(onCommunicationState);
on<EquipmentProfileChangedEvent>(_onEquipmentProfileChanged); on<EquipmentProfileChangedEvent>(_onEquipmentProfileChanged);
on<FilmChangedEvent>(_onFilmChanged);
on<IsoChangedEvent>(_onIsoChanged); on<IsoChangedEvent>(_onIsoChanged);
on<NdChangedEvent>(_onNdChanged); on<NdChangedEvent>(_onNdChanged);
on<MeasureEvent>(_onMeasure, transformer: droppable()); on<MeasureEvent>(_onMeasure, transformer: droppable());
@ -92,12 +89,9 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
/// Update selected ISO value and discard selected film, if selected equipment profile /// Update selected ISO value and discard selected film, if selected equipment profile
/// doesn't contain currently selected value /// doesn't contain currently selected value
IsoValue iso = state.iso; IsoValue iso = state.iso;
Film film = state.film;
if (!event.equipmentProfileData.isoValues.any((v) => state.iso.value == v.value)) { if (!event.equipmentProfileData.isoValues.any((v) => state.iso.value == v.value)) {
_meteringInteractor.iso = event.equipmentProfileData.isoValues.first; _meteringInteractor.iso = event.equipmentProfileData.isoValues.first;
iso = event.equipmentProfileData.isoValues.first; iso = event.equipmentProfileData.isoValues.first;
_meteringInteractor.film = Film.values.first;
film = Film.values.first;
willUpdateMeasurements = true; willUpdateMeasurements = true;
} }
@ -113,7 +107,6 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
emit( emit(
MeteringDataState( MeteringDataState(
ev100: state.ev100, ev100: state.ev100,
film: film,
iso: iso, iso: iso,
nd: nd, nd: nd,
isMetering: state.isMetering, isMetering: state.isMetering,
@ -122,46 +115,12 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
} }
} }
void _onFilmChanged(FilmChangedEvent event, Emitter emit) {
if (state.film.name != event.film.name) {
_meteringInteractor.film = event.film;
/// Find `IsoValue` with matching value
IsoValue iso = state.iso;
if (state.iso.value != event.film.iso && event.film != const Film.other()) {
iso = IsoValue.values.firstWhere(
(e) => e.value == event.film.iso,
orElse: () => state.iso,
);
_meteringInteractor.iso = iso;
}
/// If user selects 'Other' film we preserve currently selected ISO
/// and therefore only discard reciprocity formula
emit(
MeteringDataState(
ev100: state.ev100,
film: event.film,
iso: iso,
nd: state.nd,
isMetering: state.isMetering,
),
);
}
}
void _onIsoChanged(IsoChangedEvent event, Emitter emit) { void _onIsoChanged(IsoChangedEvent event, Emitter emit) {
/// Discard currently selected film even if ISO is the same,
/// because, for example, Fomapan 400 and any Ilford 400
/// have different reciprocity formulas
_meteringInteractor.film = Film.values.first;
if (state.iso != event.isoValue) { if (state.iso != event.isoValue) {
_meteringInteractor.iso = event.isoValue; _meteringInteractor.iso = event.isoValue;
emit( emit(
MeteringDataState( MeteringDataState(
ev100: state.ev100, ev100: state.ev100,
film: Film.values.first,
iso: event.isoValue, iso: event.isoValue,
nd: state.nd, nd: state.nd,
isMetering: state.isMetering, isMetering: state.isMetering,
@ -176,7 +135,6 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
emit( emit(
MeteringDataState( MeteringDataState(
ev100: state.ev100, ev100: state.ev100,
film: state.film,
iso: state.iso, iso: state.iso,
nd: event.ndValue, nd: event.ndValue,
isMetering: state.isMetering, isMetering: state.isMetering,
@ -190,7 +148,6 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
_communicationBloc.add(const communication_events.MeasureEvent()); _communicationBloc.add(const communication_events.MeasureEvent());
emit( emit(
LoadingState( LoadingState(
film: state.film,
iso: state.iso, iso: state.iso,
nd: state.nd, nd: state.nd,
), ),
@ -209,7 +166,6 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
emit( emit(
MeteringDataState( MeteringDataState(
ev100: event.ev100, ev100: event.ev100,
film: state.film,
iso: state.iso, iso: state.iso,
nd: state.nd, nd: state.nd,
isMetering: event.isMetering, isMetering: event.isMetering,
@ -221,7 +177,6 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
emit( emit(
MeteringDataState( MeteringDataState(
ev100: null, ev100: null,
film: state.film,
iso: state.iso, iso: state.iso,
nd: state.nd, nd: state.nd,
isMetering: event.isMetering, isMetering: event.isMetering,

View file

@ -31,27 +31,7 @@ class _CameraPreviewState extends State<CameraPreview> {
AnimatedSwitcher( AnimatedSwitcher(
duration: Dimens.switchDuration, duration: Dimens.switchDuration,
child: widget.controller != null child: widget.controller != null
? ValueListenableBuilder<CameraValue>( ? _CameraPreviewBuilder(controller: widget.controller!)
valueListenable: widget.controller!,
builder: (_, __, ___) => widget.controller!.value.isInitialized
? Stack(
alignment: Alignment.bottomCenter,
children: [
CameraView(controller: widget.controller!),
if (UserPreferencesProvider.meteringScreenFeatureOf(
context,
MeteringScreenLayoutFeature.histogram,
))
Positioned(
left: Dimens.grid8,
right: Dimens.grid8,
bottom: Dimens.grid16,
child: CameraHistogram(controller: widget.controller!),
),
],
)
: const SizedBox.shrink(),
)
: CameraViewPlaceholder(error: widget.error), : CameraViewPlaceholder(error: widget.error),
), ),
], ],
@ -60,3 +40,59 @@ class _CameraPreviewState extends State<CameraPreview> {
); );
} }
} }
class _CameraPreviewBuilder extends StatefulWidget {
final CameraController controller;
const _CameraPreviewBuilder({required this.controller});
@override
State<_CameraPreviewBuilder> createState() => _CameraPreviewBuilderState();
}
class _CameraPreviewBuilderState extends State<_CameraPreviewBuilder> {
late final ValueNotifier<bool> _initializedNotifier =
ValueNotifier<bool>(widget.controller.value.isInitialized);
@override
void initState() {
super.initState();
widget.controller.addListener(_update);
}
@override
void dispose() {
widget.controller.removeListener(_update);
_initializedNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: _initializedNotifier,
builder: (context, value, child) => value
? Stack(
alignment: Alignment.bottomCenter,
children: [
CameraView(controller: widget.controller),
if (UserPreferencesProvider.meteringScreenFeatureOf(
context,
MeteringScreenLayoutFeature.histogram,
))
Positioned(
left: Dimens.grid8,
right: Dimens.grid8,
bottom: Dimens.grid16,
child: CameraHistogram(controller: widget.controller),
),
],
)
: const SizedBox.shrink(),
);
}
void _update() {
_initializedNotifier.value = widget.controller.value.isInitialized;
}
}

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/data/models/exposure_pair.dart'; import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart'; import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart';
import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart'; import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart';
@ -12,10 +11,8 @@ import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class CameraContainerProvider extends StatelessWidget { class CameraContainerProvider extends StatelessWidget {
final ExposurePair? fastest; final ExposurePair? fastest;
final ExposurePair? slowest; final ExposurePair? slowest;
final Film film;
final IsoValue iso; final IsoValue iso;
final NdValue nd; final NdValue nd;
final ValueChanged<Film> onFilmChanged;
final ValueChanged<IsoValue> onIsoChanged; final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged; final ValueChanged<NdValue> onNdChanged;
final List<ExposurePair> exposurePairs; final List<ExposurePair> exposurePairs;
@ -23,10 +20,8 @@ class CameraContainerProvider extends StatelessWidget {
const CameraContainerProvider({ const CameraContainerProvider({
required this.fastest, required this.fastest,
required this.slowest, required this.slowest,
required this.film,
required this.iso, required this.iso,
required this.nd, required this.nd,
required this.onFilmChanged,
required this.onIsoChanged, required this.onIsoChanged,
required this.onNdChanged, required this.onNdChanged,
required this.exposurePairs, required this.exposurePairs,
@ -44,10 +39,8 @@ class CameraContainerProvider extends StatelessWidget {
child: CameraContainer( child: CameraContainer(
fastest: fastest, fastest: fastest,
slowest: slowest, slowest: slowest,
film: film,
iso: iso, iso: iso,
nd: nd, nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged, onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged, onNdChanged: onNdChanged,
exposurePairs: exposurePairs, exposurePairs: exposurePairs,

View file

@ -3,7 +3,6 @@ import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/data/models/exposure_pair.dart'; import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/platform_config.dart'; import 'package:lightmeter/platform_config.dart';
import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/providers/user_preferences_provider.dart';
@ -23,10 +22,8 @@ import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class CameraContainer extends StatelessWidget { class CameraContainer extends StatelessWidget {
final ExposurePair? fastest; final ExposurePair? fastest;
final ExposurePair? slowest; final ExposurePair? slowest;
final Film film;
final IsoValue iso; final IsoValue iso;
final NdValue nd; final NdValue nd;
final ValueChanged<Film> onFilmChanged;
final ValueChanged<IsoValue> onIsoChanged; final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged; final ValueChanged<NdValue> onNdChanged;
final List<ExposurePair> exposurePairs; final List<ExposurePair> exposurePairs;
@ -34,10 +31,8 @@ class CameraContainer extends StatelessWidget {
const CameraContainer({ const CameraContainer({
required this.fastest, required this.fastest,
required this.slowest, required this.slowest,
required this.film,
required this.iso, required this.iso,
required this.nd, required this.nd,
required this.onFilmChanged,
required this.onIsoChanged, required this.onIsoChanged,
required this.onNdChanged, required this.onNdChanged,
required this.exposurePairs, required this.exposurePairs,
@ -60,10 +55,8 @@ class CameraContainer extends StatelessWidget {
readingsContainer: ReadingsContainer( readingsContainer: ReadingsContainer(
fastest: fastest, fastest: fastest,
slowest: slowest, slowest: slowest,
film: film,
iso: iso, iso: iso,
nd: nd, nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged, onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged, onNdChanged: onNdChanged,
), ),

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/data/models/exposure_pair.dart'; import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
import 'package:lightmeter/screens/metering/components/light_sensor_container/bloc_container_light_sensor.dart'; import 'package:lightmeter/screens/metering/components/light_sensor_container/bloc_container_light_sensor.dart';
import 'package:lightmeter/screens/metering/components/light_sensor_container/widget_container_light_sensor.dart'; import 'package:lightmeter/screens/metering/components/light_sensor_container/widget_container_light_sensor.dart';
@ -11,10 +10,8 @@ import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class LightSensorContainerProvider extends StatelessWidget { class LightSensorContainerProvider extends StatelessWidget {
final ExposurePair? fastest; final ExposurePair? fastest;
final ExposurePair? slowest; final ExposurePair? slowest;
final Film film;
final IsoValue iso; final IsoValue iso;
final NdValue nd; final NdValue nd;
final ValueChanged<Film> onFilmChanged;
final ValueChanged<IsoValue> onIsoChanged; final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged; final ValueChanged<NdValue> onNdChanged;
final List<ExposurePair> exposurePairs; final List<ExposurePair> exposurePairs;
@ -22,10 +19,8 @@ class LightSensorContainerProvider extends StatelessWidget {
const LightSensorContainerProvider({ const LightSensorContainerProvider({
required this.fastest, required this.fastest,
required this.slowest, required this.slowest,
required this.film,
required this.iso, required this.iso,
required this.nd, required this.nd,
required this.onFilmChanged,
required this.onIsoChanged, required this.onIsoChanged,
required this.onNdChanged, required this.onNdChanged,
required this.exposurePairs, required this.exposurePairs,
@ -43,10 +38,8 @@ class LightSensorContainerProvider extends StatelessWidget {
child: LightSensorContainer( child: LightSensorContainer(
fastest: fastest, fastest: fastest,
slowest: slowest, slowest: slowest,
film: film,
iso: iso, iso: iso,
nd: nd, nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged, onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged, onNdChanged: onNdChanged,
exposurePairs: exposurePairs, exposurePairs: exposurePairs,

View file

@ -1,6 +1,5 @@
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/film.dart';
import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart'; import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart';
import 'package:lightmeter/screens/metering/components/shared/metering_top_bar/widget_top_bar_metering.dart'; import 'package:lightmeter/screens/metering/components/shared/metering_top_bar/widget_top_bar_metering.dart';
@ -10,10 +9,8 @@ import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class LightSensorContainer extends StatelessWidget { class LightSensorContainer extends StatelessWidget {
final ExposurePair? fastest; final ExposurePair? fastest;
final ExposurePair? slowest; final ExposurePair? slowest;
final Film film;
final IsoValue iso; final IsoValue iso;
final NdValue nd; final NdValue nd;
final ValueChanged<Film> onFilmChanged;
final ValueChanged<IsoValue> onIsoChanged; final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged; final ValueChanged<NdValue> onNdChanged;
final List<ExposurePair> exposurePairs; final List<ExposurePair> exposurePairs;
@ -21,10 +18,8 @@ class LightSensorContainer extends StatelessWidget {
const LightSensorContainer({ const LightSensorContainer({
required this.fastest, required this.fastest,
required this.slowest, required this.slowest,
required this.film,
required this.iso, required this.iso,
required this.nd, required this.nd,
required this.onFilmChanged,
required this.onIsoChanged, required this.onIsoChanged,
required this.onNdChanged, required this.onNdChanged,
required this.exposurePairs, required this.exposurePairs,
@ -39,10 +34,8 @@ class LightSensorContainer extends StatelessWidget {
readingsContainer: ReadingsContainer( readingsContainer: ReadingsContainer(
fastest: fastest, fastest: fastest,
slowest: slowest, slowest: slowest,
film: film,
iso: iso, iso: iso,
nd: nd, nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged, onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged, onNdChanged: onNdChanged,
), ),

View file

@ -5,6 +5,7 @@ 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;
@ -47,7 +48,8 @@ class ExposurePairsList extends StatelessWidget {
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: ExposurePairsListItem( child: ExposurePairsListItem(
exposurePairs[index].shutterSpeed, Films.selectedOf(context)
.reciprocityFailure(exposurePairs[index].shutterSpeed),
tickOnTheLeft: true, tickOnTheLeft: true,
), ),
), ),

View file

@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class EquipmentProfilePicker extends StatelessWidget {
const EquipmentProfilePicker();
@override
Widget build(BuildContext context) {
return AnimatedDialogPicker<EquipmentProfile>(
icon: Icons.camera,
title: S.of(context).equipmentProfile,
selectedValue: EquipmentProfiles.selectedOf(context),
values: EquipmentProfiles.of(context),
itemTitleBuilder: (_, value) => Text(value.id.isEmpty ? S.of(context).none : value.name),
onChanged: EquipmentProfileProvider.of(context).setProfile,
closedChild: ReadingValueContainer.singleValue(
value: ReadingValue(
label: S.of(context).equipmentProfile,
value: EquipmentProfiles.selectedOf(context).id.isEmpty
? S.of(context).none
: EquipmentProfiles.selectedOf(context).name,
),
),
);
}
}

View file

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
class ExtremeExposurePairsContainer extends StatelessWidget {
final ExposurePair? fastest;
final ExposurePair? slowest;
const ExtremeExposurePairsContainer({
required this.fastest,
required this.slowest,
super.key,
});
@override
Widget build(BuildContext context) {
return ReadingValueContainer(
values: [
ReadingValue(
label: S.of(context).fastestExposurePair,
value: _exposurePairToString(context, fastest),
),
ReadingValue(
label: S.of(context).slowestExposurePair,
value: _exposurePairToString(context, slowest),
),
],
);
}
String _exposurePairToString(BuildContext context, ExposurePair? pair) {
if (pair == null) {
return '-';
}
return '${pair.aperture} - ${Films.selectedOf(context).reciprocityFailure(pair.shutterSpeed)}';
}
}

View file

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class FilmPicker extends StatelessWidget {
final IsoValue selectedIso;
const FilmPicker({required this.selectedIso});
@override
Widget build(BuildContext context) {
return AnimatedDialogPicker<Film>(
icon: Icons.camera_roll,
title: S.of(context).film,
subtitle: S.of(context).filmReciprocityHint,
selectedValue: Films.selectedOf(context),
values: Films.inUseOf(context),
itemTitleBuilder: (_, value) => Text(value.name.isEmpty ? S.of(context).none : value.name),
onChanged: FilmsProvider.of(context).setFilm,
closedChild: ReadingValueContainer.singleValue(
value: ReadingValue(
label: _label(context),
value: Films.selectedOf(context).name.isEmpty
? S.of(context).none
: Films.selectedOf(context).name,
),
),
);
}
String _label(BuildContext context) {
if (Films.selectedOf(context) == const Film.other() ||
Films.selectedOf(context).iso == selectedIso.value) {
return S.of(context).film;
}
final evDiff = IsoValue(
Films.selectedOf(context).iso,
StopType.full,
).difference(selectedIso);
if (evDiff > 0) {
return S.of(context).filmPush;
} else if (evDiff < 0) {
return S.of(context).filmPull;
} else {
return S.of(context).film;
}
}
}

View file

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class IsoValuePicker extends StatelessWidget {
final List<IsoValue> values;
final IsoValue selectedValue;
final ValueChanged<IsoValue> onChanged;
const IsoValuePicker({
required this.selectedValue,
required this.values,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return AnimatedDialogPicker<IsoValue>(
icon: Icons.iso,
title: S.of(context).iso,
subtitle: S.of(context).filmSpeed,
selectedValue: selectedValue,
values: values,
itemTitleBuilder: (_, value) => Text(value.value.toString()),
// using ascending order, because increase in film speed rises EV
itemTrailingBuilder: (selected, value) => value.value != selected.value
? Text(S.of(context).evValue(selected.toStringDifference(value)))
: null,
onChanged: onChanged,
closedChild: ReadingValueContainer.singleValue(
value: ReadingValue(
label: S.of(context).iso,
value: selectedValue.value.toString(),
),
),
);
}
}

View file

@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class NdValuePicker extends StatelessWidget {
final List<NdValue> values;
final NdValue selectedValue;
final ValueChanged<NdValue> onChanged;
const NdValuePicker({
required this.selectedValue,
required this.values,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return AnimatedDialogPicker<NdValue>(
icon: Icons.filter_b_and_w,
title: S.of(context).nd,
subtitle: S.of(context).ndFilterFactor,
selectedValue: selectedValue,
values: values,
itemTitleBuilder: (_, value) => Text(
value.value == 0 ? S.of(context).none : value.value.toString(),
),
// using descending order, because ND filter darkens image & lowers EV
itemTrailingBuilder: (selected, value) => value.value != selected.value
? Text(S.of(context).evValue(value.toStringDifference(selected)))
: null,
onChanged: onChanged,
closedChild: ReadingValueContainer.singleValue(
value: ReadingValue(
label: S.of(context).nd,
value: selectedValue.value.toString(),
),
),
);
}
}

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart';
// Has to be stateful, so that [GlobalKey] is not recreated. // Has to be stateful, so that [GlobalKey] is not recreated.
// Otherwise use will no be able to close the dialog after EV value has changed. // Otherwise use will no be able to close the dialog after EV value has changed.

View file

@ -1,32 +1,29 @@
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/film.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/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/animated_dialog_picker/widget_picker_dialog_animated.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/reading_value_container/widget_container_reading_value.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/film_picker/widget_picker_film.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/iso_picker/widget_picker_iso.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/nd_picker/widget_picker_nd.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart'; import 'package:m3_lightmeter_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 {
final ExposurePair? fastest; final ExposurePair? fastest;
final ExposurePair? slowest; final ExposurePair? slowest;
final Film film;
final IsoValue iso; final IsoValue iso;
final NdValue nd; final NdValue nd;
final ValueChanged<Film> onFilmChanged;
final ValueChanged<IsoValue> onIsoChanged; final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged; final ValueChanged<NdValue> onNdChanged;
const ReadingsContainer({ const ReadingsContainer({
required this.fastest, required this.fastest,
required this.slowest, required this.slowest,
required this.film,
required this.iso, required this.iso,
required this.nd, required this.nd,
required this.onFilmChanged,
required this.onIsoChanged, required this.onIsoChanged,
required this.onNdChanged, required this.onNdChanged,
super.key, super.key,
@ -41,24 +38,16 @@ class ReadingsContainer extends StatelessWidget {
context, context,
MeteringScreenLayoutFeature.equipmentProfiles, MeteringScreenLayoutFeature.equipmentProfiles,
)) ...[ )) ...[
const _EquipmentProfilePicker(), const EquipmentProfilePicker(),
const _InnerPadding(), const _InnerPadding(),
], ],
if (UserPreferencesProvider.meteringScreenFeatureOf( if (UserPreferencesProvider.meteringScreenFeatureOf(
context, context,
MeteringScreenLayoutFeature.extremeExposurePairs, MeteringScreenLayoutFeature.extremeExposurePairs,
)) ...[ )) ...[
ReadingValueContainer( ExtremeExposurePairsContainer(
values: [ fastest: fastest,
ReadingValue( slowest: slowest,
label: S.of(context).fastestExposurePair,
value: fastest != null ? fastest!.toString() : '-',
),
ReadingValue(
label: S.of(context).slowestExposurePair,
value: fastest != null ? slowest!.toString() : '-',
),
],
), ),
const _InnerPadding(), const _InnerPadding(),
], ],
@ -66,17 +55,13 @@ class ReadingsContainer extends StatelessWidget {
context, context,
MeteringScreenLayoutFeature.filmPicker, MeteringScreenLayoutFeature.filmPicker,
)) ...[ )) ...[
_FilmPicker( FilmPicker(selectedIso: iso),
values: Film.values,
selectedValue: film,
onChanged: onFilmChanged,
),
const _InnerPadding(), const _InnerPadding(),
], ],
Row( Row(
children: [ children: [
Expanded( Expanded(
child: _IsoValuePicker( child: IsoValuePicker(
selectedValue: iso, selectedValue: iso,
values: EquipmentProfiles.selectedOf(context).isoValues, values: EquipmentProfiles.selectedOf(context).isoValues,
onChanged: onIsoChanged, onChanged: onIsoChanged,
@ -84,7 +69,7 @@ class ReadingsContainer extends StatelessWidget {
), ),
const _InnerPadding(), const _InnerPadding(),
Expanded( Expanded(
child: _NdValuePicker( child: NdValuePicker(
selectedValue: nd, selectedValue: nd,
values: EquipmentProfiles.selectedOf(context).ndValues, values: EquipmentProfiles.selectedOf(context).ndValues,
onChanged: onNdChanged, onChanged: onNdChanged,
@ -100,129 +85,3 @@ class ReadingsContainer extends StatelessWidget {
class _InnerPadding extends SizedBox { class _InnerPadding extends SizedBox {
const _InnerPadding() : super(height: Dimens.grid8, width: Dimens.grid8); const _InnerPadding() : super(height: Dimens.grid8, width: Dimens.grid8);
} }
class _EquipmentProfilePicker extends StatelessWidget {
const _EquipmentProfilePicker();
@override
Widget build(BuildContext context) {
return AnimatedDialogPicker<EquipmentProfile>(
icon: Icons.camera,
title: S.of(context).equipmentProfile,
selectedValue: EquipmentProfiles.selectedOf(context),
values: EquipmentProfiles.of(context),
itemTitleBuilder: (_, value) => Text(value.id.isEmpty ? S.of(context).none : value.name),
onChanged: EquipmentProfileProvider.of(context).setProfile,
closedChild: ReadingValueContainer.singleValue(
value: ReadingValue(
label: S.of(context).equipmentProfile,
value: EquipmentProfiles.selectedOf(context).id.isEmpty
? S.of(context).none
: EquipmentProfiles.selectedOf(context).name,
),
),
);
}
}
class _FilmPicker extends StatelessWidget {
final List<Film> values;
final Film selectedValue;
final ValueChanged<Film> onChanged;
const _FilmPicker({
required this.values,
required this.selectedValue,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return AnimatedDialogPicker<Film>(
icon: Icons.camera_roll,
title: S.of(context).film,
selectedValue: selectedValue,
values: values,
itemTitleBuilder: (_, value) => Text(value.name.isEmpty ? S.of(context).none : value.name),
onChanged: onChanged,
closedChild: ReadingValueContainer.singleValue(
value: ReadingValue(
label: S.of(context).film,
value: selectedValue.name.isEmpty ? S.of(context).none : selectedValue.name,
),
),
);
}
}
class _IsoValuePicker extends StatelessWidget {
final List<IsoValue> values;
final IsoValue selectedValue;
final ValueChanged<IsoValue> onChanged;
const _IsoValuePicker({
required this.selectedValue,
required this.values,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return AnimatedDialogPicker<IsoValue>(
icon: Icons.iso,
title: S.of(context).iso,
subtitle: S.of(context).filmSpeed,
selectedValue: selectedValue,
values: values,
itemTitleBuilder: (_, value) => Text(value.value.toString()),
// using ascending order, because increase in film speed rises EV
itemTrailingBuilder: (selected, value) => value.value != selected.value
? Text(S.of(context).evValue(selected.toStringDifference(value)))
: null,
onChanged: onChanged,
closedChild: ReadingValueContainer.singleValue(
value: ReadingValue(
label: S.of(context).iso,
value: selectedValue.value.toString(),
),
),
);
}
}
class _NdValuePicker extends StatelessWidget {
final List<NdValue> values;
final NdValue selectedValue;
final ValueChanged<NdValue> onChanged;
const _NdValuePicker({
required this.selectedValue,
required this.values,
required this.onChanged,
});
@override
Widget build(BuildContext context) {
return AnimatedDialogPicker<NdValue>(
icon: Icons.filter_b_and_w,
title: S.of(context).nd,
subtitle: S.of(context).ndFilterFactor,
selectedValue: selectedValue,
values: values,
itemTitleBuilder: (_, value) => Text(
value.value == 0 ? S.of(context).none : value.value.toString(),
),
// using descending order, because ND filter darkens image & lowers EV
itemTrailingBuilder: (selected, value) => value.value != selected.value
? Text(S.of(context).evValue(value.toStringDifference(selected)))
: null,
onChanged: onChanged,
closedChild: ReadingValueContainer.singleValue(
value: ReadingValue(
label: S.of(context).nd,
value: selectedValue.value.toString(),
),
),
);
}
}

View file

@ -1,4 +1,3 @@
import 'package:lightmeter/data/models/film.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
sealed class MeteringEvent { sealed class MeteringEvent {
@ -11,12 +10,6 @@ class EquipmentProfileChangedEvent extends MeteringEvent {
const EquipmentProfileChangedEvent(this.equipmentProfileData); const EquipmentProfileChangedEvent(this.equipmentProfileData);
} }
class FilmChangedEvent extends MeteringEvent {
final Film film;
const FilmChangedEvent(this.film);
}
class IsoChangedEvent extends MeteringEvent { class IsoChangedEvent extends MeteringEvent {
final IsoValue isoValue; final IsoValue isoValue;

View file

@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; 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/film.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/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';
@ -33,11 +32,8 @@ class MeteringScreen extends StatelessWidget {
child: BlocBuilder<MeteringBloc, MeteringState>( child: BlocBuilder<MeteringBloc, MeteringState>(
builder: (_, state) => MeteringContainerBuidler( builder: (_, state) => MeteringContainerBuidler(
ev: state is MeteringDataState ? state.ev : null, ev: state is MeteringDataState ? state.ev : null,
film: state.film,
iso: state.iso, iso: state.iso,
nd: state.nd, nd: state.nd,
onFilmChanged: (value) =>
context.read<MeteringBloc>().add(FilmChangedEvent(value)),
onIsoChanged: (value) => context.read<MeteringBloc>().add(IsoChangedEvent(value)), onIsoChanged: (value) => context.read<MeteringBloc>().add(IsoChangedEvent(value)),
onNdChanged: (value) => context.read<MeteringBloc>().add(NdChangedEvent(value)), onNdChanged: (value) => context.read<MeteringBloc>().add(NdChangedEvent(value)),
), ),
@ -81,7 +77,7 @@ class _InheritedListeners extends StatelessWidget {
feature: MeteringScreenLayoutFeature.filmPicker, feature: MeteringScreenLayoutFeature.filmPicker,
onDidChangeDependencies: (value) { onDidChangeDependencies: (value) {
if (!value) { if (!value) {
context.read<MeteringBloc>().add(const FilmChangedEvent(Film.other())); FilmsProvider.of(context).setFilm(const Film.other());
} }
}, },
child: child, child: child,
@ -92,19 +88,15 @@ class _InheritedListeners extends StatelessWidget {
class MeteringContainerBuidler extends StatelessWidget { class MeteringContainerBuidler extends StatelessWidget {
final double? ev; final double? ev;
final Film film;
final IsoValue iso; final IsoValue iso;
final NdValue nd; final NdValue nd;
final ValueChanged<Film> onFilmChanged;
final ValueChanged<IsoValue> onIsoChanged; final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged; final ValueChanged<NdValue> onNdChanged;
const MeteringContainerBuidler({ const MeteringContainerBuidler({
required this.ev, required this.ev,
required this.film,
required this.iso, required this.iso,
required this.nd, required this.nd,
required this.onFilmChanged,
required this.onIsoChanged, required this.onIsoChanged,
required this.onNdChanged, required this.onNdChanged,
}); });
@ -116,7 +108,6 @@ class MeteringContainerBuidler extends StatelessWidget {
ev!, ev!,
UserPreferencesProvider.stopTypeOf(context), UserPreferencesProvider.stopTypeOf(context),
EquipmentProfiles.selectedOf(context), EquipmentProfiles.selectedOf(context),
film,
) )
: <ExposurePair>[]; : <ExposurePair>[];
final fastest = exposurePairs.isNotEmpty ? exposurePairs.first : null; final fastest = exposurePairs.isNotEmpty ? exposurePairs.first : null;
@ -126,10 +117,8 @@ class MeteringContainerBuidler extends StatelessWidget {
? CameraContainerProvider( ? CameraContainerProvider(
fastest: fastest, fastest: fastest,
slowest: slowest, slowest: slowest,
film: film,
iso: iso, iso: iso,
nd: nd, nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged, onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged, onNdChanged: onNdChanged,
exposurePairs: exposurePairs, exposurePairs: exposurePairs,
@ -137,10 +126,8 @@ class MeteringContainerBuidler extends StatelessWidget {
: LightSensorContainerProvider( : LightSensorContainerProvider(
fastest: fastest, fastest: fastest,
slowest: slowest, slowest: slowest,
film: film,
iso: iso, iso: iso,
nd: nd, nd: nd,
onFilmChanged: onFilmChanged,
onIsoChanged: onIsoChanged, onIsoChanged: onIsoChanged,
onNdChanged: onNdChanged, onNdChanged: onNdChanged,
exposurePairs: exposurePairs, exposurePairs: exposurePairs,
@ -152,7 +139,6 @@ class MeteringContainerBuidler extends StatelessWidget {
double ev, double ev,
StopType stopType, StopType stopType,
EquipmentProfile equipmentProfile, EquipmentProfile equipmentProfile,
Film film,
) { ) {
if (ev.isNaN || ev.isInfinite) { if (ev.isNaN || ev.isInfinite) {
return List.empty(); return List.empty();
@ -195,7 +181,7 @@ class MeteringContainerBuidler extends StatelessWidget {
itemsCount, itemsCount,
(index) => ExposurePair( (index) => ExposurePair(
apertureValues[index + apertureOffset], apertureValues[index + apertureOffset],
film.reciprocityFailure(shutterSpeedValues[index + shutterSpeedOffset]), shutterSpeedValues[index + shutterSpeedOffset],
), ),
growable: false, growable: false,
); );

View file

@ -1,18 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
@immutable @immutable
abstract class MeteringState { abstract class MeteringState {
final double? ev100; final double? ev100;
final Film film;
final IsoValue iso; final IsoValue iso;
final NdValue nd; final NdValue nd;
final bool isMetering; final bool isMetering;
const MeteringState({ const MeteringState({
this.ev100, this.ev100,
required this.film,
required this.iso, required this.iso,
required this.nd, required this.nd,
required this.isMetering, required this.isMetering,
@ -21,7 +18,6 @@ abstract class MeteringState {
class LoadingState extends MeteringState { class LoadingState extends MeteringState {
const LoadingState({ const LoadingState({
required super.film,
required super.iso, required super.iso,
required super.nd, required super.nd,
}) : super(isMetering: true); }) : super(isMetering: true);
@ -30,7 +26,6 @@ class LoadingState extends MeteringState {
class MeteringDataState extends MeteringState { class MeteringDataState extends MeteringState {
const MeteringDataState({ const MeteringDataState({
required super.ev100, required super.ev100,
required super.film,
required super.iso, required super.iso,
required super.nd, required super.nd,
required super.isMetering, required super.isMetering,

View file

@ -1,30 +0,0 @@
import 'package:flutter/material.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class EquipmentProfileListener extends StatefulWidget {
final ValueChanged<EquipmentProfile> onDidChangeDependencies;
final Widget child;
const EquipmentProfileListener({
required this.onDidChangeDependencies,
required this.child,
super.key,
});
@override
State<EquipmentProfileListener> createState() => _EquipmentProfileListenerState();
}
class _EquipmentProfileListenerState extends State<EquipmentProfileListener> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
widget.onDidChangeDependencies(EquipmentProfiles.selectedOf(context));
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}

View file

@ -1,7 +1,7 @@
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/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/components/equipment_list_tiles/components/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/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/components/equipment_list_tiles/components/dialog_range_picker/widget_dialog_picker_range.dart'; import 'package:lightmeter/screens/settings/components/shared/dialog_range_picker/widget_dialog_picker_range.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class EquipmentListTiles extends StatelessWidget { class EquipmentListTiles extends StatelessWidget {

View file

@ -3,8 +3,8 @@ import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.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/components/equipment_list_tiles/widget_list_tiles_equipments.dart'; import 'package:lightmeter/screens/settings/components/equipment/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/components/equipment_list_tiles/widget_list_tiles_equipments.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/equipment/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class EquipmentProfileContainer extends StatefulWidget { class EquipmentProfileContainer extends StatefulWidget {

View file

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/generated/l10n.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/equipment/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/equipment/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_iap/m3_lightmeter_iap.dart';

View file

@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/settings/components/equipment/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart';
import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class EquipmentProfilesListTile extends StatelessWidget {
const EquipmentProfilesListTile({super.key});
@override
Widget build(BuildContext context) {
return IAPListTile(
leading: const Icon(Icons.camera),
title: Text(S.of(context).equipmentProfiles),
onTap: () {
Navigator.of(context).push<EquipmentProfile>(
MaterialPageRoute(builder: (_) => const EquipmentProfilesScreen()),
);
},
);
}
}

View file

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart';
import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class FilmsListTile extends StatelessWidget {
const FilmsListTile({super.key});
@override
Widget build(BuildContext context) {
return IAPListTile(
leading: const Icon(Icons.camera_roll),
title: Text(S.of(context).filmsInUse),
onTap: () {
showDialog<List<Film>>(
context: context,
builder: (_) => DialogFilter<Film>(
icon: const Icon(Icons.camera_roll),
title: S.of(context).filmsInUse,
description: S.of(context).filmsInUseDescription,
values: Films.of(context).sublist(1),
selectedValues: Films.inUseOf(context),
titleAdapter: (_, value) => value.name,
),
).then((values) {
if (values != null) {
FilmsProvider.of(context).saveFilms(values);
}
});
},
);
}
}

View file

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/settings/components/equipment/components/equipment_profiles/widget_list_tile_equipment_profiles.dart';
import 'package:lightmeter/screens/settings/components/equipment/components/films/widget_list_tile_films.dart';
import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart';
class EquipmentSettingsSection extends StatelessWidget {
const EquipmentSettingsSection({super.key});
@override
Widget build(BuildContext context) {
return SettingsSection(
title: S.of(context).equipment,
children: const [
EquipmentProfilesListTile(),
FilmsListTile(),
],
);
}
}

View file

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/supported_locale.dart'; import 'package:lightmeter/data/models/supported_locale.dart';
import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart'; import 'package:lightmeter/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart';
class LanguageListTile extends StatelessWidget { class LanguageListTile extends StatelessWidget {
const LanguageListTile({super.key}); const LanguageListTile({super.key});

View file

@ -1,43 +0,0 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class EquipmentProfilesListTile extends StatelessWidget {
const EquipmentProfilesListTile({super.key});
@override
Widget build(BuildContext context) {
final paidStatus = IAPProducts.productOf(context, IAPProductType.paidFeatures)?.status ??
IAPProductStatus.pending;
log(paidStatus.toString());
return ListTile(
leading: const Icon(Icons.camera),
title: Text(S.of(context).equipmentProfiles),
onTap: switch (paidStatus) {
IAPProductStatus.purchased => () {
Navigator.of(context).push<EquipmentProfile>(
MaterialPageRoute(builder: (_) => const EquipmentProfilesScreen()),
);
},
IAPProductStatus.pending => null,
_ => () {
IAPProductsProvider.of(context).buy(IAPProductType.paidFeatures);
},
},
trailing: switch (paidStatus) {
IAPProductStatus.purchasable => const Icon(Icons.lock),
IAPProductStatus.pending => const SizedBox(
height: Dimens.grid24,
width: Dimens.grid24,
child: CircularProgressIndicator(),
),
_ => null,
},
);
}
}

View file

@ -1,7 +1,7 @@
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/user_preferences_provider.dart'; import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart'; import 'package:lightmeter/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class StopTypeListTile extends StatelessWidget { class StopTypeListTile extends StatelessWidget {

View file

@ -1,7 +1,6 @@
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/screens/settings/components/metering/components/calibration/widget_list_tile_calibration.dart'; import 'package:lightmeter/screens/settings/components/metering/components/calibration/widget_list_tile_calibration.dart';
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/widget_list_tile_equipment_profiles.dart';
import 'package:lightmeter/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart'; import 'package:lightmeter/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart';
import 'package:lightmeter/screens/settings/components/metering/components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart'; import 'package:lightmeter/screens/settings/components/metering/components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart';
import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart'; import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart';
@ -17,7 +16,6 @@ class MeteringSettingsSection extends StatelessWidget {
StopTypeListTile(), StopTypeListTile(),
CalibrationListTile(), CalibrationListTile(),
MeteringScreenLayoutListTile(), MeteringScreenLayoutListTile(),
EquipmentProfilesListTile(),
], ],
); );
} }

View file

@ -1,9 +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/res/dimens.dart'; import 'package:lightmeter/res/dimens.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class DialogFilter<T extends PhotographyValue> extends StatefulWidget { class DialogFilter<T> extends StatefulWidget {
final Icon icon; final Icon icon;
final String title; final String title;
final String description; final String description;
@ -25,10 +24,10 @@ class DialogFilter<T extends PhotographyValue> extends StatefulWidget {
State<DialogFilter<T>> createState() => _DialogFilterState<T>(); State<DialogFilter<T>> createState() => _DialogFilterState<T>();
} }
class _DialogFilterState<T extends PhotographyValue> extends State<DialogFilter<T>> { class _DialogFilterState<T> extends State<DialogFilter<T>> {
late final List<bool> checkboxValues = List.generate( late final List<bool> checkboxValues = List.generate(
widget.values.length, widget.values.length,
(index) => widget.selectedValues.any((element) => element.value == widget.values[index].value), (index) => widget.selectedValues.any((element) => element == widget.values[index]),
growable: false, growable: false,
); );

View file

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
/// Depends on the product status and replaces [onTap] with purchase callback
/// if the product is purchasable.
class IAPListTile extends StatelessWidget {
final IAPProductType product;
final Icon leading;
final Text title;
final VoidCallback onTap;
const IAPListTile({
this.product = IAPProductType.paidFeatures,
required this.leading,
required this.title,
required this.onTap,
super.key,
});
@override
Widget build(BuildContext context) {
return ListTile(
leading: leading,
title: title,
onTap: switch (IAPProducts.productOf(context, product)?.status) {
IAPProductStatus.purchasable => () => IAPProductsProvider.of(context).buy(product),
IAPProductStatus.pending => null,
IAPProductStatus.purchased => onTap,
null => null,
},
);
}
}

View file

@ -4,10 +4,12 @@ import 'package:lightmeter/res/dimens.dart';
class SettingsSection extends StatelessWidget { class SettingsSection extends StatelessWidget {
final String title; final String title;
final List<Widget> children; final List<Widget> children;
final bool enabled;
const SettingsSection({ const SettingsSection({
required this.title, required this.title,
required this.children, required this.children,
this.enabled = true,
super.key, super.key,
}); });
@ -23,6 +25,8 @@ class SettingsSection extends StatelessWidget {
child: Card( child: Card(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM), padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
child: Opacity(
opacity: enabled ? Dimens.enabledOpacity : Dimens.disabledOpacity,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -42,6 +46,7 @@ class SettingsSection extends StatelessWidget {
), ),
), ),
), ),
),
); );
} }
} }

View file

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/theme_type.dart'; import 'package:lightmeter/data/models/theme_type.dart';
import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart'; import 'package:lightmeter/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart';
class ThemeTypeListTile extends StatelessWidget { class ThemeTypeListTile extends StatelessWidget {
const ThemeTypeListTile({super.key}); const ThemeTypeListTile({super.key});

View file

@ -1,6 +1,7 @@
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/screens/settings/components/about/widget_settings_section_about.dart'; import 'package:lightmeter/screens/settings/components/about/widget_settings_section_about.dart';
import 'package:lightmeter/screens/settings/components/equipment/widget_settings_section_equipment.dart';
import 'package:lightmeter/screens/settings/components/general/widget_settings_section_general.dart'; import 'package:lightmeter/screens/settings/components/general/widget_settings_section_general.dart';
import 'package:lightmeter/screens/settings/components/metering/widget_settings_section_metering.dart'; import 'package:lightmeter/screens/settings/components/metering/widget_settings_section_metering.dart';
import 'package:lightmeter/screens/settings/components/theme/widget_settings_section_theme.dart'; import 'package:lightmeter/screens/settings/components/theme/widget_settings_section_theme.dart';
@ -43,6 +44,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
delegate: SliverChildListDelegate( delegate: SliverChildListDelegate(
<Widget>[ <Widget>[
const MeteringSettingsSection(), const MeteringSettingsSection(),
const EquipmentSettingsSection(),
const GeneralSettingsSection(), const GeneralSettingsSection(),
const ThemeSettingsSection(), const ThemeSettingsSection(),
const AboutSettingsSection(), const AboutSettingsSection(),

View file

@ -0,0 +1,11 @@
{
"folders": [
{
"path": "iap"
},
{
"path": "."
}
],
"settings": {}
}

View file

@ -1,121 +0,0 @@
import 'package:lightmeter/data/models/film.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
import 'package:test/test.dart';
void main() {
test('iso', () {
expect(const Film.other().iso, 0);
expect(const FomapanFilm.creative100().iso, 100);
expect(const FomapanFilm.creative200().iso, 200);
expect(const FomapanFilm.action400().iso, 400);
expect(const IlfordFilm.ortho().iso, 80);
expect(const IlfordFilm.delta100().iso, 100);
expect(const IlfordFilm.delta400().iso, 400);
expect(const IlfordFilm.delta3200().iso, 3200);
expect(const IlfordFilm.fp4().iso, 125);
expect(const IlfordFilm.hp5().iso, 400);
expect(const IlfordFilm.panf().iso, 50);
expect(const IlfordFilm.sfx200().iso, 200);
expect(const IlfordFilm.xp2super().iso, 400);
expect(const IlfordFilm.pan100().iso, 100);
expect(const IlfordFilm.pan400().iso, 400);
expect(const KodakFilm.tmax100().iso, 100);
expect(const KodakFilm.tmax400().iso, 400);
expect(const KodakFilm.tmax3200().iso, 3200);
expect(const KodakFilm.trix320().iso, 320);
expect(const KodakFilm.trix400().iso, 400);
});
test('toString()', () {
expect(const Film.other().toString(), "");
expect(const FomapanFilm.creative100().toString(), "Fomapan CREATIVE 100");
expect(const FomapanFilm.creative200().toString(), "Fomapan CREATIVE 200");
expect(const FomapanFilm.action400().toString(), "Fomapan ACTION 400");
expect(const IlfordFilm.ortho().toString(), "Ilford ORTHO+");
expect(const IlfordFilm.delta100().toString(), "Ilford DELTA 100");
expect(const IlfordFilm.delta400().toString(), "Ilford DELTA 400");
expect(const IlfordFilm.delta3200().toString(), "Ilford DELTA 3200");
expect(const IlfordFilm.fp4().toString(), "Ilford FP4+");
expect(const IlfordFilm.hp5().toString(), "Ilford HP5+");
expect(const IlfordFilm.panf().toString(), "Ilford Pan F+");
expect(const IlfordFilm.sfx200().toString(), "Ilford SFX 200");
expect(const IlfordFilm.xp2super().toString(), "Ilford XP2 SUPER");
expect(const IlfordFilm.pan100().toString(), "Kentemere 100");
expect(const IlfordFilm.pan400().toString(), "Kentemere 400");
expect(const KodakFilm.tmax100().toString(), "Kodak T-MAX 100");
expect(const KodakFilm.tmax400().toString(), "Kodak T-MAX 400");
expect(const KodakFilm.tmax3200().toString(), "Kodak T-MAX 3200");
expect(const KodakFilm.trix320().toString(), "Kodak TRI-X 320");
expect(const KodakFilm.trix400().toString(), "Kodak TRI-X 400");
});
group(
'reciprocityFailure',
() {
const inputSpeeds = [
ShutterSpeedValue(1000, true, StopType.full),
ShutterSpeedValue(1, false, StopType.full),
ShutterSpeedValue(16, false, StopType.full)
];
test('No change `Film.other()`', () {
expect(
const Film.other().reciprocityFailure(inputSpeeds[0]),
const ShutterSpeedValue(1000, true, StopType.full),
);
expect(
const Film.other().reciprocityFailure(inputSpeeds[1]),
const ShutterSpeedValue(1, false, StopType.full),
);
expect(
const Film.other().reciprocityFailure(inputSpeeds[2]),
const ShutterSpeedValue(16, false, StopType.full),
);
});
test('pow `IlfordFilm.delta100()`', () {
expect(
const IlfordFilm.delta100().reciprocityFailure(inputSpeeds[0]),
const ShutterSpeedValue(1000, true, StopType.full),
);
expect(
const IlfordFilm.delta100().reciprocityFailure(inputSpeeds[1]),
const ShutterSpeedValue(1, false, StopType.full),
);
expect(
const IlfordFilm.delta100().reciprocityFailure(inputSpeeds[2]),
const ShutterSpeedValue(32.899642452994128, false, StopType.full),
);
});
test('log10polynomian `FomapanFilm.creative100()`', () {
expect(
const FomapanFilm.creative100().reciprocityFailure(inputSpeeds[0]),
const ShutterSpeedValue(1000, true, StopType.full),
);
expect(
const FomapanFilm.creative100().reciprocityFailure(inputSpeeds[1]),
const ShutterSpeedValue(2, false, StopType.full),
);
expect(
const FomapanFilm.creative100().reciprocityFailure(inputSpeeds[2]),
const ShutterSpeedValue(151.52807753457483, false, StopType.full),
);
});
test('log10polynomian `Kodak.tmax400()`', () {
expect(
const KodakFilm.tmax400().reciprocityFailure(inputSpeeds[0]),
const ShutterSpeedValue(1000, true, StopType.full),
);
expect(
const KodakFilm.tmax400().reciprocityFailure(inputSpeeds[1]),
const ShutterSpeedValue(1.3333333333333333, false, StopType.full),
);
expect(
const KodakFilm.tmax400().reciprocityFailure(inputSpeeds[2]),
const ShutterSpeedValue(27.166026086819844, false, StopType.full),
);
});
},
);
}

View file

@ -1,7 +1,6 @@
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/data/models/ev_source_type.dart'; import 'package:lightmeter/data/models/ev_source_type.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/data/models/supported_locale.dart'; import 'package:lightmeter/data/models/supported_locale.dart';
import 'package:lightmeter/data/models/theme_type.dart'; import 'package:lightmeter/data/models/theme_type.dart';
@ -392,26 +391,4 @@ void main() {
.called(1); .called(1);
}); });
}); });
group('film', () {
test('get default', () {
when(() => sharedPreferences.getString(UserPreferencesService.filmKey)).thenReturn(null);
expect(service.film, Film.values.first);
});
test('get', () {
when(() => sharedPreferences.getString(UserPreferencesService.filmKey))
.thenReturn('Fomapan ACTION 400');
expect(service.film, const FomapanFilm.action400());
});
test('set', () {
when(() => sharedPreferences.setString(UserPreferencesService.filmKey, 'Fomapan ACTION 400'))
.thenAnswer((_) => Future.value(true));
service.film = const FomapanFilm.action400();
verify(
() => sharedPreferences.setString(UserPreferencesService.filmKey, 'Fomapan ACTION 400'),
).called(1);
});
});
} }

View file

@ -2,7 +2,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:lightmeter/data/caffeine_service.dart'; import 'package:lightmeter/data/caffeine_service.dart';
import 'package:lightmeter/data/haptics_service.dart'; import 'package:lightmeter/data/haptics_service.dart';
import 'package:lightmeter/data/light_sensor_service.dart'; import 'package:lightmeter/data/light_sensor_service.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/data/models/volume_action.dart'; import 'package:lightmeter/data/models/volume_action.dart';
import 'package:lightmeter/data/permissions_service.dart'; import 'package:lightmeter/data/permissions_service.dart';
import 'package:lightmeter/data/shared_prefs_service.dart'; import 'package:lightmeter/data/shared_prefs_service.dart';
@ -124,19 +123,6 @@ void main() {
interactor.ndFilter = NdValue.values.first; interactor.ndFilter = NdValue.values.first;
verify(() => mockUserPreferencesService.ndFilter = NdValue.values.first).called(1); verify(() => mockUserPreferencesService.ndFilter = NdValue.values.first).called(1);
}); });
test('film - get', () async {
when(() => mockUserPreferencesService.film).thenReturn(Film.values.first);
expect(interactor.film, Film.values.first);
verify(() => mockUserPreferencesService.film).called(1);
});
test('film - set', () async {
when(() => mockUserPreferencesService.film = Film.values.first)
.thenReturn(Film.values.first);
interactor.film = Film.values.first;
verify(() => mockUserPreferencesService.film = Film.values.first).called(1);
});
}, },
); );

View file

@ -1,5 +1,4 @@
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:lightmeter/data/models/film.dart';
import 'package:lightmeter/data/models/volume_action.dart'; import 'package:lightmeter/data/models/volume_action.dart';
import 'package:lightmeter/interactors/metering_interactor.dart'; import 'package:lightmeter/interactors/metering_interactor.dart';
import 'package:lightmeter/screens/metering/bloc_metering.dart'; import 'package:lightmeter/screens/metering/bloc_metering.dart';
@ -34,7 +33,6 @@ void main() {
meteringInteractor = _MockMeteringInteractor(); meteringInteractor = _MockMeteringInteractor();
when<IsoValue>(() => meteringInteractor.iso).thenReturn(iso100); when<IsoValue>(() => meteringInteractor.iso).thenReturn(iso100);
when<NdValue>(() => meteringInteractor.ndFilter).thenReturn(NdValue.values.first); when<NdValue>(() => meteringInteractor.ndFilter).thenReturn(NdValue.values.first);
when<Film>(() => meteringInteractor.film).thenReturn(Film.values.first);
when(meteringInteractor.quickVibration).thenAnswer((_) async {}); when(meteringInteractor.quickVibration).thenAnswer((_) async {});
when(meteringInteractor.responseVibration).thenAnswer((_) async {}); when(meteringInteractor.responseVibration).thenAnswer((_) async {});
when(meteringInteractor.errorVibration).thenAnswer((_) async {}); when(meteringInteractor.errorVibration).thenAnswer((_) async {});
@ -157,7 +155,6 @@ void main() {
build: () => bloc, build: () => bloc,
seed: () => MeteringDataState( seed: () => MeteringDataState(
ev100: 1.0, ev100: 1.0,
film: Film.values[1],
iso: const IsoValue(100, StopType.full), iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first, nd: NdValue.values.first,
isMetering: false, isMetering: false,
@ -166,14 +163,12 @@ void main() {
bloc.add(const IsoChangedEvent(IsoValue(200, StopType.full))); bloc.add(const IsoChangedEvent(IsoValue(200, StopType.full)));
}, },
verify: (_) { verify: (_) {
verify(() => meteringInteractor.film = Film.values.first).called(1);
verify(() => meteringInteractor.iso = const IsoValue(200, StopType.full)).called(1); verify(() => meteringInteractor.iso = const IsoValue(200, StopType.full)).called(1);
}, },
expect: () => [ expect: () => [
isA<MeteringDataState>() isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 1.0) .having((state) => state.ev100, 'ev100', 1.0)
.having((state) => state.ev, 'ev', 2.0) .having((state) => state.ev, 'ev', 2.0)
.having((state) => state.film, 'film', Film.values.first)
.having((state) => state.iso, 'iso', const IsoValue(200, StopType.full)) .having((state) => state.iso, 'iso', const IsoValue(200, StopType.full))
.having((state) => state.nd, 'nd', NdValue.values.first) .having((state) => state.nd, 'nd', NdValue.values.first)
.having((state) => state.isMetering, 'isMetering', false), .having((state) => state.isMetering, 'isMetering', false),
@ -185,7 +180,6 @@ void main() {
build: () => bloc, build: () => bloc,
seed: () => MeteringDataState( seed: () => MeteringDataState(
ev100: null, ev100: null,
film: Film.values[1],
iso: const IsoValue(100, StopType.full), iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first, nd: NdValue.values.first,
isMetering: false, isMetering: false,
@ -194,14 +188,12 @@ void main() {
bloc.add(const IsoChangedEvent(IsoValue(200, StopType.full))); bloc.add(const IsoChangedEvent(IsoValue(200, StopType.full)));
}, },
verify: (_) { verify: (_) {
verify(() => meteringInteractor.film = Film.values.first).called(1);
verify(() => meteringInteractor.iso = const IsoValue(200, StopType.full)).called(1); verify(() => meteringInteractor.iso = const IsoValue(200, StopType.full)).called(1);
}, },
expect: () => [ expect: () => [
isA<MeteringDataState>() isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', null) .having((state) => state.ev100, 'ev100', null)
.having((state) => state.ev, 'ev', null) .having((state) => state.ev, 'ev', null)
.having((state) => state.film, 'film', Film.values.first)
.having((state) => state.iso, 'iso', const IsoValue(200, StopType.full)) .having((state) => state.iso, 'iso', const IsoValue(200, StopType.full))
.having((state) => state.nd, 'nd', NdValue.values.first) .having((state) => state.nd, 'nd', NdValue.values.first)
.having((state) => state.isMetering, 'isMetering', false), .having((state) => state.isMetering, 'isMetering', false),
@ -213,7 +205,6 @@ void main() {
build: () => bloc, build: () => bloc,
seed: () => MeteringDataState( seed: () => MeteringDataState(
ev100: 1.0, ev100: 1.0,
film: Film.values[1],
iso: const IsoValue(100, StopType.full), iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first, nd: NdValue.values.first,
isMetering: false, isMetering: false,
@ -222,7 +213,6 @@ void main() {
bloc.add(const IsoChangedEvent(IsoValue(100, StopType.full))); bloc.add(const IsoChangedEvent(IsoValue(100, StopType.full)));
}, },
verify: (_) { verify: (_) {
verify(() => meteringInteractor.film = Film.values.first).called(1);
verifyNever(() => meteringInteractor.iso = const IsoValue(100, StopType.full)); verifyNever(() => meteringInteractor.iso = const IsoValue(100, StopType.full));
}, },
expect: () => [], expect: () => [],
@ -233,7 +223,6 @@ void main() {
build: () => bloc, build: () => bloc,
seed: () => MeteringDataState( seed: () => MeteringDataState(
ev100: 1.0, ev100: 1.0,
film: Film.values[1],
iso: const IsoValue(100, StopType.full), iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first, nd: NdValue.values.first,
isMetering: false, isMetering: false,
@ -244,14 +233,12 @@ void main() {
bloc.onCommunicationState(const communication_states.MeteringEndedState(2)); bloc.onCommunicationState(const communication_states.MeteringEndedState(2));
}, },
verify: (_) { verify: (_) {
verify(() => meteringInteractor.film = Film.values.first).called(1);
verify(() => meteringInteractor.iso = const IsoValue(200, StopType.full)).called(1); verify(() => meteringInteractor.iso = const IsoValue(200, StopType.full)).called(1);
}, },
expect: () => [ expect: () => [
isA<MeteringDataState>() isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 1.0) .having((state) => state.ev100, 'ev100', 1.0)
.having((state) => state.ev, 'ev', 2.0) .having((state) => state.ev, 'ev', 2.0)
.having((state) => state.film, 'film', Film.values.first)
.having((state) => state.iso, 'iso', const IsoValue(200, StopType.full)) .having((state) => state.iso, 'iso', const IsoValue(200, StopType.full))
.having((state) => state.nd, 'nd', NdValue.values.first) .having((state) => state.nd, 'nd', NdValue.values.first)
.having((state) => state.isMetering, 'isMetering', false), .having((state) => state.isMetering, 'isMetering', false),
@ -259,7 +246,6 @@ void main() {
isA<MeteringDataState>() isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 2.0) .having((state) => state.ev100, 'ev100', 2.0)
.having((state) => state.ev, 'ev', 3.0) .having((state) => state.ev, 'ev', 3.0)
.having((state) => state.film, 'film', Film.values.first)
.having((state) => state.iso, 'iso', const IsoValue(200, StopType.full)) .having((state) => state.iso, 'iso', const IsoValue(200, StopType.full))
.having((state) => state.nd, 'nd', NdValue.values.first) .having((state) => state.nd, 'nd', NdValue.values.first)
.having((state) => state.isMetering, 'isMetering', false), .having((state) => state.isMetering, 'isMetering', false),
@ -276,7 +262,6 @@ void main() {
build: () => bloc, build: () => bloc,
seed: () => MeteringDataState( seed: () => MeteringDataState(
ev100: 1.0, ev100: 1.0,
film: Film.values[1],
iso: const IsoValue(100, StopType.full), iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first, nd: NdValue.values.first,
isMetering: false, isMetering: false,
@ -291,7 +276,6 @@ void main() {
isA<MeteringDataState>() isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 1.0) .having((state) => state.ev100, 'ev100', 1.0)
.having((state) => state.ev, 'ev', 0.0) .having((state) => state.ev, 'ev', 0.0)
.having((state) => state.film, 'film', Film.values[1])
.having((state) => state.iso, 'iso', const IsoValue(100, StopType.full)) .having((state) => state.iso, 'iso', const IsoValue(100, StopType.full))
.having((state) => state.nd, 'nd', const NdValue(2)) .having((state) => state.nd, 'nd', const NdValue(2))
.having((state) => state.isMetering, 'isMetering', false), .having((state) => state.isMetering, 'isMetering', false),
@ -303,7 +287,6 @@ void main() {
build: () => bloc, build: () => bloc,
seed: () => MeteringDataState( seed: () => MeteringDataState(
ev100: null, ev100: null,
film: Film.values[1],
iso: const IsoValue(100, StopType.full), iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first, nd: NdValue.values.first,
isMetering: false, isMetering: false,
@ -318,7 +301,6 @@ void main() {
isA<MeteringDataState>() isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', null) .having((state) => state.ev100, 'ev100', null)
.having((state) => state.ev, 'ev', null) .having((state) => state.ev, 'ev', null)
.having((state) => state.film, 'film', Film.values[1])
.having((state) => state.iso, 'iso', const IsoValue(100, StopType.full)) .having((state) => state.iso, 'iso', const IsoValue(100, StopType.full))
.having((state) => state.nd, 'nd', const NdValue(2)) .having((state) => state.nd, 'nd', const NdValue(2))
.having((state) => state.isMetering, 'isMetering', false), .having((state) => state.isMetering, 'isMetering', false),
@ -330,7 +312,6 @@ void main() {
build: () => bloc, build: () => bloc,
seed: () => MeteringDataState( seed: () => MeteringDataState(
ev100: 1.0, ev100: 1.0,
film: Film.values[1],
iso: const IsoValue(100, StopType.full), iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first, nd: NdValue.values.first,
isMetering: false, isMetering: false,
@ -349,7 +330,6 @@ void main() {
build: () => bloc, build: () => bloc,
seed: () => MeteringDataState( seed: () => MeteringDataState(
ev100: 1.0, ev100: 1.0,
film: Film.values[1],
iso: const IsoValue(100, StopType.full), iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first, nd: NdValue.values.first,
isMetering: false, isMetering: false,
@ -366,7 +346,6 @@ void main() {
isA<MeteringDataState>() isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 1.0) .having((state) => state.ev100, 'ev100', 1.0)
.having((state) => state.ev, 'ev', 0.0) .having((state) => state.ev, 'ev', 0.0)
.having((state) => state.film, 'film', Film.values[1])
.having((state) => state.iso, 'iso', const IsoValue(100, StopType.full)) .having((state) => state.iso, 'iso', const IsoValue(100, StopType.full))
.having((state) => state.nd, 'nd', const NdValue(2)) .having((state) => state.nd, 'nd', const NdValue(2))
.having((state) => state.isMetering, 'isMetering', false), .having((state) => state.isMetering, 'isMetering', false),
@ -374,7 +353,6 @@ void main() {
isA<MeteringDataState>() isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 2.0) .having((state) => state.ev100, 'ev100', 2.0)
.having((state) => state.ev, 'ev', 1.0) .having((state) => state.ev, 'ev', 1.0)
.having((state) => state.film, 'film', Film.values[1])
.having((state) => state.iso, 'iso', const IsoValue(100, StopType.full)) .having((state) => state.iso, 'iso', const IsoValue(100, StopType.full))
.having((state) => state.nd, 'nd', const NdValue(2)) .having((state) => state.nd, 'nd', const NdValue(2))
.having((state) => state.isMetering, 'isMetering', false), .having((state) => state.isMetering, 'isMetering', false),
@ -383,115 +361,6 @@ void main() {
}, },
); );
group(
'`FilmChangedEvent`',
() {
blocTest<MeteringBloc, MeteringState>(
'Pick different film with different ISO',
build: () => bloc,
seed: () => MeteringDataState(
ev100: 1.0,
film: const FomapanFilm.creative100(),
iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first,
isMetering: false,
),
act: (bloc) async {
bloc.add(const FilmChangedEvent(FomapanFilm.creative200()));
},
verify: (_) {
verify(() => meteringInteractor.film = const FomapanFilm.creative200()).called(1);
verify(() => meteringInteractor.iso = const IsoValue(200, StopType.full)).called(1);
},
expect: () => [
isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 1.0)
.having((state) => state.ev, 'ev', 2.0)
.having((state) => state.film, 'film', const FomapanFilm.creative200())
.having((state) => state.iso, 'iso', const IsoValue(200, StopType.full))
.having((state) => state.nd, 'nd', NdValue.values.first)
.having((state) => state.isMetering, 'isMetering', false),
],
);
blocTest<MeteringBloc, MeteringState>(
'Pick different film with same ISO',
build: () => bloc,
seed: () => MeteringDataState(
ev100: 1.0,
film: const FomapanFilm.creative100(),
iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first,
isMetering: false,
),
act: (bloc) async {
bloc.add(const FilmChangedEvent(IlfordFilm.delta100()));
},
verify: (_) {
verify(() => meteringInteractor.film = const IlfordFilm.delta100()).called(1);
verifyNever(() => meteringInteractor.iso = const IsoValue(100, StopType.full));
},
expect: () => [
isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 1.0)
.having((state) => state.ev, 'ev', 1.0)
.having((state) => state.film, 'film', const IlfordFilm.delta100())
.having((state) => state.iso, 'iso', const IsoValue(100, StopType.full))
.having((state) => state.nd, 'nd', NdValue.values.first)
.having((state) => state.isMetering, 'isMetering', false),
],
);
blocTest<MeteringBloc, MeteringState>(
'Pick same film',
build: () => bloc,
seed: () => MeteringDataState(
ev100: 1.0,
film: const FomapanFilm.creative100(),
iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first,
isMetering: false,
),
act: (bloc) async {
bloc.add(const FilmChangedEvent(FomapanFilm.creative100()));
},
verify: (_) {
verifyNever(() => meteringInteractor.film = const FomapanFilm.creative100());
},
expect: () => [],
);
blocTest<MeteringBloc, MeteringState>(
'Pick `Film.other()`',
build: () => bloc,
seed: () => MeteringDataState(
ev100: 1.0,
film: const FomapanFilm.creative100(),
iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first,
isMetering: false,
),
act: (bloc) async {
bloc.add(const FilmChangedEvent(Film.other()));
},
verify: (_) {
verify(() => meteringInteractor.film = const Film.other()).called(1);
verifyNever(() => meteringInteractor.iso = const IsoValue(0, StopType.full));
verifyNever(() => meteringInteractor.responseVibration());
},
expect: () => [
isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 1.0)
.having((state) => state.ev, 'ev', 1.0)
.having((state) => state.film, 'film', const Film.other())
.having((state) => state.iso, 'iso', const IsoValue(100, StopType.full))
.having((state) => state.nd, 'nd', NdValue.values.first)
.having((state) => state.isMetering, 'isMetering', false),
],
);
},
);
group( group(
'`EquipmentProfileChangedEvent`', '`EquipmentProfileChangedEvent`',
() { () {
@ -509,7 +378,6 @@ void main() {
build: () => bloc, build: () => bloc,
seed: () => MeteringDataState( seed: () => MeteringDataState(
ev100: 1.0, ev100: 1.0,
film: Film.values[1],
iso: const IsoValue(100, StopType.full), iso: const IsoValue(100, StopType.full),
nd: NdValue.values.first, nd: NdValue.values.first,
isMetering: false, isMetering: false,
@ -518,7 +386,6 @@ void main() {
bloc.add(EquipmentProfileChangedEvent(reducedProfile)); bloc.add(EquipmentProfileChangedEvent(reducedProfile));
}, },
verify: (_) { verify: (_) {
verifyNever(() => meteringInteractor.film = const Film.other());
verifyNever(() => meteringInteractor.iso = reducedProfile.isoValues.first); verifyNever(() => meteringInteractor.iso = reducedProfile.isoValues.first);
verifyNever(() => meteringInteractor.ndFilter = reducedProfile.ndValues.first); verifyNever(() => meteringInteractor.ndFilter = reducedProfile.ndValues.first);
verifyNever(() => meteringInteractor.responseVibration()); verifyNever(() => meteringInteractor.responseVibration());
@ -531,7 +398,6 @@ void main() {
build: () => bloc, build: () => bloc,
seed: () => MeteringDataState( seed: () => MeteringDataState(
ev100: 1.0, ev100: 1.0,
film: Film.values[1],
iso: IsoValue.values[2], iso: IsoValue.values[2],
nd: NdValue.values.first, nd: NdValue.values.first,
isMetering: false, isMetering: false,
@ -540,7 +406,6 @@ void main() {
bloc.add(EquipmentProfileChangedEvent(reducedProfile)); bloc.add(EquipmentProfileChangedEvent(reducedProfile));
}, },
verify: (_) { verify: (_) {
verify(() => meteringInteractor.film = const Film.other()).called(1);
verify(() => meteringInteractor.iso = reducedProfile.isoValues.first).called(1); verify(() => meteringInteractor.iso = reducedProfile.isoValues.first).called(1);
verifyNever(() => meteringInteractor.ndFilter = reducedProfile.ndValues.first); verifyNever(() => meteringInteractor.ndFilter = reducedProfile.ndValues.first);
verify(() => meteringInteractor.responseVibration()).called(1); verify(() => meteringInteractor.responseVibration()).called(1);
@ -548,7 +413,6 @@ void main() {
expect: () => [ expect: () => [
isA<MeteringDataState>() isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 1.0) .having((state) => state.ev100, 'ev100', 1.0)
.having((state) => state.film, 'film', const Film.other())
.having((state) => state.iso, 'iso', reducedProfile.isoValues.first) .having((state) => state.iso, 'iso', reducedProfile.isoValues.first)
.having((state) => state.nd, 'nd', NdValue.values.first) .having((state) => state.nd, 'nd', NdValue.values.first)
.having((state) => state.isMetering, 'isMetering', false), .having((state) => state.isMetering, 'isMetering', false),
@ -560,7 +424,6 @@ void main() {
build: () => bloc, build: () => bloc,
seed: () => MeteringDataState( seed: () => MeteringDataState(
ev100: 1.0, ev100: 1.0,
film: Film.values[1],
iso: const IsoValue(100, StopType.full), iso: const IsoValue(100, StopType.full),
nd: NdValue.values[4], nd: NdValue.values[4],
isMetering: false, isMetering: false,
@ -569,7 +432,6 @@ void main() {
bloc.add(EquipmentProfileChangedEvent(reducedProfile)); bloc.add(EquipmentProfileChangedEvent(reducedProfile));
}, },
verify: (_) { verify: (_) {
verifyNever(() => meteringInteractor.film = const Film.other());
verifyNever(() => meteringInteractor.iso = reducedProfile.isoValues.first); verifyNever(() => meteringInteractor.iso = reducedProfile.isoValues.first);
verify(() => meteringInteractor.ndFilter = reducedProfile.ndValues.first).called(1); verify(() => meteringInteractor.ndFilter = reducedProfile.ndValues.first).called(1);
verify(() => meteringInteractor.responseVibration()).called(1); verify(() => meteringInteractor.responseVibration()).called(1);
@ -577,7 +439,6 @@ void main() {
expect: () => [ expect: () => [
isA<MeteringDataState>() isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 1.0) .having((state) => state.ev100, 'ev100', 1.0)
.having((state) => state.film, 'film', Film.values[1])
.having((state) => state.iso, 'iso', const IsoValue(100, StopType.full)) .having((state) => state.iso, 'iso', const IsoValue(100, StopType.full))
.having((state) => state.nd, 'nd', reducedProfile.ndValues.first) .having((state) => state.nd, 'nd', reducedProfile.ndValues.first)
.having((state) => state.isMetering, 'isMetering', false), .having((state) => state.isMetering, 'isMetering', false),
@ -589,7 +450,6 @@ void main() {
build: () => bloc, build: () => bloc,
seed: () => MeteringDataState( seed: () => MeteringDataState(
ev100: 1.0, ev100: 1.0,
film: Film.values[1],
iso: IsoValue.values[2], iso: IsoValue.values[2],
nd: NdValue.values[4], nd: NdValue.values[4],
isMetering: false, isMetering: false,
@ -598,7 +458,6 @@ void main() {
bloc.add(EquipmentProfileChangedEvent(reducedProfile)); bloc.add(EquipmentProfileChangedEvent(reducedProfile));
}, },
verify: (_) { verify: (_) {
verify(() => meteringInteractor.film = const Film.other()).called(1);
verify(() => meteringInteractor.iso = reducedProfile.isoValues.first).called(1); verify(() => meteringInteractor.iso = reducedProfile.isoValues.first).called(1);
verify(() => meteringInteractor.ndFilter = reducedProfile.ndValues.first).called(1); verify(() => meteringInteractor.ndFilter = reducedProfile.ndValues.first).called(1);
verify(() => meteringInteractor.responseVibration()).called(1); verify(() => meteringInteractor.responseVibration()).called(1);
@ -606,7 +465,6 @@ void main() {
expect: () => [ expect: () => [
isA<MeteringDataState>() isA<MeteringDataState>()
.having((state) => state.ev100, 'ev100', 1.0) .having((state) => state.ev100, 'ev100', 1.0)
.having((state) => state.film, 'film', const Film.other())
.having((state) => state.iso, 'iso', reducedProfile.isoValues.first) .having((state) => state.iso, 'iso', reducedProfile.isoValues.first)
.having((state) => state.nd, 'nd', reducedProfile.ndValues.first) .having((state) => state.nd, 'nd', reducedProfile.ndValues.first)
.having((state) => state.isMetering, 'isMetering', false), .having((state) => state.isMetering, 'isMetering', false),

View file

@ -1,6 +1,5 @@
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/data/models/film.dart';
import 'package:lightmeter/screens/metering/screen_metering.dart'; import 'package:lightmeter/screens/metering/screen_metering.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
@ -19,7 +18,6 @@ void main() {
ev, ev,
StopType.full, StopType.full,
defaultEquipmentProfile, defaultEquipmentProfile,
const Film.other(),
); );
test('isNan', () { test('isNan', () {
@ -42,7 +40,6 @@ void main() {
ev, ev,
StopType.full, StopType.full,
defaultEquipmentProfile, defaultEquipmentProfile,
const Film.other(),
); );
test('EV 1', () { test('EV 1', () {
@ -142,7 +139,6 @@ void main() {
ev, ev,
StopType.half, StopType.half,
defaultEquipmentProfile, defaultEquipmentProfile,
const Film.other(),
); );
test('EV 1', () { test('EV 1', () {
@ -242,7 +238,6 @@ void main() {
ev, ev,
StopType.third, StopType.third,
defaultEquipmentProfile, defaultEquipmentProfile,
const Film.other(),
); );
test('EV 1', () { test('EV 1', () {
@ -356,7 +351,6 @@ void main() {
ev, ev,
StopType.full, StopType.full,
equipmentProfile, equipmentProfile,
const Film.other(),
); );
test('EV 1', () { test('EV 1', () {
@ -456,7 +450,6 @@ void main() {
ev, ev,
StopType.half, StopType.half,
equipmentProfile, equipmentProfile,
const Film.other(),
); );
test('EV 1', () { test('EV 1', () {
@ -556,7 +549,6 @@ void main() {
ev, ev,
StopType.third, StopType.third,
equipmentProfile, equipmentProfile,
const Film.other(),
); );
test('EV 1', () { test('EV 1', () {
@ -669,7 +661,6 @@ void main() {
ev, ev,
StopType.full, StopType.full,
equipmentProfile, equipmentProfile,
const Film.other(),
); );
test('EV 1', () { test('EV 1', () {
@ -769,7 +760,6 @@ void main() {
ev, ev,
StopType.half, StopType.half,
equipmentProfile, equipmentProfile,
const Film.other(),
); );
test('EV 1', () { test('EV 1', () {
@ -869,7 +859,6 @@ void main() {
ev, ev,
StopType.third, StopType.third,
equipmentProfile, equipmentProfile,
const Film.other(),
); );
test('EV 1', () { test('EV 1', () {