mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: unfocus the textField after switching to another popover
This commit is contained in:
parent
6c27b5455e
commit
c493ba79e0
@ -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,
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user