mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-25 07:07:32 -04:00
feat: adds drag-drop to docs section
This commit is contained in:
parent
628e53adc1
commit
8ab2fe3e30
3 changed files with 147 additions and 37 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:app_flowy/startup/startup.dart';
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
import 'package:app_flowy/workspace/application/view/view_ext.dart';
|
import 'package:app_flowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_stack.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:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:reorderables/reorderables.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'item.dart';
|
import 'item.dart';
|
||||||
import 'package:async/async.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;
|
final AppDataNotifier appData;
|
||||||
const ViewSection({Key? key, required this.appData}) : super(key: key);
|
const ViewSection({Key? key, required this.appData}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ViewSection> createState() => _ViewSectionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ViewSectionState extends State<ViewSection> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// The ViewSectionNotifier will be updated after AppDataNotifier changed passed by parent widget
|
// The ViewSectionNotifier will be updated after AppDataNotifier changed passed by parent widget
|
||||||
|
@ -20,21 +30,105 @@ class ViewSection extends StatelessWidget {
|
||||||
create: (_) {
|
create: (_) {
|
||||||
return ViewSectionNotifier(
|
return ViewSectionNotifier(
|
||||||
context: context,
|
context: context,
|
||||||
views: appData.views,
|
views: widget.appData.views,
|
||||||
initialSelectedView: appData.selectedView,
|
initialSelectedView: widget.appData.selectedView,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
update: (_, notifier, controller) => controller!..update(notifier),
|
update: (_, notifier, controller) => controller!..update(notifier),
|
||||||
child: Consumer(builder: (context, ViewSectionNotifier notifier, child) {
|
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<View> views) {
|
// Widget _renderSectionItems(BuildContext context, List<View> views) {
|
||||||
List<Widget> viewWidgets = [];
|
// return Container();
|
||||||
if (views.isNotEmpty) {
|
// }
|
||||||
viewWidgets = views
|
}
|
||||||
|
|
||||||
|
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<Widget> viewWidgets = List.empty(growable: true);
|
||||||
|
// List<Widget> sectionItems = List.empty(growable: true);
|
||||||
|
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);
|
||||||
|
// 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<ViewSectionNotifier>().selectedView = view;
|
||||||
|
// Provider.of<MenuSharedState>(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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
.map(
|
||||||
(view) => ViewSectionItem(
|
(view) => ViewSectionItem(
|
||||||
view: view,
|
view: view,
|
||||||
|
@ -45,10 +139,11 @@ class ViewSection extends StatelessWidget {
|
||||||
},
|
},
|
||||||
).padding(vertical: 4),
|
).padding(vertical: 4),
|
||||||
)
|
)
|
||||||
.toList(growable: false);
|
.toList()[index],
|
||||||
}
|
);
|
||||||
|
},
|
||||||
return Column(children: viewWidgets);
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isViewSelected(BuildContext context, String viewId) {
|
bool _isViewSelected(BuildContext context, String viewId) {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
export './app/header/header.dart';
|
export './app/header/header.dart';
|
||||||
export './app/menu_app.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/home/home_stack.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/trash/menu.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/trash/menu.dart';
|
||||||
import 'package:flowy_infra/notifier.dart';
|
import 'package:flowy_infra/notifier.dart';
|
||||||
|
@ -45,7 +47,10 @@ class HomeMenu extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomeMenuState extends State<HomeMenu> {
|
class _HomeMenuState extends State<HomeMenu> {
|
||||||
final List<Widget> _menuItems = List.empty(growable: true);
|
// final List<Widget> _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<int, int> _menuItemIndex = <int, int>{};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -122,26 +127,33 @@ class _HomeMenuState extends State<HomeMenu> {
|
||||||
behavior: const ScrollBehavior().copyWith(scrollbars: false),
|
behavior: const ScrollBehavior().copyWith(scrollbars: false),
|
||||||
child: BlocSelector<MenuBloc, MenuState, List<Widget>>(
|
child: BlocSelector<MenuBloc, MenuState, List<Widget>>(
|
||||||
selector: (state) {
|
selector: (state) {
|
||||||
// List<Widget> menuItems = [];
|
List<Widget> menuItems = [];
|
||||||
// menuItems.add(MenuUser(user));
|
// menuItems.add(MenuUser(user));
|
||||||
List<MenuApp> appWidgets =
|
List<MenuApp> appWidgets =
|
||||||
state.apps.foldRight([], (apps, _) => apps.map((app) => MenuApp(app)).toList());
|
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);
|
// 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) {
|
builder: (context, menuItems) {
|
||||||
return ReorderableListView.builder(
|
return ReorderableListView.builder(
|
||||||
|
@ -162,6 +174,8 @@ class _HomeMenuState extends State<HomeMenu> {
|
||||||
var app = a.removeAt(oldIndex);
|
var app = a.removeAt(oldIndex);
|
||||||
a.insert(index, app);
|
a.insert(index, app);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_menuItemIndex[menu.key.hashCode] = index;
|
||||||
},
|
},
|
||||||
physics: StyledScrollPhysics(),
|
physics: StyledScrollPhysics(),
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
|
|
@ -73,6 +73,7 @@ dependencies:
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
device_info_plus: ^3.2.1
|
device_info_plus: ^3.2.1
|
||||||
fluttertoast: ^8.0.8
|
fluttertoast: ^8.0.8
|
||||||
|
reorderables: ^0.4.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^1.0.0
|
flutter_lints: ^1.0.0
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue