mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-24 00:10:47 +00:00
Implemented IAP & Equipment profiles (#89)
* added equipment profiles to layout config * calculate layout height based on `MeteringScreenLayoutFeature` * Update cd_dev.yml * Fixed equipment profile tile padding * import * `webfactory/ssh-agent` * Update pubspec.yaml * fixed `MeteringScreenLayoutConfigJson` tests * fixed `UserPreferencesService` tests * reset selected equipment profile when layout feature is disabled * `IAPProductType.equipment` -> `IAPProductType.paidFeatures` * updated packages versions * Update shared_prefs_service.dart * Fixed & tested exposure pairs list builder * typo * typo * added iap repo stub * Renamed `EquipmentProfileData` ->`EquipmentProfile` * Moved `EquipmentProfileProvider` to iap repo * Update README.md * Fixed `EquipmentProfileListener` * Improved `EquipmentProfilesListTile` statuses visualization * Update README.md * Update ci.yml * Post-merge fixes * typo * Added workflow checks * more sophisticated iap icons * Include IAP by default * added loader for `IAPProductStatus.pending` * typo * Added equipment profiles list placeholder * typo * separated `IconPlaceholder` * improved `buildExposureValues` testing * cleanup
This commit is contained in:
parent
d364de4486
commit
4bb080a144
48 changed files with 1608 additions and 294 deletions
16
.github/workflows/build_apk.yml
vendored
16
.github/workflows/build_apk.yml
vendored
|
@ -16,14 +16,28 @@ on:
|
|||
- dev
|
||||
- prod
|
||||
default: 'dev'
|
||||
include-iap:
|
||||
type: boolean
|
||||
description: Include IAP package
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build .apk
|
||||
runs-on: macos-11
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Connect private iap package
|
||||
uses: webfactory/ssh-agent@v0.8.0
|
||||
if: ${{ inputs.include-iap }}
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
|
||||
|
||||
- name: Override iap package with stub
|
||||
if: ${{ !inputs.include-iap }}
|
||||
run: |
|
||||
echo "\ndependency_overrides:\n m3_lightmeter_iap:\n path: iap" >> pubspec.yaml
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
|
17
.github/workflows/create_release.yml
vendored
17
.github/workflows/create_release.yml
vendored
|
@ -31,6 +31,10 @@ on:
|
|||
type: boolean
|
||||
description: Create Google Play release
|
||||
default: true
|
||||
include-iap:
|
||||
type: boolean
|
||||
description: Include IAP package
|
||||
default: true
|
||||
|
||||
env:
|
||||
BUILD_ARGS: --release --flavor prod --dart-define cameraPreviewAspectRatio=240/320 -t lib/main_prod.dart
|
||||
|
@ -38,10 +42,21 @@ env:
|
|||
jobs:
|
||||
build:
|
||||
name: Build .apk & .aab
|
||||
if: ${{ inputs.github-release }} || ${{ inputs.google-play-release }}
|
||||
if: ${{ inputs.github-release || inputs.google-play-release }}
|
||||
runs-on: macos-11
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Connect private iap package
|
||||
uses: webfactory/ssh-agent@v0.8.0
|
||||
if: ${{ inputs.include-iap }}
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
|
||||
|
||||
- name: Override iap package with stub
|
||||
if: ${{ !inputs.include-iap }}
|
||||
run: |
|
||||
echo "\ndependency_overrides:\n m3_lightmeter_iap:\n path: iap" >> pubspec.yaml
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
|
20
.github/workflows/pr_check.yml
vendored
20
.github/workflows/pr_check.yml
vendored
|
@ -11,13 +11,27 @@ on:
|
|||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
env:
|
||||
# Stub iap package if this worlflow is running from the PR from a fork
|
||||
STUB_IAP: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
|
||||
|
||||
jobs:
|
||||
analyze_and_test:
|
||||
name: Analyze & test
|
||||
runs-on: macos-11
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Connect private iap package
|
||||
uses: webfactory/ssh-agent@v0.8.0
|
||||
if: !env.STUB_IAP
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
|
||||
|
||||
- name: Override iap package with stub
|
||||
if: env.STUB_IAP
|
||||
run: |
|
||||
echo "\ndependency_overrides:\n m3_lightmeter_iap:\n path: iap" >> pubspec.yaml
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
@ -25,7 +39,7 @@ jobs:
|
|||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: '3.10.0'
|
||||
flutter-version: "3.10.0"
|
||||
|
||||
- name: Prepare flutter project
|
||||
run: |
|
||||
|
@ -37,4 +51,4 @@ jobs:
|
|||
run: flutter analyze lib --fatal-infos
|
||||
|
||||
- name: Run tests
|
||||
run: flutter test
|
||||
run: flutter test
|
||||
|
|
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
@ -31,6 +31,7 @@
|
|||
{
|
||||
"name": "prod (android)",
|
||||
"request": "launch",
|
||||
//"flutterMode": "release",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"--flavor",
|
||||
|
@ -43,6 +44,7 @@
|
|||
{
|
||||
"name": "prod (ios)",
|
||||
"request": "launch",
|
||||
//"flutterMode": "release",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"--flavor",
|
||||
|
|
19
README.md
19
README.md
|
@ -39,7 +39,22 @@ Out of the box Firebase Crashlytics won't work. If you want to add Crashlytics t
|
|||
|
||||
### 3. Get packages
|
||||
|
||||
Fetch all the neccessary dependencies and generate translation files by running the following commands:
|
||||
As part of the app's functionallity is in the private repo, you have to replace these lines in _pubspec.yaml_:
|
||||
|
||||
```yaml
|
||||
m3_lightmeter_iap:
|
||||
git:
|
||||
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
||||
ref: main
|
||||
```
|
||||
with these:
|
||||
```yaml
|
||||
m3_lightmeter_iap:
|
||||
path: iap
|
||||
```
|
||||
and run `flutter pub get` from the _iap/_ folder.
|
||||
|
||||
Then you can fetch all the neccessary dependencies and generate translation files by running the following commands:
|
||||
```console
|
||||
flutter pub get
|
||||
flutter pub run intl_utils:generate
|
||||
|
@ -69,4 +84,4 @@ Apple does not provide API for reading Lux stream form the ambient light sensor.
|
|||
|
||||
## Volume buttons action
|
||||
|
||||
This can be [implemented](https://stackoverflow.com/questions/70161271/ios-override-hardware-volume-buttons-same-as-zello) but the app will be rejected due to [2.5.9](https://developer.apple.com/app-store/review/guidelines/#software-requirements)
|
||||
This can be [implemented](https://stackoverflow.com/questions/70161271/ios-override-hardware-volume-buttons-same-as-zello) but the app will be rejected due to [2.5.9](https://developer.apple.com/app-store/review/guidelines/#software-requirements)
|
||||
|
|
36
iap/.gitignore
vendored
Normal file
36
iap/.gitignore
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
|
||||
.fvm/
|
||||
*.properties
|
||||
ios/Flutter/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
10
iap/.metadata
Normal file
10
iap/.metadata
Normal file
|
@ -0,0 +1,10 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: 9944297138845a94256f1cf37beb88ff9a8e811a
|
||||
channel: stable
|
||||
|
||||
project_type: package
|
1
iap/LICENSE
Normal file
1
iap/LICENSE
Normal file
|
@ -0,0 +1 @@
|
|||
TODO: Add your license here.
|
4
iap/analysis_options.yaml
Normal file
4
iap/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
30
iap/lib/m3_lightmeter_iap.dart
Normal file
30
iap/lib/m3_lightmeter_iap.dart
Normal file
|
@ -0,0 +1,30 @@
|
|||
library m3_lightmeter_iap;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:m3_lightmeter_iap/src/providers/equipment_profile_provider.dart';
|
||||
import 'package:m3_lightmeter_iap/src/providers/iap_products_provider.dart';
|
||||
|
||||
export 'src/data/models/iap_product.dart';
|
||||
|
||||
export 'src/providers/equipment_profile_provider.dart' hide EquipmentProfilesAspect;
|
||||
export 'src/providers/iap_products_provider.dart';
|
||||
|
||||
class IAPProviders extends StatelessWidget {
|
||||
final Object sharedPreferences;
|
||||
final Widget child;
|
||||
|
||||
const IAPProviders({
|
||||
required this.sharedPreferences,
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IAPProductsProvider(
|
||||
child: EquipmentProfileProvider(
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
5
iap/lib/src/data/models/iap_product.dart
Normal file
5
iap/lib/src/data/models/iap_product.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
enum IAPProductType { paidFeatures }
|
||||
|
||||
class IAPProduct {
|
||||
IAPProduct();
|
||||
}
|
79
iap/lib/src/providers/equipment_profile_provider.dart
Normal file
79
iap/lib/src/providers/equipment_profile_provider.dart
Normal file
|
@ -0,0 +1,79 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class EquipmentProfileProvider extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const EquipmentProfileProvider({required this.child, super.key});
|
||||
|
||||
static EquipmentProfileProviderState of(BuildContext context) {
|
||||
return context.findAncestorStateOfType<EquipmentProfileProviderState>()!;
|
||||
}
|
||||
|
||||
@override
|
||||
State<EquipmentProfileProvider> createState() => EquipmentProfileProviderState();
|
||||
}
|
||||
|
||||
class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
|
||||
static const EquipmentProfile _defaultProfile = EquipmentProfile(
|
||||
id: '',
|
||||
name: '',
|
||||
apertureValues: ApertureValue.values,
|
||||
ndValues: NdValue.values,
|
||||
shutterSpeedValues: ShutterSpeedValue.values,
|
||||
isoValues: IsoValue.values,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EquipmentProfiles(
|
||||
profiles: const [_defaultProfile],
|
||||
selected: _defaultProfile,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void setProfile(EquipmentProfile data) {}
|
||||
|
||||
void addProfile(String name) {}
|
||||
|
||||
void updateProdile(EquipmentProfile data) {}
|
||||
|
||||
void deleteProfile(EquipmentProfile data) {}
|
||||
}
|
||||
|
||||
enum EquipmentProfilesAspect { list, selected }
|
||||
|
||||
class EquipmentProfiles extends InheritedModel<EquipmentProfilesAspect> {
|
||||
const EquipmentProfiles({
|
||||
super.key,
|
||||
required this.profiles,
|
||||
required this.selected,
|
||||
required super.child,
|
||||
});
|
||||
|
||||
final List<EquipmentProfile> profiles;
|
||||
final EquipmentProfile selected;
|
||||
|
||||
static List<EquipmentProfile> of(BuildContext context) {
|
||||
return InheritedModel.inheritFrom<EquipmentProfiles>(
|
||||
context,
|
||||
aspect: EquipmentProfilesAspect.list,
|
||||
)!
|
||||
.profiles;
|
||||
}
|
||||
|
||||
static EquipmentProfile selectedOf(BuildContext context) {
|
||||
return InheritedModel.inheritFrom<EquipmentProfiles>(
|
||||
context,
|
||||
aspect: EquipmentProfilesAspect.selected,
|
||||
)!
|
||||
.selected;
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(EquipmentProfiles oldWidget) => false;
|
||||
|
||||
@override
|
||||
bool updateShouldNotifyDependent(EquipmentProfiles oldWidget, Set<EquipmentProfilesAspect> dependencies) => false;
|
||||
}
|
47
iap/lib/src/providers/iap_products_provider.dart
Normal file
47
iap/lib/src/providers/iap_products_provider.dart
Normal file
|
@ -0,0 +1,47 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:m3_lightmeter_iap/src/data/models/iap_product.dart';
|
||||
|
||||
class IAPProductsProvider extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const IAPProductsProvider({required this.child, super.key});
|
||||
|
||||
static IAPProductsProviderState of(BuildContext context) {
|
||||
return context.findAncestorStateOfType<IAPProductsProviderState>()!;
|
||||
}
|
||||
|
||||
@override
|
||||
State<IAPProductsProvider> createState() => IAPProductsProviderState();
|
||||
}
|
||||
|
||||
class IAPProductsProviderState extends State<IAPProductsProvider> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IAPProducts(
|
||||
products: const [],
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> buy(IAPProductType type) async {}
|
||||
}
|
||||
|
||||
class IAPProducts extends InheritedModel<IAPProductType> {
|
||||
final List<IAPProduct> products;
|
||||
|
||||
const IAPProducts({
|
||||
required this.products,
|
||||
required super.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
static IAPProduct? of(BuildContext context, IAPProductType type) => null;
|
||||
|
||||
static bool isPurchased(BuildContext context, IAPProductType type) => false;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(IAPProducts oldWidget) => false;
|
||||
|
||||
@override
|
||||
bool updateShouldNotifyDependent(covariant IAPProducts oldWidget, Set<IAPProductType> dependencies) => false;
|
||||
}
|
25
iap/pubspec.yaml
Normal file
25
iap/pubspec.yaml
Normal file
|
@ -0,0 +1,25 @@
|
|||
name: m3_lightmeter_iap
|
||||
description: IAP stubs for the M3 Lightmeter app.
|
||||
version: 0.2.0
|
||||
publish_to: 'none'
|
||||
|
||||
environment:
|
||||
sdk: '>=2.19.2 <3.0.0'
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
m3_lightmeter_resources:
|
||||
git:
|
||||
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
||||
ref: main
|
||||
shared_preferences: 2.2.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
|
@ -3,7 +3,7 @@
|
|||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 51;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
|
@ -237,6 +237,7 @@
|
|||
};
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
|
@ -268,6 +269,7 @@
|
|||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
|
@ -371,7 +373,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 74JQ9DBXY6;
|
||||
DEVELOPMENT_TEAM = 489Z6UQMGN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
@ -500,7 +502,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 74JQ9DBXY6;
|
||||
DEVELOPMENT_TEAM = 489Z6UQMGN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
@ -523,7 +525,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 74JQ9DBXY6;
|
||||
DEVELOPMENT_TEAM = 489Z6UQMGN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
@ -600,7 +602,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 74JQ9DBXY6;
|
||||
DEVELOPMENT_TEAM = 489Z6UQMGN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
@ -675,7 +677,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 74JQ9DBXY6;
|
||||
DEVELOPMENT_TEAM = 489Z6UQMGN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
@ -747,7 +749,7 @@
|
|||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 74JQ9DBXY6;
|
||||
DEVELOPMENT_TEAM = 489Z6UQMGN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
|
|
@ -10,11 +10,11 @@ import 'package:lightmeter/data/shared_prefs_service.dart';
|
|||
import 'package:lightmeter/data/volume_events_service.dart';
|
||||
import 'package:lightmeter/environment.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
||||
import 'package:lightmeter/providers/services_provider.dart';
|
||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||
import 'package:lightmeter/screens/metering/flow_metering.dart';
|
||||
import 'package:lightmeter/screens/settings/flow_settings.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
@ -32,16 +32,18 @@ class Application extends StatelessWidget {
|
|||
]),
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.data != null) {
|
||||
return ServicesProvider(
|
||||
caffeineService: const CaffeineService(),
|
||||
environment: env.copyWith(hasLightSensor: snapshot.data![1] as bool),
|
||||
hapticsService: const HapticsService(),
|
||||
lightSensorService: const LightSensorService(LocalPlatform()),
|
||||
permissionsService: const PermissionsService(),
|
||||
userPreferencesService: UserPreferencesService(snapshot.data![0] as SharedPreferences),
|
||||
volumeEventsService: const VolumeEventsService(LocalPlatform()),
|
||||
child: UserPreferencesProvider(
|
||||
child: EquipmentProfileProvider(
|
||||
return IAPProviders(
|
||||
sharedPreferences: snapshot.data![0] as SharedPreferences,
|
||||
child: ServicesProvider(
|
||||
caffeineService: const CaffeineService(),
|
||||
environment: env.copyWith(hasLightSensor: snapshot.data![1] as bool),
|
||||
hapticsService: const HapticsService(),
|
||||
lightSensorService: const LightSensorService(LocalPlatform()),
|
||||
permissionsService: const PermissionsService(),
|
||||
userPreferencesService:
|
||||
UserPreferencesService(snapshot.data![0] as SharedPreferences),
|
||||
volumeEventsService: const VolumeEventsService(LocalPlatform()),
|
||||
child: UserPreferencesProvider(
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final theme = UserPreferencesProvider.themeOf(context);
|
||||
|
|
|
@ -8,4 +8,16 @@ class ExposurePair {
|
|||
|
||||
@override
|
||||
String toString() => '$aperture - $shutterSpeed';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other.runtimeType != runtimeType) return false;
|
||||
return other is ExposurePair &&
|
||||
other.aperture == aperture &&
|
||||
other.shutterSpeed == shutterSpeed;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(aperture, shutterSpeed, runtimeType);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ double log10polynomian(
|
|||
) =>
|
||||
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
|
||||
///
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
enum MeteringScreenLayoutFeature { extremeExposurePairs, filmPicker, histogram }
|
||||
enum MeteringScreenLayoutFeature {
|
||||
extremeExposurePairs,
|
||||
filmPicker,
|
||||
histogram,
|
||||
equipmentProfiles,
|
||||
}
|
||||
|
||||
typedef MeteringScreenLayoutConfig = Map<MeteringScreenLayoutFeature, bool>;
|
||||
|
||||
extension MeteringScreenLayoutConfigJson on MeteringScreenLayoutConfig {
|
||||
static MeteringScreenLayoutConfig fromJson(Map<String, dynamic> data) =>
|
||||
static MeteringScreenLayoutConfig fromJson(Map<String, dynamic> data) =>
|
||||
<MeteringScreenLayoutFeature, bool>{
|
||||
for (final f in MeteringScreenLayoutFeature.values)
|
||||
f: data[f.index.toString()] as bool? ?? true
|
||||
|
|
|
@ -95,6 +95,7 @@ class UserPreferencesService {
|
|||
);
|
||||
} else {
|
||||
return {
|
||||
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||
MeteringScreenLayoutFeature.extremeExposurePairs: true,
|
||||
MeteringScreenLayoutFeature.filmPicker: true,
|
||||
MeteringScreenLayoutFeature.histogram: true,
|
||||
|
@ -147,10 +148,4 @@ class UserPreferencesService {
|
|||
orElse: () => Film.values.first,
|
||||
);
|
||||
set film(Film value) => _sharedPreferences.setString(filmKey, value.name);
|
||||
|
||||
String get selectedEquipmentProfileId => ''; // coverage:ignore-line
|
||||
set selectedEquipmentProfileId(String id) {} // coverage:ignore-line
|
||||
|
||||
List<EquipmentProfile> get equipmentProfiles => []; // coverage:ignore-line
|
||||
set equipmentProfiles(List<EquipmentProfile> profiles) {} // coverage:ignore-line
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
class FeaturesConfig {
|
||||
static const bool equipmentProfilesEnabled = false;
|
||||
}
|
|
@ -54,6 +54,7 @@
|
|||
"isoValuesFilterDescription": "Select the ISO values to display. These may be your most commonly used values or those supported by your camera.",
|
||||
"equipmentProfile": "Equipment profile",
|
||||
"equipmentProfiles": "Equipment profiles",
|
||||
"tapToAdd": "Tap to add",
|
||||
"general": "General",
|
||||
"keepScreenOn": "Keep screen on",
|
||||
"haptics": "Haptics",
|
||||
|
@ -86,4 +87,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
"film": "Pellicule",
|
||||
"equipment": "Équipement",
|
||||
"equipmentProfileName": "Nom du profil de l'équipement",
|
||||
"tapToAdd": "Appuie pour ajouter",
|
||||
"equipmentProfileNameHint": "Praktica MTL5B",
|
||||
"equipmentProfileAllValues": "Tout",
|
||||
"apertureValues": "Valeurs Aperture",
|
||||
|
@ -86,4 +87,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"isoValuesFilterDescription": "Выберите значения ISO для отображения. Это может быть наиболее часто используемые значения или значения, поддерживаемые вашей камерой.",
|
||||
"equipmentProfile": "Оборудование",
|
||||
"equipmentProfiles": "Профили оборудования",
|
||||
"tapToAdd": "Нажмите, чтобы добавить",
|
||||
"general": "Общие",
|
||||
"keepScreenOn": "Запрет блокировки",
|
||||
"haptics": "Вибрация",
|
||||
|
@ -86,4 +87,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"isoValuesFilterDescription": "选择要显示的 ISO。这些值可能是您最常用的值,也可能是相机支持的值。",
|
||||
"equipmentProfile": "设备配置",
|
||||
"equipmentProfiles": "设备配置",
|
||||
"tapToAdd": "點擊添加",
|
||||
"general": "通用",
|
||||
"keepScreenOn": "保持屏幕常亮",
|
||||
"haptics": "震动",
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/providers/services_provider.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
// TODO(@vodemn): This will be removed in #89
|
||||
class EquipmentProfileProvider extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const EquipmentProfileProvider({required this.child, super.key});
|
||||
|
||||
static EquipmentProfileProviderState of(BuildContext context) {
|
||||
return context.findAncestorStateOfType<EquipmentProfileProviderState>()!;
|
||||
}
|
||||
|
||||
@override
|
||||
State<EquipmentProfileProvider> createState() => EquipmentProfileProviderState();
|
||||
}
|
||||
|
||||
class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
|
||||
static const EquipmentProfile _defaultProfile = EquipmentProfile(
|
||||
id: '',
|
||||
name: '',
|
||||
apertureValues: ApertureValue.values,
|
||||
ndValues: NdValue.values,
|
||||
shutterSpeedValues: ShutterSpeedValue.values,
|
||||
isoValues: IsoValue.values,
|
||||
);
|
||||
|
||||
List<EquipmentProfile> _customProfiles = [];
|
||||
String _selectedId = '';
|
||||
|
||||
EquipmentProfile get _selectedProfile => _customProfiles.firstWhere(
|
||||
(e) => e.id == _selectedId,
|
||||
orElse: () {
|
||||
ServicesProvider.of(context).userPreferencesService.selectedEquipmentProfileId =
|
||||
_defaultProfile.id;
|
||||
return _defaultProfile;
|
||||
},
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedId = ServicesProvider.of(context).userPreferencesService.selectedEquipmentProfileId;
|
||||
_customProfiles = ServicesProvider.of(context).userPreferencesService.equipmentProfiles;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EquipmentProfiles(
|
||||
profiles: [_defaultProfile] + _customProfiles,
|
||||
selected: _selectedProfile,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void setProfile(EquipmentProfile data) {
|
||||
setState(() {
|
||||
_selectedId = data.id;
|
||||
});
|
||||
ServicesProvider.of(context).userPreferencesService.selectedEquipmentProfileId =
|
||||
_selectedProfile.id;
|
||||
}
|
||||
|
||||
/// Creates a default equipment profile
|
||||
void addProfile(String name) {
|
||||
_customProfiles.add(
|
||||
EquipmentProfile(
|
||||
id: const Uuid().v1(),
|
||||
name: name,
|
||||
apertureValues: ApertureValue.values,
|
||||
ndValues: NdValue.values,
|
||||
shutterSpeedValues: ShutterSpeedValue.values,
|
||||
isoValues: IsoValue.values,
|
||||
),
|
||||
);
|
||||
_refreshSavedProfiles();
|
||||
}
|
||||
|
||||
void updateProdile(EquipmentProfile data) {
|
||||
final indexToUpdate = _customProfiles.indexWhere((element) => element.id == data.id);
|
||||
if (indexToUpdate >= 0) {
|
||||
_customProfiles[indexToUpdate] = data;
|
||||
_refreshSavedProfiles();
|
||||
}
|
||||
}
|
||||
|
||||
void deleteProfile(EquipmentProfile data) {
|
||||
_customProfiles.remove(data);
|
||||
_refreshSavedProfiles();
|
||||
}
|
||||
|
||||
void _refreshSavedProfiles() {
|
||||
ServicesProvider.of(context).userPreferencesService.equipmentProfiles = _customProfiles;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from #89
|
||||
enum EquipmentProfilesAspect { list, selected }
|
||||
|
||||
class EquipmentProfiles extends InheritedModel<EquipmentProfilesAspect> {
|
||||
const EquipmentProfiles({
|
||||
super.key,
|
||||
required this.profiles,
|
||||
required this.selected,
|
||||
required super.child,
|
||||
});
|
||||
|
||||
final List<EquipmentProfile> profiles;
|
||||
final EquipmentProfile selected;
|
||||
|
||||
static List<EquipmentProfile> of(BuildContext context) {
|
||||
return InheritedModel.inheritFrom<EquipmentProfiles>(
|
||||
context,
|
||||
aspect: EquipmentProfilesAspect.list,
|
||||
)!
|
||||
.profiles;
|
||||
}
|
||||
|
||||
static EquipmentProfile selectedOf(BuildContext context) {
|
||||
return InheritedModel.inheritFrom<EquipmentProfiles>(
|
||||
context,
|
||||
aspect: EquipmentProfilesAspect.selected,
|
||||
)!
|
||||
.selected;
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(EquipmentProfiles oldWidget) => false;
|
||||
|
||||
@override
|
||||
bool updateShouldNotifyDependent(
|
||||
EquipmentProfiles oldWidget,
|
||||
Set<EquipmentProfilesAspect> dependencies,
|
||||
) =>
|
||||
false;
|
||||
}
|
|
@ -14,7 +14,6 @@ class Dimens {
|
|||
static const double grid48 = 48;
|
||||
static const double grid56 = 56;
|
||||
static const double grid72 = 72;
|
||||
static const double grid168 = 168;
|
||||
|
||||
static const double paddingS = 8;
|
||||
static const double paddingM = 16;
|
||||
|
@ -30,6 +29,8 @@ class Dimens {
|
|||
static const double enabledOpacity = 1.0;
|
||||
static const double disabledOpacity = 0.38;
|
||||
|
||||
static const double sliverAppBarExpandedHeight = 168;
|
||||
|
||||
// TopBar
|
||||
static const double readingContainerDoubleValueHeight = 128;
|
||||
static const double readingContainerSingleValueHeight = 76;
|
||||
|
|
|
@ -18,7 +18,7 @@ import 'package:lightmeter/screens/metering/components/camera_container/event_co
|
|||
import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart';
|
||||
import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/ev_source_base/bloc_base_ev_source.dart';
|
||||
import 'package:lightmeter/utils/log_2.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraContainerState> {
|
||||
final MeteringInteractor _meteringInteractor;
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:flutter_bloc/flutter_bloc.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/features.dart';
|
||||
import 'package:lightmeter/platform_config.dart';
|
||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
@ -110,7 +109,10 @@ class CameraContainer extends StatelessWidget {
|
|||
|
||||
double _meteringContainerHeight(BuildContext context) {
|
||||
double enabledFeaturesHeight = 0;
|
||||
if (FeaturesConfig.equipmentProfilesEnabled) {
|
||||
if (UserPreferencesProvider.meteringScreenFeatureOf(
|
||||
context,
|
||||
MeteringScreenLayoutFeature.equipmentProfiles,
|
||||
)) {
|
||||
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
|
||||
enabledFeaturesHeight += Dimens.paddingS;
|
||||
}
|
||||
|
@ -133,7 +135,7 @@ class CameraContainer extends StatelessWidget {
|
|||
}
|
||||
|
||||
double _cameraPreviewHeight(BuildContext context) {
|
||||
return ((MediaQuery.of(context).size.width - Dimens.grid8 - 2 * Dimens.paddingM) / 2) /
|
||||
return ((MediaQuery.sizeOf(context).width - Dimens.grid8 - 2 * Dimens.paddingM) / 2) /
|
||||
PlatformConfig.cameraPreviewAspectRatio;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import 'package:lightmeter/screens/metering/communication/state_communication_me
|
|||
import 'package:lightmeter/screens/metering/components/light_sensor_container/event_container_light_sensor.dart';
|
||||
import 'package:lightmeter/screens/metering/components/light_sensor_container/state_container_light_sensor.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/ev_source_base/bloc_base_ev_source.dart';
|
||||
import 'package:lightmeter/utils/log_2.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class LightSensorContainerBloc
|
||||
extends EvSourceBlocBase<LightSensorContainerEvent, LightSensorContainerState> {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/components/empty_exposure_pairs_list/widget_list_exposure_pairs_empty.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';
|
||||
|
||||
class ExposurePairsList extends StatelessWidget {
|
||||
final List<ExposurePair> exposurePairs;
|
||||
|
@ -15,7 +16,10 @@ class ExposurePairsList extends StatelessWidget {
|
|||
return AnimatedSwitcher(
|
||||
duration: Dimens.switchDuration,
|
||||
child: exposurePairs.isEmpty
|
||||
? const EmptyExposurePairsList()
|
||||
? IconPlaceholder(
|
||||
icon: Icons.not_interested,
|
||||
text: S.of(context).noExposurePairs,
|
||||
)
|
||||
: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
|
|
|
@ -2,13 +2,12 @@ import 'package:flutter/material.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/features.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package: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/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 ReadingsContainer extends StatelessWidget {
|
||||
|
@ -38,7 +37,10 @@ class ReadingsContainer extends StatelessWidget {
|
|||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (FeaturesConfig.equipmentProfilesEnabled) ...[
|
||||
if (UserPreferencesProvider.meteringScreenFeatureOf(
|
||||
context,
|
||||
MeteringScreenLayoutFeature.equipmentProfiles,
|
||||
)) ...[
|
||||
const _EquipmentProfilePicker(),
|
||||
const _InnerPadding(),
|
||||
],
|
||||
|
|
|
@ -6,7 +6,6 @@ import 'package:lightmeter/data/models/ev_source_type.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/providers/equipment_profile_provider.dart';
|
||||
import 'package:lightmeter/providers/services_provider.dart';
|
||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||
import 'package:lightmeter/screens/metering/bloc_metering.dart';
|
||||
|
@ -17,6 +16,7 @@ import 'package:lightmeter/screens/metering/event_metering.dart';
|
|||
import 'package:lightmeter/screens/metering/state_metering.dart';
|
||||
import 'package:lightmeter/screens/metering/utils/listener_metering_layout_feature.dart';
|
||||
import 'package:lightmeter/screens/metering/utils/listsner_equipment_profiles.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class MeteringScreen extends StatelessWidget {
|
||||
|
@ -31,7 +31,7 @@ class MeteringScreen extends StatelessWidget {
|
|||
children: [
|
||||
Expanded(
|
||||
child: BlocBuilder<MeteringBloc, MeteringState>(
|
||||
builder: (_, state) => _MeteringContainerBuidler(
|
||||
builder: (_, state) => MeteringContainerBuidler(
|
||||
ev: state is MeteringDataState ? state.ev : null,
|
||||
film: state.film,
|
||||
iso: state.iso,
|
||||
|
@ -80,15 +80,25 @@ class _InheritedListeners extends StatelessWidget {
|
|||
child: MeteringScreenLayoutFeatureListener(
|
||||
feature: MeteringScreenLayoutFeature.filmPicker,
|
||||
onDidChangeDependencies: (value) {
|
||||
if (!value) context.read<MeteringBloc>().add(const FilmChangedEvent(Film.other()));
|
||||
if (!value) {
|
||||
context.read<MeteringBloc>().add(const FilmChangedEvent(Film.other()));
|
||||
}
|
||||
},
|
||||
child: child,
|
||||
child: MeteringScreenLayoutFeatureListener(
|
||||
feature: MeteringScreenLayoutFeature.equipmentProfiles,
|
||||
onDidChangeDependencies: (value) {
|
||||
if (!value) {
|
||||
EquipmentProfileProvider.of(context).setProfile(EquipmentProfiles.of(context).first);
|
||||
}
|
||||
},
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MeteringContainerBuidler extends StatelessWidget {
|
||||
class MeteringContainerBuidler extends StatelessWidget {
|
||||
final double? ev;
|
||||
final Film film;
|
||||
final IsoValue iso;
|
||||
|
@ -97,7 +107,7 @@ class _MeteringContainerBuidler extends StatelessWidget {
|
|||
final ValueChanged<IsoValue> onIsoChanged;
|
||||
final ValueChanged<NdValue> onNdChanged;
|
||||
|
||||
const _MeteringContainerBuidler({
|
||||
const MeteringContainerBuidler({
|
||||
required this.ev,
|
||||
required this.film,
|
||||
required this.iso,
|
||||
|
@ -109,7 +119,14 @@ class _MeteringContainerBuidler extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final exposurePairs = ev != null ? buildExposureValues(context, ev!, film) : <ExposurePair>[];
|
||||
final exposurePairs = ev != null
|
||||
? buildExposureValues(
|
||||
ev!,
|
||||
UserPreferencesProvider.stopTypeOf(context),
|
||||
EquipmentProfiles.selectedOf(context),
|
||||
film,
|
||||
)
|
||||
: <ExposurePair>[];
|
||||
final fastest = exposurePairs.isNotEmpty ? exposurePairs.first : null;
|
||||
final slowest = exposurePairs.isNotEmpty ? exposurePairs.last : null;
|
||||
// Doubled build here when switching evSourceType. As new source bloc fires a new state on init
|
||||
|
@ -138,39 +155,28 @@ class _MeteringContainerBuidler extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
List<ExposurePair> buildExposureValues(BuildContext context, double ev, Film film) {
|
||||
@visibleForTesting
|
||||
static List<ExposurePair> buildExposureValues(
|
||||
double ev,
|
||||
StopType stopType,
|
||||
EquipmentProfile equipmentProfile,
|
||||
Film film,
|
||||
) {
|
||||
if (ev.isNaN || ev.isInfinite) {
|
||||
return List.empty();
|
||||
}
|
||||
|
||||
/// Depending on the `stopType` the exposure pairs list length is multiplied by 1,2 or 3
|
||||
final StopType stopType = UserPreferencesProvider.stopTypeOf(context);
|
||||
final int evSteps = (ev * (stopType.index + 1)).round();
|
||||
|
||||
final EquipmentProfile equipmentProfile = EquipmentProfiles.selectedOf(context);
|
||||
final List<ApertureValue> apertureValues =
|
||||
equipmentProfile.apertureValues.whereStopType(stopType);
|
||||
final List<ShutterSpeedValue> shutterSpeedValues =
|
||||
equipmentProfile.shutterSpeedValues.whereStopType(stopType);
|
||||
final apertureValues = ApertureValue.values.whereStopType(stopType);
|
||||
final shutterSpeedValues = ShutterSpeedValue.values.whereStopType(stopType);
|
||||
|
||||
/// Basically we use 1" shutter speed as an anchor point for building the exposure pairs list.
|
||||
/// But user can exclude this value from the list using custom equipment profile.
|
||||
/// So we have to restore the index of the anchor value.
|
||||
const ShutterSpeedValue anchorShutterSpeed = ShutterSpeedValue(1, false, StopType.full);
|
||||
int anchorIndex = shutterSpeedValues.indexOf(anchorShutterSpeed);
|
||||
if (anchorIndex < 0) {
|
||||
final filteredFullList = ShutterSpeedValue.values.whereStopType(stopType);
|
||||
final customListStartIndex = filteredFullList.indexOf(shutterSpeedValues.first);
|
||||
final fullListAnchor = filteredFullList.indexOf(anchorShutterSpeed);
|
||||
if (customListStartIndex < fullListAnchor) {
|
||||
/// This means, that user excluded anchor value at the end,
|
||||
/// i.e. all shutter speed values are shorter than 1".
|
||||
anchorIndex = fullListAnchor - customListStartIndex;
|
||||
} else {
|
||||
/// In case user excludes anchor value at the start,
|
||||
/// we can do no adjustment.
|
||||
}
|
||||
}
|
||||
const anchorShutterSpeed = ShutterSpeedValue(1, false, StopType.full);
|
||||
final int anchorIndex = shutterSpeedValues.indexOf(anchorShutterSpeed);
|
||||
final int evOffset = anchorIndex - evSteps;
|
||||
|
||||
late final int apertureOffset;
|
||||
|
@ -189,10 +195,11 @@ class _MeteringContainerBuidler extends StatelessWidget {
|
|||
) -
|
||||
max(apertureOffset, shutterSpeedOffset);
|
||||
|
||||
if (itemsCount < 0) {
|
||||
if (itemsCount <= 0) {
|
||||
return List.empty();
|
||||
}
|
||||
return List.generate(
|
||||
|
||||
final exposurePairs = List.generate(
|
||||
itemsCount,
|
||||
(index) => ExposurePair(
|
||||
apertureValues[index + apertureOffset],
|
||||
|
@ -200,5 +207,30 @@ class _MeteringContainerBuidler extends StatelessWidget {
|
|||
),
|
||||
growable: false,
|
||||
);
|
||||
|
||||
/// Full equipment profile, nothing to cut
|
||||
if (equipmentProfile.id == "") {
|
||||
return exposurePairs;
|
||||
}
|
||||
|
||||
final equipmentApertureValues = equipmentProfile.apertureValues.whereStopType(stopType);
|
||||
final equipmentShutterSpeedValues = equipmentProfile.shutterSpeedValues.whereStopType(stopType);
|
||||
|
||||
final startCutEV = max(
|
||||
exposurePairs.first.aperture.difference(equipmentApertureValues.first),
|
||||
exposurePairs.first.shutterSpeed.difference(equipmentShutterSpeedValues.first),
|
||||
);
|
||||
final endCutEV = max(
|
||||
equipmentApertureValues.last.difference(exposurePairs.last.aperture),
|
||||
equipmentShutterSpeedValues.last.difference(exposurePairs.last.shutterSpeed),
|
||||
);
|
||||
|
||||
final startCut = (startCutEV * (stopType.index + 1)).round().clamp(0, itemsCount);
|
||||
final endCut = (endCutEV * (stopType.index + 1)).round().clamp(0, itemsCount);
|
||||
|
||||
if (startCut > itemsCount - endCut) {
|
||||
return const [];
|
||||
}
|
||||
return exposurePairs.sublist(startCut, itemsCount - endCut);
|
||||
}
|
||||
}
|
||||
|
|
30
lib/screens/metering/utils/equipment_profile_listener.dart
Normal file
30
lib/screens/metering/utils/equipment_profile_listener.dart
Normal file
|
@ -0,0 +1,30 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class EquipmentProfileListener extends StatefulWidget {
|
||||
|
|
|
@ -71,6 +71,7 @@ class EquipmentProfileContainerState extends State<EquipmentProfileContainer>
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
title: Row(
|
||||
children: [
|
||||
_AnimatedNameLeading(controller: _controller),
|
||||
|
@ -163,7 +164,7 @@ class _AnimatedNameLeading extends AnimatedWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(right: _progress.value * Dimens.grid24),
|
||||
padding: EdgeInsets.only(right: _progress.value * Dimens.grid8),
|
||||
child: Icon(
|
||||
Icons.edit,
|
||||
size: _progress.value * Dimens.grid24,
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
||||
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.dart';
|
||||
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart';
|
||||
import 'package:lightmeter/screens/shared/icon_placeholder/widget_icon_placeholder.dart';
|
||||
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class EquipmentProfilesScreen extends StatefulWidget {
|
||||
|
@ -44,30 +45,38 @@ class _EquipmentProfilesScreenState extends State<EquipmentProfilesScreen> {
|
|||
icon: const Icon(Icons.close),
|
||||
),
|
||||
],
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => index > 0
|
||||
? Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
Dimens.paddingM,
|
||||
index == 0 ? Dimens.paddingM : 0,
|
||||
Dimens.paddingM,
|
||||
Dimens.paddingM,
|
||||
),
|
||||
child: EquipmentProfileContainer(
|
||||
key: profileContainersKeys[index],
|
||||
data: EquipmentProfiles.of(context)[index],
|
||||
onExpand: () => _keepExpandedAt(index),
|
||||
onUpdate: (profileData) => _updateProfileAt(profileData, index),
|
||||
onDelete: () => _removeProfileAt(index),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
childCount: profilesCount,
|
||||
),
|
||||
),
|
||||
],
|
||||
slivers: profilesCount == 1
|
||||
? [
|
||||
SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
child: _EquipmentProfilesListPlaceholder(onTap: _addProfile),
|
||||
)
|
||||
]
|
||||
: [
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => index > 0 // skip default
|
||||
? Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
Dimens.paddingM,
|
||||
index == 0 ? Dimens.paddingM : 0,
|
||||
Dimens.paddingM,
|
||||
Dimens.paddingM,
|
||||
),
|
||||
child: EquipmentProfileContainer(
|
||||
key: profileContainersKeys[index],
|
||||
data: EquipmentProfiles.of(context)[index],
|
||||
onExpand: () => _keepExpandedAt(index),
|
||||
onUpdate: (profileData) => _updateProfileAt(profileData, index),
|
||||
onDelete: () => _removeProfileAt(index),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
childCount: profilesCount,
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: SizedBox(height: MediaQuery.paddingOf(context).bottom)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -99,3 +108,32 @@ class _EquipmentProfilesScreenState extends State<EquipmentProfilesScreen> {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _EquipmentProfilesListPlaceholder extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _EquipmentProfilesListPlaceholder({required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: Dimens.sliverAppBarExpandedHeight),
|
||||
child: FractionallySizedBox(
|
||||
widthFactor: 1 / 1.618,
|
||||
child: Center(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(Dimens.paddingL),
|
||||
child: IconPlaceholder(
|
||||
icon: Icons.add,
|
||||
text: S.of(context).tapToAdd,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
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 {
|
||||
|
@ -8,13 +12,31 @@ class EquipmentProfilesListTile extends StatelessWidget {
|
|||
|
||||
@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: () {
|
||||
Navigator.of(context).push<EquipmentProfile>(
|
||||
MaterialPageRoute(builder: (_) => const EquipmentProfilesScreen()),
|
||||
);
|
||||
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,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -33,18 +33,10 @@ class _MeteringScreenLayoutFeaturesDialogState extends State<MeteringScreenLayou
|
|||
child: Text(S.of(context).meteringScreenLayoutHint),
|
||||
),
|
||||
const SizedBox(height: Dimens.grid16),
|
||||
...MeteringScreenLayoutFeature.values.map(
|
||||
(f) => SwitchListTile(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: Dimens.dialogTitlePadding.left),
|
||||
title: Text(_toStringLocalized(context, f)),
|
||||
value: _features[f]!,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_features.update(f, (_) => value);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
_featureListTile(MeteringScreenLayoutFeature.equipmentProfiles),
|
||||
_featureListTile(MeteringScreenLayoutFeature.extremeExposurePairs),
|
||||
_featureListTile(MeteringScreenLayoutFeature.filmPicker),
|
||||
_featureListTile(MeteringScreenLayoutFeature.histogram),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -65,8 +57,23 @@ class _MeteringScreenLayoutFeaturesDialogState extends State<MeteringScreenLayou
|
|||
);
|
||||
}
|
||||
|
||||
Widget _featureListTile(MeteringScreenLayoutFeature f) {
|
||||
return SwitchListTile(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: Dimens.dialogTitlePadding.left),
|
||||
title: Text(_toStringLocalized(context, f)),
|
||||
value: _features[f]!,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_features.update(f, (_) => value);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _toStringLocalized(BuildContext context, MeteringScreenLayoutFeature feature) {
|
||||
switch (feature) {
|
||||
case MeteringScreenLayoutFeature.equipmentProfiles:
|
||||
return S.of(context).equipmentProfiles;
|
||||
case MeteringScreenLayoutFeature.extremeExposurePairs:
|
||||
return S.of(context).meteringScreenFeatureExtremeExposurePairs;
|
||||
case MeteringScreenLayoutFeature.filmPicker:
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/features.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/equipment_profiles/widget_list_tile_equipment_profiles.dart';
|
||||
|
@ -18,7 +17,7 @@ class MeteringSettingsSection extends StatelessWidget {
|
|||
StopTypeListTile(),
|
||||
CalibrationListTile(),
|
||||
MeteringScreenLayoutListTile(),
|
||||
if (FeaturesConfig.equipmentProfilesEnabled) EquipmentProfilesListTile(),
|
||||
EquipmentProfilesListTile(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(child: SizedBox(height: MediaQuery.paddingOf(context).bottom)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,24 +1,30 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
class EmptyExposurePairsList extends StatelessWidget {
|
||||
const EmptyExposurePairsList({super.key});
|
||||
class IconPlaceholder extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String text;
|
||||
|
||||
const IconPlaceholder({
|
||||
required this.icon,
|
||||
required this.text,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width / 2),
|
||||
constraints: BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width / 2),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.not_interested,
|
||||
icon,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
const SizedBox(height: Dimens.grid8),
|
||||
Text(
|
||||
S.of(context).noExposurePairs,
|
||||
text,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
|
@ -24,7 +24,7 @@ class SliverScreen extends StatelessWidget {
|
|||
SliverAppBar(
|
||||
pinned: true,
|
||||
automaticallyImplyLeading: false,
|
||||
expandedHeight: Dimens.grid168,
|
||||
expandedHeight: Dimens.sliverAppBarExpandedHeight,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
centerTitle: false,
|
||||
titlePadding: const EdgeInsets.all(Dimens.paddingM),
|
||||
|
@ -39,7 +39,6 @@ class SliverScreen extends StatelessWidget {
|
|||
actions: appBarActions,
|
||||
),
|
||||
...slivers,
|
||||
SliverToBoxAdapter(child: SizedBox(height: MediaQuery.of(context).padding.bottom)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import 'dart:math';
|
||||
|
||||
double log2(num x) => log(x) / log(2);
|
24
pubspec.yaml
24
pubspec.yaml
|
@ -1,5 +1,5 @@
|
|||
name: lightmeter
|
||||
description: A new Flutter project.
|
||||
description: Lightmeter app inspired by Material 3 design system.
|
||||
publish_to: "none"
|
||||
version: 0.13.2+38
|
||||
|
||||
|
@ -11,10 +11,10 @@ dependencies:
|
|||
bloc_concurrency: 0.2.2
|
||||
camera: 0.10.5+2
|
||||
clipboard: 0.1.3
|
||||
dynamic_color: 1.6.5
|
||||
dynamic_color: 1.6.6
|
||||
exif: 3.1.4
|
||||
firebase_core: 2.13.0
|
||||
firebase_crashlytics: 3.3.1
|
||||
firebase_core: 2.14.0
|
||||
firebase_crashlytics: 3.3.3
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_bloc: 8.1.3
|
||||
|
@ -23,22 +23,26 @@ dependencies:
|
|||
intl: 0.18.0
|
||||
intl_utils: 2.8.2
|
||||
light_sensor: 2.0.2
|
||||
m3_lightmeter_iap:
|
||||
git:
|
||||
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
||||
ref: main
|
||||
m3_lightmeter_resources:
|
||||
git:
|
||||
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
||||
ref: main
|
||||
material_color_utilities: 0.2.0
|
||||
package_info_plus: 4.0.1
|
||||
permission_handler: 10.2.0
|
||||
package_info_plus: 4.0.2
|
||||
permission_handler: 10.4.3
|
||||
platform: 3.1.0
|
||||
shared_preferences: 2.1.1
|
||||
url_launcher: 6.1.11
|
||||
shared_preferences: 2.2.0
|
||||
url_launcher: 6.1.12
|
||||
uuid: 3.0.7
|
||||
vibration: 1.7.7
|
||||
vibration: 1.8.1
|
||||
|
||||
dev_dependencies:
|
||||
bloc_test: 9.1.3
|
||||
build_runner: ^2.1.7
|
||||
build_runner: 2.4.6
|
||||
flutter_launcher_icons: 0.11.0
|
||||
flutter_native_splash: 2.2.16
|
||||
flutter_test:
|
||||
|
|
|
@ -12,12 +12,31 @@ void main() {
|
|||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
},
|
||||
),
|
||||
{
|
||||
MeteringScreenLayoutFeature.extremeExposurePairs: true,
|
||||
MeteringScreenLayoutFeature.filmPicker: true,
|
||||
MeteringScreenLayoutFeature.histogram: true,
|
||||
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('Legacy (no histogram & equipment profiles)', () {
|
||||
expect(
|
||||
MeteringScreenLayoutConfigJson.fromJson(
|
||||
{
|
||||
'0': false,
|
||||
'1': false,
|
||||
},
|
||||
),
|
||||
{
|
||||
MeteringScreenLayoutFeature.extremeExposurePairs: false,
|
||||
MeteringScreenLayoutFeature.filmPicker: false,
|
||||
MeteringScreenLayoutFeature.histogram: true,
|
||||
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
@ -26,28 +45,32 @@ void main() {
|
|||
expect(
|
||||
MeteringScreenLayoutConfigJson.fromJson(
|
||||
{
|
||||
'0': true,
|
||||
'1': true,
|
||||
'0': false,
|
||||
'1': false,
|
||||
'2': false,
|
||||
},
|
||||
),
|
||||
{
|
||||
MeteringScreenLayoutFeature.extremeExposurePairs: true,
|
||||
MeteringScreenLayoutFeature.filmPicker: true,
|
||||
MeteringScreenLayoutFeature.histogram: true,
|
||||
MeteringScreenLayoutFeature.extremeExposurePairs: false,
|
||||
MeteringScreenLayoutFeature.filmPicker: false,
|
||||
MeteringScreenLayoutFeature.histogram: false,
|
||||
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test('toJson', () {
|
||||
test('toJson()', () {
|
||||
expect(
|
||||
{
|
||||
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||
MeteringScreenLayoutFeature.extremeExposurePairs: true,
|
||||
MeteringScreenLayoutFeature.filmPicker: true,
|
||||
MeteringScreenLayoutFeature.histogram: true,
|
||||
}.toJson(),
|
||||
{
|
||||
'3': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
|
|
|
@ -193,6 +193,7 @@ void main() {
|
|||
{
|
||||
MeteringScreenLayoutFeature.extremeExposurePairs: true,
|
||||
MeteringScreenLayoutFeature.filmPicker: true,
|
||||
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||
MeteringScreenLayoutFeature.histogram: true,
|
||||
},
|
||||
);
|
||||
|
@ -207,6 +208,7 @@ void main() {
|
|||
{
|
||||
MeteringScreenLayoutFeature.extremeExposurePairs: false,
|
||||
MeteringScreenLayoutFeature.filmPicker: true,
|
||||
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||
MeteringScreenLayoutFeature.histogram: true,
|
||||
},
|
||||
);
|
||||
|
@ -216,18 +218,19 @@ void main() {
|
|||
when(
|
||||
() => sharedPreferences.setString(
|
||||
UserPreferencesService.meteringScreenLayoutKey,
|
||||
"""{"0":false,"1":true,"2":true}""",
|
||||
"""{"0":false,"1":true,"2":true,"3":true}""",
|
||||
),
|
||||
).thenAnswer((_) => Future.value(true));
|
||||
service.meteringScreenLayout = {
|
||||
MeteringScreenLayoutFeature.extremeExposurePairs: false,
|
||||
MeteringScreenLayoutFeature.filmPicker: true,
|
||||
MeteringScreenLayoutFeature.histogram: true,
|
||||
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||
};
|
||||
verify(
|
||||
() => sharedPreferences.setString(
|
||||
UserPreferencesService.meteringScreenLayoutKey,
|
||||
"""{"0":false,"1":true,"2":true}""",
|
||||
"""{"0":false,"1":true,"2":true,"3":true}""",
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
|
966
test/screens/metering/screen_metering_test.dart
Normal file
966
test/screens/metering/screen_metering_test.dart
Normal file
|
@ -0,0 +1,966 @@
|
|||
import 'package:flutter_test/flutter_test.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:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
void main() {
|
||||
const defaultEquipmentProfile = EquipmentProfile(
|
||||
id: "",
|
||||
name: 'Default',
|
||||
apertureValues: ApertureValue.values,
|
||||
ndValues: NdValue.values,
|
||||
shutterSpeedValues: ShutterSpeedValue.values,
|
||||
isoValues: IsoValue.values,
|
||||
);
|
||||
|
||||
group('Empty list', () {
|
||||
List<ExposurePair> exposurePairsFull(double ev) => MeteringContainerBuidler.buildExposureValues(
|
||||
ev,
|
||||
StopType.full,
|
||||
defaultEquipmentProfile,
|
||||
const Film.other(),
|
||||
);
|
||||
|
||||
test('isNan', () {
|
||||
expect(exposurePairsFull(double.nan), const []);
|
||||
});
|
||||
|
||||
test('isInifinity', () {
|
||||
expect(exposurePairsFull(double.infinity), const []);
|
||||
});
|
||||
|
||||
test('Big ass number', () {
|
||||
expect(exposurePairsFull(23), const []);
|
||||
});
|
||||
});
|
||||
|
||||
group('Default equipment profile', () {
|
||||
group("StopType.full", () {
|
||||
List<ExposurePair> exposurePairsFull(double ev) =>
|
||||
MeteringContainerBuidler.buildExposureValues(
|
||||
ev,
|
||||
StopType.full,
|
||||
defaultEquipmentProfile,
|
||||
const Film.other(),
|
||||
);
|
||||
|
||||
test('EV 1', () {
|
||||
final exposurePairs = exposurePairsFull(1);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(5.6, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.3', () {
|
||||
final exposurePairs = exposurePairsFull(1.3);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(5.6, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.5', () {
|
||||
final exposurePairs = exposurePairsFull(1.5);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(4, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(8, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.7', () {
|
||||
final exposurePairs = exposurePairsFull(1.7);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(4, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(8, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 2', () {
|
||||
final exposurePairs = exposurePairsFull(2);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(4, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(8, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group("StopType.half", () {
|
||||
List<ExposurePair> exposurePairsFull(double ev) =>
|
||||
MeteringContainerBuidler.buildExposureValues(
|
||||
ev,
|
||||
StopType.half,
|
||||
defaultEquipmentProfile,
|
||||
const Film.other(),
|
||||
);
|
||||
|
||||
test('EV 1', () {
|
||||
final exposurePairs = exposurePairsFull(1);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(5.6, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.3', () {
|
||||
final exposurePairs = exposurePairsFull(1.3);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(3, true, StopType.half),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(6.7, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.5', () {
|
||||
final exposurePairs = exposurePairsFull(1.5);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(3, true, StopType.half),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(6.7, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.7', () {
|
||||
final exposurePairs = exposurePairsFull(1.7);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(3, true, StopType.half),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(6.7, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 2', () {
|
||||
final exposurePairs = exposurePairsFull(2);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(4, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(8, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group("StopType.third", () {
|
||||
List<ExposurePair> exposurePairsFull(double ev) =>
|
||||
MeteringContainerBuidler.buildExposureValues(
|
||||
ev,
|
||||
StopType.third,
|
||||
defaultEquipmentProfile,
|
||||
const Film.other(),
|
||||
);
|
||||
|
||||
test('EV 1', () {
|
||||
final exposurePairs = exposurePairsFull(1);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(5.6, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.3', () {
|
||||
final exposurePairs = exposurePairsFull(1.3);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(2.5, true, StopType.third),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(6.3, StopType.third),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.5', () {
|
||||
final exposurePairs = exposurePairsFull(1.5);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(3, true, StopType.third),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(7.1, StopType.third),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.7', () {
|
||||
final exposurePairs = exposurePairsFull(1.7);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(3, true, StopType.third),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(7.1, StopType.third),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 2', () {
|
||||
final exposurePairs = exposurePairsFull(2);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1, StopType.full),
|
||||
ShutterSpeedValue(4, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(8, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('Shutter speed 1/1000-1/2"', () {
|
||||
final equipmentProfile = EquipmentProfile(
|
||||
id: "1",
|
||||
name: 'Test1',
|
||||
apertureValues: ApertureValue.values,
|
||||
ndValues: NdValue.values,
|
||||
shutterSpeedValues: ShutterSpeedValue.values.sublist(
|
||||
ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(1000, true, StopType.full)),
|
||||
ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(2, true, StopType.full)) + 1,
|
||||
),
|
||||
isoValues: IsoValue.values,
|
||||
);
|
||||
|
||||
group("StopType.full", () {
|
||||
List<ExposurePair> exposurePairsFull(double ev) =>
|
||||
MeteringContainerBuidler.buildExposureValues(
|
||||
ev,
|
||||
StopType.full,
|
||||
equipmentProfile,
|
||||
const Film.other(),
|
||||
);
|
||||
|
||||
test('EV 1', () {
|
||||
final exposurePairs = exposurePairsFull(1);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.3', () {
|
||||
final exposurePairs = exposurePairsFull(1.3);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.5', () {
|
||||
final exposurePairs = exposurePairsFull(1.5);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(4, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.4, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.7', () {
|
||||
final exposurePairs = exposurePairsFull(1.7);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(4, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.4, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 2', () {
|
||||
final exposurePairs = exposurePairsFull(2);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(4, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.4, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group("StopType.half", () {
|
||||
List<ExposurePair> exposurePairsFull(double ev) =>
|
||||
MeteringContainerBuidler.buildExposureValues(
|
||||
ev,
|
||||
StopType.half,
|
||||
equipmentProfile,
|
||||
const Film.other(),
|
||||
);
|
||||
|
||||
test('EV 1', () {
|
||||
final exposurePairs = exposurePairsFull(1);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.3', () {
|
||||
final exposurePairs = exposurePairsFull(1.3);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(3, true, StopType.half),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.2, StopType.half),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.5', () {
|
||||
final exposurePairs = exposurePairsFull(1.5);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(3, true, StopType.half),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.2, StopType.half),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.7', () {
|
||||
final exposurePairs = exposurePairsFull(1.7);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(3, true, StopType.half),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.2, StopType.half),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 2', () {
|
||||
final exposurePairs = exposurePairsFull(2);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(4, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.4, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group("StopType.third", () {
|
||||
List<ExposurePair> exposurePairsFull(double ev) =>
|
||||
MeteringContainerBuidler.buildExposureValues(
|
||||
ev,
|
||||
StopType.third,
|
||||
equipmentProfile,
|
||||
const Film.other(),
|
||||
);
|
||||
|
||||
test('EV 1', () {
|
||||
final exposurePairs = exposurePairsFull(1);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.3', () {
|
||||
final exposurePairs = exposurePairsFull(1.3);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(2.5, true, StopType.third),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.1, StopType.third),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.5', () {
|
||||
final exposurePairs = exposurePairsFull(1.5);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(3, true, StopType.third),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.2, StopType.third),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.7', () {
|
||||
final exposurePairs = exposurePairsFull(1.7);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(3, true, StopType.third),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.2, StopType.third),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 2', () {
|
||||
final exposurePairs = exposurePairsFull(2);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.0, StopType.full),
|
||||
ShutterSpeedValue(4, true, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(1.4, StopType.full),
|
||||
ShutterSpeedValue(2, true, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('Shutter speed 2"-16"', () {
|
||||
final equipmentProfile = EquipmentProfile(
|
||||
id: "1",
|
||||
name: 'Test1',
|
||||
apertureValues: ApertureValue.values.sublist(4),
|
||||
ndValues: NdValue.values,
|
||||
shutterSpeedValues: ShutterSpeedValue.values.sublist(
|
||||
ShutterSpeedValue.values.indexOf(const ShutterSpeedValue(2, false, StopType.full)),
|
||||
),
|
||||
isoValues: IsoValue.values,
|
||||
);
|
||||
|
||||
group("StopType.full", () {
|
||||
List<ExposurePair> exposurePairsFull(double ev) =>
|
||||
MeteringContainerBuidler.buildExposureValues(
|
||||
ev,
|
||||
StopType.full,
|
||||
equipmentProfile,
|
||||
const Film.other(),
|
||||
);
|
||||
|
||||
test('EV 1', () {
|
||||
final exposurePairs = exposurePairsFull(1);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.0, StopType.full),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(5.6, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.3', () {
|
||||
final exposurePairs = exposurePairsFull(1.3);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.0, StopType.full),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(5.6, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.5', () {
|
||||
final exposurePairs = exposurePairsFull(1.5);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.8, StopType.full),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(8, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.7', () {
|
||||
final exposurePairs = exposurePairsFull(1.7);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.8, StopType.full),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(8, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 2', () {
|
||||
final exposurePairs = exposurePairsFull(2);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.8, StopType.full),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(8, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group("StopType.half", () {
|
||||
List<ExposurePair> exposurePairsFull(double ev) =>
|
||||
MeteringContainerBuidler.buildExposureValues(
|
||||
ev,
|
||||
StopType.half,
|
||||
equipmentProfile,
|
||||
const Film.other(),
|
||||
);
|
||||
|
||||
test('EV 1', () {
|
||||
final exposurePairs = exposurePairsFull(1);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.0, StopType.full),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(5.6, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.3', () {
|
||||
final exposurePairs = exposurePairsFull(1.3);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.4, StopType.half),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(6.7, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.5', () {
|
||||
final exposurePairs = exposurePairsFull(1.5);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.4, StopType.half),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(6.7, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.7', () {
|
||||
final exposurePairs = exposurePairsFull(1.7);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.4, StopType.half),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(6.7, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 2', () {
|
||||
final exposurePairs = exposurePairsFull(2);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.8, StopType.full),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(8, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group("StopType.third", () {
|
||||
List<ExposurePair> exposurePairsFull(double ev) =>
|
||||
MeteringContainerBuidler.buildExposureValues(
|
||||
ev,
|
||||
StopType.third,
|
||||
equipmentProfile,
|
||||
const Film.other(),
|
||||
);
|
||||
|
||||
test('EV 1', () {
|
||||
final exposurePairs = exposurePairsFull(1);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.0, StopType.full),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(5.6, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.3', () {
|
||||
final exposurePairs = exposurePairsFull(1.3);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.2, StopType.full),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(6.3, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.5', () {
|
||||
final exposurePairs = exposurePairsFull(1.5);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.4, StopType.full),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(7.1, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 1.7', () {
|
||||
final exposurePairs = exposurePairsFull(1.7);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.4, StopType.full),
|
||||
ShutterSpeedValue(2, false, StopType.third),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(7.1, StopType.third),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('EV 2', () {
|
||||
final exposurePairs = exposurePairsFull(2);
|
||||
expect(
|
||||
exposurePairs.first,
|
||||
const ExposurePair(
|
||||
ApertureValue(2.8, StopType.full),
|
||||
ShutterSpeedValue(2, false, StopType.full),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
exposurePairs.last,
|
||||
const ExposurePair(
|
||||
ApertureValue(8, StopType.full),
|
||||
ShutterSpeedValue(16, false, StopType.full),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue