浏览代码

chore: update card ui by adding date,text,selectoption cell

appflowy 2 年之前
父节点
当前提交
9d72e36c19

+ 85 - 0
frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart

@@ -0,0 +1,85 @@
+import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'dart:async';
+part 'board_date_cell_bloc.freezed.dart';
+
+class BoardDateCellBloc extends Bloc<BoardDateCellEvent, BoardDateCellState> {
+  final GridDateCellController cellController;
+  void Function()? _onCellChangedFn;
+
+  BoardDateCellBloc({required this.cellController})
+      : super(BoardDateCellState.initial(cellController)) {
+    on<BoardDateCellEvent>(
+      (event, emit) async {
+        event.when(
+          initial: () => _startListening(),
+          didReceiveCellUpdate: (DateCellDataPB? cellData) {
+            emit(state.copyWith(
+                data: cellData, dateStr: _dateStrFromCellData(cellData)));
+          },
+          didReceiveFieldUpdate: (FieldPB value) =>
+              emit(state.copyWith(field: value)),
+        );
+      },
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    if (_onCellChangedFn != null) {
+      cellController.removeListener(_onCellChangedFn!);
+      _onCellChangedFn = null;
+    }
+    cellController.dispose();
+    return super.close();
+  }
+
+  void _startListening() {
+    _onCellChangedFn = cellController.startListening(
+      onCellChanged: ((data) {
+        if (!isClosed) {
+          add(BoardDateCellEvent.didReceiveCellUpdate(data));
+        }
+      }),
+    );
+  }
+}
+
+@freezed
+class BoardDateCellEvent with _$BoardDateCellEvent {
+  const factory BoardDateCellEvent.initial() = _InitialCell;
+  const factory BoardDateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) =
+      _DidReceiveCellUpdate;
+  const factory BoardDateCellEvent.didReceiveFieldUpdate(FieldPB field) =
+      _DidReceiveFieldUpdate;
+}
+
+@freezed
+class BoardDateCellState with _$BoardDateCellState {
+  const factory BoardDateCellState({
+    required DateCellDataPB? data,
+    required String dateStr,
+    required FieldPB field,
+  }) = _BoardDateCellState;
+
+  factory BoardDateCellState.initial(GridDateCellController context) {
+    final cellData = context.getCellData();
+
+    return BoardDateCellState(
+      field: context.field,
+      data: cellData,
+      dateStr: _dateStrFromCellData(cellData),
+    );
+  }
+}
+
+String _dateStrFromCellData(DateCellDataPB? cellData) {
+  String dateStr = "";
+  if (cellData != null) {
+    dateStr = cellData.date + " " + cellData.time;
+  }
+  return dateStr;
+}

+ 67 - 0
frontend/app_flowy/lib/plugins/board/application/card/board_number_cell_bloc.dart

@@ -0,0 +1,67 @@
+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_number_cell_bloc.freezed.dart';
+
+class BoardNumberCellBloc
+    extends Bloc<BoardNumberCellEvent, BoardNumberCellState> {
+  final GridNumberCellController cellController;
+  void Function()? _onCellChangedFn;
+  BoardNumberCellBloc({
+    required this.cellController,
+  }) : super(BoardNumberCellState.initial(cellController)) {
+    on<BoardNumberCellEvent>(
+      (event, emit) async {
+        await event.when(
+          initial: () async {
+            _startListening();
+          },
+          didReceiveCellUpdate: (content) {
+            emit(state.copyWith(content: content));
+          },
+        );
+      },
+    );
+  }
+
+  @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(BoardNumberCellEvent.didReceiveCellUpdate(cellContent ?? ""));
+        }
+      }),
+    );
+  }
+}
+
+@freezed
+class BoardNumberCellEvent with _$BoardNumberCellEvent {
+  const factory BoardNumberCellEvent.initial() = _InitialCell;
+  const factory BoardNumberCellEvent.didReceiveCellUpdate(String cellContent) =
+      _DidReceiveCellUpdate;
+}
+
+@freezed
+class BoardNumberCellState with _$BoardNumberCellState {
+  const factory BoardNumberCellState({
+    required String content,
+  }) = _BoardNumberCellState;
+
+  factory BoardNumberCellState.initial(GridCellController context) =>
+      BoardNumberCellState(
+        content: context.getCellData() ?? "",
+      );
+}

