chore: unfocus the textField after switching to another popover

This commit is contained in:
nathan 2022-09-18 16:09:28 +08:00
parent 6c27b5455e
commit c493ba79e0
7 changed files with 151 additions and 100 deletions

View File

@ -7,12 +7,12 @@ import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/generated/locale_keys.g.dart';
import 'field_name_input.dart';
import 'field_type_option_editor.dart'; import 'field_type_option_editor.dart';
class FieldEditor extends StatefulWidget { class FieldEditor extends StatefulWidget {
@ -44,6 +44,12 @@ class _FieldEditorState extends State<FieldEditor> {
super.initState(); super.initState();
} }
@override
void dispose() {
popoverMutex.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
@ -58,10 +64,26 @@ class _FieldEditorState extends State<FieldEditor> {
return ListView( return ListView(
shrinkWrap: true, shrinkWrap: true,
children: [ children: [
FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), FlowyText.medium(
fontSize: 12), LocaleKeys.grid_field_editProperty.tr(),
fontSize: 12,
),
const VSpace(10), const VSpace(10),
const _FieldNameCell(), _FieldNameTextField(popoverMutex: popoverMutex),
..._addDeleteFieldButton(state),
_FieldTypeOptionCell(popoverMutex: popoverMutex),
],
);
},
),
);
}
List<Widget> _addDeleteFieldButton(FieldEditorState state) {
if (widget.onDeleted == null) {
return [];
}
return [
const VSpace(10), const VSpace(10),
_DeleteFieldButton( _DeleteFieldButton(
popoverMutex: popoverMutex, popoverMutex: popoverMutex,
@ -72,13 +94,7 @@ class _FieldEditorState extends State<FieldEditor> {
); );
}, },
), ),
const VSpace(10), ];
_FieldTypeOptionCell(popoverMutex: popoverMutex),
],
);
},
),
);
} }
} }
@ -111,25 +127,89 @@ class _FieldTypeOptionCell extends StatelessWidget {
} }
} }
class _FieldNameCell extends StatelessWidget { class _FieldNameTextField extends StatefulWidget {
const _FieldNameCell({Key? key}) : super(key: key); final PopoverMutex popoverMutex;
const _FieldNameTextField({
required this.popoverMutex,
Key? key,
}) : super(key: key);
@override
State<_FieldNameTextField> createState() => _FieldNameTextFieldState();
}
class _FieldNameTextFieldState extends State<_FieldNameTextField> {
late String name;
FocusNode focusNode = FocusNode();
VoidCallback? _popoverCallback;
TextEditingController controller = TextEditingController();
@override
void initState() {
focusNode.addListener(() {
if (focusNode.hasFocus) {
widget.popoverMutex.close();
}
});
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<FieldEditorBloc, FieldEditorState>( final theme = context.watch<AppTheme>();
controller.text = context.read<FieldEditorBloc>().state.name;
return BlocListener<FieldEditorBloc, FieldEditorState>(
listenWhen: (previous, current) => previous.name != current.name,
listener: (context, state) {
controller.text = state.name;
},
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
builder: (context, state) { builder: (context, state) {
return FieldNameTextField( listenOnPopoverChhanged(context);
name: state.name,
return RoundedInputField(
height: 36,
autoFocus: true,
focusNode: focusNode,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
controller: controller,
normalBorderColor: theme.shader4,
errorBorderColor: theme.red,
focusBorderColor: theme.main1,
cursorColor: theme.main1,
errorText: context.read<FieldEditorBloc>().state.errorText, errorText: context.read<FieldEditorBloc>().state.errorText,
onNameChanged: (newName) { onChanged: (newName) {
context context
.read<FieldEditorBloc>() .read<FieldEditorBloc>()
.add(FieldEditorEvent.updateName(newName)); .add(FieldEditorEvent.updateName(newName));
}, },
); );
}, },
),
); );
} }
void listenOnPopoverChhanged(BuildContext context) {
if (_popoverCallback != null) {
widget.popoverMutex.removePopoverStateListener(_popoverCallback!);
}
_popoverCallback = widget.popoverMutex.listenOnPopoverStateChanged(() {
if (focusNode.hasFocus) {
final node = FocusScope.of(context);
node.unfocus();
}
});
}
@override
void didUpdateWidget(covariant _FieldNameTextField oldWidget) {
controller.selection = TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
super.didUpdateWidget(oldWidget);
}
} }
class _DeleteFieldButton extends StatelessWidget { class _DeleteFieldButton extends StatelessWidget {
@ -171,12 +251,10 @@ class _DeleteFieldButton extends StatelessWidget {
popupBuilder: (popupContext) { popupBuilder: (popupContext) {
return PopoverAlertView( return PopoverAlertView(
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
cancel: () => popoverMutex.state?.close(), cancel: () {},
confirm: () { confirm: () {
onDeleted?.call(); onDeleted?.call();
popoverMutex.state?.close();
}, },
popoverMutex: popoverMutex,
); );
}, },
child: widget, child: widget,

View File

@ -1,56 +0,0 @@
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class FieldNameTextField extends StatefulWidget {
final void Function(String) onNameChanged;
final String name;
final String errorText;
const FieldNameTextField({
required this.name,
required this.errorText,
required this.onNameChanged,
Key? key,
}) : super(key: key);
@override
State<FieldNameTextField> createState() => _FieldNameTextFieldState();
}
class _FieldNameTextFieldState extends State<FieldNameTextField> {
late String name;
TextEditingController controller = TextEditingController();
@override
void initState() {
controller.text = widget.name;
super.initState();
}
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return RoundedInputField(
height: 36,
autoFocus: true,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
controller: controller,
normalBorderColor: theme.shader4,
errorBorderColor: theme.red,
focusBorderColor: theme.main1,
cursorColor: theme.main1,
errorText: widget.errorText,
onChanged: widget.onNameChanged,
);
}
@override
void didUpdateWidget(covariant FieldNameTextField oldWidget) {
controller.text = widget.name;
controller.selection = TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
super.didUpdateWidget(oldWidget);
}
}

View File

@ -66,7 +66,8 @@ class FieldTypeOptionEditor extends StatelessWidget {
height: GridSize.typeOptionItemHeight, height: GridSize.typeOptionItemHeight,
child: AppFlowyStylePopover( child: AppFlowyStylePopover(
constraints: BoxConstraints.loose(const Size(460, 440)), constraints: BoxConstraints.loose(const Size(460, 440)),
triggerActions: PopoverTriggerActionFlags.click, triggerActions:
PopoverTriggerActionFlags.click | PopoverTriggerActionFlags.hover,
mutex: popoverMutex, mutex: popoverMutex,
offset: const Offset(20, 0), offset: const Offset(20, 0),
popupBuilder: (context) { popupBuilder: (context) {

View File

@ -50,7 +50,9 @@ Widget? makeTypeOptionWidget({
required PopoverMutex popoverMutex, required PopoverMutex popoverMutex,
}) { }) {
final builder = makeTypeOptionWidgetBuilder( final builder = makeTypeOptionWidgetBuilder(
dataController: dataController, popoverMutex: popoverMutex); dataController: dataController,
popoverMutex: popoverMutex,
);
return builder.build(context); return builder.build(context);
} }

View File

@ -123,8 +123,6 @@ class NumberFormatList extends StatelessWidget {
format: format, format: format,
onSelected: (format) { onSelected: (format) {
onSelected(format); onSelected(format);
FlowyOverlay.of(context)
.remove(NumberFormatList.identifier());
}); });
}).toList(); }).toList();

View File

@ -1,4 +1,3 @@
import 'package:appflowy_popover/popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra/text_style.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
@ -88,13 +87,11 @@ class _CreateTextFieldDialog extends State<NavigatorTextFieldDialog> {
} }
class PopoverAlertView extends StatelessWidget { class PopoverAlertView extends StatelessWidget {
final PopoverMutex popoverMutex;
final String title; final String title;
final void Function()? cancel; final void Function()? cancel;
final void Function()? confirm; final void Function()? confirm;
const PopoverAlertView({ const PopoverAlertView({
required this.popoverMutex,
required this.title, required this.title,
this.confirm, this.confirm,
this.cancel, this.cancel,

View File

@ -6,7 +6,42 @@ import 'package:flutter/services.dart';
/// If multiple popovers are exclusive, /// If multiple popovers are exclusive,
/// pass the same mutex to them. /// pass the same mutex to them.
class PopoverMutex { class PopoverMutex {
PopoverState? state; final ValueNotifier<PopoverState?> _stateNofitier = ValueNotifier(null);
PopoverMutex();
void removePopoverStateListener(VoidCallback listener) {
_stateNofitier.removeListener(listener);
}
VoidCallback listenOnPopoverStateChanged(VoidCallback callback) {
listenerCallback() {
callback();
}
_stateNofitier.addListener(listenerCallback);
return listenerCallback;
}
void close() {
_stateNofitier.value?.close();
}
PopoverState? get state => _stateNofitier.value;
set state(PopoverState? newState) {
if (_stateNofitier.value != null && _stateNofitier.value != newState) {
_stateNofitier.value?.close();
}
_stateNofitier.value = newState;
}
void _removeState() {
_stateNofitier.value = null;
}
void dispose() {
_stateNofitier.dispose();
}
} }
class PopoverController { class PopoverController {
@ -109,11 +144,7 @@ class PopoverState extends State<Popover> {
close(); close();
if (widget.mutex != null) { if (widget.mutex != null) {
if (widget.mutex!.state != null && widget.mutex!.state != this) { widget.mutex?.state = this;
widget.mutex!.state!.close();
}
widget.mutex!.state = this;
} }
if (_popoverWithMask == null) { if (_popoverWithMask == null) {
@ -163,7 +194,7 @@ class PopoverState extends State<Popover> {
} }
if (widget.mutex?.state == this) { if (widget.mutex?.state == this) {
widget.mutex!.state = null; widget.mutex?._removeState();
} }
} }