feat: enable shared section on mobile (#8020)

* feat: enable shared section on mobile

* feat: update mobile view item

* feat: shared with me section on mobile
This commit is contained in:
Lucas 2025-06-04 10:59:16 +08:00 committed by GitHub
parent 5598689dba
commit 11200a5b3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 247 additions and 15 deletions

View File

@ -0,0 +1,91 @@
import 'package:appflowy/features/shared_section/data/repositories/rust_shared_pages_repository_impl.dart';
import 'package:appflowy/features/shared_section/logic/shared_section_bloc.dart';
import 'package:appflowy/features/shared_section/presentation/widgets/m_shared_page_list.dart';
import 'package:appflowy/features/shared_section/presentation/widgets/m_shared_section_header.dart';
import 'package:appflowy/features/shared_section/presentation/widgets/refresh_button.dart';
import 'package:appflowy/features/shared_section/presentation/widgets/shared_section_error.dart';
import 'package:appflowy/features/shared_section/presentation/widgets/shared_section_loading.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/shared/icon_emoji_picker/tab.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MSharedSection extends StatelessWidget {
const MSharedSection({
super.key,
required this.workspaceId,
});
final String workspaceId;
@override
Widget build(BuildContext context) {
final repository = RustSharePagesRepositoryImpl();
return BlocProvider(
create: (_) => SharedSectionBloc(
workspaceId: workspaceId,
repository: repository,
enablePolling: true,
)..add(const SharedSectionInitEvent()),
child: BlocBuilder<SharedSectionBloc, SharedSectionState>(
builder: (context, state) {
if (state.isLoading) {
return const SharedSectionLoading();
}
if (state.errorMessage.isNotEmpty) {
return SharedSectionError(errorMessage: state.errorMessage);
}
// hide the shared section if there are no shared pages
if (state.sharedPages.isEmpty) {
return const SizedBox.shrink();
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const VSpace(HomeSpaceViewSizes.mVerticalPadding),
// Shared header
MSharedSectionHeader(),
Padding(
padding: const EdgeInsets.only(
left: HomeSpaceViewSizes.mHorizontalPadding,
),
child: MSharedPageList(
sharedPages: state.sharedPages,
onSelected: (view) {
context.pushView(
view,
tabs: [
PickerTabType.emoji,
PickerTabType.icon,
PickerTabType.custom,
].map((e) => e.name).toList(),
);
},
),
),
// Refresh button, for debugging only
if (kDebugMode)
RefreshSharedSectionButton(
onTap: () {
context.read<SharedSectionBloc>().add(
const SharedSectionEvent.refresh(),
);
},
),
],
);
},
),
);
}
}

View File

