Compare commits

..

No commits in common. "f965b99e1d13af339e6b7a00a49f0364416d3055" and "9cb1cbaa90009188a9e6c9104b288d006220c140" have entirely different histories.

21 changed files with 139 additions and 172 deletions

4
.vscode/launch.json vendored
View file

@ -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)",

View file

@ -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(

View file

@ -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);
} }

View file

@ -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);
} }

View file

@ -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);
}
} }

View file

@ -0,0 +1,3 @@
enum LightmeterAnalyticsEvent {
unlockProFeatures,
}

View file

@ -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
View 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());
}
}

View file

@ -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(),
),
),
);
}

View file

@ -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
View 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(),
),
),
);
}

View file

@ -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

View file

@ -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,
);
}

View file

@ -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;
} }
} }

View file

@ -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;
} }
} }

View file

@ -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(

View file

@ -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),
), ),

View file

@ -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);
} }

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: 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"

View file

@ -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);

View file

@ -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));
}, },
); );
}); });