Przeglądaj źródła

chore: Merge main 521 (#2574)

* chore: merge main branch

* chore: remove document plugins

* chore: add color generator

* ci: tests
Nathan.fooo 2 lat temu
rodzic
commit
5cc8340e05
27 zmienionych plików z 416 dodań i 240 usunięć
  1. 7 3
      frontend/appflowy_flutter/assets/translations/en.json
  2. 0 1
      frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_data_persistence.dart
  3. 12 2
      frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart
  4. 13 1
      frontend/appflowy_flutter/lib/plugins/database_view/application/database_view_service.dart
  5. 1 1
      frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_cache.dart
  6. 2 2
      frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_bloc.dart
  7. 3 0
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart
  8. 12 0
      frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart
  9. 102 77
      frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart
  10. 57 32
      frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart
  11. 2 1
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart
  12. 6 9
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/cell_container.dart
  13. 0 1
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cal_bloc.dart
  14. 10 1
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_action.dart
  15. 22 4
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_node_widget.dart
  16. 3 0
      frontend/appflowy_flutter/lib/plugins/document/presentation/more/more_button.dart
  17. 11 0
      frontend/appflowy_flutter/lib/util/color_generator/color_generator.dart
  18. 5 5
      frontend/appflowy_flutter/lib/workspace/presentation/home/home_sizes.dart
  19. 33 35
      frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart
  20. 25 1
      frontend/appflowy_flutter/lib/workspace/presentation/home/menu/menu_user.dart
  21. 21 25
      frontend/appflowy_flutter/lib/workspace/presentation/widgets/left_bar_item.dart
  22. 1 1
      frontend/appflowy_flutter/packages/flowy_infra/lib/language.dart
  23. 2 2
      frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/extension.dart
  24. 15 4
      frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/icon_button.dart
  25. 31 0
      frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart
  26. 6 6
      frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_tests.rs
  27. 14 26
      inlang.config.js

+ 7 - 3
frontend/appflowy_flutter/assets/translations/en.json

@@ -45,7 +45,8 @@
     "medium": "medium",
     "large": "large",
     "fontSize": "Font Size",
-    "import": "Import"
+    "import": "Import",
+    "moreOptions": "More options"
   },
   "disclosureAction": {
     "rename": "Rename",
@@ -108,6 +109,7 @@
     "openAsPage": "Open as a Page",
     "addNewRow": "Add a new row",
     "openMenu": "Click to open menu",
+    "dragRow": "Long press to reorder the row",
     "viewDataBase": "View database",
     "referencePage": "This {name} is referenced"
   },
@@ -358,12 +360,14 @@
       "autoGeneratorGenerate": "Generate",
       "autoGeneratorHintText": "Ask OpenAI ...",
       "autoGeneratorCantGetOpenAIKey": "Can't get OpenAI key",
+      "autoGeneratorRewrite": "Rewrite",
       "smartEdit": "AI Assistants",
       "openAI": "OpenAI",
       "smartEditFixSpelling": "Fix spelling",
       "warning": "⚠️ AI responses can be inaccurate or misleading.",
       "smartEditSummarize": "Summarize",
-      "smartEditImproveWriting": "Improve Writing",
+      "smartEditImproveWriting": "Improve writing",
+      "smartEditMakeLonger": "Make longer",
       "smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
       "smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
       "smartEditDisabled": "Connect OpenAI in Settings",
@@ -427,4 +431,4 @@
       "emptyNoDate": "No unscheduled events"
     }
   }
-}
+}

+ 0 - 1
frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_data_persistence.dart

