Made TopBarShape responsive

returned `TopBarShape`

fixed `TopBarShape`
This commit is contained in:
Vadim 2023-01-20 22:31:35 +03:00
parent 5eb0869aa0
commit 5bea96669d
5 changed files with 280 additions and 101 deletions

View file

@ -6,4 +6,7 @@ class ExposurePair {
final ShutterSpeedValue shutterSpeed;
const ExposurePair(this.aperture, this.shutterSpeed);
@override
String toString() => '${aperture.toString()} - ${shutterSpeed.toString()}';
}

View file

@ -0,0 +1,97 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:lightmeter/res/dimens.dart';
class TopBarShape extends CustomPainter {
final Color color;
/// The appendix is on the left side
/// but if appendix height is negative, then we have to make a cutout
///
/// negative positive
/// | | /// | |
/// | | /// | |
/// | | /// | |
/// | | /// | |
/// \________ | /// | ________/
/// \ | /// | /
/// | | /// | | | appendix height
/// \__________/ /// \__________/
///
final double appendixHeight;
final double appendixWidth;
TopBarShape({
required this.color,
required this.appendixHeight,
required this.appendixWidth,
});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
final path = Path();
const circularRadius = Radius.circular(Dimens.borderRadiusL);
if (appendixHeight == 0 || appendixWidth == 0) {
path.addRRect(
RRect.fromLTRBAndCorners(
0,
0,
0,
0,
bottomLeft: circularRadius,
bottomRight: circularRadius,
),
);
} else {
// Left side with bottom corner
path.lineTo(0, size.height + appendixHeight - Dimens.borderRadiusL);
path.arcToPoint(
Offset(Dimens.borderRadiusL, size.height + appendixHeight),
radius: circularRadius,
clockwise: false,
);
// Bottom side with step
final double allowedRadius = min(appendixHeight.abs() / 2, Dimens.borderRadiusL);
path.lineTo(appendixWidth - allowedRadius, size.height + appendixHeight);
final bool isCutout = appendixHeight < 0;
if (isCutout) {
path.arcToPoint(
Offset(appendixWidth, size.height + appendixHeight + allowedRadius),
radius: circularRadius,
clockwise: true,
);
path.lineTo(appendixWidth, size.height - allowedRadius);
} else {
path.arcToPoint(
Offset(appendixWidth, size.height + appendixHeight - allowedRadius),
radius: circularRadius,
clockwise: false,
);
path.lineTo(appendixWidth, size.height + allowedRadius);
}
path.arcToPoint(
Offset(appendixWidth + allowedRadius, size.height),
radius: circularRadius,
clockwise: !isCutout,
);
// Right side with bottom corner
path.lineTo(size.width - Dimens.borderRadiusL, size.height);
path.arcToPoint(
Offset(size.width, size.height - Dimens.borderRadiusL),
radius: circularRadius,
clockwise: false,
);
path.lineTo(size.width, 0);
path.close();
}
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}

View file

@ -1,6 +1,7 @@
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/platform_config.dart';
import 'package:lightmeter/screens/metering/components/topbar/shape_topbar.dart';
import 'package:lightmeter/screens/metering/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';
@ -13,7 +14,7 @@ import 'components/shared/widget_dialog_animated.dart';
import 'components/widget_dialog_picker.dart';
import 'components/container_reading_value.dart';
class MeteringTopBar extends StatelessWidget {
class MeteringTopBar extends StatefulWidget {
final ExposurePair? fastest;
final ExposurePair? slowest;
final double ev;
@ -22,6 +23,8 @@ class MeteringTopBar extends StatelessWidget {
final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged;
final ValueChanged<double> onCutoutLayout;
const MeteringTopBar({
required this.fastest,
required this.slowest,
@ -30,18 +33,27 @@ class MeteringTopBar extends StatelessWidget {
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 ClipRRect(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(Dimens.borderRadiusL),
bottomRight: Radius.circular(Dimens.borderRadiusL),
),
child: ColoredBox(
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(
@ -53,6 +65,8 @@ class MeteringTopBar extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: ReadingsContainer(
onLayout: (size) => _onReadingsLayout(size.height),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -60,15 +74,11 @@ class MeteringTopBar extends StatelessWidget {
values: [
ReadingValue(
label: S.of(context).fastestExposurePair,
value: fastest != null
? '${fastest!.aperture.toString()} - ${fastest!.shutterSpeed.toString()}'
: '-',
value: widget.fastest != null ? widget.fastest!.toString() : '-',
),
ReadingValue(
label: S.of(context).slowestExposurePair,
value: fastest != null
? '${slowest!.aperture.toString()} - ${slowest!.shutterSpeed.toString()}'
: '-',
value: widget.fastest != null ? widget.slowest!.toString() : '-',
),
],
),
@ -85,30 +95,16 @@ class MeteringTopBar extends StatelessWidget {
Row(
children: [
Expanded(
child: _AnimatedDialogPicker(
title: S.of(context).iso,
subtitle: S.of(context).filmSpeed,
selectedValue: iso,
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: onIsoChanged,
child: _IsoValueTile(
value: widget.iso,
onChanged: widget.onIsoChanged,
),
),
const _InnerPadding(),
Expanded(
child: _AnimatedDialogPicker(
title: S.of(context).nd,
subtitle: S.of(context).ndFilterFactor,
selectedValue: nd,
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: onNdChanged,
child: _NdValueTile(
value: widget.nd,
onChanged: widget.onNdChanged,
),
),
],
@ -116,17 +112,12 @@ class MeteringTopBar extends StatelessWidget {
],
),
),
),
const _InnerPadding(),
Expanded(
child: AnimatedDialog(
openedSize: Size(
MediaQuery.of(context).size.width - Dimens.paddingM * 2,
(MediaQuery.of(context).size.width - Dimens.paddingM * 2) / 3 * 4,
),
child: BlocProvider.value(
value: context.read<CameraBloc>(),
child: const CameraView(),
),
const Expanded(
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(Dimens.borderRadiusM)),
child: CameraView(),
),
),
],
@ -134,15 +125,63 @@ class MeteringTopBar extends StatelessWidget {
),
),
),
),
);
}
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;

View file

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class ReadingsContainer extends SingleChildRenderObjectWidget {
final ValueChanged<Size>? onLayout;
const ReadingsContainer({
super.key,
super.child,
this.onLayout,
});
@override
RenderReadingsContainer createRenderObject(BuildContext context) => RenderReadingsContainer(onLayout: onLayout);
}
class RenderReadingsContainer extends RenderProxyBox {
final ValueChanged<Size>? onLayout;
RenderReadingsContainer({this.onLayout});
@override
void performLayout() {
if (child != null) {
child!.layout(constraints, parentUsesSize: true);
size = child!.size;
} else {
size = computeSizeForNoChild(constraints);
}
onLayout?.call(size);
}
}

View file

@ -21,6 +21,8 @@ class MeteringScreen extends StatefulWidget {
}
class _MeteringScreenState extends State<MeteringScreen> {
double topBarOverflow = 0.0;
@override
void didChangeDependencies() {
super.didChangeDependencies();
@ -37,17 +39,37 @@ class _MeteringScreenState extends State<MeteringScreen> {
children: [
Column(
children: [
_topBar(state),
MeteringTopBar(
fastest: state.fastest,
slowest: state.slowest,
ev: state.ev,
iso: state.iso,
nd: state.nd,
onIsoChanged: (value) => context.read<MeteringBloc>().add(IsoChangedEvent(value)),
onNdChanged: (value) => context.read<MeteringBloc>().add(NdChangedEvent(value)),
onCutoutLayout: (value) => topBarOverflow = value,
),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) => OverflowBox(
alignment: Alignment.bottomCenter,
maxHeight: constraints.maxHeight + topBarOverflow.abs(),
maxWidth: constraints.maxWidth,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
child: Row(
children: [
Expanded(child: ExposurePairsList(state.exposurePairs)),
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),
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM).add(
topBarOverflow <= 0 ? EdgeInsets.only(top: -topBarOverflow) : EdgeInsets.zero),
child: Column(
children: const [
Expanded(child: CameraExposureSlider()),
@ -61,35 +83,21 @@ class _MeteringScreenState extends State<MeteringScreen> {
),
),
),
_bottomBar(),
],
),
],
);
},
),
);
}
Widget _topBar(MeteringState state) {
return MeteringTopBar(
fastest: state.fastest,
slowest: state.slowest,
ev: state.ev,
iso: state.iso,
nd: state.nd,
onIsoChanged: (value) => context.read<MeteringBloc>().add(IsoChangedEvent(value)),
onNdChanged: (value) => context.read<MeteringBloc>().add(NdChangedEvent(value)),
);
}
Widget _bottomBar() {
return MeteringBottomControls(
MeteringBottomControls(
onSourceChanged: () {},
onMeasure: () => context.read<MeteringBloc>().add(const MeasureEvent()),
onSettings: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => const SettingsScreen()));
},
),
],
),
],
);
},
),
);
}
}