2023-08-14 13:34:01 -07:00
|
|
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
2023-11-29 19:01:29 -07:00
|
|
|
import 'package:appflowy/mobile/presentation/database/card/card.dart';
|
2024-01-24 23:59:45 +08:00
|
|
|
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
|
|
|
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
|
2024-01-05 17:30:54 +08:00
|
|
|
import 'package:appflowy/plugins/database/application/row/row_cache.dart';
|
|
|
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/row/action.dart';
|
2023-04-28 14:08:53 +08:00
|
|
|
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
2023-12-15 18:20:44 +08:00
|
|
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
2022-09-19 18:12:41 +08:00
|
|
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
2024-01-24 23:59:45 +08:00
|
|
|
import 'package:collection/collection.dart';
|
2022-09-19 15:36:59 +08:00
|
|
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
2023-05-31 16:52:37 +08:00
|
|
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
2022-08-10 17:59:28 +08:00
|
|
|
import 'package:flutter/material.dart';
|
2022-08-12 20:10:56 +08:00
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
2023-11-26 15:10:48 +08:00
|
|
|
|
2023-03-08 21:19:44 +08:00
|
|
|
import 'card_bloc.dart';
|
2024-01-24 23:59:45 +08:00
|
|
|
import '../cell/card_cell_builder.dart';
|
|
|
|
import '../cell/card_cell_skeleton/card_cell.dart';
|
2022-09-19 15:36:59 +08:00
|
|
|
import 'container/accessory.dart';
|
|
|
|
import 'container/card_container.dart';
|
2022-08-10 17:59:28 +08:00
|
|
|
|
2023-04-30 20:50:24 +08:00
|
|
|
/// Edit a database row with card style widget
|
2024-01-24 23:59:45 +08:00
|
|
|
class RowCard extends StatefulWidget {
|
|
|
|
final FieldController fieldController;
|
2023-06-14 22:16:33 +08:00
|
|
|
final RowMetaPB rowMeta;
|
2023-01-31 08:28:31 +08:00
|
|
|
final String viewId;
|
2023-04-30 20:50:24 +08:00
|
|
|
final String? groupingFieldId;
|
2023-08-01 08:49:48 +05:30
|
|
|
final String? groupId;
|
2023-04-30 20:50:24 +08:00
|
|
|
|
2022-08-13 16:23:44 +08:00
|
|
|
final bool isEditing;
|
2023-03-08 21:19:44 +08:00
|
|
|
final RowCache rowCache;
|
2023-04-30 20:50:24 +08:00
|
|
|
|
|
|
|
/// The [CardCellBuilder] is used to build the card cells.
|
2024-01-24 23:59:45 +08:00
|
|
|
final CardCellBuilder cellBuilder;
|
2023-04-30 20:50:24 +08:00
|
|
|
|
|
|
|
/// Called when the user taps on the card.
|
2022-08-16 18:02:39 +08:00
|
|
|
final void Function(BuildContext) openCard;
|
2023-04-30 20:50:24 +08:00
|
|
|
|
|
|
|
/// Called when the user starts editing the card.
|
2022-10-06 22:26:18 +08:00
|
|
|
final VoidCallback onStartEditing;
|
2023-04-30 20:50:24 +08:00
|
|
|
|
|
|
|
/// Called when the user ends editing the card.
|
2022-10-06 22:26:18 +08:00
|
|
|
final VoidCallback onEndEditing;
|
2022-08-12 20:10:56 +08:00
|
|
|
|
2023-04-30 20:50:24 +08:00
|
|
|
final RowCardStyleConfiguration styleConfiguration;
|
|
|
|
|
|
|
|
const RowCard({
|
2023-11-09 00:30:50 +01:00
|
|
|
super.key,
|
2024-01-24 23:59:45 +08:00
|
|
|
required this.fieldController,
|
2023-06-14 22:16:33 +08:00
|
|
|
required this.rowMeta,
|
2023-01-31 08:28:31 +08:00
|
|
|
required this.viewId,
|
2022-08-13 16:23:44 +08:00
|
|
|
required this.isEditing,
|
2023-03-08 21:19:44 +08:00
|
|
|
required this.rowCache,
|
2022-08-12 20:10:56 +08:00
|
|
|
required this.cellBuilder,
|
2022-08-16 18:02:39 +08:00
|
|
|
required this.openCard,
|
2022-10-06 22:26:18 +08:00
|
|
|
required this.onStartEditing,
|
|
|
|
required this.onEndEditing,
|
2024-01-24 23:59:45 +08:00
|
|
|
required this.styleConfiguration,
|
2023-11-09 00:30:50 +01:00
|
|
|
this.groupingFieldId,
|
|
|
|
this.groupId,
|
|
|
|
});
|
2022-08-12 20:10:56 +08:00
|
|
|
|
|
|
|
@override
|
2024-01-24 23:59:45 +08:00
|
|
|
State<RowCard> createState() => _RowCardState();
|
2022-08-12 20:10:56 +08:00
|
|
|
}
|
|
|
|
|
2024-01-24 23:59:45 +08:00
|
|
|
class _RowCardState extends State<RowCard> {
|
2023-11-24 00:25:12 +08:00
|
|
|
final popoverController = PopoverController();
|
2023-05-31 16:52:37 +08:00
|
|
|
late final CardBloc _cardBloc;
|
|
|
|
late final EditableRowNotifier rowNotifier;
|
2022-08-12 20:10:56 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
2023-11-09 00:30:50 +01:00
|
|
|
super.initState();
|
2022-09-07 11:33:42 +08:00
|
|
|
rowNotifier = EditableRowNotifier(isEditing: widget.isEditing);
|
2023-03-08 21:19:44 +08:00
|
|
|
_cardBloc = CardBloc(
|
2024-01-24 23:59:45 +08:00
|
|
|
fieldController: widget.fieldController,
|
2023-01-31 08:28:31 +08:00
|
|
|
viewId: widget.viewId,
|
2023-04-30 20:50:24 +08:00
|
|
|
groupFieldId: widget.groupingFieldId,
|
2022-09-07 11:33:42 +08:00
|
|
|
isEditing: widget.isEditing,
|
2023-06-14 22:16:33 +08:00
|
|
|
rowMeta: widget.rowMeta,
|
2023-03-08 21:19:44 +08:00
|
|
|
rowCache: widget.rowCache,
|
2024-01-24 23:59:45 +08:00
|
|
|
)..add(const CardEvent.initial());
|
2022-09-07 11:33:42 +08:00
|
|
|
|
|
|
|
rowNotifier.isEditing.addListener(() {
|
|
|
|
if (!mounted) return;
|
2024-01-24 23:59:45 +08:00
|
|
|
_cardBloc.add(CardEvent.setIsEditing(rowNotifier.isEditing.value));
|
2022-10-06 22:26:18 +08:00
|
|
|
|
|
|
|
if (rowNotifier.isEditing.value) {
|
|
|
|
widget.onStartEditing();
|
|
|
|
} else {
|
|
|
|
widget.onEndEditing();
|
|
|
|
}
|
2022-09-07 11:33:42 +08:00
|
|
|
});
|
2022-08-12 20:10:56 +08:00
|
|
|
}
|
2022-08-10 17:59:28 +08:00
|
|
|
|
2024-01-24 23:59:45 +08:00
|
|
|
@override
|
|
|
|
Future<void> dispose() async {
|
|
|
|
rowNotifier.dispose();
|
|
|
|
_cardBloc.close();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
2022-08-10 17:59:28 +08:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2022-08-12 20:10:56 +08:00
|
|
|
return BlocProvider.value(
|
|
|
|
value: _cardBloc,
|
2024-01-24 23:59:45 +08:00
|
|
|
child: BlocBuilder<CardBloc, CardState>(
|
|
|
|
builder: (context, state) =>
|
|
|
|
PlatformExtension.isMobile ? _mobile(state) : _desktop(state),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2023-11-24 00:25:12 +08:00
|
|
|
|
2024-01-24 23:59:45 +08:00
|
|
|
Widget _mobile(CardState state) {
|
|
|
|
return GestureDetector(
|
|
|
|
onTap: () => widget.openCard(context),
|
|
|
|
behavior: HitTestBehavior.opaque,
|
|
|
|
child: MobileCardContent(
|
|
|
|
rowMeta: state.rowMeta,
|
|
|
|
cellBuilder: widget.cellBuilder,
|
|
|
|
styleConfiguration: widget.styleConfiguration,
|
|
|
|
cells: state.cells,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget _desktop(CardState state) {
|
|
|
|
final accessories = widget.styleConfiguration.showAccessory
|
|
|
|
? <CardAccessory>[
|
|
|
|
EditCardAccessory(rowNotifier: rowNotifier),
|
|
|
|
const MoreCardOptionsAccessory(),
|
|
|
|
]
|
|
|
|
: null;
|
|
|
|
return AppFlowyPopover(
|
|
|
|
controller: popoverController,
|
|
|
|
triggerActions: PopoverTriggerFlags.none,
|
|
|
|
constraints: BoxConstraints.loose(const Size(140, 200)),
|
|
|
|
direction: PopoverDirection.rightWithCenterAligned,
|
|
|
|
popupBuilder: (_) {
|
|
|
|
return RowActionMenu.board(
|
|
|
|
viewId: _cardBloc.viewId,
|
|
|
|
rowId: _cardBloc.rowId,
|
|
|
|
groupId: widget.groupId,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
child: RowCardContainer(
|
|
|
|
buildAccessoryWhen: () => state.isEditing == false,
|
|
|
|
accessories: accessories ?? [],
|
|
|
|
openAccessory: _handleOpenAccessory,
|
|
|
|
openCard: widget.openCard,
|
|
|
|
child: _CardContent(
|
|
|
|
rowMeta: state.rowMeta,
|
|
|
|
rowNotifier: rowNotifier,
|
|
|
|
cellBuilder: widget.cellBuilder,
|
|
|
|
styleConfiguration: widget.styleConfiguration,
|
|
|
|
cells: state.cells,
|
|
|
|
),
|
2022-08-12 20:10:56 +08:00
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-09-19 15:36:59 +08:00
|
|
|
void _handleOpenAccessory(AccessoryType newAccessoryType) {
|
|
|
|
switch (newAccessoryType) {
|
|
|
|
case AccessoryType.edit:
|
|
|
|
break;
|
|
|
|
case AccessoryType.more:
|
|
|
|
popoverController.show();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-09-18 11:06:04 +08:00
|
|
|
}
|
|
|
|
|
2024-01-24 23:59:45 +08:00
|
|
|
class _CardContent extends StatelessWidget {
|
2023-03-08 21:19:44 +08:00
|
|
|
const _CardContent({
|
2024-01-24 23:59:45 +08:00
|
|
|
required this.rowMeta,
|
2022-09-18 11:06:04 +08:00
|
|
|
required this.rowNotifier,
|
|
|
|
required this.cellBuilder,
|
|
|
|
required this.cells,
|
2023-04-30 20:50:24 +08:00
|
|
|
required this.styleConfiguration,
|
2023-11-09 00:30:50 +01:00
|
|
|
});
|
|
|
|
|
2024-01-24 23:59:45 +08:00
|
|
|
final RowMetaPB rowMeta;
|
2023-11-09 00:30:50 +01:00
|
|
|
final EditableRowNotifier rowNotifier;
|
2024-01-24 23:59:45 +08:00
|
|
|
final CardCellBuilder cellBuilder;
|
|
|
|
final List<CellContext> cells;
|
2023-11-09 00:30:50 +01:00
|
|
|
final RowCardStyleConfiguration styleConfiguration;
|
2023-11-26 15:10:48 +08:00
|
|
|
|
2022-09-18 11:06:04 +08:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2024-01-24 23:59:45 +08:00
|
|
|
final child = Padding(
|
|
|
|
padding: styleConfiguration.cardPadding,
|
2023-05-31 16:52:37 +08:00
|
|
|
child: Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
2024-01-24 23:59:45 +08:00
|
|
|
children: _makeCells(context, rowMeta, cells),
|
2023-05-31 16:52:37 +08:00
|
|
|
),
|
2022-09-18 11:06:04 +08:00
|
|
|
);
|
2024-01-24 23:59:45 +08:00
|
|
|
return styleConfiguration.hoverStyle == null
|
|
|
|
? child
|
|
|
|
: FlowyHover(
|
|
|
|
style: styleConfiguration.hoverStyle,
|
|
|
|
buildWhenOnHover: () => !rowNotifier.isEditing.value,
|
|
|
|
child: child,
|
|
|
|
);
|
2022-09-18 11:06:04 +08:00
|
|
|
}
|
|
|
|
|
2022-08-26 16:07:51 +08:00
|
|
|
List<Widget> _makeCells(
|
|
|
|
BuildContext context,
|
2024-01-24 23:59:45 +08:00
|
|
|
RowMetaPB rowMeta,
|
|
|
|
List<CellContext> cells,
|
2022-08-26 16:07:51 +08:00
|
|
|
) {
|
2022-09-18 11:06:04 +08:00
|
|
|
// Remove all the cell listeners.
|
2024-01-24 23:59:45 +08:00
|
|
|
rowNotifier.unbind();
|
2022-09-18 11:06:04 +08:00
|
|
|
|
2024-01-24 23:59:45 +08:00
|
|
|
return cells.mapIndexed((int index, CellContext cellContext) {
|
|
|
|
EditableCardNotifier? cellNotifier;
|
2022-09-18 11:06:04 +08:00
|
|
|
|
2023-11-09 00:30:50 +01:00
|
|
|
if (index == 0) {
|
2024-01-24 23:59:45 +08:00
|
|
|
cellNotifier =
|
|
|
|
EditableCardNotifier(isEditing: rowNotifier.isEditing.value);
|
|
|
|
rowNotifier.bindCell(cellContext, cellNotifier);
|
2023-11-09 00:30:50 +01:00
|
|
|
}
|
2022-09-06 21:10:41 +08:00
|
|
|
|
2024-01-24 23:59:45 +08:00
|
|
|
return cellBuilder.build(
|
|
|
|
cellContext: cellContext,
|
|
|
|
cellNotifier: cellNotifier,
|
|
|
|
styleMap: styleConfiguration.cellStyleMap,
|
|
|
|
hasNotes: !rowMeta.isDocumentEmpty,
|
2023-11-09 00:30:50 +01:00
|
|
|
);
|
2024-01-24 23:59:45 +08:00
|
|
|
}).toList();
|
2022-08-10 17:59:28 +08:00
|
|
|
}
|
|
|
|
}
|
2022-08-13 11:51:26 +08:00
|
|
|
|
2024-01-24 23:59:45 +08:00
|
|
|
class MoreCardOptionsAccessory extends StatelessWidget with CardAccessory {
|
|
|
|
const MoreCardOptionsAccessory({super.key});
|
2022-08-13 11:51:26 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2022-08-30 20:54:11 +08:00
|
|
|
return Padding(
|
|
|
|
padding: const EdgeInsets.all(3.0),
|
2023-08-14 13:34:01 -07:00
|
|
|
child: FlowySvg(
|
2023-11-24 00:25:12 +08:00
|
|
|
FlowySvgs.three_dots_s,
|
|
|
|
color: Theme.of(context).hintColor,
|
2022-11-10 14:22:18 +08:00
|
|
|
),
|
2022-08-30 20:54:11 +08:00
|
|
|
);
|
2022-08-13 11:51:26 +08:00
|
|
|
}
|
2024-01-24 23:59:45 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
AccessoryType get type => AccessoryType.more;
|
2022-08-13 11:51:26 +08:00
|
|
|
}
|
2022-08-30 20:54:11 +08:00
|
|
|
|
2024-01-24 23:59:45 +08:00
|
|
|
class EditCardAccessory extends StatelessWidget with CardAccessory {
|
2022-09-18 11:06:04 +08:00
|
|
|
final EditableRowNotifier rowNotifier;
|
2024-01-24 23:59:45 +08:00
|
|
|
const EditCardAccessory({
|
|
|
|
super.key,
|
2022-09-18 11:06:04 +08:00
|
|
|
required this.rowNotifier,
|
2023-12-08 20:01:54 +07:00
|
|
|
});
|
2022-08-30 20:54:11 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Padding(
|
|
|
|
padding: const EdgeInsets.all(3.0),
|
2023-08-14 13:34:01 -07:00
|
|
|
child: FlowySvg(
|
|
|
|
FlowySvgs.edit_s,
|
2023-11-24 00:25:12 +08:00
|
|
|
color: Theme.of(context).hintColor,
|
2022-08-30 20:54:11 +08:00
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2022-09-19 15:36:59 +08:00
|
|
|
void onTap(BuildContext context) => rowNotifier.becomeFirstResponder();
|
|
|
|
|
|
|
|
@override
|
|
|
|
AccessoryType get type => AccessoryType.edit;
|
2022-08-30 20:54:11 +08:00
|
|
|
}
|
2023-04-30 20:50:24 +08:00
|
|
|
|
|
|
|
class RowCardStyleConfiguration {
|
2024-01-24 23:59:45 +08:00
|
|
|
final CardCellStyleMap cellStyleMap;
|
2023-04-30 20:50:24 +08:00
|
|
|
final bool showAccessory;
|
2023-05-31 16:52:37 +08:00
|
|
|
final EdgeInsets cardPadding;
|
|
|
|
final HoverStyle? hoverStyle;
|
2023-04-30 20:50:24 +08:00
|
|
|
|
|
|
|
const RowCardStyleConfiguration({
|
2024-01-24 23:59:45 +08:00
|
|
|
required this.cellStyleMap,
|
2023-04-30 20:50:24 +08:00
|
|
|
this.showAccessory = true,
|
2023-05-31 16:52:37 +08:00
|
|
|
this.cardPadding = const EdgeInsets.all(8),
|
|
|
|
this.hoverStyle,
|
2023-04-30 20:50:24 +08:00
|
|
|
});
|
|
|
|
}
|