mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
commit
bb61bf0a30
@ -33,8 +33,10 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
|||||||
await dataController.loadTypeOptionData();
|
await dataController.loadTypeOptionData();
|
||||||
},
|
},
|
||||||
updateName: (name) {
|
updateName: (name) {
|
||||||
dataController.fieldName = name;
|
if (state.name != name) {
|
||||||
emit(state.copyWith(name: name));
|
dataController.fieldName = name;
|
||||||
|
emit(state.copyWith(name: name));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
didReceiveFieldChanged: (FieldPB field) {
|
didReceiveFieldChanged: (FieldPB field) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
|
@ -58,7 +58,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final alignment = widget.cellStyle != null
|
final alignment = widget.cellStyle != null
|
||||||
? widget.cellStyle!.alignment
|
? widget.cellStyle!.alignment
|
||||||
: Alignment.center;
|
: Alignment.centerLeft;
|
||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
child: BlocBuilder<DateCellBloc, DateCellState>(
|
child: BlocBuilder<DateCellBloc, DateCellState>(
|
||||||
@ -77,7 +77,10 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
|||||||
cursor: SystemMouseCursors.click,
|
cursor: SystemMouseCursors.click,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
child: FlowyText.medium(state.dateStr, fontSize: 12),
|
child: FlowyText.medium(
|
||||||
|
state.dateStr,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -11,20 +11,17 @@ import 'package:textfield_tags/textfield_tags.dart';
|
|||||||
|
|
||||||
import 'extension.dart';
|
import 'extension.dart';
|
||||||
|
|
||||||
class SelectOptionTextField extends StatelessWidget {
|
class SelectOptionTextField extends StatefulWidget {
|
||||||
final FocusNode _focusNode;
|
|
||||||
final TextEditingController _controller;
|
|
||||||
final TextfieldTagsController tagController;
|
final TextfieldTagsController tagController;
|
||||||
final List<SelectOptionPB> options;
|
final List<SelectOptionPB> options;
|
||||||
final LinkedHashMap<String, SelectOptionPB> selectedOptionMap;
|
final LinkedHashMap<String, SelectOptionPB> selectedOptionMap;
|
||||||
|
|
||||||
final double distanceToText;
|
final double distanceToText;
|
||||||
|
|
||||||
final Function(String) onNewTag;
|
final Function(String) onNewTag;
|
||||||
final Function(String) newText;
|
final Function(String) newText;
|
||||||
final VoidCallback? onClick;
|
final VoidCallback? onClick;
|
||||||
|
|
||||||
SelectOptionTextField({
|
const SelectOptionTextField({
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.selectedOptionMap,
|
required this.selectedOptionMap,
|
||||||
required this.distanceToText,
|
required this.distanceToText,
|
||||||
@ -35,33 +32,55 @@ class SelectOptionTextField extends StatelessWidget {
|
|||||||
TextEditingController? textController,
|
TextEditingController? textController,
|
||||||
FocusNode? focusNode,
|
FocusNode? focusNode,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : _controller = textController ?? TextEditingController(),
|
}) : super(key: key);
|
||||||
_focusNode = focusNode ?? FocusNode(),
|
|
||||||
super(key: key);
|
@override
|
||||||
|
State<SelectOptionTextField> createState() => _SelectOptionTextFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SelectOptionTextFieldState extends State<SelectOptionTextField> {
|
||||||
|
late FocusNode focusNode;
|
||||||
|
late TextEditingController controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
focusNode = FocusNode();
|
||||||
|
controller = TextEditingController();
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
focusNode.requestFocus();
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
|
|
||||||
return TextFieldTags(
|
return TextFieldTags(
|
||||||
textEditingController: _controller,
|
textEditingController: controller,
|
||||||
textfieldTagsController: tagController,
|
textfieldTagsController: widget.tagController,
|
||||||
initialTags: selectedOptionMap.keys.toList(),
|
initialTags: widget.selectedOptionMap.keys.toList(),
|
||||||
focusNode: _focusNode,
|
focusNode: focusNode,
|
||||||
textSeparators: const [','],
|
textSeparators: const [','],
|
||||||
inputfieldBuilder: (BuildContext context, editController, focusNode,
|
inputfieldBuilder: (
|
||||||
error, onChanged, onSubmitted) {
|
BuildContext context,
|
||||||
|
editController,
|
||||||
|
focusNode,
|
||||||
|
error,
|
||||||
|
onChanged,
|
||||||
|
onSubmitted,
|
||||||
|
) {
|
||||||
return ((context, sc, tags, onTagDelegate) {
|
return ((context, sc, tags, onTagDelegate) {
|
||||||
return TextField(
|
return TextField(
|
||||||
autofocus: true,
|
|
||||||
controller: editController,
|
controller: editController,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
onTap: onClick,
|
onTap: widget.onClick,
|
||||||
onChanged: (text) {
|
onChanged: (text) {
|
||||||
if (onChanged != null) {
|
if (onChanged != null) {
|
||||||
onChanged(text);
|
onChanged(text);
|
||||||
}
|
}
|
||||||
newText(text);
|
widget.newText(text);
|
||||||
},
|
},
|
||||||
onSubmitted: (text) {
|
onSubmitted: (text) {
|
||||||
if (onSubmitted != null) {
|
if (onSubmitted != null) {
|
||||||
@ -69,7 +88,7 @@ class SelectOptionTextField extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (text.isNotEmpty) {
|
if (text.isNotEmpty) {
|
||||||
onNewTag(text);
|
widget.onNewTag(text);
|
||||||
focusNode.requestFocus();
|
focusNode.requestFocus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -83,7 +102,8 @@ class SelectOptionTextField extends StatelessWidget {
|
|||||||
isDense: true,
|
isDense: true,
|
||||||
prefixIcon: _renderTags(context, sc),
|
prefixIcon: _renderTags(context, sc),
|
||||||
hintText: LocaleKeys.grid_selectOption_searchOption.tr(),
|
hintText: LocaleKeys.grid_selectOption_searchOption.tr(),
|
||||||
prefixIconConstraints: BoxConstraints(maxWidth: distanceToText),
|
prefixIconConstraints:
|
||||||
|
BoxConstraints(maxWidth: widget.distanceToText),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderSide: BorderSide(color: theme.main1, width: 1.0),
|
borderSide: BorderSide(color: theme.main1, width: 1.0),
|
||||||
borderRadius: Corners.s10Border,
|
borderRadius: Corners.s10Border,
|
||||||
@ -96,11 +116,11 @@ class SelectOptionTextField extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget? _renderTags(BuildContext context, ScrollController sc) {
|
Widget? _renderTags(BuildContext context, ScrollController sc) {
|
||||||
if (selectedOptionMap.isEmpty) {
|
if (widget.selectedOptionMap.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final children = selectedOptionMap.values
|
final children = widget.selectedOptionMap.values
|
||||||
.map((option) =>
|
.map((option) =>
|
||||||
SelectOptionTag.fromOption(context: context, option: option))
|
SelectOptionTag.fromOption(context: context, option: option))
|
||||||
.toList();
|
.toList();
|
||||||
|
@ -137,9 +137,11 @@ class _DragToExpandLine extends StatelessWidget {
|
|||||||
class FieldCellButton extends StatelessWidget {
|
class FieldCellButton extends StatelessWidget {
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
final FieldPB field;
|
final FieldPB field;
|
||||||
|
final int? maxLines;
|
||||||
const FieldCellButton({
|
const FieldCellButton({
|
||||||
required this.field,
|
required this.field,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
|
this.maxLines = 1,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -150,7 +152,11 @@ class FieldCellButton extends StatelessWidget {
|
|||||||
hoverColor: theme.shader6,
|
hoverColor: theme.shader6,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
|
leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
|
||||||
text: FlowyText.medium(field.name, fontSize: 12),
|
text: FlowyText.medium(
|
||||||
|
field.name,
|
||||||
|
fontSize: 12,
|
||||||
|
maxLines: maxLines,
|
||||||
|
),
|
||||||
margin: GridSize.cellContentInsets,
|
margin: GridSize.cellContentInsets,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart';
|
import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
import 'package:dartz/dartz.dart' show none;
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.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/rounded_input_field.dart';
|
||||||
@ -59,38 +58,38 @@ class _FieldEditorState extends State<FieldEditor> {
|
|||||||
isGroupField: widget.isGroupField,
|
isGroupField: widget.isGroupField,
|
||||||
loader: widget.typeOptionLoader,
|
loader: widget.typeOptionLoader,
|
||||||
)..add(const FieldEditorEvent.initial()),
|
)..add(const FieldEditorEvent.initial()),
|
||||||
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
child: ListView(
|
||||||
builder: (context, state) {
|
shrinkWrap: true,
|
||||||
return ListView(
|
children: [
|
||||||
shrinkWrap: true,
|
FlowyText.medium(
|
||||||
children: [
|
LocaleKeys.grid_field_editProperty.tr(),
|
||||||
FlowyText.medium(
|
fontSize: 12,
|
||||||
LocaleKeys.grid_field_editProperty.tr(),
|
),
|
||||||
fontSize: 12,
|
const VSpace(10),
|
||||||
),
|
_FieldNameTextField(popoverMutex: popoverMutex),
|
||||||
const VSpace(10),
|
const VSpace(10),
|
||||||
_FieldNameTextField(popoverMutex: popoverMutex),
|
..._addDeleteFieldButton(),
|
||||||
const VSpace(10),
|
_FieldTypeOptionCell(popoverMutex: popoverMutex),
|
||||||
..._addDeleteFieldButton(state),
|
],
|
||||||
_FieldTypeOptionCell(popoverMutex: popoverMutex),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _addDeleteFieldButton(FieldEditorState state) {
|
List<Widget> _addDeleteFieldButton() {
|
||||||
if (widget.onDeleted == null) {
|
if (widget.onDeleted == null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
_DeleteFieldButton(
|
BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||||
popoverMutex: popoverMutex,
|
builder: (context, state) {
|
||||||
onDeleted: () {
|
return _DeleteFieldButton(
|
||||||
state.field.fold(
|
popoverMutex: popoverMutex,
|
||||||
() => Log.error('Can not delete the field'),
|
onDeleted: () {
|
||||||
(field) => widget.onDeleted?.call(field.id),
|
state.field.fold(
|
||||||
|
() => Log.error('Can not delete the field'),
|
||||||
|
(field) => widget.onDeleted?.call(field.id),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -139,13 +138,13 @@ class _FieldNameTextField extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FieldNameTextFieldState extends State<_FieldNameTextField> {
|
class _FieldNameTextFieldState extends State<_FieldNameTextField> {
|
||||||
late String name;
|
|
||||||
FocusNode focusNode = FocusNode();
|
FocusNode focusNode = FocusNode();
|
||||||
VoidCallback? _popoverCallback;
|
VoidCallback? _popoverCallback;
|
||||||
TextEditingController controller = TextEditingController();
|
late TextEditingController controller;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
controller = TextEditingController();
|
||||||
focusNode.addListener(() {
|
focusNode.addListener(() {
|
||||||
if (focusNode.hasFocus) {
|
if (focusNode.hasFocus) {
|
||||||
widget.popoverMutex.close();
|
widget.popoverMutex.close();
|
||||||
@ -158,20 +157,29 @@ class _FieldNameTextFieldState extends State<_FieldNameTextField> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
|
return MultiBlocListener(
|
||||||
controller.text = context.read<FieldEditorBloc>().state.name;
|
listeners: [
|
||||||
return BlocListener<FieldEditorBloc, FieldEditorState>(
|
BlocListener<FieldEditorBloc, FieldEditorState>(
|
||||||
listenWhen: (previous, current) => previous.name != current.name,
|
listenWhen: (p, c) => p.field == none(),
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
controller.text = state.name;
|
focusNode.requestFocus();
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
BlocListener<FieldEditorBloc, FieldEditorState>(
|
||||||
|
listenWhen: (p, c) => controller.text != c.name,
|
||||||
|
listener: (context, state) {
|
||||||
|
controller.text = state.name;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
previous.errorText != current.errorText,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
listenOnPopoverChanged(context);
|
listenOnPopoverChanged(context);
|
||||||
|
|
||||||
return RoundedInputField(
|
return RoundedInputField(
|
||||||
height: 36,
|
height: 36,
|
||||||
autoFocus: true,
|
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
@ -193,23 +201,15 @@ class _FieldNameTextFieldState extends State<_FieldNameTextField> {
|
|||||||
|
|
||||||
void listenOnPopoverChanged(BuildContext context) {
|
void listenOnPopoverChanged(BuildContext context) {
|
||||||
if (_popoverCallback != null) {
|
if (_popoverCallback != null) {
|
||||||
widget.popoverMutex.removePopoverStateListener(_popoverCallback!);
|
widget.popoverMutex.removePopoverListener(_popoverCallback!);
|
||||||
}
|
}
|
||||||
_popoverCallback = widget.popoverMutex.listenOnPopoverStateChanged(() {
|
_popoverCallback = widget.popoverMutex.listenOnPopoverChanged(() {
|
||||||
if (focusNode.hasFocus) {
|
if (focusNode.hasFocus) {
|
||||||
final node = FocusScope.of(context);
|
final node = FocusScope.of(context);
|
||||||
node.unfocus();
|
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 {
|
||||||
@ -235,29 +235,11 @@ class _DeleteFieldButton extends StatelessWidget {
|
|||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: enable ? null : theme.shader4,
|
color: enable ? null : theme.shader4,
|
||||||
),
|
),
|
||||||
|
onTap: () => onDeleted?.call(),
|
||||||
);
|
);
|
||||||
if (enable) button = _wrapPopover(button);
|
// if (enable) button = button;
|
||||||
return button;
|
return button;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _wrapPopover(Widget widget) {
|
|
||||||
return AppFlowyPopover(
|
|
||||||
triggerActions: PopoverTriggerFlags.click,
|
|
||||||
constraints: BoxConstraints.loose(const Size(400, 240)),
|
|
||||||
mutex: popoverMutex,
|
|
||||||
direction: PopoverDirection.center,
|
|
||||||
popupBuilder: (popupContext) {
|
|
||||||
return PopoverAlertView(
|
|
||||||
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
|
|
||||||
cancel: () {},
|
|
||||||
confirm: () {
|
|
||||||
onDeleted?.call();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: widget,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ class FieldTypeOptionEditor extends StatelessWidget {
|
|||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: GridSize.typeOptionItemHeight,
|
height: GridSize.typeOptionItemHeight,
|
||||||
child: AppFlowyPopover(
|
child: AppFlowyPopover(
|
||||||
constraints: BoxConstraints.loose(const Size(460, 440)),
|
constraints: BoxConstraints.loose(const Size(460, 540)),
|
||||||
asBarrier: true,
|
asBarrier: true,
|
||||||
triggerActions: PopoverTriggerFlags.click | PopoverTriggerFlags.hover,
|
triggerActions: PopoverTriggerFlags.click | PopoverTriggerFlags.hover,
|
||||||
mutex: popoverMutex,
|
mutex: popoverMutex,
|
||||||
|
@ -64,6 +64,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
|
|||||||
Widget _renderDateFormatButton(BuildContext context, DateFormat dataFormat) {
|
Widget _renderDateFormatButton(BuildContext context, DateFormat dataFormat) {
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
mutex: popoverMutex,
|
mutex: popoverMutex,
|
||||||
|
asBarrier: true,
|
||||||
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||||
offset: const Offset(20, 0),
|
offset: const Offset(20, 0),
|
||||||
constraints: BoxConstraints.loose(const Size(460, 440)),
|
constraints: BoxConstraints.loose(const Size(460, 440)),
|
||||||
@ -85,6 +86,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
|
|||||||
Widget _renderTimeFormatButton(BuildContext context, TimeFormat timeFormat) {
|
Widget _renderTimeFormatButton(BuildContext context, TimeFormat timeFormat) {
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
mutex: popoverMutex,
|
mutex: popoverMutex,
|
||||||
|
asBarrier: true,
|
||||||
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||||
offset: const Offset(20, 0),
|
offset: const Offset(20, 0),
|
||||||
constraints: BoxConstraints.loose(const Size(460, 440)),
|
constraints: BoxConstraints.loose(const Size(460, 440)),
|
||||||
|
@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
|
|||||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
|
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/row/row_detail_bloc.dart';
|
import 'package:app_flowy/plugins/grid/application/row/row_detail_bloc.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
@ -112,67 +113,61 @@ class _PropertyList extends StatelessWidget {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(child: _wrapScrollbar(buildList(state))),
|
||||||
child: ScrollbarListStack(
|
|
||||||
axis: Axis.vertical,
|
|
||||||
controller: _scrollController,
|
|
||||||
barSize: GridSize.scrollBarSize,
|
|
||||||
autoHideScrollbar: false,
|
|
||||||
child: ListView.separated(
|
|
||||||
controller: _scrollController,
|
|
||||||
itemCount: state.gridCells.length,
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
|
||||||
return _RowDetailCell(
|
|
||||||
cellId: state.gridCells[index],
|
|
||||||
cellBuilder: cellBuilder,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
separatorBuilder: (BuildContext context, int index) {
|
|
||||||
return const VSpace(2);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const VSpace(10),
|
const VSpace(10),
|
||||||
_CreateFieldButton(
|
_CreateFieldButton(
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
onClosed: () {
|
onClosed: _handleDidCreateField,
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
_scrollController.animateTo(
|
|
||||||
_scrollController.position.maxScrollExtent,
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
curve: Curves.ease,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onOpened: (controller) {
|
|
||||||
return FieldEditor(
|
|
||||||
gridId: viewId,
|
|
||||||
typeOptionLoader: NewFieldTypeOptionLoader(gridId: viewId),
|
|
||||||
onDeleted: (fieldId) {
|
|
||||||
controller.close();
|
|
||||||
context
|
|
||||||
.read<RowDetailBloc>()
|
|
||||||
.add(RowDetailEvent.deleteField(fieldId));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildList(RowDetailState state) {
|
||||||
|
return ListView.separated(
|
||||||
|
controller: _scrollController,
|
||||||
|
itemCount: state.gridCells.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return _RowDetailCell(
|
||||||
|
cellId: state.gridCells[index],
|
||||||
|
cellBuilder: cellBuilder,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (BuildContext context, int index) {
|
||||||
|
return const VSpace(2);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _wrapScrollbar(Widget child) {
|
||||||
|
return ScrollbarListStack(
|
||||||
|
axis: Axis.vertical,
|
||||||
|
controller: _scrollController,
|
||||||
|
barSize: GridSize.scrollBarSize,
|
||||||
|
autoHideScrollbar: false,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleDidCreateField() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_scrollController.animateTo(
|
||||||
|
_scrollController.position.maxScrollExtent,
|
||||||
|
duration: const Duration(milliseconds: 250),
|
||||||
|
curve: Curves.ease,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CreateFieldButton extends StatefulWidget {
|
class _CreateFieldButton extends StatefulWidget {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final Widget Function(PopoverController) onOpened;
|
|
||||||
final VoidCallback onClosed;
|
final VoidCallback onClosed;
|
||||||
|
|
||||||
const _CreateFieldButton({
|
const _CreateFieldButton({
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.onOpened,
|
|
||||||
required this.onClosed,
|
required this.onClosed,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -213,8 +208,24 @@ class _CreateFieldButtonState extends State<_CreateFieldButton> {
|
|||||||
leftIcon: svgWidget("home/add"),
|
leftIcon: svgWidget("home/add"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
popupBuilder: (BuildContext context) =>
|
popupBuilder: (BuildContext popOverContext) {
|
||||||
widget.onOpened(popoverController),
|
return FieldEditor(
|
||||||
|
gridId: widget.viewId,
|
||||||
|
typeOptionLoader: NewFieldTypeOptionLoader(gridId: widget.viewId),
|
||||||
|
onDeleted: (fieldId) {
|
||||||
|
popoverController.close();
|
||||||
|
|
||||||
|
NavigatorAlertDialog(
|
||||||
|
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
|
||||||
|
confirm: () {
|
||||||
|
context
|
||||||
|
.read<RowDetailBloc>()
|
||||||
|
.add(RowDetailEvent.deleteField(fieldId));
|
||||||
|
},
|
||||||
|
).show(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,41 +271,25 @@ class _RowDetailCellState extends State<_RowDetailCell> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return ConstrainedBox(
|
return IntrinsicHeight(
|
||||||
constraints: const BoxConstraints(minHeight: 40),
|
child: ConstrainedBox(
|
||||||
child: IntrinsicHeight(
|
constraints: const BoxConstraints(minHeight: 40),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
AppFlowyPopover(
|
||||||
width: 150,
|
controller: popover,
|
||||||
child: Popover(
|
constraints: BoxConstraints.loose(const Size(240, 600)),
|
||||||
controller: popover,
|
popupBuilder: (popoverContext) => buildFieldEditor(),
|
||||||
offset: const Offset(20, 0),
|
child: SizedBox(
|
||||||
popupBuilder: (popoverContext) {
|
width: 150,
|
||||||
return OverlayContainer(
|
|
||||||
constraints: BoxConstraints.loose(const Size(240, 600)),
|
|
||||||
child: FieldEditor(
|
|
||||||
gridId: widget.cellId.gridId,
|
|
||||||
fieldName: widget.cellId.fieldContext.field.name,
|
|
||||||
isGroupField: widget.cellId.fieldContext.isGroupField,
|
|
||||||
typeOptionLoader: FieldTypeOptionLoader(
|
|
||||||
gridId: widget.cellId.gridId,
|
|
||||||
field: widget.cellId.fieldContext.field,
|
|
||||||
),
|
|
||||||
onDeleted: (fieldId) {
|
|
||||||
popover.close();
|
|
||||||
context
|
|
||||||
.read<RowDetailBloc>()
|
|
||||||
.add(RowDetailEvent.deleteField(fieldId));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: FieldCellButton(
|
child: FieldCellButton(
|
||||||
|
maxLines: null,
|
||||||
field: widget.cellId.fieldContext.field,
|
field: widget.cellId.fieldContext.field,
|
||||||
onTap: () => popover.show(),
|
onTap: () {
|
||||||
|
popover.show();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -305,6 +300,30 @@ class _RowDetailCellState extends State<_RowDetailCell> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildFieldEditor() {
|
||||||
|
return FieldEditor(
|
||||||
|
gridId: widget.cellId.gridId,
|
||||||
|
fieldName: widget.cellId.fieldContext.field.name,
|
||||||
|
isGroupField: widget.cellId.fieldContext.isGroupField,
|
||||||
|
typeOptionLoader: FieldTypeOptionLoader(
|
||||||
|
gridId: widget.cellId.gridId,
|
||||||
|
field: widget.cellId.fieldContext.field,
|
||||||
|
),
|
||||||
|
onDeleted: (fieldId) {
|
||||||
|
popover.close();
|
||||||
|
|
||||||
|
NavigatorAlertDialog(
|
||||||
|
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
|
||||||
|
confirm: () {
|
||||||
|
context
|
||||||
|
.read<RowDetailBloc>()
|
||||||
|
.add(RowDetailEvent.deleteField(fieldId));
|
||||||
|
},
|
||||||
|
).show(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GridCellStyle? _customCellStyle(AppTheme theme, FieldType fieldType) {
|
GridCellStyle? _customCellStyle(AppTheme theme, FieldType fieldType) {
|
||||||
|
@ -120,7 +120,7 @@ class _GridPropertyCell extends StatelessWidget {
|
|||||||
mutex: popoverMutex,
|
mutex: popoverMutex,
|
||||||
triggerActions: PopoverTriggerFlags.click,
|
triggerActions: PopoverTriggerFlags.click,
|
||||||
offset: const Offset(20, 0),
|
offset: const Offset(20, 0),
|
||||||
constraints: BoxConstraints.loose(const Size(240, 200)),
|
constraints: BoxConstraints.loose(const Size(240, 400)),
|
||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
text: FlowyText.medium(fieldContext.name, fontSize: 12),
|
text: FlowyText.medium(fieldContext.name, fontSize: 12),
|
||||||
hoverColor: theme.hover,
|
hoverColor: theme.hover,
|
||||||
|
@ -140,23 +140,23 @@ class _TrashPageState extends State<TrashPage> {
|
|||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 36,
|
height: 36,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
FlowyText.semibold(LocaleKeys.trash_text.tr()),
|
FlowyText.semibold(LocaleKeys.trash_text.tr()),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
SizedBox.fromSize(
|
IntrinsicWidth(
|
||||||
size: const Size(102, 30),
|
|
||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
text: FlowyText.medium(LocaleKeys.trash_restoreAll.tr(),
|
text: FlowyText.medium(LocaleKeys.trash_restoreAll.tr(),
|
||||||
fontSize: 12),
|
fontSize: 12),
|
||||||
leftIcon: svgWidget('editor/restore', color: theme.iconColor),
|
leftIcon: svgWidget('editor/restore', color: theme.iconColor),
|
||||||
hoverColor: theme.hover,
|
hoverColor: theme.hover,
|
||||||
onTap: () =>
|
onTap: () => context.read<TrashBloc>().add(
|
||||||
context.read<TrashBloc>().add(const TrashEvent.restoreAll()),
|
const TrashEvent.restoreAll(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const HSpace(6),
|
const HSpace(6),
|
||||||
SizedBox.fromSize(
|
IntrinsicWidth(
|
||||||
size: const Size(102, 30),
|
|
||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
text: FlowyText.medium(LocaleKeys.trash_deleteAll.tr(),
|
text: FlowyText.medium(LocaleKeys.trash_deleteAll.tr(),
|
||||||
fontSize: 12),
|
fontSize: 12),
|
||||||
|
@ -86,42 +86,6 @@ class _CreateTextFieldDialog extends State<NavigatorTextFieldDialog> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PopoverAlertView extends StatelessWidget {
|
|
||||||
final String title;
|
|
||||||
final void Function()? cancel;
|
|
||||||
final void Function()? confirm;
|
|
||||||
|
|
||||||
const PopoverAlertView({
|
|
||||||
required this.title,
|
|
||||||
this.confirm,
|
|
||||||
this.cancel,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = context.watch<AppTheme>();
|
|
||||||
return StyledDialog(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
...[
|
|
||||||
FlowyText.medium(title, color: theme.shader4),
|
|
||||||
],
|
|
||||||
if (confirm != null) ...[
|
|
||||||
const VSpace(20),
|
|
||||||
OkCancelButton(
|
|
||||||
onOkPressed: confirm,
|
|
||||||
onCancelPressed: cancel,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NavigatorAlertDialog extends StatefulWidget {
|
class NavigatorAlertDialog extends StatefulWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final void Function()? cancel;
|
final void Function()? cancel;
|
||||||
|
@ -5,14 +5,14 @@ import 'popover.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 {
|
||||||
final ValueNotifier<PopoverState?> _stateNotifier = ValueNotifier(null);
|
final _PopoverStateNotifier _stateNotifier = _PopoverStateNotifier();
|
||||||
PopoverMutex();
|
PopoverMutex();
|
||||||
|
|
||||||
void removePopoverStateListener(VoidCallback listener) {
|
void removePopoverListener(VoidCallback listener) {
|
||||||
_stateNotifier.removeListener(listener);
|
_stateNotifier.removeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
VoidCallback listenOnPopoverStateChanged(VoidCallback callback) {
|
VoidCallback listenOnPopoverChanged(VoidCallback callback) {
|
||||||
listenerCallback() {
|
listenerCallback() {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
@ -21,24 +21,31 @@ class PopoverMutex {
|
|||||||
return listenerCallback;
|
return listenerCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void close() {
|
void close() => _stateNotifier.state?.close();
|
||||||
_stateNotifier.value?.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
PopoverState? get state => _stateNotifier.value;
|
PopoverState? get state => _stateNotifier.state;
|
||||||
|
|
||||||
set state(PopoverState? newState) {
|
set state(PopoverState? newState) => _stateNotifier.state = newState;
|
||||||
if (_stateNotifier.value != null && _stateNotifier.value != newState) {
|
|
||||||
_stateNotifier.value?.close();
|
|
||||||
}
|
|
||||||
_stateNotifier.value = newState;
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeState() {
|
void removeState() {
|
||||||
_stateNotifier.value = null;
|
_stateNotifier.state = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_stateNotifier.dispose();
|
_stateNotifier.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PopoverStateNotifier extends ChangeNotifier {
|
||||||
|
PopoverState? _state;
|
||||||
|
|
||||||
|
PopoverState? get state => _state;
|
||||||
|
|
||||||
|
set state(PopoverState? newState) {
|
||||||
|
if (_state != null && _state != newState) {
|
||||||
|
_state?.close();
|
||||||
|
}
|
||||||
|
_state = newState;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@ class FlowyText extends StatelessWidget {
|
|||||||
final double fontSize;
|
final double fontSize;
|
||||||
final FontWeight fontWeight;
|
final FontWeight fontWeight;
|
||||||
final TextAlign? textAlign;
|
final TextAlign? textAlign;
|
||||||
|
final int? maxLines;
|
||||||
final Color? color;
|
final Color? color;
|
||||||
|
|
||||||
const FlowyText(
|
const FlowyText(
|
||||||
@ -18,21 +19,40 @@ class FlowyText extends StatelessWidget {
|
|||||||
this.fontWeight = FontWeight.w400,
|
this.fontWeight = FontWeight.w400,
|
||||||
this.textAlign,
|
this.textAlign,
|
||||||
this.color,
|
this.color,
|
||||||
|
this.maxLines = 1,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
const FlowyText.semibold(this.title,
|
const FlowyText.semibold(
|
||||||
{Key? key, this.fontSize = 16, this.overflow, this.color, this.textAlign})
|
this.title, {
|
||||||
: fontWeight = FontWeight.w600,
|
Key? key,
|
||||||
|
this.fontSize = 16,
|
||||||
|
this.overflow,
|
||||||
|
this.color,
|
||||||
|
this.textAlign,
|
||||||
|
this.maxLines = 1,
|
||||||
|
}) : fontWeight = FontWeight.w600,
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
const FlowyText.medium(this.title,
|
const FlowyText.medium(
|
||||||
{Key? key, this.fontSize = 16, this.overflow, this.color, this.textAlign})
|
this.title, {
|
||||||
: fontWeight = FontWeight.w500,
|
Key? key,
|
||||||
|
this.fontSize = 16,
|
||||||
|
this.overflow,
|
||||||
|
this.color,
|
||||||
|
this.textAlign,
|
||||||
|
this.maxLines = 1,
|
||||||
|
}) : fontWeight = FontWeight.w500,
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
const FlowyText.regular(this.title,
|
const FlowyText.regular(
|
||||||
{Key? key, this.fontSize = 16, this.overflow, this.color, this.textAlign})
|
this.title, {
|
||||||
: fontWeight = FontWeight.w400,
|
Key? key,
|
||||||
|
this.fontSize = 16,
|
||||||
|
this.overflow,
|
||||||
|
this.color,
|
||||||
|
this.textAlign,
|
||||||
|
this.maxLines = 1,
|
||||||
|
}) : fontWeight = FontWeight.w400,
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -40,6 +60,7 @@ class FlowyText extends StatelessWidget {
|
|||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
return Text(
|
return Text(
|
||||||
title,
|
title,
|
||||||
|
maxLines: maxLines,
|
||||||
textAlign: textAlign,
|
textAlign: textAlign,
|
||||||
overflow: overflow ?? TextOverflow.clip,
|
overflow: overflow ?? TextOverflow.clip,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user