chore: improve UI design on mobile (#5816)

This commit is contained in:
Lucas.Xu 2024-07-27 21:05:51 +08:00 committed by GitHub
parent 22b108df70
commit ddf68b010d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 325 additions and 156 deletions

View File

@ -175,7 +175,7 @@ SPEC CHECKSUMS:
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265 fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425 image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4

View File

@ -23,6 +23,9 @@ class DocumentPageStyleBloc
await event.when( await event.when(
initial: () async { initial: () async {
try { try {
if (view.id.isEmpty) {
return;
}
final layoutObject = final layoutObject =
await ViewBackendService.getView(view.id).fold( await ViewBackendService.getView(view.id).fold(
(s) => jsonDecode(s.extra), (s) => jsonDecode(s.extra),

View File

@ -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<AnimatedGestureDetector> createState() =>
_AnimatedGestureDetectorState();
}
class _AnimatedGestureDetectorState extends State<AnimatedGestureDetector> {
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,
),
);
}
}

View File

@ -24,9 +24,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
height: 52.0, height: 52.0,
leftIcon: const FlowySvg( leftIcon: const FlowySvg(
FlowySvgs.icon_document_s, FlowySvgs.icon_document_s,
size: Size.square(18), size: Size.square(20),
), ),
showTopBorder: false, showTopBorder: false,
showBottomBorder: false,
onTap: () => onAction(ViewLayoutPB.Document), onTap: () => onAction(ViewLayoutPB.Document),
), ),
FlowyOptionTile.text( FlowyOptionTile.text(
@ -34,9 +35,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
height: 52.0, height: 52.0,
leftIcon: const FlowySvg( leftIcon: const FlowySvg(
FlowySvgs.icon_grid_s, FlowySvgs.icon_grid_s,
size: Size.square(18), size: Size.square(20),
), ),
showTopBorder: false, showTopBorder: false,
showBottomBorder: false,
onTap: () => onAction(ViewLayoutPB.Grid), onTap: () => onAction(ViewLayoutPB.Grid),
), ),
FlowyOptionTile.text( FlowyOptionTile.text(
@ -44,9 +46,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
height: 52.0, height: 52.0,
leftIcon: const FlowySvg( leftIcon: const FlowySvg(
FlowySvgs.icon_board_s, FlowySvgs.icon_board_s,
size: Size.square(18), size: Size.square(20),
), ),
showTopBorder: false, showTopBorder: false,
showBottomBorder: false,
onTap: () => onAction(ViewLayoutPB.Board), onTap: () => onAction(ViewLayoutPB.Board),
), ),
FlowyOptionTile.text( FlowyOptionTile.text(
@ -54,9 +57,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
height: 52.0, height: 52.0,
leftIcon: const FlowySvg( leftIcon: const FlowySvg(
FlowySvgs.icon_calendar_s, FlowySvgs.icon_calendar_s,
size: Size.square(18), size: Size.square(20),
), ),
showTopBorder: false, showTopBorder: false,
showBottomBorder: false,
onTap: () => onAction(ViewLayoutPB.Calendar), onTap: () => onAction(ViewLayoutPB.Calendar),
), ),
FlowyOptionTile.text( FlowyOptionTile.text(
@ -64,9 +68,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
height: 52.0, height: 52.0,
leftIcon: const FlowySvg( leftIcon: const FlowySvg(
FlowySvgs.chat_ai_page_s, FlowySvgs.chat_ai_page_s,
size: Size.square(18), size: Size.square(20),
), ),
showTopBorder: false, showTopBorder: false,
showBottomBorder: false,
onTap: () => onAction(ViewLayoutPB.Chat), onTap: () => onAction(ViewLayoutPB.Chat),
), ),
], ],

View File

@ -1,4 +1,3 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.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/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/recent/recent_views_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/application/view/view_bloc.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -109,16 +109,6 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
await _showConfirmDialog( await _showConfirmDialog(
onDelete: () { onDelete: () {
recentViewsBloc.add(RecentViewsEvent.removeRecentViews([viewId])); 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<MobileViewItemBottomSheet> {
), ),
onRightButtonPressed: (context) { onRightButtonPressed: (context) {
onDelete(); onDelete();
Navigator.pop(context); 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,
),
],
),
);
}
}

View File

@ -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/recent/recent_views_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_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/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:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -40,18 +41,32 @@ enum MobilePaneActionType {
backgroundColor: const Color(0xFFFA217F), backgroundColor: const Color(0xFFFA217F),
svg: FlowySvgs.favorite_section_remove_from_favorite_s, svg: FlowySvgs.favorite_section_remove_from_favorite_s,
size: 24.0, size: 24.0,
onPressed: (context) => context onPressed: (context) {
.read<FavoriteBloc>() showToastNotification(
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)), context,
message: LocaleKeys.button_unfavoriteSuccessfully.tr(),
);
context
.read<FavoriteBloc>()
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view));
},
); );
case MobilePaneActionType.addToFavorites: case MobilePaneActionType.addToFavorites:
return MobileSlideActionButton( return MobileSlideActionButton(
backgroundColor: const Color(0xFF00C8FF), backgroundColor: const Color(0xFF00C8FF),
svg: FlowySvgs.favorite_s, svg: FlowySvgs.favorite_s,
size: 24.0, size: 24.0,
onPressed: (context) => context onPressed: (context) {
.read<FavoriteBloc>() showToastNotification(
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)), context,
message: LocaleKeys.button_favoriteSuccessfully.tr(),
);
context
.read<FavoriteBloc>()
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view));
},
); );
case MobilePaneActionType.add: case MobilePaneActionType.add:
return MobileSlideActionButton( return MobileSlideActionButton(
@ -69,6 +84,7 @@ enum MobilePaneActionType {
showDragHandle: true, showDragHandle: true,
showCloseButton: true, showCloseButton: true,
useRootNavigator: true, useRootNavigator: true,
showDivider: false,
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
builder: (sheetContext) { builder: (sheetContext) {
return AddNewPageWidgetBottomSheet( return AddNewPageWidgetBottomSheet(
@ -145,8 +161,6 @@ enum MobilePaneActionType {
? MobileViewItemBottomSheetBodyAction.removeFromFavorites ? MobileViewItemBottomSheetBodyAction.removeFromFavorites
: MobileViewItemBottomSheetBodyAction.addToFavorites, : MobileViewItemBottomSheetBodyAction.addToFavorites,
MobileViewItemBottomSheetBodyAction.divider, MobileViewItemBottomSheetBodyAction.divider,
if (view.layout != ViewLayoutPB.Chat)
MobileViewItemBottomSheetBodyAction.duplicate,
MobileViewItemBottomSheetBodyAction.divider, MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.removeFromRecent, MobileViewItemBottomSheetBodyAction.removeFromRecent,
]; ];
@ -156,7 +170,6 @@ enum MobilePaneActionType {
? MobileViewItemBottomSheetBodyAction.removeFromFavorites ? MobileViewItemBottomSheetBodyAction.removeFromFavorites
: MobileViewItemBottomSheetBodyAction.addToFavorites, : MobileViewItemBottomSheetBodyAction.addToFavorites,
MobileViewItemBottomSheetBodyAction.divider, MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.duplicate,
]; ];
} }
} }
@ -181,12 +194,13 @@ ActionPane buildEndActionPane(
bool needSpace = true, bool needSpace = true,
MobilePageCardType? cardType, MobilePageCardType? cardType,
FolderSpaceType? spaceType, FolderSpaceType? spaceType,
required double spaceRatio,
}) { }) {
return ActionPane( return ActionPane(
motion: const ScrollMotion(), motion: const ScrollMotion(),
extentRatio: actions.length / 5, extentRatio: actions.length / spaceRatio,
children: [ children: [
if (needSpace) const HSpace(20), if (needSpace) const HSpace(60),
...actions.map( ...actions.map(
(action) => action.actionButton( (action) => action.actionButton(
context, context,

View File

@ -70,6 +70,7 @@ Future<T?> showMobileBottomSheet<T>(
backgroundColor ??= Theme.of(context).brightness == Brightness.light backgroundColor ??= Theme.of(context).brightness == Brightness.light
? const Color(0xFFF7F8FB) ? const Color(0xFFF7F8FB)
: const Color(0xFF23262B); : const Color(0xFF23262B);
barrierColor ??= Colors.black.withOpacity(0.3);
return showModalBottomSheet<T>( return showModalBottomSheet<T>(
context: context, context: context,
@ -226,10 +227,14 @@ class BottomSheetHeader extends StatelessWidget {
), ),
), ),
Align( Align(
child: FlowyText( child: Container(
title, constraints: const BoxConstraints(maxWidth: 250),
fontSize: 16.0, child: FlowyText(
fontWeight: FontWeight.w500, title,
fontSize: 17.0,
fontWeight: FontWeight.w500,
overflow: TextOverflow.ellipsis,
),
), ),
), ),
if (showDoneButton) if (showDoneButton)

View File

@ -100,8 +100,6 @@ class _FavoriteViews extends StatelessWidget {
child: ListView.separated( child: ListView.separated(
key: const PageStorageKey('favorite_views_page_storage_key'), key: const PageStorageKey('favorite_views_page_storage_key'),
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: HomeSpaceViewSizes.mHorizontalPadding,
right: HomeSpaceViewSizes.mHorizontalPadding,
bottom: HomeSpaceViewSizes.mVerticalPadding + bottom: HomeSpaceViewSizes.mVerticalPadding +
MediaQuery.of(context).padding.bottom, MediaQuery.of(context).padding.bottom,
), ),

View File

@ -72,6 +72,7 @@ class MobileFavoriteFolder extends StatelessWidget {
MobilePaneActionType.more, MobilePaneActionType.more,
], ],
spaceType: FolderSpaceType.favorite, spaceType: FolderSpaceType.favorite,
spaceRatio: 5,
), ),
), ),
), ),

View File

@ -29,8 +29,6 @@ class _MobileHomeSpaceState extends State<MobileHomeSpace>
child: SingleChildScrollView( child: SingleChildScrollView(
child: Padding( child: Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: HomeSpaceViewSizes.mHorizontalPadding,
right: HomeSpaceViewSizes.mHorizontalPadding,
top: HomeSpaceViewSizes.mVerticalPadding, top: HomeSpaceViewSizes.mVerticalPadding,
bottom: HomeSpaceViewSizes.mVerticalPadding + bottom: HomeSpaceViewSizes.mVerticalPadding +
MediaQuery.of(context).padding.bottom, MediaQuery.of(context).padding.bottom,

View File

@ -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/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_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/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:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -91,7 +92,12 @@ class MobileFolders extends StatelessWidget {
children: [ children: [
..._buildSpaceOrSection(context, state), ..._buildSpaceOrSection(context, state),
const VSpace(4.0), const VSpace(4.0),
const _TrashButton(), const Padding(
padding: EdgeInsets.symmetric(
horizontal: HomeSpaceViewSizes.mHorizontalPadding,
),
child: _TrashButton(),
),
], ],
), ),
); );

View File

@ -1,5 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/home/mobile_home_setting_page.dart'; import 'package:appflowy/mobile/presentation/home/mobile_home_setting_page.dart';
import 'package:appflowy/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart';
@ -113,8 +114,9 @@ class _MobileWorkspace extends StatelessWidget {
if (currentWorkspace == null) { if (currentWorkspace == null) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return GestureDetector( return AnimatedGestureDetector(
onTap: () { alignment: Alignment.centerLeft,
onTapUp: () {
context.read<UserWorkspaceBloc>().add( context.read<UserWorkspaceBloc>().add(
const UserWorkspaceEvent.fetchWorkspaces(), const UserWorkspaceEvent.fetchWorkspaces(),
); );
@ -143,7 +145,7 @@ class _MobileWorkspace extends StatelessWidget {
: const HSpace(8), : const HSpace(8),
FlowyText.semibold( FlowyText.semibold(
currentWorkspace.name, currentWorkspace.name,
fontSize: 16.0, fontSize: 20.0,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
], ],
@ -162,9 +164,10 @@ class _MobileWorkspace extends StatelessWidget {
showHeader: true, showHeader: true,
showDragHandle: true, showDragHandle: true,
showCloseButton: true, showCloseButton: true,
useRootNavigator: true,
title: LocaleKeys.workspace_menuTitle.tr(), title: LocaleKeys.workspace_menuTitle.tr(),
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
builder: (_) { builder: (sheetContext) {
return BlocProvider.value( return BlocProvider.value(
value: context.read<UserWorkspaceBloc>(), value: context.read<UserWorkspaceBloc>(),
child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>( child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
@ -179,7 +182,7 @@ class _MobileWorkspace extends StatelessWidget {
currentWorkspace: currentWorkspace, currentWorkspace: currentWorkspace,
workspaces: workspaces, workspaces: workspaces,
onWorkspaceSelected: (workspace) { onWorkspaceSelected: (workspace) {
context.pop(); Navigator.of(sheetContext).pop();
if (workspace == currentWorkspace) { if (workspace == currentWorkspace) {
return; return;

View File

@ -72,8 +72,6 @@ class _RecentViews extends StatelessWidget {
child: ListView.separated( child: ListView.separated(
key: const PageStorageKey('recent_views_page_storage_key'), key: const PageStorageKey('recent_views_page_storage_key'),
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: HomeSpaceViewSizes.mHorizontalPadding,
right: HomeSpaceViewSizes.mHorizontalPadding,
bottom: HomeSpaceViewSizes.mVerticalPadding + bottom: HomeSpaceViewSizes.mVerticalPadding +
MediaQuery.of(context).padding.bottom, MediaQuery.of(context).padding.bottom,
), ),

View File

@ -82,6 +82,7 @@ class MobileSectionFolder extends StatelessWidget {
], ],
spaceType: spaceType, spaceType: spaceType,
needSpace: false, needSpace: false,
spaceRatio: 5,
); );
}, },
), ),

View File

@ -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/mobile_router.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.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/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/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/shared/appflowy_network_image.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/date_format_ext.dart';
import 'package:appflowy/workspace/application/settings/date_time/time_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/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-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
@ -76,13 +78,14 @@ class MobileViewPage extends StatelessWidget {
: MobilePaneActionType.addToFavorites, : MobilePaneActionType.addToFavorites,
], ],
cardType: type, cardType: type,
spaceRatio: 4,
), ),
child: GestureDetector( child: AnimatedGestureDetector(
behavior: HitTestBehavior.opaque, onTapUp: () => context.pushView(view),
onTapUp: (_) => context.pushView(view),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const HSpace(HomeSpaceViewSizes.mHorizontalPadding),
Expanded(child: _buildDescription(context, state)), Expanded(child: _buildDescription(context, state)),
const HSpace(20.0), const HSpace(20.0),
SizedBox( SizedBox(
@ -90,6 +93,7 @@ class MobileViewPage extends StatelessWidget {
height: 60, height: 60,
child: _buildCover(context, state), child: _buildCover(context, state),
), ),
const HSpace(HomeSpaceViewSizes.mHorizontalPadding),
], ],
), ),
), ),
@ -211,7 +215,7 @@ class MobileViewPage extends StatelessWidget {
Widget _buildLastViewed(BuildContext context) { Widget _buildLastViewed(BuildContext context) {
final textColor = Theme.of(context).isLightMode final textColor = Theme.of(context).isLightMode
? const Color(0xFF171717) ? const Color(0x7F171717)
: Colors.white.withOpacity(0.45); : Colors.white.withOpacity(0.45);
if (timestamp == null) { if (timestamp == null) {
return const SizedBox.shrink(); return const SizedBox.shrink();

View File

@ -50,23 +50,17 @@ class _MobileSpaceState extends State<MobileSpace> {
MobileSpaceHeader( MobileSpaceHeader(
isExpanded: state.isExpanded, isExpanded: state.isExpanded,
space: currentSpace, space: currentSpace,
onAdded: () { onAdded: () => _showCreatePageMenu(currentSpace),
context.read<SpaceBloc>().add(
SpaceEvent.createPage(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout: ViewLayoutPB.Document,
index: 0,
),
);
context.read<SpaceBloc>().add(
SpaceEvent.expand(currentSpace, true),
);
},
onPressed: () => _showSpaceMenu(context), onPressed: () => _showSpaceMenu(context),
), ),
_Pages( Padding(
key: ValueKey(currentSpace.id), padding: const EdgeInsets.only(
space: currentSpace, left: HomeSpaceViewSizes.mHorizontalPadding,
),
child: _Pages(
key: ValueKey(currentSpace.id),
space: currentSpace,
),
), ),
], ],
); );
@ -82,6 +76,7 @@ class _MobileSpaceState extends State<MobileSpace> {
showDragHandle: true, showDragHandle: true,
showCloseButton: true, showCloseButton: true,
showDoneButton: true, showDoneButton: true,
useRootNavigator: true,
title: LocaleKeys.space_title.tr(), title: LocaleKeys.space_title.tr(),
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
builder: (_) { builder: (_) {
@ -104,6 +99,38 @@ class _MobileSpaceState extends State<MobileSpace> {
), ),
); );
} }
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<SpaceBloc>().add(
SpaceEvent.createPage(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout: layout,
index: 0,
),
);
context.read<SpaceBloc>().add(
SpaceEvent.expand(space, true),
);
},
);
},
);
}
} }
class _Pages extends StatelessWidget { class _Pages extends StatelessWidget {
@ -148,7 +175,7 @@ class _Pages extends StatelessWidget {
MobilePaneActionType.add, MobilePaneActionType.add,
], ],
spaceType: spaceType, spaceType: spaceType,
needSpace: false, spaceRatio: 4,
); );
}, },
), ),

View File

@ -1,4 +1,5 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; 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/workspace/presentation/home/menu/sidebar/space/space_icon.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -30,6 +31,7 @@ class MobileSpaceHeader extends StatelessWidget {
height: 48, height: 48,
child: Row( child: Row(
children: [ children: [
const HSpace(HomeSpaceViewSizes.mHorizontalPadding),
SpaceIcon( SpaceIcon(
dimension: 24, dimension: 24,
space: space, space: space,
@ -49,8 +51,15 @@ class MobileSpaceHeader extends StatelessWidget {
GestureDetector( GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: onAdded, onTap: onAdded,
child: const FlowySvg( child: Container(
FlowySvgs.m_space_add_s, // expand the touch area
margin: const EdgeInsets.symmetric(
horizontal: HomeSpaceViewSizes.mHorizontalPadding,
vertical: 8.0,
),
child: const FlowySvg(
FlowySvgs.m_space_add_s,
),
), ),
), ),
], ],

View File

@ -59,6 +59,7 @@ class _SidebarSpaceMenuItem extends StatelessWidget {
children: [ children: [
FlowyText.medium( FlowyText.medium(
space.name, space.name,
fontSize: 16.0,
), ),
const HSpace(6.0), const HSpace(6.0),
if (space.spacePermission == SpacePermission.private) if (space.spacePermission == SpacePermission.private)
@ -68,16 +69,17 @@ class _SidebarSpaceMenuItem extends StatelessWidget {
), ),
], ],
), ),
margin: const EdgeInsets.symmetric(horizontal: 12.0),
iconPadding: 10, iconPadding: 10,
leftIcon: SpaceIcon( leftIcon: SpaceIcon(
dimension: 24, dimension: 24,
space: space, space: space,
cornerRadius: 6.0, cornerRadius: 6.0,
), ),
leftIconSize: const Size.square(20), leftIconSize: const Size.square(24),
rightIcon: isSelected rightIcon: isSelected
? const FlowySvg( ? const FlowySvg(
FlowySvgs.workspace_selected_s, FlowySvgs.m_blue_check_s,
blendMode: null, blendMode: null,
) )
: null, : null,

View File

@ -138,17 +138,20 @@ class _WorkspaceMenuItem extends StatelessWidget {
height: 60, height: 60,
showTopBorder: showTopBorder, showTopBorder: showTopBorder,
showBottomBorder: false, showBottomBorder: false,
leftIcon: WorkspaceIcon( leftIcon: Padding(
enableEdit: false, padding: const EdgeInsets.symmetric(horizontal: 4.0),
iconSize: 26, child: WorkspaceIcon(
fontSize: 16.0, enableEdit: false,
workspace: workspace, iconSize: 26,
onSelected: (result) => context.read<UserWorkspaceBloc>().add( fontSize: 16.0,
UserWorkspaceEvent.updateWorkspaceIcon( workspace: workspace,
workspace.workspaceId, onSelected: (result) => context.read<UserWorkspaceBloc>().add(
result.emoji, UserWorkspaceEvent.updateWorkspaceIcon(
workspace.workspaceId,
result.emoji,
),
), ),
), ),
), ),
trailing: workspace.workspaceId == currentWorkspace.workspaceId trailing: workspace.workspaceId == currentWorkspace.workspaceId
? const FlowySvg( ? const FlowySvg(

View File

@ -50,6 +50,9 @@ class MobileBottomNavigationBar extends StatelessWidget {
final backgroundColor = isLightMode final backgroundColor = isLightMode
? Colors.white.withOpacity(0.95) ? Colors.white.withOpacity(0.95)
: const Color(0xFF23262B).withOpacity(0.95); : const Color(0xFF23262B).withOpacity(0.95);
final borderColor = isLightMode
? const Color(0x141F2329)
: const Color(0xFF23262B).withOpacity(0.5);
return Scaffold( return Scaffold(
body: navigationShell, body: navigationShell,
extendBody: true, extendBody: true,
@ -62,9 +65,7 @@ class MobileBottomNavigationBar extends StatelessWidget {
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
border: isLightMode border: isLightMode
? Border( ? Border(top: BorderSide(color: borderColor))
top: BorderSide(color: Theme.of(context).dividerColor),
)
: null, : null,
color: backgroundColor, color: backgroundColor,
), ),

View File

@ -300,7 +300,7 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
) )
: Opacity( : Opacity(
opacity: 0.7, opacity: 0.7,
child: widget.view.defaultIcon(), child: widget.view.defaultIcon(size: const Size.square(18)),
); );
return SizedBox(width: 18.0, child: icon); return SizedBox(width: 18.0, child: icon);
} }

View File

@ -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_configuration.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/shared/markdown_to_document.dart'; import 'package:appflowy/shared/markdown_to_document.dart';
import 'package:appflowy/util/theme_extension.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:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:markdown_widget/markdown_widget.dart'; import 'package:markdown_widget/markdown_widget.dart';
import 'selectable_highlight.dart'; import 'selectable_highlight.dart';
@ -30,7 +33,11 @@ class AIMarkdownText extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
switch (type) { switch (type) {
case AIMarkdownType.appflowyEditor: 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: case AIMarkdownType.markdownWidget:
return _ThirdPartyMarkdown(markdown: markdown); return _ThirdPartyMarkdown(markdown: markdown);
} }

View File

@ -90,19 +90,22 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
); );
} }
return Stack( return Padding(
children: [ padding: const EdgeInsets.only(bottom: 16),
_buildCover(context, state), child: Stack(
Positioned( children: [
left: 0, _buildCover(context, state),
right: 0, Positioned(
bottom: 0, left: 0,
child: Padding( right: 0,
padding: const EdgeInsets.symmetric(vertical: 24.0), bottom: 0,
child: iconAndTitle, child: Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: iconAndTitle,
),
), ),
), ],
], ),
); );
}, },
), ),

