mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-12-24 13:44:19 +00:00
feat: support share page and block (#6431)
* feat: support share page * feat: support copy share link * chore: replace share icon * chore: update translations * chore: optimize code * test: add share link test * feat: support copy block link * test: add copy link to block test * chore: refactor share code * fix: doc bloc not found issue
This commit is contained in:
parent
c1cf58b99e
commit
97913c390b
@ -1,15 +1,14 @@
|
||||
import 'document/document_delete_block_test.dart' as document_delete_block_test;
|
||||
import 'document/document_drag_block_test.dart' as document_drag_block_test;
|
||||
import 'document/document_option_actions_test.dart'
|
||||
as document_option_actions_test;
|
||||
import 'sidebar/sidebar_move_page_test.dart' as sidebar_move_page_test;
|
||||
import 'uncategorized/anon_user_continue_test.dart' as anon_user_continue_test;
|
||||
import 'uncategorized/appflowy_cloud_auth_test.dart'
|
||||
as appflowy_cloud_auth_test;
|
||||
import 'uncategorized/empty_test.dart' as preset_af_cloud_env_test;
|
||||
import 'uncategorized/user_setting_sync_test.dart' as user_sync_test;
|
||||
import 'workspace/change_name_and_icon_test.dart'
|
||||
as change_workspace_name_and_icon_test;
|
||||
import 'workspace/collaborative_workspace_test.dart'
|
||||
as collaboration_workspace_test;
|
||||
import 'workspace/workspace_settings_test.dart' as workspace_settings_test;
|
||||
import 'workspace/workspace_test_runner.dart' as workspace_test_runner;
|
||||
|
||||
Future<void> main() async {
|
||||
preset_af_cloud_env_test.main();
|
||||
@ -18,12 +17,12 @@ Future<void> main() async {
|
||||
anon_user_continue_test.main();
|
||||
|
||||
// workspace
|
||||
collaboration_workspace_test.main();
|
||||
change_workspace_name_and_icon_test.main();
|
||||
workspace_settings_test.main();
|
||||
workspace_test_runner.startTesting();
|
||||
|
||||
// document
|
||||
document_option_actions_test.main();
|
||||
document_drag_block_test.main();
|
||||
document_delete_block_test.main();
|
||||
|
||||
// sidebar
|
||||
sidebar_move_page_test.main();
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@ -9,7 +12,7 @@ import '../../../shared/util.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document drag block: ', () {
|
||||
group('document option actions:', () {
|
||||
testWidgets('drag block to the top', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
@ -63,5 +66,36 @@ void main() {
|
||||
final afterMoveBlock = tester.editor.getNodeAtPath([9, 0]);
|
||||
expect(afterMoveBlock.delta, beforeMoveBlock.delta);
|
||||
});
|
||||
|
||||
testWidgets('copy block link', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// hover and click on the option menu button beside the block component.
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
|
||||
// click the copy link to block option
|
||||
await tester.tap(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_optionAction_copyLinkToBlock.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// check the clipboard
|
||||
final content = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(
|
||||
content?.text,
|
||||
matches(
|
||||
r'^https:\/\/appflowy\.com\/app\/[a-f0-9-]{36}\/[a-f0-9-]{36}\?blockId=[A-Za-z0-9_-]+$',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -0,0 +1,101 @@
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document option actions:', () {
|
||||
testWidgets('drag block to the top', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// before move
|
||||
final beforeMoveBlock = tester.editor.getNodeAtPath([1]);
|
||||
|
||||
// move the desktop guide to the top, above the getting started
|
||||
await tester.editor.dragBlock(
|
||||
[1],
|
||||
const Offset(20, -80),
|
||||
);
|
||||
|
||||
// wait for the move animation to complete
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// check if the block is moved to the top
|
||||
final afterMoveBlock = tester.editor.getNodeAtPath([0]);
|
||||
expect(afterMoveBlock.delta, beforeMoveBlock.delta);
|
||||
});
|
||||
|
||||
testWidgets('drag block to other block\'s child', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// before move
|
||||
final beforeMoveBlock = tester.editor.getNodeAtPath([10]);
|
||||
|
||||
// move the checkbox to the child of the block at path [9]
|
||||
await tester.editor.dragBlock(
|
||||
[10],
|
||||
const Offset(80, -30),
|
||||
);
|
||||
|
||||
// wait for the move animation to complete
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// check if the block is moved to the child of the block at path [9]
|
||||
final afterMoveBlock = tester.editor.getNodeAtPath([9, 0]);
|
||||
expect(afterMoveBlock.delta, beforeMoveBlock.delta);
|
||||
});
|
||||
|
||||
testWidgets('copy block link', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// hover and click on the option menu button beside the block component.
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
|
||||
// click the copy link to block option
|
||||
await tester.tap(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_optionAction_copyLinkToBlock.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// check the clipboard
|
||||
final content = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(
|
||||
content?.text,
|
||||
matches(
|
||||
r'^https:\/\/appflowy\.com\/app\/[a-f0-9-]{36}\/[a-f0-9-]{36}\?blockId=[A-Za-z0-9_-]+$',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/shared/share/constants.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_menu.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
import '../../../shared/workspace.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Share menu:', () {
|
||||
testWidgets('share tab', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
const pageName = 'Document';
|
||||
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
pageName: pageName,
|
||||
);
|
||||
|
||||
// click the share button
|
||||
await tester.tapShareButton();
|
||||
|
||||
// expect the share menu is shown
|
||||
final shareMenu = find.byType(ShareMenu);
|
||||
expect(shareMenu, findsOneWidget);
|
||||
|
||||
// click the copy link button
|
||||
final copyLinkButton = find.textContaining(
|
||||
LocaleKeys.button_copyLink.tr(),
|
||||
);
|
||||
await tester.tapButton(copyLinkButton);
|
||||
|
||||
// read the clipboard content
|
||||
final clipboardContent = await getIt<ClipboardService>().getData();
|
||||
final plainText = clipboardContent.plainText;
|
||||
expect(
|
||||
plainText,
|
||||
startsWith(ShareConstants.shareBaseUrl),
|
||||
);
|
||||
|
||||
final shareValues = plainText!
|
||||
.replaceAll('${ShareConstants.shareBaseUrl}/', '')
|
||||
.split('/');
|
||||
final workspaceId = shareValues[0];
|
||||
expect(workspaceId, isNotEmpty);
|
||||
final pageId = shareValues[1];
|
||||
expect(pageId, isNotEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'change_name_and_icon_test.dart' as change_name_and_icon_test;
|
||||
import 'collaborative_workspace_test.dart' as collaborative_workspace_test;
|
||||
import 'share_menu_test.dart' as share_menu_test;
|
||||
import 'workspace_settings_test.dart' as workspace_settings_test;
|
||||
|
||||
void startTesting() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
workspace_settings_test.main();
|
||||
share_menu_test.main();
|
||||
collaborative_workspace_test.main();
|
||||
change_name_and_icon_test.main();
|
||||
}
|
||||
@ -1,6 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
@ -16,6 +13,8 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
@ -29,7 +28,13 @@ Map<String, BlockComponentBuilder> getEditorBuilderMap({
|
||||
String Function(Node)? placeholderText,
|
||||
EdgeInsets? customHeadingPadding,
|
||||
}) {
|
||||
final standardActions = [OptionAction.delete, OptionAction.duplicate];
|
||||
final standardActions = [
|
||||
OptionAction.delete,
|
||||
OptionAction.duplicate,
|
||||
// filter out the copy link to block option if in local mode
|
||||
if (context.read<DocumentBloc?>()?.isLocalMode != true)
|
||||
OptionAction.copyLinkToBlock,
|
||||
];
|
||||
|
||||
final calloutBGColor = AFThemeExtension.of(context).calloutBGColor;
|
||||
final configuration = BlockComponentConfiguration(
|
||||
|
||||
@ -1,19 +1,25 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart';
|
||||
import 'package:appflowy/plugins/shared/share/constants.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:toastification/toastification.dart';
|
||||
|
||||
import 'drag_to_reorder/draggable_option_button.dart';
|
||||
|
||||
@ -121,6 +127,9 @@ class _BlockOptionButtonState extends State<BlockOptionButton> {
|
||||
case OptionAction.moveDown:
|
||||
transaction.moveNode(node.path.next.next, node);
|
||||
break;
|
||||
case OptionAction.copyLinkToBlock:
|
||||
await _copyLinkToBlock(context, node);
|
||||
break;
|
||||
case OptionAction.align:
|
||||
case OptionAction.color:
|
||||
case OptionAction.divider:
|
||||
@ -234,6 +243,44 @@ class _BlockOptionButtonState extends State<BlockOptionButton> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _copyLinkToBlock(BuildContext context, Node node) async {
|
||||
final viewId = context.read<DocumentBloc>().documentId;
|
||||
|
||||
final workspace = await FolderEventReadCurrentWorkspace().send();
|
||||
final workspaceId = workspace.fold(
|
||||
(l) => l.id,
|
||||
(r) => '',
|
||||
);
|
||||
|
||||
if (workspaceId.isEmpty || viewId.isEmpty) {
|
||||
Log.error('Failed to get workspace id: $workspaceId or view id: $viewId');
|
||||
if (context.mounted) {
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.shareAction_copyLinkToBlockFailed.tr(),
|
||||
type: ToastificationType.error,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final link = ShareConstants.buildShareUrl(
|
||||
workspaceId: workspaceId,
|
||||
viewId: viewId,
|
||||
blockId: node.id,
|
||||
);
|
||||
await getIt<ClipboardService>().setData(
|
||||
ClipboardServiceData(plainText: link),
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.shareAction_copyLinkToBlockSuccess.tr(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles duplicating a SubPage.
|
||||
///
|
||||
/// If the duplication fails for any reason, this method will return false, and inserting
|
||||
|
||||
@ -18,6 +18,7 @@ enum OptionAction {
|
||||
turnInto,
|
||||
moveUp,
|
||||
moveDown,
|
||||
copyLinkToBlock,
|
||||
|
||||
/// callout background color
|
||||
color,
|
||||
@ -28,7 +29,7 @@ enum OptionAction {
|
||||
FlowySvgData get svg {
|
||||
switch (this) {
|
||||
case OptionAction.delete:
|
||||
return FlowySvgs.delete_s;
|
||||
return FlowySvgs.trash_s;
|
||||
case OptionAction.duplicate:
|
||||
return FlowySvgs.copy_s;
|
||||
case OptionAction.turnInto:
|
||||
@ -45,6 +46,8 @@ enum OptionAction {
|
||||
return FlowySvgs.m_aa_bulleted_list_s;
|
||||
case OptionAction.depth:
|
||||
return FlowySvgs.tag_s;
|
||||
case OptionAction.copyLinkToBlock:
|
||||
return FlowySvgs.share_tab_copy_s;
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,6 +69,8 @@ enum OptionAction {
|
||||
return LocaleKeys.document_plugins_optionAction_align.tr();
|
||||
case OptionAction.depth:
|
||||
return LocaleKeys.document_plugins_optionAction_depth.tr();
|
||||
case OptionAction.copyLinkToBlock:
|
||||
return LocaleKeys.document_plugins_optionAction_copyLinkToBlock.tr();
|
||||
case OptionAction.divider:
|
||||
throw UnsupportedError('Divider does not have description');
|
||||
}
|
||||
@ -142,9 +147,12 @@ enum OptionDepthType {
|
||||
class DividerOptionAction extends CustomActionCell {
|
||||
@override
|
||||
Widget buildWithContext(BuildContext context, PopoverController controller) {
|
||||
return const Divider(
|
||||
height: 1.0,
|
||||
thickness: 1.0,
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Divider(
|
||||
height: 1.0,
|
||||
thickness: 1.0,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
class ShareConstants {
|
||||
static const String publishBaseUrl = 'https://appflowy.com';
|
||||
static const String shareBaseUrl = 'https://appflowy.com/app';
|
||||
|
||||
static String buildPublishUrl({
|
||||
required String nameSpace,
|
||||
required String publishName,
|
||||
}) {
|
||||
return '$publishBaseUrl/$nameSpace/$publishName';
|
||||
}
|
||||
|
||||
static String buildShareUrl({
|
||||
required String workspaceId,
|
||||
required String viewId,
|
||||
String? blockId,
|
||||
}) {
|
||||
final url = '$shareBaseUrl/$workspaceId/$viewId';
|
||||
if (blockId == null || blockId.isEmpty) {
|
||||
return url;
|
||||
}
|
||||
return '$url?blockId=$blockId';
|
||||
}
|
||||
}
|
||||
@ -13,9 +13,9 @@ import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'share_bloc.freezed.dart';
|
||||
import 'constants.dart';
|
||||
|
||||
const _url = 'https://appflowy.com';
|
||||
part 'share_bloc.freezed.dart';
|
||||
|
||||
class ShareBloc extends Bloc<ShareEvent, ShareState> {
|
||||
ShareBloc({
|
||||
@ -27,7 +27,7 @@ class ShareBloc extends Bloc<ShareEvent, ShareState> {
|
||||
viewListener = ViewListener(viewId: view.id)
|
||||
..start(
|
||||
onViewUpdated: (value) {
|
||||
add(ShareEvent.updateViewName(value.name));
|
||||
add(ShareEvent.updateViewName(value.name, value.id));
|
||||
},
|
||||
onViewMoveToTrash: (p0) {
|
||||
add(const ShareEvent.setPublishStatus(false));
|
||||
@ -70,7 +70,10 @@ class ShareBloc extends Bloc<ShareEvent, ShareState> {
|
||||
isPublished: true,
|
||||
publishResult: FlowySuccess(null),
|
||||
unpublishResult: null,
|
||||
url: '$_url/${result.namespace}/$publishName',
|
||||
url: ShareConstants.buildPublishUrl(
|
||||
nameSpace: result.namespace,
|
||||
publishName: publishName,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -113,8 +116,8 @@ class ShareBloc extends Bloc<ShareEvent, ShareState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
updateViewName: (viewName) async {
|
||||
emit(state.copyWith(viewName: viewName));
|
||||
updateViewName: (viewName, viewId) async {
|
||||
emit(state.copyWith(viewName: viewName, viewId: viewId));
|
||||
},
|
||||
setPublishStatus: (isPublished) {
|
||||
emit(
|
||||
@ -131,13 +134,23 @@ class ShareBloc extends Bloc<ShareEvent, ShareState> {
|
||||
(v) => v.authenticator == AuthenticatorPB.AppFlowyCloud,
|
||||
(p) => false,
|
||||
);
|
||||
String workspaceId = state.workspaceId;
|
||||
if (workspaceId.isEmpty) {
|
||||
workspaceId = await UserBackendService.getCurrentWorkspace()
|
||||
.fold((s) => s.id, (f) => '');
|
||||
}
|
||||
publishInfo.fold((s) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isPublished: true,
|
||||
url: '$_url/${s.namespace}/${s.publishName}',
|
||||
url: ShareConstants.buildPublishUrl(
|
||||
nameSpace: s.namespace,
|
||||
publishName: s.publishName,
|
||||
),
|
||||
viewName: view.name,
|
||||
enablePublish: enablePublish,
|
||||
workspaceId: workspaceId,
|
||||
viewId: view.id,
|
||||
),
|
||||
);
|
||||
}, (f) {
|
||||
@ -147,6 +160,8 @@ class ShareBloc extends Bloc<ShareEvent, ShareState> {
|
||||
url: '',
|
||||
viewName: view.name,
|
||||
enablePublish: enablePublish,
|
||||
workspaceId: workspaceId,
|
||||
viewId: view.id,
|
||||
),
|
||||
);
|
||||
});
|
||||
@ -261,7 +276,8 @@ class ShareEvent with _$ShareEvent {
|
||||
List<String> selectedViewIds,
|
||||
) = _Publish;
|
||||
const factory ShareEvent.unPublish() = _UnPublish;
|
||||
const factory ShareEvent.updateViewName(String name) = _UpdateViewName;
|
||||
const factory ShareEvent.updateViewName(String name, String viewId) =
|
||||
_UpdateViewName;
|
||||
const factory ShareEvent.updatePublishStatus() = _UpdatePublishStatus;
|
||||
const factory ShareEvent.setPublishStatus(bool isPublished) =
|
||||
_SetPublishStatus;
|
||||
@ -278,6 +294,8 @@ class ShareState with _$ShareState {
|
||||
FlowyResult<ShareType, FlowyError>? exportResult,
|
||||
FlowyResult<void, FlowyError>? publishResult,
|
||||
FlowyResult<void, FlowyError>? unpublishResult,
|
||||
required String viewId,
|
||||
required String workspaceId,
|
||||
}) = _ShareState;
|
||||
|
||||
factory ShareState.initial() => const ShareState(
|
||||
@ -286,5 +304,7 @@ class ShareState with _$ShareState {
|
||||
enablePublish: true,
|
||||
url: '',
|
||||
viewName: '',
|
||||
viewId: '',
|
||||
workspaceId: '',
|
||||
);
|
||||
}
|
||||
|
||||
@ -46,7 +46,11 @@ class ShareButton extends StatelessWidget {
|
||||
child: BlocBuilder<ShareBloc, ShareState>(
|
||||
builder: (context, state) {
|
||||
final tabs = [
|
||||
if (state.enablePublish) ShareMenuTab.publish,
|
||||
if (state.enablePublish) ...[
|
||||
// share the same permission with publish
|
||||
ShareMenuTab.share,
|
||||
ShareMenuTab.publish,
|
||||
],
|
||||
ShareMenuTab.exportAs,
|
||||
];
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/tab/_round_underline_tab_indicator.dart';
|
||||
import 'package:appflowy/plugins/shared/share/export_tab.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_bloc.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_tab.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -121,10 +122,8 @@ class _ShareMenuState extends State<ShareMenu>
|
||||
return const PublishTab();
|
||||
case ShareMenuTab.exportAs:
|
||||
return const ExportTab();
|
||||
default:
|
||||
return const Center(
|
||||
child: FlowyText('🏡 under construction'),
|
||||
);
|
||||
case ShareMenuTab.share:
|
||||
return const ShareTab();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,124 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_bloc.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.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 'constants.dart';
|
||||
|
||||
class ShareTab extends StatelessWidget {
|
||||
const ShareTab({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
VSpace(18),
|
||||
_ShareTabHeader(),
|
||||
VSpace(2),
|
||||
_ShareTabDescription(),
|
||||
VSpace(14),
|
||||
_ShareTabContent(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ShareTabHeader extends StatelessWidget {
|
||||
const _ShareTabHeader();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
const FlowySvg(FlowySvgs.share_tab_icon_s),
|
||||
const HSpace(6),
|
||||
FlowyText.medium(
|
||||
LocaleKeys.shareAction_shareTabTitle.tr(),
|
||||
figmaLineHeight: 18.0,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ShareTabDescription extends StatelessWidget {
|
||||
const _ShareTabDescription();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2.0),
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.shareAction_shareTabDescription.tr(),
|
||||
fontSize: 13.0,
|
||||
figmaLineHeight: 18.0,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ShareTabContent extends StatelessWidget {
|
||||
const _ShareTabContent();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ShareBloc, ShareState>(
|
||||
builder: (context, state) {
|
||||
final shareUrl = ShareConstants.buildShareUrl(
|
||||
workspaceId: state.workspaceId,
|
||||
viewId: state.viewId,
|
||||
);
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: FlowyTextField(
|
||||
text: shareUrl, // todo: add workspace id + view id
|
||||
readOnly: true,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
const HSpace(8.0),
|
||||
PrimaryRoundedButton(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 9.0,
|
||||
horizontal: 14.0,
|
||||
),
|
||||
text: LocaleKeys.button_copyLink.tr(),
|
||||
figmaLineHeight: 18.0,
|
||||
leftIcon: FlowySvg(
|
||||
FlowySvgs.share_tab_copy_s,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
onTap: () => _copy(context, shareUrl),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _copy(BuildContext context, String url) {
|
||||
getIt<ClipboardService>().setData(
|
||||
ClipboardServiceData(plainText: url),
|
||||
);
|
||||
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.grid_url_copy.tr(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,7 @@ class PrimaryRoundedButton extends StatelessWidget {
|
||||
this.useIntrinsicWidth = true,
|
||||
this.lineHeight,
|
||||
this.figmaLineHeight,
|
||||
this.leftIcon,
|
||||
});
|
||||
|
||||
final String text;
|
||||
@ -31,11 +32,13 @@ class PrimaryRoundedButton extends StatelessWidget {
|
||||
final bool useIntrinsicWidth;
|
||||
final double? lineHeight;
|
||||
final double? figmaLineHeight;
|
||||
final Widget? leftIcon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyButton(
|
||||
useIntrinsicWidth: useIntrinsicWidth,
|
||||
leftIcon: leftIcon,
|
||||
text: FlowyText(
|
||||
text,
|
||||
fontSize: fontSize ?? 14.0,
|
||||
|
||||
5
frontend/resources/flowy_icons/16x/share_tab_copy.svg
Normal file
5
frontend/resources/flowy_icons/16x/share_tab_copy.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.07617 4.30773L8.42791 2.95589C9.70243 1.68137 11.7688 1.68137 13.0434 2.95589C14.3179 4.23042 14.3179 6.29682 13.0434 7.57135L11.6916 8.92318" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.92308 11.6924L7.57135 13.0442C6.29682 14.3187 4.23042 14.3187 2.9559 13.0442C1.68137 11.7697 1.68137 9.70326 2.9559 8.42873L4.30762 7.0769" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.84666 6.15381L6.1543 9.84617" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 701 B |
6
frontend/resources/flowy_icons/16x/share_tab_icon.svg
Normal file
6
frontend/resources/flowy_icons/16x/share_tab_icon.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.10573 7.24659C6.03906 7.23992 5.95906 7.23992 5.88573 7.24659C4.29906 7.19325 3.03906 5.89325 3.03906 4.29325C3.03906 2.65992 4.35906 1.33325 5.99906 1.33325C7.6324 1.33325 8.95906 2.65992 8.95906 4.29325C8.9524 5.89325 7.6924 7.19325 6.10573 7.24659Z" stroke="#171717" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.9402 2.66675C12.2335 2.66675 13.2735 3.71341 13.2735 5.00008C13.2735 6.26008 12.2735 7.28675 11.0268 7.33341C10.9735 7.32675 10.9135 7.32675 10.8535 7.33341" stroke="#171717" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2.7725 9.70675C1.15917 10.7867 1.15917 12.5467 2.7725 13.6201C4.60583 14.8467 7.6125 14.8467 9.44583 13.6201C11.0592 12.5401 11.0592 10.7801 9.44583 9.70675C7.61917 8.48675 4.6125 8.48675 2.7725 9.70675Z" stroke="#171717" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.2266 13.3333C12.7066 13.2333 13.1599 13.0399 13.5332 12.7533C14.5732 11.9733 14.5732 10.6866 13.5332 9.90659C13.1666 9.62659 12.7199 9.43992 12.2466 9.33325" stroke="#171717" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@ -121,7 +121,11 @@
|
||||
"exportAsTab": "Export as",
|
||||
"publishTab": "Publish",
|
||||
"shareTab": "Share",
|
||||
"publishOnAppFlowy": "Publish on AppFlowy"
|
||||
"publishOnAppFlowy": "Publish on AppFlowy",
|
||||
"shareTabTitle": "Invite to collaborate",
|
||||
"shareTabDescription": "For easy collaboration with anyone",
|
||||
"copyLinkToBlockSuccess": "Copied block link to clipboard",
|
||||
"copyLinkToBlockFailed": "Failed to copy block link to clipboard"
|
||||
},
|
||||
"moreAction": {
|
||||
"small": "small",
|
||||
@ -1665,7 +1669,8 @@
|
||||
"center": "Center",
|
||||
"right": "Right",
|
||||
"defaultColor": "Default",
|
||||
"depth": "Depth"
|
||||
"depth": "Depth",
|
||||
"copyLinkToBlock": "Copy link to block"
|
||||
},
|
||||
"image": {
|
||||
"addAnImage": "Add images",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user