import 'package:flutter/material.dart'; import 'package:flutter_settings_ui/flutter_settings_ui.dart'; import 'package:provider/provider.dart'; import 'package:pweb/models/edit_state.dart'; import 'package:pweb/utils/error/snackbar.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; abstract class BaseEditTile extends AbstractSettingsTile { const BaseEditTile({ super.key, required this.icon, required this.title, required this.valueGetter, required this.valueSetter, required this.errorSituation, this.editStateNotifier, }); final IconData icon; final String title; final ValueGetter valueGetter; final Future Function(T) valueSetter; final String errorSituation; final ValueNotifier? editStateNotifier; Widget buildView(BuildContext context, T? value); Widget buildEditor( BuildContext context, T? initial, void Function(T) onSave, VoidCallback onCancel, bool isSaving, ); bool get useDialogEditor => false; @override Widget build(BuildContext context) => _BaseEditTileBody(delegate: this); } class _BaseEditTileBody extends StatefulWidget { const _BaseEditTileBody({required this.delegate}); final BaseEditTile delegate; @override State<_BaseEditTileBody> createState() => _BaseEditTileBodyState(); } class _BaseEditTileBodyState extends State<_BaseEditTileBody> { late final ValueNotifier _stateNotifier; late final bool _ownsNotifier; bool get _isSaving => _stateNotifier.value == EditState.saving; @override void initState() { super.initState(); final providedNotifier = widget.delegate.editStateNotifier ?? Provider.of?>(context, listen: false); _ownsNotifier = providedNotifier == null; _stateNotifier = providedNotifier ?? ValueNotifier(EditState.view); } @override void dispose() { if (_ownsNotifier) { _stateNotifier.dispose(); } super.dispose(); } Future _performSave(T newValue) async { final current = widget.delegate.valueGetter(); if (newValue == current) { _stateNotifier.value = EditState.view; return; } _stateNotifier.value = 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) _stateNotifier.value = EditState.view; } } Future _openDialogEditor() async { final initial = widget.delegate.valueGetter(); final T? result = await showDialog( 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; if (delegate.useDialogEditor) { return ValueListenableBuilder( valueListenable: _stateNotifier, builder: (context, state, _) { final current = delegate.valueGetter(); return SettingsTile.navigation( leading: Icon(delegate.icon), title: Text(delegate.title), value: delegate.buildView(context, current), onPressed: state == EditState.saving ? null : (_) => _openDialogEditor(), ); }, ); } return ValueListenableBuilder( valueListenable: _stateNotifier, builder: (context, state, _) { final current = delegate.valueGetter(); final isView = state == EditState.view; final isSaving = state == EditState.saving; return SettingsTile.navigation( leading: Icon(delegate.icon), title: Text(delegate.title), value: isView ? delegate.buildView(context, current) : delegate.buildEditor( context, current, _performSave, () => _stateNotifier.value = EditState.view, isSaving, ), onPressed: (_) { if (isView) _stateNotifier.value = EditState.edit; }, ); }, ); } }