mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2025-07-04 13:10:42 +00:00
Compare commits
No commits in common. "f965b99e1d13af339e6b7a00a49f0364416d3055" and "9cb1cbaa90009188a9e6c9104b288d006220c140" have entirely different histories.
f965b99e1d
...
9cb1cbaa90
21 changed files with 139 additions and 172 deletions
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
@ -41,7 +41,7 @@
|
||||||
"--dart-define",
|
"--dart-define",
|
||||||
"cameraPreviewAspectRatio=240/320",
|
"cameraPreviewAspectRatio=240/320",
|
||||||
],
|
],
|
||||||
"program": "${workspaceFolder}/lib/main_prod.dart",
|
"program": "${workspaceFolder}/lib/main_release.dart",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "prod-profile (android)",
|
"name": "prod-profile (android)",
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
"--dart-define",
|
"--dart-define",
|
||||||
"cameraPreviewAspectRatio=240/320",
|
"cameraPreviewAspectRatio=240/320",
|
||||||
],
|
],
|
||||||
"program": "${workspaceFolder}/lib/main_prod.dart",
|
"program": "${workspaceFolder}/lib/main_release.dart",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dev-debug (ios)",
|
"name": "dev-debug (ios)",
|
||||||
|
|
|
@ -26,14 +26,11 @@ class ApplicationWrapper extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final remoteConfigService = env.buildType != BuildType.dev
|
|
||||||
? const RemoteConfigService(LightmeterAnalytics(api: LightmeterAnalyticsFirebase()))
|
|
||||||
: const MockRemoteConfigService();
|
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: Future.wait<dynamic>([
|
future: Future.wait<dynamic>([
|
||||||
SharedPreferences.getInstance(),
|
SharedPreferences.getInstance(),
|
||||||
const LightSensorService(LocalPlatform()).hasSensor(),
|
const LightSensorService(LocalPlatform()).hasSensor(),
|
||||||
remoteConfigService.activeAndFetchFeatures(),
|
if (env.buildType != BuildType.dev) const RemoteConfigService().activeAndFetchFeatures(),
|
||||||
]),
|
]),
|
||||||
builder: (_, snapshot) {
|
builder: (_, snapshot) {
|
||||||
if (snapshot.data != null) {
|
if (snapshot.data != null) {
|
||||||
|
@ -50,7 +47,8 @@ class ApplicationWrapper extends StatelessWidget {
|
||||||
userPreferencesService: userPreferencesService,
|
userPreferencesService: userPreferencesService,
|
||||||
volumeEventsService: const VolumeEventsService(LocalPlatform()),
|
volumeEventsService: const VolumeEventsService(LocalPlatform()),
|
||||||
child: RemoteConfigProvider(
|
child: RemoteConfigProvider(
|
||||||
remoteConfigService: remoteConfigService,
|
remoteConfigService:
|
||||||
|
env.buildType != BuildType.dev ? const RemoteConfigService() : const MockRemoteConfigService(),
|
||||||
child: EquipmentProfileProvider(
|
child: EquipmentProfileProvider(
|
||||||
storageService: iapService,
|
storageService: iapService,
|
||||||
child: FilmsProvider(
|
child: FilmsProvider(
|
||||||
|
|
|
@ -3,56 +3,32 @@ import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:lightmeter/data/analytics/api/analytics_api_interface.dart';
|
import 'package:lightmeter/data/analytics/api/analytics_api_interface.dart';
|
||||||
|
import 'package:lightmeter/data/analytics/entity/analytics_event.dart';
|
||||||
|
|
||||||
class LightmeterAnalytics {
|
class LightmeterAnalytics {
|
||||||
final ILightmeterAnalyticsApi _api;
|
final ILightmeterAnalyticsApi _api;
|
||||||
|
|
||||||
const LightmeterAnalytics({required ILightmeterAnalyticsApi api}) : _api = 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(
|
Future<void> logEvent(
|
||||||
String eventName, {
|
LightmeterAnalyticsEvent event, {
|
||||||
Map<String, dynamic>? parameters,
|
Map<String, dynamic>? parameters,
|
||||||
}) async {
|
}) async {
|
||||||
if (!kReleaseMode) {
|
if (kDebugMode) {
|
||||||
log('<LightmeterAnalytics> logEvent: $eventName / $parameters');
|
log('<LightmeterAnalytics> logEvent: ${event.name} / $parameters');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _api.logEvent(
|
return _api.logEvent(
|
||||||
eventName,
|
event: event,
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> logCrash(
|
Future<void> logUnlockProFeatures(String listTileTitle) async {
|
||||||
dynamic exception,
|
return logEvent(
|
||||||
StackTrace? stackTrace, {
|
LightmeterAnalyticsEvent.unlockProFeatures,
|
||||||
dynamic reason,
|
parameters: {"listTileTitle": listTileTitle},
|
||||||
Iterable<Object> information = const [],
|
|
||||||
}) async {
|
|
||||||
log(exception.toString(), stackTrace: stackTrace);
|
|
||||||
if (!kReleaseMode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _api.logCrash(
|
|
||||||
exception,
|
|
||||||
stackTrace,
|
|
||||||
reason: reason,
|
|
||||||
information: information,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
abstract class ILightmeterAnalyticsApi {
|
||||||
Future<void> logEvent(
|
Future<void> logEvent({
|
||||||
String eventName, {
|
required LightmeterAnalyticsEvent event,
|
||||||
Map<String, dynamic>? parameters,
|
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_analytics/firebase_analytics.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:lightmeter/data/analytics/api/analytics_api_interface.dart';
|
import 'package:lightmeter/data/analytics/api/analytics_api_interface.dart';
|
||||||
|
import 'package:lightmeter/data/analytics/entity/analytics_event.dart';
|
||||||
|
|
||||||
class LightmeterAnalyticsFirebase implements ILightmeterAnalyticsApi {
|
class LightmeterAnalyticsFirebase implements ILightmeterAnalyticsApi {
|
||||||
const LightmeterAnalyticsFirebase();
|
const LightmeterAnalyticsFirebase();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> logEvent(
|
Future<void> logEvent({
|
||||||
String eventName, {
|
required LightmeterAnalyticsEvent event,
|
||||||
Map<String, dynamic>? parameters,
|
Map<String, dynamic>? parameters,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
await FirebaseAnalytics.instance.logEvent(
|
await FirebaseAnalytics.instance.logEvent(
|
||||||
name: eventName,
|
name: event.name,
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
);
|
);
|
||||||
} on FirebaseException catch (e) {
|
} on FirebaseException catch (e) {
|
||||||
|
@ -23,25 +23,4 @@ class LightmeterAnalyticsFirebase implements ILightmeterAnalyticsApi {
|
||||||
debugPrint(e.toString());
|
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 'dart:developer';
|
||||||
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
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:firebase_remote_config/firebase_remote_config.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:lightmeter/data/analytics/analytics.dart';
|
|
||||||
import 'package:lightmeter/data/models/feature.dart';
|
import 'package:lightmeter/data/models/feature.dart';
|
||||||
|
|
||||||
abstract class IRemoteConfigService {
|
abstract class IRemoteConfigService {
|
||||||
|
@ -24,9 +24,7 @@ abstract class IRemoteConfigService {
|
||||||
}
|
}
|
||||||
|
|
||||||
class RemoteConfigService implements IRemoteConfigService {
|
class RemoteConfigService implements IRemoteConfigService {
|
||||||
final LightmeterAnalytics analytics;
|
const RemoteConfigService();
|
||||||
|
|
||||||
const RemoteConfigService(this.analytics);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> activeAndFetchFeatures() async {
|
Future<void> activeAndFetchFeatures() async {
|
||||||
|
@ -75,8 +73,8 @@ class RemoteConfigService implements IRemoteConfigService {
|
||||||
try {
|
try {
|
||||||
final feature = Feature.values.firstWhere((f) => f.name == value.key);
|
final feature = Feature.values.firstWhere((f) => f.name == value.key);
|
||||||
result[feature] = value.value.toValue(feature);
|
result[feature] = value.value.toValue(feature);
|
||||||
} catch (e, stackTrace) {
|
} catch (e) {
|
||||||
_logError(e, stackTrace: stackTrace);
|
log(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -90,8 +88,8 @@ class RemoteConfigService implements IRemoteConfigService {
|
||||||
for (final key in event.updatedKeys) {
|
for (final key in event.updatedKeys) {
|
||||||
try {
|
try {
|
||||||
updatedFeatures.add(Feature.values.firstWhere((element) => element.name == key));
|
updatedFeatures.add(Feature.values.firstWhere((element) => element.name == key));
|
||||||
} catch (e, stackTrace) {
|
} catch (e) {
|
||||||
_logError(e, stackTrace: stackTrace);
|
log(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return updatedFeatures;
|
return updatedFeatures;
|
||||||
|
@ -101,7 +99,9 @@ class RemoteConfigService implements IRemoteConfigService {
|
||||||
@override
|
@override
|
||||||
bool isEnabled(Feature feature) => FirebaseRemoteConfig.instance.getBool(feature.name);
|
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 {
|
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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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/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/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,
|
required super.child,
|
||||||
});
|
});
|
||||||
|
|
||||||
static ServicesProvider of(BuildContext context) => ServicesProvider.maybeOf(context)!;
|
static ServicesProvider of(BuildContext context) {
|
||||||
|
return context.findAncestorWidgetOfExactType<ServicesProvider>()!;
|
||||||
static ServicesProvider? maybeOf(BuildContext context) {
|
|
||||||
return context.findAncestorWidgetOfExactType<ServicesProvider>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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/interactors/metering_interactor.dart';
|
||||||
import 'package:lightmeter/platform_config.dart';
|
import 'package:lightmeter/platform_config.dart';
|
||||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.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> {
|
class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraContainerState> {
|
||||||
final MeteringInteractor _meteringInteractor;
|
final MeteringInteractor _meteringInteractor;
|
||||||
final LightmeterAnalytics _analytics;
|
|
||||||
late final _WidgetsBindingObserver _observer;
|
late final _WidgetsBindingObserver _observer;
|
||||||
|
|
||||||
CameraController? _cameraController;
|
CameraController? _cameraController;
|
||||||
|
@ -44,7 +42,6 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
CameraContainerBloc(
|
CameraContainerBloc(
|
||||||
this._meteringInteractor,
|
this._meteringInteractor,
|
||||||
MeteringCommunicationBloc communicationBloc,
|
MeteringCommunicationBloc communicationBloc,
|
||||||
this._analytics,
|
|
||||||
) : super(
|
) : super(
|
||||||
communicationBloc,
|
communicationBloc,
|
||||||
const CameraInitState(),
|
const CameraInitState(),
|
||||||
|
@ -226,8 +223,8 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
Directory(file.path).deleteSync(recursive: true);
|
Directory(file.path).deleteSync(recursive: true);
|
||||||
|
|
||||||
return await evFromImage(bytes);
|
return await evFromImage(bytes);
|
||||||
} catch (e, stackTrace) {
|
} catch (e) {
|
||||||
_analytics.logCrash(e, stackTrace);
|
log(e.toString());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ class MockCameraContainerBloc extends CameraContainerBloc {
|
||||||
MockCameraContainerBloc(
|
MockCameraContainerBloc(
|
||||||
super._meteringInteractor,
|
super._meteringInteractor,
|
||||||
super.communicationBloc,
|
super.communicationBloc,
|
||||||
super._analytics,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -73,8 +72,8 @@ class MockCameraContainerBloc extends CameraContainerBloc {
|
||||||
try {
|
try {
|
||||||
final bytes = (await rootBundle.load(PlatformConfig.cameraStubImage)).buffer.asUint8List();
|
final bytes = (await rootBundle.load(PlatformConfig.cameraStubImage)).buffer.asUint8List();
|
||||||
return await evFromImage(bytes);
|
return await evFromImage(bytes);
|
||||||
} catch (e, stackTrace) {
|
} catch (e) {
|
||||||
log(e.toString(), stackTrace: stackTrace);
|
log(e.toString());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||||
import 'package:lightmeter/platform_config.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/communication/bloc_communication_metering.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart';
|
import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart';
|
import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart';
|
||||||
|
@ -38,12 +37,10 @@ class CameraContainerProvider extends StatelessWidget {
|
||||||
? MockCameraContainerBloc(
|
? MockCameraContainerBloc(
|
||||||
MeteringInteractorProvider.of(context),
|
MeteringInteractorProvider.of(context),
|
||||||
context.read<MeteringCommunicationBloc>(),
|
context.read<MeteringCommunicationBloc>(),
|
||||||
ServicesProvider.of(context).analytics,
|
|
||||||
)
|
)
|
||||||
: CameraContainerBloc(
|
: CameraContainerBloc(
|
||||||
MeteringInteractorProvider.of(context),
|
MeteringInteractorProvider.of(context),
|
||||||
context.read<MeteringCommunicationBloc>(),
|
context.read<MeteringCommunicationBloc>(),
|
||||||
ServicesProvider.of(context).analytics,
|
|
||||||
))
|
))
|
||||||
..add(const RequestPermissionEvent()),
|
..add(const RequestPermissionEvent()),
|
||||||
child: CameraContainer(
|
child: CameraContainer(
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/providers/services_provider.dart';
|
|
||||||
import 'package:lightmeter/res/dimens.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/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';
|
import 'package:lightmeter/screens/shared/transparent_dialog/widget_dialog_transparent.dart';
|
||||||
|
@ -45,12 +44,7 @@ class ProFeaturesDialog extends StatelessWidget {
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_close(context).then((_) {
|
_close(context).then((_) => IAPProductsProvider.maybeOf(context)?.buy(IAPProductType.paidFeatures));
|
||||||
ServicesProvider.maybeOf(context)
|
|
||||||
?.analytics
|
|
||||||
.setCustomKey('iap_product_type', IAPProductType.paidFeatures.storeId);
|
|
||||||
IAPProductsProvider.maybeOf(context)?.buy(IAPProductType.paidFeatures);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
child: Text(S.of(context).unlock),
|
child: Text(S.of(context).unlock),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
|
import 'dart:developer';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:exif/exif.dart';
|
import 'package:exif/exif.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
Future<double> evFromImage(Uint8List bytes) async {
|
Future<double?> evFromImage(Uint8List bytes) async {
|
||||||
final tags = await readExifFromBytes(bytes);
|
try {
|
||||||
final iso = double.tryParse("${tags["EXIF ISOSpeedRatings"]}");
|
final tags = await readExifFromBytes(bytes);
|
||||||
final apertureValueRatio = (tags["EXIF FNumber"]?.values as IfdRatios?)?.ratios.first;
|
final iso = double.tryParse("${tags["EXIF ISOSpeedRatings"]}");
|
||||||
final speedValueRatio = (tags["EXIF ExposureTime"]?.values as IfdRatios?)?.ratios.first;
|
final apertureValueRatio = (tags["EXIF FNumber"]?.values as IfdRatios?)?.ratios.first;
|
||||||
if (iso == null || apertureValueRatio == null || speedValueRatio == null) {
|
final speedValueRatio = (tags["EXIF ExposureTime"]?.values as IfdRatios?)?.ratios.first;
|
||||||
throw FlutterError('Error parsing EXIF: ${tags.keys}');
|
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
|
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: 0.17.2+48
|
version: 0.17.1+47
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
@ -28,7 +28,7 @@ dependencies:
|
||||||
m3_lightmeter_iap:
|
m3_lightmeter_iap:
|
||||||
git:
|
git:
|
||||||
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
||||||
ref: v0.7.2
|
ref: v0.7.1
|
||||||
m3_lightmeter_resources:
|
m3_lightmeter_resources:
|
||||||
git:
|
git:
|
||||||
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.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/interactors/metering_interactor.dart';
|
||||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
||||||
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' as communication_events;
|
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>
|
extends MockBloc<communication_events.MeteringCommunicationEvent, communication_states.MeteringCommunicationState>
|
||||||
implements MeteringCommunicationBloc {}
|
implements MeteringCommunicationBloc {}
|
||||||
|
|
||||||
class _MockLightmeterAnalytics extends Mock implements LightmeterAnalytics {}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
late _MockMeteringInteractor meteringInteractor;
|
late _MockMeteringInteractor meteringInteractor;
|
||||||
late _MockMeteringCommunicationBloc communicationBloc;
|
late _MockMeteringCommunicationBloc communicationBloc;
|
||||||
late _MockLightmeterAnalytics analytics;
|
|
||||||
late CameraContainerBloc bloc;
|
late CameraContainerBloc bloc;
|
||||||
|
|
||||||
const cameraMethodChannel = MethodChannel('plugins.flutter.io/camera');
|
const cameraMethodChannel = MethodChannel('plugins.flutter.io/camera');
|
||||||
|
@ -114,21 +110,16 @@ void main() {
|
||||||
|
|
||||||
setUpAll(() {
|
setUpAll(() {
|
||||||
meteringInteractor = _MockMeteringInteractor();
|
meteringInteractor = _MockMeteringInteractor();
|
||||||
|
|
||||||
communicationBloc = _MockMeteringCommunicationBloc();
|
communicationBloc = _MockMeteringCommunicationBloc();
|
||||||
|
|
||||||
when(() => meteringInteractor.cameraEvCalibration).thenReturn(0.0);
|
when(() => meteringInteractor.cameraEvCalibration).thenReturn(0.0);
|
||||||
when(meteringInteractor.quickVibration).thenAnswer((_) async {});
|
when(meteringInteractor.quickVibration).thenAnswer((_) async {});
|
||||||
|
|
||||||
analytics = _MockLightmeterAnalytics();
|
|
||||||
registerFallbackValue(StackTrace.empty);
|
|
||||||
when(() => analytics.logCrash(any<dynamic>(), any<StackTrace>())).thenAnswer((_) async {});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
bloc = CameraContainerBloc(
|
bloc = CameraContainerBloc(
|
||||||
meteringInteractor,
|
meteringInteractor,
|
||||||
communicationBloc,
|
communicationBloc,
|
||||||
analytics,
|
|
||||||
);
|
);
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||||
.setMockMethodCallHandler(cameraMethodChannel, cameraMethodCallSuccessHandler);
|
.setMockMethodCallHandler(cameraMethodChannel, cameraMethodCallSuccessHandler);
|
||||||
|
|
|
@ -17,7 +17,7 @@ void main() {
|
||||||
'no EXIF',
|
'no EXIF',
|
||||||
() {
|
() {
|
||||||
final bytes = File('assets/launcher_icon_dev_512.png').readAsBytesSync();
|
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