+ 1 - 1
frontend/app_flowy/lib/plugins/board/presentation/board_page.dart

@@ -51,7 +51,7 @@ class BoardContent extends StatelessWidget {
         return Container(
           color: Colors.white,
           child: Padding(
-            padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
+            padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
             child: AFBoard(
               key: UniqueKey(),
               dataController: context.read<BoardBloc>().boardDataController,

+ 42 - 1
frontend/app_flowy/lib/plugins/board/presentation/card/board_date_cell.dart

@@ -1,5 +1,8 @@
+import 'package:app_flowy/plugins/board/application/card/board_date_cell_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
 class BoardDateCell extends StatefulWidget {
   final GridCellControllerBuilder cellControllerBuilder;
@@ -14,8 +17,46 @@ class BoardDateCell extends StatefulWidget {
 }
 
 class _BoardDateCellState extends State<BoardDateCell> {
+  late BoardDateCellBloc _cellBloc;
+
+  @override
+  void initState() {
+    final cellController =
+        widget.cellControllerBuilder.build() as GridDateCellController;
+
+    _cellBloc = BoardDateCellBloc(cellController: cellController)
+      ..add(const BoardDateCellEvent.initial());
+    super.initState();
+  }
+
   @override
   Widget build(BuildContext context) {
-    return Container();
+    return BlocProvider.value(
+      value: _cellBloc,
+      child: BlocBuilder<BoardDateCellBloc, BoardDateCellState>(
+        builder: (context, state) {
+          if (state.dateStr.isEmpty) {
+            return const SizedBox();
+          } else {
+            return Padding(
+              padding: const EdgeInsets.all(8.0),
+              child: Align(
+                alignment: Alignment.centerLeft,
+                child: FlowyText.regular(
+                  state.dateStr,
+                  fontSize: 14,
+                ),
+              ),
+            );
+          }
+        },
+      ),
+    );
+  }
+
+  @override
+  Future<void> dispose() async {
+    _cellBloc.close();
+    super.dispose();
   }
 }

+ 42 - 1
frontend/app_flowy/lib/plugins/board/presentation/card/board_number_cell.dart

@@ -1,5 +1,8 @@
+import 'package:app_flowy/plugins/board/application/card/board_number_cell_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
 class BoardNumberCell extends StatefulWidget {
   final GridCellControllerBuilder cellControllerBuilder;
@@ -14,8 +17,46 @@ class BoardNumberCell extends StatefulWidget {
 }
 
 class _BoardNumberCellState extends State<BoardNumberCell> {
+  late BoardNumberCellBloc _cellBloc;
+
+  @override
+  void initState() {
+    final cellController =
+        widget.cellControllerBuilder.build() as GridNumberCellController;
+
+    _cellBloc = BoardNumberCellBloc(cellController: cellController)
+      ..add(const BoardNumberCellEvent.initial());
+    super.initState();
+  }
+
   @override
   Widget build(BuildContext context) {
-    return Container();
+    return BlocProvider.value(
+      value: _cellBloc,
+      child: BlocBuilder<BoardNumberCellBloc, BoardNumberCellState>(
+        builder: (context, state) {
+          if (state.content.isEmpty) {
+            return const SizedBox();
+          } else {
+            return Padding(
+              padding: const EdgeInsets.all(8.0),
+              child: Align(
+                alignment: Alignment.centerLeft,
+                child: FlowyText.regular(
+                  state.content,
+                  fontSize: 14,
+                ),
+              ),
+            );
+          }
+        },
+      ),
+    );
+  }
+
+  @override
+  Future<void> dispose() async {
+    _cellBloc.close();
+    super.dispose();
   }
 }

+ 13 - 4
frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart

@@ -1,6 +1,6 @@
 import 'package:app_flowy/plugins/board/application/card/board_select_option_cell_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
-import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
@@ -34,9 +34,18 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
       value: _cellBloc,
       child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
         builder: (context, state) {
-          return SelectOptionWrap(
-            selectOptions: state.selectedOptions,
-            cellControllerBuilder: widget.cellControllerBuilder,
+          final children = state.selectedOptions
+              .map((option) => SelectOptionTag.fromOption(
+                    context: context,
+                    option: option,
+                  ))
+              .toList();
+          return Padding(
+            padding: const EdgeInsets.all(8.0),
+            child: Align(
+              alignment: Alignment.centerLeft,
+              child: Wrap(children: children, spacing: 4, runSpacing: 2),
+            ),
           );
         },
       ),

+ 14 - 4
frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart

@@ -32,10 +32,20 @@ class _BoardTextCellState extends State<BoardTextCell> {
       value: _cellBloc,
       child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
         builder: (context, state) {
-          return SizedBox(
-            height: 30,
-            child: FlowyText.medium(state.content),
-          );
+          if (state.content.isEmpty) {
+            return const SizedBox();
+          } else {
+            return Padding(
+              padding: const EdgeInsets.all(8.0),
+              child: Align(
+                alignment: Alignment.centerLeft,
+                child: FlowyText.regular(
+                  state.content,
+                  fontSize: 14,
+                ),
+              ),
+            );
+          }
         },
       ),
     );

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

@@ -40,11 +40,8 @@ class _BoardCardState extends State<BoardCard> {
       value: _cardBloc,
       child: BlocBuilder<BoardCardBloc, BoardCardState>(
         builder: (context, state) {
-          return SizedBox(
-            height: 100,
-            child: Column(
-              children: _makeCells(context, state.gridCellMap),
-            ),
+          return Column(
+            children: _makeCells(context, state.gridCellMap),
           );
         },
       ),
@@ -56,7 +53,7 @@ class _BoardCardState extends State<BoardCard> {
       (cellId) {
         final child = widget.cellBuilder.buildCell(cellId);
 
-        return SizedBox(height: 39, child: child);
+        return child;
       },
     ).toList();
   }

+ 5 - 4
frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart

@@ -1,6 +1,7 @@
 part of 'cell_service.dart';
 
 typedef GridCellController = IGridCellController<String, String>;
+typedef GridNumberCellController = IGridCellController<String, String>;
 typedef GridSelectOptionCellController
     = IGridCellController<SelectOptionCellDataPB, String>;
 typedef GridDateCellController
@@ -58,7 +59,7 @@ class GridCellControllerBuilder {
           parser: StringCellDataParser(),
           reloadOnFieldChanged: true,
         );
-        return GridCellController(
+        return GridNumberCellController(
           cellId: _cellId,
           cellCache: _cellCache,
           cellDataLoader: cellDataLoader,
@@ -127,7 +128,7 @@ class IGridCellController<T, D> extends Equatable {
   final GridCellDataLoader<T> _cellDataLoader;
   final IGridCellDataPersistence<D> _cellDataPersistence;
 
-  late final CellListener _cellListener;
+  CellListener? _cellListener;
   ValueNotifier<T?>? _cellDataNotifier;
 
   bool isListening = false;
@@ -186,7 +187,7 @@ class IGridCellController<T, D> extends Equatable {
     /// For example:
     ///  user input: 12
     ///  cell display: $12
-    _cellListener.start(onCellChanged: (result) {
+    _cellListener?.start(onCellChanged: (result) {
       result.fold(
         (_) => _loadData(),
         (err) => Log.error(err),
@@ -289,7 +290,7 @@ class IGridCellController<T, D> extends Equatable {
       return;
     }
     _isDispose = true;
-    _cellListener.stop();
+    _cellListener?.stop();
     _loadDataOperation?.cancel();
     _saveDataOperation?.cancel();
     _cellDataNotifier = null;

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/cell/number_cell_bloc.dart

@@ -8,7 +8,7 @@ import 'cell_service/cell_service.dart';
 part 'number_cell_bloc.freezed.dart';
 
 class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
-  final GridCellController cellController;
+  final GridNumberCellController cellController;
   void Function()? _onCellChangedFn;
 
   NumberCellBloc({

+ 4 - 3
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart

@@ -73,7 +73,7 @@ class SelectOptionTag extends StatelessWidget {
     Key? key,
   }) : super(key: key);
 
-  factory SelectOptionTag.fromSelectOption({
+  factory SelectOptionTag.fromOption({
     required BuildContext context,
     required SelectOptionPB option,
     VoidCallback? onSelected,
@@ -91,7 +91,8 @@ class SelectOptionTag extends StatelessWidget {
   Widget build(BuildContext context) {
     return ChoiceChip(
       pressElevation: 1,
-      label: FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis),
+      label:
+          FlowyText.medium(name, fontSize: 12, overflow: TextOverflow.ellipsis),
       selectedColor: color,
       backgroundColor: color,
       labelPadding: const EdgeInsets.symmetric(horizontal: 6),
@@ -133,7 +134,7 @@ class SelectOptionTagCell extends StatelessWidget {
                   Flexible(
                     fit: FlexFit.loose,
                     flex: 2,
-                    child: SelectOptionTag.fromSelectOption(
+                    child: SelectOptionTag.fromOption(
                       context: context,
                       option: option,
                       onSelected: () => onSelected(option),

+ 23 - 20
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart

@@ -153,21 +153,25 @@ class SelectOptionWrap extends StatelessWidget {
     if (selectOptions.isEmpty && cellStyle != null) {
       child = Align(
         alignment: Alignment.centerLeft,
-        child: FlowyText.medium(cellStyle!.placeholder,
-            fontSize: 14, color: theme.shader3),
+        child: FlowyText.medium(
+          cellStyle!.placeholder,
+          fontSize: 14,
+          color: theme.shader3,
+        ),
       );
     } else {
-      final tags = selectOptions
-          .map(
-            (option) => SelectOptionTag.fromSelectOption(
-              context: context,
-              option: option,
-            ),
-          )
-          .toList();
       child = Align(
         alignment: Alignment.centerLeft,
-        child: Wrap(children: tags, spacing: 4, runSpacing: 2),
+        child: Wrap(
+          children: selectOptions
+              .map((option) => SelectOptionTag.fromOption(
+                    context: context,
+                    option: option,
+                  ))
+              .toList(),
+          spacing: 4,
+          runSpacing: 2,
+        ),
       );
     }
 
@@ -176,15 +180,14 @@ class SelectOptionWrap extends StatelessWidget {
       fit: StackFit.expand,
       children: [
         child,
-        InkWell(
-          onTap: () {
-            onFocus?.call(true);
-            final cellContext =
-                cellControllerBuilder.build() as GridSelectOptionCellController;
-            SelectOptionCellEditor.show(
-                context, cellContext, () => onFocus?.call(false));
-          },
-        ),
+        InkWell(onTap: () {
+          onFocus?.call(true);
+          SelectOptionCellEditor.show(
+            context,
+            cellControllerBuilder.build() as GridSelectOptionCellController,
+            () => onFocus?.call(false),
+          );
+        }),
       ],
     );
   }

+ 4 - 2
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/text_field.dart

@@ -49,7 +49,8 @@ class SelectOptionTextField extends StatelessWidget {
       initialTags: selectedOptionMap.keys.toList(),
       focusNode: _focusNode,
       textSeparators: const [' ', ','],
-      inputfieldBuilder: (BuildContext context, editController, focusNode, error, onChanged, onSubmitted) {
+      inputfieldBuilder: (BuildContext context, editController, focusNode,
+          error, onChanged, onSubmitted) {
         return ((context, sc, tags, onTagDelegate) {
           return TextField(
             autofocus: true,
@@ -99,7 +100,8 @@ class SelectOptionTextField extends StatelessWidget {
     }
 
     final children = selectedOptionMap.values
-        .map((option) => SelectOptionTag.fromSelectOption(context: context, option: option))
+        .map((option) =>
+            SelectOptionTag.fromOption(context: context, option: option))
         .toList();
     return Padding(
       padding: const EdgeInsets.all(8.0),

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart

@@ -53,7 +53,7 @@ class MenuUser extends StatelessWidget {
           borderRadius: Corners.s5Border,
           child: CircleAvatar(
             backgroundColor: Colors.transparent,
-            child: Container(),
+            child: svgWidget('emoji/$iconUrl'),
           )),
     );
   }

+ 22 - 7
frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs

@@ -1,6 +1,5 @@
 use crate::services::cell::apply_cell_data_changeset;
-use crate::services::field::SelectOptionCellChangeset;
-use flowy_error::{FlowyError, FlowyResult};
+use crate::services::field::{DateCellChangesetPB, SelectOptionCellChangeset};
 use flowy_grid_data_model::revision::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT};
 use indexmap::IndexMap;
 use std::collections::HashMap;
@@ -35,17 +34,33 @@ impl<'a> RowRevisionBuilder<'a> {
         }
     }
 
-    pub fn insert_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> {
+    pub fn insert_cell(&mut self, field_id: &str, data: String) {
         match self.field_rev_map.get(&field_id.to_owned()) {
             None => {
-                let msg = format!("Can't find the field with id: {}", field_id);
-                Err(FlowyError::internal().context(msg))
+                tracing::warn!("Can't find the field with id: {}", field_id);
             }
             Some(field_rev) => {
-                let data = apply_cell_data_changeset(data, None, field_rev)?;
+                let data = apply_cell_data_changeset(data, None, field_rev).unwrap();
+                let cell = CellRevision::new(data);
+                self.payload.cell_by_field_id.insert(field_id.to_owned(), cell);
+            }
+        }
+    }
+
+    pub fn insert_date_cell(&mut self, field_id: &str, timestamp: i64) {
+        match self.field_rev_map.get(&field_id.to_owned()) {
+            None => {
+                tracing::warn!("Invalid field_id: {}", field_id);
+            }
+            Some(field_rev) => {
+                let cell_data = serde_json::to_string(&DateCellChangesetPB {
+                    date: Some(timestamp.to_string()),
+                    time: None,
+                })
+                .unwrap();
+                let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap();
                 let cell = CellRevision::new(data);
                 self.payload.cell_by_field_id.insert(field_id.to_owned(), cell);
-                Ok(())
             }
         }
     }

+ 20 - 3
frontend/rust-lib/flowy-grid/src/util.rs

@@ -3,6 +3,7 @@ use crate::services::field::*;
 use crate::services::row::RowRevisionBuilder;
 use flowy_grid_data_model::revision::BuildGridContext;
 use flowy_sync::client_grid::GridBuilder;
+use lib_infra::util::timestamp;
 
 pub fn make_default_grid() -> BuildGridContext {
     let mut grid_builder = GridBuilder::new();
@@ -40,24 +41,40 @@ pub fn make_default_board() -> BuildGridContext {
         .visibility(true)
         .primary(true)
         .build();
+    let text_field_id = text_field.id.clone();
     grid_builder.add_field(text_field);
 
+    // date
+    let date_type_option = DateTypeOptionBuilder::default();
+    let date_field = FieldBuilder::new(date_type_option)
+        .name("Date")
+        .visibility(true)
+        .build();
+    let date_field_id = date_field.id.clone();
+    let timestamp = timestamp();
+    grid_builder.add_field(date_field);
+
     // single select
     let in_progress_option = SelectOptionPB::new("In progress");
     let not_started_option = SelectOptionPB::new("Not started");
     let done_option = SelectOptionPB::new("Done");
-    let single_select = SingleSelectTypeOptionBuilder::default()
+    let single_select_type_option = SingleSelectTypeOptionBuilder::default()
         .add_option(not_started_option.clone())
         .add_option(in_progress_option)
         .add_option(done_option);
-    let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
+    let single_select_field = FieldBuilder::new(single_select_type_option)
+        .name("Status")
+        .visibility(true)
+        .build();
     let single_select_field_id = single_select_field.id.clone();
     grid_builder.add_field(single_select_field);
 
     // Insert rows
-    for _ in 0..3 {
+    for i in 0..10 {
         let mut row_builder = RowRevisionBuilder::new(grid_builder.block_id(), grid_builder.field_revs());
         row_builder.insert_select_option_cell(&single_select_field_id, not_started_option.id.clone());
+        row_builder.insert_cell(&text_field_id, format!("Card {}", i));
+        row_builder.insert_date_cell(&date_field_id, timestamp);
         let row = row_builder.build();
         grid_builder.add_row(row);
     }