import 'package:async_tools/async_tools.dart'; import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_translate/flutter_translate.dart'; import '../../proto/proto.dart' as proto; import '../../theme/theme.dart'; import '../models/contact_spec.dart'; import 'availability_widget.dart'; const _kDoSubmitEditContact = 'doSubmitEditContact'; class EditContactForm extends StatefulWidget { const EditContactForm({ required this.contact, required this.onSubmit, required this.submitText, required this.submitDisabledText, this.onModifiedState, super.key, }); @override State createState() => _EditContactFormState(); final proto.Contact contact; final String submitText; final String submitDisabledText; final Future Function(ContactSpec) onSubmit; final void Function(bool)? onModifiedState; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties ..add(ObjectFlagProperty Function(ContactSpec p1)>.has( 'onSubmit', onSubmit)) ..add(ObjectFlagProperty.has( 'onModifiedState', onModifiedState)) ..add(DiagnosticsProperty('contact', contact)) ..add(StringProperty('submitText', submitText)) ..add(StringProperty('submitDisabledText', submitDisabledText)); } static const String formFieldNickname = 'nickname'; static const String formFieldNotes = 'notes'; static const String formFieldShowAvailability = 'show_availability'; } class _EditContactFormState extends State { final _formKey = GlobalKey(); @override void initState() { _savedValue = ContactSpec.fromProto(widget.contact); _currentValueNickname = _savedValue.nickname; super.initState(); } ContactSpec _makeContactSpec() { final nickname = _formKey.currentState! .fields[EditContactForm.formFieldNickname]!.value as String; final notes = _formKey .currentState!.fields[EditContactForm.formFieldNotes]!.value as String; final showAvailability = _formKey.currentState! .fields[EditContactForm.formFieldShowAvailability]!.value as bool; return ContactSpec( nickname: nickname, notes: notes, showAvailability: showAvailability); } // Check if everything is the same and update state void _onChanged() { final currentValue = _makeContactSpec(); _isModified = currentValue != _savedValue; final onModifiedState = widget.onModifiedState; if (onModifiedState != null) { onModifiedState(_isModified); } } Widget _availabilityWidget(proto.Availability availability, Color color) => AvailabilityWidget( availability: availability, color: color, vertical: false, ); Widget _editContactForm(BuildContext context) { final theme = Theme.of(context); final scale = theme.extension()!; final scaleConfig = theme.extension()!; final textTheme = theme.textTheme; late final Color border; if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) { border = scale.primaryScale.elementBackground; } else { border = scale.primaryScale.subtleBorder; } return FormBuilder( key: _formKey, autovalidateMode: AutovalidateMode.onUserInteraction, onChanged: _onChanged, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ styledCard( context: context, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row(children: [ const Spacer(), AvatarWidget( name: _currentValueNickname.isNotEmpty ? _currentValueNickname : widget.contact.profile.name, size: 128, borderColor: border, foregroundColor: scale.primaryScale.primaryText, backgroundColor: scale.primaryScale.primary, scaleConfig: scaleConfig, textStyle: theme.textTheme.titleLarge! .copyWith(fontSize: 64), ).paddingLTRB(0, 0, 0, 16), const Spacer() ]), SelectableText(widget.contact.profile.name, style: textTheme.bodyLarge) .noEditDecoratorLabel( context, translate('contact_form.form_name'), ) .paddingSymmetric(vertical: 4), SelectableText(widget.contact.profile.pronouns, style: textTheme.bodyLarge) .noEditDecoratorLabel( context, translate('contact_form.form_pronouns'), ) .paddingSymmetric(vertical: 4), Row(mainAxisSize: MainAxisSize.min, children: [ _availabilityWidget( widget.contact.profile.availability, scale.primaryScale.appText), SelectableText(widget.contact.profile.status, style: textTheme.bodyMedium) .paddingSymmetric(horizontal: 8) ]) .noEditDecoratorLabel( context, translate('contact_form.form_status'), ) .paddingSymmetric(vertical: 4), SelectableText(widget.contact.profile.about, minLines: 1, maxLines: 8, style: textTheme.bodyMedium) .noEditDecoratorLabel( context, translate('contact_form.form_about'), ) .paddingSymmetric(vertical: 4), SelectableText( widget.contact.identityPublicKey.value .toVeilid() .toString(), style: textTheme.bodyMedium! .copyWith(fontFamily: 'Source Code Pro')) .noEditDecoratorLabel( context, translate('contact_form.form_fingerprint'), ) .paddingSymmetric(vertical: 4), ]).paddingAll(16)) .paddingLTRB(0, 0, 0, 16), FormBuilderTextField( name: EditContactForm.formFieldNickname, initialValue: _currentValueNickname, onChanged: (x) { setState(() { _currentValueNickname = x ?? ''; }); }, decoration: InputDecoration( labelText: translate('contact_form.form_nickname')), maxLength: 64, textInputAction: TextInputAction.next, ), FormBuilderCheckbox( name: EditContactForm.formFieldShowAvailability, initialValue: _savedValue.showAvailability, side: BorderSide(color: scale.primaryScale.border, width: 2), checkColor: scale.primaryScale.borderText, activeColor: scale.primaryScale.border, title: Text(translate('contact_form.form_show_availability'), style: textTheme.labelMedium), ), FormBuilderTextField( name: EditContactForm.formFieldNotes, initialValue: _savedValue.notes, minLines: 1, maxLines: 8, maxLength: 1024, decoration: InputDecoration( labelText: translate('contact_form.form_notes')), textInputAction: TextInputAction.newline, ), ElevatedButton( onPressed: _isModified ? _doSubmit : null, child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0), Text(widget.submitText).paddingLTRB(0, 0, 4, 0) ]), ).paddingSymmetric(vertical: 4).alignAtCenter(), ], ), ); } void _doSubmit() { final onSubmit = widget.onSubmit; if (_formKey.currentState?.saveAndValidate() ?? false) { singleFuture((this, _kDoSubmitEditContact), () async { final updatedContactSpec = _makeContactSpec(); final saved = await onSubmit(updatedContactSpec); if (saved) { setState(() { _savedValue = updatedContactSpec; }); _onChanged(); } }); } } @override Widget build(BuildContext context) => _editContactForm(context); /////////////////////////////////////////////////////////////////////////// late ContactSpec _savedValue; late String _currentValueNickname; bool _isModified = false; }