Files
sendico/frontend/pweb/lib/pages/settings/widgets/base.dart
2025-11-13 15:06:15 +03:00

144 lines
4.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter_settings_ui/flutter_settings_ui.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
import 'package:pweb/utils/error/snackbar.dart';
enum _EditState { view, edit, saving }
/// Базовый класс, управляющий состояниями (view/edit/saving),
/// показом snackbar ошибок и успешного сохранения.
abstract class BaseEditTile<T> extends AbstractSettingsTile {
const BaseEditTile({
super.key,
required this.icon,
required this.title,
required this.valueGetter,
required this.valueSetter,
required this.errorSituation,
});
final IconData icon;
final String title;
final ValueGetter<T?> valueGetter;
final Future<void> Function(T) valueSetter;
final String errorSituation;
/// Рисует в режиме просмотра (read-only).
Widget buildView(BuildContext context, T? value);
/// Рисует UI редактора.
/// Если [useDialogEditor]==true, его обернут в диалог.
Widget buildEditor(
BuildContext context,
T? initial,
void Function(T) onSave,
VoidCallback onCancel,
bool isSaving,
);
/// true → показывать редактор в диалоге, false → inline под заголовком.
bool get useDialogEditor => false;
@override
Widget build(BuildContext context) => _BaseEditTileBody<T>(delegate: this);
}
class _BaseEditTileBody<T> extends StatefulWidget {
const _BaseEditTileBody({required this.delegate});
final BaseEditTile<T> delegate;
@override
State<_BaseEditTileBody<T>> createState() => _BaseEditTileBodyState<T>();
}
class _BaseEditTileBodyState<T> extends State<_BaseEditTileBody<T>> {
_EditState _state = _EditState.view;
bool get _isSaving => _state == _EditState.saving;
Future<void> _performSave(T newValue) async {
final current = widget.delegate.valueGetter();
if (newValue == current) {
setState(() => _state = _EditState.view);
return;
}
setState(() => _state = _EditState.saving);
final sms = ScaffoldMessenger.of(context);
final locs = AppLocalizations.of(context)!;
try {
await widget.delegate.valueSetter(newValue);
sms.showSnackBar(SnackBar(
content: Text(locs.settingsSuccessfullyUpdated),
duration: const Duration(milliseconds: 1200),
));
} catch (e) {
notifyUserOfErrorX(
scaffoldMessenger: sms,
errorSituation: widget.delegate.errorSituation,
appLocalizations: locs,
exception: e,
);
} finally {
if (mounted) setState(() => _state = _EditState.view);
}
}
Future<void> _openDialogEditor() async {
final initial = widget.delegate.valueGetter();
final T? result = await showDialog<T>(
context: context,
barrierDismissible: true,
builder: (ctx) {
return Dialog(
insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)),
child: Padding(
padding: const EdgeInsets.all(16),
child: widget.delegate.buildEditor(
ctx,
initial,
(v) => Navigator.of(ctx).pop(v),
() => Navigator.of(ctx).pop(),
_isSaving,
),
),
);
},
);
if (result != null) await _performSave(result);
}
@override
Widget build(BuildContext context) {
final delegate = widget.delegate;
final current = delegate.valueGetter();
// Диалоговый режим
if (delegate.useDialogEditor) {
return SettingsTile.navigation(
leading: Icon(delegate.icon),
title: Text(delegate.title),
value: delegate.buildView(context, current),
onPressed: (_) => _openDialogEditor(),
);
}
// Inline-режим (под заголовком будет редактор прямо в tile)
return SettingsTile.navigation(
leading: Icon(delegate.icon),
title: Text(delegate.title),
value: _state == _EditState.view
? delegate.buildView(context, current)
: delegate.buildEditor(
context,
current,
_performSave,
() => setState(() => _state = _EditState.view),
_isSaving,
),
onPressed: (_) {
if (_state == _EditState.view) setState(() => _state = _EditState.edit);
},
);
}
}