@ -1,7 +1,7 @@
import 'package:appflowy/features/shared_section/data/repositories/rust_shared_pages_repository_impl.dart';
import 'package:appflowy/features/shared_section/data/repositories/local_shared_pages_repository_impl.dart';
import 'package:appflowy/features/shared_section/logic/shared_section_bloc.dart';
import 'package:appflowy/features/shared_section/presentation/widgets/refresh_button.dart';
import 'package:appflowy/features/shared_section/presentation/widgets/shared_pages_list.dart';
import 'package:appflowy/features/shared_section/presentation/widgets/shared_page_list.dart';
import 'package:appflowy/features/shared_section/presentation/widgets/shared_section_error.dart';
import 'package:appflowy/features/shared_section/presentation/widgets/shared_section_header.dart';
import 'package:appflowy/features/shared_section/presentation/widgets/shared_section_loading.dart';
@ -30,7 +30,8 @@ class SharedSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
final repository = RustSharePagesRepositoryImpl();
// final repository = RustSharePagesRepositoryImpl();
final repository = LocalSharedPagesRepositoryImpl();
return BlocProvider(
create: (_) => SharedSectionBloc(
@ -68,7 +69,7 @@ class SharedSection extends StatelessWidget {
// Shared pages list
if (state.isExpanded)
SharedPagesList(
SharedPageList(
sharedPages: state.sharedPages,
onSetEditing: (context, value) {
context.read<ViewBloc>().add(ViewEvent.setIsEditing(value));

View File

@ -0,0 +1,38 @@
import 'package:appflowy/features/shared_section/models/shared_page.dart';
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:flutter/material.dart';
/// Shared pages on mobile
class MSharedPageList extends StatelessWidget {
const MSharedPageList({
super.key,
required this.sharedPages,
required this.onSelected,
});
final SharedPages sharedPages;
final ViewItemOnSelected onSelected;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: sharedPages.map((sharedPage) {
final view = sharedPage.view;
return MobileViewItem(
key: ValueKey(view.id),
spaceType: FolderSpaceType.public,
isFirstChild: view.id == sharedPages.first.view.id,
view: view,
level: 0,
isDraggable: false, // disable draggable for shared pages
leftPadding: HomeSpaceViewSizes.leftPadding,
isFeedback: false,
onSelected: onSelected,
);
}).toList(),
);
}
}

View File

@ -0,0 +1,38 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
class MSharedSectionHeader extends StatelessWidget {
const MSharedSectionHeader({
super.key,
});
@override
Widget build(BuildContext context) {
final theme = AppFlowyTheme.of(context);
return SizedBox(
height: 48,
child: Row(
children: [
const HSpace(HomeSpaceViewSizes.mHorizontalPadding),
FlowySvg(
FlowySvgs.shared_with_me_m,
color: theme.badgeColorScheme.color13Thick2,
),
const HSpace(10.0),
FlowyText.medium(
LocaleKeys.shareSection_shared.tr(),
lineHeight: 1.15,
fontSize: 16.0,
),
const HSpace(HomeSpaceViewSizes.mHorizontalPadding),
],
),
);
}
}

View File

@ -9,8 +9,9 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class SharedPagesList extends StatelessWidget {
const SharedPagesList({
/// Shared pages on desktop
class SharedPageList extends StatelessWidget {
const SharedPageList({
super.key,
required this.sharedPages,
required this.onAction,

View File

@ -0,0 +1,41 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
class SharedSectionEmpty extends StatelessWidget {
const SharedSectionEmpty({super.key});
@override
Widget build(BuildContext context) {
final theme = AppFlowyTheme.of(context);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FlowySvg(
FlowySvgs.empty_shared_section_m,
color: theme.iconColorScheme.tertiary,
),
const VSpace(12),
Text(
'Nothing shared with you',
style: theme.textStyle.heading3.enhanced(
color: theme.textColorScheme.secondary,
),
textAlign: TextAlign.center,
),
const VSpace(4),
Text(
'Pages shared with you will show here',
style: theme.textStyle.heading4.standard(
color: theme.textColorScheme.tertiary,
),
textAlign: TextAlign.center,
),
const VSpace(kBottomNavigationBarHeight + 60.0),
],
),
);
}
}

View File

@ -239,7 +239,7 @@ class _HomePageState extends State<_HomePage> {
),
),
],
child: MobileSpaceTab(
child: MobileHomePageTab(
userProfile: widget.userProfile,
),
),

View File

@ -1,3 +1,4 @@
import 'package:appflowy/features/shared_section/presentation/m_shared_section.dart';
import 'package:appflowy/features/workspace/logic/workspace_bloc.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/home/favorite_folder/favorite_space.dart';
@ -23,8 +24,8 @@ import 'ai_bubble_button.dart';
final ValueNotifier<int> mobileCreateNewAIChatNotifier = ValueNotifier(0);
class MobileSpaceTab extends StatefulWidget {
const MobileSpaceTab({
class MobileHomePageTab extends StatefulWidget {
const MobileHomePageTab({
super.key,
required this.userProfile,
});
@ -32,10 +33,10 @@ class MobileSpaceTab extends StatefulWidget {
final UserProfilePB userProfile;
@override
State<MobileSpaceTab> createState() => _MobileSpaceTabState();
State<MobileHomePageTab> createState() => _MobileHomePageTabState();
}
class _MobileSpaceTabState extends State<MobileSpaceTab>
class _MobileHomePageTabState extends State<MobileHomePageTab>
with SingleTickerProviderStateMixin {
TabController? tabController;
@ -178,6 +179,18 @@ class _MobileSpaceTabState extends State<MobileSpaceTab>
);
case MobileSpaceTabType.favorites:
return MobileFavoriteSpace(userProfile: widget.userProfile);
case MobileSpaceTabType.shared:
final workspaceId = context
.read<UserWorkspaceBloc>()
.state
.currentWorkspace
?.workspaceId;
if (workspaceId == null) {
return const SizedBox.shrink();
}
return MSharedSection(
workspaceId: workspaceId,
);
}
}).toList();
}

View File

@ -15,7 +15,8 @@ enum MobileSpaceTabType {
// DO NOT CHANGE THE ORDER
spaces,
recent,
favorites;
favorites,
shared;
String get tr {
switch (this) {
@ -25,6 +26,8 @@ enum MobileSpaceTabType {
return LocaleKeys.sideBar_Spaces.tr();
case MobileSpaceTabType.favorites:
return LocaleKeys.sideBar_favoriteSpace.tr();
case MobileSpaceTabType.shared:
return 'Shared';
}
}
}
@ -89,6 +92,9 @@ class SpaceOrderBloc extends Bloc<SpaceOrderEvent, SpaceOrderState> {
if (order.isEmpty) {
return MobileSpaceTabType.values;
}
if (!order.contains(MobileSpaceTabType.shared.index)) {
order.add(MobileSpaceTabType.shared.index);
}
return order
.map((e) => MobileSpaceTabType.values[e])
.cast<MobileSpaceTabType>()

View File

@ -1,7 +1,7 @@
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/features/share_tab/data/models/share_access_level.dart';
import 'package:appflowy/features/shared_section/models/shared_page.dart';
import 'package:appflowy/features/shared_section/presentation/widgets/shared_pages_list.dart';
import 'package:appflowy/features/shared_section/presentation/widgets/shared_page_list.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter/material.dart';
@ -47,7 +47,7 @@ void main() {
await tester.pumpWidget(
WidgetTestWrapper(
child: SingleChildScrollView(
child: SharedPagesList(
child: SharedPageList(
sharedPages: sharedPages,
onAction: (action, view, data) {},
onSelected: (context, view) {},
@ -59,7 +59,7 @@ void main() {
);
expect(find.text('Page 1'), findsOneWidget);
expect(find.text('Page 2'), findsOneWidget);
expect(find.byType(SharedPagesList), findsOneWidget);
expect(find.byType(SharedPageList), findsOneWidget);
});
});
}

View File

@ -0,0 +1,3 @@
<svg width="45" height="44" viewBox="0 0 45 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M29.1316 17.2632C32.271 17.2632 34.8158 14.7182 34.8158 11.5789C34.8158 8.43965 32.271 5.89474 29.1316 5.89474M34.8158 26.7368C38.1395 27.4657 40.5 29.3116 40.5 31.4737C40.5 33.4239 38.5793 35.1171 35.7632 35.965M10.1842 11.5789C10.1842 13.589 10.9827 15.5167 12.404 16.9381C13.8254 18.3594 15.7531 19.1579 17.7632 19.1579C19.7732 19.1579 21.701 18.3594 23.1223 16.9381C24.5436 15.5167 25.3421 13.589 25.3421 11.5789C25.3421 9.56889 24.5436 7.64115 23.1223 6.21982C21.701 4.79849 19.7732 4 17.7632 4C15.7531 4 13.8254 4.79849 12.404 6.21982C10.9827 7.64115 10.1842 9.56889 10.1842 11.5789ZM4.5 32.4211C4.5 34.4311 5.89736 36.3589 8.38469 37.7802C10.872 39.2015 14.2455 40 17.7632 40C21.2808 40 24.6543 39.2015 27.1416 37.7802C29.629 36.3589 31.0263 34.4311 31.0263 32.4211C31.0263 30.411 29.629 28.4833 27.1416 27.0619C24.6543 25.6406 21.2808 24.8421 17.7632 24.8421C14.2455 24.8421 10.872 25.6406 8.38469 27.0619C5.89736 28.4833 4.5 30.411 4.5 32.4211Z" stroke="#B5BBD3" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB