Format & tasks

This commit is contained in:
Vadim 2023-01-26 18:03:48 +03:00
parent 130f5ff0b2
commit 42fe5d45bc
30 changed files with 293 additions and 119 deletions

2
.gitignore vendored
View file

@ -50,3 +50,5 @@ app.*.map.json
pubspec.lock pubspec.lock
/ios/Podfile.lock /ios/Podfile.lock
.fvm/

24
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,24 @@
{
"task.slowProviderWarning": true,
"dart.flutterSdkPaths": [
"fvm"
],
"dart.lineLength": 100,
"[dart]": {
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.rulers": [
100,
120,
],
"editor.selectionHighlight": true,
"editor.suggest.snippetsPreventQuickSuggestions": false,
"editor.suggestSelection": "first",
"editor.tabCompletion": "onlySnippets",
"editor.wordBasedSuggestions": false
},
"dart.doNotFormat": [
"**/generated/**",
"lib/data/**"
]
}

53
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,53 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "fvm_build_android_dev",
"type": "shell",
"command": ".fvm/flutter_sdk/bin/flutter",
"args": [
"build",
"apk",
"--flavor",
"dev",
"--release",
"--dart-define",
"cameraPreviewAspectRatio=2/3",
"-t",
"lib/main_dev.dart",
],
},
{
"label": "fvm_build_android_prod",
"type": "shell",
"command": ".fvm/flutter_sdk/bin/flutter",
"args": [
"build",
"apk",
"--flavor",
"prod",
"--release",
"--dart-define",
"cameraPreviewAspectRatio=2/3",
"-t",
"lib/main_prod.dart",
],
},
{
"label": "fvm_build_appbundle",
"type": "shell",
"command": ".fvm/flutter_sdk/bin/flutter",
"args": [
"build",
"appbundle",
"--flavor",
"prod",
"--release",
"--dart-define",
"cameraPreviewAspectRatio=2/3",
"-t",
"lib/main_prod.dart",
],
},
]
}

View file

