feat: support slide actions on mobile (#5463)

* feat: support slide actions on mobile

* fix: some ui issues

* fix: scale down emoji size on windows

* fix: flutter analyze

* fix: force text height on macos
This commit is contained in:
Lucas.Xu 2024-06-05 09:18:43 +08:00 committed by GitHub
parent 9f66dcdc8f
commit e4eff7e632
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 323 additions and 235 deletions

View File

@ -200,7 +200,7 @@ SPEC CHECKSUMS:
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
@ -227,4 +227,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: d0d9b4ff572d8695c38eb3f9b490f55cdfc57eca
COCOAPODS: 1.11.3
COCOAPODS: 1.15.2

View File

@ -21,6 +21,7 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
children: [
FlowyOptionTile.text(
text: LocaleKeys.document_menuName.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.document_s,
size: Size.square(18),
@ -30,6 +31,7 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
),
FlowyOptionTile.text(
text: LocaleKeys.grid_menuName.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.grid_s,
size: Size.square(18),
@ -39,6 +41,7 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
),
FlowyOptionTile.text(
text: LocaleKeys.board_menuName.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.board_s,
size: Size.square(18),
@ -48,6 +51,7 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
),
FlowyOptionTile.text(
text: LocaleKeys.calendar_menuName.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.calendar_s,
size: Size.square(18),
@ -57,6 +61,7 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
),
FlowyOptionTile.text(
text: LocaleKeys.chat_newChat.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.chat_ai_page_s,
size: Size.square(18),

View File

@ -44,6 +44,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
case MobileViewItemBottomSheetBodyAction.rename:
return FlowyOptionTile.text(
text: LocaleKeys.button_rename.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.view_item_rename_s,
size: Size.square(18),
@ -57,6 +58,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
case MobileViewItemBottomSheetBodyAction.duplicate:
return FlowyOptionTile.text(
text: LocaleKeys.button_duplicate.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.duplicate_s,
size: Size.square(18),
@ -71,6 +73,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
case MobileViewItemBottomSheetBodyAction.share:
return FlowyOptionTile.text(
text: LocaleKeys.button_share.tr(),
height: 52.0,
leftIcon: const FlowySvg(
FlowySvgs.share_s,
size: Size.square(18),
@ -84,9 +87,10 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
case MobileViewItemBottomSheetBodyAction.delete:
return FlowyOptionTile.text(
text: LocaleKeys.button_delete.tr(),
height: 52.0,
textColor: Theme.of(context).colorScheme.error,
leftIcon: FlowySvg(
FlowySvgs.delete_s,
FlowySvgs.trash_s,
size: const Size.square(18),
color: Theme.of(context).colorScheme.error,
),
@ -98,6 +102,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
);
case MobileViewItemBottomSheetBodyAction.addToFavorites:
return FlowyOptionTile.text(
height: 52.0,
text: LocaleKeys.button_addToFavorites.tr(),
leftIcon: const FlowySvg(
FlowySvgs.favorite_s,
@ -111,6 +116,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
);
case MobileViewItemBottomSheetBodyAction.removeFromFavorites:
return FlowyOptionTile.text(
height: 52.0,
text: LocaleKeys.button_removeFromFavorites.tr(),
leftIcon: const FlowySvg(
FlowySvgs.favorite_section_remove_from_favorite_s,
@ -124,6 +130,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
);
case MobileViewItemBottomSheetBodyAction.removeFromRecent:
return FlowyOptionTile.text(
height: 52.0,
text: LocaleKeys.button_removeFromRecent.tr(),
leftIcon: const FlowySvg(
FlowySvgs.remove_from_recent_s,
@ -137,7 +144,10 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
);
case MobileViewItemBottomSheetBodyAction.divider:
return const Divider(height: 0.5);
return const Padding(
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: Divider(height: 0.5),
);
}
}
}

View File

@ -1,10 +1,15 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/home/shared/mobile_view_card.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/recent/recent_views_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
@ -13,11 +18,14 @@ enum MobilePaneActionType {
delete,
addToFavorites,
removeFromFavorites,
more;
more,
add;
MobileSlideActionButton actionButton(
BuildContext context,
) {
BuildContext context, {
MobileViewCardType? cardType,
FolderSpaceType? spaceType,
}) {
switch (this) {
case MobilePaneActionType.delete:
return MobileSlideActionButton(
@ -29,59 +37,88 @@ enum MobilePaneActionType {
);
case MobilePaneActionType.removeFromFavorites:
return MobileSlideActionButton(
backgroundColor: Colors.orange,
svg: FlowySvgs.favorite_s,
backgroundColor: const Color(0xFFFA217F),
svg: FlowySvgs.favorite_section_remove_from_favorite_s,
size: 24.0,
onPressed: (context) => context
.read<FavoriteBloc>()
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)),
);
case MobilePaneActionType.addToFavorites:
return MobileSlideActionButton(
backgroundColor: Colors.orange,
svg: FlowySvgs.m_favorite_unselected_lg,
size: 34.0,
backgroundColor: const Color(0xFF00C8FF),
svg: FlowySvgs.favorite_s,
size: 24.0,
onPressed: (context) => context
.read<FavoriteBloc>()
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)),
);
case MobilePaneActionType.more:
case MobilePaneActionType.add:
return MobileSlideActionButton(
backgroundColor: Colors.grey,
svg: FlowySvgs.three_dots_vertical_s,
backgroundColor: const Color(0xFF00C8FF),
svg: FlowySvgs.add_m,
size: 28.0,
onPressed: (context) {
final viewBloc = context.read<ViewBloc>();
final view = viewBloc.state.view;
final title = view.name;
showMobileBottomSheet(
context,
showHeader: true,
title: title,
showDragHandle: true,
showCloseButton: true,
useRootNavigator: true,
backgroundColor: Theme.of(context).colorScheme.surface,
builder: (sheetContext) {
return AddNewPageWidgetBottomSheet(
view: view,
onAction: (layout) {
context.read<ViewBloc>().add(
ViewEvent.createView(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout,
section: spaceType!.toViewSectionPB,
),
);
},
);
},
);
},
);
case MobilePaneActionType.more:
return MobileSlideActionButton(
backgroundColor: const Color(0xE5515563),
svg: FlowySvgs.three_dots_s,
size: 24.0,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
onPressed: (context) {
final viewBloc = context.read<ViewBloc>();
final favoriteBloc = context.read<FavoriteBloc>();
final recentViewsBloc = context.read<RecentViewsBloc?>();
showMobileBottomSheet(
context,
showDragHandle: true,
showDivider: false,
backgroundColor: AFThemeExtension.of(context).background,
useRootNavigator: true,
backgroundColor: Theme.of(context).colorScheme.surface,
builder: (context) {
return MultiBlocProvider(
providers: [
BlocProvider.value(value: viewBloc),
BlocProvider.value(value: favoriteBloc),
if (recentViewsBloc != null)
BlocProvider.value(value: recentViewsBloc),
],
child: BlocBuilder<ViewBloc, ViewState>(
builder: (context, state) {
final isFavorite = state.view.isFavorite;
return MobileViewItemBottomSheet(
view: viewBloc.state.view,
actions: [
isFavorite
? MobileViewItemBottomSheetBodyAction
.removeFromFavorites
: MobileViewItemBottomSheetBodyAction
.addToFavorites,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.rename,
if (state.view.layout != ViewLayoutPB.Chat)
MobileViewItemBottomSheetBodyAction.duplicate,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.delete,
],
actions: _buildActions(state.view, cardType: cardType),
);
},
),
@ -92,19 +129,71 @@ enum MobilePaneActionType {
);
}
}
List<MobileViewItemBottomSheetBodyAction> _buildActions(
ViewPB view, {
MobileViewCardType? cardType,
}) {
final isFavorite = view.isFavorite;
if (cardType != null) {
switch (cardType) {
case MobileViewCardType.recent:
return [
isFavorite
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
: MobileViewItemBottomSheetBodyAction.addToFavorites,
MobileViewItemBottomSheetBodyAction.divider,
if (view.layout != ViewLayoutPB.Chat)
MobileViewItemBottomSheetBodyAction.duplicate,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.removeFromRecent,
];
case MobileViewCardType.favorite:
return [
isFavorite
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
: MobileViewItemBottomSheetBodyAction.addToFavorites,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.duplicate,
];
}
}
return [
isFavorite
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
: MobileViewItemBottomSheetBodyAction.addToFavorites,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.rename,
if (view.layout != ViewLayoutPB.Chat)
MobileViewItemBottomSheetBodyAction.duplicate,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.delete,
];
}
}
ActionPane buildEndActionPane(
BuildContext context,
List<MobilePaneActionType> actions,
) {
List<MobilePaneActionType> actions, {
bool needSpace = true,
MobileViewCardType? cardType,
FolderSpaceType? spaceType,
}) {
debugPrint('actions: $actions');
return ActionPane(
motion: const ScrollMotion(),
extentRatio: actions.length / 5,
children: actions
.map(
(action) => action.actionButton(context),
)
.toList(),
children: [
if (needSpace) const HSpace(20),
...actions.map(
(action) => action.actionButton(
context,
spaceType: spaceType,
cardType: cardType,
),
),
],
);
}

View File

@ -63,12 +63,16 @@ class MobileFavoriteFolder extends StatelessWidget {
view: view,
level: 0,
onSelected: context.pushView,
endActionPane: (context) => buildEndActionPane(context, [
view.isFavorite
? MobilePaneActionType.removeFromFavorites
: MobilePaneActionType.addToFavorites,
MobilePaneActionType.more,
]),
endActionPane: (context) => buildEndActionPane(
context,
[
view.isFavorite
? MobilePaneActionType.removeFromFavorites
: MobilePaneActionType.addToFavorites,
MobilePaneActionType.more,
],
spaceType: FolderSpaceType.favorite,
),
),
),
],

View File

@ -111,7 +111,7 @@ class _TrashButton extends StatelessWidget {
height: 52,
child: FlowyButton(
expand: true,
margin: const EdgeInsets.symmetric(vertical: 8),
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 2.0),
leftIcon: const FlowySvg(
FlowySvgs.m_delete_s,
),

View File

@ -123,12 +123,13 @@ class _MobileWorkspace extends StatelessWidget {
child: Row(
children: [
SizedBox.square(
dimension: 34.0,
dimension: currentWorkspace.icon.isNotEmpty ? 34.0 : 26.0,
child: WorkspaceIcon(
workspace: currentWorkspace,
iconSize: 26,
fontSize: 16.0,
enableEdit: false,
alignment: Alignment.centerLeft,
onSelected: (result) => context.read<UserWorkspaceBloc>().add(
UserWorkspaceEvent.updateWorkspaceIcon(
currentWorkspace.workspaceId,
@ -137,32 +138,13 @@ class _MobileWorkspace extends StatelessWidget {
),
),
),
const HSpace(8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
FlowyText.semibold(
currentWorkspace.name,
fontSize: 16.0,
overflow: TextOverflow.ellipsis,
),
const HSpace(4.0),
const FlowySvg(FlowySvgs.list_dropdown_s),
],
),
FlowyText.regular(
userProfile.email.isNotEmpty
? userProfile.email
: userProfile.name,
overflow: TextOverflow.ellipsis,
fontSize: 12,
color: Theme.of(context).colorScheme.onSurface,
),
],
),
currentWorkspace.icon.isNotEmpty
? const HSpace(2)
: const HSpace(8),
FlowyText.semibold(
currentWorkspace.name,
fontSize: 16.0,
overflow: TextOverflow.ellipsis,
),
],
),
@ -179,7 +161,9 @@ class _MobileWorkspace extends StatelessWidget {
showDivider: false,
showHeader: true,
showDragHandle: true,
showCloseButton: true,
title: LocaleKeys.workspace_menuTitle.tr(),
backgroundColor: Theme.of(context).colorScheme.surface,
builder: (_) {
return BlocProvider.value(
value: context.read<UserWorkspaceBloc>(),

View File

@ -6,6 +6,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class MobileRecentSpace extends StatefulWidget {
const MobileRecentSpace({super.key});
@ -62,34 +63,36 @@ class _RecentViews extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scrollbar(
child: ListView.separated(
key: const PageStorageKey('recent_views_page_storage_key'),
padding: const EdgeInsets.symmetric(
horizontal: HomeSpaceViewSizes.mHorizontalPadding,
),
itemBuilder: (context, index) {
final sectionView = recentViews[index];
return Container(
padding: const EdgeInsets.symmetric(vertical: 24.0),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor,
width: 0.5,
return SlidableAutoCloseBehavior(
child: Scrollbar(
child: ListView.separated(
key: const PageStorageKey('recent_views_page_storage_key'),
padding: const EdgeInsets.symmetric(
horizontal: HomeSpaceViewSizes.mHorizontalPadding,
),
itemBuilder: (context, index) {
final sectionView = recentViews[index];
return Container(
padding: const EdgeInsets.symmetric(vertical: 24.0),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor,
width: 0.5,
),
),
),
),
child: MobileViewCard(
key: ValueKey(sectionView.item.id),
view: sectionView.item,
timestamp: sectionView.timestamp,
type: MobileViewCardType.recent,
),
);
},
separatorBuilder: (context, index) => const HSpace(8),
itemCount: recentViews.length,
child: MobileViewCard(
key: ValueKey(sectionView.item.id),
view: sectionView.item,
timestamp: sectionView.timestamp,
type: MobileViewCardType.recent,
),
);
},
separatorBuilder: (context, index) => const HSpace(8),
itemCount: recentViews.length,
),
),
);
}

View File

@ -1,9 +1,11 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart';
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_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:easy_localization/easy_localization.dart';
@ -69,6 +71,19 @@ class MobileSectionFolder extends StatelessWidget {
leftPadding: HomeSpaceViewSizes.leftPadding,
isFeedback: false,
onSelected: context.pushView,
endActionPane: (context) {
final view = context.read<ViewBloc>().state.view;
return buildEndActionPane(
context,
[
MobilePaneActionType.more,
if (view.layout == ViewLayoutPB.Document)
MobilePaneActionType.add,
],
spaceType: spaceType,
needSpace: false,
);
},
),
),
],

View File

@ -38,7 +38,7 @@ class _MobileSectionFolderHeaderState extends State<MobileSectionFolderHeader> {
widget.title,
fontSize: 16.0,
),
margin: const EdgeInsets.symmetric(vertical: 8),
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 2.0),
expandText: false,
iconPadding: 2,
mainAxisAlignment: MainAxisAlignment.start,

View File

@ -22,6 +22,7 @@ import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import 'package:string_validator/string_validator.dart';
@ -63,21 +64,33 @@ class MobileViewCard extends StatelessWidget {
],
child: BlocBuilder<RecentViewBloc, RecentViewState>(
builder: (context, state) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTapUp: (_) => context.pushView(view),
onLongPressUp: () => _showActionSheet(context),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: _buildDescription(context, state)),
const HSpace(20.0),
SizedBox(
width: 84,
height: 60,
child: _buildCover(context, state),
),
return Slidable(
endActionPane: buildEndActionPane(
context,
[
MobilePaneActionType.more,
context.watch<ViewBloc>().state.view.isFavorite
? MobilePaneActionType.removeFromFavorites
: MobilePaneActionType.addToFavorites,
],
cardType: type,
),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapUp: (_) => context.pushView(view),
onLongPressUp: () => _showActionSheet(context),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: _buildDescription(context, state)),
const HSpace(20.0),
SizedBox(
width: 84,
height: 60,
child: _buildCover(context, state),
),
],
),
),
);
},

View File

@ -1,6 +1,7 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/util/theme_extension.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
@ -27,7 +28,13 @@ class MobileWorkspaceMenu extends StatelessWidget {
@override
Widget build(BuildContext context) {
final List<Widget> children = [];
final List<Widget> children = [
_WorkspaceUserItem(userProfile: userProfile),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: Divider(height: 0.5),
),
];
for (var i = 0; i < workspaces.length; i++) {
final workspace = workspaces[i];
children.add(
@ -35,7 +42,7 @@ class MobileWorkspaceMenu extends StatelessWidget {
key: ValueKey(workspace.workspaceId),
userProfile: userProfile,
workspace: workspace,
showTopBorder: i == 0,
showTopBorder: false,
currentWorkspace: currentWorkspace,
onWorkspaceSelected: onWorkspaceSelected,
),
@ -47,6 +54,34 @@ class MobileWorkspaceMenu extends StatelessWidget {
}
}
class _WorkspaceUserItem extends StatelessWidget {
const _WorkspaceUserItem({required this.userProfile});
final UserProfilePB userProfile;
@override
Widget build(BuildContext context) {
final color = Theme.of(context).isLightMode
? const Color(0x99333333)
: const Color(0x99CCCCCC);
return FlowyOptionTile.text(
height: 32,
showTopBorder: false,
showBottomBorder: false,
content: Expanded(
child: Padding(
padding: const EdgeInsets.only(),
child: FlowyText(
userProfile.email,
fontSize: 14,
color: color,
),
),
),
);
}
}
class _WorkspaceMenuItem extends StatelessWidget {
const _WorkspaceMenuItem({
super.key,
@ -102,6 +137,7 @@ class _WorkspaceMenuItem extends StatelessWidget {
),
height: 60,
showTopBorder: showTopBorder,
showBottomBorder: false,
leftIcon: WorkspaceIcon(
enableEdit: false,
iconSize: 26,

View File

@ -9,6 +9,7 @@ class MobileSlideActionButton extends StatelessWidget {
required this.svg,
this.size = 32.0,
this.backgroundColor = Colors.transparent,
this.borderRadius = BorderRadius.zero,
required this.onPressed,
});
@ -16,15 +17,18 @@ class MobileSlideActionButton extends StatelessWidget {
final double size;
final Color backgroundColor;
final SlidableActionCallback onPressed;
final BorderRadius borderRadius;
@override
Widget build(BuildContext context) {
return CustomSlidableAction(
borderRadius: borderRadius,
backgroundColor: backgroundColor,
onPressed: (context) {
HapticFeedback.mediumImpact();
onPressed(context);
},
padding: EdgeInsets.zero,
child: FlowySvg(
svg,
size: Size.square(size),

View File

@ -1,16 +1,11 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item_add_button.dart';
import 'package:appflowy/workspace/application/favorite/favorite_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_ext.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -270,17 +265,6 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
),
];
// hover action
// ··· more action button
children.add(_buildViewMoreButton(context));
// only support add button for document layout
if (!widget.isFeedback && widget.view.layout == ViewLayoutPB.Document) {
// + button
children.add(_buildViewAddButton(context));
}
Widget child = InkWell(
borderRadius: BorderRadius.circular(4.0),
onTap: () => widget.onSelected(widget.view),
@ -345,86 +329,6 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
},
);
}
// + button
Widget _buildViewAddButton(BuildContext context) {
return MobileViewAddButton(
onPressed: () {
final title = widget.view.name;
showMobileBottomSheet(
context,
showHeader: true,
title: title,
showDragHandle: true,
showCloseButton: true,
useRootNavigator: true,
builder: (sheetContext) {
return AddNewPageWidgetBottomSheet(
view: widget.view,
onAction: (layout) {
Navigator.of(sheetContext).pop();
context.read<ViewBloc>().add(
ViewEvent.createView(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout,
section: widget.spaceType != FolderSpaceType.favorite
? widget.spaceType.toViewSectionPB
: null,
),
);
},
);
},
);
},
);
}
// + button
Widget _buildViewMoreButton(BuildContext context) {
return MobileViewMoreButton(onPressed: () => _showMoreActions(context));
}
Future<void> _showMoreActions(BuildContext context) async {
final viewBloc = context.read<ViewBloc>();
final favoriteBloc = context.read<FavoriteBloc>();
await showMobileBottomSheet(
context,
showHeader: true,
title: widget.view.name,
showDragHandle: true,
showCloseButton: true,
useRootNavigator: true,
builder: (context) {
return MultiBlocProvider(
providers: [
BlocProvider.value(value: viewBloc),
BlocProvider.value(value: favoriteBloc),
],
child: BlocBuilder<ViewBloc, ViewState>(
builder: (context, state) {
final isFavorite = state.view.isFavorite;
return MobileViewItemBottomSheet(
view: viewBloc.state.view,
actions: [
isFavorite
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
: MobileViewItemBottomSheetBodyAction.addToFavorites,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.rename,
MobileViewItemBottomSheetBodyAction.divider,
if (state.view.layout != ViewLayoutPB.Chat)
MobileViewItemBottomSheetBodyAction.duplicate,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.delete,
],
);
},
),
);
},
);
}
}
// workaround: we should use view.isEndPoint or something to check if the view can contain child views. But currently, we don't have that field.

View File

@ -189,11 +189,13 @@ class FavoriteMoreButton extends StatelessWidget {
margin: EdgeInsets.zero,
child: FlowyButton(
onTap: () {},
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 7.0),
margin: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 7.0),
leftIcon: const FlowySvg(
FlowySvgs.workspace_three_dots_s,
),
text: FlowyText.regular(LocaleKeys.button_more.tr()),
text: FlowyText.regular(
LocaleKeys.button_more.tr(),
),
),
);
}

View File

@ -88,7 +88,7 @@ class SidebarTopMenu extends StatelessWidget {
builder: (_, value, ___) => Opacity(
opacity: value ? 1 : 0,
child: Padding(
padding: const EdgeInsets.only(top: 12.0, right: 4.0),
padding: const EdgeInsets.only(top: 12.0, right: 6.0),
child: FlowyTooltip(
richMessage: textSpan,
child: Listener(

View File

@ -191,6 +191,7 @@ class _SidebarState extends State<_Sidebar> {
Timer? _scrollDebounce;
bool _isScrolling = false;
final _isHovered = ValueNotifier(false);
final _scrollOffset = ValueNotifier<double>(0);
@override
void initState() {
@ -203,6 +204,7 @@ class _SidebarState extends State<_Sidebar> {
_scrollDebounce?.cancel();
_scrollController.removeListener(_onScrollChanged);
_scrollController.dispose();
_scrollOffset.dispose();
_isHovered.dispose();
super.dispose();
}
@ -255,11 +257,20 @@ class _SidebarState extends State<_Sidebar> {
const SidebarNewPageButton(),
// scrollable document list
const VSpace(12.0),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: Divider(
color: Color(0x1E1F2329),
height: 0.5,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: ValueListenableBuilder(
valueListenable: _scrollOffset,
builder: (_, offset, child) {
return Opacity(
opacity: offset > 0 ? 1 : 0,
child: child,
);
},
child: const Divider(
color: Color(0x141F2329),
height: 0.5,
),
),
),
Expanded(
@ -281,7 +292,7 @@ class _SidebarState extends State<_Sidebar> {
Padding(
padding: menuHorizontalInset +
const EdgeInsets.symmetric(horizontal: 4.0),
child: const Divider(height: 1.0, color: Color(0x141F2329)),
child: const Divider(height: 0.5, color: Color(0x141F2329)),
),
const VSpace(8),
Padding(
@ -302,6 +313,8 @@ class _SidebarState extends State<_Sidebar> {
_scrollDebounce?.cancel();
_scrollDebounce =
Timer(const Duration(milliseconds: 300), _setScrollStopped);
_scrollOffset.value = _scrollController.offset;
}
void _setScrollStopped() {

View File

@ -17,6 +17,7 @@ class WorkspaceIcon extends StatefulWidget {
required this.onSelected,
this.borderRadius = 4,
this.emojiSize,
this.alignment,
});
final UserWorkspacePB workspace;
@ -26,6 +27,7 @@ class WorkspaceIcon extends StatefulWidget {
final double? emojiSize;
final void Function(EmojiPickerResult) onSelected;
final double borderRadius;
final Alignment? alignment;
@override
State<WorkspaceIcon> createState() => _WorkspaceIconState();
@ -38,8 +40,8 @@ class _WorkspaceIconState extends State<WorkspaceIcon> {
Widget build(BuildContext context) {
Widget child = widget.workspace.icon.isNotEmpty
? Container(
width: widget.iconSize,
alignment: Alignment.center,
width: widget.emojiSize ?? widget.iconSize,
alignment: widget.alignment ?? Alignment.center,
child: FlowyText.emoji(
widget.workspace.icon,
fontSize: widget.emojiSize ?? widget.iconSize,

View File

@ -50,7 +50,7 @@ class _SidebarWorkspaceState extends State<SidebarWorkspace> {
UserSettingButton(userProfile: widget.userProfile),
const HSpace(8.0),
const NotificationButton(),
const HSpace(10.0),
const HSpace(12.0),
],
);
},

View File

@ -434,7 +434,6 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
child: FlowyText.regular(
widget.view.name,
overflow: TextOverflow.ellipsis,
lineHeight: 1.1,
),
),
];
@ -466,7 +465,6 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
child: Padding(
padding: EdgeInsets.only(left: widget.level * widget.leftPadding),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
),
),
@ -499,7 +497,6 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
? FlowyText.emoji(
widget.view.icon.value,
fontSize: 16.0,
lineHeight: 1.4,
)
: Opacity(opacity: 0.6, child: widget.view.defaultIcon());

View File

@ -3,8 +3,6 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
const String _emojiFontFamily = 'noto color emoji';
class FlowyText extends StatelessWidget {
final String text;
final TextOverflow? overflow;
@ -138,16 +136,13 @@ class FlowyText extends StatelessWidget {
var fontFamily = this.fontFamily;
var fallbackFontFamily = this.fallbackFontFamily;
if (isEmoji && (Platform.isLinux || Platform.isAndroid)) {
var fontSize =
this.fontSize ?? Theme.of(context).textTheme.bodyMedium!.fontSize!;
if (isEmoji && _useNotoColorEmoji) {
fontFamily = _loadEmojiFontFamilyIfNeeded();
if (fontFamily != null && fallbackFontFamily == null) {
fallbackFontFamily = [fontFamily];
}
}
var fontSize =
this.fontSize ?? Theme.of(context).textTheme.bodyMedium!.fontSize!;
if (Platform.isLinux && fontFamily == _emojiFontFamily) {
fontSize = fontSize * 0.8;
}
@ -175,6 +170,14 @@ class FlowyText extends StatelessWidget {
textAlign: textAlign,
overflow: overflow ?? TextOverflow.clip,
style: textStyle,
strutStyle: Platform.isMacOS
? StrutStyle.fromTextStyle(
textStyle,
forceStrutHeight: true,
leadingDistribution: TextLeadingDistribution.even,
height: 1.1,
)
: null,
);
}
@ -189,10 +192,13 @@ class FlowyText extends StatelessWidget {
}
String? _loadEmojiFontFamilyIfNeeded() {
if (Platform.isLinux || Platform.isAndroid) {
if (_useNotoColorEmoji) {
return GoogleFonts.notoColorEmoji().fontFamily;
}
return null;
}
bool get _useNotoColorEmoji =>
Platform.isLinux || Platform.isAndroid || Platform.isWindows;
}

View File

@ -25,6 +25,7 @@ class FlowyTooltip extends StatelessWidget {
final isLightMode = Theme.of(context).brightness == Brightness.light;
return Tooltip(
margin: margin,
verticalOffset: 16.0,
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
decoration: BoxDecoration(
color: isLightMode ? const Color(0xE5171717) : const Color(0xE5E5E5E5),