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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.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/haptics_service.dart';
|
||||||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import 'data/permissions_service.dart';
|
||||||
import 'data/shared_prefs_service.dart';
|
import 'data/shared_prefs_service.dart';
|
||||||
import 'environment.dart';
|
import 'environment.dart';
|
||||||
import 'generated/l10n.dart';
|
import 'generated/l10n.dart';
|
||||||
|
import 'providers/ev_source_type_provider.dart';
|
||||||
import 'res/theme.dart';
|
import 'res/theme.dart';
|
||||||
import 'screens/metering/flow_metering.dart';
|
import 'screens/metering/flow_metering.dart';
|
||||||
import 'screens/settings/flow_settings.dart';
|
import 'screens/settings/flow_settings.dart';
|
||||||
|
@ -18,63 +19,72 @@ import 'utils/stop_type_provider.dart';
|
||||||
|
|
||||||
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
|
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
|
||||||
|
|
||||||
class Application extends StatelessWidget {
|
class Application extends StatefulWidget {
|
||||||
final Environment env;
|
final Environment env;
|
||||||
|
|
||||||
const Application(this.env, {super.key});
|
const Application(this.env, {super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Application> createState() => _ApplicationState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ApplicationState extends State<Application> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder<SharedPreferences>(
|
return FutureBuilder(
|
||||||
future: SharedPreferences.getInstance(),
|
future: Future.wait([
|
||||||
|
SharedPreferences.getInstance(),
|
||||||
|
LightSensor.hasSensor,
|
||||||
|
]),
|
||||||
builder: (_, snapshot) {
|
builder: (_, snapshot) {
|
||||||
if (snapshot.data != null) {
|
if (snapshot.data != null) {
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider.value(value: env),
|
Provider.value(value: widget.env.copyWith(hasLightSensor: snapshot.data![1] as bool)),
|
||||||
Provider.value(value: EvSourceType.camera),
|
Provider(create: (_) => UserPreferencesService(snapshot.data![0] as SharedPreferences)),
|
||||||
Provider(create: (_) => UserPreferencesService(snapshot.data!)),
|
|
||||||
Provider(create: (_) => const HapticsService()),
|
Provider(create: (_) => const HapticsService()),
|
||||||
Provider(create: (_) => PermissionsService()),
|
Provider(create: (_) => PermissionsService()),
|
||||||
Provider(create: (_) => const LightSensorService()),
|
Provider(create: (_) => const LightSensorService()),
|
||||||
],
|
],
|
||||||
child: StopTypeProvider(
|
child: StopTypeProvider(
|
||||||
child: ThemeProvider(
|
child: EvSourceTypeProvider(
|
||||||
builder: (context, _) {
|
child: ThemeProvider(
|
||||||
final systemIconsBrightness = ThemeData.estimateBrightnessForColor(
|
builder: (context, _) {
|
||||||
context.watch<ThemeData>().colorScheme.onSurface,
|
final systemIconsBrightness = ThemeData.estimateBrightnessForColor(
|
||||||
);
|
context.watch<ThemeData>().colorScheme.onSurface,
|
||||||
return AnnotatedRegion(
|
);
|
||||||
value: SystemUiOverlayStyle(
|
return AnnotatedRegion(
|
||||||
statusBarColor: Colors.transparent,
|
value: SystemUiOverlayStyle(
|
||||||
statusBarBrightness: systemIconsBrightness == Brightness.light
|
statusBarColor: Colors.transparent,
|
||||||
? Brightness.dark
|
statusBarBrightness: systemIconsBrightness == Brightness.light
|
||||||
: Brightness.light,
|
? Brightness.dark
|
||||||
statusBarIconBrightness: systemIconsBrightness,
|
: Brightness.light,
|
||||||
systemNavigationBarColor: context.watch<ThemeData>().colorScheme.surface,
|
statusBarIconBrightness: systemIconsBrightness,
|
||||||
systemNavigationBarIconBrightness: 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!,
|
|
||||||
),
|
),
|
||||||
initialRoute: "metering",
|
child: MaterialApp(
|
||||||
routes: {
|
theme: context.watch<ThemeData>(),
|
||||||
"metering": (context) => const MeteringFlow(),
|
localizationsDelegates: const [
|
||||||
"settings": (context) => const SettingsFlow(),
|
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 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import 'models/ev_source_type.dart';
|
||||||
import 'models/photography_values/iso_value.dart';
|
import 'models/photography_values/iso_value.dart';
|
||||||
import 'models/photography_values/nd_value.dart';
|
import 'models/photography_values/nd_value.dart';
|
||||||
import 'models/theme_type.dart';
|
import 'models/theme_type.dart';
|
||||||
|
|
||||||
class UserPreferencesService {
|
class UserPreferencesService {
|
||||||
static const _isoKey = "iso";
|
static const _isoKey = "iso";
|
||||||
static const _ndFilterKey = "nd";
|
static const _ndFilterKey = "ndFilter";
|
||||||
|
|
||||||
|
static const _evSourceTypeKey = "evSourceType";
|
||||||
static const _cameraEvCalibrationKey = "cameraEvCalibration";
|
static const _cameraEvCalibrationKey = "cameraEvCalibration";
|
||||||
|
|
||||||
static const _hapticsKey = "haptics";
|
static const _hapticsKey = "haptics";
|
||||||
|
@ -24,6 +26,9 @@ class UserPreferencesService {
|
||||||
NdValue get ndFilter => ndValues.firstWhere((v) => v.value == (_sharedPreferences.getInt(_ndFilterKey) ?? 0));
|
NdValue get ndFilter => ndValues.firstWhere((v) => v.value == (_sharedPreferences.getInt(_ndFilterKey) ?? 0));
|
||||||
set ndFilter(NdValue value) => _sharedPreferences.setInt(_ndFilterKey, value.value);
|
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;
|
bool get haptics => _sharedPreferences.getBool(_hapticsKey) ?? false;
|
||||||
set haptics(bool value) => _sharedPreferences.setBool(_hapticsKey, value);
|
set haptics(bool value) => _sharedPreferences.setBool(_hapticsKey, value);
|
||||||
|
|
||||||
|
|
|
@ -3,19 +3,31 @@ class Environment {
|
||||||
final String issuesReportUrl;
|
final String issuesReportUrl;
|
||||||
final String contactEmail;
|
final String contactEmail;
|
||||||
|
|
||||||
|
final bool hasLightSensor;
|
||||||
|
|
||||||
const Environment({
|
const Environment({
|
||||||
required this.sourceCodeUrl,
|
required this.sourceCodeUrl,
|
||||||
required this.issuesReportUrl,
|
required this.issuesReportUrl,
|
||||||
required this.contactEmail,
|
required this.contactEmail,
|
||||||
|
this.hasLightSensor = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const Environment.dev()
|
const Environment.dev()
|
||||||
: sourceCodeUrl = 'https://github.com/vodemn/m3_lightmeter',
|
: sourceCodeUrl = 'https://github.com/vodemn/m3_lightmeter',
|
||||||
issuesReportUrl = 'https://github.com/vodemn/m3_lightmeter/issues',
|
issuesReportUrl = 'https://github.com/vodemn/m3_lightmeter/issues',
|
||||||
contactEmail = 'contact.vodemn@gmail.com';
|
contactEmail = 'contact.vodemn@gmail.com',
|
||||||
|
hasLightSensor = false;
|
||||||
|
|
||||||
const Environment.prod()
|
const Environment.prod()
|
||||||
: sourceCodeUrl = 'https://github.com/vodemn/m3_lightmeter',
|
: sourceCodeUrl = 'https://github.com/vodemn/m3_lightmeter',
|
||||||
issuesReportUrl = 'https://github.com/vodemn/m3_lightmeter/issues',
|
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';
|
import 'components/widget_button_secondary.dart';
|
||||||
|
|
||||||
class MeteringBottomControls extends StatelessWidget {
|
class MeteringBottomControls extends StatelessWidget {
|
||||||
|
final VoidCallback? onSwitchEvSourceType;
|
||||||
final VoidCallback onMeasure;
|
final VoidCallback onMeasure;
|
||||||
final VoidCallback onSettings;
|
final VoidCallback onSettings;
|
||||||
|
|
||||||
const MeteringBottomControls({
|
const MeteringBottomControls({
|
||||||
|
required this.onSwitchEvSourceType,
|
||||||
required this.onMeasure,
|
required this.onMeasure,
|
||||||
required this.onSettings,
|
required this.onSettings,
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -30,7 +32,15 @@ class MeteringBottomControls extends StatelessWidget {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
const Spacer(),
|
if (onSwitchEvSourceType != null)
|
||||||
|
Expanded(
|
||||||
|
child: MeteringSecondaryButton(
|
||||||
|
onPressed: onSwitchEvSourceType!,
|
||||||
|
icon: Icons.sync,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const Spacer(),
|
||||||
MeteringMeasureButton(
|
MeteringMeasureButton(
|
||||||
onTap: onMeasure,
|
onTap: onMeasure,
|
||||||
),
|
),
|
||||||
|
|
|
@ -7,7 +7,7 @@ import 'package:exif/exif.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/interactors/metering_interactor.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/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;
|
||||||
|
@ -15,10 +15,10 @@ import 'package:lightmeter/screens/metering/communication/state_communication_me
|
||||||
as communication_states;
|
as communication_states;
|
||||||
import 'package:lightmeter/utils/log_2.dart';
|
import 'package:lightmeter/utils/log_2.dart';
|
||||||
|
|
||||||
import 'event_camera.dart';
|
import 'event_container_camera.dart';
|
||||||
import 'state_camera.dart';
|
import 'state_container_camera.dart';
|
||||||
|
|
||||||
class CameraBloc extends EvSourceBlocBase<CameraEvent, CameraState> {
|
class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraContainerState> {
|
||||||
final MeteringInteractor _meteringInteractor;
|
final MeteringInteractor _meteringInteractor;
|
||||||
late final _WidgetsBindingObserver _observer;
|
late final _WidgetsBindingObserver _observer;
|
||||||
CameraController? _cameraController;
|
CameraController? _cameraController;
|
||||||
|
@ -33,7 +33,7 @@ class CameraBloc extends EvSourceBlocBase<CameraEvent, CameraState> {
|
||||||
double _exposureStep = 0.0;
|
double _exposureStep = 0.0;
|
||||||
double _currentExposureOffset = 0.0;
|
double _currentExposureOffset = 0.0;
|
||||||
|
|
||||||
CameraBloc(
|
CameraContainerBloc(
|
||||||
MeteringCommunicationBloc communicationBloc,
|
MeteringCommunicationBloc communicationBloc,
|
||||||
this._meteringInteractor,
|
this._meteringInteractor,
|
||||||
) : super(
|
) : super(
|
||||||
|
@ -139,11 +139,9 @@ class CameraBloc extends EvSourceBlocBase<CameraEvent, CameraState> {
|
||||||
|
|
||||||
void _emitActiveState(Emitter emit) {
|
void _emitActiveState(Emitter emit) {
|
||||||
emit(CameraActiveState(
|
emit(CameraActiveState(
|
||||||
minZoom: _zoomRange!.start,
|
zoomRange: _zoomRange!,
|
||||||
maxZoom: _zoomRange!.end,
|
|
||||||
currentZoom: _currentZoom,
|
currentZoom: _currentZoom,
|
||||||
minExposureOffset: _exposureOffsetRange!.start,
|
exposureOffsetRange: _exposureOffsetRange!,
|
||||||
maxExposureOffset: _exposureOffsetRange!.end,
|
|
||||||
exposureOffsetStep: _exposureStep,
|
exposureOffsetStep: _exposureStep,
|
||||||
currentExposureOffset: _currentExposureOffset,
|
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 'dart:async';
|
||||||
import 'package:lightmeter/interactors/metering_interactor.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/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;
|
||||||
|
@ -8,15 +8,16 @@ import 'package:lightmeter/screens/metering/communication/state_communication_me
|
||||||
as communication_states;
|
as communication_states;
|
||||||
import 'package:lightmeter/utils/log_2.dart';
|
import 'package:lightmeter/utils/log_2.dart';
|
||||||
|
|
||||||
import 'event_light_sensor.dart';
|
import 'event_container_light_sensor.dart';
|
||||||
import 'state_light_sensor.dart';
|
import 'state_container_light_sensor.dart';
|
||||||
|
|
||||||
class LightSensorBloc extends EvSourceBlocBase<LightSensorEvent, LightSensorState> {
|
class LightSensorContainerBloc
|
||||||
|
extends EvSourceBlocBase<LightSensorContainerEvent, LightSensorContainerState> {
|
||||||
final MeteringInteractor _meteringInteractor;
|
final MeteringInteractor _meteringInteractor;
|
||||||
|
|
||||||
StreamSubscription<int>? _luxSubscriptions;
|
StreamSubscription<int>? _luxSubscriptions;
|
||||||
|
|
||||||
LightSensorBloc(
|
LightSensorContainerBloc(
|
||||||
MeteringCommunicationBloc communicationBloc,
|
MeteringCommunicationBloc communicationBloc,
|
||||||
this._meteringInteractor,
|
this._meteringInteractor,
|
||||||
) : super(
|
) : 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 'dart:async';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.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/data/models/exposure_pair.dart';
|
||||||
import 'package:lightmeter/res/dimens.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 {
|
class ExposurePairsList extends StatelessWidget {
|
||||||
final List<ExposurePair> exposurePairs;
|
final List<ExposurePair> exposurePairs;
|
|
@ -3,7 +3,7 @@ import 'dart:math';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/res/dimens.dart';
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
|
||||||
class TopBarShape extends CustomPainter {
|
class MeteringTopBarShape extends CustomPainter {
|
||||||
final Color color;
|
final Color color;
|
||||||
|
|
||||||
/// The appendix is on the left side
|
/// The appendix is on the left side
|
||||||
|
@ -22,7 +22,7 @@ class TopBarShape extends CustomPainter {
|
||||||
final double appendixHeight;
|
final double appendixHeight;
|
||||||
final double appendixWidth;
|
final double appendixWidth;
|
||||||
|
|
||||||
TopBarShape({
|
MeteringTopBarShape({
|
||||||
required this.color,
|
required this.color,
|
||||||
required this.appendixHeight,
|
required this.appendixHeight,
|
||||||
required this.appendixWidth,
|
required this.appendixWidth,
|
||||||
|
@ -38,8 +38,8 @@ class TopBarShape extends CustomPainter {
|
||||||
RRect.fromLTRBAndCorners(
|
RRect.fromLTRBAndCorners(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
size.width,
|
||||||
0,
|
size.height,
|
||||||
bottomLeft: circularRadius,
|
bottomLeft: circularRadius,
|
||||||
bottomRight: 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(
|
typedef DialogPickerEvDifferenceBuilder<T extends PhotographyValue> = String Function(
|
||||||
T selected, T other);
|
T selected, T other);
|
||||||
|
|
||||||
class MeteringScreenDialogPicker<T extends PhotographyValue> extends StatefulWidget {
|
class PhotographyValuePickerDialog<T extends PhotographyValue> extends StatefulWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final String subtitle;
|
final String subtitle;
|
||||||
final T initialValue;
|
final T initialValue;
|
||||||
|
@ -17,7 +17,7 @@ class MeteringScreenDialogPicker<T extends PhotographyValue> extends StatefulWid
|
||||||
final VoidCallback onCancel;
|
final VoidCallback onCancel;
|
||||||
final ValueChanged onSelect;
|
final ValueChanged onSelect;
|
||||||
|
|
||||||
const MeteringScreenDialogPicker({
|
const PhotographyValuePickerDialog({
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.subtitle,
|
required this.subtitle,
|
||||||
required this.initialValue,
|
required this.initialValue,
|
||||||
|
@ -30,11 +30,11 @@ class MeteringScreenDialogPicker<T extends PhotographyValue> extends StatefulWid
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MeteringScreenDialogPicker<T>> createState() => _MeteringScreenDialogPickerState<T>();
|
State<PhotographyValuePickerDialog<T>> createState() => _PhotographyValuePickerDialogState<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MeteringScreenDialogPickerState<T extends PhotographyValue>
|
class _PhotographyValuePickerDialogState<T extends PhotographyValue>
|
||||||
extends State<MeteringScreenDialogPicker<T>> {
|
extends State<PhotographyValuePickerDialog<T>> {
|
||||||
late T _selectedValue = widget.initialValue;
|
late T _selectedValue = widget.initialValue;
|
||||||
final _scrollController = ScrollController();
|
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:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/data/haptics_service.dart';
|
import 'package:lightmeter/data/haptics_service.dart';
|
||||||
import 'package:lightmeter/data/light_sensor_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/models/photography_values/photography_value.dart';
|
||||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||||
import 'package:lightmeter/interactors/metering_interactor.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 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'ev_source/camera/bloc_camera.dart';
|
|
||||||
import 'bloc_metering.dart';
|
import 'bloc_metering.dart';
|
||||||
import 'communication/bloc_communication_metering.dart';
|
import 'communication/bloc_communication_metering.dart';
|
||||||
import 'screen_metering.dart';
|
import 'screen_metering.dart';
|
||||||
|
|
||||||
class MeteringFlow extends StatelessWidget {
|
class MeteringFlow extends StatefulWidget {
|
||||||
const MeteringFlow({super.key});
|
const MeteringFlow({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MeteringFlow> createState() => _MeteringFlowState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MeteringFlowState extends State<MeteringFlow> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sourceType = context.watch<EvSourceType>();
|
|
||||||
return Provider(
|
return Provider(
|
||||||
create: (context) => MeteringInteractor(
|
create: (context) => MeteringInteractor(
|
||||||
context.read<UserPreferencesService>(),
|
context.read<UserPreferencesService>(),
|
||||||
|
@ -37,20 +38,6 @@ class MeteringFlow extends StatelessWidget {
|
||||||
context.read<StopType>(),
|
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(),
|
child: const MeteringScreen(),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||||
import 'package:lightmeter/data/models/photography_values/photography_value.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 'package:lightmeter/res/dimens.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/provider_container_camera.dart';
|
||||||
import 'components/camera/widget_zoom_camera.dart';
|
import 'components/light_sensor_container/provider_container_light_sensor.dart';
|
||||||
import 'components/exposure_pairs_list/widget_list_exposure_pairs.dart';
|
|
||||||
import 'components/topbar/widget_topbar.dart';
|
|
||||||
import 'bloc_metering.dart';
|
import 'bloc_metering.dart';
|
||||||
import 'event_metering.dart';
|
import 'event_metering.dart';
|
||||||
import 'state_metering.dart';
|
import 'state_metering.dart';
|
||||||
|
@ -20,8 +21,6 @@ class MeteringScreen extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MeteringScreenState extends State<MeteringScreen> {
|
class _MeteringScreenState extends State<MeteringScreen> {
|
||||||
double topBarOverflow = 0.0;
|
|
||||||
|
|
||||||
MeteringBloc get _bloc => context.read<MeteringBloc>();
|
MeteringBloc get _bloc => context.read<MeteringBloc>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -34,84 +33,42 @@ class _MeteringScreenState extends State<MeteringScreen> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
body: BlocBuilder<MeteringBloc, MeteringState>(
|
body: Column(
|
||||||
builder: (context, state) => Column(
|
children: [
|
||||||
children: [
|
Expanded(
|
||||||
MeteringTopBar(
|
child: BlocBuilder<MeteringBloc, MeteringState>(
|
||||||
fastest: state.fastest,
|
builder: (context, state) => AnimatedSwitcher(
|
||||||
slowest: state.slowest,
|
duration: Dimens.durationS,
|
||||||
ev: state.ev,
|
child: context.watch<EvSourceType>() == EvSourceType.camera
|
||||||
iso: state.iso,
|
? CameraContainerProvider(
|
||||||
nd: state.nd,
|
fastest: state.fastest,
|
||||||
onIsoChanged: (value) => _bloc.add(IsoChangedEvent(value)),
|
slowest: state.slowest,
|
||||||
onNdChanged: (value) => _bloc.add(NdChangedEvent(value)),
|
iso: state.iso,
|
||||||
onCutoutLayout: (value) => topBarOverflow = value,
|
nd: state.nd,
|
||||||
),
|
onIsoChanged: (value) => _bloc.add(IsoChangedEvent(value)),
|
||||||
Expanded(
|
onNdChanged: (value) => _bloc.add(NdChangedEvent(value)),
|
||||||
child: Padding(
|
exposurePairs: state.exposurePairs,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
)
|
||||||
child: _MiddleContentWrapper(
|
: LightSensorContainerProvider(
|
||||||
topBarOverflow: topBarOverflow,
|
fastest: state.fastest,
|
||||||
leftContent: ExposurePairsList(state.exposurePairs),
|
slowest: state.slowest,
|
||||||
rightContent: Padding(
|
iso: state.iso,
|
||||||
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
|
nd: state.nd,
|
||||||
child: Column(
|
onIsoChanged: (value) => _bloc.add(IsoChangedEvent(value)),
|
||||||
children: const [
|
onNdChanged: (value) => _bloc.add(NdChangedEvent(value)),
|
||||||
Expanded(child: CameraExposureSlider()),
|
exposurePairs: state.exposurePairs,
|
||||||
SizedBox(height: Dimens.grid24),
|
),
|
||||||
CameraZoomSlider(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MeteringBottomControls(
|
),
|
||||||
onMeasure: () => _bloc.add(const MeasureEvent()),
|
MeteringBottomControls(
|
||||||
onSettings: () => Navigator.pushNamed(context, 'settings'),
|
onSwitchEvSourceType: context.read<Environment>().hasLightSensor
|
||||||
),
|
? EvSourceTypeProvider.of(context).toggleType
|
||||||
],
|
: null,
|
||||||
),
|
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue