mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-22 07:20:39 +00:00
Added zoom and exposure sliders
added `ZoomSlider` (wip) wip implemented horizontal slider implemented vertical slider integrated 2 sliders to metering screen (wip) removed filled track placed camera slider in the proper folder temp fix for exposure list fonts update exposure event added ruler to `CameraExposureSlider` moved slider sizes to dimens moved `CameraZoomSlider` to the separate folder
This commit is contained in:
parent
14bac950cf
commit
abd07764fd
12 changed files with 420 additions and 12 deletions
|
@ -20,4 +20,10 @@ class Dimens {
|
||||||
static const Duration durationM = Duration(milliseconds: 200);
|
static const Duration durationM = Duration(milliseconds: 200);
|
||||||
static const Duration durationML = Duration(milliseconds: 250);
|
static const Duration durationML = Duration(milliseconds: 250);
|
||||||
static const Duration durationL = Duration(milliseconds: 300);
|
static const Duration durationL = Duration(milliseconds: 300);
|
||||||
|
|
||||||
|
// `CameraSlider`
|
||||||
|
static const double cameraSliderTrackHeight = grid4;
|
||||||
|
static const double cameraSliderTrackRadius = cameraSliderTrackHeight / 2;
|
||||||
|
static const double cameraSliderHandleSize = 32;
|
||||||
|
static const double cameraSliderHandleIconSize = cameraSliderHandleSize * 2 / 3;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
|
||||||
|
class CameraSlider extends StatefulWidget {
|
||||||
|
final Icon icon;
|
||||||
|
final double value;
|
||||||
|
final double min;
|
||||||
|
final double max;
|
||||||
|
final ValueChanged<double> onChanged;
|
||||||
|
final bool isVertical;
|
||||||
|
|
||||||
|
const CameraSlider({
|
||||||
|
required this.icon,
|
||||||
|
required this.value,
|
||||||
|
required this.min,
|
||||||
|
required this.max,
|
||||||
|
required this.onChanged,
|
||||||
|
this.isVertical = false,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CameraSlider> createState() => _CameraSliderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CameraSliderState extends State<CameraSlider> {
|
||||||
|
double relativeValue = 0.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
relativeValue = (widget.value - widget.min) / (widget.max - widget.min);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(CameraSlider oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
relativeValue = (widget.value - widget.min) / (widget.max - widget.min);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final biggestSize = widget.isVertical ? constraints.maxHeight : constraints.maxWidth;
|
||||||
|
final handleDistance = biggestSize - Dimens.cameraSliderHandleSize;
|
||||||
|
return RotatedBox(
|
||||||
|
quarterTurns: widget.isVertical ? -1 : 0,
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onTapUp: (details) => _updateHandlePosition(details.localPosition.dx, handleDistance),
|
||||||
|
onHorizontalDragUpdate: (details) => _updateHandlePosition(details.localPosition.dx, handleDistance),
|
||||||
|
child: SizedBox(
|
||||||
|
height: Dimens.cameraSliderHandleSize,
|
||||||
|
width: biggestSize,
|
||||||
|
child: _Slider(
|
||||||
|
handleDistance: handleDistance,
|
||||||
|
handleSize: Dimens.cameraSliderHandleSize,
|
||||||
|
trackThickness: Dimens.cameraSliderTrackHeight,
|
||||||
|
value: relativeValue,
|
||||||
|
icon: RotatedBox(
|
||||||
|
quarterTurns: widget.isVertical ? 1 : 0,
|
||||||
|
child: widget.icon,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateHandlePosition(double offset, double handleDistance) {
|
||||||
|
if (offset <= Dimens.cameraSliderHandleSize / 2) {
|
||||||
|
relativeValue = 0;
|
||||||
|
} else if (offset >= handleDistance + Dimens.cameraSliderHandleSize / 2) {
|
||||||
|
relativeValue = 1;
|
||||||
|
} else {
|
||||||
|
relativeValue = (offset - Dimens.cameraSliderHandleSize / 2) / handleDistance;
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
widget.onChanged(_clampToRange(relativeValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
double _clampToRange(double relativeValue) => relativeValue * (widget.max - widget.min) + widget.min;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Slider extends StatelessWidget {
|
||||||
|
final double handleSize;
|
||||||
|
final double trackThickness;
|
||||||
|
final double handleDistance;
|
||||||
|
final double value;
|
||||||
|
final Widget icon;
|
||||||
|
|
||||||
|
const _Slider({
|
||||||
|
required this.handleSize,
|
||||||
|
required this.trackThickness,
|
||||||
|
required this.handleDistance,
|
||||||
|
required this.value,
|
||||||
|
required this.icon,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
height: trackThickness,
|
||||||
|
width: handleDistance + trackThickness, // add thickness to maintain radius overlap with handle
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(trackThickness / 2),
|
||||||
|
child: ColoredBox(color: Theme.of(context).colorScheme.surfaceVariant),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AnimatedPositioned.fromRect(
|
||||||
|
duration: Dimens.durationM,
|
||||||
|
rect: Rect.fromCenter(
|
||||||
|
center: Offset(
|
||||||
|
handleSize / 2 + handleDistance * value,
|
||||||
|
handleSize / 2,
|
||||||
|
),
|
||||||
|
width: handleSize,
|
||||||
|
height: handleSize,
|
||||||
|
),
|
||||||
|
child: _Handle(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
size: handleSize,
|
||||||
|
child: IconTheme(
|
||||||
|
data: Theme.of(context).iconTheme.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
size: Dimens.cameraSliderHandleIconSize,
|
||||||
|
),
|
||||||
|
child: icon,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Handle extends StatelessWidget {
|
||||||
|
final Color color;
|
||||||
|
final double size;
|
||||||
|
final Widget? child;
|
||||||
|
|
||||||
|
const _Handle({
|
||||||
|
required this.color,
|
||||||
|
required this.size,
|
||||||
|
this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(size / 2),
|
||||||
|
child: SizedBox(
|
||||||
|
height: size,
|
||||||
|
width: size,
|
||||||
|
child: ColoredBox(
|
||||||
|
color: color,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
import 'package:lightmeter/screens/metering/ev_source/camera/bloc_camera.dart';
|
||||||
|
import 'package:lightmeter/screens/metering/ev_source/camera/event_camera.dart';
|
||||||
|
import 'package:lightmeter/screens/metering/ev_source/camera/state_camera.dart';
|
||||||
|
import 'package:lightmeter/utils/to_string_signed.dart';
|
||||||
|
|
||||||
|
import 'shared/widget_slider_camera.dart';
|
||||||
|
|
||||||
|
class CameraExposureSlider extends StatelessWidget {
|
||||||
|
const CameraExposureSlider({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<CameraBloc, CameraState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state is CameraActiveState) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.sync),
|
||||||
|
onPressed: state.currentExposureOffset != 0.0
|
||||||
|
? () => context.read<CameraBloc>().add(const ExposureOffsetChangedEvent(0))
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: Dimens.grid8),
|
||||||
|
child: _Ruler(state.minExposureOffset, state.maxExposureOffset),
|
||||||
|
),
|
||||||
|
CameraSlider(
|
||||||
|
isVertical: true,
|
||||||
|
icon: const Icon(Icons.light_mode),
|
||||||
|
value: state.currentExposureOffset,
|
||||||
|
min: state.minExposureOffset,
|
||||||
|
max: state.maxExposureOffset,
|
||||||
|
onChanged: (value) {
|
||||||
|
context.read<CameraBloc>().add(ExposureOffsetChangedEvent(value));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Ruler extends StatelessWidget {
|
||||||
|
final double min;
|
||||||
|
final double max;
|
||||||
|
|
||||||
|
const _Ruler(this.min, this.max);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: List.generate(
|
||||||
|
(max - min + 1).toInt(),
|
||||||
|
(index) {
|
||||||
|
final bool showValue = index % 2 == 0.0 || index == 0.0;
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
if (showValue)
|
||||||
|
Text(
|
||||||
|
(index + min).toStringSigned(),
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(width: Dimens.grid8),
|
||||||
|
ColoredBox(
|
||||||
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 1,
|
||||||
|
width: showValue ? Dimens.grid16 : Dimens.grid8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: Dimens.grid8),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:lightmeter/screens/metering/ev_source/camera/bloc_camera.dart';
|
||||||
|
import 'package:lightmeter/screens/metering/ev_source/camera/event_camera.dart';
|
||||||
|
import 'package:lightmeter/screens/metering/ev_source/camera/state_camera.dart';
|
||||||
|
|
||||||
|
import 'shared/widget_slider_camera.dart';
|
||||||
|
|
||||||
|
class CameraZoomSlider extends StatelessWidget {
|
||||||
|
const CameraZoomSlider({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<CameraBloc, CameraState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state is CameraActiveState) {
|
||||||
|
return CameraSlider(
|
||||||
|
icon: const Icon(Icons.search),
|
||||||
|
value: state.currentZoom,
|
||||||
|
min: state.minZoom,
|
||||||
|
max: state.maxZoom,
|
||||||
|
onChanged: (value) {
|
||||||
|
context.read<CameraBloc>().add(ZoomChangedEvent(value));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ class ExposurePairsListItem<T extends PhotographyStopValue> extends StatelessWid
|
||||||
width: tickLength(),
|
width: tickLength(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (value.stopType != StopType.full) const SizedBox(width: Dimens.grid8),
|
if (value.stopType != StopType.full) const SizedBox(width: Dimens.grid4),
|
||||||
];
|
];
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: tickOnTheLeft ? MainAxisAlignment.start : MainAxisAlignment.end,
|
mainAxisAlignment: tickOnTheLeft ? MainAxisAlignment.start : MainAxisAlignment.end,
|
||||||
|
@ -45,9 +45,9 @@ class ExposurePairsListItem<T extends PhotographyStopValue> extends StatelessWid
|
||||||
double tickLength() {
|
double tickLength() {
|
||||||
switch (value.stopType) {
|
switch (value.stopType) {
|
||||||
case StopType.full:
|
case StopType.full:
|
||||||
return Dimens.grid24;
|
|
||||||
case StopType.half:
|
|
||||||
return Dimens.grid16;
|
return Dimens.grid16;
|
||||||
|
case StopType.half:
|
||||||
|
return Dimens.grid8;
|
||||||
case StopType.third:
|
case StopType.third:
|
||||||
return Dimens.grid8;
|
return Dimens.grid8;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ class ExposurePairsList extends StatelessWidget {
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
key: ValueKey(exposurePairs.hashCode),
|
key: ValueKey(exposurePairs.hashCode),
|
||||||
padding: const EdgeInsets.all(Dimens.paddingL),
|
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingL),
|
||||||
itemCount: exposurePairs.length,
|
itemCount: exposurePairs.length,
|
||||||
itemBuilder: (_, index) => Stack(
|
itemBuilder: (_, index) => Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
|
|
|
@ -13,14 +13,14 @@ class CameraView extends StatelessWidget {
|
||||||
return AspectRatio(
|
return AspectRatio(
|
||||||
aspectRatio: 3 / 4,
|
aspectRatio: 3 / 4,
|
||||||
child: BlocBuilder<CameraBloc, CameraState>(
|
child: BlocBuilder<CameraBloc, CameraState>(
|
||||||
|
buildWhen: (previous, current) => current is CameraInitializedState,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is CameraReadyState) {
|
if (state is CameraInitializedState) {
|
||||||
final value = state.controller.value;
|
final value = state.controller.value;
|
||||||
return ValueListenableBuilder<CameraValue>(
|
return ValueListenableBuilder<CameraValue>(
|
||||||
valueListenable: state.controller,
|
valueListenable: state.controller,
|
||||||
builder: (_, __, ___) => AspectRatio(
|
builder: (_, __, ___) => AspectRatio(
|
||||||
aspectRatio:
|
aspectRatio: _isLandscape(value) ? value.aspectRatio : (1 / value.aspectRatio),
|
||||||
_isLandscape(value) ? value.aspectRatio : (1 / value.aspectRatio),
|
|
||||||
child: RotatedBox(
|
child: RotatedBox(
|
||||||
quarterTurns: _getQuarterTurns(value),
|
quarterTurns: _getQuarterTurns(value),
|
||||||
child: state.controller.buildPreview(),
|
child: state.controller.buildPreview(),
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'dart:math';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:exif/exif.dart';
|
import 'package:exif/exif.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/screens/metering/ev_source/ev_source_bloc.dart';
|
import 'package:lightmeter/screens/metering/ev_source/ev_source_bloc.dart';
|
||||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
||||||
|
@ -20,6 +20,15 @@ class CameraBloc extends EvSourceBloc<CameraEvent, CameraState> {
|
||||||
CameraController? _cameraController;
|
CameraController? _cameraController;
|
||||||
CameraController? get cameraController => _cameraController;
|
CameraController? get cameraController => _cameraController;
|
||||||
|
|
||||||
|
static const _maxZoom = 7.0;
|
||||||
|
RangeValues? _zoomRange;
|
||||||
|
double _currentZoom = 0.0;
|
||||||
|
|
||||||
|
static const _exposureMaxRange = RangeValues(-4, 4);
|
||||||
|
RangeValues? _exposureOffsetRange;
|
||||||
|
double _exposureStep = 0.0;
|
||||||
|
double _currentExposureOffset = 0.0;
|
||||||
|
|
||||||
CameraBloc(MeteringCommunicationBloc communicationBloc)
|
CameraBloc(MeteringCommunicationBloc communicationBloc)
|
||||||
: super(
|
: super(
|
||||||
communicationBloc,
|
communicationBloc,
|
||||||
|
@ -29,6 +38,8 @@ class CameraBloc extends EvSourceBloc<CameraEvent, CameraState> {
|
||||||
WidgetsBinding.instance.addObserver(_observer);
|
WidgetsBinding.instance.addObserver(_observer);
|
||||||
|
|
||||||
on<InitializeEvent>(_onInitialize);
|
on<InitializeEvent>(_onInitialize);
|
||||||
|
on<ZoomChangedEvent>(_onZoomChanged);
|
||||||
|
on<ExposureOffsetChangedEvent>(_onExposureOffsetChanged);
|
||||||
|
|
||||||
add(const InitializeEvent());
|
add(const InitializeEvent());
|
||||||
}
|
}
|
||||||
|
@ -66,7 +77,24 @@ class CameraBloc extends EvSourceBloc<CameraEvent, CameraState> {
|
||||||
|
|
||||||
await _cameraController!.initialize();
|
await _cameraController!.initialize();
|
||||||
await _cameraController!.setFlashMode(FlashMode.off);
|
await _cameraController!.setFlashMode(FlashMode.off);
|
||||||
emit(CameraReadyState(_cameraController!));
|
|
||||||
|
_zoomRange = await Future.wait<double>([
|
||||||
|
_cameraController!.getMinZoomLevel(),
|
||||||
|
_cameraController!.getMaxZoomLevel(),
|
||||||
|
]).then((levels) => RangeValues(levels[0], min(_maxZoom, levels[1])));
|
||||||
|
_currentZoom = _zoomRange!.start;
|
||||||
|
|
||||||
|
_exposureOffsetRange = await Future.wait<double>([
|
||||||
|
_cameraController!.getMinExposureOffset(),
|
||||||
|
_cameraController!.getMaxExposureOffset(),
|
||||||
|
]).then((levels) => RangeValues(max(_exposureMaxRange.start, levels[0]), min(_exposureMaxRange.end, levels[1])));
|
||||||
|
await _cameraController!.getExposureOffsetStepSize().then((value) {
|
||||||
|
_exposureStep = value == 0 ? 0.1 : value;
|
||||||
|
});
|
||||||
|
|
||||||
|
emit(CameraInitializedState(_cameraController!));
|
||||||
|
|
||||||
|
_emitActiveState(emit);
|
||||||
_takePhoto().then((ev100) {
|
_takePhoto().then((ev100) {
|
||||||
if (ev100 != null) {
|
if (ev100 != null) {
|
||||||
communicationBloc.add(communication_event.MeasuredEvent(ev100));
|
communicationBloc.add(communication_event.MeasuredEvent(ev100));
|
||||||
|
@ -77,6 +105,30 @@ class CameraBloc extends EvSourceBloc<CameraEvent, CameraState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onZoomChanged(ZoomChangedEvent event, Emitter emit) async {
|
||||||
|
_cameraController!.setZoomLevel(event.value);
|
||||||
|
_currentZoom = event.value;
|
||||||
|
_emitActiveState(emit);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onExposureOffsetChanged(ExposureOffsetChangedEvent event, Emitter emit) async {
|
||||||
|
_cameraController!.setExposureOffset(event.value);
|
||||||
|
_currentExposureOffset = event.value;
|
||||||
|
_emitActiveState(emit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _emitActiveState(Emitter emit) {
|
||||||
|
emit(CameraActiveState(
|
||||||
|
minZoom: _zoomRange!.start,
|
||||||
|
maxZoom: _zoomRange!.end,
|
||||||
|
currentZoom: _currentZoom,
|
||||||
|
minExposureOffset: _exposureOffsetRange!.start,
|
||||||
|
maxExposureOffset: _exposureOffsetRange!.end,
|
||||||
|
exposureOffsetStep: _exposureStep,
|
||||||
|
currentExposureOffset: _currentExposureOffset,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
Future<double?> _takePhoto() async {
|
Future<double?> _takePhoto() async {
|
||||||
if (_cameraController == null ||
|
if (_cameraController == null ||
|
||||||
!_cameraController!.value.isInitialized ||
|
!_cameraController!.value.isInitialized ||
|
||||||
|
|
|
@ -5,3 +5,15 @@ abstract class CameraEvent {
|
||||||
class InitializeEvent extends CameraEvent {
|
class InitializeEvent extends CameraEvent {
|
||||||
const InitializeEvent();
|
const InitializeEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ZoomChangedEvent extends CameraEvent {
|
||||||
|
final double value;
|
||||||
|
|
||||||
|
const ZoomChangedEvent(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExposureOffsetChangedEvent extends CameraEvent {
|
||||||
|
final double value;
|
||||||
|
|
||||||
|
const ExposureOffsetChangedEvent(this.value);
|
||||||
|
}
|
||||||
|
|
|
@ -12,10 +12,30 @@ class CameraLoadingState extends CameraState {
|
||||||
const CameraLoadingState();
|
const CameraLoadingState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class CameraReadyState extends CameraState {
|
class CameraInitializedState extends CameraState {
|
||||||
final CameraController controller;
|
final CameraController controller;
|
||||||
|
|
||||||
const CameraReadyState(this.controller);
|
const CameraInitializedState(this.controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
class CameraActiveState extends CameraState {
|
||||||
|
final double minZoom;
|
||||||
|
final double maxZoom;
|
||||||
|
final double currentZoom;
|
||||||
|
final double minExposureOffset;
|
||||||
|
final double maxExposureOffset;
|
||||||
|
final double? exposureOffsetStep;
|
||||||
|
final double currentExposureOffset;
|
||||||
|
|
||||||
|
const CameraActiveState({
|
||||||
|
required this.minZoom,
|
||||||
|
required this.maxZoom,
|
||||||
|
required this.currentZoom,
|
||||||
|
required this.minExposureOffset,
|
||||||
|
required this.maxExposureOffset,
|
||||||
|
required this.exposureOffsetStep,
|
||||||
|
required this.currentExposureOffset,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class CameraErrorState extends CameraState {
|
class CameraErrorState extends CameraState {
|
||||||
|
|
|
@ -5,6 +5,8 @@ import 'package:lightmeter/res/dimens.dart';
|
||||||
import 'package:lightmeter/screens/settings/screen_settings.dart';
|
import 'package:lightmeter/screens/settings/screen_settings.dart';
|
||||||
|
|
||||||
import 'components/bottom_controls/widget_bottom_controls.dart';
|
import 'components/bottom_controls/widget_bottom_controls.dart';
|
||||||
|
import 'components/camera/widget_exposure_slider.dart';
|
||||||
|
import 'components/camera/widget_zoom_camera.dart';
|
||||||
import 'components/exposure_pairs_list/widget_list_exposure_pairs.dart';
|
import 'components/exposure_pairs_list/widget_list_exposure_pairs.dart';
|
||||||
import 'components/topbar/widget_topbar.dart';
|
import 'components/topbar/widget_topbar.dart';
|
||||||
import 'bloc_metering.dart';
|
import 'bloc_metering.dart';
|
||||||
|
@ -42,6 +44,19 @@ class _MeteringScreenState extends State<MeteringScreen> {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: ExposurePairsList(state.exposurePairs)),
|
Expanded(child: ExposurePairsList(state.exposurePairs)),
|
||||||
|
const SizedBox(width: Dimens.grid8),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
|
||||||
|
child: Column(
|
||||||
|
children: const [
|
||||||
|
Expanded(child: CameraExposureSlider()),
|
||||||
|
SizedBox(height: Dimens.grid24),
|
||||||
|
CameraZoomSlider(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
9
lib/utils/to_string_signed.dart
Normal file
9
lib/utils/to_string_signed.dart
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
extension SignedString on num {
|
||||||
|
String toStringSigned() {
|
||||||
|
if (this > 0) {
|
||||||
|
return "+${toString()}";
|
||||||
|
} else {
|
||||||
|
return toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue