m3_lightmeter/lib/screens/metering/components/topbar/topbar.dart

203 lines
7.5 KiB
Dart
Raw Normal View History

2022-10-24 20:25:38 +00:00
import 'package:flutter/material.dart';
2022-10-30 18:30:12 +00:00
import 'package:lightmeter/generated/l10n.dart';
2022-10-29 18:02:45 +00:00
import 'package:lightmeter/models/exposure_pair.dart';
import 'package:lightmeter/models/iso_value.dart';
import 'package:lightmeter/models/nd_value.dart';
import 'package:lightmeter/models/photography_value.dart';
2022-10-24 20:25:38 +00:00
import 'package:lightmeter/res/dimens.dart';
2022-10-29 18:02:45 +00:00
import 'components/shared/animated_dialog.dart';
import 'components/dialog_picker.dart';
2022-10-29 18:02:45 +00:00
import 'components/reading_container.dart';
import 'models/reading_value.dart';
2022-10-24 20:25:38 +00:00
class MeteringTopBar extends StatelessWidget {
static const _columnsCount = 3;
2022-10-29 18:02:45 +00:00
final ExposurePair? fastest;
final ExposurePair? slowest;
2022-10-24 20:25:38 +00:00
final double ev;
final IsoValue iso;
final NdValue nd;
2022-12-04 19:00:43 +00:00
final ValueChanged<IsoValue> onIsoChanged;
final ValueChanged<NdValue> onNdChanged;
2022-10-24 20:25:38 +00:00
const MeteringTopBar({
2022-10-29 18:02:45 +00:00
required this.fastest,
required this.slowest,
2022-10-24 20:25:38 +00:00
required this.ev,
required this.iso,
required this.nd,
2022-12-04 19:00:43 +00:00
required this.onIsoChanged,
required this.onNdChanged,
2022-10-24 20:25:38 +00:00
super.key,
});
@override
Widget build(BuildContext context) {
2022-10-29 11:04:46 +00:00
final columnWidth =
(MediaQuery.of(context).size.width - Dimens.paddingM * 2 - Dimens.grid16 * (_columnsCount - 1)) / 3;
2022-10-29 11:06:39 +00:00
return ClipRRect(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(Dimens.borderRadiusL),
bottomRight: Radius.circular(Dimens.borderRadiusL),
),
child: ColoredBox(
color: Theme.of(context).colorScheme.surface,
child: Padding(
padding: const EdgeInsets.all(Dimens.paddingM),
child: SafeArea(
bottom: false,
child: MediaQuery(
data: MediaQuery.of(context),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
height: columnWidth / 3 * 4,
child: ReadingContainer(
values: [
ReadingValue(
label: S.of(context).fastestExposurePair,
value: fastest != null
? '${fastest!.aperture.toString()} - ${fastest!.shutterSpeed.toString()}'
: 'N/A',
2022-10-29 11:06:39 +00:00
),
ReadingValue(
label: S.of(context).slowestExposurePair,
value: fastest != null
? '${slowest!.aperture.toString()} - ${slowest!.shutterSpeed.toString()}'
: 'N/A',
),
],
2022-10-29 11:04:46 +00:00
),
),
const _InnerPadding(),
Row(
children: [
SizedBox(
width: columnWidth,
child: ReadingContainer.singleValue(
value: ReadingValue(
label: 'EV',
value: ev.toStringAsFixed(1),
),
2022-10-29 11:06:39 +00:00
),
2022-10-29 11:04:46 +00:00
),
const _InnerPadding(),
SizedBox(
width: columnWidth,
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,
),
),
],
)
],
),
2022-10-29 11:06:39 +00:00
),
const _InnerPadding(),
SizedBox(
width: columnWidth,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AnimatedDialog(
openedSize: Size(
MediaQuery.of(context).size.width - Dimens.paddingM * 2,
(MediaQuery.of(context).size.width - Dimens.paddingM * 2) / 3 * 4,
),
child: const AspectRatio(
aspectRatio: 3 / 4,
child: ColoredBox(color: Colors.black),
),
2022-10-29 11:06:39 +00:00
),
const _InnerPadding(),
_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,
2022-10-29 11:06:39 +00:00
),
],
),
2022-10-29 11:06:39 +00:00
),
],
),
2022-10-29 11:06:39 +00:00
),
2022-10-24 20:25:38 +00:00
),
),
),
);
}
}
class _InnerPadding extends SizedBox {
2022-10-29 11:04:46 +00:00
const _InnerPadding() : super(height: Dimens.grid16, width: Dimens.grid16);
2022-10-24 20:25:38 +00:00
}
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: ReadingContainer.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));
},
),
);
}
}