diff --git a/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart b/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart index c4b3c8e1ff..5f97e7028e 100644 --- a/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart @@ -43,11 +43,19 @@ class AppBloc extends Bloc { appListener.start( viewsChanged: (result) { result.fold( - (views) => add(AppEvent.didReceiveViewUpdated(views)), + (views) { + if (!isClosed) { + add(AppEvent.didReceiveViewUpdated(views)); + } + }, (error) => Log.error(error), ); }, - appUpdated: (app) => add(AppEvent.appDidUpdate(app)), + appUpdated: (app) { + if (!isClosed) { + add(AppEvent.appDidUpdate(app)); + } + }, ); } @@ -149,40 +157,73 @@ class AppState with _$AppState { ); } -class AppViewDataNotifier extends ChangeNotifier { - List _views = []; - View? _selectedView; +class AppViewDataContext extends ChangeNotifier { + final String appId; + final ValueNotifier> _viewsNotifier = ValueNotifier([]); + final ValueNotifier _selectedViewNotifier = ValueNotifier(null); ExpandableController expandController = ExpandableController(initialExpanded: false); - AppViewDataNotifier() { + AppViewDataContext({required this.appId}) { _setLatestView(getIt().latestOpenView); getIt().addLatestViewListener((view) { _setLatestView(view); }); } - void _setLatestView(View? view) { - view?.freeze(); - _selectedView = view; - _expandIfNeed(); + VoidCallback addSelectedViewChangeListener(void Function(View?) callback) { + listener() { + callback(_selectedViewNotifier.value); + } + + _selectedViewNotifier.addListener(listener); + return listener; } - View? get selectedView => _selectedView; + void removeSelectedViewListener(VoidCallback listener) { + _selectedViewNotifier.removeListener(listener); + } - set views(List views) { - if (_views != views) { - _views = views; + void _setLatestView(View? view) { + view?.freeze(); + + if (_selectedViewNotifier.value != view) { + _selectedViewNotifier.value = view; _expandIfNeed(); notifyListeners(); } } + View? get selectedView => _selectedViewNotifier.value; + + set views(List views) { + if (_viewsNotifier.value != views) { + _viewsNotifier.value = views; + _expandIfNeed(); + notifyListeners(); + } + } + + UnmodifiableListView get views => UnmodifiableListView(_viewsNotifier.value); + + VoidCallback addViewsChangeListener(void Function(UnmodifiableListView) callback) { + listener() { + callback(views); + } + + _viewsNotifier.addListener(listener); + return listener; + } + + void removeViewsListener(VoidCallback listener) { + _viewsNotifier.removeListener(listener); + } + void _expandIfNeed() { - if (_selectedView == null) { + if (_selectedViewNotifier.value == null) { return; } - if (!_views.contains(_selectedView!)) { + if (!_viewsNotifier.value.contains(_selectedViewNotifier.value)) { return; } @@ -193,6 +234,4 @@ class AppViewDataNotifier extends ChangeNotifier { }); } } - - UnmodifiableListView get views => UnmodifiableListView(_views); } 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 new file mode 100644 index 0000000000..583eeca4ed --- /dev/null +++ b/frontend/app_flowy/lib/workspace/application/menu/menu_view_section_bloc.dart @@ -0,0 +1,82 @@ +import 'dart:async'; + +import 'package:app_flowy/workspace/application/app/app_bloc.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'; + +part 'menu_view_section_bloc.freezed.dart'; + +class ViewSectionBloc extends Bloc { + void Function()? _viewsListener; + void Function()? _selectedViewlistener; + final AppViewDataContext appViewData; + + ViewSectionBloc({ + required this.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)); + } + }, + didReceiveViewUpdated: (_DidReceiveViewUpdated value) { + emit(state.copyWith(views: value.views)); + }, + ); + }); + } + + void _startListening() { + _viewsListener = appViewData.addViewsChangeListener((views) { + if (!isClosed) { + add(ViewSectionEvent.didReceiveViewUpdated(views)); + } + }); + _selectedViewlistener = appViewData.addSelectedViewChangeListener((view) { + if (!isClosed) { + add(ViewSectionEvent.setSelectedView(view)); + } + }); + } + + @override + Future close() async { + if (_selectedViewlistener != null) { + appViewData.removeSelectedViewListener(_selectedViewlistener!); + } + + if (_viewsListener != null) { + appViewData.removeViewsListener(_viewsListener!); + } + + return super.close(); + } +} + +@freezed +class ViewSectionEvent with _$ViewSectionEvent { + const factory ViewSectionEvent.initial() = _Initial; + const factory ViewSectionEvent.setSelectedView(View? view) = _SetSelectedView; + const factory ViewSectionEvent.didReceiveViewUpdated(List views) = _DidReceiveViewUpdated; +} + +@freezed +class ViewSectionState with _$ViewSectionState { + const factory ViewSectionState({ + required List views, + View? selectedView, + }) = _ViewSectionState; + + factory ViewSectionState.initial(AppViewDataContext appViewData) => ViewSectionState( + views: appViewData.views, + selectedView: appViewData.selectedView, + ); +} diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart index a7167a1997..67b52481e7 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart @@ -18,11 +18,11 @@ class MenuApp extends StatefulWidget { } class _MenuAppState extends State { - late AppViewDataNotifier notifier; + late AppViewDataContext viewDataContext; @override void initState() { - notifier = AppViewDataNotifier(); + viewDataContext = AppViewDataContext(appId: widget.app.id); super.initState(); } @@ -46,16 +46,16 @@ class _MenuAppState extends State { ), BlocListener( listenWhen: (p, c) => p.views != c.views, - listener: (context, state) => notifier.views = state.views, + listener: (context, state) => viewDataContext.views = state.views, ), ], child: BlocBuilder( builder: (context, state) { return ChangeNotifierProvider.value( - value: notifier, - child: Consumer( - builder: (context, notifier, _) { - return expandableWrapper(context, notifier); + value: viewDataContext, + child: Consumer( + builder: (context, viewDataContext, _) { + return expandableWrapper(context, viewDataContext); }, ), ); @@ -65,9 +65,9 @@ class _MenuAppState extends State { ); } - ExpandableNotifier expandableWrapper(BuildContext context, AppViewDataNotifier notifier) { + ExpandableNotifier expandableWrapper(BuildContext context, AppViewDataContext viewDataContext) { return ExpandableNotifier( - controller: notifier.expandController, + controller: viewDataContext.expandController, child: ScrollOnExpand( scrollOnExpand: false, scrollOnCollapse: false, @@ -86,7 +86,7 @@ class _MenuAppState extends State { value: Provider.of(context, listen: true), child: MenuAppHeader(widget.app), ), - expanded: _renderViewSection(notifier), + expanded: ViewSection(appViewData: viewDataContext), collapsed: const SizedBox(), ), ], @@ -95,20 +95,9 @@ class _MenuAppState extends State { ); } - Widget _renderViewSection(AppViewDataNotifier notifier) { - return MultiProvider( - providers: [ChangeNotifierProvider.value(value: notifier)], - child: Consumer( - builder: (context, AppViewDataNotifier notifier, child) { - return ViewSection(appData: notifier); - }, - ), - ); - } - @override void dispose() { - notifier.dispose(); + viewDataContext.dispose(); super.dispose(); } } 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 3eb28aef1e..116f494887 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 @@ -3,35 +3,34 @@ 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:provider/provider.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 { - final AppViewDataNotifier appData; - const ViewSection({Key? key, required this.appData}) : super(key: key); + final AppViewDataContext appViewData; + const ViewSection({Key? key, required this.appViewData}) : super(key: key); @override Widget build(BuildContext context) { - // The ViewSectionNotifier will be updated after AppDataNotifier changed passed by parent widget - return ChangeNotifierProxyProvider( - create: (_) { - return ViewSectionNotifier( - context: context, - views: appData.views, - initialSelectedView: appData.selectedView, - ); + return BlocProvider( + create: (context) { + final bloc = ViewSectionBloc(appViewData: appViewData); + bloc.add(const ViewSectionEvent.initial()); + return bloc; }, - update: (_, notifier, controller) => controller!..update(notifier), - child: Consumer(builder: (context, ViewSectionNotifier notifier, child) { - return _SectionItems(views: notifier.views); - }), + child: BlocBuilder( + builder: (context, state) { + return _SectionItems(views: state.views); + }, + ), ); } @@ -135,11 +134,12 @@ class _SectionItemsState extends State<_SectionItems> { } bool _isViewSelected(BuildContext context, String viewId) { - final view = context.read().selectedView; - if (view == null) { - return false; - } - return view.id == viewId; + // final view = context.read().selectedView; + // if (view == null) { + // return false; + // } + // return view.id == viewId; + return false; } } @@ -151,7 +151,6 @@ class ViewSectionNotifier with ChangeNotifier { VoidCallback? _latestViewDidChangeFn; ViewSectionNotifier({ - required BuildContext context, required List views, View? initialSelectedView, }) : _views = views, @@ -192,7 +191,7 @@ class ViewSectionNotifier with ChangeNotifier { View? get selectedView => _selectedView; - void update(AppViewDataNotifier notifier) { + void update(AppViewDataContext notifier) { views = notifier.views; } 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 f353f533de..49959ce0ce 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart @@ -13,7 +13,6 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/workspace.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:expandable/expandable.dart'; import 'package:flowy_infra/time/duration.dart'; @@ -199,9 +198,7 @@ class MenuSharedState { final ValueNotifier _latestOpenView = ValueNotifier(null); MenuSharedState({View? view}) { - if (view != null) { - _latestOpenView.value = view; - } + _latestOpenView.value = view; } View? get latestOpenView => _latestOpenView.value; @@ -210,17 +207,17 @@ class MenuSharedState { _latestOpenView.value = view; } - VoidCallback addLatestViewListener(void Function(View?) latestViewDidChange) { - onChanged() { - latestViewDidChange(_latestOpenView.value); + VoidCallback addLatestViewListener(void Function(View?) callback) { + listener() { + callback(_latestOpenView.value); } - _latestOpenView.addListener(onChanged); - return onChanged; + _latestOpenView.addListener(listener); + return listener; } - void removeLatestViewListener(VoidCallback fn) { - _latestOpenView.removeListener(fn); + void removeLatestViewListener(VoidCallback listener) { + _latestOpenView.removeListener(listener); } }