Compare commits

...

12 commits

Author SHA1 Message Date
Vadim
2a7dad2c38 migrated to artifacts v4 2025-01-23 18:33:30 +01:00
Vadim
2ee4d3ef53 Merge branch 'cd' of https://github.com/vodemn/m3_lightmeter into cd 2025-01-23 18:32:55 +01:00
Vadim
9a2ef35eed moved firebase options to gitignore 2025-01-23 18:32:12 +01:00
Vadim
cd752aaf89 restore firebase options in workflows 2025-01-23 18:32:12 +01:00
Vadim
b614af9a97 moved firebase options to gitignore 2025-01-23 18:14:53 +01:00
Vadim
85b84000ae restore firebase options in workflows 2025-01-23 18:14:02 +01:00
Vadim
43989e5e5f Merge branch 'cd' of https://github.com/vodemn/m3_lightmeter into cd 2025-01-23 18:10:22 +01:00
Vadim
8ecab836a3 ML-209 Camera preview orientation is wrong (#210)
* lock orientation to `portraitUp`

* removed custom rotator for camera preview
2025-01-23 18:05:39 +01:00
Vadim
2439f7bfff Fixed builds being pushed to main (#211) 2025-01-23 18:05:39 +01:00
Vadim
bccd68f1ff fixed builds being pushed to main 2025-01-23 17:52:46 +01:00
github-actions[bot]
5e8f66d75c Release v1.0.1 2025-01-21 19:04:54 +00:00
Vadim
75dc9aaf13
Added builds to CI (#208)
* Update README.md

* Set exact Flutter version for workflows

* Added stub `DefaultFirebaseOptions`

* Fixed `rm`

* Removed `rm`

* Update .gitignore

* Added readable name to ci workflow

* Build -> Development

* Update ci.yml

* Extract merged native libraries

* More descriptive run name

* Delete no longer used artifacts

* Replaced "Build ..." flow with "Create release"

* renamed other flows

* try using script for iap stub

* typo

* typo

* typo

* removed working dir

* added comment to stub_iap.sh

* checkout first

* increment build number by script

* Update increment_build_number.sh

* fixed iap repo

* stub

* updated stub script to work with tags

* depend on step conclusion

* check PR number

* run integration tests before build

* reuse Build Android workflow

* added stage backend option

* reuse Build iOS workflow

* temporeraly skip release jobs

* [ios] use distribution profile for release builds

* temporary skip tests

* typo

* checkout actions

* incremented macos runner version

* Restore GoogleService-Info.plist

* Restore firebase_app_id_file.json

* style

* separated android and ios builds

* fixed invalid workflow

* simplified release workflow tree

* fixed android keystore path

* enabled integration tests

* added option to skip integration tests

* fixed android folders...

* enabled releases

* increment build number for ios

* upload ipa to app store

* test ipa upload

* typo

* try to force ipa upload

* removed flavor from ipa artefact name

* try manual ipa upload

* switched to ubuntu for upload

* decode to repo

* Update create_release.yml

* auth with username + password

* reverted temporary settings

* typo

* disable pre-release integration tests by default

* fixed integration tests

* increased integration tests timeout

* delete ipa after upload

* delete all artifacts after the run

* fixed integration tests

* reduce integration tests timeout

* build apk with latest macos runner

* allow to skip release to one of the stores

* added build checks to PR checks

* fixed inputs naming

* increased jvm heap
2025-01-21 19:43:14 +01:00
13 changed files with 147 additions and 170 deletions

View file

@ -115,7 +115,7 @@ jobs:
- name: Download release notes - name: Download release notes
continue-on-error: true continue-on-error: true
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }} name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }}
path: ${{ env.RELEASE_NOTES_PATH }} path: ${{ env.RELEASE_NOTES_PATH }}
@ -136,7 +136,7 @@ jobs:
run: flutter build ${{ inputs.binary-type }} $BUILD_ARGS run: flutter build ${{ inputs.binary-type }} $BUILD_ARGS
- name: Upload ${{ inputs.binary-type }} to artifacts - name: Upload ${{ inputs.binary-type }} to artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: m3_lightmeter_${{ inputs.binary-type }} name: m3_lightmeter_${{ inputs.binary-type }}
path: ${{ inputs.binary-type == 'apk' && env.BUILD_APK_PATH || env.BUILD_AAB_PATH }} path: ${{ inputs.binary-type == 'apk' && env.BUILD_APK_PATH || env.BUILD_AAB_PATH }}

View file

@ -108,7 +108,7 @@ jobs:
- name: Download release notes - name: Download release notes
continue-on-error: true continue-on-error: true
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }} name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }}
path: ${{ env.RELEASE_NOTES_PATH }} path: ${{ env.RELEASE_NOTES_PATH }}
@ -135,7 +135,7 @@ jobs:
--export-options-plist=ios/Runner/ExportOptions.plist --export-options-plist=ios/Runner/ExportOptions.plist
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: m3_lightmeter_ipa name: m3_lightmeter_ipa
path: build/ios/ipa/lightmeter.ipa path: build/ios/ipa/lightmeter.ipa

View file

@ -27,6 +27,16 @@ on:
required: true required: true
type: boolean type: boolean
default: true default: true
deploy-ios:
description: "Publish to App Store"
required: true
type: boolean
default: true
deploy-android:
description: "Publish to Google Play"
required: true
type: boolean
default: true
release-track: release-track:
description: "Release track" description: "Release track"
type: choice type: choice
@ -59,7 +69,7 @@ jobs:
echo ${{ inputs.release-notes }} > ${{ env.RELEASE_NOTES_FILE }} echo ${{ inputs.release-notes }} > ${{ env.RELEASE_NOTES_FILE }}
perl -i -pe 's/\s{1}(-{1})/\n$1/g' ${{ env.RELEASE_NOTES_FILE }} perl -i -pe 's/\s{1}(-{1})/\n$1/g' ${{ env.RELEASE_NOTES_FILE }}
- name: Upload merged_native_libs.zip to artifacts - name: Upload merged_native_libs.zip to artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }} name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }}
path: ${{ env.RELEASE_NOTES_FILE }} path: ${{ env.RELEASE_NOTES_FILE }}
@ -67,7 +77,7 @@ jobs:
build-android: build-android:
name: Build Android name: Build Android
needs: [generate-release-notes] needs: [generate-release-notes]
if: ${{ always() && !failure() && !cancelled() }} if: ${{ always() && !failure() && !cancelled() && inputs.deploy-android }}
strategy: strategy:
matrix: matrix:
binary-type: [apk, appbundle] binary-type: [apk, appbundle]
@ -82,7 +92,7 @@ jobs:
build-ios: build-ios:
name: Build iOS name: Build iOS
needs: [generate-release-notes] needs: [generate-release-notes]
if: ${{ always() && !failure() && !cancelled() }} if: ${{ always() && !failure() && !cancelled() && inputs.deploy-ios }}
uses: ./.github/workflows/build_ipa.yml uses: ./.github/workflows/build_ipa.yml
secrets: inherit secrets: inherit
with: with:
@ -92,7 +102,7 @@ jobs:
create-github-release: create-github-release:
name: Create Github release name: Create Github release
needs: [build-android, build-ios] needs: [build-android, build-ios]
if: ${{ always() && !failure() && !cancelled() }} if: ${{ always() && !cancelled() && inputs.deploy-android}}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
@ -102,24 +112,24 @@ jobs:
submodules: recursive submodules: recursive
- name: Download apk - name: Download apk
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: m3_lightmeter_apk name: m3_lightmeter_apk
- name: Increment build number & replace version number
run: bash ./.github/scripts/increment_build_number.sh ${{ github.event.inputs.version }}
- name: Download release notes - name: Download release notes
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }} name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }}
path: ${{ env.RELEASE_NOTES_PATH }} path: ${{ env.RELEASE_NOTES_PATH }}
- name: Increment build number & replace version number
run: bash ./.github/scripts/increment_build_number.sh ${{ github.event.inputs.version }}
- name: Commit changes - name: Commit changes
run: | run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]" git config --local user.name "github-actions[bot]"
git add -A git add --all -- ":!app-prod-release.apk"
git commit -m "Release v${{ inputs.version }}" git commit -m "Release v${{ inputs.version }}"
- name: Push to main - name: Push to main
@ -143,7 +153,7 @@ jobs:
create-google-play-release: create-google-play-release:
name: Create Google Play release name: Create Google Play release
needs: [build-android, build-ios] needs: [build-android, build-ios]
if: ${{ always() && !failure() && !cancelled() }} if: ${{ always() && !failure() && !cancelled() && inputs.deploy-android }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -151,7 +161,7 @@ jobs:
submodules: recursive submodules: recursive
- name: Download app bundle - name: Download app bundle
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: m3_lightmeter_appbundle name: m3_lightmeter_appbundle
@ -161,7 +171,7 @@ jobs:
(cd base/lib && zip -r "$OLDPWD/merged_native_libs.zip" .) (cd base/lib && zip -r "$OLDPWD/merged_native_libs.zip" .)
- name: Download release notes - name: Download release notes
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }} name: ${{ env.RELEASE_NOTES_ARTIFACT_NAME }}
@ -195,7 +205,7 @@ jobs:
upload-to-app-store: upload-to-app-store:
name: Upload to App Store name: Upload to App Store
needs: [build-android, build-ios] needs: [build-android, build-ios]
if: ${{ always() && !failure() && !cancelled() }} if: ${{ always() && !failure() && !cancelled() && inputs.deploy-ios }}
runs-on: macos-13 runs-on: macos-13
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -203,7 +213,7 @@ jobs:
submodules: recursive submodules: recursive
- name: Download ipa - name: Download ipa
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: m3_lightmeter_ipa name: m3_lightmeter_ipa

View file

@ -36,8 +36,10 @@ jobs:
if: steps.fetch-iap.conclusion != 'success' if: steps.fetch-iap.conclusion != 'success'
run: bash ./.github/scripts/stub_iap.sh run: bash ./.github/scripts/stub_iap.sh
- name: Restore constants.dart - name: Restore secrets
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.CONSTANTS }}" "lib/constants.dart" run: |
bash .github/scripts/restore_from_base64.sh "${{ secrets.CONSTANTS }}" "lib/constants.dart"
bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_OPTIONS }}" "lib/firebase_options.dart"
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
@ -64,3 +66,62 @@ jobs:
bash ./.github/scripts/stub_iap.sh bash ./.github/scripts/stub_iap.sh
flutter pub get flutter pub get
flutter analyze lib --fatal-infos flutter analyze lib --fatal-infos
platform-changes:
name: Checks for platform changes
runs-on: ubuntu-latest
outputs:
android-changed: ${{ steps.platform-changes.outputs.android-changed }}
ios-changed: ${{ steps.platform-changes.outputs.ios-changed }}
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- id: platform-changes
uses: dorny/paths-filter@v2
with:
filters: |
android-changed:
- 'android/**'
- 'pubspec.yaml'
ios-changed:
- 'ios/**'
- 'pubspec.yaml'
build-android:
name: Build Android
needs: platform-changes
if: needs.platform-changes.outputs.android-changed == 'true'
uses: ./.github/workflows/build_apk.yml
secrets: inherit
with:
binary-type: apk
flavor: prod
stage-backend: false
version: "1.0.0"
build-ios:
name: Build iOS
needs: platform-changes
if: needs.platform-changes.outputs.ios-changed == 'true'
uses: ./.github/workflows/build_ipa.yml
secrets: inherit
with:
stage-backend: false
version: "1.0.0"
cleanup:
name: Cleanup
if: ${{ always() }}
needs: [build-android, build-ios]
runs-on: ubuntu-latest
steps:
- name: Delete release artifacts
uses: geekyeggo/delete-artifact@v2
with:
failOnError: false
name: |
m3_lightmeter_apk
m3_lightmeter_appbundle
m3_lightmeter_ipa