View File

@ -4,7 +4,6 @@ import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
typedef FeatureFlagMap = Map<FeatureFlag, bool>; typedef FeatureFlagMap = Map<FeatureFlag, bool>;

View File

@ -1,8 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/shared/feature_flags.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:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.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:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:toastification/toastification.dart';
import 'prelude.dart'; import 'prelude.dart';
@ -197,31 +197,33 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
child: BlocBuilder<AppearanceSettingsCubit, AppearanceSettingsState>( child: BlocBuilder<AppearanceSettingsCubit, AppearanceSettingsState>(
builder: (context, state) { builder: (context, state) {
_setSystemOverlayStyle(state); _setSystemOverlayStyle(state);
return MaterialApp.router( return ToastificationWrapper(
builder: (context, child) => MediaQuery( child: MaterialApp.router(
// use the 1.0 as the textScaleFactor to avoid the text size builder: (context, child) => MediaQuery(
// affected by the system setting. // use the 1.0 as the textScaleFactor to avoid the text size
data: MediaQuery.of(context).copyWith( // affected by the system setting.
textScaler: TextScaler.linear(state.textScaleFactor), data: MediaQuery.of(context).copyWith(
), textScaler: TextScaler.linear(state.textScaleFactor),
child: overlayManagerBuilder( ),
context, child: overlayManagerBuilder(
!PlatformExtension.isMobile && FeatureFlag.search.isOn context,
? CommandPalette( !PlatformExtension.isMobile && FeatureFlag.search.isOn
notifier: _commandPaletteNotifier, ? CommandPalette(
child: child, notifier: _commandPaletteNotifier,
) child: 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,
); );
}, },
), ),

View File

@ -48,7 +48,7 @@ class ViewExtKeys {
} }
extension ViewExtension on ViewPB { extension ViewExtension on ViewPB {
Widget defaultIcon() => FlowySvg( Widget defaultIcon({Size? size}) => FlowySvg(
switch (layout) { switch (layout) {
ViewLayoutPB.Board => FlowySvgs.icon_board_s, ViewLayoutPB.Board => FlowySvgs.icon_board_s,
ViewLayoutPB.Calendar => FlowySvgs.icon_calendar_s, ViewLayoutPB.Calendar => FlowySvgs.icon_calendar_s,
@ -57,6 +57,7 @@ extension ViewExtension on ViewPB {
ViewLayoutPB.Chat => FlowySvgs.chat_ai_page_s, ViewLayoutPB.Chat => FlowySvgs.chat_ai_page_s,
_ => FlowySvgs.document_s, _ => FlowySvgs.document_s,
}, },
size: size,
); );
PluginType get pluginType => switch (layout) { PluginType get pluginType => switch (layout) {

View File

@ -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/generated/locale_keys.g.dart';
import 'package:appflowy/startup/tasks/app_widget.dart'; import 'package:appflowy/startup/tasks/app_widget.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_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:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/style_widget/text.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/buttons/secondary_button.dart';
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:toastification/toastification.dart'; import 'package:toastification/toastification.dart';
export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
@ -303,6 +304,18 @@ void showToastNotification(
String? description, String? description,
ToastificationType type = ToastificationType.success, 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( toastification.show(
context: context, context: context,
type: type, 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<void> showConfirmDeletionDialog({ Future<void> showConfirmDeletionDialog({
required BuildContext context, required BuildContext context,
required String name, required String name,

View File

@ -276,7 +276,7 @@
"justNow": "just now", "justNow": "just now",
"minutesAgo": "{count} minutes ago", "minutesAgo": "{count} minutes ago",
"lastViewed": "Last viewed", "lastViewed": "Last viewed",
"favoriteAt": "Favorited at", "favoriteAt": "Favorited",
"emptyRecent": "No Recent Documents", "emptyRecent": "No Recent Documents",
"emptyRecentDescription": "As you view documents, they will appear here for easy retrieval", "emptyRecentDescription": "As you view documents, they will appear here for easy retrieval",
"emptyFavorite": "No Favorite Documents", "emptyFavorite": "No Favorite Documents",
@ -337,6 +337,8 @@
"removeFromFavorites": "Remove from favorites", "removeFromFavorites": "Remove from favorites",
"removeFromRecent": "Remove from recent", "removeFromRecent": "Remove from recent",
"addToFavorites": "Add to favorites", "addToFavorites": "Add to favorites",
"favoriteSuccessfully": "Favorited success",
"unfavoriteSuccessfully": "Unfavorited success",
"rename": "Rename", "rename": "Rename",
"helpCenter": "Help Center", "helpCenter": "Help Center",
"add": "Add", "add": "Add",