diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_folders.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_folders.dart index c9588981ce..a5f4c210d7 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_folders.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_folders.dart @@ -1,10 +1,8 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/mobile/application/mobile_router.dart'; -import 'package:appflowy/mobile/presentation/home/home.dart'; import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder.dart'; import 'package:appflowy/mobile/presentation/home/space/mobile_space.dart'; -import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; +import 'package:appflowy/mobile/presentation/presentation.dart'; import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; @@ -33,78 +31,56 @@ class MobileFolders extends StatelessWidget { @override Widget build(BuildContext context) { - return MultiBlocProvider( - providers: [ - BlocProvider( - create: (_) => SidebarSectionsBloc() - ..add(SidebarSectionsEvent.initial(user, workspaceId)), - ), - BlocProvider( - create: (_) => FavoriteBloc()..add(const FavoriteEvent.initial()), - ), - BlocProvider( - create: (_) => SpaceBloc() - ..add(SpaceEvent.initial(user, workspaceId, openFirstPage: false)), - ), - ], - child: BlocListener( - listener: (context, state) { - context.read().add( - SidebarSectionsEvent.initial( - user, - state.currentWorkspace?.workspaceId ?? workspaceId, + final workspaceId = + context.read().state.currentWorkspace?.workspaceId ?? + ''; + return BlocListener( + listener: (context, state) { + context.read().add( + SidebarSectionsEvent.initial( + user, + state.currentWorkspace?.workspaceId ?? workspaceId, + ), + ); + context.read().add( + SpaceEvent.reset( + user, + state.currentWorkspace?.workspaceId ?? workspaceId, + ), + ); + }, + child: const _MobileFolder(), + ); + } +} + +class _MobileFolder extends StatefulWidget { + const _MobileFolder(); + + @override + State<_MobileFolder> createState() => _MobileFolderState(); +} + +class _MobileFolderState extends State<_MobileFolder> { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return SlidableAutoCloseBehavior( + child: Column( + children: [ + ..._buildSpaceOrSection(context, state), + const VSpace(4.0), + const Padding( + padding: EdgeInsets.symmetric( + horizontal: HomeSpaceViewSizes.mHorizontalPadding, ), - ); - context.read().add( - SpaceEvent.reset( - user, - state.currentWorkspace?.workspaceId ?? workspaceId, - ), - ); - }, - child: MultiBlocListener( - listeners: [ - BlocListener( - listenWhen: (p, c) => - p.lastCreatedPage?.id != c.lastCreatedPage?.id, - listener: (context, state) { - final lastCreatedPage = state.lastCreatedPage; - if (lastCreatedPage != null) { - context.pushView(lastCreatedPage); - } - }, - ), - BlocListener( - listenWhen: (p, c) => - p.lastCreatedRootView?.id != c.lastCreatedRootView?.id, - listener: (context, state) { - final lastCreatedPage = state.lastCreatedRootView; - if (lastCreatedPage != null) { - context.pushView(lastCreatedPage); - } - }, - ), - ], - child: BlocBuilder( - builder: (context, state) { - return SlidableAutoCloseBehavior( - child: Column( - children: [ - ..._buildSpaceOrSection(context, state), - const VSpace(4.0), - const Padding( - padding: EdgeInsets.symmetric( - horizontal: HomeSpaceViewSizes.mHorizontalPadding, - ), - child: _TrashButton(), - ), - ], - ), - ); - }, + child: _TrashButton(), + ), + ], ), - ), - ), + ); + }, ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart index be5eab3fbd..99c24bb05e 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart @@ -7,7 +7,9 @@ import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; +import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart'; import 'package:appflowy/workspace/application/recent/cached_recent_service.dart'; +import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart'; import 'package:appflowy/workspace/presentation/home/home_sizes.dart'; @@ -102,6 +104,7 @@ class _MobileHomePageState extends State { @override void dispose() { getIt().removeLatestViewListener(_onLatestViewChange); + super.dispose(); } @@ -134,6 +137,8 @@ class _MobileHomePageState extends State { return const SizedBox.shrink(); } + final workspaceId = state.currentWorkspace!.workspaceId; + return Column( children: [ // Header @@ -149,9 +154,36 @@ class _MobileHomePageState extends State { ), Expanded( - child: BlocProvider( - create: (context) => - SpaceOrderBloc()..add(const SpaceOrderEvent.initial()), + child: MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) => SpaceOrderBloc() + ..add(const SpaceOrderEvent.initial()), + ), + BlocProvider( + create: (_) => SidebarSectionsBloc() + ..add( + SidebarSectionsEvent.initial( + widget.userProfile, + workspaceId, + ), + ), + ), + BlocProvider( + create: (_) => + FavoriteBloc()..add(const FavoriteEvent.initial()), + ), + BlocProvider( + create: (_) => SpaceBloc() + ..add( + SpaceEvent.initial( + widget.userProfile, + workspaceId, + openFirstPage: false, + ), + ), + ), + ], child: MobileSpaceTab( userProfile: widget.userProfile, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart index f0f121df6b..d610b4452b 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/mobile_router.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart'; import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart'; import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart'; @@ -43,48 +43,17 @@ class MobileSectionFolder extends StatelessWidget { onPressed: () => context .read() .add(const FolderEvent.expandOrUnExpand()), - onAdded: () { - context.read().add( - SidebarSectionsEvent.createRootViewInSection( - name: LocaleKeys.menuAppHeader_defaultNewPageName - .tr(), - index: 0, - viewSection: spaceType.toViewSectionPB, - ), - ); - context.read().add( - const FolderEvent.expandOrUnExpand(isExpanded: true), - ); - }, + onAdded: () => _createNewPage(context), ), ), if (state.isExpanded) - ...views.map( - (view) => MobileViewItem( - key: ValueKey( - '${FolderSpaceType.private.name} ${view.id}', - ), + Padding( + padding: const EdgeInsets.only( + left: HomeSpaceViewSizes.leftPadding, + ), + child: _Pages( + views: views, spaceType: spaceType, - isFirstChild: view.id == views.first.id, - view: view, - level: 0, - leftPadding: HomeSpaceViewSizes.leftPadding, - isFeedback: false, - onSelected: context.pushView, - endActionPane: (context) { - final view = context.read().state.view; - return buildEndActionPane( - context, - [ - MobilePaneActionType.more, - if (view.layout == ViewLayoutPB.Document) - MobilePaneActionType.add, - ], - spaceType: spaceType, - needSpace: false, - spaceRatio: 5, - ); - }, ), ), ], @@ -93,4 +62,63 @@ class MobileSectionFolder extends StatelessWidget { ), ); } + + void _createNewPage(BuildContext context) { + context.read().add( + SidebarSectionsEvent.createRootViewInSection( + name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + index: 0, + viewSection: spaceType.toViewSectionPB, + ), + ); + context.read().add( + const FolderEvent.expandOrUnExpand(isExpanded: true), + ); + } +} + +class _Pages extends StatelessWidget { + const _Pages({ + required this.views, + required this.spaceType, + }); + + final List views; + final FolderSpaceType spaceType; + + @override + Widget build(BuildContext context) { + return Column( + children: views + .map( + (view) => MobileViewItem( + key: ValueKey( + '${FolderSpaceType.private.name} ${view.id}', + ), + spaceType: spaceType, + isFirstChild: view.id == views.first.id, + view: view, + level: 0, + leftPadding: HomeSpaceViewSizes.leftPadding, + isFeedback: false, + onSelected: context.pushView, + endActionPane: (context) { + final view = context.read().state.view; + return buildEndActionPane( + context, + [ + MobilePaneActionType.more, + if (view.layout == ViewLayoutPB.Document) + MobilePaneActionType.add, + ], + spaceType: spaceType, + needSpace: false, + spaceRatio: 5, + ); + }, + ), + ) + .toList(), + ); + } } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart index aa84dc7601..b1d2bf6909 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart @@ -32,6 +32,7 @@ class _MobileSectionFolderHeaderState extends State { Widget build(BuildContext context) { return Row( children: [ + const HSpace(HomeSpaceViewSizes.mHorizontalPadding), Expanded( child: FlowyButton( text: FlowyText.medium( @@ -57,15 +58,19 @@ class _MobileSectionFolderHeaderState extends State { }, ), ), - FlowyIconButton( - key: mobileCreateNewPageButtonKey, - hoverColor: Theme.of(context).colorScheme.secondaryContainer, - height: HomeSpaceViewSizes.mViewButtonDimension, - width: HomeSpaceViewSizes.mViewButtonDimension, - icon: const FlowySvg( - FlowySvgs.m_space_add_s, + GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: widget.onAdded, + child: Container( + // expand the touch area + margin: const EdgeInsets.symmetric( + horizontal: HomeSpaceViewSizes.mHorizontalPadding, + vertical: 8.0, + ), + child: const FlowySvg( + FlowySvgs.m_space_add_s, + ), ), - onPressed: widget.onAdded, ), ], ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart index ef46489c0d..ec2636eb0d 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart @@ -4,7 +4,6 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/home/space/mobile_space_header.dart'; import 'package:appflowy/mobile/presentation/home/space/mobile_space_menu.dart'; import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart'; -import 'package:appflowy/mobile/presentation/presentation.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; @@ -15,26 +14,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class MobileSpace extends StatefulWidget { +class MobileSpace extends StatelessWidget { const MobileSpace({super.key}); - @override - State createState() => _MobileSpaceState(); -} - -class _MobileSpaceState extends State { - @override - void initState() { - super.initState(); - createNewPageNotifier.addListener(_createNewPage); - } - - @override - void dispose() { - createNewPageNotifier.removeListener(_createNewPage); - super.dispose(); - } - @override Widget build(BuildContext context) { return BlocBuilder( @@ -50,7 +32,7 @@ class _MobileSpaceState extends State { MobileSpaceHeader( isExpanded: state.isExpanded, space: currentSpace, - onAdded: () => _showCreatePageMenu(currentSpace), + onAdded: () => _showCreatePageMenu(context, currentSpace), onPressed: () => _showSpaceMenu(context), ), Padding( @@ -91,16 +73,7 @@ class _MobileSpaceState extends State { ); } - void _createNewPage() { - context.read().add( - SpaceEvent.createPage( - name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), - layout: ViewLayoutPB.Document, - ), - ); - } - - void _showCreatePageMenu(ViewPB space) { + void _showCreatePageMenu(BuildContext context, ViewPB space) { final title = space.name; showMobileBottomSheet( context, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart index 097bd22910..fe36c392b4 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart @@ -1,16 +1,27 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/mobile/presentation/home/favorite_folder/favorite_space.dart'; import 'package:appflowy/mobile/presentation/home/home_space/home_space.dart'; import 'package:appflowy/mobile/presentation/home/recent_folder/recent_space.dart'; import 'package:appflowy/mobile/presentation/home/tab/_tab_bar.dart'; import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart'; +import 'package:appflowy/mobile/presentation/presentation.dart'; +import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart'; +import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; +import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; class MobileSpaceTab extends StatefulWidget { - const MobileSpaceTab({super.key, required this.userProfile}); + const MobileSpaceTab({ + super.key, + required this.userProfile, + }); final UserProfilePB userProfile; @@ -22,10 +33,19 @@ class _MobileSpaceTabState extends State with SingleTickerProviderStateMixin { TabController? tabController; + @override + void initState() { + super.initState(); + + mobileCreateNewPageNotifier.addListener(_createNewPage); + } + @override void dispose() { tabController?.removeListener(_onTabChange); tabController?.dispose(); + mobileCreateNewPageNotifier.removeListener(_createNewPage); + super.dispose(); } @@ -33,36 +53,60 @@ class _MobileSpaceTabState extends State Widget build(BuildContext context) { return Provider.value( value: widget.userProfile, - child: BlocBuilder( - builder: (context, state) { - if (state.isLoading) { - return const SizedBox.shrink(); - } + child: MultiBlocListener( + listeners: [ + BlocListener( + listenWhen: (p, c) => + p.lastCreatedPage?.id != c.lastCreatedPage?.id, + listener: (context, state) { + final lastCreatedPage = state.lastCreatedPage; + if (lastCreatedPage != null) { + context.pushView(lastCreatedPage); + } + }, + ), + BlocListener( + listenWhen: (p, c) => + p.lastCreatedRootView?.id != c.lastCreatedRootView?.id, + listener: (context, state) { + final lastCreatedPage = state.lastCreatedRootView; + if (lastCreatedPage != null) { + context.pushView(lastCreatedPage); + } + }, + ), + ], + child: BlocBuilder( + builder: (context, state) { + if (state.isLoading) { + return const SizedBox.shrink(); + } - _initTabController(state); + _initTabController(state); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MobileSpaceTabBar( - tabController: tabController!, - tabs: state.tabsOrder, - onReorder: (from, to) { - context.read().add( - SpaceOrderEvent.reorder(from, to), - ); - }, - ), - const HSpace(12.0), - Expanded( - child: TabBarView( - controller: tabController, - children: _buildTabs(state), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MobileSpaceTabBar( + tabController: tabController!, + tabs: state.tabsOrder, + onReorder: (from, to) { + context.read().add( + SpaceOrderEvent.reorder(from, to), + ); + }, ), - ), - ], - ); - }, + const HSpace(12.0), + Expanded( + child: TabBarView( + controller: tabController, + children: _buildTabs(state), + ), + ), + ], + ); + }, + ), ), ); } @@ -104,4 +148,27 @@ class _MobileSpaceTabState extends State } }).toList(); } + + // quick create new page when clicking the add button in navigation bar + void _createNewPage() { + if (context.read().state.spaces.isNotEmpty) { + context.read().add( + SpaceEvent.createPage( + name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + layout: ViewLayoutPB.Document, + ), + ); + } else { + context.read().add( + SidebarSectionsEvent.createRootViewInSection( + name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + index: 0, + viewSection: FolderSpaceType.public.toViewSectionPB, + ), + ); + context.read().add( + const FolderEvent.expandOrUnExpand(isExpanded: true), + ); + } + } } 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 538ec0a354..5253022d40 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 @@ enum BottomNavigationBarActionType { notificationMultiSelect, } -final PropertyValueNotifier createNewPageNotifier = +final PropertyValueNotifier mobileCreateNewPageNotifier = PropertyValueNotifier(null); final ValueNotifier bottomNavigationBarType = ValueNotifier(BottomNavigationBarActionType.home); @@ -232,7 +232,8 @@ class _HomePageNavigationBar extends StatelessWidget { void _onTap(BuildContext context, int bottomBarIndex) { if (_items[bottomBarIndex].label == _addLabel) { // show an add dialog - createNewPageNotifier.value = ViewLayoutPB.Document; + mobileCreateNewPageNotifier.value = ViewLayoutPB.Document; + return; } // When navigating to a new branch, it's recommended to use the goBranch