diff --git a/frontend/appflowy_flutter/lib/mobile/application/recent/recent_view_bloc.dart b/frontend/appflowy_flutter/lib/mobile/application/recent/recent_view_bloc.dart index 410bc68c4e..547c81f00b 100644 --- a/frontend/appflowy_flutter/lib/mobile/application/recent/recent_view_bloc.dart +++ b/frontend/appflowy_flutter/lib/mobile/application/recent/recent_view_bloc.dart @@ -57,7 +57,19 @@ class RecentViewBloc extends Bloc { } }, ); + + // only document supports the cover + if (view.layout != ViewLayoutPB.Document) { + emit( + state.copyWith( + name: view.name, + icon: view.icon.value, + ), + ); + } + final cover = getCoverV2(); + if (cover != null) { emit( state.copyWith( diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/favorite_space.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/favorite_space.dart index f734690eb1..684442a74a 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/favorite_space.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/favorite_space.dart @@ -11,7 +11,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class MobileFavoriteSpace extends StatelessWidget { +class MobileFavoriteSpace extends StatefulWidget { const MobileFavoriteSpace({ super.key, required this.userProfile, @@ -19,8 +19,18 @@ class MobileFavoriteSpace extends StatelessWidget { final UserProfilePB userProfile; + @override + State createState() => _MobileFavoriteSpaceState(); +} + +class _MobileFavoriteSpaceState extends State + with AutomaticKeepAliveClientMixin { + @override + bool get wantKeepAlive => true; + @override Widget build(BuildContext context) { + super.build(context); final workspaceId = context.read().state.currentWorkspace?.workspaceId ?? ''; @@ -28,7 +38,9 @@ class MobileFavoriteSpace extends StatelessWidget { providers: [ BlocProvider( create: (_) => SidebarSectionsBloc() - ..add(SidebarSectionsEvent.initial(userProfile, workspaceId)), + ..add( + SidebarSectionsEvent.initial(widget.userProfile, workspaceId), + ), ), BlocProvider( create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/home_space/home_space.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/home_space/home_space.dart index 9ce6bbe91f..6ba27a6766 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/home_space/home_space.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/home_space/home_space.dart @@ -5,13 +5,23 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class MobileHomeSpace extends StatelessWidget { +class MobileHomeSpace extends StatefulWidget { const MobileHomeSpace({super.key, required this.userProfile}); final UserProfilePB userProfile; + @override + State createState() => _MobileHomeSpaceState(); +} + +class _MobileHomeSpaceState extends State + with AutomaticKeepAliveClientMixin { + @override + bool get wantKeepAlive => true; + @override Widget build(BuildContext context) { + super.build(context); final workspaceId = context.read().state.currentWorkspace?.workspaceId ?? ''; @@ -23,7 +33,7 @@ class MobileHomeSpace extends StatelessWidget { vertical: HomeSpaceViewSizes.mVerticalPadding, ), child: MobileFolders( - user: userProfile, + user: widget.userProfile, workspaceId: workspaceId, showFavorite: false, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/recent_space.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/recent_space.dart index a9dc41d093..3cab831c23 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/recent_space.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/recent_space.dart @@ -7,11 +7,21 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class MobileRecentSpace extends StatelessWidget { +class MobileRecentSpace extends StatefulWidget { const MobileRecentSpace({super.key}); + @override + State createState() => _MobileRecentSpaceState(); +} + +class _MobileRecentSpaceState extends State + with AutomaticKeepAliveClientMixin { + @override + bool get wantKeepAlive => true; + @override Widget build(BuildContext context) { + super.build(context); return BlocProvider( create: (context) => RecentViewsBloc()..add(const RecentViewsEvent.initial()), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_view_card.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_view_card.dart index b93123db6a..c4498a27cd 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_view_card.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_view_card.dart @@ -53,8 +53,8 @@ class MobileViewCard extends StatelessWidget { return MultiBlocProvider( providers: [ BlocProvider( - create: (context) => - ViewBloc(view: view)..add(const ViewEvent.initial()), + create: (context) => ViewBloc(view: view, shouldLoadChildViews: false) + ..add(const ViewEvent.initial()), ), BlocProvider( create: (context) => diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/_tab_bar.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/_tab_bar.dart index 24add88427..adf13ed667 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/_tab_bar.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/_tab_bar.dart @@ -39,6 +39,7 @@ class MobileSpaceTabBar extends StatelessWidget { indicatorColor: Theme.of(context).primaryColor, isScrollable: true, labelStyle: labelStyle, + labelColor: baseStyle?.color, labelPadding: const EdgeInsets.symmetric(horizontal: 12.0), unselectedLabelStyle: unselectedLabelStyle, overlayColor: WidgetStateProperty.all(Colors.transparent), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart index 1849388db4..3115428eb5 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart @@ -21,7 +21,7 @@ class MobileBottomNavigationBar extends StatelessWidget { return Scaffold( body: navigationShell, bottomNavigationBar: Theme( - data: ThemeData( + data: Theme.of(context).copyWith( splashColor: Colors.transparent, highlightColor: Colors.transparent, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart b/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart index 021c671ce9..7bd7a50157 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart @@ -257,7 +257,6 @@ class _SingleMobileInnerViewItemState extends State { final children = [ // expand icon _buildLeftIcon(), - const HSpace(6), // icon _buildViewIcon(), const HSpace(8), @@ -274,11 +273,11 @@ class _SingleMobileInnerViewItemState extends State { // hover action // ··· more action button - // children.add(_buildViewMoreActionButton(context)); + children.add(_buildViewMoreButton(context)); // only support add button for document layout if (!widget.isFeedback && widget.view.layout == ViewLayoutPB.Document) { // + button - children.add(_buildViewMoreButton(context)); + children.add(_buildViewAddButton(context)); } @@ -326,22 +325,20 @@ class _SingleMobileInnerViewItemState extends State { // show > if the view is expandable. // show · if the view can't contain child views. Widget _buildLeftIcon() { - if (isReferencedDatabaseView(widget.view, widget.parentView)) { - return const _DotIconWidget(); - } - if (context.read().state.view.childViews.isEmpty) { return HSpace(widget.leftPadding); } return GestureDetector( - child: AnimatedRotation( - duration: const Duration(milliseconds: 250), - turns: widget.isExpanded ? 0 : -0.25, - child: const FlowySvg( - FlowySvgs.m_expand_s, - blendMode: null, - ), + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.only(right: 6.0, top: 6.0, bottom: 6.0), + child: FlowySvg( + widget.isExpanded + ? FlowySvgs.m_expand_s + : FlowySvgs.m_collapse_s, + blendMode: null, + ), ), onTap: () { context @@ -431,25 +428,6 @@ class _SingleMobileInnerViewItemState extends State { } } -class _DotIconWidget extends StatelessWidget { - const _DotIconWidget(); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(6.0), - child: Container( - width: 4, - height: 4, - decoration: BoxDecoration( - color: Theme.of(context).iconTheme.color, - borderRadius: BorderRadius.circular(2), - ), - ), - ); - } -} - // workaround: we should use view.isEndPoint or something to check if the view can contain child views. But currently, we don't have that field. bool isReferencedDatabaseView(ViewPB view, ViewPB? parentView) { if (parentView == null) { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart index 6efdd815bb..92f04719a4 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart @@ -1,12 +1,14 @@ import 'dart:io'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; -import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart'; -import 'package:appflowy/plugins/base/icon/icon_picker.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/plugins/document/application/prelude.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/build_context_extension.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/icon/icon_selector.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart'; import 'package:appflowy/shared/appflowy_network_image.dart'; import 'package:appflowy/shared/flowy_gradient_colors.dart'; import 'package:appflowy/shared/google_fonts_extension.dart'; @@ -17,11 +19,12 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:auto_size_text_field/auto_size_text_field.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; double kDocumentCoverHeight = 98.0; double kDocumentTitlePadding = 20.0; @@ -171,12 +174,40 @@ class _DocumentImmersiveCoverState extends State { ), ), onTap: () async { - final result = await context.push( - MobileEmojiPickerScreen.routeName, + final pageStyleIconBloc = PageStyleIconBloc(view: widget.view) + ..add(const PageStyleIconEvent.initial()); + await showMobileBottomSheet( + context, + showDragHandle: true, + showDivider: false, + showDoneButton: true, + showHeader: true, + title: LocaleKeys.titleBar_pageIcon.tr(), + backgroundColor: AFThemeExtension.of(context).background, + enableDraggableScrollable: true, + minChildSize: 0.6, + initialChildSize: 0.61, + showRemoveButton: true, + onRemove: () { + pageStyleIconBloc.add( + const PageStyleIconEvent.updateIcon('', true), + ); + }, + scrollableWidgetBuilder: (_, controller) { + return BlocProvider.value( + value: pageStyleIconBloc, + child: Expanded( + child: Scrollbar( + controller: controller, + child: IconSelector( + scrollController: controller, + ), + ), + ), + ); + }, + builder: (_) => const SizedBox.shrink(), ); - if (result != null && context.mounted) { - context.read().add(ViewEvent.updateIcon(result.emoji)); - } }, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/icon/icon_selector.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/icon/icon_selector.dart new file mode 100644 index 0000000000..5da4528a3c --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/icon/icon_selector.dart @@ -0,0 +1,159 @@ +import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_search_text_field.dart'; +import 'package:appflowy/plugins/base/emoji/emoji_picker.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_emoji_mart/flutter_emoji_mart.dart'; + +class IconSelector extends StatefulWidget { + const IconSelector({ + super.key, + required this.scrollController, + }); + + final ScrollController scrollController; + + @override + State createState() => _IconSelectorState(); +} + +class _IconSelectorState extends State { + EmojiData? emojiData; + List availableEmojis = []; + + PageStyleIconBloc? pageStyleIconBloc; + + @override + void initState() { + super.initState(); + + // load the emoji data from cache if it's available + if (kCachedEmojiData != null) { + emojiData = kCachedEmojiData; + availableEmojis = _setupAvailableEmojis(emojiData!); + } else { + EmojiData.builtIn().then( + (value) { + kCachedEmojiData = value; + setState(() { + emojiData = value; + availableEmojis = _setupAvailableEmojis(value); + }); + }, + ); + } + + pageStyleIconBloc = context.read(); + } + + @override + void dispose() { + pageStyleIconBloc?.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (emojiData == null) { + return const Center(child: CircularProgressIndicator()); + } + + return RepaintBoundary( + child: BlocBuilder( + builder: (_, state) => Column( + children: [ + _buildSearchBar(context), + Expanded( + child: GridView.count( + crossAxisCount: 7, + controller: widget.scrollController, + children: [ + for (final emoji in availableEmojis) + _buildEmoji(context, emoji, state.icon), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildEmoji( + BuildContext context, + String emoji, + String? selectedEmoji, + ) { + Widget child = SizedBox.square( + dimension: 24.0, + child: Center( + child: FlowyText.emoji( + emoji, + fontSize: 24, + ), + ), + ); + + if (emoji == selectedEmoji) { + child = Center( + child: Container( + width: 40, + height: 40, + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + side: const BorderSide( + width: 1.40, + strokeAlign: BorderSide.strokeAlignOutside, + color: Color(0xFF00BCF0), + ), + borderRadius: BorderRadius.circular(10), + ), + ), + child: child, + ), + ); + } + + return GestureDetector( + onTap: () { + context.read().add( + PageStyleIconEvent.updateIcon(emoji, true), + ); + }, + child: child, + ); + } + + List _setupAvailableEmojis(EmojiData emojiData) { + final categories = emojiData.categories; + availableEmojis = categories + .map((e) => e.emojiIds.map((e) => emojiData.getEmojiById(e))) + .expand((e) => e) + .toList(); + return availableEmojis; + } + + Widget _buildSearchBar(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 12.0, + ), + child: FlowyMobileSearchTextField( + onChanged: (keyword) { + if (emojiData == null) { + return; + } + + final filtered = emojiData!.filterByKeyword(keyword); + final availableEmojis = _setupAvailableEmojis(filtered); + + setState(() { + this.availableEmojis = availableEmojis; + }); + }, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart index 434dba7337..3f3ed87522 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart @@ -1,8 +1,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; -import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_search_text_field.dart'; -import 'package:appflowy/plugins/base/emoji/emoji_picker.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/icon/icon_selector.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_util.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; @@ -11,7 +10,6 @@ import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_emoji_mart/flutter_emoji_mart.dart'; import 'package:go_router/go_router.dart'; class PageStyleIcon extends StatefulWidget { @@ -94,7 +92,7 @@ class _PageStyleIconState extends State { child: Expanded( child: Scrollbar( controller: controller, - child: _IconSelector( + child: IconSelector( scrollController: controller, ), ), @@ -105,154 +103,3 @@ class _PageStyleIconState extends State { ); } } - -class _IconSelector extends StatefulWidget { - const _IconSelector({ - required this.scrollController, - }); - - final ScrollController scrollController; - - @override - State<_IconSelector> createState() => _IconSelectorState(); -} - -class _IconSelectorState extends State<_IconSelector> { - EmojiData? emojiData; - List availableEmojis = []; - - PageStyleIconBloc? pageStyleIconBloc; - - @override - void initState() { - super.initState(); - - // load the emoji data from cache if it's available - if (kCachedEmojiData != null) { - emojiData = kCachedEmojiData; - availableEmojis = _setupAvailableEmojis(emojiData!); - } else { - EmojiData.builtIn().then( - (value) { - kCachedEmojiData = value; - setState(() { - emojiData = value; - availableEmojis = _setupAvailableEmojis(value); - }); - }, - ); - } - - pageStyleIconBloc = context.read(); - } - - @override - void dispose() { - pageStyleIconBloc?.close(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - if (emojiData == null) { - return const Center(child: CircularProgressIndicator()); - } - - return RepaintBoundary( - child: BlocBuilder( - builder: (_, state) => Column( - children: [ - _buildSearchBar(context), - Expanded( - child: GridView.count( - crossAxisCount: 7, - controller: widget.scrollController, - children: [ - for (final emoji in availableEmojis) - _buildEmoji(context, emoji, state.icon), - ], - ), - ), - ], - ), - ), - ); - } - - Widget _buildEmoji( - BuildContext context, - String emoji, - String? selectedEmoji, - ) { - Widget child = SizedBox.square( - dimension: 24.0, - child: Center( - child: FlowyText.emoji( - emoji, - fontSize: 24, - ), - ), - ); - - if (emoji == selectedEmoji) { - child = Center( - child: Container( - width: 40, - height: 40, - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - side: const BorderSide( - width: 1.40, - strokeAlign: BorderSide.strokeAlignOutside, - color: Color(0xFF00BCF0), - ), - borderRadius: BorderRadius.circular(10), - ), - ), - child: child, - ), - ); - } - - return GestureDetector( - onTap: () { - context.read().add( - PageStyleIconEvent.updateIcon(emoji, true), - ); - }, - child: child, - ); - } - - List _setupAvailableEmojis(EmojiData emojiData) { - final categories = emojiData.categories; - availableEmojis = categories - .map((e) => e.emojiIds.map((e) => emojiData.getEmojiById(e))) - .expand((e) => e) - .toList(); - return availableEmojis; - } - - Widget _buildSearchBar(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric( - vertical: 8.0, - horizontal: 12.0, - ), - child: FlowyMobileSearchTextField( - onChanged: (keyword) { - if (emojiData == null) { - return; - } - - final filtered = emojiData!.filterByKeyword(keyword); - final availableEmojis = _setupAvailableEmojis(filtered); - - setState(() { - this.availableEmojis = availableEmojis; - }); - }, - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart index ebf1cf2b3f..bf243f8a1c 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart @@ -19,7 +19,7 @@ import 'package:protobuf/protobuf.dart'; part 'view_bloc.freezed.dart'; class ViewBloc extends Bloc { - ViewBloc({required this.view}) + ViewBloc({required this.view, this.shouldLoadChildViews = true}) : viewBackendSvc = ViewBackendService(), listener = ViewListener(viewId: view.id), favoriteListener = FavoriteListener(), @@ -31,6 +31,7 @@ class ViewBloc extends Bloc { final ViewBackendService viewBackendSvc; final ViewListener listener; final FavoriteListener favoriteListener; + final bool shouldLoadChildViews; @override Future close() async { @@ -74,8 +75,10 @@ class ViewBloc extends Bloc { }, ); final isExpanded = await _getViewIsExpanded(view); - emit(state.copyWith(isExpanded: isExpanded)); - await _loadChildViews(emit); + emit(state.copyWith(isExpanded: isExpanded, view: view)); + if (shouldLoadChildViews) { + await _loadChildViews(emit); + } }, setIsEditing: (e) { emit(state.copyWith(isEditing: e.isEditing)); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart index 6ac2fd3e2e..1e6279f304 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart @@ -109,6 +109,7 @@ class _FavoriteFolderState extends State { const HSpace(4.0), ], shouldRenderChildren: false, + shouldLoadChildViews: false, onTertiarySelected: (_, view) => context.read().openTab(view), onSelected: (_, view) { @@ -152,6 +153,7 @@ class FavoriteMoreButton extends StatelessWidget { @override Widget build(BuildContext context) { final favoriteBloc = context.watch(); + final tabsBloc = context.read(); final unpinnedViews = favoriteBloc.state.unpinnedViews; // only show the more button if there are unpinned views if (unpinnedViews.isEmpty) { @@ -169,8 +171,11 @@ class FavoriteMoreButton extends StatelessWidget { borderRadius: 10.0, ), popupBuilder: (_) { - return BlocProvider.value( - value: favoriteBloc, + return MultiBlocProvider( + providers: [ + BlocProvider.value(value: favoriteBloc), + BlocProvider.value(value: tabsBloc), + ], child: const FavoriteMenu(minWidth: minWidth), ); }, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_menu.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_menu.dart index 4ec75da1d2..91f178af60 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_menu.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_menu.dart @@ -1,11 +1,13 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; +import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_menu_bloc.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_more_actions.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_pin_action.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/cupertino.dart'; @@ -125,7 +127,10 @@ class FavoriteMenu extends StatelessWidget { view: e, spaceType: FolderSpaceType.favorite, level: 0, - onSelected: (view, _) {}, + onSelected: (_, view) { + context.read().openPlugin(view); + PopoverContainer.maybeOf(context)?.close(); + }, isFeedback: false, isDraggable: false, shouldRenderChildren: false, @@ -143,7 +148,7 @@ class FavoriteMenu extends StatelessWidget { } } -class _FavoriteSearchField extends StatelessWidget { +class _FavoriteSearchField extends StatefulWidget { const _FavoriteSearchField({ required this.width, required this.onSearch, @@ -152,11 +157,30 @@ class _FavoriteSearchField extends StatelessWidget { final double width; final void Function(BuildContext context, String text) onSearch; + @override + State<_FavoriteSearchField> createState() => _FavoriteSearchFieldState(); +} + +class _FavoriteSearchFieldState extends State<_FavoriteSearchField> { + final focusNode = FocusNode(); + + @override + void initState() { + super.initState(); + focusNode.requestFocus(); + } + + @override + void dispose() { + focusNode.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Container( height: 30, - width: width, + width: widget.width, clipBehavior: Clip.antiAlias, decoration: ShapeDecoration( shape: RoundedRectangleBorder( @@ -169,8 +193,9 @@ class _FavoriteSearchField extends StatelessWidget { ), ), child: CupertinoSearchTextField( - onChanged: (text) => onSearch(context, text), + onChanged: (text) => widget.onSearch(context, text), padding: EdgeInsets.zero, + focusNode: focusNode, placeholder: LocaleKeys.search_label.tr(), prefixIcon: const FlowySvg(FlowySvgs.m_search_m), prefixInsets: const EdgeInsets.only(left: 12.0, right: 8.0), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_menu_bloc.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_menu_bloc.dart index 4ad963f50d..eb5d271836 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_menu_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_menu_bloc.dart @@ -1,5 +1,4 @@ import 'package:appflowy/workspace/application/favorite/favorite_service.dart'; -import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -43,9 +42,9 @@ class FavoriteMenuBloc extends Bloc { if (query.isNotEmpty) { queriedViews = _filter(views, query); - todayViews = _filter(state.todayViews, query); - thisWeekViews = _filter(state.thisWeekViews, query); - otherViews = _filter(state.otherViews, query); + todayViews = _filter(todayViews, query); + thisWeekViews = _filter(thisWeekViews, query); + otherViews = _filter(otherViews, query); } emit( @@ -66,28 +65,20 @@ class FavoriteMenuBloc extends Bloc { final FavoriteService _service = FavoriteService(); RepeatedFavoriteViewPB? _source; - List _filter(List views, String query) { - return views - .where( - (view) => view.name.toLowerCase().contains(query.toLowerCase()), - ) - .toList(); - } + List _filter(List views, String query) => views + .where((view) => view.name.toLowerCase().contains(query.toLowerCase())) + .toList(); // all, today, last week, other (List, List, List, List) _getViews( RepeatedFavoriteViewPB source, ) { - final List views = - source.items.map((v) => v.item).where((e) => !e.isPinned).toList(); + final List views = source.items.map((v) => v.item).toList(); final List todayViews = []; final List thisWeekViews = []; final List otherViews = []; for (final favoriteView in source.items) { final view = favoriteView.item; - if (view.isPinned) { - continue; - } final date = DateTime.fromMillisecondsSinceEpoch( favoriteView.timestamp.toInt() * 1000, ); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_pin_action.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_pin_action.dart index 53a53e5ae9..2a849b7e8d 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_pin_action.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_pin_action.dart @@ -3,7 +3,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; -import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; @@ -31,8 +30,6 @@ class FavoritePinAction extends StatelessWidget { width: 24, icon: icon, onPressed: () { - PopoverContainer.maybeOf(context)?.closeAll(); - view.isPinned ? context.read().add(FavoriteEvent.unpin(view)) : context.read().add(FavoriteEvent.pin(view)); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart index c10d14105a..6c16609a33 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart @@ -13,11 +13,12 @@ class SidebarFooter extends StatelessWidget { return const Row( children: [ Expanded(child: SidebarTrashButton()), - SizedBox( - height: 16, - child: VerticalDivider(width: 1, color: Color(0x141F2329)), - ), - Expanded(child: SidebarWidgetButton()), + // Enable it when the widget button is ready + // SizedBox( + // height: 16, + // child: VerticalDivider(width: 1, color: Color(0x141F2329)), + // ), + // Expanded(child: SidebarWidgetButton()), ], ); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart index 896896bfcc..a6c88b3059 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart @@ -57,6 +57,7 @@ class ViewItem extends StatelessWidget { this.shouldRenderChildren = true, this.leftIconBuilder, this.rightIconsBuilder, + this.shouldLoadChildViews = true, }); final ViewPB view; @@ -107,10 +108,14 @@ class ViewItem extends StatelessWidget { // custom the right icon widget, if it's null, the default ... and + button will be used final ViewItemRightIconsBuilder? rightIconsBuilder; + final bool shouldLoadChildViews; + @override Widget build(BuildContext context) { return BlocProvider( - create: (_) => ViewBloc(view: view)..add(const ViewEvent.initial()), + create: (_) => + ViewBloc(view: view, shouldLoadChildViews: shouldLoadChildViews) + ..add(const ViewEvent.initial()), child: BlocConsumer( listenWhen: (p, c) => c.lastCreatedView != null && diff --git a/frontend/resources/flowy_icons/16x/m_collapse.svg b/frontend/resources/flowy_icons/16x/m_collapse.svg index c88452eafd..bf88470d69 100644 --- a/frontend/resources/flowy_icons/16x/m_collapse.svg +++ b/frontend/resources/flowy_icons/16x/m_collapse.svg @@ -1,3 +1,3 @@ - - + + diff --git a/frontend/resources/translations/el-GR.json b/frontend/resources/translations/el-GR.json index 0ee0bc10cd..633a4adf65 100644 --- a/frontend/resources/translations/el-GR.json +++ b/frontend/resources/translations/el-GR.json @@ -201,8 +201,8 @@ "addBlockBelow": "Add a block below" }, "sideBar": { - "closeSidebar": "Close side bar", - "openSidebar": "Open side bar", + "closeSidebar": "Close sidebar", + "openSidebar": "Open sidebar", "personal": "Personal", "favorites": "Favorites", "clickToHidePersonal": "Click to hide personal section", diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 03a21fdedb..9504a20136 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -139,7 +139,7 @@ "addToFavorites": "Add to Favorites", "copyLink": "Copy Link", "changeIcon": "Change icon", - "collapseAllPages": "Collapse all pages" + "collapseAllPages": "Collapse all subpages" }, "blankPageTitle": "Blank page", "newPageText": "New page", @@ -229,8 +229,8 @@ "genSummary": "Generate summary" }, "sideBar": { - "closeSidebar": "Close side bar", - "openSidebar": "Open side bar", + "closeSidebar": "Close sidebar", + "openSidebar": "Open sidebar", "personal": "Personal", "private": "Private", "workspace": "Workspace",