implemented LogbookPhotoEditScreen

This commit is contained in:
Vadim 2025-07-10 00:08:57 +02:00
parent a517a28daf
commit 4e7c080b97
16 changed files with 603 additions and 11 deletions

3
devtools_options.yaml Normal file
View file

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View file

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View file

@ -12,7 +12,10 @@ import 'package:lightmeter/screens/equipment_profiles/screen_equipment_profiles.
import 'package:lightmeter/screens/film_edit/flow_film_edit.dart';
import 'package:lightmeter/screens/films/screen_films.dart';
import 'package:lightmeter/screens/lightmeter_pro/screen_lightmeter_pro.dart';
import 'package:lightmeter/screens/logbook/screen_logbook.dart';
import 'package:lightmeter/screens/logbook_photo_edit/flow_logbook_photo_edit.dart';
import 'package:lightmeter/screens/metering/flow_metering.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
import 'package:lightmeter/screens/settings/flow_settings.dart';
import 'package:lightmeter/screens/shared/release_notes_dialog/flow_dialog_release_notes.dart';
import 'package:lightmeter/screens/timer/flow_timer.dart';
@ -51,13 +54,15 @@ class Application extends StatelessWidget {
routes: {
NavigationRoutes.meteringScreen.name: (_) => const ReleaseNotesFlow(child: MeteringFlow()),
NavigationRoutes.settingsScreen.name: (_) => const SettingsFlow(),
NavigationRoutes.equipmentProfilesListScreen.name: (_) => const EquipmentProfilesScreen(),
NavigationRoutes.equipmentProfilesListScreen.name: (_) => const LogbookScreen(),
NavigationRoutes.equipmentProfileEditScreen.name: (context) =>
EquipmentProfileEditFlow(args: context.routeArgs<EquipmentProfileEditArgs>()),
NavigationRoutes.filmsListScreen.name: (_) => const FilmsScreen(),
NavigationRoutes.filmEditScreen.name: (context) => FilmEditFlow(args: context.routeArgs<FilmEditArgs>()),
NavigationRoutes.proFeaturesScreen.name: (_) => LightmeterProScreen(),
NavigationRoutes.timerScreen.name: (context) => TimerFlow(args: context.routeArgs<TimerFlowArgs>()),
NavigationRoutes.logbookPhotoEditScreen.name: (context) =>
LogbookPhotoEditFlow(args: LogbookPhotoEditArgs(photo: context.routeArgs<LogbookPhoto>())),
},
),
);

View file

@ -55,10 +55,12 @@
"equipmentProfileNameHint": "Praktica MTL5B",
"equipmentProfileAllValues": "Alle",
"apertureValues": "Blend-Werte",
"apertureValue": "Blend-Wert",
"apertureValuesFilterDescription": "Wähle die anzuzeigenden Blend-Werte aus. Die Werte sind normalerweise von dem verwendeten Objektiv bestimmt.",
"ndFilters": "ND Filter",
"ndFiltersFilterDescription": "Wähle die anzuzeigenden ND Filter aus. (Beispielsweise die Meistverwendeten)",
"shutterSpeedValues": "Belichtungszeiten",
"shutterSpeedValue": "Belichtungszeit",
"shutterSpeedValuesFilterDescription": "Wähle die anzuzeigenden Belichtungszeiten aus. Die Werte sind normalerweise von der Kamera bestimmt.",
"shutterSpeedManualShort": "B",
"shutterSpeedManual": "Manuell",
@ -163,5 +165,11 @@
"filmFormulaExponentialRfPlaceholder": "1.3",
"name": "Name",
"addEquipmentProfileTitle": "Ausrüstung hinzufügen",
"editEquipmentProfileTitle": "Ausrüstung bearbeiten"
"editEquipmentProfileTitle": "Ausrüstung bearbeiten",
"editPhotoTitle": "Foto bearbeiten",
"date": "Datum",
"ndFilter": "ND Filter",
"film": "Film",
"note": "Notiz",
"notSet": "Nicht gesetzt"
}

View file

@ -55,10 +55,12 @@
"equipmentProfileNameHint": "Praktica MTL5B",
"equipmentProfileAllValues": "All",
"apertureValues": "Aperture values",
"apertureValue": "Aperture value",
"apertureValuesFilterDescription": "Select the range of aperture values to display. This is usually determined by the lens you are using.",
"ndFilters": "ND filters",
"ndFiltersFilterDescription": "Select the ND filters to display. These may be your most commonly used ND filters or the ones that fit your lens.",
"shutterSpeedValues": "Shutter speed values",
"shutterSpeedValue": "Shutter speed value",
"shutterSpeedValuesFilterDescription": "Select the range of shutter speed values to display. This is usually determined by the camera body you are using.",
"shutterSpeedManualShort": "B",
"shutterSpeedManual": "Manual",
@ -163,5 +165,11 @@
"filmFormulaExponentialRfPlaceholder": "1.3",
"name": "Name",
"addEquipmentProfileTitle": "Add equipment",
"editEquipmentProfileTitle": "Edit equipment"
"editEquipmentProfileTitle": "Edit equipment",
"editPhotoTitle": "Edit Photo",
"date": "Date",
"ndFilter": "ND Filter",
"film": "Film",
"note": "Note",
"notSet": "Not set"
}

View file

@ -55,10 +55,12 @@
"equipmentProfileNameHint": "Praktica MTL5B",
"equipmentProfileAllValues": "Tout",
"apertureValues": "Valeurs Aperture",
"apertureValue": "Valeur Aperture",
"apertureValuesFilterDescription": "Sélectionnez la plage de valeurs d'ouverture à afficher. Cela est généralement déterminé par l'objectif que vous utilisez.",
"ndFilters": "Filtres ND",
"ndFiltersFilterDescription": "Sélectionnez les filtres ND à afficher. Ce sont peut-être vos filtres ND les plus couramment utilisés ou ceux qui correspondent à votre lentille.",
"shutterSpeedValues": "Valeurs de la vitesse d'obturation",
"shutterSpeedValue": "Valeur de la vitesse d'obturation",
"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.",
"shutterSpeedManualShort": "B",
"shutterSpeedManual": "Manuelle",
@ -154,5 +156,11 @@
"filmFormulaExponentialRfPlaceholder": "1.3",
"name": "Titre",
"addEquipmentProfileTitle": "Ajouter un profil",
"editEquipmentProfileTitle": "Editer le profil"
"editEquipmentProfileTitle": "Editer le profil",
"editPhotoTitle": "Modifier la photo",
"date": "Date",
"ndFilter": "Filtre ND",
"film": "Film",
"note": "Note",
"notSet": "Non défini"
}

View file

@ -55,10 +55,12 @@
"equipmentProfileNameHint": "Praktica MTL5B",
"equipmentProfileAllValues": "Все",
"apertureValues": "Значения диафрагмы",
"apertureValue": "Значение диафрагмы",
"apertureValuesFilterDescription": "Выберите диапазон значений диафрагмы для отображения. Обычно определяется объективом, который вы используете.",
"ndFilters": "ND фильтры",
"ndFiltersFilterDescription": "Выберите ND фильтры для отображения. Это могут быть наиболее часто используемые ND фильтры или фильтры, подходящие под ваш объектив.",
"shutterSpeedValues": "Значения выдержки",
"shutterSpeedValue": "Значение выдержки",
"shutterSpeedValuesFilterDescription": "Выберите диапазон значений выдержки. Обычно ограничивается возможностями вашей камеры.",
"shutterSpeedManualShort": "B",
"shutterSpeedManual": "Ручная",
@ -153,5 +155,11 @@
"filmFormulaExponentialRfPlaceholder": "1.3",
"name": "Название",
"addEquipmentProfileTitle": "Добавить профиль",
"editEquipmentProfileTitle": "Редактировать профиль"
"editEquipmentProfileTitle": "Редактировать профиль",
"editPhotoTitle": "Редактировать фото",
"date": "Дата",
"ndFilter": "ND фильтр",
"film": "Плёнка",
"note": "Заметка",
"notSet": "Не задано"
}

View file