View file

@ -27,8 +27,9 @@ jobs:
bash .github/scripts/restore_from_base64.sh "${{ secrets.CONSTANTS }}" "lib/constants.dart" bash .github/scripts/restore_from_base64.sh "${{ secrets.CONSTANTS }}" "lib/constants.dart"
bash .github/scripts/restore_from_base64.sh "${{ secrets.GOOGLE_SERVICES_JSON_IOS }}" "ios/Runner/GoogleService-Info.plist" bash .github/scripts/restore_from_base64.sh "${{ secrets.GOOGLE_SERVICES_JSON_IOS }}" "ios/Runner/GoogleService-Info.plist"
bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_APP_ID_FILE }}" "ios/firebase_app_id_file.json" bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_APP_ID_FILE }}" "ios/firebase_app_id_file.json"
bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_OPTIONS }}" "lib/firebase_options.dart"
bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_JSON }}" "firebase.json" bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_JSON }}" "firebase.json"
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
channel: "stable" channel: "stable"

4
.gitignore vendored
View file

@ -58,8 +58,8 @@ keystore.properties
android/app/google-services.json android/app/google-services.json
ios/firebase_app_id_file.json ios/firebase_app_id_file.json
ios/Runner/GoogleService-Info.plist ios/Runner/GoogleService-Info.plist
/lib/firebase_options.dart lib/firebase_options.dart
/lib/constants.dart lib/constants.dart
coverage/ coverage/
test/coverage_helper_test.dart test/coverage_helper_test.dart

View file

@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx2048M
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true android.defaults.buildfeatures.buildconfig=true

View file

@ -0,0 +1 @@
- Performance improvements and bug fixes.

View file

@ -1,67 +0,0 @@
// File generated by FlutterFire CLI.
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for web - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for macos - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.windows:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for windows - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions android = FirebaseOptions(
apiKey: '',
messagingSenderId: '',
projectId: '',
storageBucket: '',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: '',
appId: '',
messagingSenderId: '',
projectId: '',
storageBucket: '',
iosClientId: '',
iosBundleId: '',
);
}

View file

