From 73ea1a0685b033427494403c21268cf8dff3549c Mon Sep 17 00:00:00 2001 From: Yijing Huang Date: Thu, 2 Nov 2023 11:15:15 -0700 Subject: [PATCH] feat:add toast in trash and confirm dialog + fix issues from launch review (#3787) * chore: improve trash button * feat: improve restore all&delete all * refactor: add showFlowyMobileConfirmDialog * feat: add toast in delete/restore single file * refactor: refactor to TrashActionType enum * fix: text invisible in signin page in dark mode * feat: add FlowyMobileErrorStateContainer to display error state * refactor: add FlowyMobileStateContainer to handle empty or error state - Replace MobileErrorPage by FlowyMobileStateContainer.error - Implement app version in reporting issue on github - Implement FlowyMobileStateContainer in trash,setting,favorite and mobile view page * refactor: unify bottom sheet style - Unify bottom sheet style in add new page, page action, and trash action - Add icon color in BottomSheetActionWidget for future use - Add theme color in MobileBottomSheetDragHandle * chore: unify Appbar style * chore: remove the more button when trash list is empty * fix: show bottom sheet error * fix: fix merge and ui issue * refactor: refactor ViewPageBottomSheet and origanize code * chore: add icon color for favorite button * fix: add missing icon color and delete comments --------- Co-authored-by: Lucas.Xu --- .../mobile/application/mobile_theme_data.dart | 5 +- .../presentation/base/mobile_view_page.dart | 17 +- .../bottom_sheet/bottom_sheet.dart | 155 +-------- .../bottom_sheet_action_widget.dart | 46 +-- .../bottom_sheet_add_new_page.dart | 47 +-- .../bottom_sheet_drag_handler.dart | 2 +- .../bottom_sheet_view_item_body.dart | 17 +- .../bottom_sheet_view_item_header.dart | 25 +- .../bottom_sheet/bottom_sheet_view_page.dart | 51 +-- .../default_mobile_action_pane.dart | 2 +- ...tem.dart => show_mobile_bottom_sheet.dart} | 52 +-- .../mobile/presentation/error/error_page.dart | 49 --- .../favorite/mobile_favorite_folder.dart | 34 +- .../favorite/mobile_favorite_page.dart | 9 +- .../presentation/home/mobile_home_page.dart | 10 +- .../home/mobile_home_page_recent_files.dart | 19 +- .../home/mobile_home_setting_page.dart | 50 ++- .../home/mobile_home_trash_page.dart | 309 ++++++++++-------- .../page_item/mobile_view_item.dart | 34 +- .../personal_info_setting_group.dart | 6 +- .../setting/support_setting_group.dart | 50 +-- .../widgets/flowy_mobile_state_container.dart | 102 ++++++ .../show_flowy_mobile_confirm_dialog.dart | 62 ++++ .../mobile/presentation/widgets/widgets.dart | 3 + frontend/resources/translations/en.json | 8 +- 25 files changed, 592 insertions(+), 572 deletions(-) rename frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/{bottom_sheet_view_item.dart => show_mobile_bottom_sheet.dart} (84%) delete mode 100644 frontend/appflowy_flutter/lib/mobile/presentation/error/error_page.dart create mode 100644 frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_state_container.dart create mode 100644 frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart create mode 100644 frontend/appflowy_flutter/lib/mobile/presentation/widgets/widgets.dart diff --git a/frontend/appflowy_flutter/lib/mobile/application/mobile_theme_data.dart b/frontend/appflowy_flutter/lib/mobile/application/mobile_theme_data.dart index 55e827f103168..0b4557f9feaef 100644 --- a/frontend/appflowy_flutter/lib/mobile/application/mobile_theme_data.dart +++ b/frontend/appflowy_flutter/lib/mobile/application/mobile_theme_data.dart @@ -64,7 +64,6 @@ ThemeData getMobileThemeData( appBarTheme: AppBarTheme( foregroundColor: mobileColorTheme.onBackground, backgroundColor: mobileColorTheme.background, - elevation: 80, centerTitle: false, titleTextStyle: TextStyle( color: mobileColorTheme.onBackground, @@ -116,7 +115,7 @@ ThemeData getMobileThemeData( foregroundColor: MaterialStateProperty.all( mobileColorTheme.onBackground, ), - backgroundColor: MaterialStateProperty.all(Colors.white), + backgroundColor: MaterialStateProperty.all(mobileColorTheme.background), shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), @@ -129,7 +128,7 @@ ThemeData getMobileThemeData( ), ), padding: MaterialStateProperty.all( - const EdgeInsets.symmetric(horizontal: 16), + const EdgeInsets.symmetric(horizontal: 8, vertical: 12), ), // splash color overlayColor: MaterialStateProperty.all( diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart index ec85278ea3b84..bf798a11056a1 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart @@ -1,8 +1,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart'; import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart'; -import 'package:appflowy/mobile/presentation/error/error_page.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; @@ -56,8 +55,11 @@ class _MobileViewPageState extends State { child: CircularProgressIndicator(), ); } else if (!state.hasData) { - body = MobileErrorPage( - message: LocaleKeys.error_loadingViewError.tr(), + body = FlowyMobileStateContainer.error( + emoji: '😔', + title: LocaleKeys.error_weAreSorry.tr(), + description: LocaleKeys.error_loadingViewError.tr(), + errorMsg: state.error.toString(), ); } else { body = state.data!.fold((view) { @@ -65,8 +67,11 @@ class _MobileViewPageState extends State { actions.add(_buildAppBarMoreButton(view)); return view.plugin().widgetBuilder.buildWidget(shrinkWrap: false); }, (error) { - return MobileErrorPage( - message: error.toString(), + return FlowyMobileStateContainer.error( + emoji: '😔', + title: LocaleKeys.error_weAreSorry.tr(), + description: LocaleKeys.error_loadingViewError.tr(), + errorMsg: error.toString(), ); }); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet.dart index c1f43af889a04..a128f5434778d 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet.dart @@ -1,146 +1,9 @@ -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_drag_handler.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_view_item_header.dart'; -import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; -import 'package:appflowy/workspace/application/view/view_bloc.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; - -Future showMobileBottomSheet({ - required BuildContext context, - required WidgetBuilder builder, -}) async { - showModalBottomSheet( - context: context, - isScrollControlled: true, - enableDrag: true, - useSafeArea: true, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(8.0), - topRight: Radius.circular(8.0), - ), - ), - builder: builder, - ); -} - -enum MobileBottomSheetType { - view, - rename, -} - -class MobileViewItemBottomSheet extends StatefulWidget { - const MobileViewItemBottomSheet({ - super.key, - required this.view, - this.defaultType = MobileBottomSheetType.view, - }); - - final ViewPB view; - final MobileBottomSheetType defaultType; - - @override - State createState() => - _MobileViewItemBottomSheetState(); -} - -class _MobileViewItemBottomSheetState extends State { - MobileBottomSheetType type = MobileBottomSheetType.view; - - @override - initState() { - super.initState(); - - type = widget.defaultType; - } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - // drag handler - const MobileBottomSheetDragHandler(), - - // header - _buildHeader(), - const VSpace(8.0), - const Divider(), - - // body - _buildBody(), - const VSpace(12.0), - ], - ); - } - - Widget _buildHeader() { - switch (type) { - case MobileBottomSheetType.view: - case MobileBottomSheetType.rename: - // header - return MobileViewItemBottomSheetHeader( - showBackButton: type != MobileBottomSheetType.view, - view: widget.view, - onBack: () { - setState(() { - type = MobileBottomSheetType.view; - }); - }, - ); - } - } - - Widget _buildBody() { - switch (type) { - case MobileBottomSheetType.view: - return MobileViewItemBottomSheetBody( - isFavorite: widget.view.isFavorite, - onAction: (action) { - switch (action) { - case MobileViewItemBottomSheetBodyAction.rename: - setState(() { - type = MobileBottomSheetType.rename; - }); - break; - case MobileViewItemBottomSheetBodyAction.duplicate: - context.pop(); - context.read().add(const ViewEvent.duplicate()); - break; - case MobileViewItemBottomSheetBodyAction.share: - // unimplemented - context.pop(); - break; - case MobileViewItemBottomSheetBodyAction.delete: - context.pop(); - context.read().add(const ViewEvent.delete()); - - break; - case MobileViewItemBottomSheetBodyAction.addToFavorites: - case MobileViewItemBottomSheetBodyAction.removeFromFavorites: - context.pop(); - context - .read() - .add(FavoriteEvent.toggle(widget.view)); - break; - } - }, - ); - case MobileBottomSheetType.rename: - return MobileBottomSheetRenameWidget( - name: widget.view.name, - onRename: (name) { - if (name != widget.view.name) { - context.read().add(ViewEvent.rename(name)); - } - context.pop(); - }, - ); - } - } -} +export 'bottom_sheet_action_widget.dart'; +export 'bottom_sheet_add_new_page.dart'; +export 'bottom_sheet_drag_handler.dart'; +export 'bottom_sheet_rename_widget.dart'; +export 'bottom_sheet_view_item_body.dart'; +export 'bottom_sheet_view_item_header.dart'; +export 'bottom_sheet_view_page.dart'; +export 'default_mobile_action_pane.dart'; +export 'show_mobile_bottom_sheet.dart'; diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart index 34c243d92ef01..b4aa9deee1015 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart @@ -1,8 +1,5 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/mobile/presentation/base/box_container.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; class BottomSheetActionWidget extends StatelessWidget { const BottomSheetActionWidget({ @@ -10,42 +7,31 @@ class BottomSheetActionWidget extends StatelessWidget { required this.svg, required this.text, required this.onTap, + this.iconColor, }); final FlowySvgData svg; final String text; final VoidCallback onTap; + final Color? iconColor; @override Widget build(BuildContext context) { - return FlowyBoxContainer( - child: InkWell( - onTap: () { - HapticFeedback.mediumImpact(); - onTap(); - }, - enableFeedback: true, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 10.0, - horizontal: 12.0, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FlowySvg( - svg, - size: const Size.square(24.0), - blendMode: BlendMode.dst, - ), - const HSpace(6.0), - FlowyText(text), - const Spacer(), - ], - ), - ), + final iconColor = + this.iconColor ?? Theme.of(context).colorScheme.onBackground; + + return OutlinedButton.icon( + icon: FlowySvg( + svg, + size: const Size.square(22.0), + color: iconColor, ), + label: Text(text), + style: Theme.of(context) + .outlinedButtonTheme + .style + ?.copyWith(alignment: Alignment.centerLeft), + onPressed: onTap, ); } } 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 4fec21177ed02..a89cf44f7b080 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 @@ -1,8 +1,6 @@ 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_action_widget.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_drag_handler.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_view_item_header.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -21,46 +19,9 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget { @override Widget build(BuildContext context) { return Column( - mainAxisSize: MainAxisSize.min, children: [ - // drag handler - const MobileBottomSheetDragHandler(), - - // header - MobileViewItemBottomSheetHeader( - showBackButton: false, - view: view, - onBack: () {}, - ), - const VSpace(8.0), - const Divider(), - - // body - _AddNewPageBody( - onAction: onAction, - ), - const VSpace(24.0), - ], - ); - } -} - -class _AddNewPageBody extends StatelessWidget { - const _AddNewPageBody({ - required this.onAction, - }); - - final void Function(ViewLayoutPB layout) onAction; - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - // rename, duplicate + // new document, new grid Row( - mainAxisSize: MainAxisSize.min, children: [ Expanded( child: BottomSheetActionWidget( @@ -69,6 +30,7 @@ class _AddNewPageBody extends StatelessWidget { onTap: () => onAction(ViewLayoutPB.Document), ), ), + const HSpace(8), Expanded( child: BottomSheetActionWidget( svg: FlowySvgs.grid_s, @@ -78,10 +40,10 @@ class _AddNewPageBody extends StatelessWidget { ), ], ), + const VSpace(8), - // share, delete + // new board, new calendar Row( - mainAxisSize: MainAxisSize.min, children: [ Expanded( child: BottomSheetActionWidget( @@ -90,6 +52,7 @@ class _AddNewPageBody extends StatelessWidget { onTap: () => onAction(ViewLayoutPB.Board), ), ), + const HSpace(8), Expanded( child: BottomSheetActionWidget( svg: FlowySvgs.date_s, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_drag_handler.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_drag_handler.dart index 9a8d6d2253a5c..4e9fcd3d7e3ee 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_drag_handler.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_drag_handler.dart @@ -12,7 +12,7 @@ class MobileBottomSheetDragHandler extends StatelessWidget { height: 4, decoration: BoxDecoration( borderRadius: BorderRadius.circular(2.0), - color: Theme.of(context).colorScheme.onSecondary, + color: Theme.of(context).hintColor, ), ), ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart index 6c4cf8924dccd..d4278317ba40b 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart @@ -2,6 +2,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_action_widget.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; enum MobileViewItemBottomSheetBodyAction { @@ -26,12 +27,11 @@ class MobileViewItemBottomSheetBody extends StatelessWidget { @override Widget build(BuildContext context) { return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // rename, duplicate Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: MainAxisSize.max, children: [ Expanded( child: BottomSheetActionWidget( @@ -42,6 +42,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget { ), ), ), + const HSpace(8), Expanded( child: BottomSheetActionWidget( svg: FlowySvgs.m_duplicate_m, @@ -53,10 +54,11 @@ class MobileViewItemBottomSheetBody extends StatelessWidget { ), ], ), + const VSpace(8), // share, delete Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: MainAxisSize.max, children: [ Expanded( child: BottomSheetActionWidget( @@ -67,6 +69,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget { ), ), ), + const HSpace(8), Expanded( child: BottomSheetActionWidget( svg: FlowySvgs.m_delete_m, @@ -78,13 +81,15 @@ class MobileViewItemBottomSheetBody extends StatelessWidget { ), ], ), + const VSpace(8), - // remove from favorites - + // remove from favorites/add to favorites BottomSheetActionWidget( svg: isFavorite ? FlowySvgs.m_favorite_selected_lg : FlowySvgs.m_favorite_unselected_lg, + //TODO(yijing): switch to theme color + iconColor: isFavorite ? Colors.yellow : null, text: isFavorite ? LocaleKeys.button_removeFromFavorites.tr() : LocaleKeys.button_addToFavorites.tr(), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_header.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_header.dart index 1f186af7ae39e..9474653789c40 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_header.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_header.dart @@ -1,6 +1,6 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; class MobileViewItemBottomSheetHeader extends StatelessWidget { const MobileViewItemBottomSheetHeader({ @@ -16,8 +16,8 @@ class MobileViewItemBottomSheetHeader extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // back button, showBackButton @@ -31,14 +31,23 @@ class MobileViewItemBottomSheetHeader extends StatelessWidget { ), ), ) - : const HSpace(40.0), + : const SizedBox.shrink(), // title - FlowyText.regular( - view.name, - fontSize: 16.0, + Expanded( + child: Text( + view.name, + style: theme.textTheme.labelSmall, + ), ), - // placeholder, ensure the title is centered - const HSpace(40.0), + IconButton( + icon: Icon( + Icons.close, + color: theme.hintColor, + ), + onPressed: () { + context.pop(); + }, + ) ], ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart index 69cbc1bc2c72c..4317f3afa2c27 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart @@ -1,10 +1,6 @@ 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_action_widget.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_drag_handler.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_view_item_header.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -47,21 +43,18 @@ class _ViewPageBottomSheetState extends State { @override Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - // drag handler - const MobileBottomSheetDragHandler(), - - // header - _buildHeader(), - const VSpace(8.0), - const Divider(), - - // body - _buildBody(), - const VSpace(24.0), - ], + return Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // header + _buildHeader(), + const VSpace(16), + // body + _buildBody(), + ], + ), ); } @@ -125,12 +118,11 @@ class MobileViewBottomSheetBody extends StatelessWidget { Widget build(BuildContext context) { final isFavorite = view.isFavorite; return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // undo, redo Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: MainAxisSize.max, children: [ Expanded( child: BottomSheetActionWidget( @@ -141,6 +133,7 @@ class MobileViewBottomSheetBody extends StatelessWidget { ), ), ), + const HSpace(8), Expanded( child: BottomSheetActionWidget( svg: FlowySvgs.m_redo_m, @@ -152,10 +145,11 @@ class MobileViewBottomSheetBody extends StatelessWidget { ), ], ), + const VSpace(8), // rename, duplicate Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: MainAxisSize.max, children: [ Expanded( child: BottomSheetActionWidget( @@ -166,6 +160,7 @@ class MobileViewBottomSheetBody extends StatelessWidget { ), ), ), + const HSpace(8), Expanded( child: BottomSheetActionWidget( svg: FlowySvgs.m_duplicate_m, @@ -177,10 +172,11 @@ class MobileViewBottomSheetBody extends StatelessWidget { ), ], ), + const VSpace(8), // share, delete Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: MainAxisSize.max, children: [ Expanded( child: BottomSheetActionWidget( @@ -191,6 +187,7 @@ class MobileViewBottomSheetBody extends StatelessWidget { ), ), ), + const HSpace(8), Expanded( child: BottomSheetActionWidget( svg: FlowySvgs.m_delete_m, @@ -202,12 +199,15 @@ class MobileViewBottomSheetBody extends StatelessWidget { ), ], ), + const VSpace(8), // favorites BottomSheetActionWidget( svg: isFavorite ? FlowySvgs.m_favorite_selected_lg : FlowySvgs.m_favorite_unselected_lg, + //TODO(yijing): switch to theme color + iconColor: isFavorite ? Colors.yellow : null, text: isFavorite ? LocaleKeys.button_removeFromFavorites.tr() : LocaleKeys.button_addToFavorites.tr(), @@ -217,6 +217,7 @@ class MobileViewBottomSheetBody extends StatelessWidget { : MobileViewBottomSheetBodyAction.addToFavorites, ), ), + const VSpace(8), // help center BottomSheetActionWidget( 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 34ec310da50f5..fd41ca52bd073 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 @@ -1,5 +1,5 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/page_item/mobile_slide_action_button.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; 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/show_mobile_bottom_sheet.dart similarity index 84% rename from frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart rename to frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart index d6d252fe7c0fc..c8116ff380e95 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart @@ -1,7 +1,4 @@ -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_drag_handler.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_rename_widget.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_view_item_header.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart'; @@ -10,6 +7,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; +Future showMobileBottomSheet({ + required BuildContext context, + required WidgetBuilder builder, +}) async { + showModalBottomSheet( + context: context, + isScrollControlled: true, + enableDrag: true, + useSafeArea: true, + builder: builder, + ); +} + enum MobileBottomSheetType { view, rename, @@ -42,21 +52,18 @@ class _MobileViewItemBottomSheetState extends State { @override Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - // drag handler - const MobileBottomSheetDragHandler(), - - // header - _buildHeader(), - const VSpace(8.0), - const Divider(), - - // body - _buildBody(), - const VSpace(24.0), - ], + return Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // header + _buildHeader(), + const VSpace(16), + // body + _buildBody(), + ], + ), ); } @@ -90,23 +97,24 @@ class _MobileViewItemBottomSheetState extends State { }); break; case MobileViewItemBottomSheetBodyAction.duplicate: - context.read().add(const ViewEvent.duplicate()); context.pop(); + context.read().add(const ViewEvent.duplicate()); break; case MobileViewItemBottomSheetBodyAction.share: // unimplemented context.pop(); break; case MobileViewItemBottomSheetBodyAction.delete: - context.read().add(const ViewEvent.delete()); context.pop(); + context.read().add(const ViewEvent.delete()); + break; case MobileViewItemBottomSheetBodyAction.addToFavorites: case MobileViewItemBottomSheetBodyAction.removeFromFavorites: + context.pop(); context .read() .add(FavoriteEvent.toggle(widget.view)); - context.pop(); break; } }, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/error/error_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/error/error_page.dart deleted file mode 100644 index 09ef2cf627099..0000000000000 --- a/frontend/appflowy_flutter/lib/mobile/presentation/error/error_page.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; - -class MobileErrorPage extends StatelessWidget { - const MobileErrorPage({ - super.key, - this.header, - this.title, - required this.message, - }); - - final Widget? header; - final String? title; - final String message; - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - header != null - ? header! - : const FlowyText.semibold( - '😔', - fontSize: 50, - ), - const VSpace(14.0), - FlowyText.semibold( - title ?? LocaleKeys.error_weAreSorry.tr(), - fontSize: 32, - textAlign: TextAlign.center, - ), - const VSpace(4.0), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: FlowyText.regular( - message, - fontSize: 16, - maxLines: 100, - color: Colors.grey, // FIXME: use theme color - textAlign: TextAlign.center, - ), - ), - ], - ); - } -} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_folder.dart b/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_folder.dart index 0f7499b94be35..933292f38f705 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_folder.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_folder.dart @@ -1,7 +1,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/mobile_router.dart'; -import 'package:appflowy/mobile/presentation/error/error_page.dart'; import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart'; +import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/menu/menu_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart'; @@ -49,27 +49,27 @@ class MobileFavoritePageFolder extends StatelessWidget { builder: (context) { final favoriteState = context.watch().state; if (favoriteState.views.isEmpty) { - return MobileErrorPage( - header: const FlowyText.semibold( - '😁', - fontSize: 50, - ), + return FlowyMobileStateContainer.info( + emoji: '😁', title: LocaleKeys.favorite_noFavorite.tr(), - message: LocaleKeys.favorite_noFavoriteHintText.tr(), + description: LocaleKeys.favorite_noFavoriteHintText.tr(), ); } return Scrollbar( child: SingleChildScrollView( - child: SlidableAutoCloseBehavior( - child: Column( - children: [ - MobileFavoriteFolder( - showHeader: false, - forceExpanded: true, - views: favoriteState.views, - ), - const VSpace(100.0), - ], + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SlidableAutoCloseBehavior( + child: Column( + children: [ + MobileFavoriteFolder( + showHeader: false, + forceExpanded: true, + views: favoriteState.views, + ), + const VSpace(100.0), + ], + ), ), ), ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart index f0b45373bc036..5541588d1008f 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart @@ -82,12 +82,9 @@ class MobileFavoritePage extends StatelessWidget { // Folder Expanded( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: MobileFavoritePageFolder( - userProfile: userProfile, - workspaceSetting: workspaceSetting, - ), + child: MobileFavoritePageFolder( + userProfile: userProfile, + workspaceSetting: workspaceSetting, ), ), ], 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 8ed1a81f49fb2..87da5a6b3c90f 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 @@ -110,7 +110,10 @@ class MobileHomePage extends StatelessWidget { ), ), const SizedBox(height: 8), - const _TrashButton(), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: _TrashButton(), + ), ], ), ), @@ -142,7 +145,10 @@ class _TrashButton extends StatelessWidget { LocaleKeys.trash_text.tr(), style: Theme.of(context).textTheme.labelMedium, ), - style: const ButtonStyle(alignment: Alignment.centerLeft), + style: const ButtonStyle( + alignment: Alignment.centerLeft, + splashFactory: NoSplash.splashFactory, + ), ), ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_recent_files.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_recent_files.dart index 9fa880553d989..010e8d30a96a6 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_recent_files.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_recent_files.dart @@ -28,6 +28,7 @@ class MobileHomePageRecentFilesWidget extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); // TODO: implement the details later. return SizedBox( height: 168, @@ -54,13 +55,10 @@ class MobileHomePageRecentFilesWidget extends StatelessWidget { return Container( width: 120, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, + color: theme.colorScheme.background, borderRadius: BorderRadius.circular(8), border: Border.all( - color: Theme.of(context) - .colorScheme - .outline - .withOpacity(0.5), + color: theme.colorScheme.outline.withOpacity(0.5), ), ), child: Stack( @@ -104,14 +102,9 @@ class MobileHomePageRecentFilesWidget extends StatelessWidget { child: Text( recentFilesList[index].title, softWrap: true, - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith( - color: Theme.of(context) - .colorScheme - .onBackground, - ), + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onBackground, + ), maxLines: 2, ), ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart index 9f7289188547b..0623b77430c33 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/presentation.dart'; - +import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -23,33 +23,47 @@ class _MobileHomeSettingPageState extends State { return FutureBuilder( future: getIt().getUser(), builder: ((context, snapshot) { + String? errorMsg; if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator.adaptive()); } - final userProfile = snapshot.data?.fold((error) => null, (userProfile) { + final userProfile = snapshot.data?.fold((error) { + errorMsg = error.msg; + return null; + }, (userProfile) { return userProfile; }); + return Scaffold( appBar: AppBar( title: Text(LocaleKeys.settings_title.tr()), ), - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - PersonalInfoSettingGroup( - userProfile: userProfile, + body: userProfile == null + ? FlowyMobileStateContainer.error( + emoji: '🛸', + title: LocaleKeys.settings_mobile_userprofileError.tr(), + description: LocaleKeys + .settings_mobile_userprofileErrorDescription + .tr(), + errorMsg: errorMsg, + ) + : SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + PersonalInfoSettingGroup( + userProfile: userProfile, + ), + // TODO(yijing): implement this along with Notification Page + const NotificationsSettingGroup(), + const AppearanceSettingGroup(), + const SupportSettingGroup(), + const AboutSettingGroup(), + ], + ), ), - // TODO(yijing): implement this along with Notification Page - const NotificationsSettingGroup(), - const AppearanceSettingGroup(), - const SupportSettingGroup(), - const AboutSettingGroup(), - ], - ), - ), - ), + ), ); }), ); 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 989107e2ffea2..945bdfeee86d8 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 @@ -1,12 +1,13 @@ 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_action_widget.dart'; -import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart'; +import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; import 'package:appflowy/plugins/trash/application/prelude.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:go_router/go_router.dart'; class MobileHomeTrashPage extends StatelessWidget { @@ -18,61 +19,53 @@ class MobileHomeTrashPage extends StatelessWidget { Widget build(BuildContext context) { return BlocProvider( create: (context) => getIt()..add(const TrashEvent.initial()), - child: Builder( - builder: (context) { + child: BlocBuilder( + builder: (context, state) { return Scaffold( appBar: AppBar( title: Text(LocaleKeys.trash_text.tr()), - elevation: 0, actions: [ - IconButton( - splashRadius: 20, - icon: const Icon(Icons.more_horiz), - onPressed: () { - showFlowyMobileBottomSheet( - context, - title: LocaleKeys.trash_mobile_actions.tr(), - builder: (_) => Row( - children: [ - Expanded( - child: BottomSheetActionWidget( - svg: FlowySvgs.m_restore_m, - text: LocaleKeys.trash_restoreAll.tr(), - onTap: () { - context - ..read() - .add(const TrashEvent.restoreAll()) - ..pop(); - }, + state.objects.isEmpty + ? const SizedBox.shrink() + : IconButton( + splashRadius: 20, + icon: const Icon(Icons.more_horiz), + onPressed: () { + final trashBloc = context.read(); + showFlowyMobileBottomSheet( + context, + title: LocaleKeys.trash_mobile_actions.tr(), + builder: (_) => Row( + children: [ + Expanded( + child: _TrashActionAllButton( + trashBloc: trashBloc, + type: _TrashActionType.deleteAll, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: _TrashActionAllButton( + trashBloc: trashBloc, + type: _TrashActionType.restoreAll, + ), + ) + ], ), - ), - Expanded( - child: BottomSheetActionWidget( - svg: FlowySvgs.m_delete_m, - text: LocaleKeys.trash_deleteAll.tr(), - onTap: () { - context - ..read() - .add(const TrashEvent.deleteAll()) - ..pop(); - }, - ), - ) - ], + ); + }, ), - ); - }, - ), ], ), - body: BlocBuilder( - builder: (_, state) { - if (state.objects.isEmpty) { - return const _TrashEmptyPage(); - } - return _DeletedFilesListView(state); - }, - ), + body: state.objects.isEmpty + ? FlowyMobileStateContainer.info( + emoji: '🗑️', + title: LocaleKeys.trash_mobile_empty.tr(), + description: LocaleKeys.trash_mobile_emptyDescription.tr(), + ) + : _DeletedFilesListView(state), ); }, ), @@ -80,104 +73,154 @@ class MobileHomeTrashPage extends StatelessWidget { } } -class _DeletedFilesListView extends StatelessWidget { - const _DeletedFilesListView( - this.state, - ); +enum _TrashActionType { + restoreAll, + deleteAll, +} + +class _TrashActionAllButton extends StatelessWidget { + /// Switch between 'delete all' and 'restore all' feature + const _TrashActionAllButton({ + this.type = _TrashActionType.deleteAll, + required this.trashBloc, + }); + final _TrashActionType type; + final TrashBloc trashBloc; - final TrashState state; @override Widget build(BuildContext context) { final theme = Theme.of(context); - return ListView.builder( - itemBuilder: (context, index) { - final object = state.objects[index]; - - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), - child: ListTile( - // TODO(Yijing): implement file type after TrashPB has file type - leading: FlowySvg( - FlowySvgs.documents_s, - size: const Size.square(24), - color: theme.colorScheme.onSurface, - ), - title: Text( - object.name, - style: theme.textTheme.labelMedium - ?.copyWith(color: theme.colorScheme.onBackground), - ), - horizontalTitleGap: 0, - // TODO(yiing): needs improve by container/surface theme color - tileColor: theme.colorScheme.onSurface.withOpacity(0.1), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - // TODO(yijing): extract icon button - IconButton( - splashRadius: 20, - icon: FlowySvg( - FlowySvgs.m_restore_m, - size: const Size.square(24), - color: theme.colorScheme.onSurface, - ), - onPressed: () { - context - .read() - .add(TrashEvent.putback(object.id)); - }, - ), - IconButton( - splashRadius: 20, - icon: FlowySvg( - FlowySvgs.m_delete_m, - size: const Size.square(24), - color: theme.colorScheme.onSurface, - ), - onPressed: () { - context.read().add(TrashEvent.delete(object)); - }, - ) - ], - ), - ), - ); - }, - itemCount: state.objects.length, + final isDeleteAll = type == _TrashActionType.deleteAll; + return BlocProvider.value( + value: trashBloc, + child: BottomSheetActionWidget( + svg: isDeleteAll ? FlowySvgs.m_delete_m : FlowySvgs.m_restore_m, + text: isDeleteAll + ? LocaleKeys.trash_deleteAll.tr() + : LocaleKeys.trash_restoreAll.tr(), + onTap: () { + final trashList = trashBloc.state.objects; + if (trashList.isNotEmpty) { + context.pop(); + showFlowyMobileConfirmDialog( + context, + title: isDeleteAll + ? LocaleKeys.trash_confirmDeleteAll_title.tr() + : LocaleKeys.trash_restoreAll.tr(), + content: isDeleteAll + ? LocaleKeys.trash_confirmDeleteAll_caption.tr() + : LocaleKeys.trash_confirmRestoreAll_caption.tr(), + actionButtonTitle: isDeleteAll + ? LocaleKeys.trash_deleteAll.tr() + : LocaleKeys.trash_restoreAll.tr(), + actionButtonColor: isDeleteAll + ? theme.colorScheme.error + : theme.colorScheme.primary, + onActionButtonPressed: () { + if (isDeleteAll) { + trashBloc.add( + const TrashEvent.deleteAll(), + ); + } else { + trashBloc.add( + const TrashEvent.restoreAll(), + ); + } + }, + cancelButtonTitle: LocaleKeys.button_cancel.tr(), + ); + } else { + // when there is no deleted files + // show toast + Fluttertoast.showToast( + msg: LocaleKeys.trash_mobile_empty.tr(), + gravity: ToastGravity.CENTER, + ); + } + }, + ), ); } } -class _TrashEmptyPage extends StatelessWidget { - const _TrashEmptyPage(); +class _DeletedFilesListView extends StatelessWidget { + const _DeletedFilesListView( + this.state, + ); + final TrashState state; @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - '🗑️', - style: TextStyle(fontSize: 40), - ), - const SizedBox(height: 8), - Text( - LocaleKeys.trash_mobile_empty.tr(), - style: theme.textTheme.labelLarge, - ), - const SizedBox(height: 4), - Text( - LocaleKeys.trash_mobile_emptyDescription.tr(), - style: theme.textTheme.bodyMedium?.copyWith( - color: theme.hintColor, + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: ListView.builder( + itemBuilder: (context, index) { + final object = state.objects[index]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: ListTile( + // TODO(Yijing): implement file type after TrashPB has file type + leading: FlowySvg( + FlowySvgs.documents_s, + size: const Size.square(24), + color: theme.colorScheme.onSurface, + ), + title: Text( + object.name, + style: theme.textTheme.labelMedium + ?.copyWith(color: theme.colorScheme.onBackground), + ), + horizontalTitleGap: 0, + // TODO(yiing): needs improve by container/surface theme color + tileColor: theme.colorScheme.onSurface.withOpacity(0.1), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // TODO(yijing): extract icon button + IconButton( + splashRadius: 20, + icon: FlowySvg( + FlowySvgs.m_restore_m, + size: const Size.square(24), + color: theme.colorScheme.onSurface, + ), + onPressed: () { + context + .read() + .add(TrashEvent.putback(object.id)); + Fluttertoast.showToast( + msg: + '${object.name} ${LocaleKeys.trash_mobile_isRestored.tr()}', + gravity: ToastGravity.BOTTOM, + ); + }, + ), + IconButton( + splashRadius: 20, + icon: FlowySvg( + FlowySvgs.m_delete_m, + size: const Size.square(24), + color: theme.colorScheme.onSurface, + ), + onPressed: () { + context.read().add(TrashEvent.delete(object)); + Fluttertoast.showToast( + msg: + '${object.name} ${LocaleKeys.trash_mobile_isDeleted.tr()}', + gravity: ToastGravity.BOTTOM, + ); + }, + ) + ], + ), ), - ), - ], + ); + }, + itemCount: state.objects.length, ), ); } 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 18cc9a1bb9ebe..f6d9c44ef1cf7 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 @@ -1,8 +1,8 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/mobile_router.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart'; import 'package:appflowy/mobile/presentation/page_item/mobile_view_item_add_button.dart'; +import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; @@ -395,20 +395,24 @@ class _SingleMobileInnerViewItemState extends State { Widget _buildViewAddButton(BuildContext context) { return MobileViewAddButton( onPressed: () { - showMobileBottomSheet( - context: context, - builder: (_) => AddNewPageWidgetBottomSheet( - view: widget.view, - onAction: (layout) { - context.pop(); - context.read().add( - ViewEvent.createView( - LocaleKeys.menuAppHeader_defaultNewPageName.tr(), - layout, - ), - ); - }, - ), + final title = widget.view.name; + showFlowyMobileBottomSheet( + context, + title: title, + builder: (_) { + return AddNewPageWidgetBottomSheet( + view: widget.view, + onAction: (layout) { + context.pop(); + context.read().add( + ViewEvent.createView( + LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + layout, + ), + ); + }, + ); + }, ); }, ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/personal_info/personal_info_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/personal_info/personal_info_setting_group.dart index 1f177bed22ec6..397621248c516 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/personal_info/personal_info_setting_group.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/personal_info/personal_info_setting_group.dart @@ -16,7 +16,7 @@ class PersonalInfoSettingGroup extends StatelessWidget { required this.userProfile, }); - final UserProfilePB? userProfile; + final UserProfilePB userProfile; @override Widget build(BuildContext context) { @@ -33,9 +33,9 @@ class PersonalInfoSettingGroup extends StatelessWidget { settingItemList: [ MobileSettingItem( name: userName, - subtitle: isCloudEnabled && userProfile != null + subtitle: isCloudEnabled ? Text( - userProfile!.email, + userProfile.email, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurface, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart index 5fcbf62fe8ac6..3995e11b983cf 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart @@ -4,6 +4,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'widgets/widgets.dart'; @@ -14,32 +15,33 @@ class SupportSettingGroup extends StatelessWidget { @override Widget build(BuildContext context) { - return MobileSettingGroup( - groupTitle: LocaleKeys.settings_mobile_support.tr(), - settingItemList: [ - // 'Help Center' - MobileSettingItem( - name: LocaleKeys.settings_mobile_joinDiscord.tr(), - trailing: const Icon( - Icons.chevron_right, + return FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) => MobileSettingGroup( + groupTitle: LocaleKeys.settings_mobile_support.tr(), + settingItemList: [ + MobileSettingItem( + name: LocaleKeys.settings_mobile_joinDiscord.tr(), + trailing: const Icon( + Icons.chevron_right, + ), + onTap: () => safeLaunchUrl('https://discord.gg/JucBXeU2FE'), ), - onTap: () => safeLaunchUrl('https://discord.gg/JucBXeU2FE'), - ), - MobileSettingItem( - name: LocaleKeys.workspace_errorActions_reportIssue.tr(), - trailing: const Icon( - Icons.chevron_right, + MobileSettingItem( + name: LocaleKeys.workspace_errorActions_reportIssue.tr(), + trailing: const Icon( + Icons.chevron_right, + ), + onTap: () { + final String? version = snapshot.data?.version; + final String os = Platform.operatingSystem; + safeLaunchUrl( + 'https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&projects=&template=bug_report.yaml&title=[Bug]%20Mobile:%20&version=$version&os=$os', + ); + }, ), - onTap: () { - // TODO(yijing): get app version before release - const String version = 'Beta'; - final String os = Platform.operatingSystem; - safeLaunchUrl( - 'https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&projects=&template=bug_report.yaml&title=[Bug]%20Mobile:%20&version=$version&os=$os', - ); - }, - ), - ], + ], + ), ); } } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_state_container.dart b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_state_container.dart new file mode 100644 index 0000000000000..ad5aa3c894b84 --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_state_container.dart @@ -0,0 +1,102 @@ +import 'dart:io'; + +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +enum _FlowyMobileStateContainerType { + info, + error, +} + +/// Used to display info(like empty state) or error state +/// error state has two buttons to report issue with error message or reach out on discord +class FlowyMobileStateContainer extends StatelessWidget { + const FlowyMobileStateContainer.error({ + this.emoji, + required this.title, + this.description, + required this.errorMsg, + super.key, + }) : _stateType = _FlowyMobileStateContainerType.error; + + const FlowyMobileStateContainer.info({ + this.emoji, + required this.title, + this.description, + super.key, + }) : errorMsg = null, + _stateType = _FlowyMobileStateContainerType.info; + + final String? emoji; + final String title; + final String? description; + final String? errorMsg; + final _FlowyMobileStateContainerType _stateType; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return SizedBox.expand( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 32), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + emoji ?? '', + style: const TextStyle(fontSize: 40), + ), + const SizedBox(height: 8), + Text( + title, + style: theme.textTheme.labelLarge, + ), + const SizedBox(height: 4), + Text( + description ?? '', + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.hintColor, + ), + ), + if (_stateType == _FlowyMobileStateContainerType.error) ...[ + const SizedBox(height: 8), + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + OutlinedButton( + onPressed: () { + final String? version = snapshot.data?.version; + final String os = Platform.operatingSystem; + safeLaunchUrl( + 'https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&projects=&template=bug_report.yaml&title=[Bug]%20Mobile:%20&version=$version&os=$os&context=Error%20log:%20$errorMsg', + ); + }, + child: Text( + LocaleKeys.workspace_errorActions_reportIssue.tr(), + ), + ), + OutlinedButton( + onPressed: () => + safeLaunchUrl('https://discord.gg/JucBXeU2FE'), + child: Text( + LocaleKeys.workspace_errorActions_reachOut.tr(), + ), + ), + ], + ); + }, + ) + ] + ], + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart new file mode 100644 index 0000000000000..4966c1d9cd95a --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart @@ -0,0 +1,62 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +///show the dialog to confirm one single action +///[onActionButtonPressed] and [onCancelButtonPressed] end with close the dialog +Future showFlowyMobileConfirmDialog( + BuildContext context, { + String? title, + String? content, + required String actionButtonTitle, + Color? actionButtonColor, + String? cancelButtonTitle, + required void Function()? onActionButtonPressed, + void Function()? onCancelButtonPressed, +}) async { + return showDialog( + context: context, + builder: (dialogContext) { + final foregroundColor = Theme.of(context).colorScheme.onSurface; + return AlertDialog( + title: Text( + title ?? "", + ), + content: Text( + content ?? "", + ), + actions: [ + TextButton( + child: Text( + actionButtonTitle, + style: TextStyle( + color: actionButtonColor ?? foregroundColor, + ), + ), + onPressed: () { + onActionButtonPressed?.call(); + // we cannot use dialogContext.pop() here because this is no GoRouter in dialogContext. Use Navigator instead to close the dialog. + Navigator.of( + dialogContext, + ).pop(); + }, + ), + TextButton( + child: Text( + cancelButtonTitle ?? LocaleKeys.button_cancel.tr(), + style: TextStyle( + color: foregroundColor, + ), + ), + onPressed: () { + onCancelButtonPressed?.call(); + Navigator.of( + dialogContext, + ).pop(); + }, + ), + ], + ); + }, + ); +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/widgets.dart b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/widgets.dart new file mode 100644 index 0000000000000..8c8b0ad0bd44d --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/widgets.dart @@ -0,0 +1,3 @@ +export 'show_flowy_mobile_confirm_dialog.dart'; +export 'show_flowy_mobile_bottom_sheet.dart'; +export 'flowy_mobile_state_container.dart'; diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 5aea6e5b446aa..fff0e8ec08a1d 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -120,7 +120,9 @@ "mobile": { "actions": "Trash Actions", "empty": "Trash Bin is Empty", - "emptyDescription": "You don't have any deleted file" + "emptyDescription": "You don't have any deleted file", + "isDeleted": "is deleted", + "isRestored": "is restored" } }, "deletePagePrompt": { @@ -394,7 +396,9 @@ "support": "Support", "joinDiscord": "Join us in Discord", "privacyPolicy": "Privacy Policy", - "userAgreement": "User Agreement" + "userAgreement": "User Agreement", + "userprofileError": "Failed to load user profile", + "userprofileErrorDescription": "Please try to log out and log back in to check if the issue still persists." } }, "grid": {