Browse Source

chore: add card container

appflowy 3 years ago
parent
commit
28e77ae68c

+ 4 - 2
frontend/app_flowy/lib/plugins/board/application/board_bloc.dart

@@ -108,8 +108,8 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
     );
   }
 
-  List<BoardColumnItem> _buildRows(List<RowPB> rows) {
-    return rows.map((row) {
+  List<AFColumnItem> _buildRows(List<RowPB> rows) {
+    final items = rows.map((row) {
       // final rowInfo = RowInfo(
       //   gridId: _dataController.gridId,
       //   blockId: row.blockId,
@@ -120,6 +120,8 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
       // );
       return BoardColumnItem(row: row);
     }).toList();
+
+    return <AFColumnItem>[...items];
   }
 
   Future<void> _loadGrid(Emitter<BoardState> emit) async {

+ 71 - 0
frontend/app_flowy/lib/plugins/board/application/card/board_checkbox_cell_bloc.dart

@@ -0,0 +1,71 @@
+import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'dart:async';
+
+part 'board_checkbox_cell_bloc.freezed.dart';
+
+class BoardCheckboxCellBloc
+    extends Bloc<BoardCheckboxCellEvent, BoardCheckboxCellState> {
+  final GridCheckboxCellController cellController;
+  void Function()? _onCellChangedFn;
+  BoardCheckboxCellBloc({
+    required this.cellController,
+  }) : super(BoardCheckboxCellState.initial(cellController)) {
+    on<BoardCheckboxCellEvent>(
+      (event, emit) async {
+        await event.when(
+          initial: () async {
+            _startListening();
+          },
+          didReceiveCellUpdate: (cellData) {
+            emit(state.copyWith(isSelected: _isSelected(cellData)));
+          },
+        );
+      },
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    if (_onCellChangedFn != null) {
+      cellController.removeListener(_onCellChangedFn!);
+      _onCellChangedFn = null;
+    }
+    cellController.dispose();
+    return super.close();
+  }
+
+  void _startListening() {
+    _onCellChangedFn = cellController.startListening(
+      onCellChanged: ((cellContent) {
+        if (!isClosed) {
+          add(BoardCheckboxCellEvent.didReceiveCellUpdate(cellContent ?? ""));
+        }
+      }),
+    );
+  }
+}
+
+@freezed
+class BoardCheckboxCellEvent with _$BoardCheckboxCellEvent {
+  const factory BoardCheckboxCellEvent.initial() = _InitialCell;
+  const factory BoardCheckboxCellEvent.didReceiveCellUpdate(
+      String cellContent) = _DidReceiveCellUpdate;
+}
+
+@freezed
+class BoardCheckboxCellState with _$BoardCheckboxCellState {
+  const factory BoardCheckboxCellState({
+    required bool isSelected,
+  }) = _CheckboxCellState;
+
+  factory BoardCheckboxCellState.initial(GridCellController context) {
+    return BoardCheckboxCellState(
+        isSelected: _isSelected(context.getCellData()));
+  }
+}
+
+bool _isSelected(String? cellData) {
+  return cellData == "Yes";
+}

+ 78 - 0
frontend/app_flowy/lib/plugins/board/application/card/board_url_cell_bloc.dart

@@ -0,0 +1,78 @@
+import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option_entities.pb.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'dart:async';
+
+part 'board_url_cell_bloc.freezed.dart';
+
+class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> {
+  final GridURLCellController cellController;
+  void Function()? _onCellChangedFn;
+  BoardURLCellBloc({
+    required this.cellController,
+  }) : super(BoardURLCellState.initial(cellController)) {
+    on<BoardURLCellEvent>(
+      (event, emit) async {
+        event.when(
+          initial: () {
+            _startListening();
+          },
+          didReceiveCellUpdate: (cellData) {
+            emit(state.copyWith(
+              content: cellData?.content ?? "",
+              url: cellData?.url ?? "",
+            ));
+          },
+          updateURL: (String url) {
+            cellController.saveCellData(url, deduplicate: true);
+          },
+        );
+      },
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    if (_onCellChangedFn != null) {
+      cellController.removeListener(_onCellChangedFn!);
+      _onCellChangedFn = null;
+    }
+    cellController.dispose();
+    return super.close();
+  }
+
+  void _startListening() {
+    _onCellChangedFn = cellController.startListening(
+      onCellChanged: ((cellData) {
+        if (!isClosed) {
+          add(BoardURLCellEvent.didReceiveCellUpdate(cellData));
+        }
+      }),
+    );
+  }
+}
+
+@freezed
+class BoardURLCellEvent with _$BoardURLCellEvent {
+  const factory BoardURLCellEvent.initial() = _InitialCell;
+  const factory BoardURLCellEvent.updateURL(String url) = _UpdateURL;
+  const factory BoardURLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) =
+      _DidReceiveCellUpdate;
+}
+
+@freezed
+class BoardURLCellState with _$BoardURLCellState {
+  const factory BoardURLCellState({
+    required String content,
+    required String url,
+  }) = _BoardURLCellState;
+
+  factory BoardURLCellState.initial(GridURLCellController context) {
+    final cellData = context.getCellData();
+    return BoardURLCellState(
+      content: cellData?.content ?? "",
+      url: cellData?.url ?? "",
+    );
+  }
+}

+ 21 - 3
frontend/app_flowy/lib/plugins/board/presentation/card/card.dart

@@ -1,10 +1,12 @@
 import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
 import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra/theme.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-
 import 'card_cell_builder.dart';
+import 'card_container.dart';
 
 class BoardCard extends StatefulWidget {
   final String gridId;
@@ -40,8 +42,10 @@ class _BoardCardState extends State<BoardCard> {
       value: _cardBloc,
       child: BlocBuilder<BoardCardBloc, BoardCardState>(
         builder: (context, state) {
-          return Padding(
-            padding: const EdgeInsets.all(8.0),
+          return BoardCardContainer(
+            accessoryBuilder: (context) {
+              return [const _CardMoreOption()];
+            },
             child: Column(
               children: _makeCells(context, state.gridCellMap),
             ),
@@ -64,3 +68,17 @@ class _BoardCardState extends State<BoardCard> {
     ).toList();
   }
 }
+
+class _CardMoreOption extends StatelessWidget with CardAccessory {
+  const _CardMoreOption({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return svgWidget('home/details', color: context.read<AppTheme>().iconColor);
+  }
+
+  @override
+  void onTap(BuildContext context) {
+    print('show options');
+  }
+}

+ 133 - 0
frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart

@@ -0,0 +1,133 @@
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/hover.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:styled_widget/styled_widget.dart';
+
+class BoardCardContainer extends StatelessWidget {
+  final Widget child;
+  final CardAccessoryBuilder? accessoryBuilder;
+  const BoardCardContainer({
+    required this.child,
+    this.accessoryBuilder,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return ChangeNotifierProvider(
+      create: (_) => _CardContainerNotifier(),
+      child: Consumer<_CardContainerNotifier>(
+        builder: (context, notifier, _) {
+          Widget container = Center(child: child);
+          if (accessoryBuilder != null) {
+            final accessories = accessoryBuilder!(context);
+            if (accessories.isNotEmpty) {
+              container = _CardEnterRegion(
+                child: container,
+                accessories: accessories,
+              );
+            }
+          }
+          return Padding(
+            padding: const EdgeInsets.all(8),
+            child: container,
+          );
+        },
+      ),
+    );
+  }
+}
+
+abstract class CardAccessory implements Widget {
+  void onTap(BuildContext context);
+}
+
+typedef CardAccessoryBuilder = List<CardAccessory> Function(
+  BuildContext buildContext,
+);
+
+class CardAccessoryContainer extends StatelessWidget {
+  final List<CardAccessory> accessories;
+  const CardAccessoryContainer({required this.accessories, Key? key})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.read<AppTheme>();
+    final children = accessories.map((accessory) {
+      final hover = FlowyHover(
+        style: HoverStyle(
+          hoverColor: theme.hover,
+          backgroundColor: theme.surface,
+        ),
+        builder: (_, onHover) => Container(
+          width: 26,
+          height: 26,
+          padding: const EdgeInsets.all(3),
+          child: accessory,
+        ),
+      );
+      return GestureDetector(
+        child: hover,
+        behavior: HitTestBehavior.opaque,
+        onTap: () => accessory.onTap(context),
+      );
+    }).toList();
+
+    return Wrap(children: children, spacing: 6);
+  }
+}
+
+class _CardEnterRegion extends StatelessWidget {
+  final Widget child;
+  final List<CardAccessory> accessories;
+  const _CardEnterRegion(
+      {required this.child, required this.accessories, Key? key})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Selector<_CardContainerNotifier, bool>(
+      selector: (context, notifier) => notifier.onEnter,
+      builder: (context, onEnter, _) {
+        List<Widget> children = [child];
+        if (onEnter) {
+          children.add(CardAccessoryContainer(accessories: accessories)
+              .positioned(right: 0));
+        }
+
+        return MouseRegion(
+          cursor: SystemMouseCursors.click,
+          onEnter: (p) =>
+              Provider.of<_CardContainerNotifier>(context, listen: false)
+                  .onEnter = true,
+          onExit: (p) =>
+              Provider.of<_CardContainerNotifier>(context, listen: false)
+                  .onEnter = false,
+          child: IntrinsicHeight(
+              child: Stack(
+            alignment: AlignmentDirectional.center,
+            fit: StackFit.expand,
+            children: children,
+          )),
+        );
+      },
+    );
+  }
+}
+
+class _CardContainerNotifier extends ChangeNotifier {
+  bool _onEnter = false;
+
+  _CardContainerNotifier();
+
+  set onEnter(bool value) {
+    if (_onEnter != value) {
+      _onEnter = value;
+      notifyListeners();
+    }
+  }
+
+  bool get onEnter => _onEnter;
+}

+ 2 - 12
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_accessory.dart

@@ -8,6 +8,8 @@ import 'package:styled_widget/styled_widget.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:easy_localization/easy_localization.dart';
 
+import 'cell_builder.dart';
+
 class GridCellAccessoryBuildContext {
   final BuildContext anchorContext;
   final bool isCellEditing;
@@ -57,18 +59,6 @@ class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
   bool enable() => !isCellEditing;
 }
 
-typedef AccessoryBuilder = List<GridCellAccessory> Function(
-    GridCellAccessoryBuildContext buildContext);
-
-abstract class CellAccessory extends Widget {
-  const CellAccessory({Key? key}) : super(key: key);
-
-  // The hover will show if the isHover's value is true
-  ValueNotifier<bool>? get onAccessoryHover;
-
-  AccessoryBuilder? get accessoryBuilder;
-}
-
 class AccessoryHover extends StatefulWidget {
   final CellAccessory child;
   final EdgeInsets contentPadding;

+ 12 - 0
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart

@@ -94,6 +94,18 @@ abstract class CellEditable {
   ValueNotifier<bool> get onCellEditing;
 }
 
+typedef AccessoryBuilder = List<GridCellAccessory> Function(
+    GridCellAccessoryBuildContext buildContext);
+
+abstract class CellAccessory extends Widget {
+  const CellAccessory({Key? key}) : super(key: key);
+
+  // The hover will show if the isHover's value is true
+  ValueNotifier<bool>? get onAccessoryHover;
+
+  AccessoryBuilder? get accessoryBuilder;
+}
+
 abstract class GridCellWidget extends StatefulWidget
     implements CellAccessory, CellEditable, CellShortcuts {
   GridCellWidget({Key? key}) : super(key: key) {

+ 20 - 16
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart

@@ -25,24 +25,28 @@ class CellContainer extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return ChangeNotifierProxyProvider<RegionStateNotifier,
-        CellContainerNotifier>(
-      create: (_) => CellContainerNotifier(child),
+        _CellContainerNotifier>(
+      create: (_) => _CellContainerNotifier(child),
       update: (_, rowStateNotifier, cellStateNotifier) =>
           cellStateNotifier!..onEnter = rowStateNotifier.onEnter,
-      child: Selector<CellContainerNotifier, bool>(
+      child: Selector<_CellContainerNotifier, bool>(
         selector: (context, notifier) => notifier.isFocus,
         builder: (context, isFocus, _) {
           Widget container = Center(child: GridCellShortcuts(child: child));
 
           if (accessoryBuilder != null) {
-            final accessories = accessoryBuilder!(GridCellAccessoryBuildContext(
-              anchorContext: context,
-              isCellEditing: isFocus,
-            ));
+            final accessories = accessoryBuilder!(
+              GridCellAccessoryBuildContext(
+                anchorContext: context,
+                isCellEditing: isFocus,
+              ),
+            );
 
             if (accessories.isNotEmpty) {
-              container =
-                  CellEnterRegion(child: container, accessories: accessories);
+              container = _GridCellEnterRegion(
+                child: container,
+                accessories: accessories,
+              );
             }
           }
 
@@ -74,16 +78,16 @@ class CellContainer extends StatelessWidget {
   }
 }
 
-class CellEnterRegion extends StatelessWidget {
+class _GridCellEnterRegion extends StatelessWidget {
   final Widget child;
   final List<GridCellAccessory> accessories;
-  const CellEnterRegion(
+  const _GridCellEnterRegion(
       {required this.child, required this.accessories, Key? key})
       : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return Selector<CellContainerNotifier, bool>(
+    return Selector<_CellContainerNotifier, bool>(
       selector: (context, notifier) => notifier.onEnter,
       builder: (context, onEnter, _) {
         List<Widget> children = [child];
@@ -95,10 +99,10 @@ class CellEnterRegion extends StatelessWidget {
         return MouseRegion(
           cursor: SystemMouseCursors.click,
           onEnter: (p) =>
-              Provider.of<CellContainerNotifier>(context, listen: false)
+              Provider.of<_CellContainerNotifier>(context, listen: false)
                   .onEnter = true,
           onExit: (p) =>
-              Provider.of<CellContainerNotifier>(context, listen: false)
+              Provider.of<_CellContainerNotifier>(context, listen: false)
                   .onEnter = false,
           child: Stack(
             alignment: AlignmentDirectional.center,
@@ -111,13 +115,13 @@ class CellEnterRegion extends StatelessWidget {
   }
 }
 
-class CellContainerNotifier extends ChangeNotifier {
+class _CellContainerNotifier extends ChangeNotifier {
   final CellEditable cellEditable;
   VoidCallback? _onCellFocusListener;
   bool _isFocus = false;
   bool _onEnter = false;
 
-  CellContainerNotifier(this.cellEditable) {
+  _CellContainerNotifier(this.cellEditable) {
     _onCellFocusListener = () => isFocus = cellEditable.onCellFocus.value;
     cellEditable.onCellFocus.addListener(_onCellFocusListener!);
   }

+ 15 - 16
frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart

@@ -181,28 +181,27 @@ class RowContent extends StatelessWidget {
     return gridCellMap.values.map(
       (cellId) {
         final GridCellWidget child = builder.build(cellId);
-        accessoryBuilder(GridCellAccessoryBuildContext buildContext) {
-          final builder = child.accessoryBuilder;
-          List<GridCellAccessory> accessories = [];
-          if (cellId.field.isPrimary) {
-            accessories.add(PrimaryCellAccessory(
-              onTapCallback: onExpand,
-              isCellEditing: buildContext.isCellEditing,
-            ));
-          }
-
-          if (builder != null) {
-            accessories.addAll(builder(buildContext));
-          }
-          return accessories;
-        }
 
         return CellContainer(
           width: cellId.field.width.toDouble(),
           child: child,
           rowStateNotifier:
               Provider.of<RegionStateNotifier>(context, listen: false),
-          accessoryBuilder: accessoryBuilder,
+          accessoryBuilder: (buildContext) {
+            final builder = child.accessoryBuilder;
+            List<GridCellAccessory> accessories = [];
+            if (cellId.field.isPrimary) {
+              accessories.add(PrimaryCellAccessory(
+                onTapCallback: onExpand,
+                isCellEditing: buildContext.isCellEditing,
+              ));
+            }
+
+            if (builder != null) {
+              accessories.addAll(builder(buildContext));
+            }
+            return accessories;
+          },
         );
       },
     ).toList();

+ 8 - 7
frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart

@@ -23,18 +23,19 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
 
   @override
   void initState() {
-    final column1 = AFBoardColumnData(id: "To Do", items: [
+    List<AFColumnItem> a = [
       TextItem("Card 1"),
       TextItem("Card 2"),
-      RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'),
+      // RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'),
       TextItem("Card 4"),
-    ]);
-    final column2 = AFBoardColumnData(id: "In Progress", items: [
-      RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
-      TextItem("Card 6"),
+    ];
+    final column1 = AFBoardColumnData(id: "To Do", items: a);
+    final column2 = AFBoardColumnData(id: "In Progress", items: <AFColumnItem>[
+      // RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
+      // TextItem("Card 6"),
     ]);
 
-    final column3 = AFBoardColumnData(id: "Done", items: []);
+    final column3 = AFBoardColumnData(id: "Done", items: <AFColumnItem>[]);
 
     boardDataController.addColumn(column1);
     boardDataController.addColumn(column2);