mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: add basic relation field (#4397)
* feat: add basic relation field
* fix: clippy
* fix: tauri build 🤞
* chore: merge changes
* fix: merge main
* chore: initial code review pass
* fix: rust-lib test
* chore: code cleanup
* fix: unwrap or default
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/card_cell_skeleton/relation_card_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/card_cell_skeleton/timestamp_card_cell.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -84,6 +85,12 @@ class CardCellBuilder {
|
||||
databaseController: databaseController,
|
||||
cellContext: cellContext,
|
||||
),
|
||||
FieldType.Relation => RelationCardCell(
|
||||
key: key,
|
||||
style: isStyleOrNull(style),
|
||||
databaseController: databaseController,
|
||||
cellContext: cellContext,
|
||||
),
|
||||
_ => throw UnimplementedError,
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,82 @@
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'card_cell.dart';
|
||||
|
||||
class RelationCardCellStyle extends CardCellStyle {
|
||||
RelationCardCellStyle({
|
||||
required super.padding,
|
||||
required this.textStyle,
|
||||
required this.wrap,
|
||||
});
|
||||
|
||||
final TextStyle textStyle;
|
||||
final bool wrap;
|
||||
}
|
||||
|
||||
class RelationCardCell extends CardCell<RelationCardCellStyle> {
|
||||
const RelationCardCell({
|
||||
super.key,
|
||||
required super.style,
|
||||
required this.databaseController,
|
||||
required this.cellContext,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
final CellContext cellContext;
|
||||
|
||||
@override
|
||||
State<RelationCardCell> createState() => _RelationCellState();
|
||||
}
|
||||
|
||||
class _RelationCellState extends State<RelationCardCell> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) {
|
||||
return RelationCellBloc(
|
||||
cellController: makeCellController(
|
||||
widget.databaseController,
|
||||
widget.cellContext,
|
||||
).as(),
|
||||
);
|
||||
},
|
||||
child: BlocBuilder<RelationCellBloc, RelationCellState>(
|
||||
builder: (context, state) {
|
||||
if (state.rows.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final children = state.rows
|
||||
.map(
|
||||
(row) => FlowyText.medium(
|
||||
row.name,
|
||||
decoration: TextDecoration.underline,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
return Container(
|
||||
alignment: AlignmentDirectional.topStart,
|
||||
padding: widget.style.padding,
|
||||
child: widget.style.wrap
|
||||
? Wrap(spacing: 4, runSpacing: 4, children: children)
|
||||
: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import '../card_cell_skeleton/checkbox_card_cell.dart';
|
||||
import '../card_cell_skeleton/checklist_card_cell.dart';
|
||||
import '../card_cell_skeleton/date_card_cell.dart';
|
||||
import '../card_cell_skeleton/number_card_cell.dart';
|
||||
import '../card_cell_skeleton/relation_card_cell.dart';
|
||||
import '../card_cell_skeleton/select_option_card_cell.dart';
|
||||
import '../card_cell_skeleton/text_card_cell.dart';
|
||||
import '../card_cell_skeleton/timestamp_card_cell.dart';
|
||||
@ -73,5 +74,10 @@ CardCellStyleMap desktopCalendarCardCellStyleMap(BuildContext context) {
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
FieldType.Relation: RelationCardCellStyle(
|
||||
padding: padding,
|
||||
wrap: true,
|
||||
textStyle: textStyle,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import '../card_cell_skeleton/checkbox_card_cell.dart';
|
||||
import '../card_cell_skeleton/checklist_card_cell.dart';
|
||||
import '../card_cell_skeleton/date_card_cell.dart';
|
||||
import '../card_cell_skeleton/number_card_cell.dart';
|
||||
import '../card_cell_skeleton/relation_card_cell.dart';
|
||||
import '../card_cell_skeleton/select_option_card_cell.dart';
|
||||
import '../card_cell_skeleton/text_card_cell.dart';
|
||||
import '../card_cell_skeleton/timestamp_card_cell.dart';
|
||||
@ -73,5 +74,10 @@ CardCellStyleMap desktopBoardCardCellStyleMap(BuildContext context) {
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
FieldType.Relation: RelationCardCellStyle(
|
||||
padding: padding,
|
||||
wrap: true,
|
||||
textStyle: textStyle,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import '../card_cell_skeleton/checkbox_card_cell.dart';
|
||||
import '../card_cell_skeleton/checklist_card_cell.dart';
|
||||
import '../card_cell_skeleton/date_card_cell.dart';
|
||||
import '../card_cell_skeleton/number_card_cell.dart';
|
||||
import '../card_cell_skeleton/relation_card_cell.dart';
|
||||
import '../card_cell_skeleton/select_option_card_cell.dart';
|
||||
import '../card_cell_skeleton/text_card_cell.dart';
|
||||
import '../card_cell_skeleton/timestamp_card_cell.dart';
|
||||
@ -72,5 +73,10 @@ CardCellStyleMap mobileBoardCardCellStyleMap(BuildContext context) {
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
FieldType.Relation: RelationCardCellStyle(
|
||||
padding: padding,
|
||||
textStyle: textStyle,
|
||||
wrap: true,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../editable_cell_skeleton/relation.dart';
|
||||
|
||||
class DesktopGridRelationCellSkin extends IEditableRelationCellSkin {
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
RelationCellBloc bloc,
|
||||
RelationCellState state,
|
||||
PopoverController popoverController,
|
||||
) {
|
||||
return AppFlowyPopover(
|
||||
controller: popoverController,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
constraints: const BoxConstraints(maxWidth: 400, maxHeight: 400),
|
||||
margin: EdgeInsets.zero,
|
||||
onClose: () => cellContainerNotifier.isFocus = false,
|
||||
popupBuilder: (context) {
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
child: RelationCellEditor(
|
||||
selectedRowIds: state.rows.map((row) => row.rowId).toList(),
|
||||
databaseId: state.relatedDatabaseId,
|
||||
onSelectRow: (rowId) {
|
||||
bloc.add(RelationCellEvent.selectRow(rowId));
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: Wrap(
|
||||
runSpacing: 4.0,
|
||||
spacing: 4.0,
|
||||
children: state.rows
|
||||
.map(
|
||||
(row) => FlowyText.medium(
|
||||
row.name,
|
||||
decoration: TextDecoration.underline,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../editable_cell_skeleton/relation.dart';
|
||||
|
||||
class DesktopRowDetailRelationCellSkin extends IEditableRelationCellSkin {
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
RelationCellBloc bloc,
|
||||
RelationCellState state,
|
||||
PopoverController popoverController,
|
||||
) {
|
||||
return AppFlowyPopover(
|
||||
controller: popoverController,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
constraints: const BoxConstraints(maxWidth: 400, maxHeight: 400),
|
||||
margin: EdgeInsets.zero,
|
||||
onClose: () => cellContainerNotifier.isFocus = false,
|
||||
popupBuilder: (context) {
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
child: BlocBuilder<RelationCellBloc, RelationCellState>(
|
||||
builder: (context, state) => RelationCellEditor(
|
||||
selectedRowIds: state.rows.map((row) => row.rowId).toList(),
|
||||
databaseId: state.relatedDatabaseId,
|
||||
onSelectRow: (rowId) {
|
||||
context
|
||||
.read<RelationCellBloc>()
|
||||
.add(RelationCellEvent.selectRow(rowId));
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
child: Wrap(
|
||||
runSpacing: 4.0,
|
||||
spacing: 4.0,
|
||||
children: state.rows
|
||||
.map(
|
||||
(row) => FlowyText.medium(
|
||||
row.name,
|
||||
decoration: TextDecoration.underline,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import 'editable_cell_skeleton/checkbox.dart';
|
||||
import 'editable_cell_skeleton/checklist.dart';
|
||||
import 'editable_cell_skeleton/date.dart';
|
||||
import 'editable_cell_skeleton/number.dart';
|
||||
import 'editable_cell_skeleton/relation.dart';
|
||||
import 'editable_cell_skeleton/select_option.dart';
|
||||
import 'editable_cell_skeleton/text.dart';
|
||||
import 'editable_cell_skeleton/timestamp.dart';
|
||||
@ -106,6 +107,12 @@ class EditableCellBuilder {
|
||||
skin: IEditableURLCellSkin.fromStyle(style),
|
||||
key: key,
|
||||
),
|
||||
FieldType.Relation => EditableRelationCell(
|
||||
databaseController: databaseController,
|
||||
cellContext: cellContext,
|
||||
skin: IEditableRelationCellSkin.fromStyle(style),
|
||||
key: key,
|
||||
),
|
||||
_ => throw UnimplementedError(),
|
||||
};
|
||||
}
|
||||
@ -186,6 +193,12 @@ class EditableCellBuilder {
|
||||
skin: skinMap.urlSkin!,
|
||||
key: key,
|
||||
),
|
||||
FieldType.Relation => EditableRelationCell(
|
||||
databaseController: databaseController,
|
||||
cellContext: cellContext,
|
||||
skin: skinMap.relationSkin!,
|
||||
key: key,
|
||||
),
|
||||
_ => throw UnimplementedError(),
|
||||
};
|
||||
}
|
||||
@ -340,6 +353,7 @@ class EditableCellSkinMap {
|
||||
this.numberSkin,
|
||||
this.textSkin,
|
||||
this.urlSkin,
|
||||
this.relationSkin,
|
||||
});
|
||||
|
||||
final IEditableCheckboxCellSkin? checkboxSkin;
|
||||
@ -350,6 +364,7 @@ class EditableCellSkinMap {
|
||||
final IEditableNumberCellSkin? numberSkin;
|
||||
final IEditableTextCellSkin? textSkin;
|
||||
final IEditableURLCellSkin? urlSkin;
|
||||
final IEditableRelationCellSkin? relationSkin;
|
||||
|
||||
bool has(FieldType fieldType) {
|
||||
return switch (fieldType) {
|
||||
|
@ -0,0 +1,94 @@
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../desktop_grid/desktop_grid_relation_cell.dart';
|
||||
import '../desktop_row_detail/desktop_row_detail_relation_cell.dart';
|
||||
import '../mobile_grid/mobile_grid_relation_cell.dart';
|
||||
import '../mobile_row_detail/mobile_row_detail_relation_cell.dart';
|
||||
|
||||
abstract class IEditableRelationCellSkin {
|
||||
factory IEditableRelationCellSkin.fromStyle(EditableCellStyle style) {
|
||||
return switch (style) {
|
||||
EditableCellStyle.desktopGrid => DesktopGridRelationCellSkin(),
|
||||
EditableCellStyle.desktopRowDetail => DesktopRowDetailRelationCellSkin(),
|
||||
EditableCellStyle.mobileGrid => MobileGridRelationCellSkin(),
|
||||
EditableCellStyle.mobileRowDetail => MobileRowDetailRelationCellSkin(),
|
||||
};
|
||||
}
|
||||
|
||||
const IEditableRelationCellSkin();
|
||||
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
RelationCellBloc bloc,
|
||||
RelationCellState state,
|
||||
PopoverController popoverController,
|
||||
);
|
||||
}
|
||||
|
||||
class EditableRelationCell extends EditableCellWidget {
|
||||
EditableRelationCell({
|
||||
super.key,
|
||||
required this.databaseController,
|
||||
required this.cellContext,
|
||||
required this.skin,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
final CellContext cellContext;
|
||||
final IEditableRelationCellSkin skin;
|
||||
|
||||
@override
|
||||
GridCellState<EditableRelationCell> createState() => _RelationCellState();
|
||||
}
|
||||
|
||||
class _RelationCellState extends GridCellState<EditableRelationCell> {
|
||||
final PopoverController _popover = PopoverController();
|
||||
late final cellBloc = RelationCellBloc(
|
||||
cellController: makeCellController(
|
||||
widget.databaseController,
|
||||
widget.cellContext,
|
||||
).as(),
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: cellBloc,
|
||||
child: BlocBuilder<RelationCellBloc, RelationCellState>(
|
||||
builder: (context, state) {
|
||||
return widget.skin.build(
|
||||
context,
|
||||
widget.cellContainerNotifier,
|
||||
cellBloc,
|
||||
state,
|
||||
_popover,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onRequestFocus() {
|
||||
_popover.show();
|
||||
widget.cellContainerNotifier.isFocus = true;
|
||||
}
|
||||
|
||||
@override
|
||||
String? onCopy() => "";
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../editable_cell_skeleton/relation.dart';
|
||||
|
||||
class MobileGridRelationCellSkin extends IEditableRelationCellSkin {
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
RelationCellBloc bloc,
|
||||
RelationCellState state,
|
||||
PopoverController popoverController,
|
||||
) {
|
||||
return FlowyButton(
|
||||
radius: BorderRadius.zero,
|
||||
hoverColor: Colors.transparent,
|
||||
margin: EdgeInsets.zero,
|
||||
text: Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: state.rows
|
||||
.map(
|
||||
(row) => FlowyText(
|
||||
row.name,
|
||||
fontSize: 15,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
builder: (context) {
|
||||
return const FlowyText("Coming soon");
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/relation.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileRowDetailRelationCellSkin extends IEditableRelationCellSkin {
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
RelationCellBloc bloc,
|
||||
RelationCellState state,
|
||||
PopoverController popoverController,
|
||||
) {
|
||||
return InkWell(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(14)),
|
||||
onTap: () => showMobileBottomSheet(
|
||||
context,
|
||||
padding: EdgeInsets.zero,
|
||||
builder: (context) {
|
||||
return const FlowyText("Coming soon");
|
||||
},
|
||||
),
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 48,
|
||||
minWidth: double.infinity,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(14)),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 13),
|
||||
child: Wrap(
|
||||
runSpacing: 4.0,
|
||||
spacing: 4.0,
|
||||
children: state.rows
|
||||
.map(
|
||||
(row) => FlowyText(
|
||||
row.name,
|
||||
fontSize: 16,
|
||||
decoration: TextDecoration.underline,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../application/cell/bloc/relation_cell_bloc.dart';
|
||||
import '../../application/cell/bloc/relation_row_search_bloc.dart';
|
||||
|
||||
class RelationCellEditor extends StatelessWidget {
|
||||
const RelationCellEditor({
|
||||
super.key,
|
||||
required this.databaseId,
|
||||
required this.selectedRowIds,
|
||||
required this.onSelectRow,
|
||||
});
|
||||
|
||||
final String databaseId;
|
||||
final List<String> selectedRowIds;
|
||||
final void Function(String rowId) onSelectRow;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (databaseId.isEmpty) {
|
||||
// no i18n here because UX needs thorough checking.
|
||||
return const Center(
|
||||
child: FlowyText(
|
||||
'''
|
||||
No database has been selected,
|
||||
please select one first in the field editor.
|
||||
''',
|
||||
maxLines: null,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return BlocProvider<RelationRowSearchBloc>(
|
||||
create: (context) => RelationRowSearchBloc(
|
||||
databaseId: databaseId,
|
||||
),
|
||||
child: BlocBuilder<RelationCellBloc, RelationCellState>(
|
||||
builder: (context, cellState) {
|
||||
return BlocBuilder<RelationRowSearchBloc, RelationRowSearchState>(
|
||||
builder: (context, state) {
|
||||
final children = state.filteredRows
|
||||
.map(
|
||||
(row) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(
|
||||
row.name.trim().isEmpty
|
||||
? LocaleKeys.grid_title_placeholder.tr()
|
||||
: row.name,
|
||||
color: row.name.trim().isEmpty
|
||||
? Theme.of(context).hintColor
|
||||
: null,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
rightIcon: cellState.rows
|
||||
.map((e) => e.rowId)
|
||||
.contains(row.rowId)
|
||||
? FlowySvg(
|
||||
FlowySvgs.check_s,
|
||||
color: Theme.of(context).primaryColor,
|
||||
)
|
||||
: null,
|
||||
onTap: () => onSelectRow(row.rowId),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const VSpace(6.0),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0) +
|
||||
GridSize.typeOptionContentInsets,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText.regular(
|
||||
LocaleKeys.grid_relation_inRelatedDatabase.tr(),
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
const HSpace(2.0),
|
||||
FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 2,
|
||||
),
|
||||
text: FlowyText.regular(
|
||||
cellState.relatedDatabaseId,
|
||||
fontSize: 11,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: FlowyTextField(
|
||||
onChanged: (text) => context
|
||||
.read<RelationRowSearchBloc>()
|
||||
.add(RelationRowSearchEvent.updateFilter(text)),
|
||||
),
|
||||
),
|
||||
const VSpace(6.0),
|
||||
const TypeOptionSeparator(spacing: 0.0),
|
||||
if (state.filteredRows.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(6.0) +
|
||||
GridSize.typeOptionContentInsets,
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.grid_relation_emptySearchResult.tr(),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
)
|
||||
else
|
||||
Flexible(
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||
separatorBuilder: (context, index) =>
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
itemCount: children.length,
|
||||
itemBuilder: (context, index) => children[index],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -75,6 +75,7 @@ class _SelectOptionTextFieldState extends State<SelectOptionTextField> {
|
||||
@override
|
||||
void dispose() {
|
||||
widget.textController.removeListener(_onChanged);
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user