mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-12-03 12:50:40 +00:00
ML-44 Save zoom level for equipment profile (#164)
* made zoom slider vertical & added more ticks to ruler * show sliders values * increased slider tappable area * more accurate sliders values * added zoom slider to equipment profiles settings * split `EquipmentListTiles` widget * set zoom on equipment profile change * clamp zoom to the nearest value * added missing translations * added zoom checks to e2e test * removed unused import
This commit is contained in:
parent
bfd0bfe531
commit
2117df2f11
22 changed files with 548 additions and 270 deletions
13
.vscode/launch.json
vendored
13
.vscode/launch.json
vendored
|
@ -61,5 +61,18 @@
|
|||
],
|
||||
"program": "${workspaceFolder}/lib/main_dev.dart",
|
||||
},
|
||||
{
|
||||
"name": "integration-test",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "debug",
|
||||
"args": [
|
||||
"--flavor",
|
||||
"dev",
|
||||
"--dart-define",
|
||||
"cameraStubImage=assets/camera_stub_image.jpg"
|
||||
],
|
||||
"program": "${workspaceFolder}/integration_test/run_all_tests.dart",
|
||||
},
|
||||
],
|
||||
}
|
|
@ -16,6 +16,7 @@ import 'package:lightmeter/screens/metering/components/shared/readings_container
|
|||
import 'package:lightmeter/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_range_picker/widget_dialog_picker_range.dart';
|
||||
import 'package:lightmeter/screens/settings/screen_settings.dart';
|
||||
import 'package:lightmeter/utils/double_to_zoom.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
@ -56,6 +57,8 @@ void testE2E(String description) {
|
|||
await tester.setNdValues(0, mockEquipmentProfiles[0].ndValues);
|
||||
await tester.setApertureValues(0, mockEquipmentProfiles[0].apertureValues);
|
||||
await tester.setShutterSpeedValues(0, mockEquipmentProfiles[0].shutterSpeedValues);
|
||||
await tester.setZoomValue(0, mockEquipmentProfiles[0].lensZoom);
|
||||
expect(find.text('x1.91'), findsOneWidget);
|
||||
expect(find.text('f/1.7 - f/16'), findsOneWidget);
|
||||
expect(find.text('1/1000 - 16"'), findsOneWidget);
|
||||
|
||||
|
@ -65,6 +68,8 @@ void testE2E(String description) {
|
|||
await tester.setProfileName(mockEquipmentProfiles[1].name);
|
||||
await tester.expandEquipmentProfileContainer(mockEquipmentProfiles[1].name);
|
||||
await tester.setApertureValues(1, mockEquipmentProfiles[1].apertureValues);
|
||||
await tester.setZoomValue(1, mockEquipmentProfiles[1].lensZoom);
|
||||
expect(find.text('x5.02'), findsOneWidget);
|
||||
expect(find.text('f/3.5 - f/22'), findsOneWidget);
|
||||
expect(find.text('1/1000 - 16"'), findsNWidgets(2));
|
||||
await tester.navigatorPop();
|
||||
|
@ -171,6 +176,9 @@ extension EquipmentProfileActions on WidgetTester {
|
|||
|
||||
Future<void> setShutterSpeedValues(int profileIndex, List<ShutterSpeedValue> values) =>
|
||||
_setDialogRangePickerValues<ShutterSpeedValue>(profileIndex, S.current.shutterSpeedValues, values);
|
||||
|
||||
Future<void> setZoomValue(int profileIndex, double value) =>
|
||||
_setDialogSliderPickerValue(profileIndex, S.current.lensZoom, value);
|
||||
}
|
||||
|
||||
extension on WidgetTester {
|
||||
|
@ -235,6 +243,30 @@ extension on WidgetTester {
|
|||
|
||||
await tapSaveButton();
|
||||
}
|
||||
|
||||
Future<void> _setDialogSliderPickerValue(
|
||||
int profileIndex,
|
||||
String listTileTitle,
|
||||
double value,
|
||||
) async {
|
||||
await tap(find.text(listTileTitle).at(profileIndex));
|
||||
await pumpAndSettle();
|
||||
|
||||
final sliderFinder = find.byType(Slider);
|
||||
final trackWidth = getSize(sliderFinder).width - (2 * Dimens.paddingL);
|
||||
final trackStep = trackWidth / (widget<Slider>(sliderFinder).max - widget<Slider>(sliderFinder).min);
|
||||
|
||||
final oldValue = widget<Slider>(sliderFinder).value;
|
||||
final oldStart = (oldValue - 1) * trackStep;
|
||||
final newStart = (value - 1) * trackStep;
|
||||
await dragFrom(
|
||||
getTopLeft(sliderFinder) + Offset(Dimens.paddingL + oldStart, getSize(sliderFinder).height / 2),
|
||||
Offset(newStart - oldStart, 0),
|
||||
);
|
||||
await pump();
|
||||
|
||||
await tapSaveButton();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _expectMeteringState(
|
||||
|
@ -257,6 +289,7 @@ Future<void> _expectMeteringState(
|
|||
await tester.scrollToTheLastExposurePair(equipmentProfile: equipmentProfile);
|
||||
expectExposurePairsListItem(tester, slowest.split(' - ')[0], slowest.split(' - ')[1]);
|
||||
expectMeasureButton(ev);
|
||||
expect(find.text(equipmentProfile.lensZoom.toZoom()), findsOneWidget);
|
||||
}
|
||||
|
||||
Future<void> _expectMeteringStateAndMeasure(
|
||||
|
|
|
@ -91,6 +91,7 @@ final mockEquipmentProfiles = [
|
|||
IsoValue(1600, StopType.full),
|
||||
IsoValue(3200, StopType.full),
|
||||
],
|
||||
lensZoom: 1.91,
|
||||
),
|
||||
EquipmentProfile(
|
||||
id: '2',
|
||||
|
@ -120,6 +121,7 @@ final mockEquipmentProfiles = [
|
|||
IsoValue(1600, StopType.full),
|
||||
IsoValue(3200, StopType.full),
|
||||
],
|
||||
lensZoom: 5.02,
|
||||
),
|
||||
];
|
||||
|
||||
|
|
|
@ -60,6 +60,8 @@
|
|||
"shutterSpeedValuesFilterDescription": "Select the range of shutter speed values to display. This is usually determined by the camera body you are using.",
|
||||
"isoValues": "ISO values",
|
||||
"isoValuesFilterDescription": "Select the ISO values to display. These may be your most commonly used values or those supported by your camera.",
|
||||
"lensZoom": "Lens zoom",
|
||||
"lensZoomDescription": "Set the zoom level relative to the phone's camera to match your camera's viewfinder.",
|
||||
"equipmentProfile": "Equipment profile",
|
||||
"equipmentProfiles": "Equipment profiles",
|
||||
"tapToAdd": "Tap to add",
|
||||
|
|
|
@ -60,6 +60,8 @@
|
|||
"shutterSpeedValuesFilterDescription": "Sélectionnez la plage de valeurs de vitesse d'obturation à afficher. Cela est généralement déterminé par le corps de l'appareil que vous utilisez.",
|
||||
"isoValues": "Valeurs ISO",
|
||||
"isoValuesFilterDescription": "Sélectionnez les valeurs ISO à afficher. Ce sont peut-être vos valeurs les plus couramment utilisées ou celles prises en charge par votre caméra.",
|
||||
"lensZoom": "Zoom sur l'objectif",
|
||||
"lensZoomDescription": "Réglez le niveau de zoom par rapport à l'appareil photo du téléphone pour qu'il corresponde au viseur de votre appareil photo.",
|
||||
"equipmentProfile": "Profil de l'équipement",
|
||||
"equipmentProfiles": "Profils de l'équipement",
|
||||
"tapToAdd": "Appuie pour ajouter",
|
||||
|
|
|
@ -60,6 +60,8 @@
|
|||
"shutterSpeedValuesFilterDescription": "Выберите диапазон значений выдержки. Обычно ограничивается возможностями вашей камеры.",
|
||||
"isoValues": "Значения ISO",
|
||||
"isoValuesFilterDescription": "Выберите значения ISO для отображения. Это может быть наиболее часто используемые значения или значения, поддерживаемые вашей камерой.",
|
||||
"lensZoom": "Зум объектива",
|
||||
"lensZoomDescription": "Установите уровень зума относительно камеры телефона, чтобы он соответствовал видоискателю вашей камеры.",
|
||||
"equipmentProfile": "Оборудование",
|
||||
"equipmentProfiles": "Профили оборудования",
|
||||
"tapToAdd": "Нажмите, чтобы добавить",
|
||||
|
|
|
@ -60,6 +60,8 @@
|
|||
"shutterSpeedValuesFilterDescription": "选择要显示的快门速度范围。这通常由您使用的相机机身决定。",
|
||||
"isoValues": "ISO",
|
||||
"isoValuesFilterDescription": "选择要显示的 ISO 。这些可能是您常用的ISO值,也可以是相机支持的ISO范围。",
|
||||
"lensZoom": "镜头变焦",
|
||||
"lensZoomDescription": "设置相对于手机摄像头的变焦级别,使其与相机取景器相匹配。",
|
||||
"equipmentProfile": "设备配置",
|
||||
"equipmentProfiles": "设备配置",
|
||||
"tapToAdd": "点击添加",
|
||||
|
@ -113,4 +115,4 @@
|
|||
"tooltipUseLightSensor": "使用光线传感器",
|
||||
"tooltipUseCamera": "使用摄像头",
|
||||
"tooltipOpenSettings": "打开设置"
|
||||
}
|
||||
}
|
|
@ -38,7 +38,8 @@ class Dimens {
|
|||
// `CenteredSlider`
|
||||
static const double cameraSliderTrackHeight = grid4;
|
||||
static const double cameraSliderTrackRadius = cameraSliderTrackHeight / 2;
|
||||
static const double cameraSliderHandleSize = 32;
|
||||
static const double cameraSliderHandleArea = 32;
|
||||
static const double cameraSliderHandleSize = 24;
|
||||
static const double cameraSliderHandleIconSize = cameraSliderHandleSize * 2 / 3;
|
||||
|
||||
// Dialog
|
||||
|
|
|
@ -169,9 +169,10 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
|||
}
|
||||
|
||||
Future<void> _onZoomChanged(ZoomChangedEvent event, Emitter emit) async {
|
||||
if (_cameraController != null && event.value >= _zoomRange!.start && event.value <= _zoomRange!.end) {
|
||||
_cameraController!.setZoomLevel(event.value);
|
||||
_currentZoom = event.value;
|
||||
if (_cameraController != null) {
|
||||
final double zoom = event.value.clamp(_zoomRange!.start, _zoomRange!.end);
|
||||
_cameraController!.setZoomLevel(zoom);
|
||||
_currentZoom = zoom;
|
||||
_emitActiveState(emit);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/shared/centered_slider/widget_slider_centered.dart';
|
||||
import 'package:lightmeter/screens/shared/ruler_slider/widget_slider_ruler.dart';
|
||||
import 'package:lightmeter/utils/to_string_signed.dart';
|
||||
|
||||
class ExposureOffsetSlider extends StatelessWidget {
|
||||
|
@ -18,76 +17,14 @@ class ExposureOffsetSlider extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.sync),
|
||||
onPressed: value != 0.0 ? () => onChanged(0.0) : null,
|
||||
tooltip: S.of(context).tooltipResetToZero,
|
||||
),
|
||||
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).toStringSignedAsFixed(0),
|
||||
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(),
|
||||
return RulerSlider(
|
||||
range: range,
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
icon: Icons.light_mode,
|
||||
defaultValue: 0,
|
||||
rulerValueAdapter: (value) => value.toStringSignedAsFixed(0),
|
||||
valueAdapter: (value) => S.of(context).evValue(value.toStringSignedAsFixed(1)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/screens/shared/centered_slider/widget_slider_centered.dart';
|
||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
||||
import 'package:lightmeter/screens/shared/ruler_slider/widget_slider_ruler.dart';
|
||||
import 'package:lightmeter/utils/double_to_zoom.dart';
|
||||
|
||||
class ZoomSlider extends StatelessWidget {
|
||||
class ZoomSlider extends StatefulWidget {
|
||||
final RangeValues range;
|
||||
final double value;
|
||||
final ValueChanged<double> onChanged;
|
||||
|
@ -13,14 +15,27 @@ class ZoomSlider extends StatelessWidget {
|
|||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ZoomSlider> createState() => _ZoomSliderState();
|
||||
}
|
||||
|
||||
class _ZoomSliderState extends State<ZoomSlider> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
widget.onChanged(EquipmentProfiles.selectedOf(context).lensZoom);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CenteredSlider(
|
||||
icon: const Icon(Icons.search),
|
||||
value: value,
|
||||
min: range.start,
|
||||
max: range.end,
|
||||
onChanged: onChanged,
|
||||
return RulerSlider(
|
||||
range: widget.range,
|
||||
value: widget.value,
|
||||
onChanged: widget.onChanged,
|
||||
icon: Icons.search,
|
||||
defaultValue: EquipmentProfiles.selectedOf(context).lensZoom,
|
||||
rulerValueAdapter: (value) => value.toStringAsFixed(0),
|
||||
valueAdapter: (value) => value.toZoom(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_controls/components/exposure_offset_slider/widget_slider_exposure_offset.dart';
|
||||
import 'package:lightmeter/screens/metering/components/camera_container/components/camera_controls/components/zoom_slider/widget_slider_zoom.dart';
|
||||
|
@ -25,16 +24,14 @@ class CameraControls extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ExposureOffsetSlider(
|
||||
range: exposureOffsetRange,
|
||||
value: exposureOffsetValue,
|
||||
onChanged: onExposureOffsetChanged,
|
||||
),
|
||||
ExposureOffsetSlider(
|
||||
range: exposureOffsetRange,
|
||||
value: exposureOffsetValue,
|
||||
onChanged: onExposureOffsetChanged,
|
||||
),
|
||||
const SizedBox(height: Dimens.grid24),
|
||||
ZoomSlider(
|
||||
range: zoomRange,
|
||||
value: zoomValue,
|
||||
|
|
|
@ -157,7 +157,12 @@ class _CameraControlsBuilder extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
Dimens.paddingL,
|
||||
Dimens.paddingL,
|
||||
0,
|
||||
Dimens.paddingM,
|
||||
),
|
||||
child: BlocBuilder<CameraContainerBloc, CameraContainerState>(
|
||||
builder: (context, state) {
|
||||
late final Widget child;
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_range_picker/widget_dialog_picker_range.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class EquipmentListTiles extends StatelessWidget {
|
||||
final List<ApertureValue> selectedApertureValues;
|
||||
final List<IsoValue> selectedIsoValues;
|
||||
final List<NdValue> selectedNdValues;
|
||||
final List<ShutterSpeedValue> selectedShutterSpeedValues;
|
||||
final ValueChanged<List<ApertureValue>> onApertureValuesSelected;
|
||||
final ValueChanged<List<IsoValue>> onIsoValuesSelecred;
|
||||
final ValueChanged<List<NdValue>> onNdValuesSelected;
|
||||
final ValueChanged<List<ShutterSpeedValue>> onShutterSpeedValuesSelected;
|
||||
|
||||
const EquipmentListTiles({
|
||||
required this.selectedApertureValues,
|
||||
required this.selectedIsoValues,
|
||||
required this.selectedNdValues,
|
||||
required this.selectedShutterSpeedValues,
|
||||
required this.onApertureValuesSelected,
|
||||
required this.onIsoValuesSelecred,
|
||||
required this.onNdValuesSelected,
|
||||
required this.onShutterSpeedValuesSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_EquipmentListTile<IsoValue>(
|
||||
icon: Icons.iso,
|
||||
title: S.of(context).isoValues,
|
||||
description: S.of(context).isoValuesFilterDescription,
|
||||
values: IsoValue.values,
|
||||
selectedValues: selectedIsoValues,
|
||||
rangeSelect: false,
|
||||
onChanged: onIsoValuesSelecred,
|
||||
),
|
||||
_EquipmentListTile<NdValue>(
|
||||
icon: Icons.filter_b_and_w,
|
||||
title: S.of(context).ndFilters,
|
||||
description: S.of(context).ndFiltersFilterDescription,
|
||||
values: NdValue.values,
|
||||
selectedValues: selectedNdValues,
|
||||
rangeSelect: false,
|
||||
onChanged: onNdValuesSelected,
|
||||
),
|
||||
_EquipmentListTile<ApertureValue>(
|
||||
icon: Icons.camera,
|
||||
title: S.of(context).apertureValues,
|
||||
description: S.of(context).apertureValuesFilterDescription,
|
||||
values: ApertureValue.values,
|
||||
selectedValues: selectedApertureValues,
|
||||
rangeSelect: true,
|
||||
onChanged: onApertureValuesSelected,
|
||||
),
|
||||
_EquipmentListTile<ShutterSpeedValue>(
|
||||
icon: Icons.shutter_speed,
|
||||
title: S.of(context).shutterSpeedValues,
|
||||
description: S.of(context).shutterSpeedValuesFilterDescription,
|
||||
values: ShutterSpeedValue.values,
|
||||
selectedValues: selectedShutterSpeedValues,
|
||||
rangeSelect: true,
|
||||
onChanged: onShutterSpeedValuesSelected,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EquipmentListTile<T extends PhotographyValue> extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String description;
|
||||
final List<T> selectedValues;
|
||||
final List<T> values;
|
||||
final ValueChanged<List<T>> onChanged;
|
||||
final bool rangeSelect;
|
||||
|
||||
const _EquipmentListTile({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.selectedValues,
|
||||
required this.values,
|
||||
required this.onChanged,
|
||||
required this.rangeSelect,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: Icon(icon),
|
||||
title: Text(title),
|
||||
trailing: rangeSelect
|
||||
? Text("${selectedValues.first} - ${selectedValues.last}")
|
||||
: Text(
|
||||
values.length == selectedValues.length
|
||||
? S.of(context).equipmentProfileAllValues
|
||||
: selectedValues.length.toString(),
|
||||
),
|
||||
onTap: () {
|
||||
showDialog<List<T>>(
|
||||
context: context,
|
||||
builder: (_) => rangeSelect
|
||||
? DialogRangePicker<T>(
|
||||
icon: Icon(icon),
|
||||
title: title,
|
||||
description: description,
|
||||
values: values,
|
||||
selectedValues: selectedValues,
|
||||
titleAdapter: (_, value) => value.toString(),
|
||||
)
|
||||
: DialogFilter<T>(
|
||||
icon: Icon(icon),
|
||||
title: title,
|
||||
description: description,
|
||||
values: values,
|
||||
selectedValues: selectedValues,
|
||||
titleAdapter: (_, value) => value.toString(),
|
||||
),
|
||||
).then((values) {
|
||||
if (values != null) {
|
||||
onChanged(values);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_filter/widget_dialog_filter.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class FilterListTile<T extends PhotographyValue> extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String description;
|
||||
final List<T> selectedValues;
|
||||
final List<T> values;
|
||||
final ValueChanged<List<T>> onChanged;
|
||||
|
||||
const FilterListTile({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.selectedValues,
|
||||
required this.values,
|
||||
required this.onChanged,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: Icon(icon),
|
||||
title: Text(title),
|
||||
trailing: Text(
|
||||
values.length == selectedValues.length
|
||||
? S.of(context).equipmentProfileAllValues
|
||||
: selectedValues.length.toString(),
|
||||
),
|
||||
onTap: () {
|
||||
showDialog<List<T>>(
|
||||
context: context,
|
||||
builder: (_) => DialogFilter<T>(
|
||||
icon: Icon(icon),
|
||||
title: title,
|
||||
description: description,
|
||||
values: values,
|
||||
selectedValues: selectedValues,
|
||||
titleAdapter: (_, value) => value.toString(),
|
||||
),
|
||||
).then((values) {
|
||||
if (values != null) {
|
||||
onChanged(values);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_range_picker/widget_dialog_picker_range.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class RangePickerListTile<T extends PhotographyValue> extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String description;
|
||||
final List<T> selectedValues;
|
||||
final List<T> values;
|
||||
final ValueChanged<List<T>> onChanged;
|
||||
|
||||
const RangePickerListTile({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.selectedValues,
|
||||
required this.values,
|
||||
required this.onChanged,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: Icon(icon),
|
||||
title: Text(title),
|
||||
trailing: Text("${selectedValues.first} - ${selectedValues.last}"),
|
||||
onTap: () {
|
||||
showDialog<List<T>>(
|
||||
context: context,
|
||||
builder: (_) => DialogRangePicker<T>(
|
||||
icon: Icon(icon),
|
||||
title: title,
|
||||
description: description,
|
||||
values: values,
|
||||
selectedValues: selectedValues,
|
||||
titleAdapter: (_, value) => value.toString(),
|
||||
),
|
||||
).then((values) {
|
||||
if (values != null) {
|
||||
onChanged(values);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_slider_picker/widget_dialog_slider_picker.dart';
|
||||
|
||||
class SliderPickerListTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String description;
|
||||
final double value;
|
||||
final RangeValues range;
|
||||
final ValueChanged<double> onChanged;
|
||||
final String Function(BuildContext, double) valueAdapter;
|
||||
|
||||
const SliderPickerListTile({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.value,
|
||||
required this.range,
|
||||
required this.onChanged,
|
||||
required this.valueAdapter,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: Icon(icon),
|
||||
title: Text(title),
|
||||
trailing: Text(valueAdapter(context, value)),
|
||||
onTap: () {
|
||||
showDialog<double>(
|
||||
context: context,
|
||||
builder: (_) => DialogSliderPicker(
|
||||
icon: Icon(icon),
|
||||
title: title,
|
||||
description: description,
|
||||
value: value,
|
||||
range: range,
|
||||
valueAdapter: valueAdapter,
|
||||
),
|
||||
).then((value) {
|
||||
if (value != null) {
|
||||
onChanged(value);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,8 +4,11 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/components/equipment_list_tiles/widget_list_tiles_equipments.dart';
|
||||
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/components/filter_list_tile/widget_list_tile_filter.dart';
|
||||
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/components/range_picker_list_tile/widget_list_tile_range_picker.dart';
|
||||
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/components/slider_picker_list_tile/widget_list_tile_slider_picker.dart';
|
||||
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart';
|
||||
import 'package:lightmeter/utils/double_to_zoom.dart';
|
||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||
|
||||
class EquipmentProfileContainer extends StatefulWidget {
|
||||
|
@ -28,8 +31,7 @@ class EquipmentProfileContainer extends StatefulWidget {
|
|||
State<EquipmentProfileContainer> createState() => EquipmentProfileContainerState();
|
||||
}
|
||||
|
||||
class EquipmentProfileContainerState extends State<EquipmentProfileContainer>
|
||||
with TickerProviderStateMixin {
|
||||
class EquipmentProfileContainerState extends State<EquipmentProfileContainer> with TickerProviderStateMixin {
|
||||
late EquipmentProfile _equipmentData = EquipmentProfile(
|
||||
id: widget.data.id,
|
||||
name: widget.data.name,
|
||||
|
@ -37,6 +39,7 @@ class EquipmentProfileContainerState extends State<EquipmentProfileContainer>
|
|||
ndValues: widget.data.ndValues,
|
||||
shutterSpeedValues: widget.data.shutterSpeedValues,
|
||||
isoValues: widget.data.isoValues,
|
||||
lensZoom: widget.data.lensZoom,
|
||||
);
|
||||
|
||||
late final AnimationController _controller = AnimationController(
|
||||
|
@ -55,6 +58,7 @@ class EquipmentProfileContainerState extends State<EquipmentProfileContainer>
|
|||
ndValues: widget.data.ndValues,
|
||||
shutterSpeedValues: widget.data.shutterSpeedValues,
|
||||
isoValues: widget.data.isoValues,
|
||||
lensZoom: widget.data.lensZoom,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -113,6 +117,10 @@ class EquipmentProfileContainerState extends State<EquipmentProfileContainer>
|
|||
_equipmentData = _equipmentData.copyWith(shutterSpeedValues: value);
|
||||
widget.onUpdate(_equipmentData);
|
||||
},
|
||||
onLensZoomChanged: (value) {
|
||||
_equipmentData = _equipmentData.copyWith(lensZoom: value);
|
||||
widget.onUpdate(_equipmentData);
|
||||
},
|
||||
onCopy: widget.onCopy,
|
||||
onDelete: widget.onDelete,
|
||||
),
|
||||
|
@ -154,8 +162,7 @@ class EquipmentProfileContainerState extends State<EquipmentProfileContainer>
|
|||
}
|
||||
|
||||
class _AnimatedNameLeading extends AnimatedWidget {
|
||||
const _AnimatedNameLeading({required AnimationController controller})
|
||||
: super(listenable: controller);
|
||||
const _AnimatedNameLeading({required AnimationController controller}) : super(listenable: controller);
|
||||
|
||||
Animation<double> get _progress => listenable as Animation<double>;
|
||||
|
||||
|
@ -200,6 +207,7 @@ class _AnimatedEquipmentListTiles extends AnimatedWidget {
|
|||
final ValueChanged<List<IsoValue>> onIsoValuesSelecred;
|
||||
final ValueChanged<List<NdValue>> onNdValuesSelected;
|
||||
final ValueChanged<List<ShutterSpeedValue>> onShutterSpeedValuesSelected;
|
||||
final ValueChanged<double> onLensZoomChanged;
|
||||
final VoidCallback onCopy;
|
||||
final VoidCallback onDelete;
|
||||
|
||||
|
@ -210,6 +218,7 @@ class _AnimatedEquipmentListTiles extends AnimatedWidget {
|
|||
required this.onIsoValuesSelecred,
|
||||
required this.onNdValuesSelected,
|
||||
required this.onShutterSpeedValuesSelected,
|
||||
required this.onLensZoomChanged,
|
||||
required this.onCopy,
|
||||
required this.onDelete,
|
||||
}) : super(listenable: controller);
|
||||
|
@ -222,22 +231,53 @@ class _AnimatedEquipmentListTiles extends AnimatedWidget {
|
|||
alignment: Alignment.topCenter,
|
||||
size: Size(
|
||||
double.maxFinite,
|
||||
_progress.value * Dimens.grid56 * 5,
|
||||
_progress.value * Dimens.grid56 * 6,
|
||||
),
|
||||
// https://github.com/gskinnerTeam/flutter-folio/pull/62
|
||||
child: Opacity(
|
||||
opacity: _progress.value,
|
||||
child: Column(
|
||||
children: [
|
||||
EquipmentListTiles(
|
||||
selectedApertureValues: equipmentData.apertureValues,
|
||||
selectedIsoValues: equipmentData.isoValues,
|
||||
selectedNdValues: equipmentData.ndValues,
|
||||
selectedShutterSpeedValues: equipmentData.shutterSpeedValues,
|
||||
onApertureValuesSelected: onApertureValuesSelected,
|
||||
onIsoValuesSelecred: onIsoValuesSelecred,
|
||||
onNdValuesSelected: onNdValuesSelected,
|
||||
onShutterSpeedValuesSelected: onShutterSpeedValuesSelected,
|
||||
FilterListTile<IsoValue>(
|
||||
icon: Icons.iso,
|
||||
title: S.of(context).isoValues,
|
||||
description: S.of(context).isoValuesFilterDescription,
|
||||
values: IsoValue.values,
|
||||
selectedValues: equipmentData.isoValues,
|
||||
onChanged: onIsoValuesSelecred,
|
||||
),
|
||||
FilterListTile<NdValue>(
|
||||
icon: Icons.filter_b_and_w,
|
||||
title: S.of(context).ndFilters,
|
||||
description: S.of(context).ndFiltersFilterDescription,
|
||||
values: NdValue.values,
|
||||
selectedValues: equipmentData.ndValues,
|
||||
onChanged: onNdValuesSelected,
|
||||
),
|
||||
RangePickerListTile<ApertureValue>(
|
||||
icon: Icons.camera,
|
||||
title: S.of(context).apertureValues,
|
||||
description: S.of(context).apertureValuesFilterDescription,
|
||||
values: ApertureValue.values,
|
||||
selectedValues: equipmentData.apertureValues,
|
||||
onChanged: onApertureValuesSelected,
|
||||
),
|
||||
RangePickerListTile<ShutterSpeedValue>(
|
||||
icon: Icons.shutter_speed,
|
||||
title: S.of(context).shutterSpeedValues,
|
||||
description: S.of(context).shutterSpeedValuesFilterDescription,
|
||||
values: ShutterSpeedValue.values,
|
||||
selectedValues: equipmentData.shutterSpeedValues,
|
||||
onChanged: onShutterSpeedValuesSelected,
|
||||
),
|
||||
SliderPickerListTile(
|
||||
icon: Icons.zoom_in,
|
||||
title: S.of(context).lensZoom,
|
||||
description: S.of(context).lensZoomDescription,
|
||||
value: equipmentData.lensZoom,
|
||||
range: const RangeValues(1, 7),
|
||||
onChanged: onLensZoomChanged,
|
||||
valueAdapter: (_, value) => value.toZoom(),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM),
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
class DialogSliderPicker extends StatefulWidget {
|
||||
final Icon icon;
|
||||
final String title;
|
||||
final String description;
|
||||
final double value;
|
||||
final RangeValues range;
|
||||
final String Function(BuildContext context, double value) valueAdapter;
|
||||
|
||||
const DialogSliderPicker({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.value,
|
||||
required this.range,
|
||||
required this.valueAdapter,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DialogSliderPicker> createState() => _DialogSliderPickerState();
|
||||
}
|
||||
|
||||
class _DialogSliderPickerState extends State<DialogSliderPicker> {
|
||||
late double value = widget.value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
icon: widget.icon,
|
||||
titlePadding: Dimens.dialogIconTitlePadding,
|
||||
title: Text(widget.title),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: Dimens.dialogIconTitlePadding,
|
||||
child: Text(widget.description),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL),
|
||||
child: Text(
|
||||
widget.valueAdapter(context, value),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: value,
|
||||
min: widget.range.start,
|
||||
max: widget.range.end,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
this.value = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actionsPadding: Dimens.dialogActionsPadding,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: Text(S.of(context).cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(value),
|
||||
child: Text(S.of(context).save),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -41,12 +41,12 @@ class _CenteredSliderState extends State<CenteredSlider> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: widget.isVertical ? double.maxFinite : Dimens.cameraSliderHandleSize,
|
||||
width: !widget.isVertical ? double.maxFinite : Dimens.cameraSliderHandleSize,
|
||||
height: widget.isVertical ? double.maxFinite : Dimens.cameraSliderHandleArea,
|
||||
width: !widget.isVertical ? double.maxFinite : Dimens.cameraSliderHandleArea,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final biggestSize = widget.isVertical ? constraints.maxHeight : constraints.maxWidth;
|
||||
final handleDistance = biggestSize - Dimens.cameraSliderHandleSize;
|
||||
final handleDistance = biggestSize - Dimens.cameraSliderHandleArea;
|
||||
return RotatedBox(
|
||||
quarterTurns: widget.isVertical ? -1 : 0,
|
||||
child: GestureDetector(
|
||||
|
@ -60,11 +60,11 @@ class _CenteredSliderState extends State<CenteredSlider> {
|
|||
handleDistance,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: Dimens.cameraSliderHandleSize,
|
||||
height: Dimens.cameraSliderHandleArea,
|
||||
width: biggestSize,
|
||||
child: _Slider(
|
||||
handleDistance: handleDistance,
|
||||
handleSize: Dimens.cameraSliderHandleSize,
|
||||
handleSize: Dimens.cameraSliderHandleArea,
|
||||
trackThickness: Dimens.cameraSliderTrackHeight,
|
||||
value: relativeValue,
|
||||
icon: RotatedBox(
|
||||
|
@ -81,12 +81,12 @@ class _CenteredSliderState extends State<CenteredSlider> {
|
|||
}
|
||||
|
||||
void _updateHandlePosition(double offset, double handleDistance) {
|
||||
if (offset <= Dimens.cameraSliderHandleSize / 2) {
|
||||
if (offset <= Dimens.cameraSliderHandleArea / 2) {
|
||||
relativeValue = 0;
|
||||
} else if (offset >= handleDistance + Dimens.cameraSliderHandleSize / 2) {
|
||||
} else if (offset >= handleDistance + Dimens.cameraSliderHandleArea / 2) {
|
||||
relativeValue = 1;
|
||||
} else {
|
||||
relativeValue = (offset - Dimens.cameraSliderHandleSize / 2) / handleDistance;
|
||||
relativeValue = (offset - Dimens.cameraSliderHandleArea / 2) / handleDistance;
|
||||
}
|
||||
setState(() {});
|
||||
widget.onChanged(relativeValue * (widget.max - widget.min) + widget.min);
|
||||
|
@ -124,7 +124,7 @@ class _Slider extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
AnimatedPositioned.fromRect(
|
||||
duration: Dimens.durationM,
|
||||
duration: Dimens.durationS,
|
||||
rect: Rect.fromCenter(
|
||||
center: Offset(
|
||||
handleSize / 2 + handleDistance * value,
|
||||
|
@ -133,15 +133,17 @@ class _Slider extends StatelessWidget {
|
|||
width: handleSize,
|
||||
height: handleSize,
|
||||
),
|
||||
child: _Handle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: handleSize,
|
||||
child: IconTheme(
|
||||
data: Theme.of(context).iconTheme.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
size: Dimens.cameraSliderHandleIconSize,
|
||||
),
|
||||
child: icon,
|
||||
child: Center(
|
||||
child: _Handle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: Dimens.cameraSliderHandleSize,
|
||||
child: IconTheme(
|
||||
data: Theme.of(context).iconTheme.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
size: Dimens.cameraSliderHandleIconSize,
|
||||
),
|
||||
child: icon,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -163,15 +165,15 @@ class _Handle extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(size / 2),
|
||||
child: SizedBox(
|
||||
height: size,
|
||||
width: size,
|
||||
child: ColoredBox(
|
||||
return SizedBox(
|
||||
height: size,
|
||||
width: size,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
child: child,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
121
lib/screens/shared/ruler_slider/widget_slider_ruler.dart
Normal file
121
lib/screens/shared/ruler_slider/widget_slider_ruler.dart
Normal file
|
@ -0,0 +1,121 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
import 'package:lightmeter/screens/shared/centered_slider/widget_slider_centered.dart';
|
||||
|
||||
class RulerSlider extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final RangeValues range;
|
||||
final double value;
|
||||
final double defaultValue;
|
||||
final ValueChanged<double> onChanged;
|
||||
final String Function(double value) rulerValueAdapter;
|
||||
final String Function(double value) valueAdapter;
|
||||
|
||||
const RulerSlider({
|
||||
required this.icon,
|
||||
required this.range,
|
||||
required this.value,
|
||||
required this.defaultValue,
|
||||
required this.onChanged,
|
||||
required this.rulerValueAdapter,
|
||||
required this.valueAdapter,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
valueAdapter(value),
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
const SizedBox(height: Dimens.grid4),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
_Ruler(
|
||||
range.start,
|
||||
range.end,
|
||||
rulerValueAdapter,
|
||||
),
|
||||
CenteredSlider(
|
||||
isVertical: true,
|
||||
icon: Icon(icon),
|
||||
value: value,
|
||||
min: range.start,
|
||||
max: range.end,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.sync),
|
||||
onPressed: value != defaultValue ? () => onChanged(defaultValue) : null,
|
||||
tooltip: S.of(context).tooltipResetToZero,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Ruler extends StatelessWidget {
|
||||
final double min;
|
||||
final double max;
|
||||
final String Function(double value) rulerValueAdapter;
|
||||
late final int mainTicksCount = (max - min + 1).toInt();
|
||||
late final int itemsCount = mainTicksCount * 2 - 1;
|
||||
|
||||
_Ruler(
|
||||
this.min,
|
||||
this.max,
|
||||
this.rulerValueAdapter,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final bool showAllMainTicks = Dimens.cameraSliderHandleArea * mainTicksCount <= constraints.maxHeight;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(
|
||||
itemsCount,
|
||||
(index) {
|
||||
final bool isMainTick = index % 2 == 0.0;
|
||||
if (!showAllMainTicks && !isMainTick) {
|
||||
return const SizedBox();
|
||||
}
|
||||
final bool showValue = (index % (showAllMainTicks ? 2 : 4) == 0.0);
|
||||
return SizedBox(
|
||||
height: index == itemsCount - 1 || showValue ? Dimens.cameraSliderHandleArea : 1,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (showValue)
|
||||
Text(
|
||||
rulerValueAdapter(index / 2 + min),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(width: Dimens.grid4),
|
||||
ColoredBox(
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
child: SizedBox(
|
||||
height: 1,
|
||||
width: isMainTick ? Dimens.grid8 : Dimens.grid4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
).reversed.toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
3
lib/utils/double_to_zoom.dart
Normal file
3
lib/utils/double_to_zoom.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
extension DoubleToZoom on double {
|
||||
String toZoom() => 'x${toStringAsFixed(2)}';
|
||||
}
|
Loading…
Reference in a new issue