| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 | import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/action.dart';import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';import 'package:appflowy_popover/appflowy_popover.dart';import 'package:flowy_infra/image.dart';import 'package:flowy_infra_ui/flowy_infra_ui.dart';import 'package:flowy_infra_ui/style_widget/hover.dart';import 'package:flutter/foundation.dart';import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import 'card_bloc.dart';import 'cells/card_cell.dart';import 'card_cell_builder.dart';import 'container/accessory.dart';import 'container/card_container.dart';/// Edit a database row with card style widgetclass RowCard<CustomCardData> extends StatefulWidget {  final RowPB row;  final String viewId;  final String? groupingFieldId;  /// Allows passing a custom card data object to the card. The card will be  /// returned in the [CardCellBuilder] and can be used to build the card.  final CustomCardData? cardData;  final bool isEditing;  final RowCache rowCache;  /// The [CardCellBuilder] is used to build the card cells.  final CardCellBuilder<CustomCardData> cellBuilder;  /// Called when the user taps on the card.  final void Function(BuildContext) openCard;  /// Called when the user starts editing the card.  final VoidCallback onStartEditing;  /// Called when the user ends editing the card.  final VoidCallback onEndEditing;  /// The [RowCardRenderHook] is used to render the card's cell. Other than  /// using the default cell builder. For example the [SelectOptionCardCell]  final RowCardRenderHook<CustomCardData>? renderHook;  final RowCardStyleConfiguration styleConfiguration;  const RowCard({    required this.row,    required this.viewId,    this.groupingFieldId,    required this.isEditing,    required this.rowCache,    required this.cellBuilder,    required this.openCard,    required this.onStartEditing,    required this.onEndEditing,    this.cardData,    this.styleConfiguration = const RowCardStyleConfiguration(      showAccessory: true,    ),    this.renderHook,    Key? key,  }) : super(key: key);  @override  State<RowCard<CustomCardData>> createState() =>      _RowCardState<CustomCardData>();}class _RowCardState<T> extends State<RowCard<T>> {  late final CardBloc _cardBloc;  late final EditableRowNotifier rowNotifier;  late final PopoverController popoverController;  AccessoryType? accessoryType;  @override  void initState() {    rowNotifier = EditableRowNotifier(isEditing: widget.isEditing);    _cardBloc = CardBloc(      viewId: widget.viewId,      groupFieldId: widget.groupingFieldId,      isEditing: widget.isEditing,      row: widget.row,      rowCache: widget.rowCache,    )..add(const RowCardEvent.initial());    rowNotifier.isEditing.addListener(() {      if (!mounted) return;      _cardBloc.add(RowCardEvent.setIsEditing(rowNotifier.isEditing.value));      if (rowNotifier.isEditing.value) {        widget.onStartEditing();      } else {        widget.onEndEditing();      }    });    popoverController = PopoverController();    super.initState();  }  @override  Widget build(BuildContext context) {    return BlocProvider.value(      value: _cardBloc,      child: BlocBuilder<CardBloc, RowCardState>(        buildWhen: (previous, current) {          // Rebuild when:          // 1.If the length of the cells is not the same          // 2.isEditing changed          if (previous.cells.length != current.cells.length ||              previous.isEditing != current.isEditing) {            return true;          }          // 3.Compare the content of the cells. The cells consists of          // list of [BoardCellEquatable] that extends the [Equatable].          return !listEquals(previous.cells, current.cells);        },        builder: (context, state) {          return AppFlowyPopover(            controller: popoverController,            triggerActions: PopoverTriggerFlags.none,            constraints: BoxConstraints.loose(const Size(140, 200)),            margin: const EdgeInsets.all(6),            direction: PopoverDirection.rightWithCenterAligned,            popupBuilder: (popoverContext) => _handlePopoverBuilder(              context,              popoverContext,            ),            child: RowCardContainer(              buildAccessoryWhen: () => state.isEditing == false,              accessoryBuilder: (context) {                if (widget.styleConfiguration.showAccessory == false) {                  return [];                } else {                  return [                    _CardEditOption(rowNotifier: rowNotifier),                    _CardMoreOption(),                  ];                }              },              openAccessory: _handleOpenAccessory,              openCard: (context) => widget.openCard(context),              child: _CardContent<T>(                rowNotifier: rowNotifier,                cellBuilder: widget.cellBuilder,                styleConfiguration: widget.styleConfiguration,                cells: state.cells,                renderHook: widget.renderHook,                cardData: widget.cardData,              ),            ),          );        },      ),    );  }  void _handleOpenAccessory(AccessoryType newAccessoryType) {    accessoryType = newAccessoryType;    switch (newAccessoryType) {      case AccessoryType.edit:        break;      case AccessoryType.more:        popoverController.show();        break;    }  }  Widget _handlePopoverBuilder(    BuildContext context,    BuildContext popoverContext,  ) {    switch (accessoryType!) {      case AccessoryType.edit:        throw UnimplementedError();      case AccessoryType.more:        return RowActions(          rowData: context.read<CardBloc>().rowInfo(),        );    }  }  @override  Future<void> dispose() async {    rowNotifier.dispose();    _cardBloc.close();    super.dispose();  }}class _CardContent<CustomCardData> extends StatelessWidget {  final CardCellBuilder<CustomCardData> cellBuilder;  final EditableRowNotifier rowNotifier;  final List<DatabaseCellContext> cells;  final RowCardRenderHook<CustomCardData>? renderHook;  final CustomCardData? cardData;  final RowCardStyleConfiguration styleConfiguration;  const _CardContent({    required this.rowNotifier,    required this.cellBuilder,    required this.cells,    required this.cardData,    required this.styleConfiguration,    this.renderHook,    Key? key,  }) : super(key: key);  @override  Widget build(BuildContext context) {    if (styleConfiguration.hoverStyle != null) {      return FlowyHover(        style: styleConfiguration.hoverStyle,        child: Padding(          padding: styleConfiguration.cardPadding,          child: Column(            mainAxisSize: MainAxisSize.min,            children: _makeCells(context, cells),          ),        ),      );    }    return Padding(      padding: styleConfiguration.cardPadding,      child: Column(        mainAxisSize: MainAxisSize.min,        children: _makeCells(context, cells),      ),    );  }  List<Widget> _makeCells(    BuildContext context,    List<DatabaseCellContext> cells,  ) {    final List<Widget> children = [];    // Remove all the cell listeners.    rowNotifier.unbind();    cells.asMap().forEach(      (int index, DatabaseCellContext cellContext) {        final isEditing = index == 0 ? rowNotifier.isEditing.value : false;        final cellNotifier = EditableCardNotifier(isEditing: isEditing);        if (index == 0) {          // Only use the first cell to receive user's input when click the edit          // button          rowNotifier.bindCell(cellContext, cellNotifier);        }        final child = Padding(          key: cellContext.key(),          padding: styleConfiguration.cellPadding,          child: cellBuilder.buildCell(            cellContext: cellContext,            cellNotifier: cellNotifier,            renderHook: renderHook,            cardData: cardData,          ),        );        children.add(child);      },    );    return children;  }}class _CardMoreOption extends StatelessWidget with CardAccessory {  _CardMoreOption({Key? key}) : super(key: key);  @override  Widget build(BuildContext context) {    return Padding(      padding: const EdgeInsets.all(3.0),      child: svgWidget(        'grid/details',        color: Theme.of(context).iconTheme.color,      ),    );  }  @override  AccessoryType get type => AccessoryType.more;}class _CardEditOption extends StatelessWidget with CardAccessory {  final EditableRowNotifier rowNotifier;  const _CardEditOption({    required this.rowNotifier,    Key? key,  }) : super(key: key);  @override  Widget build(BuildContext context) {    return Padding(      padding: const EdgeInsets.all(3.0),      child: svgWidget(        'editor/edit',        color: Theme.of(context).iconTheme.color,      ),    );  }  @override  void onTap(BuildContext context) => rowNotifier.becomeFirstResponder();  @override  AccessoryType get type => AccessoryType.edit;}class RowCardStyleConfiguration {  final bool showAccessory;  final EdgeInsets cellPadding;  final EdgeInsets cardPadding;  final HoverStyle? hoverStyle;  const RowCardStyleConfiguration({    this.showAccessory = true,    this.cellPadding = const EdgeInsets.only(left: 4, right: 4),    this.cardPadding = const EdgeInsets.all(8),    this.hoverStyle,  });}
 |