card.dart 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
  2. import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/action.dart';
  3. import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
  4. import 'package:appflowy_popover/appflowy_popover.dart';
  5. import 'package:flowy_infra/image.dart';
  6. import 'package:flowy_infra_ui/flowy_infra_ui.dart';
  7. import 'package:flutter/foundation.dart';
  8. import 'package:flutter/material.dart';
  9. import 'package:flutter_bloc/flutter_bloc.dart';
  10. import 'card_bloc.dart';
  11. import 'cells/card_cell.dart';
  12. import 'card_cell_builder.dart';
  13. import 'container/accessory.dart';
  14. import 'container/card_container.dart';
  15. class Card<CustomCardData> extends StatefulWidget {
  16. final RowPB row;
  17. final String viewId;
  18. final String fieldId;
  19. final CustomCardData? cardData;
  20. final bool isEditing;
  21. final RowCache rowCache;
  22. final CardCellBuilder<CustomCardData> cellBuilder;
  23. final void Function(BuildContext) openCard;
  24. final VoidCallback onStartEditing;
  25. final VoidCallback onEndEditing;
  26. final CardConfiguration<CustomCardData>? configuration;
  27. const Card({
  28. required this.row,
  29. required this.viewId,
  30. required this.fieldId,
  31. required this.isEditing,
  32. required this.rowCache,
  33. required this.cellBuilder,
  34. required this.openCard,
  35. required this.onStartEditing,
  36. required this.onEndEditing,
  37. this.cardData,
  38. this.configuration,
  39. Key? key,
  40. }) : super(key: key);
  41. @override
  42. State<Card<CustomCardData>> createState() => _CardState<CustomCardData>();
  43. }
  44. class _CardState<T> extends State<Card<T>> {
  45. late CardBloc _cardBloc;
  46. late EditableRowNotifier rowNotifier;
  47. late PopoverController popoverController;
  48. AccessoryType? accessoryType;
  49. @override
  50. void initState() {
  51. rowNotifier = EditableRowNotifier(isEditing: widget.isEditing);
  52. _cardBloc = CardBloc(
  53. viewId: widget.viewId,
  54. groupFieldId: widget.fieldId,
  55. isEditing: widget.isEditing,
  56. row: widget.row,
  57. rowCache: widget.rowCache,
  58. )..add(const BoardCardEvent.initial());
  59. rowNotifier.isEditing.addListener(() {
  60. if (!mounted) return;
  61. _cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
  62. if (rowNotifier.isEditing.value) {
  63. widget.onStartEditing();
  64. } else {
  65. widget.onEndEditing();
  66. }
  67. });
  68. popoverController = PopoverController();
  69. super.initState();
  70. }
  71. @override
  72. Widget build(BuildContext context) {
  73. return BlocProvider.value(
  74. value: _cardBloc,
  75. child: BlocBuilder<CardBloc, BoardCardState>(
  76. buildWhen: (previous, current) {
  77. // Rebuild when:
  78. // 1.If the length of the cells is not the same
  79. // 2.isEditing changed
  80. if (previous.cells.length != current.cells.length ||
  81. previous.isEditing != current.isEditing) {
  82. return true;
  83. }
  84. // 3.Compare the content of the cells. The cells consists of
  85. // list of [BoardCellEquatable] that extends the [Equatable].
  86. return !listEquals(previous.cells, current.cells);
  87. },
  88. builder: (context, state) {
  89. return AppFlowyPopover(
  90. controller: popoverController,
  91. triggerActions: PopoverTriggerFlags.none,
  92. constraints: BoxConstraints.loose(const Size(140, 200)),
  93. margin: const EdgeInsets.all(6),
  94. direction: PopoverDirection.rightWithCenterAligned,
  95. popupBuilder: (popoverContext) => _handlePopoverBuilder(
  96. context,
  97. popoverContext,
  98. ),
  99. child: BoardCardContainer(
  100. buildAccessoryWhen: () => state.isEditing == false,
  101. accessoryBuilder: (context) {
  102. return [
  103. _CardEditOption(rowNotifier: rowNotifier),
  104. _CardMoreOption(),
  105. ];
  106. },
  107. openAccessory: _handleOpenAccessory,
  108. openCard: (context) => widget.openCard(context),
  109. child: _CardContent<T>(
  110. rowNotifier: rowNotifier,
  111. cellBuilder: widget.cellBuilder,
  112. cells: state.cells,
  113. cardConfiguration: widget.configuration,
  114. cardData: widget.cardData,
  115. ),
  116. ),
  117. );
  118. },
  119. ),
  120. );
  121. }
  122. void _handleOpenAccessory(AccessoryType newAccessoryType) {
  123. accessoryType = newAccessoryType;
  124. switch (newAccessoryType) {
  125. case AccessoryType.edit:
  126. break;
  127. case AccessoryType.more:
  128. popoverController.show();
  129. break;
  130. }
  131. }
  132. Widget _handlePopoverBuilder(
  133. BuildContext context,
  134. BuildContext popoverContext,
  135. ) {
  136. switch (accessoryType!) {
  137. case AccessoryType.edit:
  138. throw UnimplementedError();
  139. case AccessoryType.more:
  140. return RowActions(
  141. rowData: context.read<CardBloc>().rowInfo(),
  142. );
  143. }
  144. }
  145. @override
  146. Future<void> dispose() async {
  147. rowNotifier.dispose();
  148. _cardBloc.close();
  149. super.dispose();
  150. }
  151. }
  152. class _CardContent<CustomCardData> extends StatelessWidget {
  153. final CardCellBuilder<CustomCardData> cellBuilder;
  154. final EditableRowNotifier rowNotifier;
  155. final List<BoardCellEquatable> cells;
  156. final CardConfiguration<CustomCardData>? cardConfiguration;
  157. final CustomCardData? cardData;
  158. const _CardContent({
  159. required this.rowNotifier,
  160. required this.cellBuilder,
  161. required this.cells,
  162. required this.cardData,
  163. this.cardConfiguration,
  164. Key? key,
  165. }) : super(key: key);
  166. @override
  167. Widget build(BuildContext context) {
  168. return Column(
  169. mainAxisSize: MainAxisSize.min,
  170. children: _makeCells(context, cells),
  171. );
  172. }
  173. List<Widget> _makeCells(
  174. BuildContext context,
  175. List<BoardCellEquatable> cells,
  176. ) {
  177. final List<Widget> children = [];
  178. // Remove all the cell listeners.
  179. rowNotifier.unbind();
  180. cells.asMap().forEach(
  181. (int index, BoardCellEquatable cell) {
  182. final isEditing = index == 0 ? rowNotifier.isEditing.value : false;
  183. final cellNotifier = EditableCardNotifier(isEditing: isEditing);
  184. if (index == 0) {
  185. // Only use the first cell to receive user's input when click the edit
  186. // button
  187. rowNotifier.bindCell(cell.identifier, cellNotifier);
  188. }
  189. final child = Padding(
  190. key: cell.identifier.key(),
  191. padding: const EdgeInsets.only(left: 4, right: 4),
  192. child: cellBuilder.buildCell(
  193. cellId: cell.identifier,
  194. cellNotifier: cellNotifier,
  195. cardConfiguration: cardConfiguration,
  196. cardData: cardData,
  197. ),
  198. );
  199. children.add(child);
  200. },
  201. );
  202. return children;
  203. }
  204. }
  205. class _CardMoreOption extends StatelessWidget with CardAccessory {
  206. _CardMoreOption({Key? key}) : super(key: key);
  207. @override
  208. Widget build(BuildContext context) {
  209. return Padding(
  210. padding: const EdgeInsets.all(3.0),
  211. child: svgWidget(
  212. 'grid/details',
  213. color: Theme.of(context).iconTheme.color,
  214. ),
  215. );
  216. }
  217. @override
  218. AccessoryType get type => AccessoryType.more;
  219. }
  220. class _CardEditOption extends StatelessWidget with CardAccessory {
  221. final EditableRowNotifier rowNotifier;
  222. const _CardEditOption({
  223. required this.rowNotifier,
  224. Key? key,
  225. }) : super(key: key);
  226. @override
  227. Widget build(BuildContext context) {
  228. return Padding(
  229. padding: const EdgeInsets.all(3.0),
  230. child: svgWidget(
  231. 'editor/edit',
  232. color: Theme.of(context).iconTheme.color,
  233. ),
  234. );
  235. }
  236. @override
  237. void onTap(BuildContext context) => rowNotifier.becomeFirstResponder();
  238. @override
  239. AccessoryType get type => AccessoryType.edit;
  240. }