mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-23 16:00:41 +00:00
wip
This commit is contained in:
parent
827c5e1981
commit
ae07f882ad
40 changed files with 995 additions and 699 deletions
|
@ -1,8 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:light_sensor/light_sensor.dart';
|
||||
import 'package:lightmeter/data/haptics_service.dart';
|
||||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
@ -11,6 +11,7 @@ import 'data/permissions_service.dart';
|
|||
import 'data/shared_prefs_service.dart';
|
||||
import 'environment.dart';
|
||||
import 'generated/l10n.dart';
|
||||
import 'providers/ev_source_type_provider.dart';
|
||||
import 'res/theme.dart';
|
||||
import 'screens/metering/flow_metering.dart';
|
||||
import 'screens/settings/flow_settings.dart';
|
||||
|
@ -18,63 +19,72 @@ import 'utils/stop_type_provider.dart';
|
|||
|
||||
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
|
||||
|
||||
class Application extends StatelessWidget {
|
||||
class Application extends StatefulWidget {
|
||||
final Environment env;
|
||||
|
||||
const Application(this.env, {super.key});
|
||||
|
||||
@override
|
||||
State<Application> createState() => _ApplicationState();
|
||||
}
|
||||
|
||||
class _ApplicationState extends State<Application> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<SharedPreferences>(
|
||||
future: SharedPreferences.getInstance(),
|
||||
return FutureBuilder(
|
||||
future: Future.wait([
|
||||
SharedPreferences.getInstance(),
|
||||
LightSensor.hasSensor,
|
||||
]),
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.data != null) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: env),
|
||||
Provider.value(value: EvSourceType.camera),
|
||||
Provider(create: (_) => UserPreferencesService(snapshot.data!)),
|
||||
Provider.value(value: widget.env.copyWith(hasLightSensor: snapshot.data![1] as bool)),
|
||||
Provider(create: (_) => UserPreferencesService(snapshot.data![0] as SharedPreferences)),
|
||||
Provider(create: (_) => const HapticsService()),
|
||||
Provider(create: (_) => PermissionsService()),
|
||||
Provider(create: (_) => const LightSensorService()),
|
||||
],
|
||||
child: StopTypeProvider(
|
||||
child: ThemeProvider(
|
||||
builder: (context, _) {
|
||||
final systemIconsBrightness = ThemeData.estimateBrightnessForColor(
|
||||
context.watch<ThemeData>().colorScheme.onSurface,
|
||||
);
|
||||
return AnnotatedRegion(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarBrightness: systemIconsBrightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
statusBarIconBrightness: systemIconsBrightness,
|
||||
systemNavigationBarColor: context.watch<ThemeData>().colorScheme.surface,
|
||||
systemNavigationBarIconBrightness: systemIconsBrightness,
|
||||
),
|
||||
child: MaterialApp(
|
||||
theme: context.watch<ThemeData>(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
builder: (context, child) => MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
|
||||
child: child!,
|
||||
child: EvSourceTypeProvider(
|
||||
child: ThemeProvider(
|
||||
builder: (context, _) {
|
||||
final systemIconsBrightness = ThemeData.estimateBrightnessForColor(
|
||||
context.watch<ThemeData>().colorScheme.onSurface,
|
||||
);
|
||||
return AnnotatedRegion(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarBrightness: systemIconsBrightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
statusBarIconBrightness: systemIconsBrightness,
|
||||
systemNavigationBarColor: context.watch<ThemeData>().colorScheme.surface,
|
||||
systemNavigationBarIconBrightness: systemIconsBrightness,
|
||||
),
|
||||
initialRoute: "metering",
|
||||
routes: {
|
||||
"metering": (context) => const MeteringFlow(),
|
||||
"settings": (context) => const SettingsFlow(),
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: MaterialApp(
|
||||
theme: context.watch<ThemeData>(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
builder: (context, child) => MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
|
||||
child: child!,
|
||||
),
|
||||
initialRoute: "metering",
|
||||
routes: {
|
||||
"metering": (context) => const MeteringFlow(),
|
||||
"settings": (context) => const SettingsFlow(),
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'models/ev_source_type.dart';
|
||||
import 'models/photography_values/iso_value.dart';
|
||||
import 'models/photography_values/nd_value.dart';
|
||||
import 'models/theme_type.dart';
|
||||
|
||||
class UserPreferencesService {
|
||||
static const _isoKey = "iso";
|
||||
static const _ndFilterKey = "nd";
|
||||
static const _ndFilterKey = "ndFilter";
|
||||
|
||||
static const _evSourceTypeKey = "evSourceType";
|
||||
static const _cameraEvCalibrationKey = "cameraEvCalibration";
|
||||
|
||||
static const _hapticsKey = "haptics";
|
||||
|
@ -24,6 +26,9 @@ class UserPreferencesService {
|
|||
NdValue get ndFilter => ndValues.firstWhere((v) => v.value == (_sharedPreferences.getInt(_ndFilterKey) ?? 0));
|
||||
set ndFilter(NdValue value) => _sharedPreferences.setInt(_ndFilterKey, value.value);
|
||||
|
||||
EvSourceType get evSourceType => EvSourceType.values[_sharedPreferences.getInt(_evSourceTypeKey) ?? 0];
|
||||
set evSourceType(EvSourceType value) => _sharedPreferences.setInt(_evSourceTypeKey, value.index);
|
||||
|
||||
bool get haptics => _sharedPreferences.getBool(_hapticsKey) ?? false;
|
||||
set haptics(bool value) => _sharedPreferences.setBool(_hapticsKey, value);
|
||||
|
||||
|
|
|
@ -3,19 +3,31 @@ class Environment {
|
|||
final String issuesReportUrl;
|
||||
final String contactEmail;
|
||||
|
||||
final bool hasLightSensor;
|
||||
|
||||
const Environment({
|
||||
required this.sourceCodeUrl,
|
||||
required this.issuesReportUrl,
|
||||
required this.contactEmail,
|
||||
this.hasLightSensor = false,
|
||||
});
|
||||
|
||||
const Environment.dev()
|
||||
: sourceCodeUrl = 'https://github.com/vodemn/m3_lightmeter',
|
||||
issuesReportUrl = 'https://github.com/vodemn/m3_lightmeter/issues',
|
||||
contactEmail = 'contact.vodemn@gmail.com';
|
||||
contactEmail = 'contact.vodemn@gmail.com',
|
||||
hasLightSensor = false;
|
||||
|
||||
const Environment.prod()
|
||||
: sourceCodeUrl = 'https://github.com/vodemn/m3_lightmeter',
|
||||
issuesReportUrl = 'https://github.com/vodemn/m3_lightmeter/issues',
|
||||
contactEmail = 'contact.vodemn@gmail.com';
|
||||
contactEmail = 'contact.vodemn@gmail.com',
|
||||
hasLightSensor = false;
|
||||
|
||||
Environment copyWith({bool? hasLightSensor}) => Environment(
|
||||
sourceCodeUrl: sourceCodeUrl,
|
||||
issuesReportUrl: issuesReportUrl,
|
||||
contactEmail: contactEmail,
|
||||
hasLightSensor: hasLightSensor ?? this.hasLightSensor,
|
||||
);
|
||||
}
|
||||
|
|
64
lib/providers/ev_source_type_provider.dart
Normal file
64
lib/providers/ev_source_type_provider.dart
Normal file
|
@ -0,0 +1,64 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||
import 'package:lightmeter/environment.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class EvSourceTypeProvider extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
const EvSourceTypeProvider({required this.child, super.key});
|
||||
|
||||
static EvSourceTypeProviderState of(BuildContext context) {
|
||||
return context.findAncestorStateOfType<EvSourceTypeProviderState>()!;
|
||||
}
|
||||
|
||||
@override
|
||||
State<EvSourceTypeProvider> createState() => EvSourceTypeProviderState();
|
||||
}
|
||||
|
||||
class EvSourceTypeProviderState extends State<EvSourceTypeProvider> {
|
||||
late final ValueNotifier<EvSourceType> valueListenable;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final evSourceType = context.read<UserPreferencesService>().evSourceType;
|
||||
valueListenable = ValueNotifier(
|
||||
evSourceType == EvSourceType.sensor && !context.read<Environment>().hasLightSensor
|
||||
? EvSourceType.camera
|
||||
: evSourceType,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
valueListenable.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: valueListenable,
|
||||
builder: (_, value, child) => Provider.value(
|
||||
value: value,
|
||||
child: child,
|
||||
),
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void toggleType() {
|
||||
switch (valueListenable.value) {
|
||||
case EvSourceType.camera:
|
||||
if (context.read<Environment>().hasLightSensor) {
|
||||
valueListenable.value = EvSourceType.sensor;
|
||||
}
|
||||
break;
|
||||
case EvSourceType.sensor:
|
||||
valueListenable.value = EvSourceType.camera;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,10 +5,12 @@ import 'components/widget_button_measure.dart';
|
|||
import 'components/widget_button_secondary.dart';
|
||||
|
||||
class MeteringBottomControls extends StatelessWidget {
|
||||
final VoidCallback? onSwitchEvSourceType;
|
||||
final VoidCallback onMeasure;
|
||||
final VoidCallback onSettings;
|
||||
|
||||
const MeteringBottomControls({
|
||||
required this.onSwitchEvSourceType,
|
||||
required this.onMeasure,
|
||||
required this.onSettings,
|
||||
super.key,
|
||||
|
@ -30,7 +32,15 @@ class MeteringBottomControls extends StatelessWidget {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
const Spacer(),
|
||||
if (onSwitchEvSourceType != null)
|
||||
Expanded(
|
||||
child: MeteringSecondaryButton(
|
||||
onPressed: onSwitchEvSourceType!,
|
||||
icon: Icons.sync,
|
||||
),
|
||||
)
|
||||
else
|
||||
const Spacer(),
|
||||
MeteringMeasureButton(
|
||||
onTap: onMeasure,
|
||||
),
|
||||
|
|
|
@ -7,7 +7,7 @@ import 'package:exif/exif.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/interactors/metering_interactor.dart';
|
||||
import 'package:lightmeter/screens/metering/ev_source/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/communication/bloc_communication_metering.dart';
|
||||
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart'
|
||||
as communication_event;
|
||||
|
@ -15,10 +15,10 @@ import 'package:lightmeter/screens/metering/communication/state_communication_me
|
|||
as communication_states;
|
||||
import 'package:lightmeter/utils/log_2.dart';
|
||||
|
||||
import 'event_camera.dart';
|
||||
import 'state_camera.dart';
|
||||
import 'event_container_camera.dart';
|
||||
import 'state_container_camera.dart';
|
||||
|
||||
class CameraBloc extends EvSourceBlocBase<CameraEvent, CameraState> {
|
||||
class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraContainerState> {
|
||||
final MeteringInteractor _meteringInteractor;
|
||||
late final _WidgetsBindingObserver _observer;
|
||||
CameraController? _cameraController;
|
||||
|
@ -33,7 +33,7 @@ class CameraBloc extends EvSourceBlocBase<CameraEvent, CameraState> {
|
|||
double _exposureStep = 0.0;
|
||||
double _currentExposureOffset = 0.0;
|
||||
|
||||
CameraBloc(
|
||||
CameraContainerBloc(
|
||||
MeteringCommunicationBloc communicationBloc,
|
||||
this._meteringInteractor,
|
||||
) : super(
|
||||
|
@ -139,11 +139,9 @@ class CameraBloc extends EvSourceBlocBase<CameraEvent, CameraState> {
|
|||
|
||||
void _emitActiveState(Emitter emit) {
|
||||
emit(CameraActiveState(
|
||||
minZoom: _zoomRange!.start,
|
||||
maxZoom: _zoomRange!.end,
|
||||
zoomRange: _zoomRange!,
|
||||
currentZoom: _currentZoom,
|
||||
minExposureOffset: _exposureOffsetRange!.start,
|
||||
maxExposureOffset: _exposureOffsetRange!.end,
|
||||
exposureOffsetRange: _exposureOffsetRange!,
|
||||
exposureOffsetStep: _exposureStep,
|
||||
currentExposureOffset: _currentExposureOffset,
|
||||
));
|
|
@ -0,0 +1,91 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/shared/centered_slider/widget_slider_centered.dart';
|
||||
import 'package:lightmeter/utils/to_string_signed.dart';
|
||||
|
||||
class ExposureOffsetSlider extends StatelessWidget {
|
||||
final RangeValues range;
|
||||
final double value;
|
||||
final ValueChanged<double> onChanged;
|
||||
|
||||
const ExposureOffsetSlider({
|
||||
required this.range,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.sync),
|
||||
onPressed: value != 0.0 ? () => onChanged(0.0) : null,
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: Dimens.grid8),
|
||||
child: _Ruler(
|
||||
range.start,
|
||||
range.end,
|
||||
),
|
||||
),
|
||||
CenteredSlider(
|
||||
isVertical: true,
|
||||
icon: const Icon(Icons.light_mode),
|
||||
value: value,
|
||||
min: range.start,
|
||||
max: range.end,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
],
|
||||
);
|
||||
},
|
||||
).reversed.toList(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/screens/shared/centered_slider/widget_slider_centered.dart';
|
||||
|
||||
class ZoomSlider extends StatelessWidget {
|
||||
final RangeValues range;
|
||||
final double value;
|
||||
final ValueChanged<double> onChanged;
|
||||
|
||||
const ZoomSlider({
|
||||
required this.range,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CenteredSlider(
|
||||
icon: const Icon(Icons.search),
|
||||
value: value,
|
||||
min: range.start,
|
||||
max: range.end,
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
import 'components/exposure_offset_slider/widget_slider_exposure_offset.dart';
|
||||
import 'components/zoom_slider/widget_slider_zoom.dart';
|
||||
|
||||
class CameraControls extends StatelessWidget {
|
||||
final RangeValues exposureOffsetRange;
|
||||
final double exposureOffsetValue;
|
||||
final ValueChanged<double> onExposureOffsetChanged;
|
||||
|
||||
final RangeValues zoomRange;
|
||||
final double zoomValue;
|
||||
final ValueChanged<double> onZoomChanged;
|
||||
|
||||
const CameraControls({
|
||||
required this.exposureOffsetRange,
|
||||
required this.exposureOffsetValue,
|
||||
required this.onExposureOffsetChanged,
|
||||
required this.zoomRange,
|
||||
required this.zoomValue,
|
||||
required this.onZoomChanged,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ExposureOffsetSlider(
|
||||
range: exposureOffsetRange,
|
||||
value: exposureOffsetValue,
|
||||
onChanged: onExposureOffsetChanged,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: Dimens.grid24),
|
||||
ZoomSlider(
|
||||
range: zoomRange,
|
||||
value: zoomValue,
|
||||
onChanged: onZoomChanged,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class CameraView extends StatelessWidget {
|
||||
final CameraController controller;
|
||||
|
||||
const CameraView({required this.controller, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final value = controller.value;
|
||||
return ValueListenableBuilder<CameraValue>(
|
||||
valueListenable: controller,
|
||||
builder: (_, __, ___) => AspectRatio(
|
||||
aspectRatio: _isLandscape(value) ? value.aspectRatio : (1 / value.aspectRatio),
|
||||
child: RotatedBox(
|
||||
quarterTurns: _getQuarterTurns(value),
|
||||
child: controller.buildPreview(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool _isLandscape(CameraValue value) {
|
||||
return <DeviceOrientation>[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]
|
||||
.contains(_getApplicableOrientation(value));
|
||||
}
|
||||
|
||||
int _getQuarterTurns(CameraValue value) {
|
||||
final Map<DeviceOrientation, int> turns = <DeviceOrientation, int>{
|
||||
DeviceOrientation.portraitUp: 0,
|
||||
DeviceOrientation.landscapeRight: 1,
|
||||
DeviceOrientation.portraitDown: 2,
|
||||
DeviceOrientation.landscapeLeft: 3,
|
||||
};
|
||||
return turns[_getApplicableOrientation(value)]!;
|
||||
}
|
||||
|
||||
DeviceOrientation _getApplicableOrientation(CameraValue value) {
|
||||
return value.isRecordingVideo
|
||||
? value.recordingOrientation!
|
||||
: (value.previewPauseOrientation ??
|
||||
value.lockedCaptureOrientation ??
|
||||
value.deviceOrientation);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
abstract class CameraContainerEvent {
|
||||
const CameraContainerEvent();
|
||||
}
|
||||
|
||||
class InitializeEvent extends CameraContainerEvent {
|
||||
const InitializeEvent();
|
||||
}
|
||||
|
||||
class ZoomChangedEvent extends CameraContainerEvent {
|
||||
final double value;
|
||||
|
||||
const ZoomChangedEvent(this.value);
|
||||
}
|
||||
|
||||
class ExposureOffsetChangedEvent extends CameraContainerEvent {
|
||||
final double value;
|
||||
|
||||
const ExposureOffsetChangedEvent(this.value);
|
||||
}
|
||||
|
||||
class ExposureOffsetResetEvent extends CameraContainerEvent {
|
||||
const ExposureOffsetResetEvent();
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/iso_value.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/nd_value.dart';
|
||||
import 'package:lightmeter/interactors/metering_interactor.dart';
|
||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
||||
|
||||
import 'bloc_container_camera.dart';
|
||||
import 'widget_container_camera.dart';
|
||||
|
||||
class CameraContainerProvider extends StatelessWidget {
|
||||
final ExposurePair? fastest;
|
||||
final ExposurePair? slowest;
|
||||
final IsoValue iso;
|
||||
final NdValue nd;
|
||||
final ValueChanged<IsoValue> onIsoChanged;
|
||||
final ValueChanged<NdValue> onNdChanged;
|
||||
final List<ExposurePair> exposurePairs;
|
||||
|
||||
const CameraContainerProvider({
|
||||
required this.fastest,
|
||||
required this.slowest,
|
||||
required this.iso,
|
||||
required this.nd,
|
||||
required this.onIsoChanged,
|
||||
required this.onNdChanged,
|
||||
required this.exposurePairs,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => CameraContainerBloc(
|
||||
context.read<MeteringCommunicationBloc>(),
|
||||
context.read<MeteringInteractor>(),
|
||||
),
|
||||
child: CameraContainer(
|
||||
fastest: fastest,
|
||||
slowest: slowest,
|
||||
iso: iso,
|
||||
nd: nd,
|
||||
onIsoChanged: onIsoChanged,
|
||||
onNdChanged: onNdChanged,
|
||||
exposurePairs: exposurePairs,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class CameraContainerState {
|
||||
const CameraContainerState();
|
||||
}
|
||||
|
||||
class CameraInitState extends CameraContainerState {
|
||||
const CameraInitState();
|
||||
}
|
||||
|
||||
class CameraLoadingState extends CameraContainerState {
|
||||
const CameraLoadingState();
|
||||
}
|
||||
|
||||
class CameraInitializedState extends CameraContainerState {
|
||||
final CameraController controller;
|
||||
|
||||
const CameraInitializedState(this.controller);
|
||||
}
|
||||
|
||||
class CameraActiveState extends CameraContainerState {
|
||||
final RangeValues zoomRange;
|
||||
final double currentZoom;
|
||||
final RangeValues exposureOffsetRange;
|
||||
final double? exposureOffsetStep;
|
||||
final double currentExposureOffset;
|
||||
|
||||
const CameraActiveState({
|
||||
required this.zoomRange,
|
||||
required this.currentZoom,
|
||||
required this.exposureOffsetRange,
|
||||
required this.exposureOffsetStep,
|
||||
required this.currentExposureOffset,
|
||||
});
|
||||
}
|
||||
|
||||
class CameraErrorState extends CameraContainerState {
|
||||
const CameraErrorState();
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/iso_value.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/nd_value.dart';
|
||||
import 'package:lightmeter/platform_config.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/metering/components/camera/components/camera_view/widget_camera_view.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/metering_top_bar/widget_top_bar_metering.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/widget_container_readings.dart';
|
||||
|
||||
import 'bloc_container_camera.dart';
|
||||
import 'components/camera_controls/widget_camera_controls.dart';
|
||||
import 'event_container_camera.dart';
|
||||
import 'state_container_camera.dart';
|
||||
|
||||
// TODO: add stepHeight calculation based on Text
|
||||
class CameraContainer extends StatelessWidget {
|
||||
final ExposurePair? fastest;
|
||||
final ExposurePair? slowest;
|
||||
final IsoValue iso;
|
||||
final NdValue nd;
|
||||
final ValueChanged<IsoValue> onIsoChanged;
|
||||
final ValueChanged<NdValue> onNdChanged;
|
||||
final List<ExposurePair> exposurePairs;
|
||||
|
||||
const CameraContainer({
|
||||
required this.fastest,
|
||||
required this.slowest,
|
||||
required this.iso,
|
||||
required this.nd,
|
||||
required this.onIsoChanged,
|
||||
required this.onNdChanged,
|
||||
required this.exposurePairs,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
MeteringTopBar(
|
||||
readingsContainer: ReadingsContainer(
|
||||
fastest: fastest,
|
||||
slowest: slowest,
|
||||
iso: iso,
|
||||
nd: nd,
|
||||
onIsoChanged: onIsoChanged,
|
||||
onNdChanged: onNdChanged,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
child: ExposurePairsList(exposurePairs),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CameraViewBuilder extends StatelessWidget {
|
||||
const _CameraViewBuilder({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: PlatformConfig.cameraPreviewAspectRatio,
|
||||
child: Center(
|
||||
child: BlocBuilder<CameraContainerBloc, CameraContainerState>(
|
||||
buildWhen: (previous, current) => current is CameraInitializedState,
|
||||
builder: (context, state) => state is CameraInitializedState
|
||||
? CameraView(controller: state.controller)
|
||||
: const ColoredBox(color: Colors.black),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CameraControlsBuilder extends StatelessWidget {
|
||||
const _CameraControlsBuilder();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<CameraContainerBloc, CameraContainerState>(
|
||||
builder: (context, state) => AnimatedSwitcher(
|
||||
duration: Dimens.durationS,
|
||||
child: state is CameraActiveState
|
||||
? CameraControls(
|
||||
exposureOffsetRange: state.exposureOffsetRange,
|
||||
exposureOffsetValue: state.currentExposureOffset,
|
||||
onExposureOffsetChanged: (value) {
|
||||
context.read<CameraContainerBloc>().add(ExposureOffsetChangedEvent(value));
|
||||
},
|
||||
zoomRange: state.zoomRange,
|
||||
zoomValue: state.currentZoom,
|
||||
onZoomChanged: (value) {
|
||||
context.read<CameraContainerBloc>().add(ZoomChangedEvent(value));
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
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/screens/shared/centered_slider/widget_slider_centered.dart';
|
||||
import 'package:lightmeter/utils/to_string_signed.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.0))
|
||||
: null,
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: Dimens.grid8),
|
||||
child: _Ruler(state.minExposureOffset, state.maxExposureOffset),
|
||||
),
|
||||
CenteredSlider(
|
||||
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),
|
||||
],
|
||||
);
|
||||
},
|
||||
).reversed.toList(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
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/centered_slider/widget_slider_centered.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 CenteredSlider(
|
||||
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();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'package:lightmeter/interactors/metering_interactor.dart';
|
||||
import 'package:lightmeter/screens/metering/ev_source/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/communication/bloc_communication_metering.dart';
|
||||
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart'
|
||||
as communication_event;
|
||||
|
@ -8,15 +8,16 @@ import 'package:lightmeter/screens/metering/communication/state_communication_me
|
|||
as communication_states;
|
||||
import 'package:lightmeter/utils/log_2.dart';
|
||||
|
||||
import 'event_light_sensor.dart';
|
||||
import 'state_light_sensor.dart';
|
||||
import 'event_container_light_sensor.dart';
|
||||
import 'state_container_light_sensor.dart';
|
||||
|
||||
class LightSensorBloc extends EvSourceBlocBase<LightSensorEvent, LightSensorState> {
|
||||
class LightSensorContainerBloc
|
||||
extends EvSourceBlocBase<LightSensorContainerEvent, LightSensorContainerState> {
|
||||
final MeteringInteractor _meteringInteractor;
|
||||
|
||||
StreamSubscription<int>? _luxSubscriptions;
|
||||
|
||||
LightSensorBloc(
|
||||
LightSensorContainerBloc(
|
||||
MeteringCommunicationBloc communicationBloc,
|
||||
this._meteringInteractor,
|
||||
) : super(
|
|
@ -0,0 +1,3 @@
|
|||
abstract class LightSensorContainerEvent {
|
||||
const LightSensorContainerEvent();
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/iso_value.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/nd_value.dart';
|
||||
import 'package:lightmeter/interactors/metering_interactor.dart';
|
||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
||||
|
||||
import 'bloc_container_light_sensor.dart';
|
||||
import 'widget_container_light_sensor.dart';
|
||||
|
||||
class LightSensorContainerProvider extends StatelessWidget {
|
||||
final ExposurePair? fastest;
|
||||
final ExposurePair? slowest;
|
||||
final IsoValue iso;
|
||||
final NdValue nd;
|
||||
final ValueChanged<IsoValue> onIsoChanged;
|
||||
final ValueChanged<NdValue> onNdChanged;
|
||||
final List<ExposurePair> exposurePairs;
|
||||
|
||||
const LightSensorContainerProvider({
|
||||
required this.fastest,
|
||||
required this.slowest,
|
||||
required this.iso,
|
||||
required this.nd,
|
||||
required this.onIsoChanged,
|
||||
required this.onNdChanged,
|
||||
required this.exposurePairs,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LightSensorContainerBloc(
|
||||
context.read<MeteringCommunicationBloc>(),
|
||||
context.read<MeteringInteractor>(),
|
||||
),
|
||||
child: LightSensorContainer(
|
||||
fastest: fastest,
|
||||
slowest: slowest,
|
||||
iso: iso,
|
||||
nd: nd,
|
||||
onIsoChanged: onIsoChanged,
|
||||
onNdChanged: onNdChanged,
|
||||
exposurePairs: exposurePairs,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
abstract class LightSensorContainerState {
|
||||
const LightSensorContainerState();
|
||||
}
|
||||
|
||||
class LightSensorInitState extends LightSensorContainerState {
|
||||
const LightSensorInitState();
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/iso_value.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/nd_value.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/exposure_pairs_list/widget_list_exposure_pairs.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/metering_top_bar/widget_top_bar_metering.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/widget_container_readings.dart';
|
||||
|
||||
class LightSensorContainer extends StatelessWidget {
|
||||
final ExposurePair? fastest;
|
||||
final ExposurePair? slowest;
|
||||
final IsoValue iso;
|
||||
final NdValue nd;
|
||||
final ValueChanged<IsoValue> onIsoChanged;
|
||||
final ValueChanged<NdValue> onNdChanged;
|
||||
final List<ExposurePair> exposurePairs;
|
||||
|
||||
const LightSensorContainer({
|
||||
required this.fastest,
|
||||
required this.slowest,
|
||||
required this.iso,
|
||||
required this.nd,
|
||||
required this.onIsoChanged,
|
||||
required this.onNdChanged,
|
||||
required this.exposurePairs,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
MeteringTopBar(
|
||||
readingsContainer: ReadingsContainer(
|
||||
fastest: fastest,
|
||||
slowest: slowest,
|
||||
iso: iso,
|
||||
nd: nd,
|
||||
onIsoChanged: onIsoChanged,
|
||||
onNdChanged: onNdChanged,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
child: ExposurePairsList(exposurePairs),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
import 'components/widget_item_list_exposure_pairs.dart';
|
||||
import 'components/exposure_pairs_list_item/widget_item_list_exposure_pairs.dart';
|
||||
|
||||
class ExposurePairsList extends StatelessWidget {
|
||||
final List<ExposurePair> exposurePairs;
|
|
@ -3,7 +3,7 @@ import 'dart:math';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
class TopBarShape extends CustomPainter {
|
||||
class MeteringTopBarShape extends CustomPainter {
|
||||
final Color color;
|
||||
|
||||
/// The appendix is on the left side
|
||||
|
@ -22,7 +22,7 @@ class TopBarShape extends CustomPainter {
|
|||
final double appendixHeight;
|
||||
final double appendixWidth;
|
||||
|
||||
TopBarShape({
|
||||
MeteringTopBarShape({
|
||||
required this.color,
|
||||
required this.appendixHeight,
|
||||
required this.appendixWidth,
|
||||
|
@ -38,8 +38,8 @@ class TopBarShape extends CustomPainter {
|
|||
RRect.fromLTRBAndCorners(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
size.width,
|
||||
size.height,
|
||||
bottomLeft: circularRadius,
|
||||
bottomRight: circularRadius,
|
||||
),
|
|
@ -0,0 +1,56 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/widget_container_readings.dart';
|
||||
|
||||
import 'shape_top_bar_metering.dart';
|
||||
|
||||
class MeteringTopBar extends StatelessWidget {
|
||||
final ReadingsContainer readingsContainer;
|
||||
final double appendixHeight;
|
||||
final Widget? preview;
|
||||
|
||||
const MeteringTopBar({
|
||||
required this.readingsContainer,
|
||||
this.preview,
|
||||
this.appendixHeight = 0.0,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomPaint(
|
||||
painter: MeteringTopBarShape(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
appendixWidth: appendixHeight > 0
|
||||
? MediaQuery.of(context).size.width / 2 - Dimens.grid8 + Dimens.paddingM
|
||||
: MediaQuery.of(context).size.width / 2 + Dimens.grid8 - Dimens.paddingM,
|
||||
appendixHeight: appendixHeight,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(Dimens.paddingM),
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: MediaQuery(
|
||||
data: MediaQuery.of(context),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(child: readingsContainer),
|
||||
if (preview != null) ...[
|
||||
const SizedBox(width: Dimens.grid8),
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(Dimens.borderRadiusM)),
|
||||
child: preview,
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ typedef DialogPickerItemBuilder<T extends PhotographyValue> = Widget Function(Bu
|
|||
typedef DialogPickerEvDifferenceBuilder<T extends PhotographyValue> = String Function(
|
||||
T selected, T other);
|
||||
|
||||
class MeteringScreenDialogPicker<T extends PhotographyValue> extends StatefulWidget {
|
||||
class PhotographyValuePickerDialog<T extends PhotographyValue> extends StatefulWidget {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final T initialValue;
|
||||
|
@ -17,7 +17,7 @@ class MeteringScreenDialogPicker<T extends PhotographyValue> extends StatefulWid
|
|||
final VoidCallback onCancel;
|
||||
final ValueChanged onSelect;
|
||||
|
||||
const MeteringScreenDialogPicker({
|
||||
const PhotographyValuePickerDialog({
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.initialValue,
|
||||
|
@ -30,11 +30,11 @@ class MeteringScreenDialogPicker<T extends PhotographyValue> extends StatefulWid
|
|||
});
|
||||
|
||||
@override
|
||||
State<MeteringScreenDialogPicker<T>> createState() => _MeteringScreenDialogPickerState<T>();
|
||||
State<PhotographyValuePickerDialog<T>> createState() => _PhotographyValuePickerDialogState<T>();
|
||||
}
|
||||
|
||||
class _MeteringScreenDialogPickerState<T extends PhotographyValue>
|
||||
extends State<MeteringScreenDialogPicker<T>> {
|
||||
class _PhotographyValuePickerDialogState<T extends PhotographyValue>
|
||||
extends State<PhotographyValuePickerDialog<T>> {
|
||||
late T _selectedValue = widget.initialValue;
|
||||
final _scrollController = ScrollController();
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/photography_value.dart';
|
||||
|
||||
import 'components/animated_dialog/widget_dialog_animated.dart';
|
||||
import 'components/photography_value_picker_dialog/widget_dialog_picker_photography_value.dart';
|
||||
|
||||
class AnimatedDialogPicker<T extends PhotographyValue> extends StatelessWidget {
|
||||
final _key = GlobalKey<AnimatedDialogState>();
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final T selectedValue;
|
||||
final List<T> values;
|
||||
final DialogPickerItemBuilder<T> itemTitleBuilder;
|
||||
final DialogPickerEvDifferenceBuilder<T> evDifferenceBuilder;
|
||||
final ValueChanged<T> onChanged;
|
||||
final Widget closedChild;
|
||||
|
||||
AnimatedDialogPicker({
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.selectedValue,
|
||||
required this.values,
|
||||
required this.itemTitleBuilder,
|
||||
required this.evDifferenceBuilder,
|
||||
required this.onChanged,
|
||||
required this.closedChild,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedDialog(
|
||||
key: _key,
|
||||
closedChild: closedChild,
|
||||
openedChild: PhotographyValuePickerDialog<T>(
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
initialValue: selectedValue,
|
||||
values: values,
|
||||
itemTitleBuilder: itemTitleBuilder,
|
||||
evDifferenceBuilder: evDifferenceBuilder,
|
||||
onCancel: () {
|
||||
_key.currentState?.close();
|
||||
},
|
||||
onSelect: (value) {
|
||||
_key.currentState?.close().then((_) => onChanged(value));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/iso_value.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/nd_value.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
import 'components/animated_dialog_picker/widget_dialog_animated_picker.dart';
|
||||
import 'components/reading_value_container/widget_container_reading_value.dart';
|
||||
|
||||
/// Contains a column of fastest & slowest exposure pairs + a row of ISO and ND pickers
|
||||
class ReadingsContainer extends StatelessWidget {
|
||||
final ExposurePair? fastest;
|
||||
final ExposurePair? slowest;
|
||||
final IsoValue iso;
|
||||
final NdValue nd;
|
||||
final ValueChanged<IsoValue> onIsoChanged;
|
||||
final ValueChanged<NdValue> onNdChanged;
|
||||
|
||||
const ReadingsContainer({
|
||||
required this.fastest,
|
||||
required this.slowest,
|
||||
required this.iso,
|
||||
required this.nd,
|
||||
required this.onIsoChanged,
|
||||
required this.onNdChanged,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
ReadingValueContainer(
|
||||
values: [
|
||||
ReadingValue(
|
||||
label: S.of(context).fastestExposurePair,
|
||||
value: fastest != null ? fastest!.toString() : '-',
|
||||
),
|
||||
ReadingValue(
|
||||
label: S.of(context).slowestExposurePair,
|
||||
value: fastest != null ? slowest!.toString() : '-',
|
||||
),
|
||||
],
|
||||
),
|
||||
const _InnerPadding(),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _IsoValueTile(
|
||||
value: iso,
|
||||
onChanged: onIsoChanged,
|
||||
),
|
||||
),
|
||||
const _InnerPadding(),
|
||||
Expanded(
|
||||
child: _NdValueTile(
|
||||
value: nd,
|
||||
onChanged: onNdChanged,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _InnerPadding extends SizedBox {
|
||||
const _InnerPadding() : super(height: Dimens.grid8, width: Dimens.grid8);
|
||||
}
|
||||
|
||||
class _IsoValueTile extends StatelessWidget {
|
||||
final IsoValue value;
|
||||
final ValueChanged<IsoValue> onChanged;
|
||||
|
||||
const _IsoValueTile({required this.value, required this.onChanged});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedDialogPicker<IsoValue>(
|
||||
title: S.of(context).iso,
|
||||
subtitle: S.of(context).filmSpeed,
|
||||
selectedValue: value,
|
||||
values: isoValues,
|
||||
itemTitleBuilder: (_, value) => Text(value.value.toString()),
|
||||
// using ascending order, because increase in film speed rises EV
|
||||
evDifferenceBuilder: (selected, other) => selected.toStringDifference(other),
|
||||
onChanged: onChanged,
|
||||
closedChild: ReadingValueContainer.singleValue(
|
||||
value: ReadingValue(
|
||||
label: S.of(context).iso,
|
||||
value: value.value.toString(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NdValueTile extends StatelessWidget {
|
||||
final NdValue value;
|
||||
final ValueChanged<NdValue> onChanged;
|
||||
|
||||
const _NdValueTile({required this.value, required this.onChanged});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedDialogPicker<NdValue>(
|
||||
title: S.of(context).nd,
|
||||
subtitle: S.of(context).ndFilterFactor,
|
||||
selectedValue: value,
|
||||
values: ndValues,
|
||||
itemTitleBuilder: (_, value) => Text(
|
||||
value.value == 0 ? S.of(context).none : value.value.toString(),
|
||||
),
|
||||
// using descending order, because ND filter darkens image & lowers EV
|
||||
evDifferenceBuilder: (selected, other) => other.toStringDifference(selected),
|
||||
onChanged: onChanged,
|
||||
closedChild: ReadingValueContainer.singleValue(
|
||||
value: ReadingValue(
|
||||
label: S.of(context).iso,
|
||||
value: value.value.toString(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/platform_config.dart';
|
||||
import 'package:lightmeter/screens/metering/ev_source/camera/bloc_camera.dart';
|
||||
import 'package:lightmeter/screens/metering/ev_source/camera/state_camera.dart';
|
||||
|
||||
class CameraView extends StatelessWidget {
|
||||
const CameraView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: PlatformConfig.cameraPreviewAspectRatio,
|
||||
child: Center(
|
||||
child: BlocBuilder<CameraBloc, CameraState>(
|
||||
buildWhen: (previous, current) => current is CameraInitializedState,
|
||||
builder: (context, state) {
|
||||
if (state is CameraInitializedState) {
|
||||
final value = state.controller.value;
|
||||
return ValueListenableBuilder<CameraValue>(
|
||||
valueListenable: state.controller,
|
||||
builder: (_, __, ___) => AspectRatio(
|
||||
aspectRatio: _isLandscape(value) ? value.aspectRatio : (1 / value.aspectRatio),
|
||||
child: RotatedBox(
|
||||
quarterTurns: _getQuarterTurns(value),
|
||||
child: state.controller.buildPreview(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const ColoredBox(color: Colors.black);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool _isLandscape(CameraValue value) {
|
||||
return <DeviceOrientation>[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]
|
||||
.contains(_getApplicableOrientation(value));
|
||||
}
|
||||
|
||||
int _getQuarterTurns(CameraValue value) {
|
||||
final Map<DeviceOrientation, int> turns = <DeviceOrientation, int>{
|
||||
DeviceOrientation.portraitUp: 0,
|
||||
DeviceOrientation.landscapeRight: 1,
|
||||
DeviceOrientation.portraitDown: 2,
|
||||
DeviceOrientation.landscapeLeft: 3,
|
||||
};
|
||||
return turns[_getApplicableOrientation(value)]!;
|
||||
}
|
||||
|
||||
DeviceOrientation _getApplicableOrientation(CameraValue value) {
|
||||
return value.isRecordingVideo
|
||||
? value.recordingOrientation!
|
||||
: (value.previewPauseOrientation ??
|
||||
value.lockedCaptureOrientation ??
|
||||
value.deviceOrientation);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
class SizeRenderWidget extends SingleChildRenderObjectWidget {
|
||||
final ValueChanged<Size>? onLayout;
|
||||
|
||||
const SizeRenderWidget({
|
||||
super.key,
|
||||
super.child,
|
||||
this.onLayout,
|
||||
});
|
||||
|
||||
@override
|
||||
SizeRenderBox createRenderObject(BuildContext context) => SizeRenderBox(onLayout: onLayout);
|
||||
}
|
||||
|
||||
class SizeRenderBox extends RenderProxyBox {
|
||||
final ValueChanged<Size>? onLayout;
|
||||
|
||||
SizeRenderBox({this.onLayout});
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
if (child != null) {
|
||||
child!.layout(constraints, parentUsesSize: true);
|
||||
size = child!.size;
|
||||
} else {
|
||||
size = computeSizeForNoChild(constraints);
|
||||
}
|
||||
onLayout?.call(size);
|
||||
}
|
||||
}
|
|
@ -1,233 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/platform_config.dart';
|
||||
import 'package:lightmeter/screens/metering/components/topbar/shape_topbar.dart';
|
||||
import 'package:lightmeter/screens/metering/components/topbar/components/widget_size_render.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/iso_value.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/nd_value.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/photography_value.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
import 'components/widget_camera_preview.dart';
|
||||
import 'components/shared/widget_dialog_animated.dart';
|
||||
import 'components/widget_dialog_picker.dart';
|
||||
import 'components/container_reading_value.dart';
|
||||
|
||||
class MeteringTopBar extends StatefulWidget {
|
||||
final ExposurePair? fastest;
|
||||
final ExposurePair? slowest;
|
||||
final double ev;
|
||||
final IsoValue iso;
|
||||
final NdValue nd;
|
||||
final ValueChanged<IsoValue> onIsoChanged;
|
||||
final ValueChanged<NdValue> onNdChanged;
|
||||
|
||||
final ValueChanged<double> onCutoutLayout;
|
||||
|
||||
const MeteringTopBar({
|
||||
required this.fastest,
|
||||
required this.slowest,
|
||||
required this.ev,
|
||||
required this.iso,
|
||||
required this.nd,
|
||||
required this.onIsoChanged,
|
||||
required this.onNdChanged,
|
||||
required this.onCutoutLayout,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MeteringTopBar> createState() => _MeteringTopBarState();
|
||||
}
|
||||
|
||||
class _MeteringTopBarState extends State<MeteringTopBar> {
|
||||
double stepHeight = 0.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomPaint(
|
||||
painter: TopBarShape(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
appendixWidth: stepHeight > 0
|
||||
? MediaQuery.of(context).size.width / 2 - Dimens.grid8 + Dimens.paddingM
|
||||
: MediaQuery.of(context).size.width / 2 + Dimens.grid8 - Dimens.paddingM,
|
||||
appendixHeight: stepHeight,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(Dimens.paddingM),
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: MediaQuery(
|
||||
data: MediaQuery.of(context),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: SizeRenderWidget(
|
||||
onLayout: (size) => _onReadingsLayout(size.height),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
ReadingValueContainer(
|
||||
values: [
|
||||
ReadingValue(
|
||||
label: S.of(context).fastestExposurePair,
|
||||
value: widget.fastest != null ? widget.fastest!.toString() : '-',
|
||||
),
|
||||
ReadingValue(
|
||||
label: S.of(context).slowestExposurePair,
|
||||
value: widget.fastest != null ? widget.slowest!.toString() : '-',
|
||||
),
|
||||
],
|
||||
),
|
||||
/*
|
||||
const _InnerPadding(),
|
||||
ReadingValueContainer.singleValue(
|
||||
value: ReadingValue(
|
||||
label: 'EV',
|
||||
value: ev.toStringAsFixed(1),
|
||||
),
|
||||
),
|
||||
*/
|
||||
const _InnerPadding(),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _IsoValueTile(
|
||||
value: widget.iso,
|
||||
onChanged: widget.onIsoChanged,
|
||||
),
|
||||
),
|
||||
const _InnerPadding(),
|
||||
Expanded(
|
||||
child: _NdValueTile(
|
||||
value: widget.nd,
|
||||
onChanged: widget.onNdChanged,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const _InnerPadding(),
|
||||
const Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(Dimens.borderRadiusM)),
|
||||
child: CameraView(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onReadingsLayout(double readingsSectionHeight) {
|
||||
stepHeight = readingsSectionHeight -
|
||||
((MediaQuery.of(context).size.width - Dimens.grid8 - 2 * Dimens.paddingM) / 2) /
|
||||
PlatformConfig.cameraPreviewAspectRatio;
|
||||
widget.onCutoutLayout(stepHeight);
|
||||
}
|
||||
}
|
||||
|
||||
class _InnerPadding extends SizedBox {
|
||||
const _InnerPadding() : super(height: Dimens.grid8, width: Dimens.grid8);
|
||||
}
|
||||
|
||||
class _IsoValueTile extends StatelessWidget {
|
||||
final IsoValue value;
|
||||
final ValueChanged<IsoValue> onChanged;
|
||||
|
||||
const _IsoValueTile({required this.value, required this.onChanged});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _AnimatedDialogPicker<IsoValue>(
|
||||
title: S.of(context).iso,
|
||||
subtitle: S.of(context).filmSpeed,
|
||||
selectedValue: value,
|
||||
values: isoValues,
|
||||
itemTitleBuilder: (_, value) => Text(value.value.toString()),
|
||||
// using ascending order, because increase in film speed rises EV
|
||||
evDifferenceBuilder: (selected, other) => selected.toStringDifference(other),
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NdValueTile extends StatelessWidget {
|
||||
final NdValue value;
|
||||
final ValueChanged<NdValue> onChanged;
|
||||
|
||||
const _NdValueTile({required this.value, required this.onChanged});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _AnimatedDialogPicker<NdValue>(
|
||||
title: S.of(context).nd,
|
||||
subtitle: S.of(context).ndFilterFactor,
|
||||
selectedValue: value,
|
||||
values: ndValues,
|
||||
itemTitleBuilder: (_, value) => Text(
|
||||
value.value == 0 ? S.of(context).none : value.value.toString(),
|
||||
),
|
||||
// using descending order, because ND filter darkens image & lowers EV
|
||||
evDifferenceBuilder: (selected, other) => other.toStringDifference(selected),
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AnimatedDialogPicker<T extends PhotographyValue> extends StatelessWidget {
|
||||
final _key = GlobalKey<AnimatedDialogState>();
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final T selectedValue;
|
||||
final List<T> values;
|
||||
final DialogPickerItemBuilder<T> itemTitleBuilder;
|
||||
final DialogPickerEvDifferenceBuilder<T> evDifferenceBuilder;
|
||||
final ValueChanged<T> onChanged;
|
||||
|
||||
_AnimatedDialogPicker({
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.selectedValue,
|
||||
required this.values,
|
||||
required this.itemTitleBuilder,
|
||||
required this.evDifferenceBuilder,
|
||||
required this.onChanged,
|
||||
}) : super();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedDialog(
|
||||
key: _key,
|
||||
closedChild: ReadingValueContainer.singleValue(
|
||||
value: ReadingValue(
|
||||
label: title,
|
||||
value: selectedValue.value.toString(),
|
||||
),
|
||||
),
|
||||
openedChild: MeteringScreenDialogPicker<T>(
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
initialValue: selectedValue,
|
||||
values: values,
|
||||
itemTitleBuilder: itemTitleBuilder,
|
||||
evDifferenceBuilder: evDifferenceBuilder,
|
||||
onCancel: () {
|
||||
_key.currentState?.close();
|
||||
},
|
||||
onSelect: (value) {
|
||||
_key.currentState?.close().then((_) => onChanged(value));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
abstract class CameraEvent {
|
||||
const CameraEvent();
|
||||
}
|
||||
|
||||
class InitializeEvent extends CameraEvent {
|
||||
const InitializeEvent();
|
||||
}
|
||||
|
||||
class ZoomChangedEvent extends CameraEvent {
|
||||
final double value;
|
||||
|
||||
const ZoomChangedEvent(this.value);
|
||||
}
|
||||
|
||||
class ExposureOffsetChangedEvent extends CameraEvent {
|
||||
final double value;
|
||||
|
||||
const ExposureOffsetChangedEvent(this.value);
|
||||
}
|
||||
|
||||
class ExposureOffsetResetEvent extends CameraEvent {
|
||||
const ExposureOffsetResetEvent();
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
import 'package:camera/camera.dart';
|
||||
|
||||
abstract class CameraState {
|
||||
const CameraState();
|
||||
}
|
||||
|
||||
class CameraInitState extends CameraState {
|
||||
const CameraInitState();
|
||||
}
|
||||
|
||||
class CameraLoadingState extends CameraState {
|
||||
const CameraLoadingState();
|
||||
}
|
||||
|
||||
class CameraInitializedState extends CameraState {
|
||||
final CameraController 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 {
|
||||
const CameraErrorState();
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
abstract class LightSensorEvent {
|
||||
const LightSensorEvent();
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
abstract class LightSensorState {
|
||||
const LightSensorState();
|
||||
}
|
||||
|
||||
class LightSensorInitState extends LightSensorState {
|
||||
const LightSensorInitState();
|
||||
}
|
|
@ -2,24 +2,25 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/data/haptics_service.dart';
|
||||
import 'package:lightmeter/data/light_sensor_service.dart';
|
||||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/photography_value.dart';
|
||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||
import 'package:lightmeter/interactors/metering_interactor.dart';
|
||||
import 'package:lightmeter/screens/metering/ev_source/light_sensor/bloc_light_sensor.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'ev_source/camera/bloc_camera.dart';
|
||||
import 'bloc_metering.dart';
|
||||
import 'communication/bloc_communication_metering.dart';
|
||||
import 'screen_metering.dart';
|
||||
|
||||
class MeteringFlow extends StatelessWidget {
|
||||
class MeteringFlow extends StatefulWidget {
|
||||
const MeteringFlow({super.key});
|
||||
|
||||
@override
|
||||
State<MeteringFlow> createState() => _MeteringFlowState();
|
||||
}
|
||||
|
||||
class _MeteringFlowState extends State<MeteringFlow> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sourceType = context.watch<EvSourceType>();
|
||||
return Provider(
|
||||
create: (context) => MeteringInteractor(
|
||||
context.read<UserPreferencesService>(),
|
||||
|
@ -37,20 +38,6 @@ class MeteringFlow extends StatelessWidget {
|
|||
context.read<StopType>(),
|
||||
),
|
||||
),
|
||||
if (sourceType == EvSourceType.camera)
|
||||
BlocProvider(
|
||||
create: (context) => CameraBloc(
|
||||
context.read<MeteringCommunicationBloc>(),
|
||||
context.read<MeteringInteractor>(),
|
||||
),
|
||||
),
|
||||
if (sourceType == EvSourceType.sensor)
|
||||
BlocProvider(
|
||||
create: (context) => LightSensorBloc(
|
||||
context.read<MeteringCommunicationBloc>(),
|
||||
context.read<MeteringInteractor>(),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: const MeteringScreen(),
|
||||
),
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/photography_value.dart';
|
||||
import 'package:lightmeter/environment.dart';
|
||||
import 'package:lightmeter/providers/ev_source_type_provider.dart';
|
||||
import 'package:lightmeter/res/dimens.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/topbar/widget_topbar.dart';
|
||||
import 'components/camera/provider_container_camera.dart';
|
||||
import 'components/light_sensor_container/provider_container_light_sensor.dart';
|
||||
import 'bloc_metering.dart';
|
||||
import 'event_metering.dart';
|
||||
import 'state_metering.dart';
|
||||
|
@ -20,8 +21,6 @@ class MeteringScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _MeteringScreenState extends State<MeteringScreen> {
|
||||
double topBarOverflow = 0.0;
|
||||
|
||||
MeteringBloc get _bloc => context.read<MeteringBloc>();
|
||||
|
||||
@override
|
||||
|
@ -34,84 +33,42 @@ class _MeteringScreenState extends State<MeteringScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
body: BlocBuilder<MeteringBloc, MeteringState>(
|
||||
builder: (context, state) => Column(
|
||||
children: [
|
||||
MeteringTopBar(
|
||||
fastest: state.fastest,
|
||||
slowest: state.slowest,
|
||||
ev: state.ev,
|
||||
iso: state.iso,
|
||||
nd: state.nd,
|
||||
onIsoChanged: (value) => _bloc.add(IsoChangedEvent(value)),
|
||||
onNdChanged: (value) => _bloc.add(NdChangedEvent(value)),
|
||||
onCutoutLayout: (value) => topBarOverflow = value,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
child: _MiddleContentWrapper(
|
||||
topBarOverflow: topBarOverflow,
|
||||
leftContent: ExposurePairsList(state.exposurePairs),
|
||||
rightContent: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
|
||||
child: Column(
|
||||
children: const [
|
||||
Expanded(child: CameraExposureSlider()),
|
||||
SizedBox(height: Dimens.grid24),
|
||||
CameraZoomSlider(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: BlocBuilder<MeteringBloc, MeteringState>(
|
||||
builder: (context, state) => AnimatedSwitcher(
|
||||
duration: Dimens.durationS,
|
||||
child: context.watch<EvSourceType>() == EvSourceType.camera
|
||||
? CameraContainerProvider(
|
||||
fastest: state.fastest,
|
||||
slowest: state.slowest,
|
||||
iso: state.iso,
|
||||
nd: state.nd,
|
||||
onIsoChanged: (value) => _bloc.add(IsoChangedEvent(value)),
|
||||
onNdChanged: (value) => _bloc.add(NdChangedEvent(value)),
|
||||
exposurePairs: state.exposurePairs,
|
||||
)
|
||||
: LightSensorContainerProvider(
|
||||
fastest: state.fastest,
|
||||
slowest: state.slowest,
|
||||
iso: state.iso,
|
||||
nd: state.nd,
|
||||
onIsoChanged: (value) => _bloc.add(IsoChangedEvent(value)),
|
||||
onNdChanged: (value) => _bloc.add(NdChangedEvent(value)),
|
||||
exposurePairs: state.exposurePairs,
|
||||
),
|
||||
),
|
||||
),
|
||||
MeteringBottomControls(
|
||||
onMeasure: () => _bloc.add(const MeasureEvent()),
|
||||
onSettings: () => Navigator.pushNamed(context, 'settings'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MiddleContentWrapper extends StatelessWidget {
|
||||
final double topBarOverflow;
|
||||
final Widget leftContent;
|
||||
final Widget rightContent;
|
||||
|
||||
const _MiddleContentWrapper({
|
||||
required this.topBarOverflow,
|
||||
required this.leftContent,
|
||||
required this.rightContent,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) => OverflowBox(
|
||||
alignment: Alignment.bottomCenter,
|
||||
maxHeight: constraints.maxHeight + topBarOverflow.abs(),
|
||||
maxWidth: constraints.maxWidth,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: topBarOverflow >= 0 ? topBarOverflow : 0),
|
||||
child: leftContent,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: Dimens.grid8),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: topBarOverflow <= 0 ? -topBarOverflow : 0),
|
||||
child: rightContent,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
MeteringBottomControls(
|
||||
onSwitchEvSourceType: context.read<Environment>().hasLightSensor
|
||||
? EvSourceTypeProvider.of(context).toggleType
|
||||
: null,
|
||||
onMeasure: () => _bloc.add(const MeasureEvent()),
|
||||
onSettings: () => Navigator.pushNamed(context, 'settings'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue