mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-08-01 18:56:03 -04:00
ui cleanup
This commit is contained in:
parent
d460a0388c
commit
77c68aa45f
57 changed files with 1158 additions and 914 deletions
|
@ -1,3 +1,4 @@
|
|||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -6,13 +7,18 @@ 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.formKey,
|
||||
required this.contact,
|
||||
this.onSubmit,
|
||||
required this.onSubmit,
|
||||
required this.submitText,
|
||||
required this.submitDisabledText,
|
||||
this.onModifiedState,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -20,19 +26,22 @@ class EditContactForm extends StatefulWidget {
|
|||
State createState() => _EditContactFormState();
|
||||
|
||||
final proto.Contact contact;
|
||||
final Future<void> Function(GlobalKey<FormBuilderState>)? onSubmit;
|
||||
final GlobalKey<FormBuilderState> formKey;
|
||||
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<void> Function(
|
||||
GlobalKey<FormBuilderState> p1)?>.has('onSubmit', onSubmit))
|
||||
..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(
|
||||
DiagnosticsProperty<GlobalKey<FormBuilderState>>('formKey', formKey));
|
||||
..add(StringProperty('submitText', submitText))
|
||||
..add(StringProperty('submitDisabledText', submitDisabledText));
|
||||
}
|
||||
|
||||
static const String formFieldNickname = 'nickname';
|
||||
|
@ -41,16 +50,46 @@ class EditContactForm extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _EditContactFormState extends State<EditContactForm> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_savedValue = ContactSpec.fromProto(widget.contact);
|
||||
_currentValueNickname = _savedValue.nickname;
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Widget _availabilityWidget(proto.Availability availability, Color color) =>
|
||||
AvailabilityWidget(availability: availability, color: color);
|
||||
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;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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>()!;
|
||||
|
@ -60,75 +99,94 @@ class _EditContactFormState extends State<EditContactForm> {
|
|||
if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) {
|
||||
border = scale.primaryScale.elementBackground;
|
||||
} else {
|
||||
border = scale.primaryScale.border;
|
||||
border = scale.primaryScale.subtleBorder;
|
||||
}
|
||||
|
||||
return FormBuilder(
|
||||
key: widget.formKey,
|
||||
key: _formKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
onChanged: _onChanged,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
AvatarWidget(
|
||||
name: 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),
|
||||
SelectableText(widget.contact.profile.name,
|
||||
style: textTheme.headlineMedium)
|
||||
.noEditDecoratorLabel(
|
||||
context,
|
||||
translate('contact_form.form_name'),
|
||||
scale: scale.secondaryScale,
|
||||
)
|
||||
.paddingSymmetric(vertical: 4),
|
||||
SelectableText(widget.contact.profile.pronouns,
|
||||
style: textTheme.headlineSmall)
|
||||
.noEditDecoratorLabel(
|
||||
context,
|
||||
translate('contact_form.form_pronouns'),
|
||||
scale: scale.secondaryScale,
|
||||
)
|
||||
.paddingSymmetric(vertical: 4),
|
||||
Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
_availabilityWidget(widget.contact.profile.availability,
|
||||
scale.primaryScale.primaryText),
|
||||
SelectableText(widget.contact.profile.status,
|
||||
style: textTheme.bodyMedium)
|
||||
.paddingSymmetric(horizontal: 8)
|
||||
])
|
||||
.noEditDecoratorLabel(
|
||||
context,
|
||||
translate('contact_form.form_status'),
|
||||
scale: scale.secondaryScale,
|
||||
)
|
||||
.paddingSymmetric(vertical: 4),
|
||||
SelectableText(widget.contact.profile.about,
|
||||
minLines: 1, maxLines: 8, style: textTheme.bodyMedium)
|
||||
.noEditDecoratorLabel(
|
||||
context,
|
||||
translate('contact_form.form_about'),
|
||||
scale: scale.secondaryScale,
|
||||
)
|
||||
.paddingSymmetric(vertical: 4),
|
||||
SelectableText(
|
||||
widget.contact.identityPublicKey.value.toVeilid().toString(),
|
||||
style: textTheme.labelMedium!
|
||||
.copyWith(fontFamily: 'Source Code Pro'))
|
||||
.noEditDecoratorLabel(
|
||||
context,
|
||||
translate('contact_form.form_fingerprint'),
|
||||
scale: scale.secondaryScale,
|
||||
)
|
||||
.paddingSymmetric(vertical: 4),
|
||||
Divider(color: border).paddingLTRB(8, 0, 8, 8),
|
||||
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(
|
||||
//autofocus: true,
|
||||
name: EditContactForm.formFieldNickname,
|
||||
initialValue: widget.contact.nickname,
|
||||
initialValue: _currentValueNickname,
|
||||
onChanged: (x) {
|
||||
setState(() {
|
||||
_currentValueNickname = x ?? '';
|
||||
});
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('contact_form.form_nickname')),
|
||||
maxLength: 64,
|
||||
|
@ -136,14 +194,16 @@ class _EditContactFormState extends State<EditContactForm> {
|
|||
),
|
||||
FormBuilderCheckbox(
|
||||
name: EditContactForm.formFieldShowAvailability,
|
||||
initialValue: widget.contact.showAvailability,
|
||||
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: widget.contact.notes,
|
||||
initialValue: _savedValue.notes,
|
||||
minLines: 1,
|
||||
maxLines: 8,
|
||||
maxLength: 1024,
|
||||
|
@ -152,24 +212,38 @@ class _EditContactFormState extends State<EditContactForm> {
|
|||
textInputAction: TextInputAction.newline,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: widget.onSubmit == null
|
||||
? null
|
||||
: () async {
|
||||
if (widget.formKey.currentState?.saveAndValidate() ??
|
||||
false) {
|
||||
await widget.onSubmit!(widget.formKey);
|
||||
}
|
||||
},
|
||||
onPressed: _isModified ? _doSubmit : null,
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0),
|
||||
Text((widget.onSubmit == null)
|
||||
? translate('contact_form.save')
|
||||
: translate('contact_form.save'))
|
||||
.paddingLTRB(0, 0, 4, 0)
|
||||
Text(widget.submitText).paddingLTRB(0, 0, 4, 0)
|
||||
]),
|
||||
).paddingSymmetric(vertical: 4).alignAtCenterRight(),
|
||||
).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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue