mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2025-04-20 08:20:41 +00:00
imlemented CameraSpotDetector
This commit is contained in:
parent
ddc7ec8c8b
commit
d54d2a121a
7 changed files with 152 additions and 22 deletions
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue