Explorar el Código

Merge pull request #423 from gaganyadav80/drag_drop_docs

feat: adds drag and drop support to pages and docs
Nathan.fooo hace 3 años
padre
commit
c7d34465e1

+ 101 - 17
frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart

@@ -1,4 +1,5 @@
 import 'dart:async';
+import 'dart:developer';
 
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/view/view_ext.dart';
@@ -7,6 +8,7 @@ import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
+import 'package:reorderables/reorderables.dart';
 import 'package:styled_widget/styled_widget.dart';
 import 'item.dart';
 
@@ -27,29 +29,111 @@ class ViewSection extends StatelessWidget {
       },
       update: (_, notifier, controller) => controller!..update(notifier),
       child: Consumer(builder: (context, ViewSectionNotifier notifier, child) {
-        return _renderSectionItems(context, notifier.views);
+        return RenderSectionItems(views: notifier.views);
       }),
     );
   }
 
-  Widget _renderSectionItems(BuildContext context, List<View> views) {
-    List<Widget> viewWidgets = [];
-    if (views.isNotEmpty) {
-      viewWidgets = views
-          .map(
-            (view) => ViewSectionItem(
-              view: view,
-              isSelected: _isViewSelected(context, view.id),
-              onSelected: (view) {
-                context.read<ViewSectionNotifier>().selectedView = view;
-                Provider.of<MenuSharedState>(context, listen: false).selectedView.value = view;
-              },
-            ).padding(vertical: 4),
-          )
-          .toList(growable: false);
+  // Widget _renderSectionItems(BuildContext context, List<View> views) {
+  //   List<Widget> viewWidgets = [];
+  //   if (views.isNotEmpty) {
+  //     viewWidgets = views
+  //         .map(
+  //           (view) => ViewSectionItem(
+  //             view: view,
+  //             isSelected: _isViewSelected(context, view.id),
+  //             onSelected: (view) {
+  //               context.read<ViewSectionNotifier>().selectedView = view;
+  //               Provider.of<MenuSharedState>(context, listen: false).selectedView.value = view;
+  //             },
+  //           ).padding(vertical: 4),
+  //         )
+  //         .toList(growable: false);
+  //   }
+
+  //   return Column(children: viewWidgets);
+  // }
+
+  // bool _isViewSelected(BuildContext context, String viewId) {
+  //   final view = context.read<ViewSectionNotifier>().selectedView;
+  //   if (view == null) {
+  //     return false;
+  //   }
+  //   return view.id == viewId;
+  // }
+}
+
+class RenderSectionItems extends StatefulWidget {
+  const RenderSectionItems({Key? key, required this.views}) : super(key: key);
+
+  final List<View> views;
+
+  @override
+  State<RenderSectionItems> createState() => _RenderSectionItemsState();
+}
+
+class _RenderSectionItemsState extends State<RenderSectionItems> {
+  List<View> views = <View>[];
+
+  /// Maps the hasmap value of the section items to their index in the reorderable list.
+  //TODO @gaganyadav80: Retain this map to persist the order of the items.
+  final Map<String, int> _sectionItemIndex = <String, int>{};
+
+  void _initItemList() {
+    views.addAll(widget.views);
+
+    for (int i = 0; i < views.length; i++) {
+      if (_sectionItemIndex[views[i].id] == null) {
+        _sectionItemIndex[views[i].id] = i;
+      }
+    }
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _initItemList();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (views.isEmpty) {
+      _initItemList();
     }
 
-    return Column(children: viewWidgets);
+    log("BUILD: Section items: ${views.length}");
+    return ReorderableColumn(
+      needsLongPressDraggable: false,
+      onReorder: (oldIndex, index) {
+        setState(() {
+          // int index = newIndex > oldIndex ? newIndex - 1 : newIndex;
+          View section = views.removeAt(oldIndex);
+          views.insert(index, section);
+
+          _sectionItemIndex[section.id] = index;
+        });
+      },
+      children: List.generate(
+        views.length,
+        (index) {
+          return Container(
+            key: ValueKey(views[index].id),
+            child: views
+                .map(
+                  (view) => ViewSectionItem(
+                    view: view,
+                    isSelected: _isViewSelected(context, view.id),
+                    onSelected: (view) {
+                      context.read<ViewSectionNotifier>().selectedView = view;
+                      Provider.of<MenuSharedState>(context, listen: false).selectedView.value = view;
+                    },
+                  ).padding(vertical: 4),
+                )
+                .toList()[index],
+          );
+        },
+      ),
+    );
   }
 
   bool _isViewSelected(BuildContext context, String viewId) {

+ 69 - 20
frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart

@@ -27,7 +27,7 @@ import 'app/menu_app.dart';
 import 'app/create_button.dart';
 import 'menu_user.dart';
 
-class HomeMenu extends StatelessWidget {
+class HomeMenu extends StatefulWidget {
   final PublishNotifier<bool> _collapsedNotifier;
   final UserProfile user;
   final CurrentWorkspaceSetting workspaceSetting;
@@ -40,13 +40,22 @@ class HomeMenu extends StatelessWidget {
   })  : _collapsedNotifier = collapsedNotifier,
         super(key: key);
 
+  @override
+  State<HomeMenu> createState() => _HomeMenuState();
+}
+
+class _HomeMenuState extends State<HomeMenu> {
+  /// Maps the hashmap of the menu items to their index in reorderable list view.
+  //TODO @gaganyadav80: Retain this map to persist on app restarts.
+  final Map<int, int> _menuItemIndex = <int, int>{};
+
   @override
   Widget build(BuildContext context) {
     return MultiBlocProvider(
       providers: [
         BlocProvider<MenuBloc>(
           create: (context) {
-            final menuBloc = getIt<MenuBloc>(param1: user, param2: workspaceSetting.workspace.id);
+            final menuBloc = getIt<MenuBloc>(param1: widget.user, param2: widget.workspaceSetting.workspace.id);
             menuBloc.add(const MenuEvent.initial());
             return menuBloc;
           },
@@ -63,7 +72,7 @@ class HomeMenu extends StatelessWidget {
           BlocListener<MenuBloc, MenuState>(
             listenWhen: (p, c) => p.isCollapse != c.isCollapse,
             listener: (context, state) {
-              _collapsedNotifier.value = state.isCollapse;
+              widget._collapsedNotifier.value = state.isCollapse;
             },
           )
         ],
@@ -80,7 +89,8 @@ class HomeMenu extends StatelessWidget {
     return Container(
       color: theme.bg1,
       child: ChangeNotifierProvider(
-        create: (_) => MenuSharedState(view: workspaceSetting.hasLatestView() ? workspaceSetting.latestView : null),
+        create: (_) =>
+            MenuSharedState(view: widget.workspaceSetting.hasLatestView() ? widget.workspaceSetting.latestView : null),
         child: Consumer(builder: (context, MenuSharedState sharedState, child) {
           return Column(
             mainAxisAlignment: MainAxisAlignment.start,
@@ -115,26 +125,65 @@ class HomeMenu extends StatelessWidget {
           child: BlocSelector<MenuBloc, MenuState, List<Widget>>(
             selector: (state) {
               List<Widget> menuItems = [];
-              menuItems.add(MenuUser(user));
+              // menuItems.add(MenuUser(user));
               List<MenuApp> appWidgets =
                   state.apps.foldRight([], (apps, _) => apps.map((app) => MenuApp(app)).toList());
-              menuItems.addAll(appWidgets);
+              // menuItems.addAll(appWidgets);
+              for (int i = 0; i < appWidgets.length; i++) {
+                if (_menuItemIndex[appWidgets[i].key.hashCode] == null) {
+                  _menuItemIndex[appWidgets[i].key.hashCode] = i;
+                }
+
+                menuItems.insert(_menuItemIndex[appWidgets[i].key.hashCode]!, appWidgets[i]);
+              }
+
               return menuItems;
             },
-            builder: (context, menuItems) => ListView.separated(
-              itemCount: menuItems.length,
-              separatorBuilder: (context, index) {
-                if (index == 0) {
-                  return const VSpace(20);
-                } else {
-                  return VSpace(MenuAppSizes.appVPadding);
-                }
-              },
-              physics: StyledScrollPhysics(),
-              itemBuilder: (BuildContext context, int index) {
-                return menuItems[index];
-              },
-            ),
+            builder: (context, menuItems) {
+              return ReorderableListView.builder(
+                itemCount: menuItems.length,
+                buildDefaultDragHandles: false,
+                header: Padding(
+                  padding: EdgeInsets.only(bottom: 20.0 - MenuAppSizes.appVPadding),
+                  child: MenuUser(widget.user),
+                ),
+                onReorder: (oldIndex, newIndex) {
+                  int index = newIndex > oldIndex ? newIndex - 1 : newIndex;
+
+                  Widget menu = menuItems.removeAt(oldIndex);
+                  menuItems.insert(index, menu);
+
+                  final menuBloc = context.read<MenuBloc>();
+                  menuBloc.state.apps.forEach((a) {
+                    var app = a.removeAt(oldIndex);
+                    a.insert(index, app);
+                  });
+
+                  _menuItemIndex[menu.key.hashCode] = index;
+                },
+                physics: StyledScrollPhysics(),
+                itemBuilder: (BuildContext context, int index) {
+                  //? @gaganyadav80: To mimic the ListView.separated behavior, we need to add a padding.
+                  // EdgeInsets padding = EdgeInsets.zero;
+                  // if (index == 0) {
+                  //   padding = EdgeInsets.only(bottom: MenuAppSizes.appVPadding / 2);
+                  // } else if (index == menuItems.length - 1) {
+                  //   padding = EdgeInsets.only(top: MenuAppSizes.appVPadding / 2);
+                  // } else {
+                  //   padding = EdgeInsets.symmetric(vertical: MenuAppSizes.appVPadding / 2);
+                  // }
+
+                  return ReorderableDragStartListener(
+                    key: ValueKey(menuItems[index].hashCode),
+                    index: index,
+                    child: Padding(
+                      padding: EdgeInsets.symmetric(vertical: MenuAppSizes.appVPadding / 2),
+                      child: menuItems[index],
+                    ),
+                  );
+                },
+              );
+            },
           ),
         ),
       ),