From 25548ad9ebb02c45035bd56d3bd6f21e8a5fd2f8 Mon Sep 17 00:00:00 2001 From: appflowy Date: Tue, 26 Apr 2022 20:35:41 +0800 Subject: [PATCH] chore: add move view event --- .../app_flowy/lib/startup/deps_resolver.dart | 2 +- .../application/app/app_service.dart | 13 ++ .../menu/menu_view_section_bloc.dart | 49 +++- .../home/menu/app/section/item.dart | 15 +- .../home/menu/app/section/section.dart | 211 +++--------------- 5 files changed, 92 insertions(+), 198 deletions(-) diff --git a/frontend/app_flowy/lib/startup/deps_resolver.dart b/frontend/app_flowy/lib/startup/deps_resolver.dart index dc4a1b6fc7..a5d6321fa0 100644 --- a/frontend/app_flowy/lib/startup/deps_resolver.dart +++ b/frontend/app_flowy/lib/startup/deps_resolver.dart @@ -116,7 +116,7 @@ void _resolveFolderDeps(GetIt getIt) { getIt.registerFactoryParam( (app, _) => AppBloc( app: app, - appService: AppService(), + appService: AppService(appId: app.id), appListener: AppListener(appId: app.id), ), ); diff --git a/frontend/app_flowy/lib/workspace/application/app/app_service.dart b/frontend/app_flowy/lib/workspace/application/app/app_service.dart index 2ed3435b9f..af0be16497 100644 --- a/frontend/app_flowy/lib/workspace/application/app/app_service.dart +++ b/frontend/app_flowy/lib/workspace/application/app/app_service.dart @@ -54,4 +54,17 @@ class AppService { } return FolderEventUpdateApp(request).send(); } + + Future> moveView({ + required String viewId, + required int fromIndex, + required int toIndex, + }) { + UpdateAppPayload request = UpdateAppPayload.create()..appId = appId; + + if (name != null) { + request.name = name; + } + return FolderEventUpdateApp(request).send(); + } } diff --git a/frontend/app_flowy/lib/workspace/application/menu/menu_view_section_bloc.dart b/frontend/app_flowy/lib/workspace/application/menu/menu_view_section_bloc.dart index 583eeca4ed..10f4058a97 100644 --- a/frontend/app_flowy/lib/workspace/application/menu/menu_view_section_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/menu/menu_view_section_bloc.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'package:app_flowy/workspace/application/app/app_bloc.dart'; +import 'package:app_flowy/workspace/application/app/app_service.dart'; +import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -10,51 +12,73 @@ part 'menu_view_section_bloc.freezed.dart'; class ViewSectionBloc extends Bloc { void Function()? _viewsListener; void Function()? _selectedViewlistener; - final AppViewDataContext appViewData; + final AppViewDataContext _appViewData; + late final AppService _appService; ViewSectionBloc({ - required this.appViewData, - }) : super(ViewSectionState.initial(appViewData)) { + required AppViewDataContext appViewData, + }) : _appService = AppService(appId: appViewData.appId), + _appViewData = appViewData, + super(ViewSectionState.initial(appViewData)) { on((event, emit) async { await event.map( initial: (e) async { _startListening(); }, setSelectedView: (_SetSelectedView value) { - if (state.views.contains(value.view)) { - emit(state.copyWith(selectedView: value.view)); - } else { - emit(state.copyWith(selectedView: null)); - } + _setSelectView(value, emit); }, didReceiveViewUpdated: (_DidReceiveViewUpdated value) { emit(state.copyWith(views: value.views)); }, + moveView: (_MoveView value) async { + await _moveView(value); + }, ); }); } void _startListening() { - _viewsListener = appViewData.addViewsChangeListener((views) { + _viewsListener = _appViewData.addViewsChangeListener((views) { if (!isClosed) { add(ViewSectionEvent.didReceiveViewUpdated(views)); } }); - _selectedViewlistener = appViewData.addSelectedViewChangeListener((view) { + _selectedViewlistener = _appViewData.addSelectedViewChangeListener((view) { if (!isClosed) { add(ViewSectionEvent.setSelectedView(view)); } }); } + void _setSelectView(_SetSelectedView value, Emitter emit) { + if (state.views.contains(value.view)) { + emit(state.copyWith(selectedView: value.view)); + } else { + emit(state.copyWith(selectedView: null)); + } + } + + Future _moveView(_MoveView value) async { + if (value.fromIndex < state.views.length) { + final viewId = state.views[value.fromIndex].id; + final result = await _appService.moveView( + viewId: viewId, + fromIndex: value.fromIndex, + toIndex: value.toIndex, + ); + result.fold((l) => null, (err) => Log.error(err)); + } + } + @override Future close() async { if (_selectedViewlistener != null) { - appViewData.removeSelectedViewListener(_selectedViewlistener!); + _appViewData.removeSelectedViewListener(_selectedViewlistener!); } if (_viewsListener != null) { - appViewData.removeViewsListener(_viewsListener!); + _appViewData.removeViewsListener(_viewsListener!); } return super.close(); @@ -65,6 +89,7 @@ class ViewSectionBloc extends Bloc { class ViewSectionEvent with _$ViewSectionEvent { const factory ViewSectionEvent.initial() = _Initial; const factory ViewSectionEvent.setSelectedView(View? view) = _SetSelectedView; + const factory ViewSectionEvent.moveView(int fromIndex, int toIndex) = _MoveView; const factory ViewSectionEvent.didReceiveViewUpdated(List views) = _DidReceiveViewUpdated; } diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/item.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/item.dart index 290d2bd328..d2bc81472c 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/item.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/item.dart @@ -40,12 +40,15 @@ class ViewSectionItem extends StatelessWidget { ], child: BlocBuilder( builder: (context, state) { - return InkWell( - onTap: () => onSelected(context.read().state.view), - child: FlowyHover( - style: HoverStyle(hoverColor: theme.bg3), - builder: (_, onHover) => _render(context, onHover, state, theme.iconColor), - setSelected: () => state.isEditing || isSelected, + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: InkWell( + onTap: () => onSelected(context.read().state.view), + child: FlowyHover( + style: HoverStyle(hoverColor: theme.bg3), + builder: (_, onHover) => _render(context, onHover, state, theme.iconColor), + setSelected: () => state.isEditing || isSelected, + ), ), ); }, 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 116f494887..1dc1393a6e 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,17 +1,14 @@ -import 'dart:async'; -import 'dart:developer'; - import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/app/app_bloc.dart'; import 'package:app_flowy/workspace/application/menu/menu_view_section_bloc.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/menu/menu.dart'; -import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:reorderables/reorderables.dart'; -import 'package:styled_widget/styled_widget.dart'; + import 'item.dart'; class ViewSection extends StatelessWidget { @@ -26,192 +23,48 @@ class ViewSection extends StatelessWidget { bloc.add(const ViewSectionEvent.initial()); return bloc; }, - child: BlocBuilder( - builder: (context, state) { - return _SectionItems(views: state.views); + child: BlocListener( + listenWhen: (p, c) => p.selectedView != c.selectedView, + listener: (context, state) { + if (state.selectedView != null) { + WidgetsBinding.instance?.addPostFrameCallback((_) { + getIt().setPlugin(state.selectedView!.plugin()); + }); + } }, + child: BlocBuilder( + builder: (context, state) { + return _reorderableColum(context, state); + }, + ), ), ); } - // 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); - // } + ReorderableColumn _reorderableColum(BuildContext context, ViewSectionState state) { + final children = state.views.map((view) { + return ViewSectionItem( + key: ValueKey(view.id), + view: view, + isSelected: _isViewSelected(state, view.id), + onSelected: (view) => getIt().latestOpenView = view, + ); + }).toList(); - // return Column(children: viewWidgets); - // } - - // bool _isViewSelected(BuildContext context, String viewId) { - // final view = context.read().selectedView; - // if (view == null) { - // return false; - // } - // return view.id == viewId; - // } -} - -class _SectionItems extends StatefulWidget { - const _SectionItems({Key? key, required this.views}) : super(key: key); - - final List views; - - @override - State<_SectionItems> createState() => _SectionItemsState(); -} - -class _SectionItemsState extends State<_SectionItems> { - 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); - - 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(); - } - - 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; - }); + context.read().add(ViewSectionEvent.moveView(oldIndex, 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) => getIt().latestOpenView = view, - ).padding(vertical: 4), - ) - .toList()[index], - ); - }, - ), + children: children, ); } - bool _isViewSelected(BuildContext context, String viewId) { - // final view = context.read().selectedView; - // if (view == null) { - // return false; - // } - // return view.id == viewId; - return false; - } -} - -class ViewSectionNotifier with ChangeNotifier { - bool isDisposed = false; - List _views; - View? _selectedView; - Timer? _notifyListenerOperation; - VoidCallback? _latestViewDidChangeFn; - - ViewSectionNotifier({ - required List views, - View? initialSelectedView, - }) : _views = views, - _selectedView = initialSelectedView { - _latestViewDidChangeFn = getIt().addLatestViewListener((latestOpenView) { - if (_views.contains(latestOpenView)) { - selectedView = latestOpenView; - } else { - selectedView = null; - } - }); - } - - set views(List views) { - if (_views != views) { - _views = views; - _notifyListeners(); - } - } - - List get views => _views; - - set selectedView(View? view) { - if (_selectedView == view) { - return; - } - _selectedView = view; - _notifyListeners(); - - if (view != null) { - WidgetsBinding.instance?.addPostFrameCallback((_) { - getIt().setPlugin(view.plugin()); - }); - } else { - // do nothing - } - } - - View? get selectedView => _selectedView; - - void update(AppViewDataContext notifier) { - views = notifier.views; - } - - void _notifyListeners() { - _notifyListenerOperation?.cancel(); - _notifyListenerOperation = Timer(const Duration(milliseconds: 30), () { - if (!isDisposed) { - notifyListeners(); - } - }); - } - - @override - void dispose() { - isDisposed = true; - _notifyListenerOperation?.cancel(); - if (_latestViewDidChangeFn != null) { - getIt().removeLatestViewListener(_latestViewDidChangeFn!); - _latestViewDidChangeFn = null; - } - super.dispose(); + bool _isViewSelected(ViewSectionState state, String viewId) { + final view = state.selectedView; + if (view == null) { + return false; + } + return view.id == viewId; } }