@ -55,10 +55,12 @@
"equipmentProfileNameHint": "Praktica MTL5B",
"equipmentProfileAllValues": "全部",
"apertureValues": "光圈值",
"apertureValue": "光圈值",
"apertureValuesFilterDescription": "选择要显示的光圈值范围。取决于使用的镜头。",
"ndFilters": "ND 滤镜",
"ndFiltersFilterDescription": "选择要显示的 ND 滤镜系数。取决于使用的 ND 滤镜",
"shutterSpeedValues": "快门速度",
"shutterSpeedValue": "快门速度",
"shutterSpeedValuesFilterDescription": "选择要显示的快门速度范围。取决于相机机身。",
"shutterSpeedManualShort": "B门",
"shutterSpeedManual": "手动",
@ -151,5 +153,11 @@
"filmFormulaExponentialRf": "Rf",
"filmFormulaExponentialRfPlaceholder": "1.3",
"addEquipmentProfileTitle": "添加设备",
"editEquipmentProfileTitle": "编辑设备"
"editEquipmentProfileTitle": "编辑设备",
"editPhotoTitle": "编辑照片",
"date": "日期",
"ndFilter": "ND 滤镜",
"film": "胶片",
"note": "备注",
"notSet": "未设置"
}

View file

@ -7,4 +7,5 @@ enum NavigationRoutes {
filmEditScreen,
proFeaturesScreen,
timerScreen,
logbookPhotoEditScreen,
}

View file

@ -36,7 +36,12 @@ class _LogbookScreenState extends State<LogbookScreen> with SingleTickerProvider
);
}
void _editProfile(LogbookPhoto photo) {}
void _editProfile(LogbookPhoto photo) {
Navigator.of(context).pushNamed(
NavigationRoutes.logbookPhotoEditScreen.name,
arguments: photo,
);
}
}
class _PicturesGridBuilder extends StatelessWidget {
@ -62,10 +67,13 @@ class _PicturesGridBuilder extends StatelessWidget {
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
return GestureDetector(
onTap: () => onEdit(values[index]),
child: Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: Image.file(File(values[index].name)),
),
);
},
childCount: values.length,

View file

@ -0,0 +1,96 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/providers/logbook_photos_provider.dart';
import 'package:lightmeter/screens/logbook_photo_edit/event_logbook_photo_edit.dart';
import 'package:lightmeter/screens/logbook_photo_edit/state_logbook_photo_edit.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class LogbookPhotoEditBloc extends Bloc<LogbookPhotoEditEvent, LogbookPhotoEditState> {
final LogbookPhotosProviderState photosProvider;
final LogbookPhoto _originalPhoto;
LogbookPhoto _newPhoto;
LogbookPhotoEditBloc(
this.photosProvider,
LogbookPhoto photo,
) : _originalPhoto = photo,
_newPhoto = photo,
super(
LogbookPhotoEditState(
id: photo.id,
name: photo.name,
timestamp: photo.timestamp,
ev: photo.ev,
iso: photo.iso,
nd: photo.nd,
film: photo.film,
coordinates: photo.coordinates,
aperture: null,
shutterSpeed: null,
note: photo.note,
canSave: false,
),
) {
on<LogbookPhotoEditEvent>(
(event, emit) async {
switch (event) {
case final LogbookPhotoApertureChangedEvent e:
await _onApertureChanged(e, emit);
case final LogbookPhotoShutterSpeedChangedEvent e:
await _onShutterSpeedChanged(e, emit);
case final LogbookPhotoNoteChangedEvent e:
await _onNoteChanged(e, emit);
case LogbookPhotoSaveEvent():
await _onSave(event, emit);
case LogbookPhotoDeleteEvent():
await _onDelete(event, emit);
}
},
);
}
Future<void> _onApertureChanged(LogbookPhotoApertureChangedEvent event, Emitter emit) async {
// For now, we'll just update the state since LogbookPhoto doesn't support aperture
emit(
state.copyWith(
aperture: event.aperture,
canSave: _canSave(),
),
);
}
Future<void> _onShutterSpeedChanged(LogbookPhotoShutterSpeedChangedEvent event, Emitter emit) async {
// For now, we'll just update the state since LogbookPhoto doesn't support shutterSpeed
emit(
state.copyWith(
shutterSpeed: event.shutterSpeed,
canSave: _canSave(),
),
);
}
Future<void> _onNoteChanged(LogbookPhotoNoteChangedEvent event, Emitter emit) async {
_newPhoto = _newPhoto.copyWith(note: event.note);
emit(
state.copyWith(
note: event.note,
canSave: _canSave(),
),
);
}
Future<void> _onSave(LogbookPhotoSaveEvent _, Emitter emit) async {
emit(state.copyWith(isLoading: true));
await photosProvider.updateProfile(_newPhoto);
emit(state.copyWith(isLoading: false));
}
Future<void> _onDelete(LogbookPhotoDeleteEvent _, Emitter emit) async {
emit(state.copyWith(isLoading: true));
await photosProvider.deleteProfile(_newPhoto);
emit(state.copyWith(isLoading: false));
}
bool _canSave() {
return _newPhoto != _originalPhoto;
}
}

View file

@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/screens/settings/components/shared/dialog_picker/widget_dialog_picker.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class PickerListTile<T extends PhotographyValue> extends StatelessWidget {
final IconData icon;
final String title;
final T? selectedValue;
final List<T> values;
final ValueChanged<T?> onChanged;
const PickerListTile({
required this.icon,
required this.title,
required this.selectedValue,
required this.values,
required this.onChanged,
super.key,
});
@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(icon),
title: Text(title),
trailing: Text(selectedValue?.toString() ?? S.of(context).notSet),
onTap: () {
showDialog<T?>(
context: context,
builder: (_) => DialogPicker<T?>(
icon: icon,
title: title,
selectedValue: selectedValue,
values: [null, ...values],
titleAdapter: (context, value) => value?.toString() ?? S.of(context).notSet,
),
).then((value) {
if (value != null) {
onChanged(value);
}
});
},
);
}
}

View file

@ -0,0 +1,31 @@
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
sealed class LogbookPhotoEditEvent {
const LogbookPhotoEditEvent();
}
class LogbookPhotoApertureChangedEvent extends LogbookPhotoEditEvent {
final ApertureValue? aperture;
const LogbookPhotoApertureChangedEvent(this.aperture);
}
class LogbookPhotoShutterSpeedChangedEvent extends LogbookPhotoEditEvent {
final ShutterSpeedValue? shutterSpeed;
const LogbookPhotoShutterSpeedChangedEvent(this.shutterSpeed);
}
class LogbookPhotoNoteChangedEvent extends LogbookPhotoEditEvent {
final String? note;
const LogbookPhotoNoteChangedEvent(this.note);
}
class LogbookPhotoSaveEvent extends LogbookPhotoEditEvent {
const LogbookPhotoSaveEvent();
}
class LogbookPhotoDeleteEvent extends LogbookPhotoEditEvent {
const LogbookPhotoDeleteEvent();
}

View file

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/providers/logbook_photos_provider.dart';
import 'package:lightmeter/screens/logbook_photo_edit/bloc_logbook_photo_edit.dart';
import 'package:lightmeter/screens/logbook_photo_edit/screen_logbook_photo_edit.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class LogbookPhotoEditArgs {
final LogbookPhoto photo;
const LogbookPhotoEditArgs({required this.photo});
}
class LogbookPhotoEditFlow extends StatelessWidget {
final LogbookPhotoEditArgs args;
const LogbookPhotoEditFlow({
required this.args,
super.key,
});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => LogbookPhotoEditBloc(
LogbookPhotosProvider.of(context),
args.photo,
),
child: const LogbookPhotoEditScreen(),
);
}
}

View file

@ -0,0 +1,263 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/platform_config.dart';
import 'package:lightmeter/res/dimens.dart';
import 'package:lightmeter/screens/logbook_photo_edit/bloc_logbook_photo_edit.dart';
import 'package:lightmeter/screens/logbook_photo_edit/components/picker_list_tile/widget_list_tile_picker.dart';
import 'package:lightmeter/screens/logbook_photo_edit/event_logbook_photo_edit.dart';
import 'package:lightmeter/screens/logbook_photo_edit/state_logbook_photo_edit.dart';
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
import 'package:lightmeter/screens/shared/text_field/widget_text_field.dart';
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class LogbookPhotoEditScreen extends StatefulWidget {
const LogbookPhotoEditScreen({super.key});
@override
State<LogbookPhotoEditScreen> createState() => _LogbookPhotoEditScreenState();
}
class _LogbookPhotoEditScreenState extends State<LogbookPhotoEditScreen> {
@override
Widget build(BuildContext context) {
return BlocConsumer<LogbookPhotoEditBloc, LogbookPhotoEditState>(
listenWhen: (previous, current) => previous.isLoading != current.isLoading,
listener: (context, state) {
if (state.isLoading) {
FocusScope.of(context).unfocus();
} else {
Navigator.of(context).pop();
}
},
buildWhen: (previous, current) => previous.isLoading != current.isLoading,
builder: (context, state) => IgnorePointer(
ignoring: state.isLoading,
child: SliverScreen(
title: Text(S.of(context).editPhotoTitle),
appBarActions: [
BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
buildWhen: (previous, current) => previous.canSave != current.canSave,
builder: (context, state) => IconButton(
onPressed: state.canSave
? () {
context.read<LogbookPhotoEditBloc>().add(const LogbookPhotoSaveEvent());
}
: null,
icon: const Icon(Icons.save_outlined),
),
),
IconButton(
onPressed: () {
context.read<LogbookPhotoEditBloc>().add(const LogbookPhotoDeleteEvent());
},
icon: const Icon(Icons.delete_outlined),
),
],
slivers: [
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(
Dimens.paddingM,
0,
Dimens.paddingM,
Dimens.paddingM,
),
child: Column(
children: [
const _PhotoPreviewBuilder(),
const SizedBox(height: Dimens.grid16),
Card(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
child: Opacity(
opacity: state.isLoading ? Dimens.disabledOpacity : Dimens.enabledOpacity,
child: const Column(
children: [
_DateListTile(),
_NoteListTile(),
_EvListTile(),
_IsoListTile(),
_NdFilterListTile(),
_AperturePickerListTile(),
_ShutterSpeedPickerListTile(),
],
),
),
),
),
],
),
),
),
SliverToBoxAdapter(child: SizedBox(height: MediaQuery.paddingOf(context).bottom)),
],
),
),
);
}
}
class _PhotoPreviewBuilder extends StatelessWidget {
const _PhotoPreviewBuilder();
@override
Widget build(BuildContext context) {
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
buildWhen: (_, __) => false,
builder: (context, state) => AspectRatio(
aspectRatio: PlatformConfig.cameraPreviewAspectRatio,
child: ClipRRect(
borderRadius: BorderRadius.circular(Dimens.borderRadiusM),
child: Image.file(
File(state.name),
fit: BoxFit.cover,
),
),
),
);
}
}
class _DateListTile extends StatelessWidget {
const _DateListTile();
String _formatDate(DateTime dateTime) {
final day = dateTime.day.toString().padLeft(2, '0');
final month = dateTime.month.toString().padLeft(2, '0');
final year = dateTime.year.toString();
final hour = dateTime.hour.toString().padLeft(2, '0');
final minute = dateTime.minute.toString().padLeft(2, '0');
return '$day.$month.$year $hour:$minute';
}
@override
Widget build(BuildContext context) {
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
buildWhen: (_, __) => false,
builder: (context, state) => ListTile(
leading: const Icon(Icons.access_time),
title: Text(S.of(context).date),
trailing: Text(_formatDate(state.timestamp)),
),
);
}
}
class _NoteListTile extends StatelessWidget {
const _NoteListTile();
@override
Widget build(BuildContext context) {
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
builder: (context, state) => Padding(
padding: const EdgeInsets.only(
left: Dimens.paddingM,
top: Dimens.paddingS / 2,
right: Dimens.paddingL,
bottom: Dimens.paddingS / 2,
),
child: LightmeterTextField(
initialValue: state.note,
maxLength: 500,
hintText: S.of(context).note,
style: Theme.of(context).listTileTheme.titleTextStyle,
leading: const Icon(Icons.note_outlined),
onChanged: (value) {
context.read<LogbookPhotoEditBloc>().add(LogbookPhotoNoteChangedEvent(value));
},
),
),
);
}
}
class _EvListTile extends StatelessWidget {
const _EvListTile();
@override
Widget build(BuildContext context) {
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
buildWhen: (_, __) => false,
builder: (context, state) => ListTile(
leading: const Icon(Icons.exposure),
title: Text(S.of(context).ev),
trailing: Text(state.ev.toStringAsFixed(1)),
),
);
}
}
class _IsoListTile extends StatelessWidget {
const _IsoListTile();
@override
Widget build(BuildContext context) {
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
buildWhen: (_, __) => false,
builder: (context, state) => ListTile(
leading: const Icon(Icons.iso_outlined),
title: Text(S.of(context).iso),
trailing: Text(state.iso.toString()),
),
);
}
}
class _NdFilterListTile extends StatelessWidget {
const _NdFilterListTile();
@override
Widget build(BuildContext context) {
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
buildWhen: (_, __) => false,
builder: (context, state) => ListTile(
leading: const Icon(Icons.filter_b_and_w_outlined),
title: Text(S.of(context).ndFilter),
trailing: Text(state.nd.toString()),
),
);
}
}
class _AperturePickerListTile extends StatelessWidget {
const _AperturePickerListTile();
@override
Widget build(BuildContext context) {
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
buildWhen: (previous, current) => previous.aperture != current.aperture,
builder: (context, state) => PickerListTile(
icon: Icons.camera_outlined,
title: S.of(context).apertureValue,
values: ApertureValue.values,
selectedValue: state.aperture,
onChanged: (value) {
context.read<LogbookPhotoEditBloc>().add(LogbookPhotoApertureChangedEvent(value));
},
),
);
}
}
class _ShutterSpeedPickerListTile extends StatelessWidget {
const _ShutterSpeedPickerListTile();
@override
Widget build(BuildContext context) {
return BlocBuilder<LogbookPhotoEditBloc, LogbookPhotoEditState>(
buildWhen: (previous, current) => previous.shutterSpeed != current.shutterSpeed,
builder: (context, state) => PickerListTile(
icon: Icons.shutter_speed_outlined,
title: S.of(context).shutterSpeedValue,
values: ShutterSpeedValue.values,
selectedValue: state.shutterSpeed,
onChanged: (value) {
context.read<LogbookPhotoEditBloc>().add(LogbookPhotoShutterSpeedChangedEvent(value));
},
),
);
}
}

View file

@ -0,0 +1,64 @@
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
class LogbookPhotoEditState {
final String id;
final String name;
final DateTime timestamp;
final double ev;
final int iso;
final int nd;
final Film film;
final Coordinates? coordinates;
final ApertureValue? aperture;
final ShutterSpeedValue? shutterSpeed;
final String? note;
final bool canSave;
final bool isLoading;
const LogbookPhotoEditState({
required this.id,
required this.name,
required this.timestamp,
required this.ev,
required this.iso,
required this.nd,
required this.film,
this.coordinates,
this.aperture,
this.shutterSpeed,
this.note,
required this.canSave,
this.isLoading = false,
});
LogbookPhotoEditState copyWith({
String? id,
String? name,
DateTime? timestamp,
double? ev,
int? iso,
int? nd,
Film? film,
Coordinates? coordinates,
ApertureValue? aperture,
ShutterSpeedValue? shutterSpeed,
String? note,
bool? canSave,
bool? isLoading,
}) =>
LogbookPhotoEditState(
id: id ?? this.id,
name: name ?? this.name,
timestamp: timestamp ?? this.timestamp,
ev: ev ?? this.ev,
iso: iso ?? this.iso,
nd: nd ?? this.nd,
film: film ?? this.film,
coordinates: coordinates ?? this.coordinates,
aperture: aperture ?? this.aperture,
shutterSpeed: shutterSpeed ?? this.shutterSpeed,
note: note ?? this.note,
canSave: canSave ?? this.canSave,
isLoading: isLoading ?? this.isLoading,
);
}