mirror of
				https://github.com/AppFlowy-IO/AppFlowy.git
				synced 2025-11-04 03:54:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			288 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			288 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'package:appflowy/generated/flowy_svgs.g.dart';
 | 
						|
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';
 | 
						|
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';
 | 
						|
import 'package:flutter/material.dart';
 | 
						|
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';
 | 
						|
 | 
						|
/// Edit a database row with card style widget
 | 
						|
class RowCard extends StatefulWidget {
 | 
						|
  const RowCard({
 | 
						|
    super.key,
 | 
						|
    required this.fieldController,
 | 
						|
    required this.rowMeta,
 | 
						|
    required this.viewId,
 | 
						|
    required this.isEditing,
 | 
						|
    required this.rowCache,
 | 
						|
    required this.cellBuilder,
 | 
						|
    required this.openCard,
 | 
						|
    required this.onStartEditing,
 | 
						|
    required this.onEndEditing,
 | 
						|
    required this.styleConfiguration,
 | 
						|
    this.groupingFieldId,
 | 
						|
    this.groupId,
 | 
						|
  });
 | 
						|
 | 
						|
  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.
 | 
						|
  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;
 | 
						|
 | 
						|
  final RowCardStyleConfiguration styleConfiguration;
 | 
						|
 | 
						|
  @override
 | 
						|
  State<RowCard> createState() => _RowCardState();
 | 
						|
}
 | 
						|
 | 
						|
class _RowCardState extends State<RowCard> {
 | 
						|
  final popoverController = PopoverController();
 | 
						|
  late final CardBloc _cardBloc;
 | 
						|
  late final EditableRowNotifier rowNotifier;
 | 
						|
 | 
						|
  @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));
 | 
						|
 | 
						|
      if (rowNotifier.isEditing.value) {
 | 
						|
        widget.onStartEditing();
 | 
						|
      } else {
 | 
						|
        widget.onEndEditing();
 | 
						|
      }
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  void dispose() {
 | 
						|
    rowNotifier.dispose();
 | 
						|
    _cardBloc.close();
 | 
						|
    super.dispose();
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    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,
 | 
						|
        ),
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  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);
 | 
						|
      }
 | 
						|
 | 
						|
      return cellBuilder.build(
 | 
						|
        cellContext: cellContext,
 | 
						|
        cellNotifier: cellNotifier,
 | 
						|
        styleMap: styleConfiguration.cellStyleMap,
 | 
						|
        hasNotes: !rowMeta.isDocumentEmpty,
 | 
						|
      );
 | 
						|
    }).toList();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class MoreCardOptionsAccessory extends StatelessWidget with CardAccessory {
 | 
						|
  const MoreCardOptionsAccessory({super.key});
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    return Padding(
 | 
						|
      padding: const EdgeInsets.all(3.0),
 | 
						|
      child: FlowySvg(
 | 
						|
        FlowySvgs.three_dots_s,
 | 
						|
        color: Theme.of(context).hintColor,
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  AccessoryType get type => AccessoryType.more;
 | 
						|
}
 | 
						|
 | 
						|
class EditCardAccessory extends StatelessWidget with CardAccessory {
 | 
						|
  const EditCardAccessory({super.key, required this.rowNotifier});
 | 
						|
 | 
						|
  final EditableRowNotifier rowNotifier;
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context) {
 | 
						|
    return Padding(
 | 
						|
      padding: const EdgeInsets.all(3.0),
 | 
						|
      child: FlowySvg(
 | 
						|
        FlowySvgs.edit_s,
 | 
						|
        color: Theme.of(context).hintColor,
 | 
						|
      ),
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  void onTap(BuildContext context) => rowNotifier.becomeFirstResponder();
 | 
						|
 | 
						|
  @override
 | 
						|
  AccessoryType get type => AccessoryType.edit;
 | 
						|
}
 | 
						|
 | 
						|
class RowCardStyleConfiguration {
 | 
						|
  const RowCardStyleConfiguration({
 | 
						|
    required this.cellStyleMap,
 | 
						|
    this.showAccessory = true,
 | 
						|
    this.cardPadding = const EdgeInsets.all(8),
 | 
						|
    this.hoverStyle,
 | 
						|
  });
 | 
						|
 | 
						|
  final CardCellStyleMap cellStyleMap;
 | 
						|
  final bool showAccessory;
 | 
						|
  final EdgeInsets cardPadding;
 | 
						|
  final HoverStyle? hoverStyle;
 | 
						|
}
 |