navigation.dart 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import 'dart:io';
  2. import 'package:appflowy/generated/flowy_svgs.g.dart';
  3. import 'package:appflowy/generated/locale_keys.g.dart';
  4. import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
  5. import 'package:appflowy/workspace/presentation/home/home_stack.dart';
  6. import 'package:easy_localization/easy_localization.dart';
  7. import 'package:flowy_infra/size.dart';
  8. import 'package:flowy_infra_ui/style_widget/icon_button.dart';
  9. import 'package:flowy_infra_ui/style_widget/text.dart';
  10. import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
  11. import 'package:flutter/material.dart';
  12. import 'package:flutter_bloc/flutter_bloc.dart';
  13. import 'package:provider/provider.dart';
  14. import 'package:styled_widget/styled_widget.dart';
  15. typedef NaviAction = void Function();
  16. class NavigationNotifier with ChangeNotifier {
  17. List<NavigationItem> navigationItems;
  18. NavigationNotifier({required this.navigationItems});
  19. void update(PageNotifier notifier) {
  20. if (navigationItems != notifier.plugin.widgetBuilder.navigationItems) {
  21. navigationItems = notifier.plugin.widgetBuilder.navigationItems;
  22. notifyListeners();
  23. }
  24. }
  25. }
  26. class FlowyNavigation extends StatelessWidget {
  27. const FlowyNavigation({Key? key}) : super(key: key);
  28. @override
  29. Widget build(BuildContext context) {
  30. return ChangeNotifierProxyProvider<PageNotifier, NavigationNotifier>(
  31. create: (_) {
  32. final notifier = Provider.of<PageNotifier>(context, listen: false);
  33. return NavigationNotifier(
  34. navigationItems: notifier.plugin.widgetBuilder.navigationItems,
  35. );
  36. },
  37. update: (_, notifier, controller) => controller!..update(notifier),
  38. child: Expanded(
  39. child: Row(
  40. children: [
  41. _renderCollapse(context),
  42. Selector<NavigationNotifier, List<NavigationItem>>(
  43. selector: (context, notifier) => notifier.navigationItems,
  44. builder: (ctx, items, child) => Expanded(
  45. child: Row(
  46. children: _renderNavigationItems(items),
  47. ),
  48. ),
  49. ),
  50. ],
  51. ),
  52. ),
  53. );
  54. }
  55. Widget _renderCollapse(BuildContext context) {
  56. return BlocBuilder<HomeSettingBloc, HomeSettingState>(
  57. buildWhen: (p, c) => p.isMenuCollapsed != c.isMenuCollapsed,
  58. builder: (context, state) {
  59. if (state.isMenuCollapsed) {
  60. return RotationTransition(
  61. turns: const AlwaysStoppedAnimation(180 / 360),
  62. child: FlowyTooltip(
  63. richMessage: sidebarTooltipTextSpan(
  64. context,
  65. LocaleKeys.sideBar_openSidebar.tr(),
  66. ),
  67. child: FlowyIconButton(
  68. width: 24,
  69. hoverColor: Colors.transparent,
  70. onPressed: () {
  71. context
  72. .read<HomeSettingBloc>()
  73. .add(const HomeSettingEvent.collapseMenu());
  74. },
  75. iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
  76. icon: FlowySvg(
  77. FlowySvgs.hide_menu_m,
  78. color: Theme.of(context).iconTheme.color,
  79. ),
  80. ),
  81. ),
  82. );
  83. } else {
  84. return Container();
  85. }
  86. },
  87. );
  88. }
  89. List<Widget> _renderNavigationItems(List<NavigationItem> items) {
  90. if (items.isEmpty) {
  91. return [];
  92. }
  93. final List<NavigationItem> newItems = _filter(items);
  94. final Widget last = NaviItemWidget(newItems.removeLast());
  95. final List<Widget> widgets = List.empty(growable: true);
  96. // widgets.addAll(newItems.map((item) => NaviItemDivider(child: NaviItemWidget(item))).toList());
  97. for (final item in newItems) {
  98. widgets.add(NaviItemWidget(item));
  99. widgets.add(const Text('/'));
  100. }
  101. widgets.add(last);
  102. return widgets;
  103. }
  104. List<NavigationItem> _filter(List<NavigationItem> items) {
  105. final length = items.length;
  106. if (length > 4) {
  107. final first = items[0];
  108. final ellipsisItems = items.getRange(1, length - 2).toList();
  109. final last = items.getRange(length - 2, length).toList();
  110. return [
  111. first,
  112. EllipsisNaviItem(items: ellipsisItems),
  113. ...last,
  114. ];
  115. } else {
  116. return items;
  117. }
  118. }
  119. }
  120. class NaviItemWidget extends StatelessWidget {
  121. final NavigationItem item;
  122. const NaviItemWidget(this.item, {Key? key}) : super(key: key);
  123. @override
  124. Widget build(BuildContext context) {
  125. return Expanded(
  126. child: item.leftBarItem.padding(horizontal: 2, vertical: 2),
  127. );
  128. }
  129. }
  130. class NaviItemDivider extends StatelessWidget {
  131. final Widget child;
  132. const NaviItemDivider({super.key, required this.child});
  133. @override
  134. Widget build(BuildContext context) {
  135. return Row(
  136. children: [child, const Text('/')],
  137. );
  138. }
  139. }
  140. class EllipsisNaviItem extends NavigationItem {
  141. final List<NavigationItem> items;
  142. EllipsisNaviItem({
  143. required this.items,
  144. });
  145. @override
  146. Widget get leftBarItem => FlowyText.medium(
  147. '...',
  148. fontSize: FontSizes.s16,
  149. );
  150. @override
  151. Widget tabBarItem(String pluginId) => leftBarItem;
  152. @override
  153. NavigationCallback get action => (id) {};
  154. }
  155. TextSpan sidebarTooltipTextSpan(BuildContext context, String hintText) =>
  156. TextSpan(
  157. children: [
  158. TextSpan(
  159. text: "$hintText\n",
  160. ),
  161. TextSpan(
  162. text: Platform.isMacOS ? "⌘+\\" : "Ctrl+\\",
  163. ),
  164. ],
  165. );