290 lines
8.0 KiB
Dart
Raw Normal View History

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';
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
import 'package:appflowy/plugins/database/application/row/row_cache.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/row/action.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
2022-09-19 18:12:41 +08:00
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
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';
import 'card_bloc.dart';
import '../cell/card_cell_builder.dart';
import '../cell/card_cell_skeleton/card_cell.dart';
import 'container/accessory.dart';
import 'container/card_container.dart';
2022-08-10 17:59:28 +08:00
/// Edit a database row with card style widget
class RowCard extends StatefulWidget {
final FieldController fieldController;
final RowMetaPB rowMeta;
final String viewId;
final String? groupingFieldId;
final String? groupId;
final bool isEditing;
final RowCache rowCache;
/// The [CardCellBuilder] is used to build the card cells.
final CardCellBuilder cellBuilder;
/// Called when the user taps on the card.
2022-08-16 18:02:39 +08:00
final void Function(BuildContext) openCard;
/// Called when the user starts editing the card.
2022-10-06 22:26:18 +08:00
final VoidCallback onStartEditing;
/// 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
final RowCardStyleConfiguration styleConfiguration;
const RowCard({
super.key,
required this.fieldController,
required this.rowMeta,
required this.viewId,
required this.isEditing,
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,
required this.styleConfiguration,
this.groupingFieldId,
this.groupId,
});
2022-08-12 20:10:56 +08:00
@override
State<RowCard> createState() => _RowCardState();
2022-08-12 20:10:56 +08:00
}
class _RowCardState extends State<RowCard> {
final popoverController = PopoverController();
late final CardBloc _cardBloc;
late final EditableRowNotifier rowNotifier;
2022-08-12 20:10:56 +08:00
@override
void initState() {
super.initState();
rowNotifier = EditableRowNotifier(isEditing: widget.isEditing);
_cardBloc = CardBloc(
fieldController: widget.fieldController,
viewId: widget.viewId,
groupFieldId: widget.groupingFieldId,
isEditing: widget.isEditing,
rowMeta: widget.rowMeta,
rowCache: widget.rowCache,
)..add(const CardEvent.initial());
rowNotifier.isEditing.addListener(() {
if (!mounted) return;
_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-08-12 20:10:56 +08:00
}
2022-08-10 17:59:28 +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,
child: BlocBuilder<CardBloc, CardState>(
builder: (context, state) =>
PlatformExtension.isMobile ? _mobile(state) : _desktop(state),
),
);
}
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
),
);
}
void _handleOpenAccessory(AccessoryType newAccessoryType) {
switch (newAccessoryType) {
case AccessoryType.edit:
break;
case AccessoryType.more:
popoverController.show();
break;
}
}
}
class _CardContent extends StatelessWidget {
const _CardContent({
required this.rowMeta,
required this.rowNotifier,
required this.cellBuilder,
required this.cells,
required this.styleConfiguration,
});
final RowMetaPB rowMeta;
final EditableRowNotifier rowNotifier;
final CardCellBuilder cellBuilder;
final List<CellContext> cells;
final RowCardStyleConfiguration styleConfiguration;
@override
Widget build(BuildContext context) {
final child = Padding(
padding: styleConfiguration.cardPadding,
child: Column(
mainAxisSize: MainAxisSize.min,
children: _makeCells(context, rowMeta, cells),
),
);
return styleConfiguration.hoverStyle == null
? child
: FlowyHover(
style: styleConfiguration.hoverStyle,
buildWhenOnHover: () => !rowNotifier.isEditing.value,
child: child,
);
}
List<Widget> _makeCells(
BuildContext context,
RowMetaPB rowMeta,
List<CellContext> cells,
) {
// Remove all the cell listeners.
rowNotifier.unbind();
return cells.mapIndexed((int index, CellContext cellContext) {
EditableCardNotifier? cellNotifier;
if (index == 0) {
cellNotifier =
EditableCardNotifier(isEditing: rowNotifier.isEditing.value);
rowNotifier.bindCell(cellContext, cellNotifier);
}
2022-09-06 21:10:41 +08:00
return cellBuilder.build(
cellContext: cellContext,
cellNotifier: cellNotifier,
styleMap: styleConfiguration.cellStyleMap,
hasNotes: !rowMeta.isDocumentEmpty,
);
}).toList();
2022-08-10 17:59:28 +08:00
}
}
2022-08-13 11:51:26 +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),
child: FlowySvg(
FlowySvgs.three_dots_s,
color: Theme.of(context).hintColor,
),
2022-08-30 20:54:11 +08:00
);
2022-08-13 11:51:26 +08:00
}
@override
AccessoryType get type => AccessoryType.more;
2022-08-13 11:51:26 +08:00
}
2022-08-30 20:54:11 +08:00
class EditCardAccessory extends StatelessWidget with CardAccessory {
final EditableRowNotifier rowNotifier;
const EditCardAccessory({
super.key,
required this.rowNotifier,
});
2022-08-30 20:54:11 +08:00
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(3.0),
child: FlowySvg(
FlowySvgs.edit_s,
color: Theme.of(context).hintColor,
2022-08-30 20:54:11 +08:00
),
);
}
@override
void onTap(BuildContext context) => rowNotifier.becomeFirstResponder();
@override
AccessoryType get type => AccessoryType.edit;
2022-08-30 20:54:11 +08:00
}
class RowCardStyleConfiguration {
final CardCellStyleMap cellStyleMap;
final bool showAccessory;
final EdgeInsets cardPadding;
final HoverStyle? hoverStyle;
const RowCardStyleConfiguration({
required this.cellStyleMap,
this.showAccessory = true,
this.cardPadding = const EdgeInsets.all(8),
this.hoverStyle,
});
}