mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2025-07-04 13:10:42 +00:00
Compare commits
No commits in common. "1d2f858ea0951c79f60768fda6b8bb4a5f71696a" and "a42459de07627f0edfdda80c5bd013b50a9f1dc4" have entirely different histories.
1d2f858ea0
...
a42459de07
31 changed files with 212 additions and 327 deletions
14
.github/scripts/restore_from_base64.sh
vendored
14
.github/scripts/restore_from_base64.sh
vendored
|
@ -1,14 +0,0 @@
|
|||
content="$1"
|
||||
filename="$2"
|
||||
|
||||
if [[ ! -n "$content" ]]; then
|
||||
echo "Provide file content"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -n "$filename" ]]; then
|
||||
echo "Provide a path to an output file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -n "$content" | base64 --decode --output "$filename"
|
74
.github/workflows/build_apk.yml
vendored
74
.github/workflows/build_apk.yml
vendored
|
@ -3,17 +3,27 @@
|
|||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
name: Build Prod .ipa
|
||||
name: Build .apk
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FLAVOR: "prod"
|
||||
inputs:
|
||||
flavor:
|
||||
description: 'Flavor'
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- dev
|
||||
- prod
|
||||
default: 'dev'
|
||||
include-iap:
|
||||
type: boolean
|
||||
description: Include IAP package
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build .ipa
|
||||
name: Build .apk
|
||||
runs-on: macos-11
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
|
@ -23,46 +33,66 @@ jobs:
|
|||
|
||||
- name: Connect private iap package
|
||||
uses: webfactory/ssh-agent@v0.8.0
|
||||
if: ${{ inputs.include-iap }}
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
|
||||
|
||||
- name: Override iap package with stub
|
||||
if: ${{ !inputs.include-iap }}
|
||||
run: bash ./.github/scripts/stub_iap.sh
|
||||
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: "zulu"
|
||||
java-version: "11"
|
||||
|
||||
- name: Install Apple Certificate
|
||||
uses: apple-actions/import-codesign-certs@v1
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.APP_STORE_P12 }}
|
||||
p12-password: ${{ secrets.APP_STORE_P12_PASSWORD }}
|
||||
- name: Install the provisioning profile
|
||||
- name: Restore Android keystore .jsk and .properties files
|
||||
env:
|
||||
KEYSTORE: ${{ secrets.KEYSTORE }}
|
||||
KEYSTORE_PROPERTIES: ${{ secrets.KEYSTORE_PROPERTIES }}
|
||||
run: |
|
||||
PROVISION_CERT_PATH=$RUNNER_TEMP/provision_prod.mobileprovision
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.APP_STORE_PROVISION_PROD }}" $PROVISION_CERT_PATH
|
||||
mkdir -p ~/Library/MobileDevice\ Profiles
|
||||
cp $PROVISION_CERT_PATH ~/Library/MobileDevice\ Profiles
|
||||
KEYSTORE_PATH=$RUNNER_TEMP/keystore.jks
|
||||
echo -n "$KEYSTORE" | base64 --decode --output $KEYSTORE_PATH
|
||||
cp $KEYSTORE_PATH ./android/app
|
||||
KEYSTORE_PROPERTIES_PATH=$RUNNER_TEMP/key.properties
|
||||
echo -n "$KEYSTORE_PROPERTIES" | base64 --decode --output $KEYSTORE_PROPERTIES_PATH
|
||||
cp $KEYSTORE_PROPERTIES_PATH ./android
|
||||
|
||||
- name: Restore android/app/google-services.json
|
||||
env:
|
||||
GOOGLE_SERVICES_JSON_ANDROID: ${{ secrets.GOOGLE_SERVICES_JSON_ANDROID }}
|
||||
run: |
|
||||
GOOGLE_SERVICES_JSON_ANDROID_PATH=$RUNNER_TEMP/google-services.json
|
||||
echo -n "$GOOGLE_SERVICES_JSON_ANDROID" | base64 --decode --output $GOOGLE_SERVICES_JSON_ANDROID_PATH
|
||||
cp $GOOGLE_SERVICES_JSON_ANDROID_PATH ./android/app
|
||||
|
||||
- name: Restore firebase_options.dart
|
||||
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_OPTIONS }}" "lib/firebase_options.dart"
|
||||
env:
|
||||
FIREBASE_OPTIONS: ${{ secrets.FIREBASE_OPTIONS }}
|
||||
run: |
|
||||
FIREBASE_OPTIONS_PATH=$RUNNER_TEMP/firebase_options.dart
|
||||
echo -n "$FIREBASE_OPTIONS" | base64 --decode --output $FIREBASE_OPTIONS_PATH
|
||||
cp $FIREBASE_OPTIONS_PATH ./lib
|
||||
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.10.0"
|
||||
flutter-version: '3.10.0'
|
||||
|
||||
- name: Prepare flutter project
|
||||
run: |
|
||||
run: |
|
||||
flutter --version
|
||||
flutter pub get
|
||||
flutter pub run intl_utils:generate
|
||||
|
||||
- name: Build .ipa
|
||||
run: flutter build ipa --release --flavor $FLAVOR -t lib/main_$FLAVOR.dart
|
||||
- name: Build .apk
|
||||
env:
|
||||
FLAVOR: ${{ github.event.inputs.flavor }}
|
||||
run: flutter build apk --release --flavor $FLAVOR -t lib/main_$FLAVOR.dart
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: m3_lightmeter_$FLAVOR_ipa
|
||||
path: build/ios/ipa/lightmeter.ipa
|
||||
name: m3_lightmeter_${{ github.event.inputs.flavor }}
|
||||
path: build/app/outputs/flutter-apk/app-${{ github.event.inputs.flavor }}-release.apk
|
||||
|
|
68
.github/workflows/build_ipa.yml
vendored
68
.github/workflows/build_ipa.yml
vendored
|
@ -1,68 +0,0 @@
|
|||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
name: Build Prod .ipa
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FLAVOR: "prod"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build .ipa
|
||||
runs-on: macos-11
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Connect private iap package
|
||||
uses: webfactory/ssh-agent@v0.8.0
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
|
||||
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: "zulu"
|
||||
java-version: "11"
|
||||
|
||||
- name: Install Apple Certificate
|
||||
uses: apple-actions/import-codesign-certs@v1
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.APP_STORE_P12 }}
|
||||
p12-password: ${{ secrets.APP_STORE_P12_PASSWORD }}
|
||||
- name: Install the provisioning profile
|
||||
run: |
|
||||
PROVISION_CERT_PATH=$RUNNER_TEMP/provision_prod.mobileprovision
|
||||
bash .github/scripts/restore_from_base64.sh "${{ secrets.APP_STORE_PROVISION_PROD }}" $PROVISION_CERT_PATH
|
||||
mkdir -p ~/Library/MobileDevice\ Profiles
|
||||
cp $PROVISION_CERT_PATH ~/Library/MobileDevice\ Profiles
|
||||
|
||||
- name: Restore firebase_options.dart
|
||||
run: bash .github/scripts/restore_from_base64.sh "${{ secrets.FIREBASE_OPTIONS }}" "lib/firebase_options.dart"
|
||||
|
||||
- name: Install Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.10.0"
|
||||
|
||||
- name: Prepare flutter project
|
||||
run: |
|
||||
flutter --version
|
||||
flutter pub get
|
||||
flutter pub run intl_utils:generate
|
||||
|
||||
- name: Build .ipa
|
||||
run: flutter build ipa --release --flavor $FLAVOR -t lib/main_$FLAVOR.dart
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: m3_lightmeter_$FLAVOR_ipa
|
||||
path: build/ios/ipa/lightmeter.ipa
|
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
@ -35,7 +35,7 @@
|
|||
"--flavor",
|
||||
"prod",
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main_prod.dart",
|
||||
"program": "${workspaceFolder}/lib/main_release.dart",
|
||||
},
|
||||
{
|
||||
"name": "prod-profile",
|
||||
|
@ -46,7 +46,7 @@
|
|||
"--flavor",
|
||||
"prod",
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main_prod.dart",
|
||||
"program": "${workspaceFolder}/lib/main_release.dart",
|
||||
},
|
||||
{
|
||||
"name": "dev-simulator",
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<exclude domain="sharedpref" path="FlutterSecureStorage"/>
|
||||
<exclude domain="sharedpref" path="FlutterSecureKeyStorage"/>
|
||||
</cloud-backup>
|
||||
<device-transfer>
|
||||
<exclude domain="sharedpref" path="FlutterSecureStorage"/>
|
||||
<exclude domain="sharedpref" path="FlutterSecureKeyStorage"/>
|
||||
</device-transfer>
|
||||
</data-extraction-rules>
|
|
@ -399,11 +399,9 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 489Z6UQMGN;
|
||||
DEVELOPMENT_TEAM = 489Z6UQMGN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Lightmeter;
|
||||
|
@ -414,7 +412,6 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter;
|
||||
PRODUCT_NAME = Lightmeter;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = m3_lightmeter_provision;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
@ -536,11 +533,9 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 489Z6UQMGN;
|
||||
DEVELOPMENT_TEAM = 489Z6UQMGN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Lightmeter;
|
||||
|
@ -551,7 +546,6 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter;
|
||||
PRODUCT_NAME = Lightmeter;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = m3_lightmeter_provision;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
@ -567,11 +561,9 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 489Z6UQMGN;
|
||||
DEVELOPMENT_TEAM = 489Z6UQMGN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Lightmeter;
|
||||
|
@ -582,7 +574,6 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter;
|
||||
PRODUCT_NAME = Lightmeter;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = m3_lightmeter_provision;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
@ -652,11 +643,9 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 489Z6UQMGN;
|
||||
DEVELOPMENT_TEAM = 489Z6UQMGN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Lightmeter (DEV)";
|
||||
|
@ -667,7 +656,6 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter.dev;
|
||||
PRODUCT_NAME = "Lightmeter (DEV)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = m3_lightmeter_provision;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
@ -735,11 +723,9 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 489Z6UQMGN;
|
||||
DEVELOPMENT_TEAM = 489Z6UQMGN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Lightmeter (DEV)";
|
||||
|
@ -750,7 +736,6 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter.dev;
|
||||
PRODUCT_NAME = "Lightmeter (DEV)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = m3_lightmeter_provision;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
@ -815,11 +800,9 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 489Z6UQMGN;
|
||||
DEVELOPMENT_TEAM = 489Z6UQMGN;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Lightmeter (DEV)";
|
||||
|
@ -830,7 +813,6 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = com.vodemn.lightmeter.dev;
|
||||
PRODUCT_NAME = "Lightmeter (DEV)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = m3_lightmeter_provision;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
|
|
@ -26,14 +26,11 @@ class ApplicationWrapper extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final remoteConfigService = env.buildType != BuildType.dev
|
||||
? const RemoteConfigService(LightmeterAnalytics(api: LightmeterAnalyticsFirebase()))
|
||||
: const MockRemoteConfigService();
|
||||
return FutureBuilder(
|
||||
future: Future.wait<dynamic>([
|
||||
SharedPreferences.getInstance(),
|
||||
const LightSensorService(LocalPlatform()).hasSensor(),
|
||||
remoteConfigService.activeAndFetchFeatures(),
|
||||
if (env.buildType != BuildType.dev) const RemoteConfigService().activeAndFetchFeatures(),
|
||||
]),
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.data != null) {
|
||||
|
@ -50,7 +47,8 @@ class ApplicationWrapper extends StatelessWidget {
|
|||
userPreferencesService: userPreferencesService,
|
||||
volumeEventsService: const VolumeEventsService(LocalPlatform()),
|
||||
child: RemoteConfigProvider(
|
||||
remoteConfigService: remoteConfigService,
|
||||
remoteConfigService:
|
||||
env.buildType != BuildType.dev ? const RemoteConfigService() : const MockRemoteConfigService(),
|
||||
child: EquipmentProfileProvider(
|
||||
storageService: iapService,
|
||||
child: FilmsProvider(
|
||||
|
|
|
@ -3,56 +3,32 @@ import 'dart:developer';
|
|||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:lightmeter/data/analytics/api/analytics_api_interface.dart';
|
||||
import 'package:lightmeter/data/analytics/entity/analytics_event.dart';
|
||||
|
||||
class LightmeterAnalytics {
|
||||
final ILightmeterAnalyticsApi _api;
|
||||
|
||||
const LightmeterAnalytics({required ILightmeterAnalyticsApi api}) : _api = api;
|
||||
|
||||
void init() {
|
||||
FlutterError.onError = (details) {
|
||||
if (details.silent) return;
|
||||
logCrash(details.exception, details.stack);
|
||||
};
|
||||
PlatformDispatcher.instance.onError = (error, stack) {
|
||||
logCrash(error, stack);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> logEvent(
|
||||
String eventName, {
|
||||
LightmeterAnalyticsEvent event, {
|
||||
Map<String, dynamic>? parameters,
|
||||
}) async {
|
||||
if (!kReleaseMode) {
|
||||
log('<LightmeterAnalytics> logEvent: $eventName / $parameters');
|
||||
if (kDebugMode) {
|
||||
log('<LightmeterAnalytics> logEvent: ${event.name} / $parameters');
|
||||
return;
|
||||
}
|
||||
|
||||
return _api.logEvent(
|
||||
eventName,
|
||||
event: event,
|
||||
parameters: parameters,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> logCrash(
|
||||
dynamic exception,
|
||||
StackTrace? stackTrace, {
|
||||
dynamic reason,
|
||||
Iterable<Object> information = const [],
|
||||
}) async {
|
||||
log(exception.toString(), stackTrace: stackTrace);
|
||||
if (!kReleaseMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
return _api.logCrash(
|
||||
exception,
|
||||
stackTrace,
|
||||
reason: reason,
|
||||
information: information,
|
||||
Future<void> logUnlockProFeatures(String listTileTitle) async {
|
||||
return logEvent(
|
||||
LightmeterAnalyticsEvent.unlockProFeatures,
|
||||
parameters: {"listTileTitle": listTileTitle},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setCustomKey(String key, String value) async => _api.setCustomKey(key, value);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
import 'package:lightmeter/data/analytics/entity/analytics_event.dart';
|
||||
|
||||
abstract class ILightmeterAnalyticsApi {
|
||||
Future<void> logEvent(
|
||||
String eventName, {
|
||||
Future<void> logEvent({
|
||||
required LightmeterAnalyticsEvent event,
|
||||
Map<String, dynamic>? parameters,
|
||||
});
|
||||
|
||||
Future<void> logCrash(
|
||||
dynamic exception,
|
||||
StackTrace? stack, {
|
||||
dynamic reason,
|
||||
Iterable<Object> information = const [],
|
||||
});
|
||||
|
||||
Future<void> setCustomKey(String key, String value);
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:lightmeter/data/analytics/api/analytics_api_interface.dart';
|
||||
import 'package:lightmeter/data/analytics/entity/analytics_event.dart';
|
||||
|
||||
class LightmeterAnalyticsFirebase implements ILightmeterAnalyticsApi {
|
||||
const LightmeterAnalyticsFirebase();
|
||||
|
||||
@override
|
||||
Future<void> logEvent(
|
||||
String eventName, {
|
||||
Future<void> logEvent({
|
||||
required LightmeterAnalyticsEvent event,
|
||||
Map<String, dynamic>? parameters,
|
||||
}) async {
|
||||
try {
|
||||
await FirebaseAnalytics.instance.logEvent(
|
||||
name: eventName,
|
||||
name: event.name,
|
||||
parameters: parameters,
|
||||
);
|
||||
} on FirebaseException catch (e) {
|
||||
|
@ -23,25 +23,4 @@ class LightmeterAnalyticsFirebase implements ILightmeterAnalyticsApi {
|
|||
debugPrint(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> logCrash(
|
||||
dynamic exception,
|
||||
StackTrace? stackTrace, {
|
||||
dynamic reason,
|
||||
Iterable<Object> information = const [],
|
||||
}) async {
|
||||
FirebaseCrashlytics.instance.recordError(
|
||||
exception,
|
||||
stackTrace,
|
||||
reason: reason,
|
||||
information: information,
|
||||
fatal: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setCustomKey(String key, String value) async {
|
||||
await FirebaseCrashlytics.instance.setCustomKey(key, value);
|
||||
}
|
||||
}
|
||||
|
|
3
lib/data/analytics/entity/analytics_event.dart
Normal file
3
lib/data/analytics/entity/analytics_event.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
enum LightmeterAnalyticsEvent {
|
||||
unlockProFeatures,
|
||||
}
|
|
@ -2,9 +2,9 @@ import 'dart:async';
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||
import 'package:firebase_remote_config/firebase_remote_config.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:lightmeter/data/analytics/analytics.dart';
|
||||
import 'package:lightmeter/data/models/feature.dart';
|
||||
|
||||
abstract class IRemoteConfigService {
|
||||
|
@ -24,9 +24,7 @@ abstract class IRemoteConfigService {
|
|||
}
|
||||
|
||||
class RemoteConfigService implements IRemoteConfigService {
|
||||
final LightmeterAnalytics analytics;
|
||||
|
||||
const RemoteConfigService(this.analytics);
|
||||
const RemoteConfigService();
|
||||
|
||||
@override
|
||||
Future<void> activeAndFetchFeatures() async {
|
||||
|
@ -75,8 +73,8 @@ class RemoteConfigService implements IRemoteConfigService {
|
|||
try {
|
||||
final feature = Feature.values.firstWhere((f) => f.name == value.key);
|
||||
result[feature] = value.value.toValue(feature);
|
||||
} catch (e, stackTrace) {
|
||||
_logError(e, stackTrace: stackTrace);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -90,8 +88,8 @@ class RemoteConfigService implements IRemoteConfigService {
|
|||
for (final key in event.updatedKeys) {
|
||||
try {
|
||||
updatedFeatures.add(Feature.values.firstWhere((element) => element.name == key));
|
||||
} catch (e, stackTrace) {
|
||||
_logError(e, stackTrace: stackTrace);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
return updatedFeatures;
|
||||
|
@ -101,7 +99,9 @@ class RemoteConfigService implements IRemoteConfigService {
|
|||
@override
|
||||
bool isEnabled(Feature feature) => FirebaseRemoteConfig.instance.getBool(feature.name);
|
||||
|
||||
void _logError(dynamic throwable, {StackTrace? stackTrace}) => analytics.logCrash(throwable, stackTrace);
|
||||
void _logError(dynamic throwable, {StackTrace? stackTrace}) {
|
||||
FirebaseCrashlytics.instance.recordError(throwable, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
class MockRemoteConfigService implements IRemoteConfigService {
|
||||
|
|
22
lib/firebase.dart
Normal file
22
lib/firebase.dart
Normal file
|
@ -0,0 +1,22 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:lightmeter/firebase_options.dart';
|
||||
|
||||
Future<void> initializeFirebase({required bool handleErrors}) async {
|
||||
try {
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
if (handleErrors) {
|
||||
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
|
||||
PlatformDispatcher.instance.onError = (error, stack) {
|
||||
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
|
@ -99,7 +99,7 @@
|
|||
},
|
||||
"proFeatures": "Pro features",
|
||||
"unlockProFeatures": "Unlock Pro features",
|
||||
"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 & Histogram\n \u2022 And more!\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",
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
},
|
||||
"proFeatures": "Fonctionnalités professionnelles",
|
||||
"unlockProFeatures": "Déverrouiller les fonctionnalités professionnelles",
|
||||
"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 & Histogramme\n \u2022 Et plus encore!\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",
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
},
|
||||
"proFeatures": "Профессиональные настройки",
|
||||
"unlockProFeatures": "Разблокировать профессиональные настройки",
|
||||
"unlockProFeaturesDescription": "Вы можете разблокировать профессиональные настройки:\n \u2022 Профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений\n \u2022 Список пленок с компенсацией эффекта Шварцшильда\n \u2022 Точечный замер и гистограмма\n \u2022 И другие возможности!\n\nПолучая доступ к профессиональным настройкам, вы поддерживаете разработку и делаете возможным появление новых функций в приложении.",
|
||||
"unlockProFeaturesDescription": "Вы можете разблокировать профессиональные настройки:\n \u2022 Профили оборудования, содержащие фильтры для диафрагмы, выдержки и других значений\n \u2022 Список пленок с компенсацией эффекта Шварцшильда\n \u2022 Точечный замер\n \u2022 Гистограмма\n\nПолучая доступ к профессиональным настройкам, вы поддерживаете разработку и делаете возможным появление новых функций в приложении.",
|
||||
"unlock": "Разблокировать",
|
||||
"tooltipAdd": "Добавить",
|
||||
"tooltipClose": "Закрыть",
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
"halfStops": "1/2",
|
||||
"thirdStops": "1/3",
|
||||
"calibration": "校准",
|
||||
"calibrationMessage": "此应用程序测量读数的准确性取决于设备的后置摄像头。如需更精确的测量结果或测量结果存在偏差,请手动校准 EV 。",
|
||||
"calibrationMessageCameraOnly": "此应用程序测量读数的准确性取决于设备的后置摄像头。如需更精确的测量结果或测量结果存在偏差,请手动校准 EV 。",
|
||||
"calibrationMessage": "此应用测量读数的准确性完全取决于设备的硬件。因此,请考虑测试此应用并手动设置 EV 校准,以获得准确的测量结果。",
|
||||
"calibrationMessageCameraOnly": "此应用程序测量读数的准确s性完全取决于设备的后置摄像头。因此,请考虑测试此应用并手动设置 EV 校准,以获得准确的测量结果。",
|
||||
"camera": "摄像头",
|
||||
"lightSensor": "光传感器",
|
||||
"showEv100": "显示 EV\u2081\u2080\u2080",
|
||||
|
@ -55,14 +55,14 @@
|
|||
"apertureValues": "光圈值",
|
||||
"apertureValuesFilterDescription": "选择要显示的光圈值范围。这通常由您使用的镜头决定。",
|
||||
"ndFilters": "ND 滤镜",
|
||||
"ndFiltersFilterDescription": "选择要显示的 ND 滤镜系数。可能是您最常用的 ND 滤镜,也可能是适合您镜头的减光镜。",
|
||||
"ndFiltersFilterDescription": "选择要显示的 ND 滤镜系数。这些可能是您最常用的 ND 滤镜,也可能是适合您镜头的滤光镜。",
|
||||
"shutterSpeedValues": "快门速度",
|
||||
"shutterSpeedValuesFilterDescription": "选择要显示的快门速度范围。这通常由您使用的相机机身决定。",
|
||||
"isoValues": "ISO",
|
||||
"isoValuesFilterDescription": "选择要显示的 ISO 。这些可能是您常用的ISO值,也可以是相机支持的ISO范围。",
|
||||
"isoValuesFilterDescription": "选择要显示的 ISO 。这些值可能是您最常用的值,也可能是相机支持的值。",
|
||||
"equipmentProfile": "设备配置",
|
||||
"equipmentProfiles": "设备配置",
|
||||
"tapToAdd": "点击添加",
|
||||
"tapToAdd": "點擊添加",
|
||||
"filmsInUse": "使用的胶片",
|
||||
"filmsInUseDescription": "选择你使用的胶片",
|
||||
"general": "通用",
|
||||
|
@ -99,7 +99,7 @@
|
|||
},
|
||||
"proFeatures": "专业功能",
|
||||
"unlockProFeatures": "解锁专业功能",
|
||||
"unlockProFeaturesDescription": "\n \u2022 配置文件,其中包含光圈、快门速度等参数\n \u2022 胶卷列表,对胶片倒易率失效进行曝光补偿\n \u2022 点测光和直方图\n \u2022 和更多\n\n通过解锁专业版功能,您可以支持开发工作,帮助为应用程序添加新功能。",
|
||||
"unlockProFeaturesDescription": "\n \u2022 配置文件,其中包含光圈、快门速度等参数\n \u2022 胶卷列表,对胶片倒易率失效进行曝光补偿\n \u2022 点测光\n \u2022 直方图\n\n通过解锁专业版功能,您可以支持开发工作,帮助为应用程序添加新功能。",
|
||||
"unlock": "解锁",
|
||||
"tooltipAdd": "添加",
|
||||
"tooltipClose": "关闭",
|
||||
|
|
|
@ -1,4 +1,18 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/application.dart';
|
||||
import 'package:lightmeter/application_wrapper.dart';
|
||||
import 'package:lightmeter/environment.dart';
|
||||
import 'package:lightmeter/runner.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
|
||||
Future<void> main() => runLightmeterApp(const Environment.dev());
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(
|
||||
IAPProducts(
|
||||
products: [IAPProduct(storeId: IAPProductType.paidFeatures.storeId)],
|
||||
child: const ApplicationWrapper(
|
||||
Environment.dev(),
|
||||
child: Application(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,19 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/application.dart';
|
||||
import 'package:lightmeter/application_wrapper.dart';
|
||||
import 'package:lightmeter/environment.dart';
|
||||
import 'package:lightmeter/runner.dart';
|
||||
import 'package:lightmeter/firebase.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
|
||||
Future<void> main() => runLightmeterApp(const Environment.prod());
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await initializeFirebase(handleErrors: true);
|
||||
runApp(
|
||||
const IAPProductsProvider(
|
||||
child: ApplicationWrapper(
|
||||
Environment.prod(),
|
||||
child: Application(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
19
lib/main_release.dart
Normal file
19
lib/main_release.dart
Normal file
|
@ -0,0 +1,19 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/application.dart';
|
||||
import 'package:lightmeter/application_wrapper.dart';
|
||||
import 'package:lightmeter/environment.dart';
|
||||
import 'package:lightmeter/firebase.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await initializeFirebase(handleErrors: false);
|
||||
runApp(
|
||||
const IAPProductsProvider(
|
||||
child: ApplicationWrapper(
|
||||
Environment.prod(),
|
||||
child: Application(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
|
@ -31,10 +31,8 @@ class ServicesProvider extends InheritedWidget {
|
|||
required super.child,
|
||||
});
|
||||
|
||||
static ServicesProvider of(BuildContext context) => ServicesProvider.maybeOf(context)!;
|
||||
|
||||
static ServicesProvider? maybeOf(BuildContext context) {
|
||||
return context.findAncestorWidgetOfExactType<ServicesProvider>();
|
||||
static ServicesProvider of(BuildContext context) {
|
||||
return context.findAncestorWidgetOfExactType<ServicesProvider>()!;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:lightmeter/application.dart';
|
||||
import 'package:lightmeter/application_wrapper.dart';
|
||||
import 'package:lightmeter/data/analytics/analytics.dart';
|
||||
import 'package:lightmeter/data/analytics/api/analytics_firebase.dart';
|
||||
import 'package:lightmeter/environment.dart';
|
||||
import 'package:lightmeter/firebase_options.dart';
|
||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||
|
||||
const _errorsLogger = LightmeterAnalytics(api: LightmeterAnalyticsFirebase());
|
||||
|
||||
Future<void> runLightmeterApp(Environment env) async {
|
||||
runZonedGuarded(
|
||||
() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
if (env.buildType == BuildType.prod) {
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
}
|
||||
_errorsLogger.init();
|
||||
final application = ApplicationWrapper(env, child: const Application());
|
||||
runApp(
|
||||
env.buildType == BuildType.dev
|
||||
? IAPProducts(
|
||||
products: [IAPProduct(storeId: IAPProductType.paidFeatures.storeId)],
|
||||
child: application,
|
||||
)
|
||||
: IAPProductsProvider(child: application),
|
||||
);
|
||||
},
|
||||
_errorsLogger.logCrash,
|
||||
);
|
||||
}
|
|
@ -7,7 +7,6 @@ import 'package:camera/camera.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/data/analytics/analytics.dart';
|
||||
import 'package:lightmeter/interactors/metering_interactor.dart';
|
||||
import 'package:lightmeter/platform_config.dart';
|
||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
||||
|
@ -23,7 +22,6 @@ part 'mock_bloc_container_camera.dart';
|
|||
|
||||
class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraContainerState> {
|
||||
final MeteringInteractor _meteringInteractor;
|
||||
final LightmeterAnalytics _analytics;
|
||||
late final _WidgetsBindingObserver _observer;
|
||||
|
||||
CameraController? _cameraController;
|
||||
|
@ -44,7 +42,6 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
|||
CameraContainerBloc(
|
||||
this._meteringInteractor,
|
||||
MeteringCommunicationBloc communicationBloc,
|
||||
this._analytics,
|
||||
) : super(
|
||||
communicationBloc,
|
||||
const CameraInitState(),
|
||||
|
@ -226,8 +223,8 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
|||
Directory(file.path).deleteSync(recursive: true);
|
||||
|
||||
return await evFromImage(bytes);
|
||||
} catch (e, stackTrace) {
|
||||
_analytics.logCrash(e, stackTrace);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ class MockCameraContainerBloc extends CameraContainerBloc {
|
|||
MockCameraContainerBloc(
|
||||
super._meteringInteractor,
|
||||
super.communicationBloc,
|
||||
super._analytics,
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -73,8 +72,8 @@ class MockCameraContainerBloc extends CameraContainerBloc {
|
|||
try {
|
||||
final bytes = (await rootBundle.load(PlatformConfig.cameraStubImage)).buffer.asUint8List();
|
||||
return await evFromImage(bytes);
|
||||
} catch (e, stackTrace) {
|
||||
log(e.toString(), stackTrace: stackTrace);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ 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/providers/services_provider.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';
|
||||
|
@ -38,12 +37,10 @@ class CameraContainerProvider extends StatelessWidget {
|
|||
? MockCameraContainerBloc(
|
||||
MeteringInteractorProvider.of(context),
|
||||
context.read<MeteringCommunicationBloc>(),
|
||||
ServicesProvider.of(context).analytics,
|
||||
)
|
||||
: CameraContainerBloc(
|
||||
MeteringInteractorProvider.of(context),
|
||||
context.read<MeteringCommunicationBloc>(),
|
||||
ServicesProvider.of(context).analytics,
|
||||
))
|
||||
..add(const RequestPermissionEvent()),
|
||||
child: CameraContainer(
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/providers/services_provider.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';
|
||||
import 'package:lightmeter/screens/shared/transparent_dialog/widget_dialog_transparent.dart';
|
||||
|
@ -45,12 +44,7 @@ class ProFeaturesDialog extends StatelessWidget {
|
|||
),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
_close(context).then((_) {
|
||||
ServicesProvider.maybeOf(context)
|
||||
?.analytics
|
||||
.setCustomKey('iap_product_type', IAPProductType.paidFeatures.storeId);
|
||||
IAPProductsProvider.maybeOf(context)?.buy(IAPProductType.paidFeatures);
|
||||
});
|
||||
_close(context).then((_) => IAPProductsProvider.maybeOf(context)?.buy(IAPProductType.paidFeatures));
|
||||
},
|
||||
child: Text(S.of(context).unlock),
|
||||
),
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
import 'dart:developer';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:exif/exif.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
Future<double> evFromImage(Uint8List bytes) async {
|
||||
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) {
|
||||
throw FlutterError('Error parsing EXIF: ${tags.keys}');
|
||||
Future<double?> evFromImage(Uint8List bytes) async {
|
||||
try {
|
||||
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);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
name: lightmeter
|
||||
description: Lightmeter app inspired by Material 3 design system.
|
||||
publish_to: "none"
|
||||
version: 0.17.2+48
|
||||
version: 0.17.0+46
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
@ -28,7 +28,7 @@ dependencies:
|
|||
m3_lightmeter_iap:
|
||||
git:
|
||||
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
||||
ref: v0.7.2
|
||||
ref: v0.7.1
|
||||
m3_lightmeter_resources:
|
||||
git:
|
||||
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'package:bloc_test/bloc_test.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lightmeter/data/analytics/analytics.dart';
|
||||
import 'package:lightmeter/interactors/metering_interactor.dart';
|
||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
||||
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' as communication_events;
|
||||
|
@ -19,14 +18,11 @@ class _MockMeteringCommunicationBloc
|
|||
extends MockBloc<communication_events.MeteringCommunicationEvent, communication_states.MeteringCommunicationState>
|
||||
implements MeteringCommunicationBloc {}
|
||||
|
||||
class _MockLightmeterAnalytics extends Mock implements LightmeterAnalytics {}
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
late _MockMeteringInteractor meteringInteractor;
|
||||
late _MockMeteringCommunicationBloc communicationBloc;
|
||||
late _MockLightmeterAnalytics analytics;
|
||||
late CameraContainerBloc bloc;
|
||||
|
||||
const cameraMethodChannel = MethodChannel('plugins.flutter.io/camera');
|
||||
|
@ -114,21 +110,16 @@ void main() {
|
|||
|
||||
setUpAll(() {
|
||||
meteringInteractor = _MockMeteringInteractor();
|
||||
|
||||
communicationBloc = _MockMeteringCommunicationBloc();
|
||||
|
||||
when(() => meteringInteractor.cameraEvCalibration).thenReturn(0.0);
|
||||
when(meteringInteractor.quickVibration).thenAnswer((_) async {});
|
||||
|
||||
analytics = _MockLightmeterAnalytics();
|
||||
registerFallbackValue(StackTrace.empty);
|
||||
when(() => analytics.logCrash(any<dynamic>(), any<StackTrace>())).thenAnswer((_) async {});
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
bloc = CameraContainerBloc(
|
||||
meteringInteractor,
|
||||
communicationBloc,
|
||||
analytics,
|
||||
);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(cameraMethodChannel, cameraMethodCallSuccessHandler);
|
||||
|
|
|
@ -17,7 +17,7 @@ void main() {
|
|||
'no EXIF',
|
||||
() {
|
||||
final bytes = File('assets/launcher_icon_dev_512.png').readAsBytesSync();
|
||||
expectLater(evFromImage(bytes), throwsFlutterError);
|
||||
expectLater(evFromImage(bytes), completion(null));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue