mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #1085 from AppFlowy-IO/fix/popover_bugs
Fix/popover bugs
This commit is contained in:
@ -23,7 +23,7 @@ class EditableRowNotifier {
|
|||||||
EditableRowNotifier({required bool isEditing})
|
EditableRowNotifier({required bool isEditing})
|
||||||
: isEditing = ValueNotifier(isEditing);
|
: isEditing = ValueNotifier(isEditing);
|
||||||
|
|
||||||
void insertCell(
|
void bindCell(
|
||||||
GridCellIdentifier cellIdentifier,
|
GridCellIdentifier cellIdentifier,
|
||||||
EditableCellNotifier notifier,
|
EditableCellNotifier notifier,
|
||||||
) {
|
) {
|
||||||
@ -59,7 +59,7 @@ class EditableRowNotifier {
|
|||||||
_cells.values.first.isCellEditing.value = false;
|
_cells.values.first.isCellEditing.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
void unbind() {
|
||||||
for (final notifier in _cells.values) {
|
for (final notifier in _cells.values) {
|
||||||
notifier.dispose();
|
notifier.dispose();
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
|
|||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.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 'define.dart';
|
import 'define.dart';
|
||||||
|
|
||||||
class BoardNumberCell extends StatefulWidget {
|
class BoardNumberCell extends StatefulWidget {
|
||||||
|
@ -2,6 +2,8 @@ import 'package:app_flowy/plugins/board/application/card/board_select_option_cel
|
|||||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
|
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
|
||||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
|
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
|
||||||
|
import 'package:appflowy_popover/popover.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.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';
|
||||||
|
|
||||||
@ -26,9 +28,11 @@ class BoardSelectOptionCell extends StatefulWidget with EditableCell {
|
|||||||
|
|
||||||
class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
||||||
late BoardSelectOptionCellBloc _cellBloc;
|
late BoardSelectOptionCellBloc _cellBloc;
|
||||||
|
late PopoverController _popover;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
_popover = PopoverController();
|
||||||
final cellController =
|
final cellController =
|
||||||
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
|
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
|
||||||
_cellBloc = BoardSelectOptionCellBloc(cellController: cellController)
|
_cellBloc = BoardSelectOptionCellBloc(cellController: cellController)
|
||||||
@ -43,29 +47,20 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
|||||||
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
|
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
|
||||||
buildWhen: (previous, current) {
|
buildWhen: (previous, current) {
|
||||||
return previous.selectedOptions != current.selectedOptions;
|
return previous.selectedOptions != current.selectedOptions;
|
||||||
},
|
}, builder: (context, state) {
|
||||||
builder: (context, state) {
|
// Returns SizedBox if the content of the cell is empty
|
||||||
if (state.selectedOptions
|
if (_isEmpty(state)) return const SizedBox();
|
||||||
.where((element) => element.id == widget.groupId)
|
|
||||||
.isNotEmpty ||
|
final children = state.selectedOptions.map(
|
||||||
state.selectedOptions.isEmpty) {
|
(option) {
|
||||||
return const SizedBox();
|
final tag = SelectOptionTag.fromOption(
|
||||||
} else {
|
|
||||||
final children = state.selectedOptions
|
|
||||||
.map(
|
|
||||||
(option) => SelectOptionTag.fromOption(
|
|
||||||
context: context,
|
context: context,
|
||||||
option: option,
|
option: option,
|
||||||
onSelected: () {
|
onSelected: () => _popover.show(),
|
||||||
SelectOptionCellEditor.show(
|
|
||||||
context: context,
|
|
||||||
cellController: widget.cellControllerBuilder.build()
|
|
||||||
as GridSelectOptionCellController,
|
|
||||||
);
|
);
|
||||||
|
return _wrapPopover(tag);
|
||||||
},
|
},
|
||||||
),
|
).toList();
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
return IntrinsicHeight(
|
return IntrinsicHeight(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@ -75,9 +70,35 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isEmpty(BoardSelectOptionCellState state) {
|
||||||
|
// The cell should hide if the option id is equal to the groupId.
|
||||||
|
final isInGroup = state.selectedOptions
|
||||||
|
.where((element) => element.id == widget.groupId)
|
||||||
|
.isNotEmpty;
|
||||||
|
return isInGroup || state.selectedOptions.isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _wrapPopover(Widget child) {
|
||||||
|
final constraints = BoxConstraints.loose(Size(
|
||||||
|
SelectOptionCellEditor.editorPanelWidth,
|
||||||
|
300,
|
||||||
|
));
|
||||||
|
return AppFlowyStylePopover(
|
||||||
|
controller: _popover,
|
||||||
|
constraints: constraints,
|
||||||
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
|
popupBuilder: (BuildContext context) {
|
||||||
|
return SelectOptionCellEditor(
|
||||||
|
cellController: widget.cellControllerBuilder.build()
|
||||||
|
as GridSelectOptionCellController,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
onClose: () {},
|
||||||
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
|
import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
|
||||||
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
|
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
|
||||||
import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet.dart';
|
import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet.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';
|
||||||
@ -64,10 +63,16 @@ class _BoardCardState extends State<BoardCard> {
|
|||||||
value: _cardBloc,
|
value: _cardBloc,
|
||||||
child: BlocBuilder<BoardCardBloc, BoardCardState>(
|
child: BlocBuilder<BoardCardBloc, BoardCardState>(
|
||||||
buildWhen: (previous, current) {
|
buildWhen: (previous, current) {
|
||||||
|
// Rebuild when:
|
||||||
|
// 1.If the lenght of the cells is not the same
|
||||||
|
// 2.isEditing changed
|
||||||
if (previous.cells.length != current.cells.length ||
|
if (previous.cells.length != current.cells.length ||
|
||||||
previous.isEditing != current.isEditing) {
|
previous.isEditing != current.isEditing) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3.Compare the content of the cells. The cells consisits of
|
||||||
|
// list of [BoardCellEquatable] that extends the [Equatable].
|
||||||
return !listEquals(previous.cells, current.cells);
|
return !listEquals(previous.cells, current.cells);
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@ -75,21 +80,16 @@ class _BoardCardState extends State<BoardCard> {
|
|||||||
buildAccessoryWhen: () => state.isEditing == false,
|
buildAccessoryWhen: () => state.isEditing == false,
|
||||||
accessoryBuilder: (context) {
|
accessoryBuilder: (context) {
|
||||||
return [
|
return [
|
||||||
_CardEditOption(
|
_CardEditOption(rowNotifier: rowNotifier),
|
||||||
startEditing: () => rowNotifier.becomeFirstResponder(),
|
|
||||||
),
|
|
||||||
const _CardMoreOption(),
|
const _CardMoreOption(),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
onTap: (context) {
|
onTap: (context) => widget.openCard(context),
|
||||||
widget.openCard(context);
|
child: _CellColumn(
|
||||||
},
|
groupId: widget.groupId,
|
||||||
child: Column(
|
rowNotifier: rowNotifier,
|
||||||
mainAxisSize: MainAxisSize.min,
|
cellBuilder: widget.cellBuilder,
|
||||||
children: _makeCells(
|
cells: state.cells,
|
||||||
context,
|
|
||||||
state.cells.map((cell) => cell.identifier).toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -97,44 +97,6 @@ class _BoardCardState extends State<BoardCard> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _makeCells(
|
|
||||||
BuildContext context,
|
|
||||||
List<GridCellIdentifier> cells,
|
|
||||||
) {
|
|
||||||
final List<Widget> children = [];
|
|
||||||
rowNotifier.clear();
|
|
||||||
cells.asMap().forEach(
|
|
||||||
(int index, GridCellIdentifier cellId) {
|
|
||||||
EditableCellNotifier cellNotifier;
|
|
||||||
if (index == 0) {
|
|
||||||
// Only use the first cell to receive user's input when click the edit
|
|
||||||
// button
|
|
||||||
cellNotifier = EditableCellNotifier(
|
|
||||||
isEditing: rowNotifier.isEditing.value,
|
|
||||||
);
|
|
||||||
rowNotifier.insertCell(cellId, cellNotifier);
|
|
||||||
} else {
|
|
||||||
cellNotifier = EditableCellNotifier();
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget child = widget.cellBuilder.buildCell(
|
|
||||||
widget.groupId,
|
|
||||||
cellId,
|
|
||||||
cellNotifier,
|
|
||||||
);
|
|
||||||
|
|
||||||
child = Padding(
|
|
||||||
key: cellId.key(),
|
|
||||||
padding: const EdgeInsets.only(left: 4, right: 4),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
|
|
||||||
children.add(child);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return children;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
rowNotifier.dispose();
|
rowNotifier.dispose();
|
||||||
@ -143,6 +105,63 @@ class _BoardCardState extends State<BoardCard> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _CellColumn extends StatelessWidget {
|
||||||
|
final String groupId;
|
||||||
|
final BoardCellBuilder cellBuilder;
|
||||||
|
final EditableRowNotifier rowNotifier;
|
||||||
|
final List<BoardCellEquatable> cells;
|
||||||
|
const _CellColumn({
|
||||||
|
required this.groupId,
|
||||||
|
required this.rowNotifier,
|
||||||
|
required this.cellBuilder,
|
||||||
|
required this.cells,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: _makeCells(context, cells),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _makeCells(
|
||||||
|
BuildContext context,
|
||||||
|
List<BoardCellEquatable> cells,
|
||||||
|
) {
|
||||||
|
final List<Widget> children = [];
|
||||||
|
// Remove all the cell listeners.
|
||||||
|
rowNotifier.unbind();
|
||||||
|
|
||||||
|
cells.asMap().forEach(
|
||||||
|
(int index, BoardCellEquatable cell) {
|
||||||
|
final isEditing = index == 0 ? rowNotifier.isEditing.value : false;
|
||||||
|
final cellNotifier = EditableCellNotifier(isEditing: isEditing);
|
||||||
|
|
||||||
|
if (index == 0) {
|
||||||
|
// Only use the first cell to receive user's input when click the edit
|
||||||
|
// button
|
||||||
|
rowNotifier.bindCell(cell.identifier, cellNotifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
final child = Padding(
|
||||||
|
key: cell.identifier.key(),
|
||||||
|
padding: const EdgeInsets.only(left: 4, right: 4),
|
||||||
|
child: cellBuilder.buildCell(
|
||||||
|
groupId,
|
||||||
|
cell.identifier,
|
||||||
|
cellNotifier,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
children.add(child);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _CardMoreOption extends StatelessWidget with CardAccessory {
|
class _CardMoreOption extends StatelessWidget with CardAccessory {
|
||||||
const _CardMoreOption({Key? key}) : super(key: key);
|
const _CardMoreOption({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@ -164,9 +183,9 @@ class _CardMoreOption extends StatelessWidget with CardAccessory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _CardEditOption extends StatelessWidget with CardAccessory {
|
class _CardEditOption extends StatelessWidget with CardAccessory {
|
||||||
final VoidCallback startEditing;
|
final EditableRowNotifier rowNotifier;
|
||||||
const _CardEditOption({
|
const _CardEditOption({
|
||||||
required this.startEditing,
|
required this.rowNotifier,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -183,6 +202,6 @@ class _CardEditOption extends StatelessWidget with CardAccessory {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onTap(BuildContext context) {
|
void onTap(BuildContext context) {
|
||||||
startEditing();
|
rowNotifier.becomeFirstResponder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,17 @@ class CardAccessoryContainer extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.read<AppTheme>();
|
final theme = context.read<AppTheme>();
|
||||||
final children = accessories.map((accessory) {
|
final children = accessories.map((accessory) {
|
||||||
final hover = FlowyHover(
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () => accessory.onTap(context),
|
||||||
|
child: _wrapHover(theme, accessory),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
return _wrapDecoration(context, Row(children: children));
|
||||||
|
}
|
||||||
|
|
||||||
|
FlowyHover _wrapHover(AppTheme theme, CardAccessory accessory) {
|
||||||
|
return FlowyHover(
|
||||||
style: HoverStyle(
|
style: HoverStyle(
|
||||||
hoverColor: theme.hover,
|
hoverColor: theme.hover,
|
||||||
backgroundColor: theme.surface,
|
backgroundColor: theme.surface,
|
||||||
@ -84,38 +94,22 @@ class CardAccessoryContainer extends StatelessWidget {
|
|||||||
child: accessory,
|
child: accessory,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return GestureDetector(
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onTap: () => accessory.onTap(context),
|
|
||||||
child: hover,
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
decoration: _makeBoxDecoration(context),
|
|
||||||
child: Row(children: children),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BoxDecoration _makeBoxDecoration(BuildContext context) {
|
Widget _wrapDecoration(BuildContext context, Widget child) {
|
||||||
final theme = context.read<AppTheme>();
|
final theme = context.read<AppTheme>();
|
||||||
final borderSide = BorderSide(color: theme.shader6, width: 1.0);
|
final borderSide = BorderSide(color: theme.shader6, width: 1.0);
|
||||||
return BoxDecoration(
|
final decoration = BoxDecoration(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
border: Border.fromBorderSide(borderSide),
|
border: Border.fromBorderSide(borderSide),
|
||||||
// boxShadow: const [
|
|
||||||
// BoxShadow(
|
|
||||||
// color: Colors.transparent,
|
|
||||||
// spreadRadius: 0,
|
|
||||||
// blurRadius: 5,
|
|
||||||
// offset: Offset.zero,
|
|
||||||
// )
|
|
||||||
// ],
|
|
||||||
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
);
|
);
|
||||||
|
return Container(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: decoration,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CardEnterRegion extends StatelessWidget {
|
class _CardEnterRegion extends StatelessWidget {
|
||||||
|
@ -164,67 +164,65 @@ class _SelectOptionWrapState extends State<SelectOptionWrap> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
final Widget child;
|
Widget child = _buildOptions(theme, context);
|
||||||
if (widget.selectOptions.isEmpty && widget.cellStyle != null) {
|
|
||||||
child = Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: FlowyText.medium(
|
|
||||||
widget.cellStyle!.placeholder,
|
|
||||||
fontSize: 14,
|
|
||||||
color: theme.shader3,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
child = Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Wrap(
|
|
||||||
spacing: 4,
|
|
||||||
runSpacing: 2,
|
|
||||||
children: widget.selectOptions
|
|
||||||
.map((option) => SelectOptionTag.fromOption(
|
|
||||||
context: context,
|
|
||||||
option: option,
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
alignment: AlignmentDirectional.center,
|
alignment: AlignmentDirectional.center,
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
AppFlowyStylePopover(
|
_wrapPopover(child),
|
||||||
controller: _popover,
|
InkWell(onTap: () => _popover.show()),
|
||||||
constraints: BoxConstraints.loose(
|
|
||||||
Size(SelectOptionCellEditor.editorPanelWidth, 300)),
|
|
||||||
offset: const Offset(0, 20),
|
|
||||||
direction: PopoverDirection.bottomWithLeftAligned,
|
|
||||||
// triggerActions: PopoverTriggerActionFlags.c,
|
|
||||||
popupBuilder: (BuildContext context) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
|
||||||
widget.onFocus?.call(true);
|
|
||||||
});
|
|
||||||
return SizedBox(
|
|
||||||
width: SelectOptionCellEditor.editorPanelWidth,
|
|
||||||
child: SelectOptionCellEditor(
|
|
||||||
cellController: widget.cellControllerBuilder.build()
|
|
||||||
as GridSelectOptionCellController,
|
|
||||||
onDismissed: () {
|
|
||||||
widget.onFocus?.call(false);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onClose: () {
|
|
||||||
widget.onFocus?.call(false);
|
|
||||||
},
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
InkWell(onTap: () {
|
|
||||||
_popover.show();
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _wrapPopover(Widget child) {
|
||||||
|
final constraints = BoxConstraints.loose(Size(
|
||||||
|
SelectOptionCellEditor.editorPanelWidth,
|
||||||
|
300,
|
||||||
|
));
|
||||||
|
return AppFlowyStylePopover(
|
||||||
|
controller: _popover,
|
||||||
|
constraints: constraints,
|
||||||
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
|
popupBuilder: (BuildContext context) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
widget.onFocus?.call(true);
|
||||||
|
});
|
||||||
|
return SelectOptionCellEditor(
|
||||||
|
cellController: widget.cellControllerBuilder.build()
|
||||||
|
as GridSelectOptionCellController,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onClose: () => widget.onFocus?.call(false),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildOptions(AppTheme theme, BuildContext context) {
|
||||||
|
final Widget child;
|
||||||
|
if (widget.selectOptions.isEmpty && widget.cellStyle != null) {
|
||||||
|
child = FlowyText.medium(
|
||||||
|
widget.cellStyle!.placeholder,
|
||||||
|
fontSize: 14,
|
||||||
|
color: theme.shader3,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final children = widget.selectOptions.map(
|
||||||
|
(option) {
|
||||||
|
return SelectOptionTag.fromOption(
|
||||||
|
context: context,
|
||||||
|
option: option,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).toList();
|
||||||
|
|
||||||
|
child = Wrap(
|
||||||
|
spacing: 4,
|
||||||
|
runSpacing: 2,
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Align(alignment: Alignment.centerLeft, child: child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,17 +25,13 @@ import 'text_field.dart';
|
|||||||
|
|
||||||
const double _editorPanelWidth = 300;
|
const double _editorPanelWidth = 300;
|
||||||
|
|
||||||
class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
|
class SelectOptionCellEditor extends StatelessWidget {
|
||||||
final GridSelectOptionCellController cellController;
|
final GridSelectOptionCellController cellController;
|
||||||
final VoidCallback? onDismissed;
|
|
||||||
|
|
||||||
static double editorPanelWidth = 300;
|
static double editorPanelWidth = 300;
|
||||||
|
|
||||||
const SelectOptionCellEditor({
|
const SelectOptionCellEditor({required this.cellController, Key? key})
|
||||||
required this.cellController,
|
: super(key: key);
|
||||||
this.onDismissed,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -60,44 +56,6 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void show({
|
|
||||||
required BuildContext context,
|
|
||||||
required GridSelectOptionCellController cellController,
|
|
||||||
VoidCallback? onDismissed,
|
|
||||||
}) {
|
|
||||||
SelectOptionCellEditor.remove(context);
|
|
||||||
final editor = SelectOptionCellEditor(
|
|
||||||
cellController: cellController,
|
|
||||||
onDismissed: onDismissed,
|
|
||||||
);
|
|
||||||
|
|
||||||
//
|
|
||||||
FlowyOverlay.of(context).insertWithAnchor(
|
|
||||||
widget: OverlayContainer(
|
|
||||||
constraints: BoxConstraints.loose(const Size(_editorPanelWidth, 300)),
|
|
||||||
child: SizedBox(width: _editorPanelWidth, child: editor),
|
|
||||||
),
|
|
||||||
identifier: SelectOptionCellEditor.identifier(),
|
|
||||||
anchorContext: context,
|
|
||||||
anchorDirection: AnchorDirection.bottomWithCenterAligned,
|
|
||||||
delegate: editor,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void remove(BuildContext context) {
|
|
||||||
FlowyOverlay.of(context).remove(identifier());
|
|
||||||
}
|
|
||||||
|
|
||||||
static String identifier() {
|
|
||||||
return (SelectOptionCellEditor).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool asBarrier() => true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didRemove() => onDismissed?.call();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _OptionList extends StatelessWidget {
|
class _OptionList extends StatelessWidget {
|
||||||
|
@ -3,6 +3,7 @@ import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
|
|||||||
import 'package:appflowy_popover/popover.dart';
|
import 'package:appflowy_popover/popover.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/style_widget/button.dart';
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
@ -29,7 +30,8 @@ class GridFieldCell extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: BlocBuilder<FieldCellBloc, FieldCellState>(
|
child: BlocBuilder<FieldCellBloc, FieldCellState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final button = Popover(
|
final button = AppFlowyStylePopover(
|
||||||
|
constraints: BoxConstraints.loose(const Size(240, 840)),
|
||||||
direction: PopoverDirection.bottomWithLeftAligned,
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
triggerActions: PopoverTriggerActionFlags.click,
|
triggerActions: PopoverTriggerActionFlags.click,
|
||||||
offset: const Offset(0, 10),
|
offset: const Offset(0, 10),
|
||||||
|
@ -3,9 +3,9 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_editor.
|
|||||||
import 'package:app_flowy/startup/startup.dart';
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
|
import 'package:appflowy_popover/popover.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/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/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
@ -32,23 +32,18 @@ class _GridFieldCellActionSheetState extends State<GridFieldCellActionSheet> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_showFieldEditor) {
|
if (_showFieldEditor) {
|
||||||
final field = widget.cellContext.field;
|
final field = widget.cellContext.field;
|
||||||
return OverlayContainer(
|
return FieldEditor(
|
||||||
constraints: BoxConstraints.loose(const Size(240, 200)),
|
|
||||||
child: FieldEditor(
|
|
||||||
gridId: widget.cellContext.gridId,
|
gridId: widget.cellContext.gridId,
|
||||||
fieldName: field.name,
|
fieldName: field.name,
|
||||||
typeOptionLoader: FieldTypeOptionLoader(
|
typeOptionLoader: FieldTypeOptionLoader(
|
||||||
gridId: widget.cellContext.gridId,
|
gridId: widget.cellContext.gridId,
|
||||||
field: field,
|
field: field,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
getIt<FieldActionSheetBloc>(param1: widget.cellContext),
|
getIt<FieldActionSheetBloc>(param1: widget.cellContext),
|
||||||
child: OverlayContainer(
|
|
||||||
constraints: BoxConstraints.loose(const Size(240, 200)),
|
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@ -65,7 +60,6 @@ class _GridFieldCellActionSheetState extends State<GridFieldCellActionSheet> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,8 +153,11 @@ class FieldActionCell extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
return FlowyButton(
|
return FlowyButton(
|
||||||
text: FlowyText.medium(action.title(),
|
text: FlowyText.medium(
|
||||||
fontSize: 12, color: enable ? null : theme.shader4),
|
action.title(),
|
||||||
|
fontSize: 12,
|
||||||
|
color: enable ? null : theme.shader4,
|
||||||
|
),
|
||||||
hoverColor: theme.hover,
|
hoverColor: theme.hover,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
@ -168,8 +165,10 @@ class FieldActionCell extends StatelessWidget {
|
|||||||
onTap();
|
onTap();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
leftIcon: svgWidget(action.iconName(),
|
leftIcon: svgWidget(
|
||||||
color: enable ? theme.iconColor : theme.disableIconColor),
|
action.iconName(),
|
||||||
|
color: enable ? theme.iconColor : theme.disableIconColor,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,6 +215,7 @@ extension _FieldActionExtension on FieldAction {
|
|||||||
.add(const FieldActionSheetEvent.duplicateField());
|
.add(const FieldActionSheetEvent.duplicateField());
|
||||||
break;
|
break;
|
||||||
case FieldAction.delete:
|
case FieldAction.delete:
|
||||||
|
PopoverContainer.of(context).close();
|
||||||
NavigatorAlertDialog(
|
NavigatorAlertDialog(
|
||||||
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
|
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
|
||||||
confirm: () {
|
confirm: () {
|
||||||
|
@ -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,11 +64,27 @@ 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),
|
||||||
const VSpace(10),
|
const VSpace(10),
|
||||||
|
..._addDeleteFieldButton(state),
|
||||||
|
_FieldTypeOptionCell(popoverMutex: popoverMutex),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _addDeleteFieldButton(FieldEditorState state) {
|
||||||
|
if (widget.onDeleted == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [
|
||||||
_DeleteFieldButton(
|
_DeleteFieldButton(
|
||||||
popoverMutex: popoverMutex,
|
popoverMutex: popoverMutex,
|
||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -47,10 +47,6 @@ class FieldTypeList extends StatelessWidget with FlowyOverlayDelegate {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String identifier() {
|
|
||||||
return (FieldTypeList).toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FieldTypeCell extends StatelessWidget {
|
class FieldTypeCell extends StatelessWidget {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
|
|||||||
context
|
context
|
||||||
.read<DateTypeOptionBloc>()
|
.read<DateTypeOptionBloc>()
|
||||||
.add(DateTypeOptionEvent.didSelectDateFormat(format));
|
.add(DateTypeOptionEvent.didSelectDateFormat(format));
|
||||||
PopoverContainer.of(popoverContext).closeAll();
|
PopoverContainer.of(popoverContext).close();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -97,7 +97,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
|
|||||||
context
|
context
|
||||||
.read<DateTypeOptionBloc>()
|
.read<DateTypeOptionBloc>()
|
||||||
.add(DateTypeOptionEvent.didSelectTimeFormat(format));
|
.add(DateTypeOptionEvent.didSelectTimeFormat(format));
|
||||||
PopoverContainer.of(popoverContext).closeAll();
|
PopoverContainer.of(popoverContext).close();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -202,11 +202,9 @@ class DateFormatList extends StatelessWidget {
|
|||||||
final cells = DateFormat.values.map((format) {
|
final cells = DateFormat.values.map((format) {
|
||||||
return DateFormatCell(
|
return DateFormatCell(
|
||||||
dateFormat: format,
|
dateFormat: format,
|
||||||
onSelected: (format) {
|
onSelected: onSelected,
|
||||||
onSelected(format);
|
isSelected: selectedFormat == format,
|
||||||
FlowyOverlay.of(context).remove(DateFormatList.identifier());
|
);
|
||||||
},
|
|
||||||
isSelected: selectedFormat == format);
|
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
@ -224,10 +222,6 @@ class DateFormatList extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String identifier() {
|
|
||||||
return (DateFormatList).toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DateFormatCell extends StatelessWidget {
|
class DateFormatCell extends StatelessWidget {
|
||||||
@ -293,10 +287,8 @@ class TimeFormatList extends StatelessWidget {
|
|||||||
return TimeFormatCell(
|
return TimeFormatCell(
|
||||||
isSelected: format == selectedFormat,
|
isSelected: format == selectedFormat,
|
||||||
timeFormat: format,
|
timeFormat: format,
|
||||||
onSelected: (format) {
|
onSelected: onSelected,
|
||||||
onSelected(format);
|
);
|
||||||
FlowyOverlay.of(context).remove(TimeFormatList.identifier());
|
|
||||||
});
|
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
@ -314,10 +306,6 @@ class TimeFormatList extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String identifier() {
|
|
||||||
return (TimeFormatList).toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TimeFormatCell extends StatelessWidget {
|
class TimeFormatCell extends StatelessWidget {
|
||||||
|
@ -82,7 +82,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
|
|||||||
context
|
context
|
||||||
.read<NumberTypeOptionBloc>()
|
.read<NumberTypeOptionBloc>()
|
||||||
.add(NumberTypeOptionEvent.didSelectFormat(format));
|
.add(NumberTypeOptionEvent.didSelectFormat(format));
|
||||||
PopoverContainer.of(popoverContext).closeAll();
|
PopoverContainer.of(popoverContext).close();
|
||||||
},
|
},
|
||||||
selectedFormat: state.typeOption.format,
|
selectedFormat: state.typeOption.format,
|
||||||
);
|
);
|
||||||
@ -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();
|
||||||
|
|
||||||
@ -147,10 +145,6 @@ class NumberFormatList extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String identifier() {
|
|
||||||
return (NumberFormatList).toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class NumberFormatCell extends StatelessWidget {
|
class NumberFormatCell extends StatelessWidget {
|
||||||
|
@ -207,13 +207,13 @@ class _OptionCellState extends State<_OptionCell> {
|
|||||||
context
|
context
|
||||||
.read<SelectOptionTypeOptionBloc>()
|
.read<SelectOptionTypeOptionBloc>()
|
||||||
.add(SelectOptionTypeOptionEvent.deleteOption(widget.option));
|
.add(SelectOptionTypeOptionEvent.deleteOption(widget.option));
|
||||||
PopoverContainer.of(popoverContext).closeAll();
|
PopoverContainer.of(popoverContext).close();
|
||||||
},
|
},
|
||||||
onUpdated: (updatedOption) {
|
onUpdated: (updatedOption) {
|
||||||
context
|
context
|
||||||
.read<SelectOptionTypeOptionBloc>()
|
.read<SelectOptionTypeOptionBloc>()
|
||||||
.add(SelectOptionTypeOptionEvent.updateOption(updatedOption));
|
.add(SelectOptionTypeOptionEvent.updateOption(updatedOption));
|
||||||
PopoverContainer.of(popoverContext).closeAll();
|
PopoverContainer.of(popoverContext).close();
|
||||||
},
|
},
|
||||||
key: ValueKey(widget.option.id),
|
key: ValueKey(widget.option.id),
|
||||||
);
|
);
|
||||||
|
@ -131,8 +131,10 @@ class _GridPropertyCell extends StatelessWidget {
|
|||||||
return FieldEditor(
|
return FieldEditor(
|
||||||
gridId: gridId,
|
gridId: gridId,
|
||||||
fieldName: fieldContext.name,
|
fieldName: fieldContext.name,
|
||||||
typeOptionLoader:
|
typeOptionLoader: FieldTypeOptionLoader(
|
||||||
FieldTypeOptionLoader(gridId: gridId, field: fieldContext.field),
|
gridId: gridId,
|
||||||
|
field: fieldContext.field,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user