imlemented CameraSpotDetector

This commit is contained in:
Vadim 2023-11-07 17:14:53 +01:00
parent ddc7ec8c8b
commit d54d2a121a
7 changed files with 152 additions and 22 deletions

View file

@ -11,10 +11,8 @@ import 'package:flutter_bloc/flutter_bloc.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';
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' as communication_event;
as communication_event; import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' as communication_states;
import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart'
as communication_states;
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';
@ -57,6 +55,7 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
on<ZoomChangedEvent>(_onZoomChanged); on<ZoomChangedEvent>(_onZoomChanged);
on<ExposureOffsetChangedEvent>(_onExposureOffsetChanged); on<ExposureOffsetChangedEvent>(_onExposureOffsetChanged);
on<ExposureOffsetResetEvent>(_onExposureOffsetResetEvent); on<ExposureOffsetResetEvent>(_onExposureOffsetResetEvent);
on<ExposureSpotChangedEvent>(_onExposureSpotChangedEvent);
} }
@override @override
@ -166,9 +165,7 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
} }
Future<void> _onZoomChanged(ZoomChangedEvent event, Emitter emit) async { Future<void> _onZoomChanged(ZoomChangedEvent event, Emitter emit) async {
if (_cameraController != null && if (_cameraController != null && event.value >= _zoomRange!.start && event.value <= _zoomRange!.end) {
event.value >= _zoomRange!.start &&
event.value <= _zoomRange!.end) {
_cameraController!.setZoomLevel(event.value); _cameraController!.setZoomLevel(event.value);
_currentZoom = event.value; _currentZoom = event.value;
_emitActiveState(emit); _emitActiveState(emit);
@ -188,6 +185,14 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
add(const ExposureOffsetChangedEvent(0)); add(const ExposureOffsetChangedEvent(0));
} }
Future<void> _onExposureSpotChangedEvent(ExposureSpotChangedEvent event, Emitter emit) async {
if (_cameraController != null) {
_cameraController!.setExposurePoint(event.offset);
_cameraController!.setFocusPoint(event.offset);
_emitActiveState(emit);
}
}
void _emitActiveState(Emitter emit) { void _emitActiveState(Emitter emit) {
emit( emit(
CameraActiveState( CameraActiveState(

View file

@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/res/dimens.dart';
class CameraSpotDetector extends StatefulWidget {
final ValueChanged<Offset> onSpotTap;
const CameraSpotDetector({
required this.onSpotTap,
super.key,
});
@override
State<CameraSpotDetector> createState() => _CameraSpotDetectorState();
}
class _CameraSpotDetectorState extends State<CameraSpotDetector> {
Offset? spot;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (_, constraints) => GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (TapDownDetails details) => onViewFinderTap(details, constraints),
child: Stack(
children: [
if (spot != null)
AnimatedPositioned(
duration: Dimens.durationS,
left: spot!.dx - Dimens.grid16 / 2,
top: spot!.dy - Dimens.grid16 / 2,
height: Dimens.grid16,
width: Dimens.grid16,
child: const _Spot(),
),
],
),
),
);
}
void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) {
setState(() {
spot = details.localPosition;
});
widget.onSpotTap(
Offset(
details.localPosition.dx / constraints.maxWidth,
details.localPosition.dy / constraints.maxHeight,
),
);
}
}
class _Spot extends StatelessWidget {
const _Spot();
@override
Widget build(BuildContext context) {
return const DecoratedBox(
decoration: BoxDecoration(
color: Colors.white70,
shape: BoxShape.circle,
),
);
}
}

View file

@ -12,14 +12,17 @@ class CameraView extends StatelessWidget {
final value = controller.value; final value = controller.value;
return ValueListenableBuilder<CameraValue>( return ValueListenableBuilder<CameraValue>(
valueListenable: controller, valueListenable: controller,
builder: (_, __, ___) => AspectRatio( builder: (_, __, Widget? child) => AspectRatio(
aspectRatio: _isLandscape(value) ? value.aspectRatio : (1 / value.aspectRatio), aspectRatio: _isLandscape(value) ? value.aspectRatio : (1 / value.aspectRatio),
child: value.isInitialized child: Stack(
? RotatedBox( children: [
quarterTurns: _getQuarterTurns(value), RotatedBox(
child: controller.buildPreview(), quarterTurns: _getQuarterTurns(value),
) child: controller.buildPreview(),
: const SizedBox.shrink(), ),
child ?? const SizedBox(),
],
),
), ),
); );
} }
@ -42,8 +45,6 @@ class CameraView extends StatelessWidget {
DeviceOrientation _getApplicableOrientation(CameraValue value) { DeviceOrientation _getApplicableOrientation(CameraValue value) {
return value.isRecordingVideo return value.isRecordingVideo
? value.recordingOrientation! ? value.recordingOrientation!
: (value.previewPauseOrientation ?? : (value.previewPauseOrientation ?? value.lockedCaptureOrientation ?? value.deviceOrientation);
value.lockedCaptureOrientation ??
value.deviceOrientation);
} }
} }

View file

@ -4,6 +4,7 @@ import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/platform_config.dart'; import 'package:lightmeter/platform_config.dart';
import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/providers/user_preferences_provider.dart';
import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/camera_spot_detector/widget_camera_spot_detector.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart'; import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/camera_view_placeholder/widget_placeholder_camera_view.dart'; import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/camera_view_placeholder/widget_placeholder_camera_view.dart';
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/histogram/widget_histogram.dart'; import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/histogram/widget_histogram.dart';
@ -12,8 +13,14 @@ import 'package:lightmeter/screens/metering/components/camera_container/models/c
class CameraPreview extends StatefulWidget { class CameraPreview extends StatefulWidget {
final CameraController? controller; final CameraController? controller;
final CameraErrorType? error; final CameraErrorType? error;
final ValueChanged<Offset> onSpotTap;
const CameraPreview({this.controller, this.error, super.key}); const CameraPreview({
this.controller,
this.error,
required this.onSpotTap,
super.key,
});
@override @override
State<CameraPreview> createState() => _CameraPreviewState(); State<CameraPreview> createState() => _CameraPreviewState();
@ -31,7 +38,10 @@ class _CameraPreviewState extends State<CameraPreview> {
AnimatedSwitcher( AnimatedSwitcher(
duration: Dimens.switchDuration, duration: Dimens.switchDuration,
child: widget.controller != null child: widget.controller != null
? _CameraPreviewBuilder(controller: widget.controller!) ? _CameraPreviewBuilder(
controller: widget.controller!,
onSpotTap: widget.onSpotTap,
)
: CameraViewPlaceholder(error: widget.error), : CameraViewPlaceholder(error: widget.error),
), ),
], ],
@ -43,16 +53,19 @@ class _CameraPreviewState extends State<CameraPreview> {
class _CameraPreviewBuilder extends StatefulWidget { class _CameraPreviewBuilder extends StatefulWidget {
final CameraController controller; final CameraController controller;
final ValueChanged<Offset> onSpotTap;
const _CameraPreviewBuilder({required this.controller}); const _CameraPreviewBuilder({
required this.controller,
required this.onSpotTap,
});
@override @override
State<_CameraPreviewBuilder> createState() => _CameraPreviewBuilderState(); State<_CameraPreviewBuilder> createState() => _CameraPreviewBuilderState();
} }
class _CameraPreviewBuilderState extends State<_CameraPreviewBuilder> { class _CameraPreviewBuilderState extends State<_CameraPreviewBuilder> {
late final ValueNotifier<bool> _initializedNotifier = late final ValueNotifier<bool> _initializedNotifier = ValueNotifier<bool>(widget.controller.value.isInitialized);
ValueNotifier<bool>(widget.controller.value.isInitialized);
@override @override
void initState() { void initState() {
@ -89,6 +102,7 @@ class _CameraPreviewBuilderState extends State<_CameraPreviewBuilder> {
bottom: Dimens.grid16, bottom: Dimens.grid16,
child: CameraHistogram(controller: widget.controller), child: CameraHistogram(controller: widget.controller),
), ),
CameraSpotDetector(onSpotTap: widget.onSpotTap)
], ],
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),

View file

@ -1,3 +1,5 @@
import 'package:flutter/gestures.dart';
abstract class CameraContainerEvent { abstract class CameraContainerEvent {
const CameraContainerEvent(); const CameraContainerEvent();
} }
@ -53,3 +55,19 @@ class ExposureOffsetChangedEvent extends CameraContainerEvent {
class ExposureOffsetResetEvent extends CameraContainerEvent { class ExposureOffsetResetEvent extends CameraContainerEvent {
const ExposureOffsetResetEvent(); const ExposureOffsetResetEvent();
} }
class ExposureSpotChangedEvent extends CameraContainerEvent {
final Offset offset;
const ExposureSpotChangedEvent(this.offset);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other.runtimeType != runtimeType) return false;
return other is ExposureSpotChangedEvent && other.offset == offset;
}
@override
int get hashCode => Object.hash(offset, runtimeType);
}

View file

@ -143,6 +143,9 @@ class _CameraViewBuilder extends StatelessWidget {
builder: (context, state) => CameraPreview( builder: (context, state) => CameraPreview(
controller: state is CameraInitializedState ? state.controller : null, controller: state is CameraInitializedState ? state.controller : null,
error: state is CameraErrorState ? state.error : null, error: state is CameraErrorState ? state.error : null,
onSpotTap: (value) {
context.read<CameraContainerBloc>().add(ExposureSpotChangedEvent(value));
},
), ),
); );
} }

View file

@ -1,5 +1,7 @@
// ignore_for_file: prefer_const_constructors // ignore_for_file: prefer_const_constructors
import 'dart:ui';
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:test/test.dart'; import 'package:test/test.dart';
@ -41,4 +43,23 @@ void main() {
}); });
}, },
); );
group(
'`ExposureSpotChangedEvent`',
() {
final a = ExposureSpotChangedEvent(Offset(0.0, 0.0));
final b = ExposureSpotChangedEvent(Offset(0.0, 0.0));
final c = ExposureSpotChangedEvent(Offset(2.0, 2.0));
test('==', () {
expect(a == b && b == a, true);
expect(a != c && c != a, true);
expect(b != c && c != b, true);
});
test('hashCode', () {
expect(a.hashCode == b.hashCode, true);
expect(a.hashCode != c.hashCode, true);
expect(b.hashCode != c.hashCode, true);
});
},
);
} }