From ddf68b010dbf640b538d49ea26bf67225e4bc649 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Sat, 27 Jul 2024 21:05:51 +0800 Subject: [PATCH] chore: improve UI design on mobile (#5816) --- frontend/appflowy_flutter/ios/Podfile.lock | 2 +- .../page_style/document_page_style_bloc.dart | 3 + .../lib/mobile/presentation/base/gesture.dart | 54 ++++++++++++++++ .../bottom_sheet_add_new_page.dart | 15 +++-- .../bottom_sheet/bottom_sheet_view_item.dart | 48 +++------------ .../default_mobile_action_pane.dart | 36 +++++++---- .../show_mobile_bottom_sheet.dart | 13 ++-- .../home/favorite_folder/favorite_space.dart | 2 - .../mobile_home_favorite_folder.dart | 1 + .../home/home_space/home_space.dart | 2 - .../presentation/home/mobile_folders.dart | 8 ++- .../home/mobile_home_page_header.dart | 13 ++-- .../home/recent_folder/recent_space.dart | 2 - .../mobile_home_section_folder.dart | 1 + .../home/shared/mobile_page_card.dart | 12 ++-- .../presentation/home/space/mobile_space.dart | 59 +++++++++++++----- .../home/space/mobile_space_header.dart | 13 +++- .../home/space/mobile_space_menu.dart | 6 +- .../workspace_menu_bottom_sheet.dart | 23 ++++--- .../mobile_bottom_navigation_bar.dart | 7 ++- .../page_item/mobile_view_item.dart | 2 +- .../message/ai_markdown_text.dart | 9 ++- .../cover/document_immersive_cover.dart | 27 ++++---- .../lib/shared/feature_flags.dart | 1 - .../lib/startup/tasks/app_widget.dart | 54 ++++++++-------- .../workspace/application/view/view_ext.dart | 3 +- .../presentation/widgets/dialogs.dart | 61 ++++++++++++++++++- frontend/resources/translations/en.json | 4 +- 28 files changed, 325 insertions(+), 156 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/mobile/presentation/base/gesture.dart diff --git a/frontend/appflowy_flutter/ios/Podfile.lock b/frontend/appflowy_flutter/ios/Podfile.lock index 86cefebb34..c54ae23ed6 100644 --- a/frontend/appflowy_flutter/ios/Podfile.lock +++ b/frontend/appflowy_flutter/ios/Podfile.lock @@ -175,7 +175,7 @@ SPEC CHECKSUMS: file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265 + fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425 integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 diff --git a/frontend/appflowy_flutter/lib/mobile/application/page_style/document_page_style_bloc.dart b/frontend/appflowy_flutter/lib/mobile/application/page_style/document_page_style_bloc.dart index 52552fce3b..20ab2326ca 100644 --- a/frontend/appflowy_flutter/lib/mobile/application/page_style/document_page_style_bloc.dart +++ b/frontend/appflowy_flutter/lib/mobile/application/page_style/document_page_style_bloc.dart @@ -23,6 +23,9 @@ class DocumentPageStyleBloc await event.when( initial: () async { try { + if (view.id.isEmpty) { + return; + } final layoutObject = await ViewBackendService.getView(view.id).fold( (s) => jsonDecode(s.extra), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/gesture.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/gesture.dart new file mode 100644 index 0000000000..ba4ab3b1db --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/gesture.dart @@ -0,0 +1,54 @@ +import 'package:appflowy/shared/feedback_gesture_detector.dart'; +import 'package:flutter/material.dart'; + +class AnimatedGestureDetector extends StatefulWidget { + const AnimatedGestureDetector({ + super.key, + this.scaleFactor = 0.98, + this.feedback = true, + this.duration = const Duration(milliseconds: 100), + this.alignment = Alignment.center, + this.behavior = HitTestBehavior.opaque, + required this.onTapUp, + required this.child, + }); + + final Widget child; + final double scaleFactor; + final Duration duration; + final Alignment alignment; + final bool feedback; + final HitTestBehavior behavior; + final VoidCallback onTapUp; + + @override + State createState() => + _AnimatedGestureDetectorState(); +} + +class _AnimatedGestureDetectorState extends State { + double scale = 1.0; + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: widget.behavior, + onTapUp: (details) { + setState(() => scale = 1.0); + + HapticFeedbackType.vibrate.call(); + + widget.onTapUp(); + }, + onTapDown: (details) { + setState(() => scale = widget.scaleFactor); + }, + child: AnimatedScale( + scale: scale, + alignment: widget.alignment, + duration: widget.duration, + child: widget.child, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart index 1a8ff64f2b..3316b7049b 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart @@ -24,9 +24,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget { height: 52.0, leftIcon: const FlowySvg( FlowySvgs.icon_document_s, - size: Size.square(18), + size: Size.square(20), ), showTopBorder: false, + showBottomBorder: false, onTap: () => onAction(ViewLayoutPB.Document), ), FlowyOptionTile.text( @@ -34,9 +35,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget { height: 52.0, leftIcon: const FlowySvg( FlowySvgs.icon_grid_s, - size: Size.square(18), + size: Size.square(20), ), showTopBorder: false, + showBottomBorder: false, onTap: () => onAction(ViewLayoutPB.Grid), ), FlowyOptionTile.text( @@ -44,9 +46,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget { height: 52.0, leftIcon: const FlowySvg( FlowySvgs.icon_board_s, - size: Size.square(18), + size: Size.square(20), ), showTopBorder: false, + showBottomBorder: false, onTap: () => onAction(ViewLayoutPB.Board), ), FlowyOptionTile.text( @@ -54,9 +57,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget { height: 52.0, leftIcon: const FlowySvg( FlowySvgs.icon_calendar_s, - size: Size.square(18), + size: Size.square(20), ), showTopBorder: false, + showBottomBorder: false, onTap: () => onAction(ViewLayoutPB.Calendar), ), FlowyOptionTile.text( @@ -64,9 +68,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget { height: 52.0, leftIcon: const FlowySvg( FlowySvgs.chat_ai_page_s, - size: Size.square(18), + size: Size.square(20), ), showTopBorder: false, + showBottomBorder: false, onTap: () => onAction(ViewLayoutPB.Chat), ), ], diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart index 75b0151a3a..b76dc63b1d 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart @@ -1,4 +1,3 @@ -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/show_flowy_mobile_confirm_dialog.dart'; @@ -6,6 +5,7 @@ import 'package:appflowy/startup/tasks/app_widget.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -109,16 +109,6 @@ class _MobileViewItemBottomSheetState extends State { await _showConfirmDialog( onDelete: () { recentViewsBloc.add(RecentViewsEvent.removeRecentViews([viewId])); - - fToast.showToast( - child: const _RemoveToast(), - positionedToastBuilder: (context, child) { - return Positioned.fill( - top: 450, - child: child, - ); - }, - ); }, ); } @@ -136,38 +126,14 @@ class _MobileViewItemBottomSheetState extends State { ), onRightButtonPressed: (context) { onDelete(); + Navigator.pop(context); + + showToastNotification( + context, + message: LocaleKeys.sideBar_removeSuccess.tr(), + ); }, ); } } - -class _RemoveToast extends StatelessWidget { - const _RemoveToast(); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 13.0), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.0), - color: const Color(0xE5171717), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const FlowySvg( - FlowySvgs.success_s, - blendMode: null, - ), - const HSpace(8.0), - FlowyText.regular( - LocaleKeys.sideBar_removeSuccess.tr(), - fontSize: 16.0, - color: Colors.white, - ), - ], - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart index 1583e9e2e0..e0b10d153b 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart @@ -7,6 +7,7 @@ import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -40,18 +41,32 @@ enum MobilePaneActionType { backgroundColor: const Color(0xFFFA217F), svg: FlowySvgs.favorite_section_remove_from_favorite_s, size: 24.0, - onPressed: (context) => context - .read() - .add(FavoriteEvent.toggle(context.read().view)), + onPressed: (context) { + showToastNotification( + context, + message: LocaleKeys.button_unfavoriteSuccessfully.tr(), + ); + + context + .read() + .add(FavoriteEvent.toggle(context.read().view)); + }, ); case MobilePaneActionType.addToFavorites: return MobileSlideActionButton( backgroundColor: const Color(0xFF00C8FF), svg: FlowySvgs.favorite_s, size: 24.0, - onPressed: (context) => context - .read() - .add(FavoriteEvent.toggle(context.read().view)), + onPressed: (context) { + showToastNotification( + context, + message: LocaleKeys.button_favoriteSuccessfully.tr(), + ); + + context + .read() + .add(FavoriteEvent.toggle(context.read().view)); + }, ); case MobilePaneActionType.add: return MobileSlideActionButton( @@ -69,6 +84,7 @@ enum MobilePaneActionType { showDragHandle: true, showCloseButton: true, useRootNavigator: true, + showDivider: false, backgroundColor: Theme.of(context).colorScheme.surface, builder: (sheetContext) { return AddNewPageWidgetBottomSheet( @@ -145,8 +161,6 @@ enum MobilePaneActionType { ? MobileViewItemBottomSheetBodyAction.removeFromFavorites : MobileViewItemBottomSheetBodyAction.addToFavorites, MobileViewItemBottomSheetBodyAction.divider, - if (view.layout != ViewLayoutPB.Chat) - MobileViewItemBottomSheetBodyAction.duplicate, MobileViewItemBottomSheetBodyAction.divider, MobileViewItemBottomSheetBodyAction.removeFromRecent, ]; @@ -156,7 +170,6 @@ enum MobilePaneActionType { ? MobileViewItemBottomSheetBodyAction.removeFromFavorites : MobileViewItemBottomSheetBodyAction.addToFavorites, MobileViewItemBottomSheetBodyAction.divider, - MobileViewItemBottomSheetBodyAction.duplicate, ]; } } @@ -181,12 +194,13 @@ ActionPane buildEndActionPane( bool needSpace = true, MobilePageCardType? cardType, FolderSpaceType? spaceType, + required double spaceRatio, }) { return ActionPane( motion: const ScrollMotion(), - extentRatio: actions.length / 5, + extentRatio: actions.length / spaceRatio, children: [ - if (needSpace) const HSpace(20), + if (needSpace) const HSpace(60), ...actions.map( (action) => action.actionButton( context, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart index be815b6550..9af49e98c8 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart @@ -70,6 +70,7 @@ Future showMobileBottomSheet( backgroundColor ??= Theme.of(context).brightness == Brightness.light ? const Color(0xFFF7F8FB) : const Color(0xFF23262B); + barrierColor ??= Colors.black.withOpacity(0.3); return showModalBottomSheet( context: context, @@ -226,10 +227,14 @@ class BottomSheetHeader extends StatelessWidget { ), ), Align( - child: FlowyText( - title, - fontSize: 16.0, - fontWeight: FontWeight.w500, + child: Container( + constraints: const BoxConstraints(maxWidth: 250), + child: FlowyText( + title, + fontSize: 17.0, + fontWeight: FontWeight.w500, + overflow: TextOverflow.ellipsis, + ), ), ), if (showDoneButton) 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 ded486983e..36e6c57e85 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 @@ -100,8 +100,6 @@ class _FavoriteViews extends StatelessWidget { child: ListView.separated( key: const PageStorageKey('favorite_views_page_storage_key'), padding: EdgeInsets.only( - left: HomeSpaceViewSizes.mHorizontalPadding, - right: HomeSpaceViewSizes.mHorizontalPadding, bottom: HomeSpaceViewSizes.mVerticalPadding + MediaQuery.of(context).padding.bottom, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart index 57ac43f255..1efee460eb 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart @@ -72,6 +72,7 @@ class MobileFavoriteFolder extends StatelessWidget { MobilePaneActionType.more, ], spaceType: FolderSpaceType.favorite, + spaceRatio: 5, ), ), ), 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 965c396d42..02e5fce9ab 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 @@ -29,8 +29,6 @@ class _MobileHomeSpaceState extends State child: SingleChildScrollView( child: Padding( padding: EdgeInsets.only( - left: HomeSpaceViewSizes.mHorizontalPadding, - right: HomeSpaceViewSizes.mHorizontalPadding, top: HomeSpaceViewSizes.mVerticalPadding, bottom: HomeSpaceViewSizes.mVerticalPadding + MediaQuery.of(context).padding.bottom, 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 64ad7e4cd1..c9588981ce 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_folders.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_folders.dart @@ -9,6 +9,7 @@ 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/workspace/application/user/user_workspace_bloc.dart'; +import 'package:appflowy/workspace/presentation/home/home_sizes.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'; @@ -91,7 +92,12 @@ class MobileFolders extends StatelessWidget { children: [ ..._buildSpaceOrSection(context, state), const VSpace(4.0), - const _TrashButton(), + const Padding( + padding: EdgeInsets.symmetric( + horizontal: HomeSpaceViewSizes.mHorizontalPadding, + ), + child: _TrashButton(), + ), ], ), ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart index 28d0915aef..01f43ca87b 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart @@ -1,5 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/base/gesture.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/home/mobile_home_setting_page.dart'; import 'package:appflowy/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart'; @@ -113,8 +114,9 @@ class _MobileWorkspace extends StatelessWidget { if (currentWorkspace == null) { return const SizedBox.shrink(); } - return GestureDetector( - onTap: () { + return AnimatedGestureDetector( + alignment: Alignment.centerLeft, + onTapUp: () { context.read().add( const UserWorkspaceEvent.fetchWorkspaces(), ); @@ -143,7 +145,7 @@ class _MobileWorkspace extends StatelessWidget { : const HSpace(8), FlowyText.semibold( currentWorkspace.name, - fontSize: 16.0, + fontSize: 20.0, overflow: TextOverflow.ellipsis, ), ], @@ -162,9 +164,10 @@ class _MobileWorkspace extends StatelessWidget { showHeader: true, showDragHandle: true, showCloseButton: true, + useRootNavigator: true, title: LocaleKeys.workspace_menuTitle.tr(), backgroundColor: Theme.of(context).colorScheme.surface, - builder: (_) { + builder: (sheetContext) { return BlocProvider.value( value: context.read(), child: BlocBuilder( @@ -179,7 +182,7 @@ class _MobileWorkspace extends StatelessWidget { currentWorkspace: currentWorkspace, workspaces: workspaces, onWorkspaceSelected: (workspace) { - context.pop(); + Navigator.of(sheetContext).pop(); if (workspace == currentWorkspace) { return; 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 f3e807ec9c..e06506936c 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 @@ -72,8 +72,6 @@ class _RecentViews extends StatelessWidget { child: ListView.separated( key: const PageStorageKey('recent_views_page_storage_key'), padding: EdgeInsets.only( - left: HomeSpaceViewSizes.mHorizontalPadding, - right: HomeSpaceViewSizes.mHorizontalPadding, bottom: HomeSpaceViewSizes.mVerticalPadding + MediaQuery.of(context).padding.bottom, ), 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 4cd212f5a9..f0f121df6b 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 @@ -82,6 +82,7 @@ class MobileSectionFolder extends StatelessWidget { ], spaceType: spaceType, needSpace: false, + spaceRatio: 5, ); }, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_page_card.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_page_card.dart index 71be20453d..85cdc98c4a 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_page_card.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_page_card.dart @@ -5,6 +5,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/mobile/application/recent/recent_view_bloc.dart'; +import 'package:appflowy/mobile/presentation/base/gesture.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/shared/appflowy_network_image.dart'; @@ -15,6 +16,7 @@ import 'package:appflowy/workspace/application/settings/appearance/appearance_cu import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart'; import 'package:appflowy/workspace/application/settings/date_time/time_format_ext.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; +import 'package:appflowy/workspace/presentation/home/home_sizes.dart'; 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'; @@ -76,13 +78,14 @@ class MobileViewPage extends StatelessWidget { : MobilePaneActionType.addToFavorites, ], cardType: type, + spaceRatio: 4, ), - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTapUp: (_) => context.pushView(view), + child: AnimatedGestureDetector( + onTapUp: () => context.pushView(view), child: Row( mainAxisSize: MainAxisSize.min, children: [ + const HSpace(HomeSpaceViewSizes.mHorizontalPadding), Expanded(child: _buildDescription(context, state)), const HSpace(20.0), SizedBox( @@ -90,6 +93,7 @@ class MobileViewPage extends StatelessWidget { height: 60, child: _buildCover(context, state), ), + const HSpace(HomeSpaceViewSizes.mHorizontalPadding), ], ), ), @@ -211,7 +215,7 @@ class MobileViewPage extends StatelessWidget { Widget _buildLastViewed(BuildContext context) { final textColor = Theme.of(context).isLightMode - ? const Color(0xFF171717) + ? const Color(0x7F171717) : Colors.white.withOpacity(0.45); if (timestamp == null) { return const SizedBox.shrink(); 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 48ef17e86c..296d305a81 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 @@ -50,23 +50,17 @@ class _MobileSpaceState extends State { MobileSpaceHeader( isExpanded: state.isExpanded, space: currentSpace, - onAdded: () { - context.read().add( - SpaceEvent.createPage( - name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), - layout: ViewLayoutPB.Document, - index: 0, - ), - ); - context.read().add( - SpaceEvent.expand(currentSpace, true), - ); - }, + onAdded: () => _showCreatePageMenu(currentSpace), onPressed: () => _showSpaceMenu(context), ), - _Pages( - key: ValueKey(currentSpace.id), - space: currentSpace, + Padding( + padding: const EdgeInsets.only( + left: HomeSpaceViewSizes.mHorizontalPadding, + ), + child: _Pages( + key: ValueKey(currentSpace.id), + space: currentSpace, + ), ), ], ); @@ -82,6 +76,7 @@ class _MobileSpaceState extends State { showDragHandle: true, showCloseButton: true, showDoneButton: true, + useRootNavigator: true, title: LocaleKeys.space_title.tr(), backgroundColor: Theme.of(context).colorScheme.surface, builder: (_) { @@ -104,6 +99,38 @@ class _MobileSpaceState extends State { ), ); } + + void _showCreatePageMenu(ViewPB space) { + final title = space.name; + showMobileBottomSheet( + context, + showHeader: true, + title: title, + showDragHandle: true, + showCloseButton: true, + useRootNavigator: true, + showDivider: false, + backgroundColor: Theme.of(context).colorScheme.surface, + builder: (sheetContext) { + return AddNewPageWidgetBottomSheet( + view: space, + onAction: (layout) { + Navigator.of(sheetContext).pop(); + context.read().add( + SpaceEvent.createPage( + name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + layout: layout, + index: 0, + ), + ); + context.read().add( + SpaceEvent.expand(space, true), + ); + }, + ); + }, + ); + } } class _Pages extends StatelessWidget { @@ -148,7 +175,7 @@ class _Pages extends StatelessWidget { MobilePaneActionType.add, ], spaceType: spaceType, - needSpace: false, + spaceRatio: 4, ); }, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_header.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_header.dart index ffc1691404..004b527b78 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_header.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_header.dart @@ -1,4 +1,5 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/workspace/presentation/home/home_sizes.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -30,6 +31,7 @@ class MobileSpaceHeader extends StatelessWidget { height: 48, child: Row( children: [ + const HSpace(HomeSpaceViewSizes.mHorizontalPadding), SpaceIcon( dimension: 24, space: space, @@ -49,8 +51,15 @@ class MobileSpaceHeader extends StatelessWidget { GestureDetector( behavior: HitTestBehavior.translucent, onTap: onAdded, - child: const FlowySvg( - FlowySvgs.m_space_add_s, + child: Container( + // expand the touch area + margin: const EdgeInsets.symmetric( + horizontal: HomeSpaceViewSizes.mHorizontalPadding, + vertical: 8.0, + ), + child: const FlowySvg( + FlowySvgs.m_space_add_s, + ), ), ), ], diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_menu.dart index 6788e1396d..e45270f634 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_menu.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_menu.dart @@ -59,6 +59,7 @@ class _SidebarSpaceMenuItem extends StatelessWidget { children: [ FlowyText.medium( space.name, + fontSize: 16.0, ), const HSpace(6.0), if (space.spacePermission == SpacePermission.private) @@ -68,16 +69,17 @@ class _SidebarSpaceMenuItem extends StatelessWidget { ), ], ), + margin: const EdgeInsets.symmetric(horizontal: 12.0), iconPadding: 10, leftIcon: SpaceIcon( dimension: 24, space: space, cornerRadius: 6.0, ), - leftIconSize: const Size.square(20), + leftIconSize: const Size.square(24), rightIcon: isSelected ? const FlowySvg( - FlowySvgs.workspace_selected_s, + FlowySvgs.m_blue_check_s, blendMode: null, ) : null, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart index 17962bcc3c..9c79f5b20b 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart @@ -138,17 +138,20 @@ class _WorkspaceMenuItem extends StatelessWidget { height: 60, showTopBorder: showTopBorder, showBottomBorder: false, - leftIcon: WorkspaceIcon( - enableEdit: false, - iconSize: 26, - fontSize: 16.0, - workspace: workspace, - onSelected: (result) => context.read().add( - UserWorkspaceEvent.updateWorkspaceIcon( - workspace.workspaceId, - result.emoji, + leftIcon: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: WorkspaceIcon( + enableEdit: false, + iconSize: 26, + fontSize: 16.0, + workspace: workspace, + onSelected: (result) => context.read().add( + UserWorkspaceEvent.updateWorkspaceIcon( + workspace.workspaceId, + result.emoji, + ), ), - ), + ), ), trailing: workspace.workspaceId == currentWorkspace.workspaceId ? const FlowySvg( 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 5d33dfa500..f2c22e00dc 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 @@ -50,6 +50,9 @@ class MobileBottomNavigationBar extends StatelessWidget { final backgroundColor = isLightMode ? Colors.white.withOpacity(0.95) : const Color(0xFF23262B).withOpacity(0.95); + final borderColor = isLightMode + ? const Color(0x141F2329) + : const Color(0xFF23262B).withOpacity(0.5); return Scaffold( body: navigationShell, extendBody: true, @@ -62,9 +65,7 @@ class MobileBottomNavigationBar extends StatelessWidget { child: DecoratedBox( decoration: BoxDecoration( border: isLightMode - ? Border( - top: BorderSide(color: Theme.of(context).dividerColor), - ) + ? Border(top: BorderSide(color: borderColor)) : null, color: backgroundColor, ), 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 6ef969bb0b..443e2cbbc3 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 @@ -300,7 +300,7 @@ class _SingleMobileInnerViewItemState extends State { ) : Opacity( opacity: 0.7, - child: widget.view.defaultIcon(), + child: widget.view.defaultIcon(size: const Size.square(18)), ); return SizedBox(width: 18.0, child: icon); } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart index 38fe4a8807..ff7cb6bafe 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart @@ -1,11 +1,14 @@ +import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_configuration.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart'; import 'package:appflowy/shared/markdown_to_document.dart'; import 'package:appflowy/util/theme_extension.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:markdown_widget/markdown_widget.dart'; import 'selectable_highlight.dart'; @@ -30,7 +33,11 @@ class AIMarkdownText extends StatelessWidget { Widget build(BuildContext context) { switch (type) { case AIMarkdownType.appflowyEditor: - return _AppFlowyEditorMarkdown(markdown: markdown); + return BlocProvider( + create: (context) => DocumentPageStyleBloc(view: ViewPB()) + ..add(const DocumentPageStyleEvent.initial()), + child: _AppFlowyEditorMarkdown(markdown: markdown), + ); case AIMarkdownType.markdownWidget: return _ThirdPartyMarkdown(markdown: markdown); } 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 43539cea96..d1b550eded 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 @@ -90,19 +90,22 @@ class _DocumentImmersiveCoverState extends State { ); } - return Stack( - children: [ - _buildCover(context, state), - Positioned( - left: 0, - right: 0, - bottom: 0, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 24.0), - child: iconAndTitle, + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Stack( + children: [ + _buildCover(context, state), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 24.0), + child: iconAndTitle, + ), ), - ), - ], + ], + ), ); }, ), diff --git a/frontend/appflowy_flutter/lib/shared/feature_flags.dart b/frontend/appflowy_flutter/lib/shared/feature_flags.dart index 9cb1db4f00..bc87836659 100644 --- a/frontend/appflowy_flutter/lib/shared/feature_flags.dart +++ b/frontend/appflowy_flutter/lib/shared/feature_flags.dart @@ -4,7 +4,6 @@ import 'package:appflowy/core/config/kv.dart'; import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; typedef FeatureFlagMap = Map; diff --git a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart index 31d610e278..e8b52ee4be 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart @@ -1,8 +1,5 @@ import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; import 'package:appflowy/shared/feature_flags.dart'; @@ -24,8 +21,11 @@ import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; +import 'package:toastification/toastification.dart'; import 'prelude.dart'; @@ -197,31 +197,33 @@ class _ApplicationWidgetState extends State { child: BlocBuilder( builder: (context, state) { _setSystemOverlayStyle(state); - return MaterialApp.router( - builder: (context, child) => MediaQuery( - // use the 1.0 as the textScaleFactor to avoid the text size - // affected by the system setting. - data: MediaQuery.of(context).copyWith( - textScaler: TextScaler.linear(state.textScaleFactor), - ), - child: overlayManagerBuilder( - context, - !PlatformExtension.isMobile && FeatureFlag.search.isOn - ? CommandPalette( - notifier: _commandPaletteNotifier, - child: child, - ) - : child, + return ToastificationWrapper( + child: MaterialApp.router( + builder: (context, child) => MediaQuery( + // use the 1.0 as the textScaleFactor to avoid the text size + // affected by the system setting. + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.linear(state.textScaleFactor), + ), + child: overlayManagerBuilder( + context, + !PlatformExtension.isMobile && FeatureFlag.search.isOn + ? CommandPalette( + notifier: _commandPaletteNotifier, + child: child, + ) + : child, + ), ), + debugShowCheckedModeBanner: false, + theme: state.lightTheme, + darkTheme: state.darkTheme, + themeMode: state.themeMode, + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: state.locale, + routerConfig: routerConfig, ), - debugShowCheckedModeBanner: false, - theme: state.lightTheme, - darkTheme: state.darkTheme, - themeMode: state.themeMode, - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: state.locale, - routerConfig: routerConfig, ); }, ), diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart index 3dc87b0d78..0d6c8f7826 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart @@ -48,7 +48,7 @@ class ViewExtKeys { } extension ViewExtension on ViewPB { - Widget defaultIcon() => FlowySvg( + Widget defaultIcon({Size? size}) => FlowySvg( switch (layout) { ViewLayoutPB.Board => FlowySvgs.icon_board_s, ViewLayoutPB.Calendar => FlowySvgs.icon_calendar_s, @@ -57,6 +57,7 @@ extension ViewExtension on ViewPB { ViewLayoutPB.Chat => FlowySvgs.chat_ai_page_s, _ => FlowySvgs.document_s, }, + size: size, ); PluginType get pluginType => switch (layout) { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart index 188303c3f7..8a99783c11 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart @@ -1,8 +1,8 @@ -import 'package:flutter/material.dart'; - +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/tasks/app_widget.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -11,6 +11,7 @@ import 'package:flowy_infra_ui/widget/buttons/primary_button.dart'; import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart'; import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/material.dart'; import 'package:toastification/toastification.dart'; export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; @@ -303,6 +304,18 @@ void showToastNotification( String? description, ToastificationType type = ToastificationType.success, }) { + if (PlatformExtension.isMobile) { + toastification.showCustom( + alignment: Alignment.bottomCenter, + autoCloseDuration: const Duration(milliseconds: 3000), + builder: (_, __) => _MToast( + message: message, + type: type, + ), + ); + return; + } + toastification.show( context: context, type: type, @@ -329,6 +342,50 @@ void showToastNotification( ); } +class _MToast extends StatelessWidget { + const _MToast({ + required this.message, + this.type = ToastificationType.success, + }); + + final String message; + final ToastificationType type; + + @override + Widget build(BuildContext context) { + // only support success type + assert(type == ToastificationType.success); + + return Container( + alignment: Alignment.bottomCenter, + padding: const EdgeInsets.only(bottom: 100), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 13.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.0), + color: const Color(0xE5171717), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const FlowySvg( + FlowySvgs.success_s, + blendMode: null, + ), + const HSpace(8.0), + FlowyText.regular( + message, + fontSize: 16.0, + figmaLineHeight: 18.0, + color: Colors.white, + ), + ], + ), + ), + ); + } +} + Future showConfirmDeletionDialog({ required BuildContext context, required String name, diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 5c91a45b47..12351507ad 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -276,7 +276,7 @@ "justNow": "just now", "minutesAgo": "{count} minutes ago", "lastViewed": "Last viewed", - "favoriteAt": "Favorited at", + "favoriteAt": "Favorited", "emptyRecent": "No Recent Documents", "emptyRecentDescription": "As you view documents, they will appear here for easy retrieval", "emptyFavorite": "No Favorite Documents", @@ -337,6 +337,8 @@ "removeFromFavorites": "Remove from favorites", "removeFromRecent": "Remove from recent", "addToFavorites": "Add to favorites", + "favoriteSuccessfully": "Favorited success", + "unfavoriteSuccessfully": "Unfavorited success", "rename": "Rename", "helpCenter": "Help Center", "add": "Add",