@ -49,6 +49,18 @@ android {
versionName flutterVersionName versionName flutterVersionName
} }
signingConfigs {
debug {}
/*
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
*/
}
flavorDimensions "app" flavorDimensions "app"
productFlavors { productFlavors {
dev { dev {
@ -62,6 +74,17 @@ android {
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
} }
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
signingConfig signingConfigs.debug
minifyEnabled true
shrinkResources true
}
}
} }
flutter { flutter {

View file

@ -39,13 +39,15 @@ class Application extends StatelessWidget {
child: StopTypeProvider( child: StopTypeProvider(
child: ThemeProvider( child: ThemeProvider(
builder: (context, _) { builder: (context, _) {
final systemIconsBrightness = final systemIconsBrightness = ThemeData.estimateBrightnessForColor(
ThemeData.estimateBrightnessForColor(context.watch<ThemeData>().colorScheme.onSurface); context.watch<ThemeData>().colorScheme.onSurface,
);
return AnnotatedRegion( return AnnotatedRegion(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarColor: Colors.transparent, statusBarColor: Colors.transparent,
statusBarBrightness: statusBarBrightness: systemIconsBrightness == Brightness.light
systemIconsBrightness == Brightness.light ? Brightness.dark : Brightness.light, ? Brightness.dark
: Brightness.light,
statusBarIconBrightness: systemIconsBrightness, statusBarIconBrightness: systemIconsBrightness,
systemNavigationBarColor: context.watch<ThemeData>().colorScheme.surface, systemNavigationBarColor: context.watch<ThemeData>().colorScheme.surface,
systemNavigationBarIconBrightness: systemIconsBrightness, systemNavigationBarIconBrightness: systemIconsBrightness,

View file

@ -1 +1 @@
enum ThemeType {light, dark, systemDefault} enum ThemeType { light, dark, systemDefault }

View file

@ -1,6 +1,6 @@
class PlatformConfig { class PlatformConfig {
static double get cameraPreviewAspectRatio { static double get cameraPreviewAspectRatio {
final rational = const String.fromEnvironment('cameraPreviewAspectRatio', defaultValue: "3/4").split('/'); final rational = const String.fromEnvironment('cameraPreviewAspectRatio').split('/');
return int.parse(rational[0]) / int.parse(rational[1]); return int.parse(rational[0]) / int.parse(rational[1]);
} }
} }

View file

@ -25,8 +25,10 @@ class ThemeProvider extends StatefulWidget {
} }
class ThemeProviderState extends State<ThemeProvider> { class ThemeProviderState extends State<ThemeProvider> {
late final _themeTypeNotifier = ValueNotifier<ThemeType>(context.read<UserPreferencesService>().themeType); UserPreferencesService get _prefs => context.read<UserPreferencesService>();
late final _dynamicColorNotifier = ValueNotifier<bool>(context.read<UserPreferencesService>().dynamicColor);
late final _themeTypeNotifier = ValueNotifier<ThemeType>(_prefs.themeType);
late final _dynamicColorNotifier = ValueNotifier<bool>(_prefs.dynamicColor);
late final _primaryColorNotifier = ValueNotifier<Color>(const Color(0xFF2196f3)); late final _primaryColorNotifier = ValueNotifier<Color>(const Color(0xFF2196f3));
@override @override
@ -64,7 +66,7 @@ class ThemeProviderState extends State<ThemeProvider> {
void setThemeType(ThemeType themeType) { void setThemeType(ThemeType themeType) {
_themeTypeNotifier.value = themeType; _themeTypeNotifier.value = themeType;
context.read<UserPreferencesService>().themeType = themeType; _prefs.themeType = themeType;
} }
Brightness get _themeBrightness { Brightness get _themeBrightness {
@ -80,7 +82,7 @@ class ThemeProviderState extends State<ThemeProvider> {
void enableDynamicColor(bool enable) { void enableDynamicColor(bool enable) {
_dynamicColorNotifier.value = enable; _dynamicColorNotifier.value = enable;
context.read<UserPreferencesService>().dynamicColor = enable; _prefs.dynamicColor = enable;
} }
} }
@ -103,7 +105,8 @@ class _DynamicColorProvider extends StatelessWidget {
late final Color? dynamicPrimaryColor; late final Color? dynamicPrimaryColor;
if (lightDynamic != null && darkDynamic != null) { if (lightDynamic != null && darkDynamic != null) {
if (useDynamicColor) { if (useDynamicColor) {
dynamicPrimaryColor = (themeBrightness == Brightness.light ? lightDynamic : darkDynamic).primary; dynamicPrimaryColor =
(themeBrightness == Brightness.light ? lightDynamic : darkDynamic).primary;
state = DynamicColorState.enabled; state = DynamicColorState.enabled;
} else { } else {
dynamicPrimaryColor = null; dynamicPrimaryColor = null;
@ -153,7 +156,9 @@ class _ThemeDataProvider extends StatelessWidget {
} }
ColorScheme _colorSchemeFromColor() { ColorScheme _colorSchemeFromColor() {
final scheme = brightness == Brightness.light ? Scheme.light(primaryColor.value) : Scheme.dark(primaryColor.value); final scheme = brightness == Brightness.light
? Scheme.light(primaryColor.value)
: Scheme.dark(primaryColor.value);
return ColorScheme( return ColorScheme(
brightness: brightness, brightness: brightness,
primary: Color(scheme.primary), primary: Color(scheme.primary),
@ -168,7 +173,8 @@ class _ThemeDataProvider extends StatelessWidget {
onBackground: Color(scheme.onBackground), onBackground: Color(scheme.onBackground),
surface: Color.alphaBlend(Color(scheme.primary).withOpacity(0.05), Color(scheme.background)), surface: Color.alphaBlend(Color(scheme.primary).withOpacity(0.05), Color(scheme.background)),
onSurface: Color(scheme.onSurface), onSurface: Color(scheme.onSurface),
surfaceVariant: Color.alphaBlend(Color(scheme.primary).withOpacity(0.5), Color(scheme.background)), surfaceVariant:
Color.alphaBlend(Color(scheme.primary).withOpacity(0.5), Color(scheme.background)),
onSurfaceVariant: Color(scheme.onSurfaceVariant), onSurfaceVariant: Color(scheme.onSurfaceVariant),
); );
} }

View file

@ -8,8 +8,10 @@ import 'package:lightmeter/data/models/photography_values/photography_value.dart
import 'package:lightmeter/data/models/photography_values/shutter_speed_value.dart'; import 'package:lightmeter/data/models/photography_values/shutter_speed_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/communication/event_communication_metering.dart' as communication_events; import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart'
import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' as communication_states; as communication_events;
import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart'
as communication_states;
import 'package:lightmeter/utils/log_2.dart'; import 'package:lightmeter/utils/log_2.dart';
import 'communication/bloc_communication_metering.dart'; import 'communication/bloc_communication_metering.dart';
@ -132,7 +134,8 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
evSteps = (ev / 0.3).floor(); evSteps = (ev / 0.3).floor();
break; break;
} }
final evOffset = _shutterSpeedValues.indexOf(const ShutterSpeedValue(1, false, StopType.full)) - evSteps; final evOffset =
_shutterSpeedValues.indexOf(const ShutterSpeedValue(1, false, StopType.full)) - evSteps;
late final int apertureOffset; late final int apertureOffset;
late final int shutterSpeedOffset; late final int shutterSpeedOffset;
@ -144,7 +147,8 @@ class MeteringBloc extends Bloc<MeteringEvent, MeteringState> {
shutterSpeedOffset = 0; shutterSpeedOffset = 0;
} }
int itemsCount = min(_apertureValues.length + shutterSpeedOffset, _shutterSpeedValues.length + apertureOffset) - int itemsCount = min(_apertureValues.length + shutterSpeedOffset,
_shutterSpeedValues.length + apertureOffset) -
max(apertureOffset, shutterSpeedOffset); max(apertureOffset, shutterSpeedOffset);
if (itemsCount < 0) { if (itemsCount < 0) {

View file

@ -3,7 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'event_communication_metering.dart'; import 'event_communication_metering.dart';
import 'state_communication_metering.dart'; import 'state_communication_metering.dart';
class MeteringCommunicationBloc extends Bloc<MeteringCommunicationEvent, MeteringCommunicationState> { class MeteringCommunicationBloc
extends Bloc<MeteringCommunicationEvent, MeteringCommunicationState> {
MeteringCommunicationBloc() : super(const InitState()) { MeteringCommunicationBloc() : super(const InitState()) {
on<MeasureEvent>((_, emit) => emit(const MeasureState())); on<MeasureEvent>((_, emit) => emit(const MeasureState()));
on<MeasuredEvent>((event, emit) => emit(MeasuredState(event.ev100))); on<MeasuredEvent>((event, emit) => emit(MeasuredState(event.ev100)));

View file

@ -6,7 +6,11 @@ class ExposurePairsListItem<T extends PhotographyStopValue> extends StatelessWid
final T value; final T value;
final bool tickOnTheLeft; final bool tickOnTheLeft;
const ExposurePairsListItem(this.value, {required this.tickOnTheLeft, super.key}); const ExposurePairsListItem(
this.value, {
required this.tickOnTheLeft,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View file

@ -1,7 +1,8 @@
import 'package:flutter/material.dart'; 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 'package:lightmeter/screens/metering/components/exposure_pairs_list/components/widget_item_list_exposure_pairs.dart';
import 'components/widget_item_list_exposure_pairs.dart';
class ExposurePairsList extends StatelessWidget { class ExposurePairsList extends StatelessWidget {
final List<ExposurePair> exposurePairs; final List<ExposurePair> exposurePairs;
@ -51,7 +52,9 @@ class ExposurePairsList extends StatelessWidget {
builder: (context, constraints) => Align( builder: (context, constraints) => Align(
alignment: index == 0 alignment: index == 0
? Alignment.bottomCenter ? Alignment.bottomCenter
: (index == exposurePairs.length - 1 ? Alignment.topCenter : Alignment.center), : (index == exposurePairs.length - 1
? Alignment.topCenter
: Alignment.center),
child: SizedBox( child: SizedBox(
height: index == 0 || index == exposurePairs.length - 1 height: index == 0 || index == exposurePairs.length - 1
? constraints.maxHeight / 2 ? constraints.maxHeight / 2

View file

@ -60,18 +60,19 @@ class _ReadingValueBuilder extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme; final textTheme = Theme.of(context).textTheme;
final textColor = Theme.of(context).colorScheme.onPrimaryContainer;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
reading.label, reading.label,
style: textTheme.labelMedium?.copyWith(color: Theme.of(context).colorScheme.onPrimaryContainer), style: textTheme.labelMedium?.copyWith(color: textColor),
), ),
const SizedBox(height: Dimens.grid4), const SizedBox(height: Dimens.grid4),
Text( Text(
reading.value, reading.value,
style: textTheme.titleMedium?.copyWith(color: Theme.of(context).colorScheme.onPrimaryContainer), style: textTheme.titleMedium?.copyWith(color: textColor),
), ),
], ],
); );

View file

@ -47,7 +47,10 @@ class AnimatedDialogState extends State<AnimatedDialog> with SingleTickerProvide
reverseDuration: Dimens.durationML, reverseDuration: Dimens.durationML,
vsync: this, vsync: this,
); );
_defaultCurvedAnimation = CurvedAnimation(parent: _animationController, curve: Curves.easeInOut); _defaultCurvedAnimation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
_barrierColorAnimation = ColorTween( _barrierColorAnimation = ColorTween(
begin: Colors.transparent, begin: Colors.transparent,
end: Colors.black54, end: Colors.black54,

View file

@ -55,6 +55,8 @@ class CameraView extends StatelessWidget {
DeviceOrientation _getApplicableOrientation(CameraValue value) { DeviceOrientation _getApplicableOrientation(CameraValue value) {
return value.isRecordingVideo return value.isRecordingVideo
? value.recordingOrientation! ? value.recordingOrientation!
: (value.previewPauseOrientation ?? value.lockedCaptureOrientation ?? value.deviceOrientation); : (value.previewPauseOrientation ??
value.lockedCaptureOrientation ??
value.deviceOrientation);
} }
} }

View file

@ -4,7 +4,8 @@ import 'package:lightmeter/data/models/photography_values/photography_value.dart
import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/res/dimens.dart';
typedef DialogPickerItemBuilder<T extends PhotographyValue> = Widget Function(BuildContext, T); typedef DialogPickerItemBuilder<T extends PhotographyValue> = Widget Function(BuildContext, T);
typedef DialogPickerEvDifferenceBuilder<T extends PhotographyValue> = String Function(T selected, T other); typedef DialogPickerEvDifferenceBuilder<T extends PhotographyValue> = String Function(
T selected, T other);
class MeteringScreenDialogPicker<T extends PhotographyValue> extends StatefulWidget { class MeteringScreenDialogPicker<T extends PhotographyValue> extends StatefulWidget {
final String title; final String title;
@ -32,7 +33,8 @@ class MeteringScreenDialogPicker<T extends PhotographyValue> extends StatefulWid
State<MeteringScreenDialogPicker<T>> createState() => _MeteringScreenDialogPickerState<T>(); State<MeteringScreenDialogPicker<T>> createState() => _MeteringScreenDialogPickerState<T>();
} }
class _MeteringScreenDialogPickerState<T extends PhotographyValue> extends State<MeteringScreenDialogPicker<T>> { class _MeteringScreenDialogPickerState<T extends PhotographyValue>
extends State<MeteringScreenDialogPicker<T>> {
late T _selectedValue = widget.initialValue; late T _selectedValue = widget.initialValue;
final _scrollController = ScrollController(); final _scrollController = ScrollController();
@ -101,7 +103,9 @@ class _MeteringScreenDialogPickerState<T extends PhotographyValue> extends State
child: widget.itemTitleBuilder(context, widget.values[index]), child: widget.itemTitleBuilder(context, widget.values[index]),
), ),
secondary: widget.values[index].value != _selectedValue.value secondary: widget.values[index].value != _selectedValue.value
? Text(S.of(context).ev(widget.evDifferenceBuilder.call(_selectedValue, widget.values[index]))) ? Text(S
.of(context)
.ev(widget.evDifferenceBuilder.call(_selectedValue, widget.values[index])))
: null, : null,
onChanged: (value) { onChanged: (value) {
if (value != null) { if (value != null) {

View file

@ -54,7 +54,7 @@ class TopBarShape extends CustomPainter {
); );
// Bottom side with step // Bottom side with step
final double allowedRadius = min(appendixHeight.abs() / 2, Dimens.borderRadiusL); final allowedRadius = min(appendixHeight.abs() / 2, Dimens.borderRadiusL);
path.lineTo(appendixWidth - allowedRadius, size.height + appendixHeight); path.lineTo(appendixWidth - allowedRadius, size.height + appendixHeight);
final bool isCutout = appendixHeight < 0; final bool isCutout = appendixHeight < 0;

View file

@ -174,7 +174,9 @@ class _NdValueTile extends StatelessWidget {
subtitle: S.of(context).ndFilterFactor, subtitle: S.of(context).ndFilterFactor,
selectedValue: value, selectedValue: value,
values: ndValues, values: ndValues,
itemTitleBuilder: (_, value) => Text(value.value == 0 ? S.of(context).none : value.value.toString()), itemTitleBuilder: (_, value) => Text(
value.value == 0 ? S.of(context).none : value.value.toString(),
),
// using descending order, because ND filter darkens image & lowers EV // using descending order, because ND filter darkens image & lowers EV
evDifferenceBuilder: (selected, other) => other.toStringDifference(selected), evDifferenceBuilder: (selected, other) => other.toStringDifference(selected),
onChanged: onChanged, onChanged: onChanged,

View file

@ -9,8 +9,10 @@ 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/ev_source_bloc.dart'; import 'package:lightmeter/screens/metering/ev_source/ev_source_bloc.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' as communication_event; import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart'
import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' as communication_states; as communication_event;
import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart'
as communication_states;
import 'package:lightmeter/utils/log_2.dart'; import 'package:lightmeter/utils/log_2.dart';
import 'event_camera.dart'; import 'event_camera.dart';
@ -61,7 +63,9 @@ class CameraBloc extends EvSourceBloc<CameraEvent, CameraState> {
if (communicationState is communication_states.MeasureState) { if (communicationState is communication_states.MeasureState) {
_takePhoto().then((ev100) { _takePhoto().then((ev100) {
if (ev100 != null) { if (ev100 != null) {
communicationBloc.add(communication_event.MeasuredEvent(ev100 + _meteringInteractor.cameraEvCalibration)); communicationBloc.add(
communication_event.MeasuredEvent(ev100 + _meteringInteractor.cameraEvCalibration),
);
} }
}); });
} }
@ -92,7 +96,12 @@ class CameraBloc extends EvSourceBloc<CameraEvent, CameraState> {
_exposureOffsetRange = await Future.wait<double>([ _exposureOffsetRange = await Future.wait<double>([
_cameraController!.getMinExposureOffset(), _cameraController!.getMinExposureOffset(),
_cameraController!.getMaxExposureOffset(), _cameraController!.getMaxExposureOffset(),
]).then((levels) => RangeValues(max(_exposureMaxRange.start, levels[0]), min(_exposureMaxRange.end, levels[1]))); ]).then(
(levels) => RangeValues(
max(_exposureMaxRange.start, levels[0]),
min(_exposureMaxRange.end, levels[1]),
),
);
await _cameraController!.getExposureOffsetStepSize().then((value) { await _cameraController!.getExposureOffsetStepSize().then((value) {
_exposureStep = value == 0 ? 0.1 : value; _exposureStep = value == 0 ? 0.1 : value;
}); });

View file

@ -1,7 +1,8 @@
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';
import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' as communication_states; import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart'
as communication_states;
abstract class EvSourceBloc<E, S> extends Bloc<E, S> { abstract class EvSourceBloc<E, S> extends Bloc<E, S> {
final MeteringCommunicationBloc communicationBloc; final MeteringCommunicationBloc communicationBloc;

View file

@ -1,8 +1,10 @@
import 'dart:math'; import 'dart:math';
import 'package:lightmeter/screens/metering/ev_source/ev_source_bloc.dart'; import 'package:lightmeter/screens/metering/ev_source/ev_source_bloc.dart';
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart' as communication_event; import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart'
import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart' as communication_states; as communication_event;
import 'package:lightmeter/screens/metering/communication/state_communication_metering.dart'
as communication_states;
import 'event_random_ev.dart'; import 'event_random_ev.dart';
import 'state_random_ev.dart'; import 'state_random_ev.dart';

View file

@ -22,10 +22,12 @@ class MeteringScreen extends StatefulWidget {
class _MeteringScreenState extends State<MeteringScreen> { class _MeteringScreenState extends State<MeteringScreen> {
double topBarOverflow = 0.0; double topBarOverflow = 0.0;
MeteringBloc get _bloc => context.read<MeteringBloc>();
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
context.read<MeteringBloc>().add(StopTypeChangedEvent(context.watch<StopType>())); _bloc.add(StopTypeChangedEvent(context.watch<StopType>()));
} }
@override @override
@ -33,66 +35,83 @@ class _MeteringScreenState extends State<MeteringScreen> {
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).colorScheme.background, backgroundColor: Theme.of(context).colorScheme.background,
body: BlocBuilder<MeteringBloc, MeteringState>( body: BlocBuilder<MeteringBloc, MeteringState>(
builder: (context, state) { builder: (context, state) => Column(
return Stack( children: [
children: [ MeteringTopBar(
Column( fastest: state.fastest,
children: [ slowest: state.slowest,
MeteringTopBar( ev: state.ev,
fastest: state.fastest, iso: state.iso,
slowest: state.slowest, nd: state.nd,
ev: state.ev, onIsoChanged: (value) => _bloc.add(IsoChangedEvent(value)),
iso: state.iso, onNdChanged: (value) => _bloc.add(NdChangedEvent(value)),
nd: state.nd, onCutoutLayout: (value) => topBarOverflow = value,
onIsoChanged: (value) => context.read<MeteringBloc>().add(IsoChangedEvent(value)), ),
onNdChanged: (value) => context.read<MeteringBloc>().add(NdChangedEvent(value)), Expanded(
onCutoutLayout: (value) => topBarOverflow = value, child: Padding(
), padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
Expanded( child: _MiddleContentWrapper(
child: LayoutBuilder( topBarOverflow: topBarOverflow,
builder: (context, constraints) => OverflowBox( leftContent: ExposurePairsList(state.exposurePairs),
alignment: Alignment.bottomCenter, rightContent: Padding(
maxHeight: constraints.maxHeight + topBarOverflow.abs(), padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
maxWidth: constraints.maxWidth, child: Column(
child: Padding( children: const [
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM), Expanded(child: CameraExposureSlider()),
child: Row( SizedBox(height: Dimens.grid24),
children: [ CameraZoomSlider(),
Expanded( ],
child: Padding(
padding: topBarOverflow >= 0 ? EdgeInsets.only(top: topBarOverflow) : EdgeInsets.zero,
child: ExposurePairsList(state.exposurePairs),
),
),
const SizedBox(width: Dimens.grid8),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM).add(
topBarOverflow <= 0 ? EdgeInsets.only(top: -topBarOverflow) : EdgeInsets.zero),
child: Column(
children: const [
Expanded(child: CameraExposureSlider()),
SizedBox(height: Dimens.grid24),
CameraZoomSlider(),
],
),
),
),
],
),
),
),
), ),
), ),
MeteringBottomControls( ),
onMeasure: () => context.read<MeteringBloc>().add(const MeasureEvent()),
onSettings: () => Navigator.pushNamed(context, 'settings'),
),
],
), ),
], ),
); 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,
),
),
],
),
), ),
); );
} }

View file

@ -17,6 +17,8 @@ class CalibrationDialog extends StatefulWidget {
} }
class _CalibrationDialogState extends State<CalibrationDialog> { class _CalibrationDialogState extends State<CalibrationDialog> {
CalibrationDialogBloc get bloc => context.read<CalibrationDialogBloc>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
@ -37,8 +39,8 @@ class _CalibrationDialogState extends State<CalibrationDialog> {
_CalibrationUnit( _CalibrationUnit(
title: S.of(context).camera, title: S.of(context).camera,
value: state.cameraEvCalibration, value: state.cameraEvCalibration,
onChanged: (value) => context.read<CalibrationDialogBloc>().add(CameraEvCalibrationChangedEvent(value)), onChanged: (value) => bloc.add(CameraEvCalibrationChangedEvent(value)),
onReset: () => context.read<CalibrationDialogBloc>().add(const CameraEvCalibrationResetEvent()), onReset: () => bloc.add(const CameraEvCalibrationResetEvent()),
), ),
], ],
), ),
@ -56,7 +58,7 @@ class _CalibrationDialogState extends State<CalibrationDialog> {
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
context.read<CalibrationDialogBloc>().add(const SaveCalibrationDialogEvent()); bloc.add(const SaveCalibrationDialogEvent());
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Text(S.of(context).save), child: Text(S.of(context).save),

View file

@ -12,12 +12,9 @@ class VersionListTile extends StatelessWidget {
title: Text(S.of(context).version), title: Text(S.of(context).version),
trailing: FutureBuilder<PackageInfo>( trailing: FutureBuilder<PackageInfo>(
future: PackageInfo.fromPlatform(), future: PackageInfo.fromPlatform(),
builder: (context, snapshot) { builder: (context, snapshot) => snapshot.data != null
if (snapshot.data != null) { ? Text(S.of(context).versionNumber(snapshot.data!.version, snapshot.data!.buildNumber))
return Text(S.of(context).versionNumber(snapshot.data!.version, snapshot.data!.buildNumber)); : const SizedBox.shrink(),
}
return const SizedBox.shrink();
},
), ),
); );
} }

View file

@ -13,7 +13,8 @@ class WriteEmailListTile extends StatelessWidget {
leading: const Icon(Icons.email), leading: const Icon(Icons.email),
title: Text(S.of(context).writeEmail), title: Text(S.of(context).writeEmail),
onTap: () { onTap: () {
launchUrl(Uri.parse('mailto:${context.read<Environment>().contactEmail}?subject=M3 Lightmeter')); launchUrl(
Uri.parse('mailto:${context.read<Environment>().contactEmail}?subject=M3 Lightmeter'));
}, },
); );
} }

View file

@ -32,7 +32,10 @@ class SettingsScreen extends StatelessWidget {
titlePadding: const EdgeInsets.all(Dimens.paddingM), titlePadding: const EdgeInsets.all(Dimens.paddingM),
title: Text( title: Text(
S.of(context).settings, S.of(context).settings,
style: TextStyle(color: Theme.of(context).colorScheme.onSurface, fontSize: 24), style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontSize: 24,
),
), ),
), ),
actions: [ actions: [

View file

@ -51,8 +51,14 @@ class _CenteredSliderState extends State<CenteredSlider> {
quarterTurns: widget.isVertical ? -1 : 0, quarterTurns: widget.isVertical ? -1 : 0,
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTapUp: (details) => _updateHandlePosition(details.localPosition.dx, handleDistance), onTapUp: (details) => _updateHandlePosition(
onHorizontalDragUpdate: (details) => _updateHandlePosition(details.localPosition.dx, handleDistance), details.localPosition.dx,
handleDistance,
),
onHorizontalDragUpdate: (details) => _updateHandlePosition(
details.localPosition.dx,
handleDistance,
),
child: SizedBox( child: SizedBox(
height: Dimens.cameraSliderHandleSize, height: Dimens.cameraSliderHandleSize,
width: biggestSize, width: biggestSize,
@ -83,10 +89,8 @@ class _CenteredSliderState extends State<CenteredSlider> {
relativeValue = (offset - Dimens.cameraSliderHandleSize / 2) / handleDistance; relativeValue = (offset - Dimens.cameraSliderHandleSize / 2) / handleDistance;
} }
setState(() {}); setState(() {});
widget.onChanged(_clampToRange(relativeValue)); widget.onChanged(relativeValue * (widget.max - widget.min) + widget.min);
} }
double _clampToRange(double relativeValue) => relativeValue * (widget.max - widget.min) + widget.min;
} }
class _Slider extends StatelessWidget { class _Slider extends StatelessWidget {
@ -111,7 +115,9 @@ class _Slider extends StatelessWidget {
children: [ children: [
Positioned( Positioned(
height: trackThickness, height: trackThickness,
width: handleDistance + trackThickness, // add thickness to maintain radius overlap with handle
/// add thickness to maintain radius overlap with handle
width: handleDistance + trackThickness,
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(trackThickness / 2), borderRadius: BorderRadius.circular(trackThickness / 2),
child: ColoredBox(color: Theme.of(context).colorScheme.surfaceVariant), child: ColoredBox(color: Theme.of(context).colorScheme.surfaceVariant),