veilidchat/lib/contacts/views/edit_contact_form.dart
Christien Rioux 77c68aa45f ui cleanup
2025-03-17 00:51:16 -04:00

249 lines
9.6 KiB
Dart

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<bool> Function(ContactSpec) onSubmit;
final void Function(bool)? onModifiedState;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(ObjectFlagProperty<Future<bool> Function(ContactSpec p1)>.has(
'onSubmit', onSubmit))
..add(ObjectFlagProperty<void Function(bool p1)?>.has(
'onModifiedState', onModifiedState))
..add(DiagnosticsProperty<proto.Contact>('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<EditContactForm> {
final _formKey = GlobalKey<FormBuilderState>();
@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<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
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;
}