import 'package:app_flowy/workspace/presentation/widgets/menu/widget/top_bar.dart'; import 'package:flowy_infra/notifier.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_sdk/protobuf/flowy-user-infra/protobuf.dart' show UserProfile; import 'package:flowy_sdk/protobuf/flowy-core-infra/view_create.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-core-infra/workspace_setting.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'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/menu/menu_bloc.dart'; import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart'; import 'package:app_flowy/workspace/presentation/widgets/menu/widget/menu_user.dart'; import 'widget/app/menu_app.dart'; import 'widget/app/create_button.dart'; import 'widget/menu_trash.dart'; // [[diagram: HomeMenu's widget structure]] // get user profile or modify user // ┌──────┐ // ┌──────────┐ ┌──▶│IUser │ // ┌─▶│MenuTopBar│ ┌────────┐ ┌─────────────┐ │ └──────┘ // │ └──────────┘ ┌───│MenuUser│─▶│MenuUserBloc │──┤ // ┌──────────┐ │ │ └────────┘ └─────────────┘ │ ┌─────────────┐ // │ HomeMenu │─┤ │ └──▶│IUserListener│ // └──────────┘ │ │ └─────────────┘ // │ │ listen workspace changes or user // │ impl │ profile changes // │ ┌──────────┐ ┌─────────┐ │ // └─▶│ MenuList │───▶│MenuItem │◀─┤ // └──────────┘ └─────────┘ │ ┌────────┐ // │ ┌─▶│AppBloc │ fetch app's views or modify view // │ │ └────────┘ // │ ┌────────┐ │ // └───│MenuApp │──┤ // └────────┘ class HomeMenu extends StatelessWidget { final PublishNotifier _collapsedNotifier; final UserProfile user; final CurrentWorkspaceSetting workspaceSetting; const HomeMenu({ Key? key, required this.user, required this.workspaceSetting, required PublishNotifier collapsedNotifier, }) : _collapsedNotifier = collapsedNotifier, super(key: key); @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ BlocProvider( create: (context) { final menuBloc = getIt(param1: user, param2: workspaceSetting.workspace.id); menuBloc.add(const MenuEvent.initial()); return menuBloc; }, ), ], child: MultiBlocListener( listeners: [ BlocListener( listenWhen: (p, c) => p.stackContext != c.stackContext, listener: (context, state) { getIt().setStack(state.stackContext); }, ), BlocListener( listenWhen: (p, c) => p.isCollapse != c.isCollapse, listener: (context, state) => _collapsedNotifier.value = state.isCollapse, ) ], child: BlocBuilder( builder: (context, state) => _renderBody(context), ), ), ); } Widget _renderBody(BuildContext context) { // nested cloumn: https://siddharthmolleti.com/flutter-box-constraints-nested-column-s-row-s-3dfacada7361 return Container( color: Theme.of(context).colorScheme.background, child: ChangeNotifierProvider( create: (_) => MenuSharedState(view: workspaceSetting.hasLatestView() ? workspaceSetting.latestView : null), child: Consumer(builder: (context, MenuSharedState sharedState, child) { return Column( mainAxisAlignment: MainAxisAlignment.start, children: [ Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ const MenuTopBar(), const VSpace(10), _renderApps(context), ], ).padding(horizontal: Insets.l), ), const VSpace(20), _renderTrash(context).padding(horizontal: Insets.l), const VSpace(20), _renderNewAppButton(context), ], ); }), ), ); } Widget _renderApps(BuildContext context) { return ExpandableTheme( data: ExpandableThemeData(useInkWell: true, animationDuration: Durations.medium), child: Expanded( child: ScrollConfiguration( behavior: const ScrollBehavior().copyWith(scrollbars: false), child: BlocSelector>( selector: (state) { List menuItems = []; menuItems.add(MenuUser(user)); List appWidgets = state.apps.foldRight([], (apps, _) => apps.map((app) => MenuApp(app)).toList()); menuItems.addAll(appWidgets); 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]; }, ), ), ), ), ); } Widget _renderTrash(BuildContext context) { return const MenuTrash(); } Widget _renderNewAppButton(BuildContext context) { return NewAppButton( press: (appName) => context.read().add(MenuEvent.createApp(appName, desc: "")), ); } } class MenuSharedState extends ChangeNotifier { PublishNotifier forcedOpenView = PublishNotifier(); ValueNotifier selectedView = ValueNotifier(null); MenuSharedState({View? view}) { if (view != null) { selectedView.value = view; } forcedOpenView.addPublishListener((view) { selectedView.value = view; }); } }