diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart index 2ee9e14175..73a5381d42 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart @@ -64,11 +64,7 @@ class MobileHomeTrashPage extends StatelessWidget { ], ), body: state.objects.isEmpty - ? FlowyMobileStateContainer.info( - emoji: '🗑️', - title: LocaleKeys.trash_mobile_empty.tr(), - description: LocaleKeys.trash_mobile_emptyDescription.tr(), - ) + ? const _EmptyTrashBin() : _DeletedFilesListView(state), ); }, @@ -82,6 +78,41 @@ enum _TrashActionType { deleteAll, } +class _EmptyTrashBin extends StatelessWidget { + const _EmptyTrashBin(); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const FlowySvg( + FlowySvgs.m_empty_trash_xl, + size: Size.square(46), + ), + const VSpace(16.0), + FlowyText.medium( + LocaleKeys.trash_mobile_empty.tr(), + fontSize: 18.0, + textAlign: TextAlign.center, + ), + const VSpace(8.0), + FlowyText.regular( + LocaleKeys.trash_mobile_emptyDescription.tr(), + fontSize: 17.0, + maxLines: 10, + textAlign: TextAlign.center, + lineHeight: 1.3, + color: Theme.of(context).hintColor, + ), + const VSpace(kBottomNavigationBarHeight + 36.0), + ], + ), + ); + } +} + class _TrashActionAllButton extends StatelessWidget { /// Switch between 'delete all' and 'restore all' feature const _TrashActionAllButton({ diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart index a13f7b3c75..ac7dfa17e6 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart @@ -22,6 +22,7 @@ class HomePageSettingsPopupMenu extends StatelessWidget { @override Widget build(BuildContext context) { + return PopupMenuButton<_MobileSettingsPopupMenuItem>( offset: const Offset(0, 36), padding: EdgeInsets.zero, @@ -32,13 +33,7 @@ class HomePageSettingsPopupMenu extends StatelessWidget { ), shadowColor: const Color(0x68000000), elevation: 10, - color: Theme.of(context).colorScheme.surface, - child: const Padding( - padding: EdgeInsets.all(8.0), - child: FlowySvg( - FlowySvgs.m_settings_more_s, - ), - ), + color: context.popupMenuBackgroundColor, itemBuilder: (BuildContext context) => >[ _buildItem( @@ -81,6 +76,12 @@ class HomePageSettingsPopupMenu extends StatelessWidget { break; } }, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: FlowySvg( + FlowySvgs.m_settings_more_s, + ), + ), ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/empty_placeholder.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/empty_placeholder.dart index 1b99be9a42..2de40600f2 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/empty_placeholder.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/empty_placeholder.dart @@ -6,7 +6,10 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; class EmptySpacePlaceholder extends StatelessWidget { - const EmptySpacePlaceholder({super.key, required this.type}); + const EmptySpacePlaceholder({ + super.key, + required this.type, + }); final MobilePageCardType type; 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 1e1cd88ad9..1de6c568d3 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 @@ -1,9 +1,11 @@ +import 'dart:io'; import 'dart:ui'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/notifications/mobile_notifications_screen.dart'; import 'package:appflowy/mobile/presentation/widgets/navigation_bar_button.dart'; +import 'package:appflowy/shared/popup_menu/appflowy_popup_menu.dart'; import 'package:appflowy/shared/red_dot.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; @@ -193,25 +195,44 @@ class _HomePageNavigationBar extends StatelessWidget { border: context.border, color: context.backgroundColor, ), - child: BottomNavigationBar( - showSelectedLabels: false, - showUnselectedLabels: false, - enableFeedback: false, - type: BottomNavigationBarType.fixed, - elevation: 0, - items: _items, - backgroundColor: Colors.transparent, - currentIndex: navigationShell.currentIndex, - onTap: (int bottomBarIndex) => _onTap(context, bottomBarIndex), + child: Theme( + data: _getThemeData(context), + child: BottomNavigationBar( + showSelectedLabels: false, + showUnselectedLabels: false, + enableFeedback: false, + type: BottomNavigationBarType.fixed, + elevation: 0, + items: _items, + backgroundColor: Colors.transparent, + currentIndex: navigationShell.currentIndex, + onTap: (int bottomBarIndex) => _onTap(context, bottomBarIndex), + ), ), ), ), ); } + ThemeData _getThemeData(BuildContext context) { + if (Platform.isAndroid) { + return Theme.of(context); + } + + // hide the splash effect for iOS + return Theme.of(context).copyWith( + splashFactory: NoSplash.splashFactory, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + ); + } + /// Navigate to the current location of the branch at the provided index when /// tapping an item in the BottomNavigationBar. void _onTap(BuildContext context, int bottomBarIndex) { + // close the popup menu + closePopupMenu(); + final label = _items[bottomBarIndex].label; if (label == _addLabel) { // show an add dialog diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart index b414158aa5..dfa277f2ef 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart @@ -36,12 +36,7 @@ class NotificationSettingsPopupMenu extends StatelessWidget { // todo: replace it with shadows shadowColor: const Color(0x68000000), elevation: 10, - child: const Padding( - padding: EdgeInsets.all(8.0), - child: FlowySvg( - FlowySvgs.m_settings_more_s, - ), - ), + color: context.popupMenuBackgroundColor, itemBuilder: (BuildContext context) => >[ _buildItem( @@ -87,6 +82,12 @@ class NotificationSettingsPopupMenu extends StatelessWidget { break; } }, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: FlowySvg( + FlowySvgs.m_settings_more_s, + ), + ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart index 894dde0442..6d7d0860ef 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart @@ -245,12 +245,12 @@ class _MobileToolbarState extends State<_MobileToolbar> children: [ const Divider( height: 0.5, - color: Color(0xFFEDEDED), + color: Color(0x7FEDEDED), ), _buildToolbar(context), const Divider( height: 0.5, - color: Color(0xFFEDEDED), + color: Color(0x7FEDEDED), ), _buildMenuOrSpacer(context), ], diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart index 0c3868a50a..09ea9faa85 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart @@ -264,7 +264,13 @@ final referencedDocSlashMenuItem = SelectionMenuItem( isSelected: isSelected, style: style, ), - keywords: ['page', 'notes', 'referenced page', 'referenced document'], + keywords: [ + 'page', + 'notes', + 'referenced page', + 'referenced document', + 'link to page', + ], handler: (editorState, menuService, context) => showLinkToPageMenu( editorState, menuService, diff --git a/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart b/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart index e6ace027fa..0c3d759744 100644 --- a/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart +++ b/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart @@ -33,6 +33,12 @@ const double _kMenuVerticalPadding = 8.0; const double _kMenuWidthStep = 56.0; const double _kMenuScreenPadding = 8.0; +GlobalKey<_PopupMenuState>? _kPopupMenuKey; +void closePopupMenu() { + _kPopupMenuKey?.currentState?.dismiss(); + _kPopupMenuKey = null; +} + /// A base class for entries in a Material Design popup menu. /// /// The popup menu widget uses this interface to interact with the menu items. @@ -569,7 +575,7 @@ class _CheckedPopupMenuItemState } } -class _PopupMenu extends StatelessWidget { +class _PopupMenu extends StatefulWidget { const _PopupMenu({ super.key, required this.itemKeys, @@ -585,10 +591,15 @@ class _PopupMenu extends StatelessWidget { final BoxConstraints? constraints; final Clip clipBehavior; + @override + State<_PopupMenu> createState() => _PopupMenuState(); +} + +class _PopupMenuState extends State<_PopupMenu> { @override Widget build(BuildContext context) { final double unit = 1.0 / - (route.items.length + + (widget.route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade. final List children = []; final ThemeData theme = Theme.of(context); @@ -597,16 +608,16 @@ class _PopupMenu extends StatelessWidget { ? _PopupMenuDefaultsM3(context) : _PopupMenuDefaultsM2(context); - for (int i = 0; i < route.items.length; i += 1) { + for (int i = 0; i < widget.route.items.length; i += 1) { final double start = (i + 1) * unit; final double end = clampDouble(start + 1.5 * unit, 0.0, 1.0); final CurvedAnimation opacity = CurvedAnimation( - parent: route.animation!, + parent: widget.route.animation!, curve: Interval(start, end), ); - Widget item = route.items[i]; - if (route.initialValue != null && - route.items[i].represents(route.initialValue)) { + Widget item = widget.route.items[i]; + if (widget.route.initialValue != null && + widget.route.items[i].represents(widget.route.initialValue)) { item = ColoredBox( color: Theme.of(context).highlightColor, child: item, @@ -615,10 +626,10 @@ class _PopupMenu extends StatelessWidget { children.add( _MenuItem( onLayout: (Size size) { - route.itemSizes[i] = size; + widget.route.itemSizes[i] = size; }, child: FadeTransition( - key: itemKeys[i], + key: widget.itemKeys[i], opacity: opacity, child: item, ), @@ -630,10 +641,10 @@ class _PopupMenu extends StatelessWidget { CurveTween(curve: const Interval(0.0, 1.0 / 3.0)); final CurveTween width = CurveTween(curve: Interval(0.0, unit)); final CurveTween height = - CurveTween(curve: Interval(0.0, unit * route.items.length)); + CurveTween(curve: Interval(0.0, unit * widget.route.items.length)); final Widget child = ConstrainedBox( - constraints: constraints ?? + constraints: widget.constraints ?? const BoxConstraints( minWidth: _kMenuMinWidth, maxWidth: _kMenuMaxWidth, @@ -644,7 +655,7 @@ class _PopupMenu extends StatelessWidget { scopesRoute: true, namesRoute: true, explicitChildNodes: true, - label: semanticLabel, + label: widget.semanticLabel, child: SingleChildScrollView( padding: const EdgeInsets.symmetric( vertical: _kMenuVerticalPadding, @@ -656,28 +667,28 @@ class _PopupMenu extends StatelessWidget { ); return AnimatedBuilder( - animation: route.animation!, + animation: widget.route.animation!, builder: (BuildContext context, Widget? child) { return FadeTransition( - opacity: opacity.animate(route.animation!), + opacity: opacity.animate(widget.route.animation!), child: Material( - shape: route.shape ?? popupMenuTheme.shape ?? defaults.shape, - color: route.color ?? popupMenuTheme.color ?? defaults.color, - clipBehavior: clipBehavior, + shape: widget.route.shape ?? popupMenuTheme.shape ?? defaults.shape, + color: widget.route.color ?? popupMenuTheme.color ?? defaults.color, + clipBehavior: widget.clipBehavior, type: MaterialType.card, - elevation: route.elevation ?? + elevation: widget.route.elevation ?? popupMenuTheme.elevation ?? defaults.elevation!, - shadowColor: route.shadowColor ?? + shadowColor: widget.route.shadowColor ?? popupMenuTheme.shadowColor ?? defaults.shadowColor, - surfaceTintColor: route.surfaceTintColor ?? + surfaceTintColor: widget.route.surfaceTintColor ?? popupMenuTheme.surfaceTintColor ?? defaults.surfaceTintColor, child: Align( alignment: AlignmentDirectional.topEnd, - widthFactor: width.evaluate(route.animation!), - heightFactor: height.evaluate(route.animation!), + widthFactor: width.evaluate(widget.route.animation!), + heightFactor: height.evaluate(widget.route.animation!), child: child, ), ), @@ -686,6 +697,21 @@ class _PopupMenu extends StatelessWidget { child: child, ); } + + @override + void dispose() { + _kPopupMenuKey = null; + super.dispose(); + } + + void dismiss() { + if (_kPopupMenuKey == null) { + return; + } + + Navigator.of(context).pop(); + _kPopupMenuKey = null; + } } // Positioning of the menu on the screen. @@ -937,7 +963,9 @@ class _PopupMenuRoute extends PopupRoute { scrollTo(selectedItemIndex); } + _kPopupMenuKey ??= GlobalKey<_PopupMenuState>(); final Widget menu = _PopupMenu( + key: _kPopupMenuKey, route: this, itemKeys: itemKeys, semanticLabel: semanticLabel, @@ -1526,7 +1554,7 @@ class PopupMenuButtonState extends State> { if (widget.child != null) { return AnimatedGestureDetector( - scaleFactor: 0.99, + scaleFactor: 0.95, onTapUp: widget.enabled ? showButtonMenu : null, child: widget.child!, ); @@ -1607,3 +1635,12 @@ class _PopupMenuDefaultsM3 extends PopupMenuThemeData { const EdgeInsets.symmetric(horizontal: 12.0); } // END GENERATED TOKEN PROPERTIES - PopupMenu + +extension PopupMenuColors on BuildContext { + Color get popupMenuBackgroundColor { + if (Theme.of(this).brightness == Brightness.light) { + return Theme.of(this).colorScheme.surface; + } + return const Color(0xFF23262B); + } +} diff --git a/frontend/resources/flowy_icons/40x/m_empty_trash.svg b/frontend/resources/flowy_icons/40x/m_empty_trash.svg new file mode 100644 index 0000000000..1cb0d4ba15 --- /dev/null +++ b/frontend/resources/flowy_icons/40x/m_empty_trash.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 5129ba26c1..e24aaa0925 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -49,7 +49,7 @@ "syncPromptMessage": "Syncing the data might take a while. Please don't close this page", "or": "OR", "signInWithGoogle": "Continue with Google", - "signInWithGithub": "Continue with Github", + "signInWithGithub": "Continue with GitHub", "signInWithDiscord": "Continue with Discord", "signInWithApple": "Continue with Apple", "continueAnotherWay": "Continue another way", @@ -144,8 +144,8 @@ "rename": "Rename", "delete": "Delete", "duplicate": "Duplicate", - "unfavorite": "Remove from favorites", - "favorite": "Add to favorites", + "unfavorite": "Remove from Favorites", + "favorite": "Add to Favorites", "openNewTab": "Open in a new tab", "moveTo": "Move to", "addToFavorites": "Add to Favorites", @@ -204,8 +204,8 @@ }, "mobile": { "actions": "Trash Actions", - "empty": "Trash Bin is Empty", - "emptyDescription": "You don't have any deleted file", + "empty": "No pages or spaces in Trash", + "emptyDescription": "Move things you don't need to the Trash.", "isDeleted": "is deleted", "isRestored": "is restored" }, @@ -347,9 +347,9 @@ "putback": "Put Back", "update": "Update", "share": "Share", - "removeFromFavorites": "Remove from favorites", - "removeFromRecent": "Remove from recent", - "addToFavorites": "Add to favorites", + "removeFromFavorites": "Remove from Favorites", + "removeFromRecent": "Remove from Recent", + "addToFavorites": "Add to Favorites", "favoriteSuccessfully": "Favorited success", "unfavoriteSuccessfully": "Unfavorited success", "duplicateSuccessfully": "Duplicated successfully", @@ -368,7 +368,7 @@ "deleteAccount": "Delete account", "back": "Back", "signInGoogle": "Continue with Google", - "signInGithub": "Continue with Github", + "signInGithub": "Continue with GitHub", "signInDiscord": "Continue with Discord", "more": "More", "create": "Create", @@ -997,16 +997,16 @@ "archiveAll": "Archive all" }, "emptyInbox": { - "title": "No notifications yet", - "description": "You'll be notified here for @mentions" + "title": "Inbox Zero!", + "description": "Set reminders to receive notifications here." }, "emptyUnread": { "title": "No unread notifications", "description": "You're all caught up!" }, "emptyArchived": { - "title": "No archived notifications", - "description": "You haven't archived any notifications yet" + "title": "No archived", + "description": "Archived notifications will appear here." }, "tabs": { "inbox": "Inbox", @@ -2505,4 +2505,4 @@ "uploadFailedDescription": "The file upload failed", "uploadingDescription": "The file is being uploaded" } -} \ No newline at end of file +}