mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-22 15:30:59 +00:00
Compare commits
8 commits
c50baa4802
...
da152dcd37
Author | SHA1 | Date | |
---|---|---|---|
|
da152dcd37 | ||
|
83476a5036 | ||
|
6b645c0ff2 | ||
|
6741516099 | ||
|
bf37d1a1d2 | ||
|
ee54aed1f3 | ||
|
c7d92cc105 | ||
|
59f8267391 |
22 changed files with 205 additions and 226 deletions
10
README.md
10
README.md
|
@ -1,14 +1,10 @@
|
||||||
<p align="center">
|
<img src="resources/social_preview.png" width="100%" />
|
||||||
<img src="assets/launcher_icon_circle.png" width="100" height="100">
|
|
||||||
</p>
|
|
||||||
<p align="center", style="font-size:60px;">
|
|
||||||
<b>Material Lightmeter</b>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
# Table of contents
|
# Table of contents
|
||||||
|
|
||||||
- [Table of contents](#table-of-contents)
|
- [Table of contents](#table-of-contents)
|
||||||
- [Backstory](#backstory)
|
- [Backstory](#backstory)
|
||||||
|
- [Screenshots](#screenshots)
|
||||||
- [Build](#build)
|
- [Build](#build)
|
||||||
- [Contribution](#contribution)
|
- [Contribution](#contribution)
|
||||||
- [iOS Limitations](#ios-limitations)
|
- [iOS Limitations](#ios-limitations)
|
||||||
|
@ -21,7 +17,7 @@ But as the existing repo contained some sensitive data, that I've pushed due to
|
||||||
|
|
||||||
Without further delay behold my new Lightmeter app inspired by Material You (a.k.a. M3)
|
Without further delay behold my new Lightmeter app inspired by Material You (a.k.a. M3)
|
||||||
|
|
||||||
# Features
|
# Screenshots
|
||||||
|
|
||||||
<p float="center">
|
<p float="center">
|
||||||
<img src="https://lh3.googleusercontent.com/8Sd-pmNcQ0xAr5opuTeJKWr2OXeQvCoFSdVDSoKQSHHKeNmqF71hqeAdm3yjunY12zY" width="18.8%" />
|
<img src="https://lh3.googleusercontent.com/8Sd-pmNcQ0xAr5opuTeJKWr2OXeQvCoFSdVDSoKQSHHKeNmqF71hqeAdm3yjunY12zY" width="18.8%" />
|
||||||
|
|
|
@ -17,5 +17,10 @@ class LightSensorService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<int> luxStream() => LightSensor.lightSensorStream;
|
Stream<int> luxStream() {
|
||||||
|
if (!localPlatform.isAndroid) {
|
||||||
|
return const Stream<int>.empty();
|
||||||
|
}
|
||||||
|
return LightSensor.lightSensorStream;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
enum VolumeAction { shutter, zoom, none }
|
enum VolumeAction { shutter, none }
|
||||||
|
|
||||||
enum VolumeKey { up, down }
|
enum VolumeKey { up, down }
|
||||||
|
|
|
@ -56,9 +56,7 @@
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"keepScreenOn": "Keep screen on",
|
"keepScreenOn": "Keep screen on",
|
||||||
"haptics": "Haptics",
|
"haptics": "Haptics",
|
||||||
"volumeKeysAction": "Volume keys action",
|
"volumeKeysAction": "Shutter by volume keys",
|
||||||
"shutter": "Shutter",
|
|
||||||
"zoom": "Zoom",
|
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"chooseLanguage": "Choose language",
|
"chooseLanguage": "Choose language",
|
||||||
"theme": "Theme",
|
"theme": "Theme",
|
||||||
|
|
|
@ -56,9 +56,7 @@
|
||||||
"general": "Général",
|
"general": "Général",
|
||||||
"keepScreenOn": "Garder l'écran allumé",
|
"keepScreenOn": "Garder l'écran allumé",
|
||||||
"haptics": "Haptiques",
|
"haptics": "Haptiques",
|
||||||
"volumeKeysAction": "Action des touches de volume",
|
"volumeKeysAction": "Obturateur par boutons de volume",
|
||||||
"shutter": "Obturateur",
|
|
||||||
"zoom": "Zoom",
|
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
"chooseLanguage": "Choisissez la langue",
|
"chooseLanguage": "Choisissez la langue",
|
||||||
"theme": "Thème",
|
"theme": "Thème",
|
||||||
|
|
|
@ -56,9 +56,7 @@
|
||||||
"general": "Общие",
|
"general": "Общие",
|
||||||
"keepScreenOn": "Запрет блокировки",
|
"keepScreenOn": "Запрет блокировки",
|
||||||
"haptics": "Вибрация",
|
"haptics": "Вибрация",
|
||||||
"volumeKeysAction": "Кнопки регулировки громкости",
|
"volumeKeysAction": "Затвор по кнопкам громкости",
|
||||||
"shutter": "Затвор",
|
|
||||||
"zoom": "Зум",
|
|
||||||
"language": "Язык",
|
"language": "Язык",
|
||||||
"chooseLanguage": "Выберите язык",
|
"chooseLanguage": "Выберите язык",
|
||||||
"theme": "Тема",
|
"theme": "Тема",
|
||||||
|
|
|
@ -8,7 +8,6 @@ import 'package:camera/camera.dart';
|
||||||
import 'package:exif/exif.dart';
|
import 'package:exif/exif.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/data/models/volume_action.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'
|
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart'
|
||||||
|
@ -19,12 +18,10 @@ import 'package:lightmeter/screens/metering/components/camera_container/event_co
|
||||||
import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart';
|
import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart';
|
import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/shared/ev_source_base/bloc_base_ev_source.dart';
|
import 'package:lightmeter/screens/metering/components/shared/ev_source_base/bloc_base_ev_source.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart';
|
|
||||||
import 'package:lightmeter/utils/log_2.dart';
|
import 'package:lightmeter/utils/log_2.dart';
|
||||||
|
|
||||||
class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraContainerState> {
|
class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraContainerState> {
|
||||||
final MeteringInteractor _meteringInteractor;
|
final MeteringInteractor _meteringInteractor;
|
||||||
final VolumeKeysNotifier _volumeKeysNotifier;
|
|
||||||
late final _WidgetsBindingObserver _observer;
|
late final _WidgetsBindingObserver _observer;
|
||||||
|
|
||||||
CameraController? _cameraController;
|
CameraController? _cameraController;
|
||||||
|
@ -44,13 +41,11 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
|
|
||||||
CameraContainerBloc(
|
CameraContainerBloc(
|
||||||
this._meteringInteractor,
|
this._meteringInteractor,
|
||||||
this._volumeKeysNotifier,
|
|
||||||
MeteringCommunicationBloc communicationBloc,
|
MeteringCommunicationBloc communicationBloc,
|
||||||
) : super(
|
) : super(
|
||||||
communicationBloc,
|
communicationBloc,
|
||||||
const CameraInitState(),
|
const CameraInitState(),
|
||||||
) {
|
) {
|
||||||
_volumeKeysNotifier.addListener(onVolumeKey);
|
|
||||||
_observer = _WidgetsBindingObserver(_appLifecycleStateObserver);
|
_observer = _WidgetsBindingObserver(_appLifecycleStateObserver);
|
||||||
WidgetsBinding.instance.addObserver(_observer);
|
WidgetsBinding.instance.addObserver(_observer);
|
||||||
|
|
||||||
|
@ -66,7 +61,6 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
WidgetsBinding.instance.removeObserver(_observer);
|
WidgetsBinding.instance.removeObserver(_observer);
|
||||||
_volumeKeysNotifier.removeListener(onVolumeKey);
|
|
||||||
unawaited(_cameraController?.dispose().then((_) => _cameraController = null));
|
unawaited(_cameraController?.dispose().then((_) => _cameraController = null));
|
||||||
communicationBloc.add(communication_event.MeteringEndedEvent(_ev100));
|
communicationBloc.add(communication_event.MeteringEndedEvent(_ev100));
|
||||||
return super.close();
|
return super.close();
|
||||||
|
@ -165,7 +159,8 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onDeinitialize(DeinitializeEvent _, Emitter emit) async {
|
Future<void> _onDeinitialize(DeinitializeEvent _, Emitter emit) async {
|
||||||
emit(const CameraLoadingState());
|
emit(const CameraInitState());
|
||||||
|
communicationBloc.add(communication_event.MeteringEndedEvent(_ev100));
|
||||||
await _cameraController?.dispose().then((_) => _cameraController = null);
|
await _cameraController?.dispose().then((_) => _cameraController = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,18 +240,6 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
|
||||||
void onVolumeKey() {
|
|
||||||
if (_meteringInteractor.volumeAction == VolumeAction.zoom) {
|
|
||||||
switch (_volumeKeysNotifier.value) {
|
|
||||||
case VolumeKey.up:
|
|
||||||
add(ZoomChangedEvent(_currentZoom + 0.5));
|
|
||||||
case VolumeKey.down:
|
|
||||||
add(ZoomChangedEvent(_currentZoom - 0.5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is needed only because we cannot use `with` with mixins
|
/// This is needed only because we cannot use `with` with mixins
|
||||||
|
|
|
@ -7,7 +7,6 @@ import 'package:lightmeter/screens/metering/communication/bloc_communication_met
|
||||||
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';
|
||||||
import 'package:lightmeter/screens/metering/components/camera_container/widget_container_camera.dart';
|
import 'package:lightmeter/screens/metering/components/camera_container/widget_container_camera.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart';
|
|
||||||
import 'package:lightmeter/utils/inherited_generics.dart';
|
import 'package:lightmeter/utils/inherited_generics.dart';
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
|
@ -41,7 +40,6 @@ class CameraContainerProvider extends StatelessWidget {
|
||||||
lazy: false,
|
lazy: false,
|
||||||
create: (context) => CameraContainerBloc(
|
create: (context) => CameraContainerBloc(
|
||||||
context.get<MeteringInteractor>(),
|
context.get<MeteringInteractor>(),
|
||||||
context.get<VolumeKeysNotifier>(),
|
|
||||||
context.read<MeteringCommunicationBloc>(),
|
context.read<MeteringCommunicationBloc>(),
|
||||||
)..add(const RequestPermissionEvent()),
|
)..add(const RequestPermissionEvent()),
|
||||||
child: CameraContainer(
|
child: CameraContainer(
|
||||||
|
|
|
@ -25,6 +25,7 @@ class LightSensorContainerBloc
|
||||||
communicationBloc,
|
communicationBloc,
|
||||||
const LightSensorContainerState(null),
|
const LightSensorContainerState(null),
|
||||||
) {
|
) {
|
||||||
|
on<StartLuxMeteringEvent>(_onStartLuxMeteringEvent);
|
||||||
on<LuxMeteringEvent>(_onLuxMeteringEvent);
|
on<LuxMeteringEvent>(_onLuxMeteringEvent);
|
||||||
on<CancelLuxMeteringEvent>(_onCancelLuxMeteringEvent);
|
on<CancelLuxMeteringEvent>(_onCancelLuxMeteringEvent);
|
||||||
}
|
}
|
||||||
|
@ -34,22 +35,27 @@ class LightSensorContainerBloc
|
||||||
switch (communicationState) {
|
switch (communicationState) {
|
||||||
case communication_states.MeasureState():
|
case communication_states.MeasureState():
|
||||||
if (_luxSubscriptions == null) {
|
if (_luxSubscriptions == null) {
|
||||||
_startMetering();
|
add(const StartLuxMeteringEvent());
|
||||||
} else {
|
} else {
|
||||||
_cancelMetering();
|
add(const CancelLuxMeteringEvent());
|
||||||
}
|
}
|
||||||
case communication_states.SettingsOpenedState():
|
case communication_states.SettingsOpenedState():
|
||||||
_cancelMetering();
|
add(const CancelLuxMeteringEvent());
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
_cancelMetering();
|
communicationBloc.add(communication_event.MeteringEndedEvent(state.ev100));
|
||||||
|
_luxSubscriptions?.cancel().then((_) => _luxSubscriptions = null);
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onStartLuxMeteringEvent(StartLuxMeteringEvent event, _) {
|
||||||
|
_luxSubscriptions = _meteringInteractor.luxStream().listen((lux) => add(LuxMeteringEvent(lux)));
|
||||||
|
}
|
||||||
|
|
||||||
void _onLuxMeteringEvent(LuxMeteringEvent event, Emitter<LightSensorContainerState> emit) {
|
void _onLuxMeteringEvent(LuxMeteringEvent event, Emitter<LightSensorContainerState> emit) {
|
||||||
final ev100 = log2(event.lux.toDouble() / 2.5) + _meteringInteractor.lightSensorEvCalibration;
|
final ev100 = log2(event.lux.toDouble() / 2.5) + _meteringInteractor.lightSensorEvCalibration;
|
||||||
emit(LightSensorContainerState(ev100));
|
emit(LightSensorContainerState(ev100));
|
||||||
|
@ -57,14 +63,6 @@ class LightSensorContainerBloc
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onCancelLuxMeteringEvent(CancelLuxMeteringEvent event, _) {
|
void _onCancelLuxMeteringEvent(CancelLuxMeteringEvent event, _) {
|
||||||
_cancelMetering();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _startMetering() {
|
|
||||||
_luxSubscriptions = _meteringInteractor.luxStream().listen((lux) => add(LuxMeteringEvent(lux)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _cancelMetering() {
|
|
||||||
communicationBloc.add(communication_event.MeteringEndedEvent(state.ev100));
|
communicationBloc.add(communication_event.MeteringEndedEvent(state.ev100));
|
||||||
_luxSubscriptions?.cancel().then((_) => _luxSubscriptions = null);
|
_luxSubscriptions?.cancel().then((_) => _luxSubscriptions = null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,10 @@ abstract class LightSensorContainerEvent {
|
||||||
const LightSensorContainerEvent();
|
const LightSensorContainerEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StartLuxMeteringEvent extends LightSensorContainerEvent {
|
||||||
|
const StartLuxMeteringEvent();
|
||||||
|
}
|
||||||
|
|
||||||
class LuxMeteringEvent extends LightSensorContainerEvent {
|
class LuxMeteringEvent extends LightSensorContainerEvent {
|
||||||
final int lux;
|
final int lux;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
|
||||||
import 'package:lightmeter/screens/settings/components/general/components/caffeine/bloc_list_tile_caffeine.dart';
|
import 'package:lightmeter/screens/settings/components/general/components/caffeine/bloc_list_tile_caffeine.dart';
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ class CaffeineListTile extends StatelessWidget {
|
||||||
title: Text(S.of(context).keepScreenOn),
|
title: Text(S.of(context).keepScreenOn),
|
||||||
value: state,
|
value: state,
|
||||||
onChanged: context.read<CaffeineListTileBloc>().onCaffeineChanged,
|
onChanged: context.read<CaffeineListTileBloc>().onCaffeineChanged,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
|
||||||
import 'package:lightmeter/screens/settings/components/general/components/haptics/bloc_list_tile_haptics.dart';
|
import 'package:lightmeter/screens/settings/components/general/components/haptics/bloc_list_tile_haptics.dart';
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ class HapticsListTile extends StatelessWidget {
|
||||||
title: Text(S.of(context).haptics),
|
title: Text(S.of(context).haptics),
|
||||||
value: state,
|
value: state,
|
||||||
onChanged: context.read<HapticsListTileBloc>().onHapticsChanged,
|
onChanged: context.read<HapticsListTileBloc>().onHapticsChanged,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/data/models/volume_action.dart';
|
import 'package:lightmeter/data/models/volume_action.dart';
|
||||||
import 'package:lightmeter/interactors/settings_interactor.dart';
|
import 'package:lightmeter/interactors/settings_interactor.dart';
|
||||||
|
|
||||||
class VolumeActionsListTileBloc extends Cubit<VolumeAction> {
|
class VolumeActionsListTileBloc extends Cubit<bool> {
|
||||||
final SettingsInteractor _settingsInteractor;
|
final SettingsInteractor _settingsInteractor;
|
||||||
|
|
||||||
VolumeActionsListTileBloc(
|
VolumeActionsListTileBloc(
|
||||||
this._settingsInteractor,
|
this._settingsInteractor,
|
||||||
) : super(_settingsInteractor.volumeAction);
|
) : super(_settingsInteractor.volumeAction == VolumeAction.shutter);
|
||||||
|
|
||||||
void onVolumeActionChanged(VolumeAction value) {
|
void onVolumeActionChanged(bool value) {
|
||||||
_settingsInteractor.setVolumeAction(value);
|
_settingsInteractor.setVolumeAction(value ? VolumeAction.shutter : VolumeAction.none);
|
||||||
|
|
||||||
// while in settings we allow system to handle volume
|
// while in settings we allow system to handle volume
|
||||||
// so that volume keys action works only when necessary - on the metering screen
|
// so that volume keys action works only when necessary - on the metering screen
|
||||||
|
|
|
@ -1,48 +1,22 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/data/models/volume_action.dart';
|
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
import 'package:lightmeter/screens/settings/components/general/components/volume_actions/bloc_list_tile_volume_actions.dart';
|
import 'package:lightmeter/screens/settings/components/general/components/volume_actions/bloc_list_tile_volume_actions.dart';
|
||||||
import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart';
|
|
||||||
|
|
||||||
class VolumeActionsListTile extends StatelessWidget {
|
class VolumeActionsListTile extends StatelessWidget {
|
||||||
const VolumeActionsListTile({super.key});
|
const VolumeActionsListTile({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<VolumeActionsListTileBloc, VolumeAction>(
|
return BlocBuilder<VolumeActionsListTileBloc, bool>(
|
||||||
builder: (context, state) => ListTile(
|
builder: (context, state) => SwitchListTile(
|
||||||
leading: const Icon(Icons.volume_up),
|
secondary: const Icon(Icons.volume_up),
|
||||||
title: Text(S.of(context).volumeKeysAction),
|
title: Text(S.of(context).volumeKeysAction),
|
||||||
trailing: Text(actionToString(context, state)),
|
value: state,
|
||||||
onTap: () {
|
onChanged: context.read<VolumeActionsListTileBloc>().onVolumeActionChanged,
|
||||||
showDialog<VolumeAction>(
|
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||||
context: context,
|
|
||||||
builder: (_) => DialogPicker<VolumeAction>(
|
|
||||||
icon: Icons.volume_up,
|
|
||||||
title: S.of(context).volumeKeysAction,
|
|
||||||
selectedValue: state,
|
|
||||||
values: VolumeAction.values,
|
|
||||||
titleAdapter: (context, value) => actionToString(context, value),
|
|
||||||
),
|
|
||||||
).then((value) {
|
|
||||||
if (value != null) {
|
|
||||||
context.read<VolumeActionsListTileBloc>().onVolumeActionChanged(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String actionToString(BuildContext context, VolumeAction themeType) {
|
|
||||||
switch (themeType) {
|
|
||||||
case VolumeAction.shutter:
|
|
||||||
return S.of(context).shutter;
|
|
||||||
case VolumeAction.zoom:
|
|
||||||
return S.of(context).zoom;
|
|
||||||
case VolumeAction.none:
|
|
||||||
return S.of(context).none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/data/models/dynamic_colors_state.dart';
|
import 'package:lightmeter/data/models/dynamic_colors_state.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/providers/theme_provider.dart';
|
import 'package:lightmeter/providers/theme_provider.dart';
|
||||||
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
import 'package:lightmeter/utils/inherited_generics.dart';
|
import 'package:lightmeter/utils/inherited_generics.dart';
|
||||||
|
|
||||||
class DynamicColorListTile extends StatelessWidget {
|
class DynamicColorListTile extends StatelessWidget {
|
||||||
|
@ -14,6 +15,7 @@ class DynamicColorListTile extends StatelessWidget {
|
||||||
title: Text(S.of(context).dynamicColor),
|
title: Text(S.of(context).dynamicColor),
|
||||||
value: context.listen<DynamicColorState>() == DynamicColorState.enabled,
|
value: context.listen<DynamicColorState>() == DynamicColorState.enabled,
|
||||||
onChanged: ThemeProvider.of(context).enableDynamicColor,
|
onChanged: ThemeProvider.of(context).enableDynamicColor,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
resources/social_preview.png
Normal file
BIN
resources/social_preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
|
@ -83,4 +83,16 @@ void main() {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
group('luxStream', () {
|
||||||
|
// test('Android', () async {
|
||||||
|
// when(() => localPlatform.isAndroid).thenReturn(true);
|
||||||
|
// expect(service.luxStream(), const Stream.empty());
|
||||||
|
// });
|
||||||
|
|
||||||
|
test('iOS', () async {
|
||||||
|
when(() => localPlatform.isAndroid).thenReturn(false);
|
||||||
|
expect(service.luxStream(), const Stream<int>.empty());
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,4 +58,16 @@ void main() {
|
||||||
expectLater(service.setVolumeHandling(false), completion(false));
|
expectLater(service.setVolumeHandling(false), completion(false));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('volumeButtonsEventStream', () {
|
||||||
|
// test('Android', () async {
|
||||||
|
// when(() => localPlatform.isAndroid).thenReturn(true);
|
||||||
|
// expect(service.volumeButtonsEventStream(), const Stream.empty());
|
||||||
|
// });
|
||||||
|
|
||||||
|
test('iOS', () async {
|
||||||
|
when(() => localPlatform.isAndroid).thenReturn(false);
|
||||||
|
expect(service.volumeButtonsEventStream(), const Stream<int>.empty());
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -641,19 +641,6 @@ void main() {
|
||||||
expect: () => [isA<LoadingState>()],
|
expect: () => [isA<LoadingState>()],
|
||||||
);
|
);
|
||||||
|
|
||||||
blocTest<MeteringBloc, MeteringState>(
|
|
||||||
'onVolumeKey & VolumeAction.zoom',
|
|
||||||
build: () => bloc,
|
|
||||||
act: (bloc) async {
|
|
||||||
bloc.onVolumeKey();
|
|
||||||
},
|
|
||||||
setUp: () {
|
|
||||||
when(() => meteringInteractor.volumeAction).thenReturn(VolumeAction.zoom);
|
|
||||||
},
|
|
||||||
verify: (_) {},
|
|
||||||
expect: () => [],
|
|
||||||
);
|
|
||||||
|
|
||||||
blocTest<MeteringBloc, MeteringState>(
|
blocTest<MeteringBloc, MeteringState>(
|
||||||
'onVolumeKey & VolumeAction.none',
|
'onVolumeKey & VolumeAction.none',
|
||||||
build: () => bloc,
|
build: () => bloc,
|
||||||
|
@ -668,4 +655,25 @@ void main() {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'`SettingOpenedEvent`/`SettingsClosedEvent`',
|
||||||
|
() {
|
||||||
|
blocTest<MeteringBloc, MeteringState>(
|
||||||
|
'Settings opened & closed',
|
||||||
|
build: () => bloc,
|
||||||
|
act: (bloc) async {
|
||||||
|
bloc.add(const SettingsOpenedEvent());
|
||||||
|
bloc.add(const SettingsClosedEvent());
|
||||||
|
},
|
||||||
|
verify: (_) {
|
||||||
|
verify(() => communicationBloc.add(const communication_events.SettingsOpenedEvent()))
|
||||||
|
.called(1);
|
||||||
|
verify(() => communicationBloc.add(const communication_events.SettingsClosedEvent()))
|
||||||
|
.called(1);
|
||||||
|
},
|
||||||
|
expect: () => [],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,4 +98,30 @@ void main() {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'`SettingsOpenedEvent`/`SettingsClosedEvent`',
|
||||||
|
() {
|
||||||
|
blocTest<MeteringCommunicationBloc, MeteringCommunicationState>(
|
||||||
|
'Multiple consequtive settings events',
|
||||||
|
build: () => bloc,
|
||||||
|
act: (bloc) async {
|
||||||
|
bloc.add(const SettingsOpenedEvent());
|
||||||
|
bloc.add(const SettingsOpenedEvent());
|
||||||
|
bloc.add(const SettingsOpenedEvent());
|
||||||
|
bloc.add(const SettingsClosedEvent());
|
||||||
|
bloc.add(const SettingsClosedEvent());
|
||||||
|
bloc.add(const SettingsClosedEvent());
|
||||||
|
bloc.add(const SettingsOpenedEvent());
|
||||||
|
bloc.add(const SettingsClosedEvent());
|
||||||
|
},
|
||||||
|
expect: () => [
|
||||||
|
isA<SettingsOpenedState>(),
|
||||||
|
isA<SettingsClosedState>(),
|
||||||
|
isA<SettingsOpenedState>(),
|
||||||
|
isA<SettingsClosedState>(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/models/volume_action.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'
|
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart'
|
||||||
|
@ -13,13 +12,10 @@ import 'package:lightmeter/screens/metering/components/camera_container/bloc_con
|
||||||
import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart';
|
import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart';
|
import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart';
|
import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart';
|
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
class _MockMeteringInteractor extends Mock implements MeteringInteractor {}
|
class _MockMeteringInteractor extends Mock implements MeteringInteractor {}
|
||||||
|
|
||||||
class _MockVolumeKeysNotifier extends Mock implements VolumeKeysNotifier {}
|
|
||||||
|
|
||||||
class _MockMeteringCommunicationBloc extends MockBloc<
|
class _MockMeteringCommunicationBloc extends MockBloc<
|
||||||
communication_events.MeteringCommunicationEvent,
|
communication_events.MeteringCommunicationEvent,
|
||||||
communication_states.MeteringCommunicationState> implements MeteringCommunicationBloc {}
|
communication_states.MeteringCommunicationState> implements MeteringCommunicationBloc {}
|
||||||
|
@ -28,7 +24,6 @@ void main() {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
late _MockMeteringInteractor meteringInteractor;
|
late _MockMeteringInteractor meteringInteractor;
|
||||||
late _MockVolumeKeysNotifier volumeKeysNotifier;
|
|
||||||
late _MockMeteringCommunicationBloc communicationBloc;
|
late _MockMeteringCommunicationBloc communicationBloc;
|
||||||
late CameraContainerBloc bloc;
|
late CameraContainerBloc bloc;
|
||||||
|
|
||||||
|
@ -117,7 +112,6 @@ void main() {
|
||||||
|
|
||||||
setUpAll(() {
|
setUpAll(() {
|
||||||
meteringInteractor = _MockMeteringInteractor();
|
meteringInteractor = _MockMeteringInteractor();
|
||||||
volumeKeysNotifier = _MockVolumeKeysNotifier();
|
|
||||||
communicationBloc = _MockMeteringCommunicationBloc();
|
communicationBloc = _MockMeteringCommunicationBloc();
|
||||||
|
|
||||||
when(() => meteringInteractor.cameraEvCalibration).thenReturn(0.0);
|
when(() => meteringInteractor.cameraEvCalibration).thenReturn(0.0);
|
||||||
|
@ -127,7 +121,6 @@ void main() {
|
||||||
setUp(() {
|
setUp(() {
|
||||||
bloc = CameraContainerBloc(
|
bloc = CameraContainerBloc(
|
||||||
meteringInteractor,
|
meteringInteractor,
|
||||||
volumeKeysNotifier,
|
|
||||||
communicationBloc,
|
communicationBloc,
|
||||||
);
|
);
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||||
|
@ -317,6 +310,30 @@ void main() {
|
||||||
},
|
},
|
||||||
expect: () => [
|
expect: () => [
|
||||||
...initializedStateSequence,
|
...initializedStateSequence,
|
||||||
|
const CameraInitState(),
|
||||||
|
...initializedStateSequence,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<CameraContainerBloc, CameraContainerState>(
|
||||||
|
'onCommunicationState',
|
||||||
|
setUp: () {
|
||||||
|
when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => true);
|
||||||
|
},
|
||||||
|
build: () => bloc,
|
||||||
|
act: (bloc) async {
|
||||||
|
bloc.add(const InitializeEvent());
|
||||||
|
await Future.delayed(Duration.zero);
|
||||||
|
bloc.onCommunicationState(const communication_states.SettingsOpenedState());
|
||||||
|
await Future.delayed(Duration.zero);
|
||||||
|
bloc.onCommunicationState(const communication_states.SettingsClosedState());
|
||||||
|
},
|
||||||
|
verify: (_) {
|
||||||
|
verify(() => meteringInteractor.checkCameraPermission()).called(2);
|
||||||
|
},
|
||||||
|
expect: () => [
|
||||||
|
...initializedStateSequence,
|
||||||
|
const CameraInitState(),
|
||||||
...initializedStateSequence,
|
...initializedStateSequence,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -483,123 +500,6 @@ void main() {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
group(
|
|
||||||
'`Volume keys shutter action`',
|
|
||||||
() {
|
|
||||||
blocTest<CameraContainerBloc, CameraContainerState>(
|
|
||||||
'Add/remove listener',
|
|
||||||
build: () => bloc,
|
|
||||||
verify: (_) {
|
|
||||||
verify(() => volumeKeysNotifier.addListener(bloc.onVolumeKey)).called(1);
|
|
||||||
verify(() => volumeKeysNotifier.removeListener(bloc.onVolumeKey)).called(1);
|
|
||||||
},
|
|
||||||
expect: () => [],
|
|
||||||
);
|
|
||||||
|
|
||||||
blocTest<CameraContainerBloc, CameraContainerState>(
|
|
||||||
'onVolumeKey & VolumeAction.shutter',
|
|
||||||
build: () => bloc,
|
|
||||||
act: (bloc) async {
|
|
||||||
bloc.onVolumeKey();
|
|
||||||
},
|
|
||||||
setUp: () {
|
|
||||||
when(() => meteringInteractor.volumeAction).thenReturn(VolumeAction.shutter);
|
|
||||||
},
|
|
||||||
verify: (_) {},
|
|
||||||
expect: () => [],
|
|
||||||
);
|
|
||||||
|
|
||||||
blocTest<CameraContainerBloc, CameraContainerState>(
|
|
||||||
'onVolumeKey.up & VolumeAction.zoom',
|
|
||||||
build: () => bloc,
|
|
||||||
act: (bloc) async {
|
|
||||||
bloc.add(const InitializeEvent());
|
|
||||||
await Future.delayed(Duration.zero);
|
|
||||||
bloc.onVolumeKey();
|
|
||||||
await Future.delayed(Duration.zero);
|
|
||||||
bloc.onVolumeKey();
|
|
||||||
await Future.delayed(Duration.zero);
|
|
||||||
bloc.onVolumeKey();
|
|
||||||
},
|
|
||||||
setUp: () {
|
|
||||||
when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => true);
|
|
||||||
when(() => meteringInteractor.volumeAction).thenReturn(VolumeAction.zoom);
|
|
||||||
when(() => volumeKeysNotifier.value).thenReturn(VolumeKey.up);
|
|
||||||
},
|
|
||||||
verify: (_) {},
|
|
||||||
expect: () => [
|
|
||||||
...initializedStateSequence,
|
|
||||||
isA<CameraActiveState>()
|
|
||||||
.having((state) => state.zoomRange, 'zoomRange', const RangeValues(1.0, 7.0))
|
|
||||||
.having((state) => state.currentZoom, 'currentZoom', 1.5)
|
|
||||||
.having(
|
|
||||||
(state) => state.exposureOffsetRange,
|
|
||||||
'exposureOffsetRange',
|
|
||||||
const RangeValues(-4.0, 4.0),
|
|
||||||
)
|
|
||||||
.having((state) => state.exposureOffsetStep, 'exposureOffsetStep', 0.1666666)
|
|
||||||
.having((state) => state.currentExposureOffset, 'currentExposureOffset', 0.0),
|
|
||||||
isA<CameraActiveState>()
|
|
||||||
.having((state) => state.zoomRange, 'zoomRange', const RangeValues(1.0, 7.0))
|
|
||||||
.having((state) => state.currentZoom, 'currentZoom', 2.0)
|
|
||||||
.having(
|
|
||||||
(state) => state.exposureOffsetRange,
|
|
||||||
'exposureOffsetRange',
|
|
||||||
const RangeValues(-4.0, 4.0),
|
|
||||||
)
|
|
||||||
.having((state) => state.exposureOffsetStep, 'exposureOffsetStep', 0.1666666)
|
|
||||||
.having((state) => state.currentExposureOffset, 'currentExposureOffset', 0.0),
|
|
||||||
isA<CameraActiveState>()
|
|
||||||
.having((state) => state.zoomRange, 'zoomRange', const RangeValues(1.0, 7.0))
|
|
||||||
.having((state) => state.currentZoom, 'currentZoom', 2.5)
|
|
||||||
.having(
|
|
||||||
(state) => state.exposureOffsetRange,
|
|
||||||
'exposureOffsetRange',
|
|
||||||
const RangeValues(-4.0, 4.0),
|
|
||||||
)
|
|
||||||
.having((state) => state.exposureOffsetStep, 'exposureOffsetStep', 0.1666666)
|
|
||||||
.having((state) => state.currentExposureOffset, 'currentExposureOffset', 0.0),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
blocTest<CameraContainerBloc, CameraContainerState>(
|
|
||||||
'onVolumeKey.down & VolumeAction.zoom',
|
|
||||||
build: () => bloc,
|
|
||||||
act: (bloc) async {
|
|
||||||
bloc.add(const InitializeEvent());
|
|
||||||
await Future.delayed(Duration.zero);
|
|
||||||
bloc.onVolumeKey();
|
|
||||||
await Future.delayed(Duration.zero);
|
|
||||||
bloc.onVolumeKey();
|
|
||||||
await Future.delayed(Duration.zero);
|
|
||||||
bloc.onVolumeKey();
|
|
||||||
},
|
|
||||||
setUp: () {
|
|
||||||
when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => true);
|
|
||||||
when(() => meteringInteractor.volumeAction).thenReturn(VolumeAction.zoom);
|
|
||||||
when(() => volumeKeysNotifier.value).thenReturn(VolumeKey.down);
|
|
||||||
},
|
|
||||||
verify: (_) {},
|
|
||||||
expect: () => [
|
|
||||||
...initializedStateSequence,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
blocTest<CameraContainerBloc, CameraContainerState>(
|
|
||||||
'onVolumeKey & VolumeAction.none',
|
|
||||||
build: () => bloc,
|
|
||||||
act: (bloc) async {
|
|
||||||
bloc.onVolumeKey();
|
|
||||||
},
|
|
||||||
setUp: () {
|
|
||||||
when(() => meteringInteractor.volumeAction).thenReturn(VolumeAction.none);
|
|
||||||
},
|
|
||||||
verify: (_) {},
|
|
||||||
expect: () => [],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension _MethodChannelMock on MethodChannel {
|
extension _MethodChannelMock on MethodChannel {
|
||||||
|
|
|
@ -78,4 +78,67 @@ void main() {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'`communication_states.SettingsOpenedState()`',
|
||||||
|
() {
|
||||||
|
const List<int> luxIterable = [1, 2, 2, 2, 3];
|
||||||
|
final List<double> resultList = luxIterable.map((lux) => log2(lux / 2.5)).toList();
|
||||||
|
blocTest<LightSensorContainerBloc, LightSensorContainerState>(
|
||||||
|
'Metering is already canceled',
|
||||||
|
build: () => bloc,
|
||||||
|
setUp: () {
|
||||||
|
when(() => meteringInteractor.luxStream())
|
||||||
|
.thenAnswer((_) => Stream.fromIterable(luxIterable));
|
||||||
|
when(() => meteringInteractor.lightSensorEvCalibration).thenReturn(0.0);
|
||||||
|
},
|
||||||
|
act: (bloc) async {
|
||||||
|
bloc.onCommunicationState(const communication_states.SettingsOpenedState());
|
||||||
|
},
|
||||||
|
verify: (_) {
|
||||||
|
verifyNever(() => meteringInteractor.luxStream().listen((_) {}));
|
||||||
|
verifyNever(() => meteringInteractor.lightSensorEvCalibration);
|
||||||
|
verify(() {
|
||||||
|
communicationBloc.add(const communication_events.MeteringEndedEvent(null));
|
||||||
|
}).called(2); // +1 from dispose
|
||||||
|
},
|
||||||
|
expect: () => [],
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<LightSensorContainerBloc, LightSensorContainerState>(
|
||||||
|
'Metering is in progress',
|
||||||
|
build: () => bloc,
|
||||||
|
setUp: () {
|
||||||
|
when(() => meteringInteractor.luxStream())
|
||||||
|
.thenAnswer((_) => Stream.fromIterable(luxIterable));
|
||||||
|
when(() => meteringInteractor.lightSensorEvCalibration).thenReturn(0.0);
|
||||||
|
},
|
||||||
|
act: (bloc) async {
|
||||||
|
bloc.onCommunicationState(const communication_states.MeasureState());
|
||||||
|
await Future.delayed(Duration.zero);
|
||||||
|
bloc.onCommunicationState(const communication_states.SettingsOpenedState());
|
||||||
|
bloc.onCommunicationState(const communication_states.SettingsClosedState());
|
||||||
|
},
|
||||||
|
verify: (_) {
|
||||||
|
verify(() => meteringInteractor.luxStream().listen((_) {})).called(1);
|
||||||
|
verify(() => meteringInteractor.lightSensorEvCalibration).called(5);
|
||||||
|
verify(() {
|
||||||
|
communicationBloc.add(communication_events.MeteringInProgressEvent(resultList.first));
|
||||||
|
}).called(1);
|
||||||
|
verify(() {
|
||||||
|
communicationBloc.add(communication_events.MeteringInProgressEvent(resultList[1]));
|
||||||
|
}).called(3);
|
||||||
|
verify(() {
|
||||||
|
communicationBloc.add(communication_events.MeteringInProgressEvent(resultList.last));
|
||||||
|
}).called(1);
|
||||||
|
verify(() {
|
||||||
|
communicationBloc.add(communication_events.MeteringEndedEvent(resultList.last));
|
||||||
|
}).called(3); // +1 from settings closed, +1 from dispose
|
||||||
|
},
|
||||||
|
expect: () => resultList.map(
|
||||||
|
(e) => isA<LightSensorContainerState>().having((state) => state.ev100, 'ev100', e),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue