diff --git a/.vscode/launch.json b/.vscode/launch.json
index 40dbfac..a222516 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -82,5 +82,20 @@
],
"program": "${workspaceFolder}/lib/main_dev.dart",
},
+ {
+ "name": "dev-simulator",
+ "request": "launch",
+ "type": "dart",
+ "flutterMode": "debug",
+ "args": [
+ "--flavor",
+ "dev",
+ "--dart-define",
+ "cameraPreviewAspectRatio=240/320",
+ "--dart-define",
+ "cameraStubImage=assets/camera_stub_image.jpg"
+ ],
+ "program": "${workspaceFolder}/lib/main_dev.dart",
+ },
],
}
\ No newline at end of file
diff --git a/PRIVACY_POLICY.md b/PRIVACY_POLICY.md
index 05710bd..d9b9cad 100644
--- a/PRIVACY_POLICY.md
+++ b/PRIVACY_POLICY.md
@@ -1,6 +1,6 @@
**Privacy Policy**
-I, Vodemn, built the Material Lightmeter app as a Free app. This app is provided at no cost and is intended for use as is.
+I, Vadim Turko, built the Material Lightmeter app as a Free app. This app is provided at no cost and is intended for use as is.
**Information Collection and Use**
@@ -20,7 +20,7 @@ This app contains links to other sites. If you click on a third-party link, you
I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page.
-This policy is effective as of 2023-02-24
+This policy is effective as of 2024-01-04
**Contact Us**
diff --git a/README.md b/README.md
index ef38b93..b5ef345 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
- [Backstory](#backstory)
- [Screenshots](#screenshots)
- [Development](#development)
-- [Contribution](#contribution)
+- [Support](#support)
- [iOS Limitations](#ios-limitations)
# Backstory
@@ -36,7 +36,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).
-### 3. Project setup
+### 2. Project setup
As part of the app's functionallity is in the private repo, you have to replace these lines in _pubspec.yaml_:
@@ -69,11 +69,11 @@ flutter pub get
flutter pub run intl_utils:generate
```
-### 4. (Optional) Install Firebase
+### 3. (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
+### 4. Build
#### Android
@@ -87,11 +87,11 @@ flutter build apk --release --flavor dev --dart-define cameraPreviewAspectRatio=
TBD
-# Contribution
+# Support
-To report a bug or suggest a new feature open a new [issue](https://github.com/vodemn/m3_lightmeter/issues).
+To report a bug or suggest a new feature open a new [issue](https://github.com/vodemn/m3_lightmeter/issues). To contribute to the project feel free to open a Pull Request, but you need to follow this [style guide](doc/style_guide.md).
-In case you want to help develop this project feel free to open a Pull Request, but you need to follow this [style guide](doc/style_guide.md).
+In case you have any other questions please contact me via [email](mailto:contact.vodemn@gmail.com?subject="Lightmeter").
# iOS Limitations
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 5ccd388..be85756 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -75,12 +75,13 @@ android {
flavorDimensions "app"
productFlavors {
dev {
- applicationId "com.vodemn.lightmeter.dev"
+ resValue "string", "app_name", "Lightmeter (DEV)"
dimension "app"
signingConfig signingConfigs.release
+ applicationIdSuffix ".dev"
}
prod {
- applicationId "com.vodemn.lightmeter"
+ resValue "string", "app_name", "Lightmeter"
dimension "app"
signingConfig signingConfigs.release
}
diff --git a/android/app/src/dev/ic_launcher-playstore.png b/android/app/src/dev/ic_launcher-playstore.png
new file mode 100644
index 0000000..00039fd
Binary files /dev/null and b/android/app/src/dev/ic_launcher-playstore.png differ
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/dev/res/mipmap-anydpi-v26/ic_launcher.xml
similarity index 100%
rename from android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
rename to android/app/src/dev/res/mipmap-anydpi-v26/ic_launcher.xml
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/dev/res/mipmap-anydpi-v26/ic_launcher_round.xml
similarity index 100%
rename from android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
rename to android/app/src/dev/res/mipmap-anydpi-v26/ic_launcher_round.xml
diff --git a/android/app/src/dev/res/mipmap-hdpi/ic_launcher.png b/android/app/src/dev/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..6dec4cd
Binary files /dev/null and b/android/app/src/dev/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/dev/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/dev/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..7fd4342
Binary files /dev/null and b/android/app/src/dev/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/dev/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/dev/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dd78094
Binary files /dev/null and b/android/app/src/dev/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/android/app/src/dev/res/mipmap-mdpi/ic_launcher.png b/android/app/src/dev/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..5169915
Binary files /dev/null and b/android/app/src/dev/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/dev/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/dev/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..c7d106b
Binary files /dev/null and b/android/app/src/dev/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/dev/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/dev/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..89c0f9f
Binary files /dev/null and b/android/app/src/dev/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/android/app/src/dev/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/dev/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..1da9378
Binary files /dev/null and b/android/app/src/dev/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/dev/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/dev/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..79cc56f
Binary files /dev/null and b/android/app/src/dev/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/dev/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/dev/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..61ea400
Binary files /dev/null and b/android/app/src/dev/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..f1c2f64
Binary files /dev/null and b/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..f73d67b
Binary files /dev/null and b/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..672e3e1
Binary files /dev/null and b/android/app/src/dev/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..a2e0a13
Binary files /dev/null and b/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..9203230
Binary files /dev/null and b/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..5711a66
Binary files /dev/null and b/android/app/src/dev/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/dev/res/values/ic_launcher_background.xml b/android/app/src/dev/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..58043be
--- /dev/null
+++ b/android/app/src/dev/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #212121
+
\ No newline at end of file
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 6dba1a5..a7d420e 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -3,7 +3,7 @@
package="com.vodemn.lightmeter">
-
-
-
-
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index de34bbe..0000000
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
deleted file mode 100644
index ae5b426..0000000
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 5ab8dd2..0000000
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index d49e5da..0000000
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
deleted file mode 100644
index b57a18c..0000000
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index f068022..0000000
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index afaeb3c..0000000
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
deleted file mode 100644
index f75d3c2..0000000
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index d7dd8be..0000000
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 20eff01..0000000
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
deleted file mode 100644
index 3dfccd0..0000000
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index 36e8d33..0000000
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 0110c49..0000000
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
deleted file mode 100644
index ac6e5bc..0000000
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index a2162bf..0000000
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/android/app/src/prod/ic_launcher-playstore.png b/android/app/src/prod/ic_launcher-playstore.png
new file mode 100644
index 0000000..4f082f8
Binary files /dev/null and b/android/app/src/prod/ic_launcher-playstore.png differ
diff --git a/android/app/src/prod/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/prod/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/android/app/src/prod/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/prod/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/prod/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/android/app/src/prod/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/prod/res/mipmap-hdpi/ic_launcher.png b/android/app/src/prod/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..b86b175
Binary files /dev/null and b/android/app/src/prod/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/prod/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/prod/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..1392a3b
Binary files /dev/null and b/android/app/src/prod/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/prod/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/prod/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..e791add
Binary files /dev/null and b/android/app/src/prod/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/android/app/src/prod/res/mipmap-mdpi/ic_launcher.png b/android/app/src/prod/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..31b5f50
Binary files /dev/null and b/android/app/src/prod/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/prod/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/prod/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..3399a7d
Binary files /dev/null and b/android/app/src/prod/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/prod/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/prod/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..e22fb78
Binary files /dev/null and b/android/app/src/prod/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/android/app/src/prod/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/prod/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..a2ace48
Binary files /dev/null and b/android/app/src/prod/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/prod/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/prod/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..c6e5d43
Binary files /dev/null and b/android/app/src/prod/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/prod/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/prod/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..db39634
Binary files /dev/null and b/android/app/src/prod/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2eb0bfb
Binary files /dev/null and b/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..9a5a827
Binary files /dev/null and b/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..fdd534d
Binary files /dev/null and b/android/app/src/prod/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..9d3e572
Binary files /dev/null and b/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..fe2a50c
Binary files /dev/null and b/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..74f9d28
Binary files /dev/null and b/android/app/src/prod/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/prod/res/values/ic_launcher_background.xml b/android/app/src/prod/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..58043be
--- /dev/null
+++ b/android/app/src/prod/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #212121
+
\ No newline at end of file
diff --git a/assets/README.md b/assets/README.md
new file mode 100644
index 0000000..decb62c
--- /dev/null
+++ b/assets/README.md
@@ -0,0 +1,11 @@
+# Assets
+
+## Launcher icons
+
+### Android
+
+Resources for Android are generated in Android Studio from the 512x512 source image as described in this [guide](https://developer.android.com/studio/write/create-app-icons).
+
+### iOS
+
+Resources for iOS are generated in XCode from the 1024x1024 source image as described in this [guide](https://developer.apple.com/documentation/xcode/configuring-your-app-icon).
diff --git a/assets/launcher_icon_circle.png b/assets/launcher_icon_circle.png
deleted file mode 100644
index ab23e26..0000000
Binary files a/assets/launcher_icon_circle.png and /dev/null differ
diff --git a/assets/launcher_icon_dev_1024.png b/assets/launcher_icon_dev_1024.png
new file mode 100644
index 0000000..0923f36
Binary files /dev/null and b/assets/launcher_icon_dev_1024.png differ
diff --git a/assets/launcher_icon_dev_512.png b/assets/launcher_icon_dev_512.png
new file mode 100644
index 0000000..1c234a0
Binary files /dev/null and b/assets/launcher_icon_dev_512.png differ
diff --git a/assets/launcher_icon_prod_1024.png b/assets/launcher_icon_prod_1024.png
new file mode 100755
index 0000000..a54026f
Binary files /dev/null and b/assets/launcher_icon_prod_1024.png differ
diff --git a/assets/launcher_icon_prod_512.png b/assets/launcher_icon_prod_512.png
new file mode 100644
index 0000000..706987b
Binary files /dev/null and b/assets/launcher_icon_prod_512.png differ
diff --git a/assets/launcher_icon_square.png b/assets/launcher_icon_square.png
deleted file mode 100644
index e8ac5f8..0000000
Binary files a/assets/launcher_icon_square.png and /dev/null differ
diff --git a/iap/lib/src/providers/iap_products_provider.dart b/iap/lib/src/providers/iap_products_provider.dart
index 4895fdf..9d381ae 100644
--- a/iap/lib/src/providers/iap_products_provider.dart
+++ b/iap/lib/src/providers/iap_products_provider.dart
@@ -6,8 +6,10 @@ class IAPProductsProvider extends StatefulWidget {
const IAPProductsProvider({required this.child, super.key});
- static IAPProductsProviderState of(BuildContext context) {
- return context.findAncestorStateOfType()!;
+ static IAPProductsProviderState of(BuildContext context) => IAPProductsProvider.maybeOf(context)!;
+
+ static IAPProductsProviderState? maybeOf(BuildContext context) {
+ return context.findAncestorStateOfType();
}
@override
@@ -54,8 +56,7 @@ class IAPProducts extends InheritedModel {
bool updateShouldNotify(IAPProducts oldWidget) => false;
@override
- bool updateShouldNotifyDependent(IAPProducts oldWidget, Set dependencies) =>
- false;
+ bool updateShouldNotifyDependent(IAPProducts oldWidget, Set dependencies) => false;
IAPProduct? _findProduct(IAPProductType type) {
try {
diff --git a/ios/Podfile b/ios/Podfile
index e9b0728..462df98 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -40,6 +40,12 @@ post_install do |installer|
# Start of the permission_handler configuration
target.build_configurations.each do |config|
+ # https://github.com/CocoaPods/CocoaPods/issues/12012
+ xcconfig_path = config.base_configuration_reference.real_path
+ xcconfig = File.read(xcconfig_path)
+ xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR")
+ File.open(xcconfig_path, "w") { |file| file << xcconfig_mod }
+
# Preprocessor definitions can be found in: https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 73ce339..0b4c62e 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -44,7 +44,7 @@
8C539F8FF42AB22E298D5A5E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
- 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 97C146EE1CF9000F007C117D /* Lightmeter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Lightmeter.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
@@ -95,7 +95,7 @@
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
- 97C146EE1CF9000F007C117D /* Runner.app */,
+ 97C146EE1CF9000F007C117D /* Lightmeter.app */,
);
name = Products;
sourceTree = "";
@@ -154,6 +154,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
45F53C083F2EA48EF231DA16 /* [CP] Embed Pods Frameworks */,
+ FF00F85CE432774850A0EDB7 /* [firebase_crashlytics] Crashlytics Upload Symbols */,
);
buildRules = (
);
@@ -161,7 +162,7 @@
);
name = Runner;
productName = Runner;
- productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+ productReference = 97C146EE1CF9000F007C117D /* Lightmeter.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@@ -242,6 +243,7 @@
files = (
);
inputPaths = (
+ "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
@@ -282,6 +284,29 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
+ FF00F85CE432774850A0EDB7 /* [firebase_crashlytics] Crashlytics Upload Symbols */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}\"",
+ "\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/\"",
+ "\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"",
+ "\"$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)\"",
+ "\"$(PROJECT_DIR)/firebase_app_id_file.json\"",
+ );
+ name = "[firebase_crashlytics] Crashlytics Upload Symbols";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"$PODS_ROOT/FirebaseCrashlytics/upload-symbols\" --flutter-project \"$PROJECT_DIR/firebase_app_id_file.json\" ";
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -370,18 +395,23 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-prod";
+ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = Lightmeter;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter;
- PRODUCT_NAME = "$(TARGET_NAME)";
+ PRODUCT_NAME = Lightmeter;
+ PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@@ -499,18 +529,23 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-prod";
+ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = Lightmeter;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter;
- PRODUCT_NAME = "$(TARGET_NAME)";
+ PRODUCT_NAME = Lightmeter;
+ PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -522,18 +557,23 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-prod";
+ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = Lightmeter;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter;
- PRODUCT_NAME = "$(TARGET_NAME)";
+ PRODUCT_NAME = Lightmeter;
+ PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@@ -599,18 +639,23 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev";
+ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = "Lightmeter (DEV)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter.dev;
- PRODUCT_NAME = "$(TARGET_NAME)";
+ PRODUCT_NAME = "Lightmeter (DEV)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -674,18 +719,23 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev";
+ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = "Lightmeter (DEV)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter.dev;
- PRODUCT_NAME = "$(TARGET_NAME)";
+ PRODUCT_NAME = "Lightmeter (DEV)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@@ -746,18 +796,23 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-dev";
+ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 489Z6UQMGN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = "Lightmeter (DEV)";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter.dev;
- PRODUCT_NAME = "$(TARGET_NAME)";
+ PRODUCT_NAME = "Lightmeter (DEV)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
diff --git a/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json
new file mode 100644
index 0000000..c89c0fc
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json
@@ -0,0 +1,14 @@
+{
+ "images" : [
+ {
+ "filename" : "Icon square (dev).png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon square (dev).png b/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon square (dev).png
new file mode 100644
index 0000000..0923f36
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Icon square (dev).png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Contents.json
new file mode 100644
index 0000000..e85412b
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Contents.json
@@ -0,0 +1,14 @@
+{
+ "images" : [
+ {
+ "filename" : "Icon square.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Icon square.png b/ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Icon square.png
new file mode 100644
index 0000000..a54026f
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-prod.appiconset/Icon square.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index d36b1fa..0000000
--- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,122 +0,0 @@
-{
- "images" : [
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "83.5x83.5",
- "idiom" : "ipad",
- "filename" : "Icon-App-83.5x83.5@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "1024x1024",
- "idiom" : "ios-marketing",
- "filename" : "Icon-App-1024x1024@1x.png",
- "scale" : "1x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
deleted file mode 100644
index 21fba1c..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
deleted file mode 100644
index 8836ffd..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
deleted file mode 100644
index 826dc2b..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
deleted file mode 100644
index ddb081d..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
deleted file mode 100644
index e9dcbdb..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
deleted file mode 100644
index d8669e5..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
deleted file mode 100644
index 6178967..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
deleted file mode 100644
index 826dc2b..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
deleted file mode 100644
index d865bad..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
deleted file mode 100644
index 7863936..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
deleted file mode 100644
index 37d3359..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
deleted file mode 100644
index cd9bbfe..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
deleted file mode 100644
index 687010b..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
deleted file mode 100644
index 284f7ce..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
deleted file mode 100644
index 7863936..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
deleted file mode 100644
index 2ea76d6..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
deleted file mode 100644
index e5c29c0..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
deleted file mode 100644
index b895962..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
deleted file mode 100644
index 16a1ae1..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
deleted file mode 100644
index 9debf8c..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
deleted file mode 100644
index e32e837..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/Contents.json b/ios/Runner/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 9b13e6e..f2e1bd9 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -1,51 +1,51 @@
-
- CADisableMinimumFrameDurationOnPhone
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleDisplayName
- Lightmeter
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- lightmeter
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- $(FLUTTER_BUILD_NAME)
- CFBundleSignature
- ????
- CFBundleVersion
- $(FLUTTER_BUILD_NUMBER)
- LSRequiresIPhoneOS
-
- UIApplicationSupportsIndirectInputEvents
-
- UILaunchStoryboardName
- LaunchScreen
- UIMainStoryboardFile
- Main
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UIViewControllerBasedStatusBarAppearance
-
- NSCameraUsageDescription
- Provide camera permissions in order to make measurements
-
-
\ No newline at end of file
+
+ CADisableMinimumFrameDurationOnPhone
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ $(PRODUCT_NAME)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ lightmeter
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UIApplicationSupportsIndirectInputEvents
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+ NSCameraUsageDescription
+ Provide camera permissions in order to make measurements
+
+
diff --git a/lib/application.dart b/lib/application.dart
index 68dd44a..15eba3a 100644
--- a/lib/application.dart
+++ b/lib/application.dart
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:lightmeter/data/models/supported_locale.dart';
import 'package:lightmeter/generated/l10n.dart';
+import 'package:lightmeter/platform_config.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';
@@ -17,13 +18,13 @@ class Application extends StatelessWidget {
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
- statusBarBrightness:
- systemIconsBrightness == Brightness.light ? Brightness.dark : Brightness.light,
+ statusBarBrightness: systemIconsBrightness == Brightness.light ? Brightness.dark : Brightness.light,
statusBarIconBrightness: systemIconsBrightness,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness: systemIconsBrightness,
),
child: MaterialApp(
+ debugShowCheckedModeBanner: !PlatformConfig.isTest,
theme: theme,
locale: Locale(UserPreferencesProvider.localeOf(context).intlName),
localizationsDelegates: const [
diff --git a/lib/application_wrapper.dart b/lib/application_wrapper.dart
index d28fbcf..ae7d1ae 100644
--- a/lib/application_wrapper.dart
+++ b/lib/application_wrapper.dart
@@ -30,7 +30,7 @@ class ApplicationWrapper extends StatelessWidget {
future: Future.wait([
SharedPreferences.getInstance(),
const LightSensorService(LocalPlatform()).hasSensor(),
- const RemoteConfigService().activeAndFetchFeatures(),
+ if (env.buildType != BuildType.dev) const RemoteConfigService().activeAndFetchFeatures(),
]),
builder: (_, snapshot) {
if (snapshot.data != null) {
@@ -47,7 +47,8 @@ class ApplicationWrapper extends StatelessWidget {
userPreferencesService: userPreferencesService,
volumeEventsService: const VolumeEventsService(LocalPlatform()),
child: RemoteConfigProvider(
- remoteConfigService: const RemoteConfigService(),
+ remoteConfigService:
+ env.buildType != BuildType.dev ? const RemoteConfigService() : const MockRemoteConfigService(),
child: EquipmentProfileProvider(
storageService: iapService,
child: FilmsProvider(
diff --git a/lib/data/models/camera_feature.dart b/lib/data/models/camera_feature.dart
new file mode 100644
index 0000000..20193bf
--- /dev/null
+++ b/lib/data/models/camera_feature.dart
@@ -0,0 +1,13 @@
+enum CameraFeature {
+ spotMetering,
+ histogram,
+}
+
+typedef CameraFeaturesConfig = Map;
+
+extension CameraFeaturesConfigJson on CameraFeaturesConfig {
+ static CameraFeaturesConfig fromJson(Map data) =>
+ {for (final f in CameraFeature.values) f: data[f.name] as bool? ?? false};
+
+ Map toJson() => map((key, value) => MapEntry(key.name, value));
+}
diff --git a/lib/data/models/feature.dart b/lib/data/models/feature.dart
index e4dc30e..b022db7 100644
--- a/lib/data/models/feature.dart
+++ b/lib/data/models/feature.dart
@@ -1,5 +1,5 @@
enum Feature { unlockProFeaturesText }
const featuresDefaultValues = {
- Feature.unlockProFeaturesText: false,
+ Feature.unlockProFeaturesText: true,
};
diff --git a/lib/data/models/metering_screen_layout_config.dart b/lib/data/models/metering_screen_layout_config.dart
index 7802195..7d550e4 100644
--- a/lib/data/models/metering_screen_layout_config.dart
+++ b/lib/data/models/metering_screen_layout_config.dart
@@ -1,18 +1,31 @@
enum MeteringScreenLayoutFeature {
- extremeExposurePairs,
- filmPicker,
- histogram,
- equipmentProfiles,
+ extremeExposurePairs, // 0
+ filmPicker, // 1
+ equipmentProfiles, // 3
}
typedef MeteringScreenLayoutConfig = Map;
extension MeteringScreenLayoutConfigJson on MeteringScreenLayoutConfig {
- static MeteringScreenLayoutConfig fromJson(Map data) =>
- {
- for (final f in MeteringScreenLayoutFeature.values)
- f: data[f.index.toString()] as bool? ?? true
- };
+ static MeteringScreenLayoutConfig fromJson(Map data) {
+ int? migratedIndex(MeteringScreenLayoutFeature feature) {
+ switch (feature) {
+ case MeteringScreenLayoutFeature.extremeExposurePairs:
+ return 0;
+ case MeteringScreenLayoutFeature.filmPicker:
+ return 1;
+ case MeteringScreenLayoutFeature.equipmentProfiles:
+ return 3;
+ default:
+ return null;
+ }
+ }
- Map toJson() => map((key, value) => MapEntry(key.index.toString(), value));
+ return {
+ for (final f in MeteringScreenLayoutFeature.values)
+ f: (data[migratedIndex(f).toString()] ?? data[f.name]) as bool? ?? true
+ };
+ }
+
+ Map toJson() => map((key, value) => MapEntry(key.name, value));
}
diff --git a/lib/data/remote_config_service.dart b/lib/data/remote_config_service.dart
index 9fc83fc..f8c123b 100644
--- a/lib/data/remote_config_service.dart
+++ b/lib/data/remote_config_service.dart
@@ -7,9 +7,26 @@ import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/foundation.dart';
import 'package:lightmeter/data/models/feature.dart';
-class RemoteConfigService {
+abstract class IRemoteConfigService {
+ const IRemoteConfigService();
+
+ Future activeAndFetchFeatures();
+
+ Future fetchConfig();
+
+ dynamic getValue(Feature feature);
+
+ Map getAll();
+
+ Stream> onConfigUpdated();
+
+ bool isEnabled(Feature feature);
+}
+
+class RemoteConfigService implements IRemoteConfigService {
const RemoteConfigService();
+ @override
Future activeAndFetchFeatures() async {
final FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instance;
const cacheStaleDuration = kDebugMode ? Duration(minutes: 1) : Duration(hours: 12);
@@ -24,18 +41,26 @@ class RemoteConfigService {
await remoteConfig.setDefaults(featuresDefaultValues.map((key, value) => MapEntry(key.name, value)));
await remoteConfig.activate();
await remoteConfig.ensureInitialized();
- unawaited(remoteConfig.fetch());
log('Firebase remote config initialized successfully');
} on FirebaseException catch (e) {
_logError('Firebase exception during Firebase Remote Config initialization: $e');
- } on Exception catch (e) {
+ } catch (e) {
_logError('Error during Firebase Remote Config initialization: $e');
}
}
+ @override
+ Future fetchConfig() async {
+ // https://github.com/firebase/flutterfire/issues/6196#issuecomment-927751667
+ await Future.delayed(const Duration(seconds: 1));
+ await FirebaseRemoteConfig.instance.fetch();
+ }
+
+ @override
dynamic getValue(Feature feature) => FirebaseRemoteConfig.instance.getValue(feature.name).toValue(feature);
+ @override
Map getAll() {
final Map result = {};
for (final value in FirebaseRemoteConfig.instance.getAll().entries) {
@@ -49,6 +74,7 @@ class RemoteConfigService {
return result;
}
+ @override
Stream> onConfigUpdated() => FirebaseRemoteConfig.instance.onConfigUpdated.asyncMap(
(event) async {
await FirebaseRemoteConfig.instance.activate();
@@ -64,6 +90,7 @@ class RemoteConfigService {
},
);
+ @override
bool isEnabled(Feature feature) => FirebaseRemoteConfig.instance.getBool(feature.name);
void _logError(dynamic throwable, {StackTrace? stackTrace}) {
@@ -71,6 +98,29 @@ class RemoteConfigService {
}
}
+class MockRemoteConfigService implements IRemoteConfigService {
+ const MockRemoteConfigService();
+
+ @override
+ Future activeAndFetchFeatures() async {}
+
+ @override
+ Future fetchConfig() async {}
+
+ @override
+ Map getAll() => featuresDefaultValues;
+
+ @override
+ dynamic getValue(Feature feature) => featuresDefaultValues[feature];
+
+ @override
+ // ignore: cast_nullable_to_non_nullable
+ bool isEnabled(Feature feature) => featuresDefaultValues[feature] as bool;
+
+ @override
+ Stream> onConfigUpdated() => const Stream.empty();
+}
+
extension on RemoteConfigValue {
dynamic toValue(Feature feature) {
switch (feature) {
diff --git a/lib/data/shared_prefs_service.dart b/lib/data/shared_prefs_service.dart
index 96d7d9d..3bb6dfb 100644
--- a/lib/data/shared_prefs_service.dart
+++ b/lib/data/shared_prefs_service.dart
@@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:flutter/material.dart';
+import 'package:lightmeter/data/models/camera_feature.dart';
import 'package:lightmeter/data/models/ev_source_type.dart';
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/data/models/supported_locale.dart';
@@ -15,9 +16,11 @@ class UserPreferencesService {
static const evSourceTypeKey = "evSourceType";
static const stopTypeKey = "stopType";
+ static const showEv100Key = "showEv100";
static const cameraEvCalibrationKey = "cameraEvCalibration";
static const lightSensorEvCalibrationKey = "lightSensorEvCalibration";
static const meteringScreenLayoutKey = "meteringScreenLayout";
+ static const cameraFeaturesKey = "cameraFeatures";
static const caffeineKey = "caffeine";
static const hapticsKey = "haptics";
@@ -70,21 +73,21 @@ class UserPreferencesService {
}
}
- IsoValue get iso =>
- IsoValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(isoKey) ?? 100));
+ IsoValue get iso => IsoValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(isoKey) ?? 100));
set iso(IsoValue value) => _sharedPreferences.setInt(isoKey, value.value);
- NdValue get ndFilter =>
- NdValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(ndFilterKey) ?? 0));
+ NdValue get ndFilter => NdValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(ndFilterKey) ?? 0));
set ndFilter(NdValue value) => _sharedPreferences.setInt(ndFilterKey, value.value);
- EvSourceType get evSourceType =>
- EvSourceType.values[_sharedPreferences.getInt(evSourceTypeKey) ?? 0];
+ EvSourceType get evSourceType => EvSourceType.values[_sharedPreferences.getInt(evSourceTypeKey) ?? 0];
set evSourceType(EvSourceType value) => _sharedPreferences.setInt(evSourceTypeKey, value.index);
StopType get stopType => StopType.values[_sharedPreferences.getInt(stopTypeKey) ?? 2];
set stopType(StopType value) => _sharedPreferences.setInt(stopTypeKey, value.index);
+ bool get showEv100 => _sharedPreferences.getBool(showEv100Key) ?? false;
+ set showEv100(bool value) => _sharedPreferences.setBool(showEv100Key, value);
+
MeteringScreenLayoutConfig get meteringScreenLayout {
final configJson = _sharedPreferences.getString(meteringScreenLayoutKey);
if (configJson != null) {
@@ -96,7 +99,6 @@ class UserPreferencesService {
MeteringScreenLayoutFeature.equipmentProfiles: true,
MeteringScreenLayoutFeature.extremeExposurePairs: true,
MeteringScreenLayoutFeature.filmPicker: true,
- MeteringScreenLayoutFeature.histogram: true,
};
}
}
@@ -104,6 +106,21 @@ class UserPreferencesService {
set meteringScreenLayout(MeteringScreenLayoutConfig value) =>
_sharedPreferences.setString(meteringScreenLayoutKey, json.encode(value.toJson()));
+ CameraFeaturesConfig get cameraFeatures {
+ final configJson = _sharedPreferences.getString(cameraFeaturesKey);
+ if (configJson != null) {
+ return CameraFeaturesConfigJson.fromJson(json.decode(configJson) as Map);
+ } else {
+ return {
+ CameraFeature.spotMetering: false,
+ CameraFeature.histogram: false,
+ };
+ }
+ }
+
+ set cameraFeatures(CameraFeaturesConfig value) =>
+ _sharedPreferences.setString(cameraFeaturesKey, json.encode(value.toJson()));
+
bool get caffeine => _sharedPreferences.getBool(caffeineKey) ?? false;
set caffeine(bool value) => _sharedPreferences.setBool(caffeineKey, value);
@@ -114,8 +131,7 @@ class UserPreferencesService {
(e) => e.toString() == _sharedPreferences.getString(volumeActionKey),
orElse: () => VolumeAction.shutter,
);
- set volumeAction(VolumeAction value) =>
- _sharedPreferences.setString(volumeActionKey, value.toString());
+ set volumeAction(VolumeAction value) => _sharedPreferences.setString(volumeActionKey, value.toString());
SupportedLocale get locale => SupportedLocale.values.firstWhere(
(e) => e.toString() == _sharedPreferences.getString(localeKey),
@@ -124,13 +140,10 @@ class UserPreferencesService {
set locale(SupportedLocale value) => _sharedPreferences.setString(localeKey, value.toString());
double get cameraEvCalibration => _sharedPreferences.getDouble(cameraEvCalibrationKey) ?? 0.0;
- set cameraEvCalibration(double value) =>
- _sharedPreferences.setDouble(cameraEvCalibrationKey, value);
+ set cameraEvCalibration(double value) => _sharedPreferences.setDouble(cameraEvCalibrationKey, value);
- double get lightSensorEvCalibration =>
- _sharedPreferences.getDouble(lightSensorEvCalibrationKey) ?? 0.0;
- set lightSensorEvCalibration(double value) =>
- _sharedPreferences.setDouble(lightSensorEvCalibrationKey, value);
+ double get lightSensorEvCalibration => _sharedPreferences.getDouble(lightSensorEvCalibrationKey) ?? 0.0;
+ set lightSensorEvCalibration(double value) => _sharedPreferences.setDouble(lightSensorEvCalibrationKey, value);
ThemeType get themeType => ThemeType.values[_sharedPreferences.getInt(themeTypeKey) ?? 0];
set themeType(ThemeType value) => _sharedPreferences.setInt(themeTypeKey, value.index);
diff --git a/lib/data/volume_events_service.dart b/lib/data/volume_events_service.dart
index d57936a..360de75 100644
--- a/lib/data/volume_events_service.dart
+++ b/lib/data/volume_events_service.dart
@@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
import 'package:platform/platform.dart';
class VolumeEventsService {
- final LocalPlatform localPlatform;
+ final LocalPlatform _localPlatform;
@visibleForTesting
static const volumeHandlingChannel = MethodChannel("com.vodemn.lightmeter/volumeHandling");
@@ -11,12 +11,12 @@ class VolumeEventsService {
@visibleForTesting
static const volumeEventsChannel = EventChannel("com.vodemn.lightmeter/volumeEvents");
- const VolumeEventsService(this.localPlatform);
+ const VolumeEventsService(this._localPlatform);
/// If set to `false` we allow system to handle key events.
/// Returns current status of volume handling.
Future setVolumeHandling(bool enableHandling) async {
- if (!localPlatform.isAndroid) {
+ if (!_localPlatform.isAndroid) {
return false;
}
return volumeHandlingChannel
@@ -29,7 +29,7 @@ class VolumeEventsService {
/// KEYCODE_VOLUME_DOWN = 25;
/// pressed
Stream volumeButtonsEventStream() {
- if (!localPlatform.isAndroid) {
+ if (!_localPlatform.isAndroid) {
return const Stream.empty();
}
return volumeEventsChannel
diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb
index 57e91d2..02dbb3d 100644
--- a/lib/l10n/intl_en.arb
+++ b/lib/l10n/intl_en.arb
@@ -34,16 +34,21 @@
"calibrationMessageCameraOnly": "The accuracy of the readings measured by this application depends entirely on the rear camera of the device. Therefore, consider testing this application and setting up an EV calibration value that will give you the desired measurement results.",
"camera": "Camera",
"lightSensor": "Light sensor",
+ "showEv100": "Show EV\u2081\u2080\u2080",
"meteringScreenLayout": "Metering screen layout",
"meteringScreenLayoutHint": "Hide elements on the metering screen that you don't need so that they don't waste exposure pairs list space.",
"meteringScreenLayoutHintEquipmentProfiles": "Equipment profile picker",
"meteringScreenFeatureExtremeExposurePairs": "Fastest & shortest exposure pairs",
"meteringScreenFeatureFilmPicker": "Film picker",
- "meteringScreenFeatureHistogram": "Histogram",
+ "cameraFeatures": "Camera features",
+ "cameraFeatureSpotMetering": "Spot metering",
+ "cameraFeatureSpotMeteringHint": "Long press the camera view to remove metering spot",
+ "cameraFeatureHistogram": "Histogram",
+ "cameraFeatureHistogramHint": "Enabling histogram can encrease battery drain",
"film": "Film",
"filmPush": "Film (push)",
"filmPull": "Film (pull)",
- "filmReciprocityHint": "Applies correction for shutter speeds grater than 1 second",
+ "filmReciprocityHint": "Applies correction for shutter speeds greater than 1 second",
"equipmentProfileName": "Equipment profile name",
"equipmentProfileNameHint": "Praktica MTL5B",
"equipmentProfileAllValues": "All",
@@ -92,13 +97,9 @@
}
}
},
- "lightmeterPro": "Lightmeter Pro",
- "buyLightmeterPro": "Buy Lightmeter Pro",
- "lightmeterProDescription": "Unlocks extra features, such as equipment profiles containing filters for aperture, shutter speed, and more; and a list of films with compensation for what's known as reciprocity failure.\n\nThe source code of Lightmeter is available on GitHub. You are welcome to compile it yourself. However, if you want to support the development and receive new features and updates, consider purchasing Lightmeter Pro.",
- "buy": "Buy",
"proFeatures": "Pro features",
"unlockProFeatures": "Unlock Pro features",
- "unlockProFeaturesDescription": "Unlock professional features, such as equipment profiles containing filters for aperture, shutter speed, and more; and a list of films with compensation for what's known as reciprocity failure.\n\nBy unlocking Pro features you support the development and make it possible to add new features to the app.",
+ "unlockProFeaturesDescription": "Unlock professional features:\n \u2022 Equipment profiles containing filters for aperture, shutter speed, and more\n \u2022 List of films with compensation for what's known as reciprocity failure\n \u2022 Spot metering\n \u2022 Histogram\n\nBy unlocking Pro features you support the development and make it possible to add new features to the app.",
"unlock": "Unlock",
"tooltipAdd": "Add",
"tooltipClose": "Close",
@@ -112,4 +113,4 @@
"tooltipUseLightSensor": "Use lightsensor",
"tooltipUseCamera": "Use camera",
"tooltipOpenSettings": "Open settings"
-}
\ No newline at end of file
+}
diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb
index b1cd7fb..590976d 100644
--- a/lib/l10n/intl_fr.arb
+++ b/lib/l10n/intl_fr.arb
@@ -34,12 +34,17 @@
"calibrationMessageCameraOnly": "La précision des lectures mesurées par cette application dépend entièrement de la caméra arrière de l'appareil. Par conséquent, envisagez de tester cette application et de configurer une valeur d'étalonnage EV qui vous donnera les résultats de mesure souhaités.",
"camera": "Caméra",
"lightSensor": "Capteur de lumière",
+ "showEv100": "Montrer EV\u2081\u2080\u2080",
"meteringScreenLayout": "Disposition de l'écran de mesure",
"meteringScreenLayoutHint": "Masquer les éléments sur l'écran de mesure dont vous n'avez pas besoin pour qu'ils ne gaspillent pas de l'espace dans les paires d'exposition.",
"meteringScreenLayoutHintEquipmentProfiles": "Sélecteur de profil de l'équipement",
"meteringScreenFeatureExtremeExposurePairs": "Paires d'exposition les plus rapides et les plus courtes",
"meteringScreenFeatureFilmPicker": "Sélecteur de film",
- "meteringScreenFeatureHistogram": "Histogramme",
+ "cameraFeatures": "Fonctionnalités de la caméra",
+ "cameraFeatureSpotMetering": "Mesure spot",
+ "cameraFeatureSpotMeteringHint": "Appuyez longuement sur la vue de l'appareil photo pour supprimer le spot de mesure",
+ "cameraFeatureHistogram": "Histogramme",
+ "cameraFeatureHistogramHint": "L'activation de l'histogramme peut augmenter la consommation de la batterie",
"film": "Pellicule",
"filmPush": "Pellicule (push)",
"filmPull": "Pellicule (pull)",
@@ -92,13 +97,9 @@
}
}
},
- "buyLightmeterPro": "Acheter Lightmeter Pro",
- "lightmeterPro": "Lightmeter Pro",
- "lightmeterProDescription": "Déverrouille des fonctionnalités supplémentaires, telles que des profils d'équipement contenant des filtres pour l'ouverture, la vitesse d'obturation et plus encore, ainsi qu'une liste de films avec une compensation pour ce que l'on appelle l'échec de réciprocité.\n\nLe code source du Lightmeter est disponible sur GitHub. Vous pouvez le compiler vous-même. Cependant, si vous souhaitez soutenir le développement et recevoir de nouvelles fonctionnalités et mises à jour, envisagez d'acheter Lightmeter Pro.",
- "buy": "Acheter",
"proFeatures": "Fonctionnalités professionnelles",
"unlockProFeatures": "Déverrouiller les fonctionnalités professionnelles",
- "unlockProFeaturesDescription": "Déverrouillez des fonctions professionnelles, telles que des profils d'équipement contenant des filtres pour l'ouverture, la vitesse d'obturation et plus encore, ainsi qu'une liste de films avec compensation pour ce que l'on appelle l'échec de réciprocité.\n\nEn débloquant les fonctionnalités Pro, vous soutenez le développement et permettez d'ajouter de nouvelles fonctionnalités à l'application.",
+ "unlockProFeaturesDescription": "Déverrouillez des fonctions professionnelles:\n \u2022 Profils d'équipement contenant des filtres pour l'ouverture, la vitesse d'obturation et plus encore, ainsi qu'une liste de films avec compensation pour ce que l'on appelle l'échec de réciprocité\n \u2022 Mesure spot\n \u2022 Histogramme\n\nEn débloquant les fonctionnalités Pro, vous soutenez le développement et permettez d'ajouter de nouvelles fonctionnalités à l'application.",
"unlock": "Déverrouiller",
"tooltipAdd": "Ajouter",
"tooltipClose": "Fermer",
diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb
index a18cc9c..e7f0743 100644
--- a/lib/l10n/intl_ru.arb
+++ b/lib/l10n/intl_ru.arb
@@ -34,12 +34,17 @@
"calibrationMessageCameraOnly": "Точность измерений данного приложения полностью зависит от точности камеры вашего устройства. Поэтому рекомендуется самостоятельно подобрать калибровочное значение, которое даст желаемый результат измерений.",
"camera": "Камера",
"lightSensor": "Датчик освещённости",
+ "showEv100": "Показывать EV\u2081\u2080\u2080",
"meteringScreenLayout": "Элементы главного экрана",
"meteringScreenLayoutHint": "Здесь вы можете скрыть некоторые ненужные или неиспользуемые элементы с главного экрана.",
"meteringScreenLayoutHintEquipmentProfiles": "Выбор профиля оборудования",
"meteringScreenFeatureExtremeExposurePairs": "Длинная и короткая выдержки",
"meteringScreenFeatureFilmPicker": "Выбор пленки",
- "meteringScreenFeatureHistogram": "Гистограмма",
+ "cameraFeatures": "Возможности камеры",
+ "cameraFeatureSpotMetering": "Точечный замер",
+ "cameraFeatureSpotMeteringHint": "Используйте долгое нажатие, чтобы удалить точку замера",
+ "cameraFeatureHistogram": "Гистограмма",
+ "cameraFeatureHistogramHint": "Использование гистограммы может увеличить расход аккумулятора",
"film": "Пленка",
"filmPush": "Пленка (push)",
"filmPull": "Пленка (pull)",
@@ -92,13 +97,9 @@
}
}
},
- "buyLightmeterPro": "Купить Lightmeter Pro",
- "lightmeterPro": "Lightmeter Pro",
- "lightmeterProDescription": "Даёт доступ к таким функциям как профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений, а также набору пленок с компенсацией эффекта Шварцшильда.\n\nИсходный код Lightmeter доступен на GitHub. Вы можете собрать его самостоятельно. Однако если вы хотите поддержать разработку и получать новые функции и обновления, то приобретите Lightmeter Pro.",
- "buy": "Купить",
"proFeatures": "Профессиональные настройки",
"unlockProFeatures": "Разблокировать профессиональные настройки",
- "unlockProFeaturesDescription": "Вы можете разблокировать профессиональные настройки, такие как профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений, а также набору пленок с компенсацией эффекта Шварцшильда.\n\nПолучая доступ к профессиональным настройкам, вы поддерживаете разработку и делаете возможным появление новых функций в приложении.",
+ "unlockProFeaturesDescription": "Вы можете разблокировать профессиональные настройки:\n \u2022 Профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений\n \u2022 Список пленок с компенсацией эффекта Шварцшильда\n \u2022 Точечный замер\n \u2022 Гистограмма\n\nПолучая доступ к профессиональным настройкам, вы поддерживаете разработку и делаете возможным появление новых функций в приложении.",
"unlock": "Разблокировать",
"tooltipAdd": "Добавить",
"tooltipClose": "Закрыть",
diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb
index 5788ea9..71fe757 100644
--- a/lib/l10n/intl_zh.arb
+++ b/lib/l10n/intl_zh.arb
@@ -34,12 +34,17 @@
"calibrationMessageCameraOnly": "此应用程序测量读数的准确s性完全取决于设备的后置摄像头。因此,请考虑测试此应用并手动设置 EV 校准,以获得准确的测量结果。",
"camera": "摄像头",
"lightSensor": "光传感器",
+ "showEv100": "显示 EV\u2081\u2080\u2080",
"meteringScreenLayout": "布局",
"meteringScreenLayoutHint": "隐藏不需要的元素,以免浪费曝光列表空间",
"meteringScreenLayoutHintEquipmentProfiles": "设备配置选择",
"meteringScreenFeatureExtremeExposurePairs": "最快 & 最慢曝光组合",
"meteringScreenFeatureFilmPicker": "胶片选择",
- "meteringScreenFeatureHistogram": "直方图",
+ "cameraFeatures": "相机功能",
+ "cameraFeatureSpotMetering": "点测光",
+ "cameraFeatureSpotMeteringHint": "长按相机视图可移除测光点",
+ "cameraFeatureHistogram": "直方图",
+ "cameraFeatureHistogramHint": "启用直方图会增加电池消耗",
"film": "胶片",
"filmPush": "胶片 (push)",
"filmPull": "胶片 (pull)",
@@ -92,13 +97,9 @@
}
}
},
- "buyLightmeterPro": "购买 Lightmeter Pro",
- "lightmeterPro": "Lightmeter Pro",
- "lightmeterProDescription": "购买以解锁额外功能。例如包含光圈、快门速度等参数的配置文件;以及一个胶卷预设列表来提供倒易率失效时的曝光补偿。\n\n您可以在 GitHub 上获取 Lightmeter 的源代码,欢迎自行编译。不过,如果您想支持开发并获得新功能和更新,请考虑购买 Lightmeter Pro。",
- "buy": "购买",
"proFeatures": "专业功能",
"unlockProFeatures": "解锁专业功能",
- "unlockProFeaturesDescription": "解锁专业功能。例如包含光圈、快门速度等参数的配置文件;以及一个胶卷预设列表来提供倒易率失效时的曝光补偿。\n\n通过解锁专业版功能,您可以支持开发工作,帮助为应用程序添加新功能。",
+ "unlockProFeaturesDescription": "\n \u2022 配置文件,其中包含光圈、快门速度等参数\n \u2022 胶卷列表,对胶片倒易率失效进行曝光补偿\n \u2022 点测光\n \u2022 直方图\n\n通过解锁专业版功能,您可以支持开发工作,帮助为应用程序添加新功能。",
"unlock": "解锁",
"tooltipAdd": "添加",
"tooltipClose": "关闭",
diff --git a/lib/platform_config.dart b/lib/platform_config.dart
index def5a80..d06223e 100644
--- a/lib/platform_config.dart
+++ b/lib/platform_config.dart
@@ -7,4 +7,6 @@ class PlatformConfig {
}
static String get cameraStubImage => const String.fromEnvironment('cameraStubImage');
+
+ static bool get isTest => cameraStubImage.isNotEmpty;
}
diff --git a/lib/providers/equipment_profile_provider.dart b/lib/providers/equipment_profile_provider.dart
index a5e0999..74397a5 100644
--- a/lib/providers/equipment_profile_provider.dart
+++ b/lib/providers/equipment_profile_provider.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:lightmeter/utils/context_utils.dart';
import 'package:lightmeter/utils/selectable_provider.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
@@ -52,11 +53,9 @@ class EquipmentProfileProviderState extends State {
return EquipmentProfiles(
values: [
_defaultProfile,
- if (IAPProducts.isPurchased(context, IAPProductType.paidFeatures)) ..._customProfiles,
+ if (context.isPro) ..._customProfiles,
],
- selected: IAPProducts.isPurchased(context, IAPProductType.paidFeatures)
- ? _selectedProfile
- : _defaultProfile,
+ selected: context.isPro ? _selectedProfile : _defaultProfile,
child: widget.child,
);
}
@@ -85,7 +84,7 @@ class EquipmentProfileProviderState extends State {
_refreshSavedProfiles();
}
- void updateProdile(EquipmentProfile data) {
+ void updateProfile(EquipmentProfile data) {
final indexToUpdate = _customProfiles.indexWhere((element) => element.id == data.id);
if (indexToUpdate >= 0) {
_customProfiles[indexToUpdate] = data;
@@ -118,13 +117,14 @@ class EquipmentProfiles extends SelectableInheritedModel {
/// [_defaultProfile] + profiles created by the user
static List of(BuildContext context) {
- return InheritedModel.inheritFrom(context, aspect: SelectableAspect.list)!
- .values;
+ return InheritedModel.inheritFrom(context, aspect: SelectableAspect.list)!.values;
}
static EquipmentProfile selectedOf(BuildContext context) {
- return InheritedModel.inheritFrom(context,
- aspect: SelectableAspect.selected,)!
+ return InheritedModel.inheritFrom(
+ context,
+ aspect: SelectableAspect.selected,
+ )!
.selected;
}
}
diff --git a/lib/providers/films_provider.dart b/lib/providers/films_provider.dart
index aff6d01..3e9d02d 100644
--- a/lib/providers/films_provider.dart
+++ b/lib/providers/films_provider.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:lightmeter/utils/context_utils.dart';
import 'package:lightmeter/utils/selectable_provider.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
@@ -44,11 +45,9 @@ class FilmsProviderState extends State {
],
filmsInUse: [
const Film.other(),
- if (IAPProducts.isPurchased(context, IAPProductType.paidFeatures)) ..._filmsInUse,
+ if (context.isPro) ..._filmsInUse,
],
- selected: IAPProducts.isPurchased(context, IAPProductType.paidFeatures)
- ? _selected
- : const Film.other(),
+ selected: context.isPro ? _selected : const Film.other(),
child: widget.child,
);
}
diff --git a/lib/providers/remote_config_provider.dart b/lib/providers/remote_config_provider.dart
index 9736e1d..4557ed6 100644
--- a/lib/providers/remote_config_provider.dart
+++ b/lib/providers/remote_config_provider.dart
@@ -1,11 +1,12 @@
import 'dart:async';
+import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/feature.dart';
import 'package:lightmeter/data/remote_config_service.dart';
class RemoteConfigProvider extends StatefulWidget {
- final RemoteConfigService remoteConfigService;
+ final IRemoteConfigService remoteConfigService;
final Widget child;
const RemoteConfigProvider({
@@ -25,7 +26,11 @@ class RemoteConfigProviderState extends State {
@override
void initState() {
super.initState();
- _updatesSubscription = widget.remoteConfigService.onConfigUpdated().listen(_updateFeatures);
+ widget.remoteConfigService.fetchConfig();
+ _updatesSubscription = widget.remoteConfigService.onConfigUpdated().listen(
+ _updateFeatures,
+ onError: (e) => log(e.toString()),
+ );
}
@override
diff --git a/lib/providers/user_preferences_provider.dart b/lib/providers/user_preferences_provider.dart
index 3a4111c..e2eecd7 100644
--- a/lib/providers/user_preferences_provider.dart
+++ b/lib/providers/user_preferences_provider.dart
@@ -1,6 +1,7 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
+import 'package:lightmeter/data/models/camera_feature.dart';
import 'package:lightmeter/data/models/dynamic_colors_state.dart';
import 'package:lightmeter/data/models/ev_source_type.dart';
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
@@ -9,6 +10,7 @@ import 'package:lightmeter/data/models/theme_type.dart';
import 'package:lightmeter/data/shared_prefs_service.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/res/theme.dart';
+import 'package:lightmeter/utils/map_model.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class UserPreferencesProvider extends StatefulWidget {
@@ -51,6 +53,18 @@ class UserPreferencesProvider extends StatefulWidget {
return _inheritFromEnumsModel(context, _Aspect.stopType).stopType;
}
+ static bool showEv100Of(BuildContext context) {
+ return _inheritFromEnumsModel(context, _Aspect.showEv100).showEv100;
+ }
+
+ static CameraFeaturesConfig cameraConfigOf(BuildContext context) {
+ return context.findAncestorWidgetOfExactType<_CameraFeaturesModel>()!.data;
+ }
+
+ static bool cameraFeatureOf(BuildContext context, CameraFeature feature) {
+ return InheritedModel.inheritFrom<_CameraFeaturesModel>(context, aspect: feature)!.data[feature]!;
+ }
+
static ThemeData themeOf(BuildContext context) {
return _inheritFromEnumsModel(context, _Aspect.theme).theme;
}
@@ -73,7 +87,9 @@ class UserPreferencesProvider extends StatefulWidget {
class _UserPreferencesProviderState extends State with WidgetsBindingObserver {
late EvSourceType _evSourceType;
late StopType _stopType = widget.userPreferencesService.stopType;
+ late bool _showEv100 = widget.userPreferencesService.showEv100;
late MeteringScreenLayoutConfig _meteringScreenLayout = widget.userPreferencesService.meteringScreenLayout;
+ late CameraFeaturesConfig _cameraFeatures = widget.userPreferencesService.cameraFeatures;
late SupportedLocale _locale = widget.userPreferencesService.locale;
late ThemeType _themeType = widget.userPreferencesService.themeType;
late Color _primaryColor = widget.userPreferencesService.primaryColor;
@@ -83,7 +99,8 @@ class _UserPreferencesProviderState extends State with
void initState() {
super.initState();
_evSourceType = widget.userPreferencesService.evSourceType;
- _evSourceType = _evSourceType == EvSourceType.sensor && !widget.hasLightSensor ? EvSourceType.camera : _evSourceType;
+ _evSourceType =
+ _evSourceType == EvSourceType.sensor && !widget.hasLightSensor ? EvSourceType.camera : _evSourceType;
WidgetsBinding.instance.addObserver(this);
}
@@ -123,11 +140,15 @@ class _UserPreferencesProviderState extends State with
evSourceType: _evSourceType,
locale: _locale,
primaryColor: dynamicPrimaryColor ?? _primaryColor,
+ showEv100: _showEv100,
stopType: _stopType,
themeType: _themeType,
child: _MeteringScreenLayoutModel(
data: _meteringScreenLayout,
- child: widget.child,
+ child: _CameraFeaturesModel(
+ data: _cameraFeatures,
+ child: widget.child,
+ ),
),
);
},
@@ -172,6 +193,13 @@ class _UserPreferencesProviderState extends State with
widget.userPreferencesService.meteringScreenLayout = _meteringScreenLayout;
}
+ void setCameraFeature(CameraFeaturesConfig config) {
+ setState(() {
+ _cameraFeatures = config;
+ });
+ widget.userPreferencesService.cameraFeatures = _cameraFeatures;
+ }
+
void setPrimaryColor(Color primaryColor) {
setState(() {
_primaryColor = primaryColor;
@@ -179,6 +207,13 @@ class _UserPreferencesProviderState extends State with
widget.userPreferencesService.primaryColor = primaryColor;
}
+ void toggleShowEv100() {
+ setState(() {
+ _showEv100 = !_showEv100;
+ });
+ widget.userPreferencesService.showEv100 = _showEv100;
+ }
+
void setStopType(StopType stopType) {
setState(() {
_stopType = stopType;
@@ -209,6 +244,7 @@ enum _Aspect {
dynamicColorState,
evSourceType,
locale,
+ showEv100,
stopType,
theme,
themeType,
@@ -218,6 +254,7 @@ class _UserPreferencesModel extends InheritedModel<_Aspect> {
final DynamicColorState dynamicColorState;
final EvSourceType evSourceType;
final SupportedLocale locale;
+ final bool showEv100;
final StopType stopType;
final ThemeType themeType;
@@ -230,6 +267,7 @@ class _UserPreferencesModel extends InheritedModel<_Aspect> {
required this.evSourceType,
required this.locale,
required Color primaryColor,
+ required this.showEv100,
required this.stopType,
required this.themeType,
required super.child,
@@ -245,6 +283,7 @@ class _UserPreferencesModel extends InheritedModel<_Aspect> {
evSourceType != oldWidget.evSourceType ||
locale != oldWidget.locale ||
_primaryColor != oldWidget._primaryColor ||
+ showEv100 != oldWidget.showEv100 ||
stopType != oldWidget.stopType ||
themeType != oldWidget.themeType;
}
@@ -257,6 +296,7 @@ class _UserPreferencesModel extends InheritedModel<_Aspect> {
return (dependencies.contains(_Aspect.dynamicColorState) && dynamicColorState != oldWidget.dynamicColorState) ||
(dependencies.contains(_Aspect.evSourceType) && evSourceType != oldWidget.evSourceType) ||
(dependencies.contains(_Aspect.locale) && locale != oldWidget.locale) ||
+ (dependencies.contains(_Aspect.showEv100) && showEv100 != oldWidget.showEv100) ||
(dependencies.contains(_Aspect.stopType) && stopType != oldWidget.stopType) ||
(dependencies.contains(_Aspect.theme) &&
(_brightness != oldWidget._brightness || _primaryColor != oldWidget._primaryColor)) ||
@@ -264,27 +304,16 @@ class _UserPreferencesModel extends InheritedModel<_Aspect> {
}
}
-class _MeteringScreenLayoutModel extends InheritedModel {
- final Map data;
-
+class _MeteringScreenLayoutModel extends MapModel {
const _MeteringScreenLayoutModel({
- required this.data,
+ required super.data,
+ required super.child,
+ });
+}
+
+class _CameraFeaturesModel extends MapModel {
+ const _CameraFeaturesModel({
+ required super.data,
required super.child,
});
-
- @override
- bool updateShouldNotify(_MeteringScreenLayoutModel oldWidget) => oldWidget.data != data;
-
- @override
- bool updateShouldNotifyDependent(
- _MeteringScreenLayoutModel oldWidget,
- Set dependencies,
- ) {
- for (final dependecy in dependencies) {
- if (oldWidget.data[dependecy] != data[dependecy]) {
- return true;
- }
- }
- return false;
- }
}
diff --git a/lib/res/theme.dart b/lib/res/theme.dart
index a6320c1..52e0e92 100644
--- a/lib/res/theme.dart
+++ b/lib/res/theme.dart
@@ -23,7 +23,7 @@ const primaryColorsList = [
ThemeData themeFrom(Color primaryColor, Brightness brightness) {
final scheme = _colorSchemeFromColor(primaryColor, brightness);
- return ThemeData(
+ final theme = ThemeData(
useMaterial3: true,
brightness: scheme.brightness,
primaryColor: primaryColor,
@@ -60,12 +60,18 @@ ThemeData themeFrom(Color primaryColor, Brightness brightness) {
),
scaffoldBackgroundColor: scheme.surface,
);
+ return theme.copyWith(
+ listTileTheme: ListTileThemeData(
+ style: ListTileStyle.list,
+ iconColor: scheme.onSurface,
+ textColor: scheme.onSurface,
+ subtitleTextStyle: theme.textTheme.bodyMedium!.copyWith(color: scheme.onSurfaceVariant),
+ ),
+ );
}
ColorScheme _colorSchemeFromColor(Color primaryColor, Brightness brightness) {
- final scheme = brightness == Brightness.light
- ? Scheme.light(primaryColor.value)
- : Scheme.dark(primaryColor.value);
+ final scheme = brightness == Brightness.light ? Scheme.light(primaryColor.value) : Scheme.dark(primaryColor.value);
return ColorScheme(
brightness: brightness,
diff --git a/lib/screens/metering/bloc_metering.dart b/lib/screens/metering/bloc_metering.dart
index cbb0356..753a7e7 100644
--- a/lib/screens/metering/bloc_metering.dart
+++ b/lib/screens/metering/bloc_metering.dart
@@ -10,9 +10,9 @@ import 'package:lightmeter/screens/metering/communication/event_communication_me
as communication_events;
import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart'
as communication_states;
-import 'package:lightmeter/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart';
import 'package:lightmeter/screens/metering/event_metering.dart';
import 'package:lightmeter/screens/metering/state_metering.dart';
+import 'package:lightmeter/screens/metering/utils/notifier_volume_keys.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class MeteringBloc extends Bloc {
diff --git a/lib/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart b/lib/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart
index 99bd2ae..e918557 100644
--- a/lib/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart
+++ b/lib/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart
@@ -1,15 +1,21 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
+import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/shared/filled_circle/widget_circle_filled.dart';
+import 'package:lightmeter/utils/context_utils.dart';
+
+const String _subscript100 = '\u2081\u2080\u2080';
class MeteringMeasureButton extends StatefulWidget {
final double? ev;
+ final double? ev100;
final bool isMetering;
final VoidCallback onTap;
const MeteringMeasureButton({
required this.ev,
+ required this.ev100,
required this.isMetering,
required this.onTap,
super.key,
@@ -61,7 +67,7 @@ class _MeteringMeasureButtonState extends State {
color: Theme.of(context).colorScheme.onSurface,
size: Dimens.grid72 - Dimens.grid8,
child: Center(
- child: widget.ev != null ? _EvValueText(ev: widget.ev!) : null,
+ child: widget.ev != null ? _EvValueText(ev: widget.ev!, ev100: widget.ev100!) : null,
),
),
),
@@ -83,16 +89,32 @@ class _MeteringMeasureButtonState extends State {
class _EvValueText extends StatelessWidget {
final double ev;
+ final double ev100;
- const _EvValueText({required this.ev});
+ const _EvValueText({
+ required this.ev,
+ required this.ev100,
+ });
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Text(
- '${ev.toStringAsFixed(1)}\n${S.of(context).ev}',
+ _text(context),
style: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.surface),
textAlign: TextAlign.center,
);
}
+
+ String _text(BuildContext context) {
+ final bool showEv100 = context.isPro && UserPreferencesProvider.showEv100Of(context);
+ final StringBuffer buffer = StringBuffer()
+ ..writeAll([
+ (showEv100 ? ev100 : ev).toStringAsFixed(1),
+ '\n',
+ S.of(context).ev,
+ if (showEv100) _subscript100,
+ ]);
+ return buffer.toString();
+ }
}
diff --git a/lib/screens/metering/components/bottom_controls/provider_bottom_controls.dart b/lib/screens/metering/components/bottom_controls/provider_bottom_controls.dart
index dd4a9be..f9d6d47 100644
--- a/lib/screens/metering/components/bottom_controls/provider_bottom_controls.dart
+++ b/lib/screens/metering/components/bottom_controls/provider_bottom_controls.dart
@@ -5,6 +5,7 @@ import 'package:lightmeter/screens/metering/components/bottom_controls/widget_bo
class MeteringBottomControlsProvider extends StatelessWidget {
final double? ev;
+ final double? ev100;
final bool isMetering;
final VoidCallback? onSwitchEvSourceType;
final VoidCallback onMeasure;
@@ -12,6 +13,7 @@ class MeteringBottomControlsProvider extends StatelessWidget {
const MeteringBottomControlsProvider({
required this.ev,
+ required this.ev100,
required this.isMetering,
required this.onSwitchEvSourceType,
required this.onMeasure,
@@ -35,6 +37,7 @@ class MeteringBottomControlsProvider extends StatelessWidget {
),
child: MeteringBottomControls(
ev: ev,
+ ev100: ev100,
isMetering: isMetering,
onSwitchEvSourceType: onSwitchEvSourceType,
onMeasure: onMeasure,
diff --git a/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart b/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart
index 31ecac4..0eef568 100644
--- a/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart
+++ b/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart
@@ -7,6 +7,7 @@ import 'package:lightmeter/screens/metering/components/bottom_controls/component
class MeteringBottomControls extends StatelessWidget {
final double? ev;
+ final double? ev100;
final bool isMetering;
final VoidCallback? onSwitchEvSourceType;
final VoidCallback onMeasure;
@@ -14,6 +15,7 @@ class MeteringBottomControls extends StatelessWidget {
const MeteringBottomControls({
required this.ev,
+ required this.ev100,
required this.isMetering,
required this.onSwitchEvSourceType,
required this.onMeasure,
@@ -58,6 +60,7 @@ class MeteringBottomControls extends StatelessWidget {
const Spacer(),
MeteringMeasureButton(
ev: ev,
+ ev100: ev100,
isMetering: isMetering,
onTap: onMeasure,
),
diff --git a/lib/screens/metering/components/camera_container/bloc_container_camera.dart b/lib/screens/metering/components/camera_container/bloc_container_camera.dart
index 1d7d4b5..3b5596b 100644
--- a/lib/screens/metering/components/camera_container/bloc_container_camera.dart
+++ b/lib/screens/metering/components/camera_container/bloc_container_camera.dart
@@ -4,22 +4,21 @@ import 'dart:io';
import 'dart:math' as math;
import 'package:camera/camera.dart';
-import 'package:exif/exif.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/interactors/metering_interactor.dart';
import 'package:lightmeter/platform_config.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
-import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart'
- as communication_event;
-import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart'
- as communication_states;
+import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' as communication_event;
+import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' as communication_states;
import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart';
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:m3_lightmeter_resources/m3_lightmeter_resources.dart';
+import 'package:lightmeter/utils/ev_from_bytes.dart';
+
+part 'mock_bloc_container_camera.dart';
class CameraContainerBloc extends EvSourceBlocBase {
final MeteringInteractor _meteringInteractor;
@@ -57,6 +56,7 @@ class CameraContainerBloc extends EvSourceBlocBase(_onZoomChanged);
on(_onExposureOffsetChanged);
on(_onExposureOffsetResetEvent);
+ on(_onExposureSpotChangedEvent);
}
@override
@@ -166,9 +166,7 @@ class CameraContainerBloc extends EvSourceBlocBase _onZoomChanged(ZoomChangedEvent event, Emitter emit) async {
- if (_cameraController != null &&
- event.value >= _zoomRange!.start &&
- event.value <= _zoomRange!.end) {
+ if (_cameraController != null && event.value >= _zoomRange!.start && event.value <= _zoomRange!.end) {
_cameraController!.setZoomLevel(event.value);
_currentZoom = event.value;
_emitActiveState(emit);
@@ -188,6 +186,13 @@ class CameraContainerBloc extends EvSourceBlocBase _onExposureSpotChangedEvent(ExposureSpotChangedEvent event, Emitter emit) async {
+ if (_cameraController != null) {
+ _cameraController!.setExposurePoint(event.offset);
+ _cameraController!.setFocusPoint(event.offset);
+ }
+ }
+
void _emitActiveState(Emitter emit) {
emit(
CameraActiveState(
@@ -209,33 +214,15 @@ class CameraContainerBloc extends EvSourceBlocBase _takePhoto() async {
try {
// https://github.com/flutter/flutter/issues/84957#issuecomment-1661155095
+ await _cameraController!.setFocusMode(FocusMode.locked);
+ await _cameraController!.setExposureMode(ExposureMode.locked);
+ final file = await _cameraController!.takePicture();
+ await _cameraController!.setFocusMode(FocusMode.auto);
+ await _cameraController!.setExposureMode(ExposureMode.auto);
+ final bytes = await file.readAsBytes();
+ Directory(file.path).deleteSync(recursive: true);
- late final Uint8List bytes;
- if (PlatformConfig.cameraStubImage.isNotEmpty) {
- bytes = (await rootBundle.load(PlatformConfig.cameraStubImage)).buffer.asUint8List();
- } else {
- await _cameraController!.setFocusMode(FocusMode.locked);
- await _cameraController!.setExposureMode(ExposureMode.locked);
- final file = await _cameraController!.takePicture();
- await _cameraController!.setFocusMode(FocusMode.auto);
- await _cameraController!.setExposureMode(ExposureMode.auto);
- bytes = await file.readAsBytes();
- Directory(file.path).deleteSync(recursive: true);
- }
-
- final tags = await readExifFromBytes(bytes);
- final iso = double.tryParse("${tags["EXIF ISOSpeedRatings"]}");
- final apertureValueRatio = (tags["EXIF FNumber"]?.values as IfdRatios?)?.ratios.first;
- final speedValueRatio = (tags["EXIF ExposureTime"]?.values as IfdRatios?)?.ratios.first;
- if (iso == null || apertureValueRatio == null || speedValueRatio == null) {
- log('Error parsing EXIF: ${tags.keys}');
- return null;
- }
-
- final aperture = apertureValueRatio.numerator / apertureValueRatio.denominator;
- final speed = speedValueRatio.numerator / speedValueRatio.denominator;
-
- return log2(math.pow(aperture, 2)) - log2(speed) - log2(iso / 100);
+ return await evFromImage(bytes);
} catch (e) {
log(e.toString());
return null;
diff --git a/lib/screens/metering/components/camera_container/components/camera_controls/components/exposure_offset_slider/widget_slider_exposure_offset.dart b/lib/screens/metering/components/camera_container/components/camera_controls/components/exposure_offset_slider/widget_slider_exposure_offset.dart
index 484ee4b..40ec1f0 100644
--- a/lib/screens/metering/components/camera_container/components/camera_controls/components/exposure_offset_slider/widget_slider_exposure_offset.dart
+++ b/lib/screens/metering/components/camera_container/components/camera_controls/components/exposure_offset_slider/widget_slider_exposure_offset.dart
@@ -72,7 +72,7 @@ class _Ruler extends StatelessWidget {
children: [
if (showValue)
Text(
- (index + min).toStringSigned(),
+ (index + min).toStringSignedAsFixed(0),
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(width: Dimens.grid8),
diff --git a/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_spot_detector/widget_camera_spot_detector.dart b/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_spot_detector/widget_camera_spot_detector.dart
new file mode 100644
index 0000000..0ec7f17
--- /dev/null
+++ b/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_spot_detector/widget_camera_spot_detector.dart
@@ -0,0 +1,71 @@
+import 'package:flutter/material.dart';
+import 'package:lightmeter/res/dimens.dart';
+
+class CameraSpotDetector extends StatefulWidget {
+ final ValueChanged onSpotTap;
+
+ const CameraSpotDetector({
+ required this.onSpotTap,
+ super.key,
+ });
+
+ @override
+ State createState() => _CameraSpotDetectorState();
+}
+
+class _CameraSpotDetectorState extends State {
+ Offset? spot;
+
+ @override
+ Widget build(BuildContext context) {
+ return LayoutBuilder(
+ builder: (_, constraints) => GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTapDown: (TapDownDetails details) => onViewFinderTap(details, constraints),
+ onLongPress: () => onViewFinderTap(null, constraints),
+ child: Stack(
+ children: [
+ if (spot != null)
+ AnimatedPositioned(
+ duration: Dimens.durationS,
+ left: spot!.dx - Dimens.grid16 / 2,
+ top: spot!.dy - Dimens.grid16 / 2,
+ height: Dimens.grid16,
+ width: Dimens.grid16,
+ child: const _Spot(),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ void onViewFinderTap(TapDownDetails? details, BoxConstraints constraints) {
+ setState(() {
+ spot = details?.localPosition;
+ });
+
+ widget.onSpotTap(
+ details != null
+ ? Offset(
+ details.localPosition.dx / constraints.maxWidth,
+ details.localPosition.dy / constraints.maxHeight,
+ )
+ : null,
+ );
+ }
+}
+
+class _Spot extends StatelessWidget {
+ const _Spot();
+
+ @override
+ Widget build(BuildContext context) {
+ return const DecoratedBox(
+ decoration: BoxDecoration(
+ color: Colors.white70,
+ shape: BoxShape.circle,
+ ),
+ );
+ }
+}
diff --git a/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart b/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart
index 7c06062..c054f3d 100644
--- a/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart
+++ b/lib/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart
@@ -12,14 +12,17 @@ class CameraView extends StatelessWidget {
final value = controller.value;
return ValueListenableBuilder(
valueListenable: controller,
- builder: (_, __, ___) => AspectRatio(
+ builder: (_, __, Widget? child) => AspectRatio(
aspectRatio: _isLandscape(value) ? value.aspectRatio : (1 / value.aspectRatio),
- child: value.isInitialized
- ? RotatedBox(
- quarterTurns: _getQuarterTurns(value),
- child: controller.buildPreview(),
- )
- : const SizedBox.shrink(),
+ child: Stack(
+ children: [
+ RotatedBox(
+ quarterTurns: _getQuarterTurns(value),
+ child: controller.buildPreview(),
+ ),
+ child ?? const SizedBox(),
+ ],
+ ),
),
);
}
@@ -42,8 +45,6 @@ class CameraView extends StatelessWidget {
DeviceOrientation _getApplicableOrientation(CameraValue value) {
return value.isRecordingVideo
? value.recordingOrientation!
- : (value.previewPauseOrientation ??
- value.lockedCaptureOrientation ??
- value.deviceOrientation);
+ : (value.previewPauseOrientation ?? value.lockedCaptureOrientation ?? value.deviceOrientation);
}
}
diff --git a/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart b/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart
index 3e9538f..464228f 100644
--- a/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart
+++ b/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart
@@ -1,19 +1,27 @@
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
-import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
+import 'package:lightmeter/data/models/camera_feature.dart';
import 'package:lightmeter/platform_config.dart';
import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/res/dimens.dart';
+import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/camera_spot_detector/widget_camera_spot_detector.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/camera_view_placeholder/widget_placeholder_camera_view.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/histogram/widget_histogram.dart';
import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart';
+import 'package:lightmeter/utils/context_utils.dart';
class CameraPreview extends StatefulWidget {
final CameraController? controller;
final CameraErrorType? error;
+ final ValueChanged onSpotTap;
- const CameraPreview({this.controller, this.error, super.key});
+ const CameraPreview({
+ this.controller,
+ this.error,
+ required this.onSpotTap,
+ super.key,
+ });
@override
State createState() => _CameraPreviewState();
@@ -31,7 +39,10 @@ class _CameraPreviewState extends State {
AnimatedSwitcher(
duration: Dimens.switchDuration,
child: widget.controller != null
- ? _CameraPreviewBuilder(controller: widget.controller!)
+ ? _CameraPreviewBuilder(
+ controller: widget.controller!,
+ onSpotTap: widget.onSpotTap,
+ )
: CameraViewPlaceholder(error: widget.error),
),
],
@@ -43,16 +54,19 @@ class _CameraPreviewState extends State {
class _CameraPreviewBuilder extends StatefulWidget {
final CameraController controller;
+ final ValueChanged onSpotTap;
- const _CameraPreviewBuilder({required this.controller});
+ const _CameraPreviewBuilder({
+ required this.controller,
+ required this.onSpotTap,
+ });
@override
State<_CameraPreviewBuilder> createState() => _CameraPreviewBuilderState();
}
class _CameraPreviewBuilderState extends State<_CameraPreviewBuilder> {
- late final ValueNotifier _initializedNotifier =
- ValueNotifier(widget.controller.value.isInitialized);
+ late final ValueNotifier _initializedNotifier = ValueNotifier(widget.controller.value.isInitialized);
@override
void initState() {
@@ -79,16 +93,23 @@ class _CameraPreviewBuilderState extends State<_CameraPreviewBuilder> {
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),
- ),
+ if (context.isPro) ...[
+ if (UserPreferencesProvider.cameraFeatureOf(
+ context,
+ CameraFeature.histogram,
+ ))
+ Positioned(
+ left: Dimens.grid8,
+ right: Dimens.grid8,
+ bottom: Dimens.grid16,
+ child: CameraHistogram(controller: widget.controller),
+ ),
+ if (UserPreferencesProvider.cameraFeatureOf(
+ context,
+ CameraFeature.spotMetering,
+ ))
+ CameraSpotDetector(onSpotTap: widget.onSpotTap)
+ ],
],
)
: const SizedBox.shrink(),
diff --git a/lib/screens/metering/components/camera_container/event_container_camera.dart b/lib/screens/metering/components/camera_container/event_container_camera.dart
index d3e5995..fe0713d 100644
--- a/lib/screens/metering/components/camera_container/event_container_camera.dart
+++ b/lib/screens/metering/components/camera_container/event_container_camera.dart
@@ -1,3 +1,5 @@
+import 'package:flutter/gestures.dart';
+
abstract class CameraContainerEvent {
const CameraContainerEvent();
}
@@ -53,3 +55,19 @@ class ExposureOffsetChangedEvent extends CameraContainerEvent {
class ExposureOffsetResetEvent extends CameraContainerEvent {
const ExposureOffsetResetEvent();
}
+
+class ExposureSpotChangedEvent extends CameraContainerEvent {
+ final Offset? offset;
+
+ const ExposureSpotChangedEvent(this.offset);
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+ if (other.runtimeType != runtimeType) return false;
+ return other is ExposureSpotChangedEvent && other.offset == offset;
+ }
+
+ @override
+ int get hashCode => Object.hash(offset, runtimeType);
+}
diff --git a/lib/screens/metering/components/camera_container/mock_bloc_container_camera.dart b/lib/screens/metering/components/camera_container/mock_bloc_container_camera.dart
new file mode 100644
index 0000000..f915a52
--- /dev/null
+++ b/lib/screens/metering/components/camera_container/mock_bloc_container_camera.dart
@@ -0,0 +1,80 @@
+part of 'bloc_container_camera.dart';
+
+class MockCameraContainerBloc extends CameraContainerBloc {
+ MockCameraContainerBloc(
+ super._meteringInteractor,
+ super.communicationBloc,
+ );
+
+ @override
+ Future _onRequestPermission(_, Emitter emit) async {
+ add(const InitializeEvent());
+ }
+
+ @override
+ Future _onOpenAppSettings(_, Emitter emit) async {
+ _meteringInteractor.openAppSettings();
+ }
+
+ @override
+ Future _onInitialize(_, Emitter emit) async {
+ emit(const CameraLoadingState());
+ try {
+ _cameraController = CameraController(
+ const CameraDescription(name: '0', lensDirection: CameraLensDirection.back, sensorOrientation: 0),
+ ResolutionPreset.low,
+ enableAudio: false,
+ );
+
+ _zoomRange = const RangeValues(1, 6);
+ _currentZoom = _zoomRange!.start;
+
+ _exposureOffsetRange = const RangeValues(-4, 4);
+ _exposureStep = 0.1;
+ _currentExposureOffset = 0.0;
+
+ emit(CameraInitializedState(_cameraController!));
+
+ _emitActiveState(emit);
+ } catch (e) {
+ emit(const CameraErrorState(CameraErrorType.other));
+ }
+ }
+
+ @override
+ Future _onZoomChanged(ZoomChangedEvent event, Emitter emit) async {
+ if (event.value >= _zoomRange!.start && event.value <= _zoomRange!.end) {
+ _currentZoom = event.value;
+ _emitActiveState(emit);
+ }
+ }
+
+ @override
+ Future _onExposureOffsetChanged(ExposureOffsetChangedEvent event, Emitter emit) async {
+ _currentExposureOffset = event.value;
+ _emitActiveState(emit);
+ }
+
+ @override
+ Future _onExposureOffsetResetEvent(ExposureOffsetResetEvent event, Emitter emit) async {
+ _meteringInteractor.quickVibration();
+ add(const ExposureOffsetChangedEvent(0));
+ }
+
+ @override
+ Future _onExposureSpotChangedEvent(ExposureSpotChangedEvent event, Emitter emit) async {}
+
+ @override
+ bool get _canTakePhoto => PlatformConfig.cameraStubImage.isNotEmpty;
+
+ @override
+ Future _takePhoto() async {
+ try {
+ final bytes = (await rootBundle.load(PlatformConfig.cameraStubImage)).buffer.asUint8List();
+ return await evFromImage(bytes);
+ } catch (e) {
+ log(e.toString());
+ return null;
+ }
+ }
+}
diff --git a/lib/screens/metering/components/camera_container/provider_container_camera.dart b/lib/screens/metering/components/camera_container/provider_container_camera.dart
index 1d6d8c0..80be814 100644
--- a/lib/screens/metering/components/camera_container/provider_container_camera.dart
+++ b/lib/screens/metering/components/camera_container/provider_container_camera.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/data/models/exposure_pair.dart';
+import 'package:lightmeter/platform_config.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/event_container_camera.dart';
@@ -30,12 +31,18 @@ class CameraContainerProvider extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return BlocProvider(
+ return BlocProvider(
lazy: false,
- create: (context) => CameraContainerBloc(
- MeteringInteractorProvider.of(context),
- context.read(),
- )..add(const RequestPermissionEvent()),
+ create: (context) => (PlatformConfig.cameraStubImage.isNotEmpty
+ ? MockCameraContainerBloc(
+ MeteringInteractorProvider.of(context),
+ context.read(),
+ )
+ : CameraContainerBloc(
+ MeteringInteractorProvider.of(context),
+ context.read(),
+ ))
+ ..add(const RequestPermissionEvent()),
child: CameraContainer(
fastest: fastest,
slowest: slowest,
diff --git a/lib/screens/metering/components/camera_container/widget_container_camera.dart b/lib/screens/metering/components/camera_container/widget_container_camera.dart
index 944aa34..5ed2b43 100644
--- a/lib/screens/metering/components/camera_container/widget_container_camera.dart
+++ b/lib/screens/metering/components/camera_container/widget_container_camera.dart
@@ -5,7 +5,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/platform_config.dart';
-import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_controls/widget_camera_controls.dart';
@@ -17,6 +16,7 @@ import 'package:lightmeter/screens/metering/components/camera_container/state_co
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/readings_container/widget_container_readings.dart';
+import 'package:lightmeter/utils/context_utils.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class CameraContainer extends StatelessWidget {
@@ -102,27 +102,23 @@ class CameraContainer extends StatelessWidget {
double _meteringContainerHeight(BuildContext context) {
double enabledFeaturesHeight = 0;
- if (UserPreferencesProvider.meteringScreenFeatureOf(
- context,
- MeteringScreenLayoutFeature.equipmentProfiles,
- )) {
+ if (!context.isPro) {
enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
enabledFeaturesHeight += Dimens.paddingS;
+ } else {
+ if (context.meteringFeature(MeteringScreenLayoutFeature.equipmentProfiles)) {
+ enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
+ enabledFeaturesHeight += Dimens.paddingS;
+ }
+ if (context.meteringFeature(MeteringScreenLayoutFeature.filmPicker)) {
+ enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
+ enabledFeaturesHeight += Dimens.paddingS;
+ }
}
- if (UserPreferencesProvider.meteringScreenFeatureOf(
- context,
- MeteringScreenLayoutFeature.extremeExposurePairs,
- )) {
+ if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) {
enabledFeaturesHeight += Dimens.readingContainerDoubleValueHeight;
enabledFeaturesHeight += Dimens.paddingS;
}
- if (UserPreferencesProvider.meteringScreenFeatureOf(
- context,
- MeteringScreenLayoutFeature.filmPicker,
- )) {
- enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight;
- enabledFeaturesHeight += Dimens.paddingS;
- }
return enabledFeaturesHeight + Dimens.readingContainerSingleValueHeight; // ISO & ND
}
@@ -143,6 +139,9 @@ class _CameraViewBuilder extends StatelessWidget {
builder: (context, state) => CameraPreview(
controller: state is CameraInitializedState ? state.controller : null,
error: state is CameraErrorState ? state.error : null,
+ onSpotTap: (value) {
+ context.read().add(ExposureSpotChangedEvent(value));
+ },
),
);
}
diff --git a/lib/screens/metering/components/shared/readings_container/components/lightmeter_pro/widget_lightmeter_pro.dart b/lib/screens/metering/components/shared/readings_container/components/lightmeter_pro/widget_lightmeter_pro.dart
new file mode 100644
index 0000000..9d5218a
--- /dev/null
+++ b/lib/screens/metering/components/shared/readings_container/components/lightmeter_pro/widget_lightmeter_pro.dart
@@ -0,0 +1,27 @@
+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/components/animated_dialog/widget_dialog_animated.dart';
+import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart';
+import 'package:lightmeter/screens/shared/pro_features_dialog/widget_dialog_pro_features.dart';
+
+class LightmeterProAnimatedDialog extends StatelessWidget {
+ const LightmeterProAnimatedDialog({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return AnimatedDialog(
+ closedChild: ReadingValueContainer(
+ color: Theme.of(context).colorScheme.errorContainer,
+ textColor: Theme.of(context).colorScheme.onErrorContainer,
+ values: [
+ ReadingValue(
+ label: S.of(context).proFeatures,
+ value: S.of(context).unlock,
+ ),
+ ],
+ ),
+ openedChild: const ProFeaturesDialog(),
+ openedSize: Size.fromHeight(const ProFeaturesDialog().height(context)),
+ );
+ }
+}
diff --git a/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart b/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart
index 72e770b..63bd1e6 100644
--- a/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart
+++ b/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart
@@ -1,9 +1,15 @@
+import 'dart:math';
+
import 'package:flutter/material.dart';
import 'package:lightmeter/res/dimens.dart';
+mixin AnimatedDialogClosedChild on Widget {
+ Color backgroundColor(BuildContext context);
+}
+
class AnimatedDialog extends StatefulWidget {
final Size? openedSize;
- final Widget? closedChild;
+ final AnimatedDialogClosedChild? closedChild;
final Widget? openedChild;
final Widget? child;
@@ -15,6 +21,9 @@ class AnimatedDialog extends StatefulWidget {
super.key,
});
+ static Future? maybeClose(BuildContext context) =>
+ context.findAncestorWidgetOfExactType<_AnimatedOverlay>()?.onDismiss();
+
@override
State createState() => AnimatedDialogState();
}
@@ -95,7 +104,7 @@ class AnimatedDialogState extends State with SingleTickerProvide
void didChangeDependencies() {
super.didChangeDependencies();
_foregroundColorAnimation = ColorTween(
- begin: Theme.of(context).colorScheme.primaryContainer,
+ begin: widget.closedChild?.backgroundColor(context) ?? Theme.of(context).colorScheme.primaryContainer,
end: Theme.of(context).colorScheme.surface,
).animate(_defaultCurvedAnimation);
@@ -135,14 +144,15 @@ class AnimatedDialogState extends State with SingleTickerProvide
if (renderBox != null) {
final size = MediaQuery.sizeOf(context);
final padding = MediaQuery.paddingOf(context);
+ final maxWidth = size.width - padding.horizontal - Dimens.dialogMargin.horizontal;
+ final maxHeight = size.height - padding.vertical - Dimens.dialogMargin.vertical;
_closedSize = _key.currentContext!.size!;
_sizeTween = SizeTween(
begin: _closedSize,
- end: widget.openedSize ??
- Size(
- size.width - padding.horizontal - Dimens.dialogMargin.horizontal,
- size.height - padding.vertical - Dimens.dialogMargin.vertical,
- ),
+ end: Size(
+ min(widget.openedSize?.width ?? double.maxFinite, maxWidth),
+ min(widget.openedSize?.height ?? double.maxFinite, maxHeight),
+ ),
);
_sizeAnimation = _sizeTween.animate(_defaultCurvedAnimation);
@@ -181,7 +191,6 @@ class AnimatedDialogState extends State with SingleTickerProvide
onDismiss: close,
builder: widget.closedChild != null && widget.openedChild != null
? (_) => _AnimatedSwitcher(
- sizeAnimation: _sizeAnimation,
closedOpacityAnimation: _closedOpacityAnimation,
openedOpacityAnimation: _openedOpacityAnimation,
closedSize: _sizeTween.begin!,
@@ -223,7 +232,7 @@ class _AnimatedOverlay extends StatelessWidget {
final Animation borderRadiusAnimation;
final Animation foregroundColorAnimation;
final Animation elevationAnimation;
- final VoidCallback onDismiss;
+ final Future Function() onDismiss;
final Widget? child;
final Widget Function(BuildContext context)? builder;
@@ -281,7 +290,6 @@ class _AnimatedOverlay extends StatelessWidget {
}
class _AnimatedSwitcher extends StatelessWidget {
- final Animation sizeAnimation;
final Animation closedOpacityAnimation;
final Animation openedOpacityAnimation;
final Size closedSize;
@@ -290,7 +298,6 @@ class _AnimatedSwitcher extends StatelessWidget {
final Widget openedChild;
const _AnimatedSwitcher({
- required this.sizeAnimation,
required this.closedOpacityAnimation,
required this.openedOpacityAnimation,
required this.closedSize,
@@ -306,17 +313,21 @@ class _AnimatedSwitcher extends StatelessWidget {
children: [
Opacity(
opacity: closedOpacityAnimation.value,
- child: Transform.scale(
- scale: sizeAnimation.value!.width / closedSize.width,
- child: SizedBox(
- width: closedSize.width,
+ child: FittedBox(
+ child: SizedBox.fromSize(
+ size: closedSize,
child: closedChild,
),
),
),
Opacity(
opacity: openedOpacityAnimation.value,
- child: openedChild,
+ child: FittedBox(
+ child: SizedBox.fromSize(
+ size: openedSize,
+ child: openedChild,
+ ),
+ ),
),
],
);
diff --git a/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart b/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart
index 3c253b1..dfbf1ce 100644
--- a/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart
+++ b/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/dialog_picker/widget_picker_dialog.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/res/dimens.dart';
+import 'package:lightmeter/screens/shared/transparent_dialog/widget_dialog_transparent.dart';
typedef DialogPickerItemTitleBuilder = Widget Function(BuildContext context, T value);
typedef DialogPickerItemTrailingBuilder = Widget? Function(T selected, T value);
@@ -29,6 +30,14 @@ class DialogPicker extends StatefulWidget {
super.key,
});
+ double height(BuildContext context) => TransparentDialog.height(
+ context,
+ title: title,
+ subtitle: subtitle,
+ scrollableContent: true,
+ contextHeight: Dimens.grid56 * values.length,
+ );
+
@override
State> createState() => _DialogPickerState();
}
@@ -46,83 +55,43 @@ class _DialogPickerState extends State> {
@override
Widget build(BuildContext context) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- Padding(
- padding: Dimens.dialogTitlePadding,
- child: Icon(widget.icon),
- ),
- Padding(
- padding: Dimens.dialogIconTitlePadding,
- child: Text(
- widget.title,
- style: Theme.of(context).textTheme.headlineSmall,
- textAlign: TextAlign.center,
- ),
- ),
- if (widget.subtitle != null)
- Padding(
- padding: const EdgeInsets.fromLTRB(
- Dimens.paddingL,
- 0,
- Dimens.paddingL,
- Dimens.paddingM,
- ),
- child: Text(
- widget.subtitle!,
- style: Theme.of(context).textTheme.bodyMedium,
- textAlign: TextAlign.center,
- ),
- ),
- ],
- ),
- const Divider(),
- Expanded(
- child: ListView.builder(
- controller: _scrollController,
- padding: EdgeInsets.zero,
- itemCount: widget.values.length,
- itemExtent: Dimens.grid56,
- itemBuilder: (context, index) => RadioListTile(
- value: widget.values[index],
- groupValue: _selectedValue,
- title: DefaultTextStyle(
- style: Theme.of(context).textTheme.bodyLarge!,
- child: widget.itemTitleBuilder(context, widget.values[index]),
- ),
- secondary: widget.itemTrailingBuilder?.call(_selectedValue, widget.values[index]),
- onChanged: (value) {
- if (value != null) {
- setState(() {
- _selectedValue = value;
- });
- }
- },
+ return TransparentDialog(
+ icon: widget.icon,
+ title: widget.title,
+ subtitle: widget.subtitle,
+ content: Expanded(
+ child: ListView.builder(
+ controller: _scrollController,
+ padding: EdgeInsets.zero,
+ itemCount: widget.values.length,
+ itemExtent: Dimens.grid56,
+ itemBuilder: (context, index) => RadioListTile(
+ value: widget.values[index],
+ groupValue: _selectedValue,
+ title: DefaultTextStyle(
+ style: Theme.of(context).textTheme.bodyLarge!,
+ child: widget.itemTitleBuilder(context, widget.values[index]),
),
+ secondary: widget.itemTrailingBuilder?.call(_selectedValue, widget.values[index]),
+ onChanged: (value) {
+ if (value != null) {
+ setState(() {
+ _selectedValue = value;
+ });
+ }
+ },
),
),
- const Divider(),
- Padding(
- padding: Dimens.dialogActionsPadding,
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- const Spacer(),
- TextButton(
- onPressed: widget.onCancel,
- child: Text(S.of(context).cancel),
- ),
- const SizedBox(width: Dimens.grid16),
- TextButton(
- onPressed: () => widget.onSelect(_selectedValue),
- child: Text(S.of(context).select),
- ),
- ],
- ),
+ ),
+ scrollableContent: true,
+ actions: [
+ TextButton(
+ onPressed: widget.onCancel,
+ child: Text(S.of(context).cancel),
+ ),
+ TextButton(
+ onPressed: () => widget.onSelect(_selectedValue),
+ child: Text(S.of(context).select),
),
],
);
diff --git a/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart b/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart
index eeeec52..efa07c7 100644
--- a/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart
+++ b/lib/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/widget_picker_dialog_animated.dart
@@ -2,7 +2,7 @@ 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/shared/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.
class AnimatedDialogPicker extends StatefulWidget {
final IconData icon;
@@ -13,7 +13,7 @@ class AnimatedDialogPicker extends StatefulWidget {
final DialogPickerItemTitleBuilder itemTitleBuilder;
final DialogPickerItemTrailingBuilder? itemTrailingBuilder;
final ValueChanged onChanged;
- final Widget closedChild;
+ final AnimatedDialogClosedChild closedChild;
const AnimatedDialogPicker({
required this.icon,
@@ -37,24 +37,26 @@ class _AnimatedDialogPickerState extends State> {
@override
Widget build(BuildContext context) {
+ final dialogPicker = DialogPicker(
+ icon: widget.icon,
+ title: widget.title,
+ subtitle: widget.subtitle,
+ initialValue: widget.selectedValue,
+ values: widget.values,
+ itemTitleBuilder: widget.itemTitleBuilder,
+ itemTrailingBuilder: widget.itemTrailingBuilder,
+ onCancel: () {
+ _key.currentState?.close();
+ },
+ onSelect: (value) {
+ _key.currentState?.close().then((_) => widget.onChanged(value));
+ },
+ );
return AnimatedDialog(
key: _key,
closedChild: widget.closedChild,
- openedChild: DialogPicker(
- icon: widget.icon,
- title: widget.title,
- subtitle: widget.subtitle,
- initialValue: widget.selectedValue,
- values: widget.values,
- itemTitleBuilder: widget.itemTitleBuilder,
- itemTrailingBuilder: widget.itemTrailingBuilder,
- onCancel: () {
- _key.currentState?.close();
- },
- onSelect: (value) {
- _key.currentState?.close().then((_) => widget.onChanged(value));
- },
- ),
+ openedChild: dialogPicker,
+ openedSize: Size.fromHeight(dialogPicker.height(context)),
);
}
}
diff --git a/lib/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart b/lib/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart
index 3254456..6968db6 100644
--- a/lib/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart
+++ b/lib/screens/metering/components/shared/readings_container/components/shared/reading_value_container/widget_container_reading_value.dart
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/res/dimens.dart';
+import 'package:lightmeter/screens/metering/components/shared/readings_container/components/shared/animated_dialog_picker/components/animated_dialog/widget_dialog_animated.dart';
class ReadingValue {
final String label;
@@ -11,11 +12,15 @@ class ReadingValue {
});
}
-class ReadingValueContainer extends StatelessWidget {
+class ReadingValueContainer extends StatelessWidget implements AnimatedDialogClosedChild {
late final List _items;
+ final Color? color;
+ final Color? textColor;
ReadingValueContainer({
required List values,
+ this.color,
+ this.textColor,
super.key,
}) {
_items = [];
@@ -23,21 +28,26 @@ class ReadingValueContainer extends StatelessWidget {
if (i > 0) {
_items.add(const SizedBox(height: Dimens.grid8));
}
- _items.add(_ReadingValueBuilder(values[i]));
+ _items.add(_ReadingValueBuilder(values[i], textColor: textColor));
}
}
ReadingValueContainer.singleValue({
required ReadingValue value,
+ this.color,
+ this.textColor,
super.key,
- }) : _items = [_ReadingValueBuilder(value)];
+ }) : _items = [_ReadingValueBuilder(value, textColor: textColor)];
+
+ @override
+ Color backgroundColor(BuildContext context) => color ?? Theme.of(context).colorScheme.primaryContainer;
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(Dimens.borderRadiusM),
child: ColoredBox(
- color: Theme.of(context).colorScheme.primaryContainer,
+ color: backgroundColor(context),
child: Padding(
padding: const EdgeInsets.all(Dimens.paddingM),
child: Column(
@@ -53,20 +63,21 @@ class ReadingValueContainer extends StatelessWidget {
class _ReadingValueBuilder extends StatelessWidget {
final ReadingValue reading;
+ final Color? textColor;
- const _ReadingValueBuilder(this.reading);
+ const _ReadingValueBuilder(this.reading, {this.textColor});
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
- final textColor = Theme.of(context).colorScheme.onPrimaryContainer;
+ final color = textColor ?? Theme.of(context).colorScheme.onPrimaryContainer;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
reading.label,
- style: textTheme.labelMedium?.copyWith(color: textColor),
+ style: textTheme.labelMedium?.copyWith(color: color),
maxLines: 1,
overflow: TextOverflow.visible,
softWrap: false,
@@ -76,7 +87,7 @@ class _ReadingValueBuilder extends StatelessWidget {
duration: Dimens.switchDuration,
child: Text(
reading.value,
- style: textTheme.titleMedium?.copyWith(color: textColor),
+ style: textTheme.titleMedium?.copyWith(color: color),
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
diff --git a/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart b/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart
index cb8af05..ce373a8 100644
--- a/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart
+++ b/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart
@@ -2,13 +2,14 @@ import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/exposure_pair.dart';
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/providers/equipment_profile_provider.dart';
-import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/equipment_profile_picker/widget_picker_equipment_profiles.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/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/lightmeter_pro/widget_lightmeter_pro.dart';
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/nd_picker/widget_picker_nd.dart';
+import 'package:lightmeter/utils/context_utils.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class ReadingsContainer extends StatelessWidget {
@@ -34,27 +35,22 @@ class ReadingsContainer extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
- if (UserPreferencesProvider.meteringScreenFeatureOf(
- context,
- MeteringScreenLayoutFeature.equipmentProfiles,
- )) ...[
+ if (!context.isPro) ...[
+ const LightmeterProAnimatedDialog(),
+ const _InnerPadding(),
+ ],
+ if (context.isPro && context.meteringFeature(MeteringScreenLayoutFeature.equipmentProfiles)) ...[
const EquipmentProfilePicker(),
const _InnerPadding(),
],
- if (UserPreferencesProvider.meteringScreenFeatureOf(
- context,
- MeteringScreenLayoutFeature.extremeExposurePairs,
- )) ...[
+ if (context.meteringFeature(MeteringScreenLayoutFeature.extremeExposurePairs)) ...[
ExtremeExposurePairsContainer(
fastest: fastest,
slowest: slowest,
),
const _InnerPadding(),
],
- if (UserPreferencesProvider.meteringScreenFeatureOf(
- context,
- MeteringScreenLayoutFeature.filmPicker,
- )) ...[
+ if (context.isPro && context.meteringFeature(MeteringScreenLayoutFeature.filmPicker)) ...[
FilmPicker(selectedIso: iso),
const _InnerPadding(),
],
diff --git a/lib/screens/metering/flow_metering.dart b/lib/screens/metering/flow_metering.dart
index cca5675..f78482d 100644
--- a/lib/screens/metering/flow_metering.dart
+++ b/lib/screens/metering/flow_metering.dart
@@ -4,8 +4,8 @@ import 'package:lightmeter/interactors/metering_interactor.dart';
import 'package:lightmeter/providers/services_provider.dart';
import 'package:lightmeter/screens/metering/bloc_metering.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
-import 'package:lightmeter/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart';
import 'package:lightmeter/screens/metering/screen_metering.dart';
+import 'package:lightmeter/screens/metering/utils/notifier_volume_keys.dart';
class MeteringFlow extends StatefulWidget {
const MeteringFlow({super.key});
diff --git a/lib/screens/metering/screen_metering.dart b/lib/screens/metering/screen_metering.dart
index 6900159..836e6d0 100644
--- a/lib/screens/metering/screen_metering.dart
+++ b/lib/screens/metering/screen_metering.dart
@@ -13,7 +13,7 @@ import 'package:lightmeter/screens/metering/components/camera_container/provider
import 'package:lightmeter/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart';
import 'package:lightmeter/screens/metering/event_metering.dart';
import 'package:lightmeter/screens/metering/state_metering.dart';
-import 'package:lightmeter/screens/metering/utils/listsner_equipment_profiles.dart';
+import 'package:lightmeter/screens/metering/utils/listener_equipment_profiles.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class MeteringScreen extends StatelessWidget {
@@ -40,6 +40,7 @@ class MeteringScreen extends StatelessWidget {
BlocBuilder(
builder: (context, state) => MeteringBottomControlsProvider(
ev: state is MeteringDataState ? state.ev : null,
+ ev100: state is MeteringDataState ? state.ev100 : null,
isMetering: state.isMetering,
onSwitchEvSourceType: ServicesProvider.of(context).environment.hasLightSensor
? UserPreferencesProvider.of(context).toggleEvSourceType
diff --git a/lib/screens/metering/utils/listsner_equipment_profiles.dart b/lib/screens/metering/utils/listener_equipment_profiles.dart
similarity index 100%
rename from lib/screens/metering/utils/listsner_equipment_profiles.dart
rename to lib/screens/metering/utils/listener_equipment_profiles.dart
diff --git a/lib/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart b/lib/screens/metering/utils/notifier_volume_keys.dart
similarity index 82%
rename from lib/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart
rename to lib/screens/metering/utils/notifier_volume_keys.dart
index df64fdf..0c19955 100644
--- a/lib/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart
+++ b/lib/screens/metering/utils/notifier_volume_keys.dart
@@ -5,12 +5,12 @@ import 'package:lightmeter/data/models/volume_action.dart';
import 'package:lightmeter/data/volume_events_service.dart';
class VolumeKeysNotifier extends ChangeNotifier with RouteAware {
- final VolumeEventsService volumeEventsService;
+ final VolumeEventsService _volumeEventsService;
late final StreamSubscription _volumeKeysSubscription;
VolumeKey _value = VolumeKey.up;
- VolumeKeysNotifier(this.volumeEventsService) {
- _volumeKeysSubscription = volumeEventsService
+ VolumeKeysNotifier(this._volumeEventsService) {
+ _volumeKeysSubscription = _volumeEventsService
.volumeButtonsEventStream()
.map((event) => event == 24 ? VolumeKey.up : VolumeKey.down)
.listen((event) {
@@ -19,6 +19,8 @@ class VolumeKeysNotifier extends ChangeNotifier with RouteAware {
}
VolumeKey get value => _value;
+
+ @protected
set value(VolumeKey newValue) {
_value = newValue;
notifyListeners();
diff --git a/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart b/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart
index 5f8adcd..4bb401d 100644
--- a/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart
+++ b/lib/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart
@@ -1,10 +1,7 @@
import 'package:flutter/material.dart';
-import 'package:lightmeter/data/models/feature.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/providers/remote_config_provider.dart';
-import 'package:lightmeter/providers/services_provider.dart';
import 'package:lightmeter/res/dimens.dart';
-import 'package:lightmeter/screens/settings/components/utils/show_buy_pro_dialog.dart';
+import 'package:lightmeter/screens/shared/pro_features_dialog/widget_dialog_pro_features.dart';
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
class BuyProListTile extends StatelessWidget {
@@ -12,18 +9,19 @@ class BuyProListTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final unlockFeaturesEnabled = RemoteConfig.isEnabled(context, Feature.unlockProFeaturesText);
final status = IAPProducts.productOf(context, IAPProductType.paidFeatures)?.status;
final isPending = status == IAPProductStatus.purchased || status == null;
return ListTile(
leading: const Icon(Icons.star),
- title: Text(unlockFeaturesEnabled ? S.of(context).unlockProFeatures : S.of(context).buyLightmeterPro),
- onTap: () {
- showBuyProDialog(context);
- ServicesProvider.of(context)
- .analytics
- .logUnlockProFeatures(unlockFeaturesEnabled ? 'Unlock Pro features' : 'Buy Lightmeter Pro');
- },
+ title: Text(S.of(context).unlockProFeatures),
+ onTap: !isPending
+ ? () {
+ showDialog(
+ context: context,
+ builder: (_) => const Dialog(child: ProFeaturesDialog()),
+ );
+ }
+ : null,
trailing: isPending
? const SizedBox(
height: Dimens.grid24,
diff --git a/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart b/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart
index 7050ae2..57d5d14 100644
--- a/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart
+++ b/lib/screens/settings/components/lightmeter_pro/widget_settings_section_lightmeter_pro.dart
@@ -1,7 +1,5 @@
import 'package:flutter/material.dart';
-import 'package:lightmeter/data/models/feature.dart';
import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/providers/remote_config_provider.dart';
import 'package:lightmeter/screens/settings/components/lightmeter_pro/components/buy_pro/widget_list_tile_buy_pro.dart';
import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart';
@@ -11,9 +9,7 @@ class LightmeterProSettingsSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SettingsSection(
- title: RemoteConfig.isEnabled(context, Feature.unlockProFeaturesText)
- ? S.of(context).proFeatures
- : S.of(context).lightmeterPro,
+ title: S.of(context).proFeatures,
children: const [BuyProListTile()],
);
}
diff --git a/lib/screens/settings/components/metering/components/camera_features/widget_list_tile_camera_features.dart b/lib/screens/settings/components/metering/components/camera_features/widget_list_tile_camera_features.dart
new file mode 100644
index 0000000..1446be3
--- /dev/null
+++ b/lib/screens/settings/components/metering/components/camera_features/widget_list_tile_camera_features.dart
@@ -0,0 +1,45 @@
+import 'package:flutter/material.dart';
+import 'package:lightmeter/data/models/camera_feature.dart';
+import 'package:lightmeter/generated/l10n.dart';
+import 'package:lightmeter/providers/user_preferences_provider.dart';
+import 'package:lightmeter/screens/settings/components/shared/dialog_switch/widget_dialog_switch.dart';
+import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart';
+
+class CameraFeaturesListTile extends StatelessWidget {
+ const CameraFeaturesListTile({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return IAPListTile(
+ leading: const Icon(Icons.camera_alt),
+ title: Text(S.of(context).cameraFeatures),
+ onTap: () {
+ showDialog(
+ context: context,
+ builder: (_) => DialogSwitch(
+ icon: Icons.layers_outlined,
+ title: S.of(context).cameraFeatures,
+ values: UserPreferencesProvider.cameraConfigOf(context),
+ titleAdapter: (context, feature) {
+ switch (feature) {
+ case CameraFeature.spotMetering:
+ return S.of(context).cameraFeatureSpotMetering;
+ case CameraFeature.histogram:
+ return S.of(context).cameraFeatureHistogram;
+ }
+ },
+ subtitleAdapter: (context, feature) {
+ switch (feature) {
+ case CameraFeature.spotMetering:
+ return S.of(context).cameraFeatureSpotMeteringHint;
+ case CameraFeature.histogram:
+ return S.of(context).cameraFeatureHistogramHint;
+ }
+ },
+ onSave: UserPreferencesProvider.of(context).setCameraFeature,
+ ),
+ );
+ },
+ );
+ }
+}
diff --git a/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart b/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart
index b10190f..6879a2c 100644
--- a/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart
+++ b/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart
@@ -90,7 +90,7 @@ class _EquipmentProfilesScreenState extends State {
}
void _updateProfileAt(EquipmentProfile data) {
- EquipmentProfileProvider.of(context).updateProdile(data);
+ EquipmentProfileProvider.of(context).updateProfile(data);
}
void _removeProfileAt(EquipmentProfile data) {
diff --git a/lib/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart b/lib/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart
deleted file mode 100644
index 2529e2e..0000000
--- a/lib/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart
+++ /dev/null
@@ -1,97 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
-import 'package:lightmeter/generated/l10n.dart';
-import 'package:lightmeter/providers/equipment_profile_provider.dart';
-import 'package:lightmeter/providers/films_provider.dart';
-import 'package:lightmeter/providers/user_preferences_provider.dart';
-import 'package:lightmeter/res/dimens.dart';
-import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
-
-class MeteringScreenLayoutFeaturesDialog extends StatefulWidget {
- const MeteringScreenLayoutFeaturesDialog({super.key});
-
- @override
- State createState() => _MeteringScreenLayoutFeaturesDialogState();
-}
-
-class _MeteringScreenLayoutFeaturesDialogState extends State {
- late final _features = MeteringScreenLayoutConfig.from(UserPreferencesProvider.meteringScreenConfigOf(context));
-
- @override
- Widget build(BuildContext context) {
- return AlertDialog(
- icon: const Icon(Icons.layers_outlined),
- titlePadding: Dimens.dialogIconTitlePadding,
- title: Text(S.of(context).meteringScreenLayout),
- contentPadding: EdgeInsets.zero,
- content: SizedBox(
- width: double.maxFinite,
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL),
- child: Text(S.of(context).meteringScreenLayoutHint),
- ),
- const SizedBox(height: Dimens.grid16),
- ListView(
- shrinkWrap: true,
- children: [
- _featureListTile(MeteringScreenLayoutFeature.equipmentProfiles),
- _featureListTile(MeteringScreenLayoutFeature.extremeExposurePairs),
- _featureListTile(MeteringScreenLayoutFeature.filmPicker),
- _featureListTile(MeteringScreenLayoutFeature.histogram),
- ],
- ),
- ],
- ),
- ),
- actionsPadding: Dimens.dialogActionsPadding,
- actions: [
- TextButton(
- onPressed: Navigator.of(context).pop,
- child: Text(S.of(context).cancel),
- ),
- TextButton(
- onPressed: () {
- if (!_features[MeteringScreenLayoutFeature.equipmentProfiles]!) {
- EquipmentProfileProvider.of(context).setProfile(EquipmentProfiles.of(context).first);
- }
- if (!_features[MeteringScreenLayoutFeature.filmPicker]!) {
- FilmsProvider.of(context).setFilm(const Film.other());
- }
- UserPreferencesProvider.of(context).setMeteringScreenLayout(_features);
- Navigator.of(context).pop();
- },
- child: Text(S.of(context).save),
- ),
- ],
- );
- }
-
- 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).meteringScreenLayoutHintEquipmentProfiles;
- case MeteringScreenLayoutFeature.extremeExposurePairs:
- return S.of(context).meteringScreenFeatureExtremeExposurePairs;
- case MeteringScreenLayoutFeature.filmPicker:
- return S.of(context).meteringScreenFeatureFilmPicker;
- case MeteringScreenLayoutFeature.histogram:
- return S.of(context).meteringScreenFeatureHistogram;
- }
- }
-}
diff --git a/lib/screens/settings/components/metering/components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart b/lib/screens/settings/components/metering/components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart
index a540926..9dfb026 100644
--- a/lib/screens/settings/components/metering/components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart
+++ b/lib/screens/settings/components/metering/components/metering_screen_layout/widget_list_tile_metering_screen_layout.dart
@@ -1,7 +1,12 @@
import 'package:flutter/material.dart';
+import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/generated/l10n.dart';
-
-import 'package:lightmeter/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart';
+import 'package:lightmeter/providers/equipment_profile_provider.dart';
+import 'package:lightmeter/providers/films_provider.dart';
+import 'package:lightmeter/providers/user_preferences_provider.dart';
+import 'package:lightmeter/screens/settings/components/shared/dialog_switch/widget_dialog_switch.dart';
+import 'package:lightmeter/utils/context_utils.dart';
+import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class MeteringScreenLayoutListTile extends StatelessWidget {
const MeteringScreenLayoutListTile({super.key});
@@ -14,9 +19,44 @@ class MeteringScreenLayoutListTile extends StatelessWidget {
onTap: () {
showDialog(
context: context,
- builder: (_) => const MeteringScreenLayoutFeaturesDialog(),
+ builder: (_) => DialogSwitch(
+ icon: Icons.layers_outlined,
+ title: S.of(context).meteringScreenLayout,
+ description: S.of(context).meteringScreenLayoutHint,
+ values: UserPreferencesProvider.meteringScreenConfigOf(context),
+ titleAdapter: _toStringLocalized,
+ enabledAdapter: (value) {
+ switch (value) {
+ case MeteringScreenLayoutFeature.equipmentProfiles:
+ case MeteringScreenLayoutFeature.filmPicker:
+ return context.isPro;
+ default:
+ return true;
+ }
+ },
+ onSave: (value) {
+ if (!value[MeteringScreenLayoutFeature.equipmentProfiles]!) {
+ EquipmentProfileProvider.of(context).setProfile(EquipmentProfiles.of(context).first);
+ }
+ if (!value[MeteringScreenLayoutFeature.filmPicker]!) {
+ FilmsProvider.of(context).setFilm(const Film.other());
+ }
+ UserPreferencesProvider.of(context).setMeteringScreenLayout(value);
+ },
+ ),
);
},
);
}
+
+ String _toStringLocalized(BuildContext context, MeteringScreenLayoutFeature feature) {
+ switch (feature) {
+ case MeteringScreenLayoutFeature.equipmentProfiles:
+ return S.of(context).meteringScreenLayoutHintEquipmentProfiles;
+ case MeteringScreenLayoutFeature.extremeExposurePairs:
+ return S.of(context).meteringScreenFeatureExtremeExposurePairs;
+ case MeteringScreenLayoutFeature.filmPicker:
+ return S.of(context).meteringScreenFeatureFilmPicker;
+ }
+ }
}
diff --git a/lib/screens/settings/components/metering/components/show_ev_100/widget_list_tile_show_ev_100.dart b/lib/screens/settings/components/metering/components/show_ev_100/widget_list_tile_show_ev_100.dart
new file mode 100644
index 0000000..a92623e
--- /dev/null
+++ b/lib/screens/settings/components/metering/components/show_ev_100/widget_list_tile_show_ev_100.dart
@@ -0,0 +1,24 @@
+import 'package:flutter/material.dart';
+import 'package:lightmeter/generated/l10n.dart';
+import 'package:lightmeter/providers/user_preferences_provider.dart';
+import 'package:lightmeter/res/dimens.dart';
+import 'package:lightmeter/screens/settings/components/shared/disable/widget_disable.dart';
+import 'package:lightmeter/utils/context_utils.dart';
+
+class ShowEv100ListTile extends StatelessWidget {
+ const ShowEv100ListTile({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Disable(
+ disable: !context.isPro,
+ child: SwitchListTile(
+ secondary: const Icon(Icons.adjust),
+ title: Text(S.of(context).showEv100),
+ value: context.isPro && UserPreferencesProvider.showEv100Of(context),
+ onChanged: (_) => UserPreferencesProvider.of(context).toggleShowEv100(),
+ contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
+ ),
+ );
+ }
+}
diff --git a/lib/screens/settings/components/metering/widget_settings_section_metering.dart b/lib/screens/settings/components/metering/widget_settings_section_metering.dart
index 90de68d..becf531 100644
--- a/lib/screens/settings/components/metering/widget_settings_section_metering.dart
+++ b/lib/screens/settings/components/metering/widget_settings_section_metering.dart
@@ -1,10 +1,12 @@
import 'package:flutter/material.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/camera_features/widget_list_tile_camera_features.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/films/widget_list_tile_films.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/show_ev_100/widget_list_tile_show_ev_100.dart';
import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart';
class MeteringSettingsSection extends StatelessWidget {
@@ -17,9 +19,11 @@ class MeteringSettingsSection extends StatelessWidget {
children: const [
StopTypeListTile(),
CalibrationListTile(),
+ ShowEv100ListTile(),
MeteringScreenLayoutListTile(),
EquipmentProfilesListTile(),
FilmsListTile(),
+ CameraFeaturesListTile(),
],
);
}
diff --git a/lib/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart b/lib/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart
index 76cd729..eb03e23 100644
--- a/lib/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart
+++ b/lib/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:flutter/scheduler.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/res/dimens.dart';
@@ -34,18 +35,20 @@ class _DialogFilterState extends State> {
bool get _hasAnySelected => checkboxValues.contains(true);
bool get _hasAnyUnselected => checkboxValues.contains(false);
- late final ScrollController _scrollController;
+ final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
- int i = 0;
- for (; i < checkboxValues.length; i++) {
- if (checkboxValues[i]) {
- break;
+ SchedulerBinding.instance.addPostFrameCallback((_) {
+ int i = 0;
+ for (; i < checkboxValues.length; i++) {
+ if (checkboxValues[i]) {
+ break;
+ }
}
- }
- _scrollController = ScrollController(initialScrollOffset: Dimens.grid56 * i);
+ _scrollController.jumpTo((Dimens.grid56 * i).clamp(0, _scrollController.position.maxScrollExtent));
+ });
}
@override
@@ -61,79 +64,80 @@ class _DialogFilterState extends State> {
titlePadding: Dimens.dialogIconTitlePadding,
title: Text(widget.title),
contentPadding: EdgeInsets.zero,
- content: Column(
- children: [
- Padding(
- padding: Dimens.dialogIconTitlePadding,
- child: Text(widget.description),
- ),
- const Divider(),
- Expanded(
- child: SingleChildScrollView(
- controller: _scrollController,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- mainAxisSize: MainAxisSize.min,
- children: List.generate(
- widget.values.length,
- (index) => CheckboxListTile(
- value: checkboxValues[index],
- controlAffinity: ListTileControlAffinity.leading,
- title: Text(
- widget.titleAdapter(context, widget.values[index]),
- style: Theme.of(context).textTheme.bodyLarge,
+ content: SizedBox(
+ width: double.maxFinite,
+ child: Column(
+ children: [
+ Padding(
+ padding: Dimens.dialogIconTitlePadding,
+ child: Text(widget.description),
+ ),
+ const Divider(),
+ Expanded(
+ child: SingleChildScrollView(
+ controller: _scrollController,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ mainAxisSize: MainAxisSize.min,
+ children: List.generate(
+ widget.values.length,
+ (index) => CheckboxListTile(
+ value: checkboxValues[index],
+ controlAffinity: ListTileControlAffinity.leading,
+ title: Text(
+ widget.titleAdapter(context, widget.values[index]),
+ style: Theme.of(context).textTheme.bodyLarge,
+ ),
+ onChanged: (value) {
+ if (value != null) {
+ setState(() {
+ checkboxValues[index] = value;
+ });
+ }
+ },
),
- onChanged: (value) {
- if (value != null) {
- setState(() {
- checkboxValues[index] = value;
- });
- }
- },
),
),
),
),
- ),
- const Divider(),
- Padding(
- padding: Dimens.dialogActionsPadding,
- child: Row(
- children: [
- SizedBox(
- width: 40,
- child: IconButton(
- padding: EdgeInsets.zero,
- icon: Icon(_hasAnyUnselected ? Icons.select_all : Icons.deselect),
- onPressed: _toggleAll,
- tooltip: _hasAnyUnselected
- ? S.of(context).tooltipSelectAll
- : S.of(context).tooltipDesecelectAll,
+ const Divider(),
+ Padding(
+ padding: Dimens.dialogActionsPadding,
+ child: Row(
+ children: [
+ SizedBox(
+ width: 40,
+ child: IconButton(
+ padding: EdgeInsets.zero,
+ icon: Icon(_hasAnyUnselected ? Icons.select_all : Icons.deselect),
+ onPressed: _toggleAll,
+ tooltip: _hasAnyUnselected ? S.of(context).tooltipSelectAll : S.of(context).tooltipDesecelectAll,
+ ),
),
- ),
- const Spacer(),
- TextButton(
- onPressed: Navigator.of(context).pop,
- child: Text(S.of(context).cancel),
- ),
- TextButton(
- onPressed: _hasAnySelected
- ? () {
- final List selectedValues = [];
- for (int i = 0; i < widget.values.length; i++) {
- if (checkboxValues[i]) {
- selectedValues.add(widget.values[i]);
+ const Spacer(),
+ TextButton(
+ onPressed: Navigator.of(context).pop,
+ child: Text(S.of(context).cancel),
+ ),
+ TextButton(
+ onPressed: _hasAnySelected
+ ? () {
+ final List selectedValues = [];
+ for (int i = 0; i < widget.values.length; i++) {
+ if (checkboxValues[i]) {
+ selectedValues.add(widget.values[i]);
+ }
}
+ Navigator.of(context).pop(selectedValues);
}
- Navigator.of(context).pop(selectedValues);
- }
- : null,
- child: Text(S.of(context).save),
- ),
- ],
- ),
- )
- ],
+ : null,
+ child: Text(S.of(context).save),
+ ),
+ ],
+ ),
+ )
+ ],
+ ),
),
);
}
diff --git a/lib/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart b/lib/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart
index c893027..dc31b5c 100644
--- a/lib/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart
+++ b/lib/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart
@@ -32,24 +32,28 @@ class _DialogPickerState extends State> {
titlePadding: Dimens.dialogIconTitlePadding,
title: Text(widget.title),
contentPadding: EdgeInsets.zero,
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: widget.values
- .map(
- (e) => RadioListTile(
- value: e,
- groupValue: _selected,
- title: Text(widget.titleAdapter(context, e)),
- onChanged: (T? value) {
- if (value != null) {
- setState(() {
- _selected = value;
- });
- }
- },
- ),
- )
- .toList(),
+ content: SizedBox(
+ width: double.maxFinite,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ mainAxisSize: MainAxisSize.min,
+ children: widget.values
+ .map(
+ (e) => RadioListTile(
+ value: e,
+ groupValue: _selected,
+ title: Text(widget.titleAdapter(context, e)),
+ onChanged: (T? value) {
+ if (value != null) {
+ setState(() {
+ _selected = value;
+ });
+ }
+ },
+ ),
+ )
+ .toList(),
+ ),
),
actionsPadding: Dimens.dialogActionsPadding,
actions: [
diff --git a/lib/screens/settings/components/shared/dialog_range_picker/widget_dialog_picker_range.dart b/lib/screens/settings/components/shared/dialog_range_picker/widget_dialog_picker_range.dart
index cc3f4ca..56f7967 100644
--- a/lib/screens/settings/components/shared/dialog_range_picker/widget_dialog_picker_range.dart
+++ b/lib/screens/settings/components/shared/dialog_range_picker/widget_dialog_picker_range.dart
@@ -36,47 +36,50 @@ class _DialogRangePickerState extends State = String Function(BuildContext context, T value);
+
+class DialogSwitch extends StatefulWidget {
+ final IconData icon;
+ final String title;
+ final String? description;
+ final Map values;
+ final StringAdapter titleAdapter;
+ final StringAdapter? subtitleAdapter;
+ final bool Function(T value)? enabledAdapter;
+ final ValueChanged