@ -132,6 +132,7 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
await _cameraController!.initialize(); await _cameraController!.initialize();
await _cameraController!.setFlashMode(FlashMode.off); await _cameraController!.setFlashMode(FlashMode.off);
await _cameraController!.lockCaptureOrientation(DeviceOrientation.portraitUp);
_zoomRange = await Future.wait<double>([ _zoomRange = await Future.wait<double>([
_cameraController!.getMinZoomLevel(), _cameraController!.getMinZoomLevel(),

View file

@ -1,50 +0,0 @@
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class CameraView extends StatelessWidget {
final CameraController controller;
const CameraView({required this.controller, super.key});
@override
Widget build(BuildContext context) {
final value = controller.value;
return ValueListenableBuilder<CameraValue>(
valueListenable: controller,
builder: (_, __, Widget? child) => AspectRatio(
aspectRatio: _isLandscape(value) ? value.aspectRatio : (1 / value.aspectRatio),
child: Stack(
children: [
RotatedBox(
quarterTurns: _getQuarterTurns(value),
child: controller.buildPreview(),
),
child ?? const SizedBox(),
],
),
),
);
}
bool _isLandscape(CameraValue value) {
return <DeviceOrientation>[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]
.contains(_getApplicableOrientation(value));
}
int _getQuarterTurns(CameraValue value) {
final Map<DeviceOrientation, int> turns = <DeviceOrientation, int>{
DeviceOrientation.portraitUp: 0,
DeviceOrientation.landscapeRight: 1,
DeviceOrientation.portraitDown: 2,
DeviceOrientation.landscapeLeft: 3,
};
return turns[_getApplicableOrientation(value)]!;
}
DeviceOrientation _getApplicableOrientation(CameraValue value) {
return value.isRecordingVideo
? value.recordingOrientation!
: (value.previewPauseOrientation ?? value.lockedCaptureOrientation ?? value.deviceOrientation);
}
}

View file

@ -1,18 +1,17 @@
import 'package:camera/camera.dart'; import 'package:camera/camera.dart' as camera;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/camera_feature.dart'; import 'package:lightmeter/data/models/camera_feature.dart';
import 'package:lightmeter/platform_config.dart'; import 'package:lightmeter/platform_config.dart';
import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/res/dimens.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_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/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/components/camera_preview/components/histogram/widget_histogram.dart';
import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart'; import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart';
import 'package:lightmeter/utils/context_utils.dart'; import 'package:lightmeter/utils/context_utils.dart';
class CameraPreview extends StatefulWidget { class CameraPreview extends StatefulWidget {
final CameraController? controller; final camera.CameraController? controller;
final CameraErrorType? error; final CameraErrorType? error;
final ValueChanged<Offset?> onSpotTap; final ValueChanged<Offset?> onSpotTap;
@ -53,7 +52,7 @@ class _CameraPreviewState extends State<CameraPreview> {
} }
class _CameraPreviewBuilder extends StatefulWidget { class _CameraPreviewBuilder extends StatefulWidget {
final CameraController controller; final camera.CameraController controller;
final ValueChanged<Offset?> onSpotTap; final ValueChanged<Offset?> onSpotTap;
const _CameraPreviewBuilder({ const _CameraPreviewBuilder({
@ -97,29 +96,15 @@ class _CameraPreviewBuilderState extends State<_CameraPreviewBuilder> {
} }
return ValueListenableBuilder<bool>( return ValueListenableBuilder<bool>(
valueListenable: _initializedNotifier, valueListenable: _initializedNotifier,
builder: (context, value, child) => value builder: (context, value, _) => value
? Stack( ? camera.CameraPreview(
alignment: Alignment.bottomCenter, widget.controller,
children: [ child: context.isPro
CameraView(controller: widget.controller), ? _ProFeaturesOverlay(
if (context.isPro) ...[ controller: widget.controller,
if (UserPreferencesProvider.cameraFeatureOf( onSpotTap: widget.onSpotTap,
context, )
CameraFeature.histogram, : const SizedBox.shrink(),
))
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(), : const SizedBox.shrink(),
); );
@ -129,3 +114,38 @@ class _CameraPreviewBuilderState extends State<_CameraPreviewBuilder> {
_initializedNotifier.value = widget.controller.value.isInitialized; _initializedNotifier.value = widget.controller.value.isInitialized;
} }
} }
class _ProFeaturesOverlay extends StatelessWidget {
final camera.CameraController controller;
final ValueChanged<Offset?> onSpotTap;
const _ProFeaturesOverlay({
required this.controller,
required this.onSpotTap,
});
@override
Widget build(BuildContext context) {
final bool hasHistogram = UserPreferencesProvider.cameraFeatureOf(
context,
CameraFeature.histogram,
);
final bool hasSpotMetering = UserPreferencesProvider.cameraFeatureOf(
context,
CameraFeature.histogram,
);
return Stack(
alignment: Alignment.bottomCenter,
children: [
if (hasHistogram)
Positioned(
left: Dimens.grid8,
right: Dimens.grid8,
bottom: Dimens.grid16,
child: CameraHistogram(controller: controller),
),
if (hasSpotMetering) CameraSpotDetector(onSpotTap: onSpotTap),
],
);
}
}

View file

@ -1,7 +1,7 @@
name: lightmeter name: lightmeter
description: Lightmeter app inspired by Material 3 design system. description: Lightmeter app inspired by Material 3 design system.
publish_to: "none" publish_to: "none"
version: 1.0.0+55 version: 1.0.1+56
environment: environment:
sdk: ">=3.0.0 <4.0.0" sdk: ">=3.0.0 <4.0.0"