@@ -44,7 +44,6 @@ class DateCellDataPersistence implements CellDataPersistence<DateCellData> {
   @override
   Future<Option<FlowyError>> save(DateCellData data) {
     var payload = DateChangesetPB.create()..cellPath = _makeCellPath(cellId);
-
     if (data.dateTime != null) {
       final date = (data.dateTime!.millisecondsSinceEpoch ~/ 1000).toString();
       payload.date = date;

+ 12 - 2
frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart

@@ -176,18 +176,28 @@ class DatabaseController {
     );
   }
 
-  Future<Either<Unit, FlowyError>> moveRow({
+  Future<Either<Unit, FlowyError>> moveGroupRow({
     required RowPB fromRow,
     required String groupId,
     RowPB? toRow,
   }) {
-    return _databaseViewBackendSvc.moveRow(
+    return _databaseViewBackendSvc.moveGroupRow(
       fromRowId: fromRow.id,
       toGroupId: groupId,
       toRowId: toRow?.id,
     );
   }
 
+  Future<Either<Unit, FlowyError>> moveRow({
+    required RowPB fromRow,
+    required RowPB toRow,
+  }) {
+    return _databaseViewBackendSvc.moveRow(
+      fromRowId: fromRow.id,
+      toRowId: toRow.id,
+    );
+  }
+
   Future<Either<Unit, FlowyError>> moveGroup({
     required String fromGroupId,
     required String toGroupId,

+ 13 - 1
frontend/appflowy_flutter/lib/plugins/database_view/application/database_view_service.dart

@@ -43,7 +43,7 @@ class DatabaseViewBackendService {
     return DatabaseEventCreateRow(payload).send();
   }
 
-  Future<Either<Unit, FlowyError>> moveRow({
+  Future<Either<Unit, FlowyError>> moveGroupRow({
     required RowId fromRowId,
     required String toGroupId,
     RowId? toRowId,
@@ -60,6 +60,18 @@ class DatabaseViewBackendService {
     return DatabaseEventMoveGroupRow(payload).send();
   }
 
+  Future<Either<Unit, FlowyError>> moveRow({
+    required String fromRowId,
+    required String toRowId,
+  }) {
+    var payload = MoveRowPayloadPB.create()
+      ..viewId = viewId
+      ..fromRowId = fromRowId
+      ..toRowId = toRowId;
+
+    return DatabaseEventMoveRow(payload).send();
+  }
+
   Future<Either<Unit, FlowyError>> moveGroup({
     required String fromGroupId,
     required String toGroupId,

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_cache.dart

@@ -30,7 +30,7 @@ abstract class RowCacheDelegate {
 class RowCache {
   final String viewId;
 
-  /// _rows containers the current block's rows
+  /// _rows contains the current block's rows
   /// Use List to reverse the order of the GridRow.
   final RowList _rowList = RowList();
 

+ 2 - 2
frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_bloc.dart

@@ -54,7 +54,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
         final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex);
         final toRow = groupControllers[groupId]?.rowAtIndex(toIndex);
         if (fromRow != null) {
-          _databaseController.moveRow(
+          _databaseController.moveGroupRow(
             fromRow: fromRow,
             toRow: toRow,
             groupId: groupId,
@@ -70,7 +70,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
         final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex);
         final toRow = groupControllers[toGroupId]?.rowAtIndex(toIndex);
         if (fromRow != null) {
-          _databaseController.moveRow(
+          _databaseController.moveGroupRow(
             fromRow: fromRow,
             toRow: toRow,
             groupId: toGroupId,

+ 3 - 0
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart

@@ -161,6 +161,9 @@ class CalendarDayCard extends StatelessWidget {
     });
 
     renderHook.addSelectOptionHook((selectedOptions, cardData, _) {
+      if (selectedOptions.isEmpty) {
+        return const SizedBox.shrink();
+      }
       final children = selectedOptions.map(
         (option) {
           return SelectOptionTag.fromOption(

+ 12 - 0
frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart

@@ -35,6 +35,17 @@ class GridBloc extends Bloc<GridEvent, GridState> {
             );
             await rowService.deleteRow(rowInfo.rowPB.id);
           },
+          moveRow: (int from, int to) {
+            final List<RowInfo> rows = [...state.rowInfos];
+
+            final fromRow = rows[from].rowPB;
+            final toRow = rows[to].rowPB;
+
+            rows.insert(to, rows.removeAt(from));
+            emit(state.copyWith(rowInfos: rows));
+
+            databaseController.moveRow(fromRow: fromRow, toRow: toRow);
+          },
           didReceiveGridUpdate: (grid) {
             emit(state.copyWith(grid: Some(grid)));
           },
@@ -110,6 +121,7 @@ class GridEvent with _$GridEvent {
   const factory GridEvent.initial() = InitialGrid;
   const factory GridEvent.createRow() = _CreateRow;
   const factory GridEvent.deleteRow(RowInfo rowInfo) = _DeleteRow;
+  const factory GridEvent.moveRow(int from, int to) = _MoveRow;
   const factory GridEvent.didReceiveRowUpdate(
     List<RowInfo> rows,
     RowsChangedReason listState,

+ 102 - 77
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart

@@ -91,21 +91,6 @@ class _GridPageState extends State<GridPage> {
       ),
     );
   }
-
-  @override
-  void dispose() {
-    super.dispose();
-  }
-
-  @override
-  void deactivate() {
-    super.deactivate();
-  }
-
-  @override
-  void didUpdateWidget(covariant GridPage oldWidget) {
-    super.didUpdateWidget(oldWidget);
-  }
 }
 
 class FlowyGrid extends StatefulWidget {
@@ -119,12 +104,12 @@ class _FlowyGridState extends State<FlowyGrid> {
   final _scrollController = GridScrollController(
     scrollGroupController: LinkedScrollControllerGroup(),
   );
-  late ScrollController headerScrollController;
+  late final ScrollController headerScrollController;
 
   @override
   void initState() {
-    headerScrollController = _scrollController.linkHorizontalController();
     super.initState();
+    headerScrollController = _scrollController.linkHorizontalController();
   }
 
   @override
@@ -216,49 +201,78 @@ class _GridRowsState extends State<_GridRows> {
 
   @override
   Widget build(BuildContext context) {
-    return BlocConsumer<GridBloc, GridState>(
-      listenWhen: (previous, current) => previous.reason != current.reason,
-      listener: (context, state) {
-        state.reason.whenOrNull(
-          insert: (item) {
-            _key.currentState?.insertItem(item.index);
-          },
-          delete: (item) {
-            _key.currentState?.removeItem(
-              item.index,
-              (context, animation) =>
-                  _renderRow(context, item.rowInfo, animation),
+    return Builder(
+      builder: (context) {
+        final filterState = context.watch<GridFilterMenuBloc>().state;
+        final sortState = context.watch<SortMenuBloc>().state;
+
+        return BlocConsumer<GridBloc, GridState>(
+          listenWhen: (previous, current) => previous.reason != current.reason,
+          listener: (context, state) {
+            state.reason.whenOrNull(
+              insert: (item) {
+                _key.currentState?.insertItem(item.index);
+              },
+              delete: (item) {
+                _key.currentState?.removeItem(
+                  item.index,
+                  (context, animation) => _renderRow(
+                    context,
+                    item.rowInfo,
+                    animation: animation,
+                  ),
+                );
+              },
             );
           },
-          reorderSingleRow: (reorderRow, rowInfo) {
-            // _key.currentState?.removeItem(
-            //   reorderRow.oldIndex,
-            //   (context, animation) => _renderRow(context, rowInfo, animation),
-            // );
-            // _key.currentState?.insertItem(reorderRow.newIndex);
-          },
-        );
-      },
-      buildWhen: (previous, current) {
-        return current.reason.whenOrNull(
+          buildWhen: (previous, current) {
+            return current.reason.maybeWhen(
               reorderRows: () => true,
               reorderSingleRow: (reorderRow, rowInfo) => true,
-            ) ??
-            false;
-      },
-      builder: (context, state) {
-        return SliverAnimatedList(
-          key: _key,
-          initialItemCount: context.read<GridBloc>().state.rowInfos.length,
-          itemBuilder:
-              (BuildContext context, int index, Animation<double> animation) {
-            final rowInfos = context.read<GridBloc>().state.rowInfos;
-            if (index >= rowInfos.length) {
-              return const SizedBox();
-            } else {
-              final RowInfo rowInfo = rowInfos[index];
-              return _renderRow(context, rowInfo, animation);
-            }
+              delete: (item) => true,
+              insert: (item) => true,
+              orElse: () => false,
+            );
+          },
+          builder: (context, state) {
+            final rowInfos = context.watch<GridBloc>().state.rowInfos;
+
+            return SliverFillRemaining(
+              child: ReorderableListView.builder(
+                key: _key,
+                buildDefaultDragHandles: false,
+                proxyDecorator: (child, index, animation) => Material(
+                  color: Colors.white.withOpacity(.1),
+                  child: Opacity(
+                    opacity: .5,
+                    child: child,
+                  ),
+                ),
+                onReorder: (fromIndex, newIndex) {
+                  final toIndex =
+                      newIndex > fromIndex ? newIndex - 1 : newIndex;
+
+                  if (fromIndex == toIndex) {
+                    return;
+                  }
+
+                  context
+                      .read<GridBloc>()
+                      .add(GridEvent.moveRow(fromIndex, toIndex));
+                },
+                itemCount: rowInfos.length,
+                itemBuilder: (BuildContext context, int index) {
+                  final RowInfo rowInfo = rowInfos[index];
+                  return _renderRow(
+                    context,
+                    rowInfo,
+                    index: index,
+                    isSortEnabled: sortState.sortInfos.isNotEmpty,
+                    isFilterEnabled: filterState.filters.isNotEmpty,
+                  );
+                },
+              ),
+            );
           },
         );
       },
@@ -267,15 +281,18 @@ class _GridRowsState extends State<_GridRows> {
 
   Widget _renderRow(
     BuildContext context,
-    RowInfo rowInfo,
-    Animation<double> animation,
-  ) {
+    RowInfo rowInfo, {
+    int? index,
+    bool isSortEnabled = false,
+    bool isFilterEnabled = false,
+    Animation<double>? animation,
+  }) {
     final rowCache = context.read<GridBloc>().getRowCache(
           rowInfo.rowPB.id,
         );
 
     /// Return placeholder widget if the rowCache is null.
-    if (rowCache == null) return const SizedBox();
+    if (rowCache == null) return const SizedBox.shrink();
 
     final fieldController =
         context.read<GridBloc>().databaseController.fieldController;
@@ -285,24 +302,32 @@ class _GridRowsState extends State<_GridRows> {
       rowCache: rowCache,
     );
 
-    return SizeTransition(
-      sizeFactor: animation,
-      child: GridRow(
-        rowInfo: rowInfo,
-        dataController: dataController,
-        cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
-        openDetailPage: (context, cellBuilder) {
-          _openRowDetailPage(
-            context,
-            rowInfo,
-            fieldController,
-            rowCache,
-            cellBuilder,
-          );
-        },
-        key: ValueKey(rowInfo.rowPB.id),
-      ),
+    final child = GridRow(
+      key: ValueKey(rowInfo.rowPB.id),
+      index: index,
+      isDraggable: !isSortEnabled && !isFilterEnabled,
+      rowInfo: rowInfo,
+      dataController: dataController,
+      cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
+      openDetailPage: (context, cellBuilder) {
+        _openRowDetailPage(
+          context,
+          rowInfo,
+          fieldController,
+          rowCache,
+          cellBuilder,
+        );
+      },
     );
+
+    if (animation != null) {
+      return SizeTransition(
+        sizeFactor: animation,
+        child: child,
+      );
+    }
+
+    return child;
   }
 
   void _openRowDetailPage(

+ 57 - 32
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart

@@ -25,29 +25,34 @@ class GridRow extends StatefulWidget {
   final GridCellBuilder cellBuilder;
   final void Function(BuildContext, GridCellBuilder) openDetailPage;
 
+  final int? index;
+  final bool isDraggable;
+
   const GridRow({
+    super.key,
     required this.rowInfo,
     required this.dataController,
     required this.cellBuilder,
     required this.openDetailPage,
-    Key? key,
-  }) : super(key: key);
+    this.index,
+    this.isDraggable = false,
+  });
 
   @override
   State<GridRow> createState() => _GridRowState();
 }
 
 class _GridRowState extends State<GridRow> {
-  late RowBloc _rowBloc;
+  late final RowBloc _rowBloc;
 
   @override
   void initState() {
+    super.initState();
     _rowBloc = RowBloc(
       rowInfo: widget.rowInfo,
       dataController: widget.dataController,
     );
     _rowBloc.add(const RowEvent.initial());
-    super.initState();
   }
 
   @override
@@ -70,9 +75,11 @@ class _GridRowState extends State<GridRow> {
 
             return Row(
               children: [
-                const _RowLeading(),
+                _RowLeading(
+                  index: widget.index,
+                  isDraggable: widget.isDraggable,
+                ),
                 content,
-                const _RowTrailing(),
               ],
             );
           },
@@ -89,19 +96,25 @@ class _GridRowState extends State<GridRow> {
 }
 
 class _RowLeading extends StatefulWidget {
-  const _RowLeading({Key? key}) : super(key: key);
+  final int? index;
+  final bool isDraggable;
+
+  const _RowLeading({
+    this.index,
+    this.isDraggable = false,
+  });
 
   @override
   State<_RowLeading> createState() => _RowLeadingState();
 }
 
 class _RowLeadingState extends State<_RowLeading> {
-  late PopoverController popoverController;
+  late final PopoverController popoverController;
 
   @override
   void initState() {
-    popoverController = PopoverController();
     super.initState();
+    popoverController = PopoverController();
   }
 
   @override
@@ -131,23 +144,28 @@ class _RowLeadingState extends State<_RowLeading> {
       mainAxisAlignment: MainAxisAlignment.center,
       children: [
         const _InsertButton(),
-        _MenuButton(
-          openMenu: () {
-            popoverController.show();
-          },
-        ),
+        if (isDraggable) ...[
+          ReorderableDragStartListener(
+            index: widget.index!,
+            child: _MenuButton(
+              isDragEnabled: isDraggable,
+              openMenu: () {
+                popoverController.show();
+              },
+            ),
+          ),
+        ] else ...[
+          _MenuButton(
+            openMenu: () {
+              popoverController.show();
+            },
+          ),
+        ],
       ],
     );
   }
-}
 
-class _RowTrailing extends StatelessWidget {
-  const _RowTrailing({Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return const SizedBox();
-  }
+  bool get isDraggable => widget.index != null && widget.isDraggable;
 }
 
 class _InsertButton extends StatelessWidget {
@@ -172,25 +190,31 @@ class _InsertButton extends StatelessWidget {
 
 class _MenuButton extends StatefulWidget {
   final VoidCallback openMenu;
+  final bool isDragEnabled;
+
   const _MenuButton({
     required this.openMenu,
-    Key? key,
-  }) : super(key: key);
+    this.isDragEnabled = false,
+  });
 
   @override
   State<_MenuButton> createState() => _MenuButtonState();
 }
 
 class _MenuButtonState extends State<_MenuButton> {
-  @override
-  void initState() {
-    super.initState();
-  }
-
   @override
   Widget build(BuildContext context) {
     return FlowyIconButton(
-      tooltipText: LocaleKeys.tooltip_openMenu.tr(),
+      tooltipText:
+          widget.isDragEnabled ? null : LocaleKeys.tooltip_openMenu.tr(),
+      richTooltipText: widget.isDragEnabled
+          ? TextSpan(
+              children: [
+                TextSpan(text: '${LocaleKeys.tooltip_dragRow.tr()}\n'),
+                TextSpan(text: LocaleKeys.tooltip_openMenu.tr()),
+              ],
+            )
+          : null,
       hoverColor: AFThemeExtension.of(context).lightGreyHover,
       width: 20,
       height: 30,
@@ -258,6 +282,7 @@ class RowContent extends StatelessWidget {
             if (builder != null) {
               accessories.addAll(builder(buildContext));
             }
+
             return accessories;
           },
           child: child,
@@ -289,12 +314,12 @@ class _RowEnterRegion extends StatefulWidget {
 }
 
 class _RowEnterRegionState extends State<_RowEnterRegion> {
-  late RegionStateNotifier _rowStateNotifier;
+  late final RegionStateNotifier _rowStateNotifier;
 
   @override
   void initState() {
-    _rowStateNotifier = RegionStateNotifier();
     super.initState();
+    _rowStateNotifier = RegionStateNotifier();
   }
 
   @override

+ 2 - 1
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart

@@ -74,6 +74,7 @@ class GridCellBuilder {
           key: key,
         );
     }
+
     throw UnimplementedError;
   }
 }
@@ -83,7 +84,7 @@ class BlankCell extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return Container();
+    return const SizedBox.shrink();
   }
 }
 

+ 6 - 9
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/cell_container.dart

@@ -68,18 +68,15 @@ class CellContainer extends StatelessWidget {
     if (isFocus) {
       final borderSide = BorderSide(
         color: Theme.of(context).colorScheme.primary,
-        width: 1.0,
       );
+
       return BoxDecoration(border: Border.fromBorderSide(borderSide));
-    } else {
-      final borderSide = BorderSide(
-        color: Theme.of(context).dividerColor,
-        width: 1.0,
-      );
-      return BoxDecoration(
-        border: Border(right: borderSide, bottom: borderSide),
-      );
     }
+
+    final borderSide = BorderSide(color: Theme.of(context).dividerColor);
+    return BoxDecoration(
+      border: Border(right: borderSide, bottom: borderSide),
+    );
   }
 }
 

+ 0 - 1
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cal_bloc.dart

@@ -82,7 +82,6 @@ class DateCellCalendarBloc
           date != null && time == null,
     );
     String? newTime = time ?? state.time;
-
     DateTime? newDate = _utcToLocalAddTime(date);
     if (time != null && time.isNotEmpty) {
       newDate = state.dateTime ?? DateTime.now();

+ 10 - 1
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_action.dart

@@ -6,7 +6,8 @@ import 'package:easy_localization/easy_localization.dart';
 enum SmartEditAction {
   summarize,
   fixSpelling,
-  improveWriting;
+  improveWriting,
+  makeItLonger;
 
   String get toInstruction {
     switch (this) {
@@ -16,6 +17,8 @@ enum SmartEditAction {
         return 'Correct this to standard English:';
       case SmartEditAction.improveWriting:
         return 'Rewrite this in your own words:';
+      case SmartEditAction.makeItLonger:
+        return 'Make this text longer:';
     }
   }
 
@@ -27,6 +30,8 @@ enum SmartEditAction {
         return 'Correct this to standard English:\n\n$input';
       case SmartEditAction.improveWriting:
         return 'Rewrite this:\n\n$input';
+      case SmartEditAction.makeItLonger:
+        return 'Make this text longer:\n\n$input';
     }
   }
 
@@ -38,6 +43,8 @@ enum SmartEditAction {
         return SmartEditAction.fixSpelling;
       case 2:
         return SmartEditAction.improveWriting;
+      case 3:
+        return SmartEditAction.makeItLonger;
     }
     return SmartEditAction.fixSpelling;
   }
@@ -50,6 +57,8 @@ enum SmartEditAction {
         return LocaleKeys.document_plugins_smartEditFixSpelling.tr();
       case SmartEditAction.improveWriting:
         return LocaleKeys.document_plugins_smartEditImproveWriting.tr();
+      case SmartEditAction.makeItLonger:
+        return LocaleKeys.document_plugins_smartEditMakeLonger.tr();
     }
   }
 }

+ 22 - 4
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_node_widget.dart

@@ -257,15 +257,15 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
   }
 
   Widget _buildResultWidget(BuildContext context) {
-    final loading = Padding(
+    final loadingWidget = Padding(
       padding: const EdgeInsets.symmetric(horizontal: 4.0),
       child: SizedBox.fromSize(
         size: const Size.square(14),
         child: const CircularProgressIndicator(),
       ),
     );
-    if (result.isEmpty) {
-      return loading;
+    if (result.isEmpty || loading) {
+      return loadingWidget;
     }
     return Flexible(
       child: Text(
@@ -277,6 +277,18 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
   Widget _buildInputFooterWidget(BuildContext context) {
     return Row(
       children: [
+        FlowyRichTextButton(
+          TextSpan(
+            children: [
+              TextSpan(
+                text: LocaleKeys.document_plugins_autoGeneratorRewrite.tr(),
+                style: Theme.of(context).textTheme.bodyMedium,
+              ),
+            ],
+          ),
+          onPressed: () => _requestCompletions(rewrite: true),
+        ),
+        const Space(10, 0),
         FlowyRichTextButton(
           TextSpan(
             children: [
@@ -402,7 +414,13 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
     );
   }
 
-  Future<void> _requestCompletions() async {
+  Future<void> _requestCompletions({bool rewrite = false}) async {
+    if (rewrite) {
+      setState(() {
+        loading = true;
+        result = "";
+      });
+    }
     final openAIRepository = await getIt.getAsync<OpenAIRepository>();
 
     var lines = content.split('\n\n');

+ 3 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/more/more_button.dart

@@ -1,5 +1,7 @@
+import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
 import 'package:appflowy/plugins/document/presentation/more/font_size_switcher.dart';
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -14,6 +16,7 @@ class DocumentMoreButton extends StatelessWidget {
     return PopupMenuButton<int>(
       color: Theme.of(context).colorScheme.surfaceVariant,
       offset: const Offset(0, 30),
+      tooltip: LocaleKeys.moreAction_moreOptions.tr(),
       itemBuilder: (context) {
         return [
           PopupMenuItem(

+ 11 - 0
frontend/appflowy_flutter/lib/util/color_generator/color_generator.dart

@@ -0,0 +1,11 @@
+import 'dart:ui';
+
+class ColorGenerator {
+  Color generateColorFromString(String string) {
+    final hash = string.hashCode;
+    int r = (hash & 0xFF0000) >> 16;
+    int g = (hash & 0x00FF00) >> 8;
+    int b = hash & 0x0000FF;
+    return Color.fromRGBO(r, g, b, 0.5);
+  }
+}

+ 5 - 5
frontend/appflowy_flutter/lib/workspace/presentation/home/home_sizes.dart

@@ -1,10 +1,10 @@
 class HomeSizes {
-  static double get menuAddButtonHeight => 60;
-  static double get topBarHeight => 60;
-  static double get editPanelTopBarHeight => 60;
-  static double get editPanelWidth => 400;
+  static const double menuAddButtonHeight = 60;
+  static const double topBarHeight = 60;
+  static const double editPanelTopBarHeight = 60;
+  static const double editPanelWidth = 400;
 }
 
 class HomeInsets {
-  static double get topBarTitlePadding => 12;
+  static const double topBarTitlePadding = 12;
 }

+ 33 - 35
frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart

@@ -170,23 +170,24 @@ class HomeStackManager {
     return MultiProvider(
       providers: [ChangeNotifierProvider.value(value: _notifier)],
       child: Consumer(
-        builder: (ctx, HomeStackNotifier notifier, child) {
+        builder: (_, HomeStackNotifier notifier, __) {
           return FadingIndexedStack(
             index: getIt<PluginSandbox>().indexOf(notifier.plugin.ty),
-            children:
-                getIt<PluginSandbox>().supportPluginTypes.map((pluginType) {
-              if (pluginType == notifier.plugin.ty) {
-                final pluginWidget = notifier.plugin.display
-                    .buildWidget(PluginContext(onDeleted: onDeleted));
-                if (pluginType == PluginType.editor) {
-                  return pluginWidget;
-                } else {
+            children: getIt<PluginSandbox>().supportPluginTypes.map(
+              (pluginType) {
+                if (pluginType == notifier.plugin.ty) {
+                  final pluginWidget = notifier.plugin.display
+                      .buildWidget(PluginContext(onDeleted: onDeleted));
+                  if (pluginType == PluginType.editor) {
+                    return pluginWidget;
+                  }
+
                   return pluginWidget.padding(horizontal: 40, vertical: 28);
                 }
-              } else {
+
                 return const BlankPage();
-              }
-            }).toList(),
+              },
+            ).toList(),
           );
         },
       ),
@@ -204,30 +205,27 @@ class HomeTopBar extends StatelessWidget {
     return Container(
       color: Theme.of(context).colorScheme.onSecondaryContainer,
       height: HomeSizes.topBarHeight,
-      child: Row(
-        crossAxisAlignment: CrossAxisAlignment.center,
-        children: [
-          HSpace(layout.menuSpacing),
-          const FlowyNavigation(),
-          const HSpace(16),
-          ChangeNotifierProvider.value(
-            value: Provider.of<HomeStackNotifier>(context, listen: false),
-            child: Consumer(
-              builder: (
-                BuildContext context,
-                HomeStackNotifier notifier,
-                Widget? child,
-              ) {
-                return notifier.plugin.display.rightBarItem ?? const SizedBox();
-              },
+      child: Padding(
+        padding: const EdgeInsets.symmetric(
+          horizontal: HomeInsets.topBarTitlePadding,
+        ),
+        child: Row(
+          crossAxisAlignment: CrossAxisAlignment.center,
+          children: [
+            HSpace(layout.menuSpacing),
+            const FlowyNavigation(),
+            const HSpace(16),
+            ChangeNotifierProvider.value(
+              value: Provider.of<HomeStackNotifier>(context, listen: false),
+              child: Consumer(
+                builder: (_, HomeStackNotifier notifier, __) =>
+                    notifier.plugin.display.rightBarItem ??
+                    const SizedBox.shrink(),
+              ),
             ),
-          ) // _renderMoreButton(),
-        ],
-      )
-          .padding(
-            horizontal: HomeInsets.topBarTitlePadding,
-          )
-          .bottomBorder(color: Theme.of(context).dividerColor),
+          ],
+        ).bottomBorder(color: Theme.of(context).dividerColor),
+      ),
     );
   }
 }

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

@@ -1,4 +1,5 @@
 import 'package:appflowy/startup/startup.dart';
+import 'package:appflowy/util/color_generator/color_generator.dart';
 import 'package:appflowy/workspace/application/menu/menu_user_bloc.dart';
 import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
 import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart';
@@ -45,8 +46,31 @@ class MenuUser extends StatelessWidget {
     String iconUrl = context.read<MenuUserBloc>().state.userProfile.iconUrl;
     if (iconUrl.isEmpty) {
       iconUrl = defaultUserAvatar;
+      final String name = context.read<MenuUserBloc>().state.userProfile.name;
+      final Color color = ColorGenerator().generateColorFromString(name);
+      const initialsCount = 2;
+      // Taking the first letters of the name components and limiting to 2 elements
+      final nameInitials = name
+          .split(' ')
+          .where((element) => element.isNotEmpty)
+          .take(initialsCount)
+          .map((element) => element[0].toUpperCase())
+          .join('');
+      return Container(
+        width: 28,
+        height: 28,
+        alignment: Alignment.center,
+        decoration: BoxDecoration(
+          color: color,
+          shape: BoxShape.circle,
+        ),
+        child: FlowyText.semibold(
+          nameInitials,
+          color: Colors.white,
+          fontSize: nameInitials.length == initialsCount ? 12 : 14,
+        ),
+      );
     }
-
     return SizedBox(
       width: 25,
       height: 25,

+ 21 - 25
frontend/appflowy_flutter/lib/workspace/presentation/widgets/left_bar_item.dart

@@ -17,12 +17,13 @@ class ViewLeftBarItem extends StatefulWidget {
 class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
   final _controller = TextEditingController();
   final _focusNode = FocusNode();
-  late ViewService _viewService;
-  late ViewListener _viewListener;
+  late final ViewService _viewService;
+  late final ViewListener _viewListener;
   late ViewPB view;
 
   @override
   void initState() {
+    super.initState();
     view = widget.view;
     _viewService = ViewService();
     _focusNode.addListener(_handleFocusChanged);
@@ -39,7 +40,8 @@ class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
         );
       },
     );
-    super.initState();
+
+    _controller.text = view.name;
   }
 
   @override
@@ -53,30 +55,24 @@ class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
 
   @override
   Widget build(BuildContext context) {
-    _controller.text = view.name;
-
-    return IntrinsicWidth(
+    return GestureDetector(
       key: ValueKey(_controller.text),
-      child: GestureDetector(
-        onDoubleTap: () {
-          _controller.selection = TextSelection(
-            baseOffset: 0,
-            extentOffset: _controller.text.length,
-          );
-        },
-        child: TextField(
-          controller: _controller,
-          focusNode: _focusNode,
-          scrollPadding: EdgeInsets.zero,
-          decoration: const InputDecoration(
-            contentPadding: EdgeInsets.symmetric(vertical: 4.0),
-            border: InputBorder.none,
-            isDense: true,
-          ),
-          style: Theme.of(context).textTheme.bodyMedium,
-          // cursorColor: widget.cursorColor,
-          // obscureText: widget.enableObscure,
+      onDoubleTap: () {
+        _controller.selection = TextSelection(
+          baseOffset: 0,
+          extentOffset: _controller.text.length,
+        );
+      },
+      child: TextField(
+        controller: _controller,
+        focusNode: _focusNode,
+        scrollPadding: EdgeInsets.zero,
+        decoration: const InputDecoration(
+          contentPadding: EdgeInsets.symmetric(vertical: 4.0),
+          border: InputBorder.none,
+          isDense: true,
         ),
+        style: Theme.of(context).textTheme.bodyMedium,
       ),
     );
   }

+ 1 - 1
frontend/appflowy_flutter/packages/flowy_infra/lib/language.dart

@@ -38,7 +38,7 @@ String languageFromLocale(Locale locale) {
     case "hu":
       return "Magyar";
     case "id":
-      return "Bahasa";
+      return "Bahasa Indonesia";
     case "it":
       return "Italiano";
     case "ja":

+ 2 - 2
frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/extension.dart

@@ -3,7 +3,7 @@ export 'package:styled_widget/styled_widget.dart';
 
 extension FlowyStyledWidget on Widget {
   Widget bottomBorder({double width = 1.0, Color color = Colors.grey}) {
-    return Container(
+    return DecoratedBox(
       decoration: BoxDecoration(
         border: Border(
           bottom: BorderSide(width: width, color: color),
@@ -14,7 +14,7 @@ extension FlowyStyledWidget on Widget {
   }
 
   Widget topBorder({double width = 1.0, Color color = Colors.grey}) {
-    return Container(
+    return DecoratedBox(
       decoration: BoxDecoration(
         border: Border(
           top: BorderSide(width: width, color: color),

+ 15 - 4
frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/icon_button.dart

@@ -16,6 +16,7 @@ class FlowyIconButton extends StatelessWidget {
   final EdgeInsets iconPadding;
   final BorderRadius? radius;
   final String? tooltipText;
+  final InlineSpan? richTooltipText;
   final bool preferBelow;
 
   const FlowyIconButton({
@@ -29,15 +30,22 @@ class FlowyIconButton extends StatelessWidget {
     this.iconPadding = EdgeInsets.zero,
     this.radius,
     this.tooltipText,
+    this.richTooltipText,
     this.preferBelow = true,
     required this.icon,
-  }) : super(key: key);
+  })  : assert((richTooltipText != null && tooltipText == null) ||
+            (richTooltipText == null && tooltipText != null) ||
+            (richTooltipText == null && tooltipText == null)),
+        super(key: key);
 
   @override
   Widget build(BuildContext context) {
     Widget child = icon;
     final size = Size(width, height ?? width);
 
+    final tooltipMessage =
+        tooltipText == null && richTooltipText == null ? '' : tooltipText;
+
     assert(size.width > iconPadding.horizontal);
     assert(size.height > iconPadding.vertical);
 
@@ -46,11 +54,14 @@ class FlowyIconButton extends StatelessWidget {
     final childSize = Size(childWidth, childWidth);
 
     return ConstrainedBox(
-      constraints:
-          BoxConstraints.tightFor(width: size.width, height: size.height),
+      constraints: BoxConstraints.tightFor(
+        width: size.width,
+        height: size.height,
+      ),
       child: Tooltip(
         preferBelow: preferBelow,
-        message: tooltipText ?? '',
+        message: tooltipMessage,
+        richMessage: richTooltipText,
         showDuration: Duration.zero,
         child: RawMaterialButton(
           visualDensity: VisualDensity.compact,

+ 31 - 0
frontend/appflowy_flutter/test/bloc_test/grid_test/grid_bloc_test.dart

@@ -13,9 +13,11 @@ void main() {
 
   group('Edit Grid:', () {
     late GridTestContext context;
+
     setUp(() async {
       context = await gridTest.createTestGrid();
     });
+
     // The initial number of rows is 3 for each grid.
     blocTest<GridBloc, GridState>(
       "create a row",
@@ -54,5 +56,34 @@ void main() {
         );
       },
     );
+
+    String? firstId;
+    String? secondId;
+    String? thirdId;
+
+    blocTest(
+      'reorder rows',
+      build: () => GridBloc(
+        view: context.gridView,
+        databaseController: DatabaseController(
+          view: context.gridView,
+          layoutType: DatabaseLayoutPB.Grid,
+        ),
+      )..add(const GridEvent.initial()),
+      act: (bloc) async {
+        await gridResponseFuture();
+
+        firstId = bloc.state.rowInfos[0].rowPB.id;
+        secondId = bloc.state.rowInfos[1].rowPB.id;
+        thirdId = bloc.state.rowInfos[2].rowPB.id;
+
+        bloc.add(const GridEvent.moveRow(0, 2));
+      },
+      verify: (bloc) {
+        expect(secondId, bloc.state.rowInfos[0].rowPB.id);
+        expect(thirdId, bloc.state.rowInfos[1].rowPB.id);
+        expect(firstId, bloc.state.rowInfos[2].rowPB.id);
+      },
+    );
   });
 }

+ 6 - 6
frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_tests.rs

@@ -159,7 +159,7 @@ mod tests {
               date: Some("1653609600".to_owned()),
               time: Some("9:00 AM".to_owned()),
               include_time: Some(true),
-              timezone_id: None,
+              timezone_id: Some("Etc/UTC".to_owned()),
             },
             None,
             "May 27, 2022 09:00 AM",
@@ -213,7 +213,7 @@ mod tests {
         date: Some("1653609600".to_owned()),
         time: Some("1:".to_owned()),
         include_time: Some(true),
-        timezone_id: None,
+        timezone_id: Some("Etc/UTC".to_owned()),
       },
       None,
       "May 27, 2022 01:00",
@@ -252,7 +252,7 @@ mod tests {
         date: Some("1653609600".to_owned()),
         time: Some("00:00".to_owned()),
         include_time: Some(true),
-        timezone_id: None,
+        timezone_id: Some("Etc/UTC".to_owned()),
       },
       None,
       "May 27, 2022 00:00",
@@ -273,7 +273,7 @@ mod tests {
         date: Some("1653609600".to_owned()),
         time: Some("1:00 am".to_owned()),
         include_time: Some(true),
-        timezone_id: None,
+        timezone_id: Some("Etc/UTC".to_owned()),
       },
       None,
       "May 27, 2022 01:00 AM",
@@ -372,7 +372,7 @@ mod tests {
         date: Some("1700006400".to_owned()),
         time: Some("08:00".to_owned()),
         include_time: Some(true),
-        timezone_id: None,
+        timezone_id: Some("Etc/UTC".to_owned()),
       },
     );
     assert_date(
@@ -382,7 +382,7 @@ mod tests {
         date: None,
         time: Some("14:00".to_owned()),
         include_time: None,
-        timezone_id: None,
+        timezone_id: Some("Etc/UTC".to_owned()),
       },
       Some(old_cell_data),
       "Nov 15, 2023 14:00",

+ 14 - 26
inlang.config.js

@@ -1,29 +1,17 @@
-
-// @ts-check
-
-/**
- * @type { import("@inlang/core/config").DefineConfig }
- */
 export async function defineConfig(env) {
-  const plugin = await env.$import(
-    "https://cdn.jsdelivr.net/gh/samuelstroschein/[email protected]/dist/index.js"
-  );
-
-  const { default: standardLintRules } = await env.$import(
-    "https://cdn.jsdelivr.net/gh/inlang/[email protected]/dist/index.js"
-  );
+	const { default: pluginJson } = await env.$import(
+		'https://cdn.jsdelivr.net/gh/samuelstroschein/inlang-plugin-json@2/dist/index.js'
+	);
 
-  const pluginConfig = {
-    pathPattern: "./frontend/appflowy_flutter/assets/translations/{language}.json",
-  };
+	const { default: standardLintRules } = await env.$import(
+		'https://cdn.jsdelivr.net/gh/inlang/standard-lint-rules@2/dist/index.js'
+	);
 
-  return {
-    referenceLanguage: "en",
-    languages: await plugin.getLanguages({ ...env, pluginConfig }),
-    readResources: (args) => plugin.readResources({ ...args, ...env, pluginConfig }),
-    writeResources: (args) => plugin.writeResources({ ...args, ...env, pluginConfig }),
-    lint: {
-      rules: [standardLintRules()],
-    },
-  };
-}
+	return {
+		referenceLanguage: 'en',
+		plugins: [pluginJson({ 
+			pathPattern: './frontend/appflowy_flutter/assets/translations/{language}.json',
+			variableReferencePattern: ["@:"]
+		}), standardLintRules()]
+	};
+}