diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart index dcb2d454b9..c0d82930b1 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/section.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/view/view_ext.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; @@ -5,14 +7,22 @@ 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'; import 'package:async/async.dart'; -class ViewSection extends StatelessWidget { +//? @gaganyadav80: Build 3 times on startup. Then one time on each doc change. + +class ViewSection extends StatefulWidget { final AppDataNotifier appData; const ViewSection({Key? key, required this.appData}) : super(key: key); + @override + State createState() => _ViewSectionState(); +} + +class _ViewSectionState extends State { @override Widget build(BuildContext context) { // The ViewSectionNotifier will be updated after AppDataNotifier changed passed by parent widget @@ -20,35 +30,120 @@ class ViewSection extends StatelessWidget { create: (_) { return ViewSectionNotifier( context: context, - views: appData.views, - initialSelectedView: appData.selectedView, + views: widget.appData.views, + initialSelectedView: widget.appData.selectedView, ); }, update: (_, notifier, controller) => controller!..update(notifier), child: Consumer(builder: (context, ViewSectionNotifier notifier, child) { - return _renderSectionItems(context, notifier.views); + log("BUILD: Section Bloc section update triggered + ${notifier.views.length}"); + return RenderSectionItems(views: notifier.views); }), ); } - Widget _renderSectionItems(BuildContext context, List views) { - List viewWidgets = []; - if (views.isNotEmpty) { - viewWidgets = views - .map( - (view) => ViewSectionItem( - view: view, - isSelected: _isViewSelected(context, view.id), - onSelected: (view) { - context.read().selectedView = view; - Provider.of(context, listen: false).selectedView.value = view; - }, - ).padding(vertical: 4), - ) - .toList(growable: false); + // Widget _renderSectionItems(BuildContext context, List views) { + // return Container(); + // } +} + +class RenderSectionItems extends StatefulWidget { + const RenderSectionItems({Key? key, required this.views}) : super(key: key); + + final List views; + + @override + State createState() => _RenderSectionItemsState(); +} + +class _RenderSectionItemsState extends State { + // List viewWidgets = List.empty(growable: true); + // List sectionItems = List.empty(growable: true); + List views = []; + + /// 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 _sectionItemIndex = {}; + + void _initItemList() { + views.addAll(widget.views); + // log(widget.views.length.toString()); + // if (widget.views.isNotEmpty) { + // viewWidgets = widget.views + // .map( + // (view) => ViewSectionItem( + // view: view, + // isSelected: _isViewSelected(context, view.id), + // onSelected: (view) { + // context.read().selectedView = view; + // Provider.of(context, listen: false).selectedView.value = view; + // }, + // ).padding(vertical: 4), + // ) + // .toList(growable: false); + // } + + // sectionItems.clear(); + + for (int i = 0; i < views.length; i++) { + if (_sectionItemIndex[views[i].id] == null) { + _sectionItemIndex[views[i].id] = i; + } + + // sectionItems.insert(_sectionItemIndex[viewWidgets[i].key.hashCode]!, viewWidgets[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( + // itemCount: sectionItems.length, + // buildDefaultDragHandles: false, + 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; + }); + }, + // physics: StyledScrollPhysics(), + // itemBuilder: (context, index) {}, + children: List.generate( + views.length, + (index) { + return Container( + key: ValueKey(views[index].id), + // index: index, + child: views + .map( + (view) => ViewSectionItem( + view: view, + isSelected: _isViewSelected(context, view.id), + onSelected: (view) { + context.read().selectedView = view; + Provider.of(context, listen: false).selectedView.value = view; + }, + ).padding(vertical: 4), + ) + .toList()[index], + ); + }, + ), + ); } bool _isViewSelected(BuildContext context, String viewId) { diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart index a3ca1a0876..99f3d22946 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart @@ -1,6 +1,8 @@ export './app/header/header.dart'; export './app/menu_app.dart'; +import 'dart:developer'; + import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:app_flowy/workspace/presentation/plugins/trash/menu.dart'; import 'package:flowy_infra/notifier.dart'; @@ -45,7 +47,10 @@ class HomeMenu extends StatefulWidget { } class _HomeMenuState extends State { - final List _menuItems = List.empty(growable: true); + // final List _menuItems = List.empty(growable: true); + /// Maps the hashmap of the menu items to their index in reorderable list view. + //TODO @gaganyadav80: need to retain this to persist on app restarts. + final Map _menuItemIndex = {}; @override Widget build(BuildContext context) { @@ -122,26 +127,33 @@ class _HomeMenuState extends State { behavior: const ScrollBehavior().copyWith(scrollbars: false), child: BlocSelector>( selector: (state) { - // List menuItems = []; + List menuItems = []; // menuItems.add(MenuUser(user)); List appWidgets = state.apps.foldRight([], (apps, _) => apps.map((app) => MenuApp(app)).toList()); - - for (var app in appWidgets) { - if (!_menuItems.any((oldElement) => oldElement.key == app.key)) { - _menuItems.add(app); - } - } - // TODO @gaganyadav: fix: concurrent modification exception - // Unhandled Exception: Concurrent modification during iteration: Instance(length:3) of '_GrowableList'. - for (var item in _menuItems) { - if (!appWidgets.any((oldElement) => oldElement.key == item.key)) { - _menuItems.remove(item); - } - } - // menuItems.addAll(appWidgets); - return _menuItems; + 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]); + } + + // for (var app in appWidgets) { + // if (!_menuItems.any((oldElement) => oldElement.key == app.key)) { + // _menuItems.add(app); + // } + // } + // // TODO @gaganyadav80: fix: concurrent modification exception + // // Unhandled Exception: Concurrent modification during iteration: Instance(length:3) of '_GrowableList'. + // for (var item in _menuItems) { + // if (!appWidgets.any((oldElement) => oldElement.key == item.key)) { + // _menuItems.remove(item); + // } + // } + + return menuItems; }, builder: (context, menuItems) { return ReorderableListView.builder( @@ -162,6 +174,8 @@ class _HomeMenuState extends State { var app = a.removeAt(oldIndex); a.insert(index, app); }); + + _menuItemIndex[menu.key.hashCode] = index; }, physics: StyledScrollPhysics(), itemBuilder: (BuildContext context, int index) { diff --git a/frontend/app_flowy/pubspec.yaml b/frontend/app_flowy/pubspec.yaml index 7f642f51b3..67b4a8aaef 100644 --- a/frontend/app_flowy/pubspec.yaml +++ b/frontend/app_flowy/pubspec.yaml @@ -73,6 +73,7 @@ dependencies: cupertino_icons: ^1.0.2 device_info_plus: ^3.2.1 fluttertoast: ^8.0.8 + reorderables: ^0.4.3 dev_dependencies: flutter_lints: ^1.0.0