navigation.dart 5.3 KB

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