From 54096b391fe4c127b83d9d0b47f8c4cb24a9105a Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 5 Nov 2024 09:23:38 +0800 Subject: [PATCH] fix: settings site issues (#6701) * fix: pages overflow when selecting homepage * fix: settings site issues * chore: try to fix windows ci * test: add tests * fix: shareblock state update * fix: pages overflow when selecting homepage * fix: view name doesn't update after publishing * chore: add translations * feat: close popup menu after unpublishing page * feat: align the published page name with header --- .../cloud/document/document_publish_test.dart | 4 - .../workspace/workspace_settings_test.dart | 131 +++++++++++++++ .../lib/plugins/shared/share/_shared.dart | 69 ++++---- .../lib/plugins/shared/share/publish_tab.dart | 40 +---- .../lib/plugins/shared/share/share_bloc.dart | 10 +- .../lib/plugins/shared/share/share_menu.dart | 6 +- .../settings/pages/sites/constants.dart | 1 + .../pages/sites/domain/domain_item.dart | 149 +++++++++++++----- .../sites/domain/domain_more_action.dart | 92 ++++------- .../pages/sites/domain/home_page_menu.dart | 33 ++-- .../pages/sites/publish_info_view_item.dart | 21 ++- .../published_page/published_view_item.dart | 103 ++++++++---- .../published_view_more_action.dart | 30 +++- .../pages/sites/settings_sites_bloc.dart | 6 +- .../pages/sites/settings_sites_view.dart | 24 ++- frontend/resources/translations/en.json | 11 +- .../freezed/generate_freezed.cmd | 2 + 17 files changed, 501 insertions(+), 231 deletions(-) diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_publish_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_publish_test.dart index b53dcbaf2bfb..0aa308ea5354 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_publish_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_publish_test.dart @@ -59,10 +59,6 @@ void main() { // expect to see unpublish, visit site and manage all sites button expect(unpublishButton, findsOneWidget); expect(find.text(LocaleKeys.shareAction_visitSite.tr()), findsOneWidget); - expect( - find.text(LocaleKeys.shareAction_manageAllSites.tr()), - findsOneWidget, - ); // unpublish the document await tester.tapButton(unpublishButton); diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/workspace_settings_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/workspace_settings_test.dart index dca48471779c..c63a93442719 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/workspace_settings_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/workspace_settings_test.dart @@ -4,6 +4,7 @@ import 'dart:io'; 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/document/presentation/editor_plugins/openai/widgets/loading.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart'; import 'package:appflowy/plugins/shared/share/publish_tab.dart'; @@ -21,6 +22,8 @@ import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/doma import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/domain_settings_dialog.dart'; import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/home_page_menu.dart'; import 'package:appflowy/workspace/presentation/settings/pages/sites/published_page/published_view_item.dart'; +import 'package:appflowy/workspace/presentation/settings/pages/sites/published_page/published_view_more_action.dart'; +import 'package:appflowy/workspace/presentation/settings/pages/sites/published_page/published_view_settings_dialog.dart'; import 'package:appflowy/workspace/presentation/settings/shared/setting_list_tile.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart'; import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart'; @@ -233,5 +236,133 @@ void main() { // await tester.pumpUntilFound(successToast); // expect(successToast, findsOneWidget); }); + + testWidgets(''' +More actions for published page: +1. visit site +2. copy link +3. settings +4. unpublish +5. custom url +''', (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, + ); + + // open the publish menu + await tester.openPublishMenu(); + + // publish the document + await tester.tapButton(find.byType(PublishButton)); + + // click empty area to close the publish menu + await tester.tapAt(Offset.zero); + await tester.pumpAndSettle(); + // check if the page is published in sites page + await tester.openSettings(); + await tester.openSettingsPage(SettingsPage.sites); + // wait the backend return the sites data + await tester.wait(1000); + + // check if the page is published in sites page + final pageItem = find.byWidgetPredicate( + (widget) => + widget is PublishedViewItem && + widget.publishInfoView.view.name == pageName, + ); + expect(pageItem, findsOneWidget); + + final copyLinkItem = find.text(LocaleKeys.shareAction_copyLink.tr()); + final customUrlItem = find.text(LocaleKeys.settings_sites_customUrl.tr()); + final unpublishItem = find.text(LocaleKeys.shareAction_unPublish.tr()); + + // custom url + final publishMoreAction = find.byType(PublishedViewMoreAction); + + // click the copy link button + { + await tester.tapButton(publishMoreAction); + await tester.pumpAndSettle(); + await tester.pumpUntilFound(copyLinkItem); + await tester.tapButton(copyLinkItem); + await tester.pumpAndSettle(); + await tester.pumpUntilNotFound(copyLinkItem); + + final clipboardContent = await getIt().getData(); + final plainText = clipboardContent.plainText; + expect( + plainText, + contains(pageName), + ); + } + + // custom url + { + await tester.tapButton(publishMoreAction); + await tester.pumpAndSettle(); + await tester.pumpUntilFound(customUrlItem); + await tester.tapButton(customUrlItem); + await tester.pumpAndSettle(); + await tester.pumpUntilNotFound(customUrlItem); + + // see the custom url dialog + final customUrlDialog = find.byType(PublishedViewSettingsDialog); + expect(customUrlDialog, findsOneWidget); + + // rename the custom url + final textField = find.descendant( + of: customUrlDialog, + matching: find.byType(TextField), + ); + await tester.enterText(textField, 'hello-world'); + await tester.pumpAndSettle(); + + // click the save button + final saveButton = find.descendant( + of: customUrlDialog, + matching: find.text(LocaleKeys.button_save.tr()), + ); + await tester.tapButton(saveButton); + await tester.pumpAndSettle(); + + // expect to see the toast with success message + final successToast = find.text( + LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(), + ); + await tester.pumpUntilFound(successToast); + expect(successToast, findsOneWidget); + } + + // unpublish + { + await tester.tapButton(publishMoreAction); + await tester.pumpAndSettle(); + await tester.pumpUntilFound(unpublishItem); + await tester.tapButton(unpublishItem); + await tester.pumpAndSettle(); + await tester.pumpUntilNotFound(unpublishItem); + + // expect to see the toast with success message + final successToast = find.text( + LocaleKeys.publish_unpublishSuccessfully.tr(), + ); + await tester.pumpUntilFound(successToast); + expect(successToast, findsOneWidget); + await tester.pumpUntilNotFound(successToast); + + // check if the page is unpublished in sites page + expect(pageItem, findsNothing); + } + }); }); } diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart index 7f41d6b30ccf..803c9867c9e0 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart @@ -21,39 +21,46 @@ class ShareMenuButton extends StatelessWidget { final shareBloc = context.read(); final databaseBloc = context.read(); final userWorkspaceBloc = context.read(); - return SizedBox( - height: 32.0, - child: IntrinsicWidth( - child: AppFlowyPopover( - direction: PopoverDirection.bottomWithRightAligned, - constraints: const BoxConstraints( - maxWidth: 500, - ), - offset: const Offset(0, 8), - onOpen: () { - context - .read() - .add(const ShareEvent.updatePublishStatus()); - }, - popupBuilder: (context) => MultiBlocProvider( - providers: [ - if (databaseBloc != null) - BlocProvider.value( - value: databaseBloc, - ), - BlocProvider.value(value: shareBloc), - BlocProvider.value(value: userWorkspaceBloc), - ], - child: ShareMenu( - tabs: tabs, + return BlocBuilder( + builder: (context, state) { + return SizedBox( + height: 32.0, + child: IntrinsicWidth( + child: AppFlowyPopover( + direction: PopoverDirection.bottomWithRightAligned, + constraints: const BoxConstraints( + maxWidth: 500, + ), + offset: const Offset(0, 8), + onOpen: () { + context + .read() + .add(const ShareEvent.updatePublishStatus()); + }, + popupBuilder: (_) { + return MultiBlocProvider( + providers: [ + if (databaseBloc != null) + BlocProvider.value( + value: databaseBloc, + ), + BlocProvider.value(value: shareBloc), + BlocProvider.value(value: userWorkspaceBloc), + ], + child: ShareMenu( + tabs: tabs, + viewName: state.viewName, + ), + ); + }, + child: PrimaryRoundedButton( + text: LocaleKeys.shareAction_buttonText.tr(), + figmaLineHeight: 16, + ), ), ), - child: PrimaryRoundedButton( - text: LocaleKeys.shareAction_buttonText.tr(), - figmaLineHeight: 16, - ), - ), - ), + ); + }, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart index 4a2ab0fe4be8..8ab84e7fd3d6 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart @@ -9,10 +9,7 @@ import 'package:appflowy/plugins/shared/share/publish_name_generator.dart'; import 'package:appflowy/plugins/shared/share/share_bloc.dart'; import 'package:appflowy/shared/error_code/error_code_map.dart'; import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/workspace/application/settings/prelude.dart'; -import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; -import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_setting.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; @@ -25,7 +22,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class PublishTab extends StatelessWidget { - const PublishTab({super.key}); + const PublishTab({ + super.key, + required this.viewName, + }); + + final String viewName; @override Widget build(BuildContext context) { @@ -50,7 +52,7 @@ class PublishTab extends StatelessWidget { final id = context.read().view.id; final publishName = await generatePublishName( id, - state.viewName, + viewName, ); if (selectedViews.isNotEmpty) { @@ -188,7 +190,6 @@ class _PublishedWidgetState extends State<_PublishedWidget> { Row( mainAxisSize: MainAxisSize.min, children: [ - _buildManageSiteButton(), const Spacer(), UnPublishButton( onUnPublish: widget.onUnPublish, @@ -201,33 +202,6 @@ class _PublishedWidgetState extends State<_PublishedWidget> { ); } - Widget _buildManageSiteButton() { - return SizedBox( - width: 128, - height: 36, - child: FlowyButton( - radius: BorderRadius.circular(10), - text: FlowyText.regular( - lineHeight: 1.0, - LocaleKeys.shareAction_manageAllSites.tr(), - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - ), - onTap: () { - PopoverContainer.of(context).close(); - - // open settings sites page - showSettingsDialog( - context, - context.read().userProfile, - context.read(), - SettingsPage.sites, - ); - }, - ), - ); - } - Widget _buildVisitSiteButton() { return RoundedTextButton( width: 108, diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/share_bloc.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/share_bloc.dart index a713af6b0207..1b607bfc6282 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/share_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/share_bloc.dart @@ -51,7 +51,15 @@ class ShareBloc extends Bloc { unPublish: () async => _unpublish(emit), updatePublishStatus: () async => _updatePublishStatus(emit), updateViewName: (viewName, viewId) async { - emit(state.copyWith(viewName: viewName, viewId: viewId)); + emit( + state.copyWith( + viewName: viewName, + viewId: viewId, + updatePathNameResult: null, + publishResult: null, + unpublishResult: null, + ), + ); }, setPublishStatus: (isPublished) { emit( diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/share_menu.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/share_menu.dart index 8e7eca933ced..4decb1c09276 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/share_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/share_menu.dart @@ -32,9 +32,11 @@ class ShareMenu extends StatefulWidget { const ShareMenu({ super.key, required this.tabs, + required this.viewName, }); final List tabs; + final String viewName; @override State createState() => _ShareMenuState(); @@ -119,7 +121,9 @@ class _ShareMenuState extends State Widget _buildTab(BuildContext context) { switch (selectedTab) { case ShareMenuTab.publish: - return const PublishTab(); + return PublishTab( + viewName: widget.viewName, + ); case ShareMenuTab.exportAs: return const ExportTab(); case ShareMenuTab.share: diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/constants.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/constants.dart index 7c9c24cd5dec..f764bec9e7ee 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/constants.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/constants.dart @@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; class SettingsPageSitesConstants { static const threeDotsButtonWidth = 26.0; + static const alignPadding = 6.0; static final dateFormat = DateFormat('MMM d, yyyy'); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_item.dart index 001f34bddc88..57c8f4d636e2 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_item.dart @@ -5,11 +5,13 @@ import 'package:appflowy/plugins/shared/share/constants.dart'; import 'package:appflowy/shared/af_role_pb_extension.dart'; import 'package:appflowy/shared/colors.dart'; import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; +import 'package:appflowy/workspace/presentation/settings/pages/sites/constants.dart'; import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/domain_more_action.dart'; import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/home_page_menu.dart'; import 'package:appflowy/workspace/presentation/settings/pages/sites/publish_info_view_item.dart'; import 'package:appflowy/workspace/presentation/settings/pages/sites/settings_sites_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-user/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -40,7 +42,11 @@ class DomainItem extends StatelessWidget { ), // Homepage Expanded( - child: _buildHomepage(context), + child: Padding( + padding: const EdgeInsets.only( + left: SettingsPageSitesConstants.alignPadding,), + child: _buildHomepage(context), + ), ), // ... button DomainMoreAction(namespace: namespace), @@ -91,6 +97,11 @@ class _HomePageButton extends StatelessWidget { @override Widget build(BuildContext context) { + final settingsSitesState = context.watch().state; + if (settingsSitesState.isLoading) { + return const SizedBox.shrink(); + } + final isOwner = context .watch() .state @@ -98,8 +109,8 @@ class _HomePageButton extends StatelessWidget { ?.role .isOwner ?? false; - final homePageView = context.watch().state.homePageView; + final homePageView = settingsSitesState.homePageView; Widget child = homePageView == null ? _defaultHomePageButton(context) : PublishInfoViewItem( @@ -108,38 +119,13 @@ class _HomePageButton extends StatelessWidget { ); if (isOwner) { - child = AppFlowyPopover( - direction: PopoverDirection.bottomWithCenterAligned, - constraints: const BoxConstraints( - maxWidth: 260, - maxHeight: 345, - ), - margin: const EdgeInsets.symmetric( - horizontal: 14.0, - vertical: 12.0, - ), - popupBuilder: (_) { - final bloc = context.read(); - return BlocProvider.value( - value: bloc, - child: SelectHomePageMenu( - userProfile: bloc.user, - workspaceId: bloc.workspaceId, - onSelected: (view) {}, - ), - ); - }, + child = _buildHomePageButtonForOwner( + context, + homePageView: homePageView, child: child, ); } else { - child = FlowyTooltip( - message: LocaleKeys - .settings_sites_namespace_onlyWorkspaceOwnerCanSetHomePage - .tr(), - child: IgnorePointer( - child: child, - ), - ); + child = _buildHomePageButtonForNonOwner(context, child); } return Container( @@ -148,6 +134,72 @@ class _HomePageButton extends StatelessWidget { ); } + Widget _buildHomePageButtonForOwner( + BuildContext context, { + required PublishInfoViewPB? homePageView, + required Widget child, + }) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + AppFlowyPopover( + direction: PopoverDirection.bottomWithCenterAligned, + constraints: const BoxConstraints( + maxWidth: 260, + maxHeight: 345, + ), + margin: const EdgeInsets.symmetric( + horizontal: 14.0, + vertical: 12.0, + ), + popupBuilder: (_) { + final bloc = context.read(); + return BlocProvider.value( + value: bloc, + child: SelectHomePageMenu( + userProfile: bloc.user, + workspaceId: bloc.workspaceId, + onSelected: (view) {}, + ), + ); + }, + child: child, + ), + if (homePageView != null) + FlowyTooltip( + message: LocaleKeys.settings_sites_clearHomePage.tr(), + child: FlowyButton( + margin: const EdgeInsets.all(4.0), + useIntrinsicWidth: true, + onTap: () { + context.read().add( + const SettingsSitesEvent.removeHomePage(), + ); + }, + text: const FlowySvg( + FlowySvgs.close_m, + size: Size.square(18.0), + ), + ), + ), + ], + ); + } + + Widget _buildHomePageButtonForNonOwner( + BuildContext context, + Widget child, + ) { + return FlowyTooltip( + message: LocaleKeys + .settings_sites_namespace_onlyWorkspaceOwnerCanSetHomePage + .tr(), + child: IgnorePointer( + child: child, + ), + ); + } + Widget _defaultHomePageButton(BuildContext context) { return FlowyButton( useIntrinsicWidth: true, @@ -168,6 +220,13 @@ class _FreePlanUpgradeButton extends StatelessWidget { @override Widget build(BuildContext context) { + final isOwner = context + .watch() + .state + .currentWorkspaceMember + ?.role + .isOwner ?? + false; return Container( alignment: Alignment.centerLeft, child: FlowyTooltip( @@ -186,16 +245,26 @@ class _FreePlanUpgradeButton extends StatelessWidget { ), hoverColor: context.proSecondaryColor.withOpacity(0.9), onTap: () { - showToastNotification( - context, - message: - LocaleKeys.settings_sites_namespace_redirectToPayment.tr(), - type: ToastificationType.info, - ); + if (isOwner) { + showToastNotification( + context, + message: + LocaleKeys.settings_sites_namespace_redirectToPayment.tr(), + type: ToastificationType.info, + ); - context.read().add( - const SettingsSitesEvent.upgradeSubscription(), - ); + context.read().add( + const SettingsSitesEvent.upgradeSubscription(), + ); + } else { + showToastNotification( + context, + message: LocaleKeys + .settings_sites_namespace_pleaseAskOwnerToSetHomePage + .tr(), + type: ToastificationType.info, + ); + } }, ), ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_more_action.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_more_action.dart index 25e02ff06bd8..82215c9f42d0 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_more_action.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_more_action.dart @@ -6,7 +6,6 @@ import 'package:appflowy/workspace/presentation/settings/pages/sites/constants.d import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/domain_settings_dialog.dart'; import 'package:appflowy/workspace/presentation/settings/pages/sites/settings_sites_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -51,18 +50,9 @@ class _DomainMoreActionState extends State { popupBuilder: (builderContext) { return BlocProvider.value( value: context.read(), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildUpdateNamespaceButton( - context, - builderContext, - ), - _buildRemoveHomePageButton( - context, - builderContext, - ), - ], + child: _buildUpdateNamespaceButton( + context, + builderContext, ), ); }, @@ -79,48 +69,16 @@ class _DomainMoreActionState extends State { type: _ActionType.updateNamespace, ); - return _onlyOwnerCanDoWidget( - context, - tooltipMessage: LocaleKeys - .settings_sites_error_onlyWorkspaceOwnerCanUpdateNamespace - .tr(), - child: child, - ); - } - - Widget _buildRemoveHomePageButton( - BuildContext context, - BuildContext builderContext, - ) { final plan = context.read().state.subscriptionInfo?.plan; - final homePageView = context.read().state.homePageView; - if (plan == null || - plan == WorkspacePlanPB.FreePlan || - homePageView == null) { - return const SizedBox.shrink(); + if (plan != WorkspacePlanPB.ProPlan) { + return _buildForbiddenActionButton( + context, + tooltipMessage: LocaleKeys.settings_sites_namespace_upgradeToPro.tr(), + child: child, + ); } - final child = _buildActionButton( - context, - builderContext, - type: _ActionType.removeHomePage, - ); - - return _onlyOwnerCanDoWidget( - context, - tooltipMessage: LocaleKeys - .settings_sites_error_onlyWorkspaceOwnerCanRemoveHomepage - .tr(), - child: child, - ); - } - - Widget _onlyOwnerCanDoWidget( - BuildContext context, { - required String tooltipMessage, - required Widget child, - }) { final isOwner = context .watch() .state @@ -130,21 +88,35 @@ class _DomainMoreActionState extends State { false; if (!isOwner) { - return Opacity( - opacity: 0.5, - child: FlowyTooltip( - message: tooltipMessage, - child: MouseRegion( - cursor: SystemMouseCursors.forbidden, - child: IgnorePointer(child: child), - ), - ), + return _buildForbiddenActionButton( + context, + tooltipMessage: LocaleKeys + .settings_sites_error_onlyWorkspaceOwnerCanUpdateNamespace + .tr(), + child: child, ); } return child; } + Widget _buildForbiddenActionButton( + BuildContext context, { + required String tooltipMessage, + required Widget child, + }) { + return Opacity( + opacity: 0.5, + child: FlowyTooltip( + message: tooltipMessage, + child: MouseRegion( + cursor: SystemMouseCursors.forbidden, + child: IgnorePointer(child: child), + ), + ), + ); + } + Widget _buildActionButton( BuildContext context, BuildContext builderContext, { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/home_page_menu.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/home_page_menu.dart index 90d6b4d4b402..f1236c102421 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/home_page_menu.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/home_page_menu.dart @@ -65,19 +65,28 @@ class _SelectHomePageMenuState extends State { onSearch: (context, value) => _onSearch(value), ), const VSpace(10), - ...views.map( - (view) => Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: PublishInfoViewItem( - publishInfoView: view, - useIntrinsicWidth: false, - onTap: () { - context.read().add( - SettingsSitesEvent.setHomePage(view.info.viewId), - ); + Expanded( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ...views.map( + (view) => Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: PublishInfoViewItem( + publishInfoView: view, + useIntrinsicWidth: false, + onTap: () { + context.read().add( + SettingsSitesEvent.setHomePage(view.info.viewId), + ); - PopoverContainer.of(context).close(); - }, + PopoverContainer.of(context).close(); + }, + ), + ), + ), + ], ), ), ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/publish_info_view_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/publish_info_view_item.dart index d7ddf81874d9..f2a3980bf6bc 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/publish_info_view_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/publish_info_view_item.dart @@ -13,16 +13,22 @@ class PublishInfoViewItem extends StatelessWidget { this.onTap, this.useIntrinsicWidth = true, this.margin, + this.extraTooltipMessage, }); final PublishInfoViewPB publishInfoView; final VoidCallback? onTap; final bool useIntrinsicWidth; final EdgeInsets? margin; + final String? extraTooltipMessage; @override Widget build(BuildContext context) { - final name = publishInfoView.view.name; + final name = publishInfoView.view.name.orDefault( + LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + ); + final tooltipMessage = + extraTooltipMessage != null ? '$extraTooltipMessage\n$name' : name; return Container( alignment: Alignment.centerLeft, child: FlowyButton( @@ -30,13 +36,14 @@ class PublishInfoViewItem extends StatelessWidget { useIntrinsicWidth: useIntrinsicWidth, mainAxisAlignment: MainAxisAlignment.start, leftIcon: _buildIcon(), - text: FlowyText.regular( - name.orDefault( - LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + text: FlowyTooltip( + message: tooltipMessage, + child: FlowyText.regular( + name, + fontSize: 14.0, + figmaLineHeight: 18.0, + overflow: TextOverflow.ellipsis, ), - fontSize: 14.0, - figmaLineHeight: 18.0, - overflow: TextOverflow.ellipsis, ), onTap: onTap, ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/published_page/published_view_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/published_page/published_view_item.dart index e909cf356d32..8332a8f6f349 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/published_page/published_view_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/published_page/published_view_item.dart @@ -1,3 +1,6 @@ +import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/shared/share/constants.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/util/navigator_context_exntesion.dart'; import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; @@ -6,6 +9,7 @@ import 'package:appflowy/workspace/presentation/settings/pages/sites/constants.d import 'package:appflowy/workspace/presentation/settings/pages/sites/publish_info_view_item.dart'; import 'package:appflowy/workspace/presentation/settings/pages/sites/published_page/published_view_more_action.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -19,11 +23,6 @@ class PublishedViewItem extends StatelessWidget { @override Widget build(BuildContext context) { - final formattedDate = SettingsPageSitesConstants.dateFormat.format( - DateTime.fromMillisecondsSinceEpoch( - publishInfoView.info.publishTimestampSec.toInt() * 1000, - ), - ); final flexes = SettingsPageSitesConstants.publishedViewItemFlexes; return Row( @@ -32,44 +31,23 @@ class PublishedViewItem extends StatelessWidget { // Published page name Expanded( flex: flexes[0], - child: PublishInfoViewItem( - publishInfoView: publishInfoView, - onTap: () { - context.popToHome(); - - getIt().add( - ActionNavigationEvent.performAction( - action: NavigationAction( - objectId: publishInfoView.view.viewId, - ), - ), - ); - }, - ), + child: _buildPublishedPageName(context), ), // Published Name Expanded( flex: flexes[1], - child: Padding( - padding: const EdgeInsets.only(right: 48.0), - child: FlowyText( - publishInfoView.info.publishName, - withTooltip: true, - fontSize: 14.0, - figmaLineHeight: 18.0, - overflow: TextOverflow.ellipsis, - ), - ), + child: _buildPublishedName(context), ), // Published at Expanded( flex: flexes[2], - child: FlowyText( - formattedDate, - fontSize: 14.0, - overflow: TextOverflow.ellipsis, + child: Padding( + padding: const EdgeInsets.only( + left: SettingsPageSitesConstants.alignPadding, + ), + child: _buildPublishedAt(context), ), ), @@ -80,4 +58,63 @@ class PublishedViewItem extends StatelessWidget { ], ); } + + Widget _buildPublishedPageName(BuildContext context) { + return PublishInfoViewItem( + extraTooltipMessage: + LocaleKeys.settings_sites_publishedPage_clickToOpenPageInApp.tr(), + publishInfoView: publishInfoView, + onTap: () { + context.popToHome(); + + getIt().add( + ActionNavigationEvent.performAction( + action: NavigationAction( + objectId: publishInfoView.view.viewId, + ), + ), + ); + }, + ); + } + + Widget _buildPublishedAt(BuildContext context) { + final formattedDate = SettingsPageSitesConstants.dateFormat.format( + DateTime.fromMillisecondsSinceEpoch( + publishInfoView.info.publishTimestampSec.toInt() * 1000, + ), + ); + return FlowyText( + formattedDate, + fontSize: 14.0, + overflow: TextOverflow.ellipsis, + ); + } + + Widget _buildPublishedName(BuildContext context) { + return Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only(right: 48.0), + child: FlowyButton( + useIntrinsicWidth: true, + onTap: () { + final url = ShareConstants.buildPublishUrl( + nameSpace: publishInfoView.info.namespace, + publishName: publishInfoView.info.publishName, + ); + afLaunchUrlString(url); + }, + text: FlowyTooltip( + message: + '${LocaleKeys.settings_sites_publishedPage_clickToOpenPageInBrowser.tr()}\n${publishInfoView.info.publishName}', + child: FlowyText( + publishInfoView.info.publishName, + fontSize: 14.0, + figmaLineHeight: 18.0, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ); + } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/published_page/published_view_more_action.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/published_page/published_view_more_action.dart index 0188fc86d8d8..6c7b17a70b4d 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/published_page/published_view_more_action.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/published_page/published_view_more_action.dart @@ -48,6 +48,16 @@ class PublishedViewMoreAction extends StatelessWidget { builderContext, type: _ActionType.copySiteLink, ), + _buildActionButton( + context, + builderContext, + type: _ActionType.unpublish, + ), + _buildActionButton( + context, + builderContext, + type: _ActionType.customUrl, + ), _buildActionButton( context, builderContext, @@ -109,6 +119,18 @@ class PublishedViewMoreAction extends StatelessWidget { builderContext, ); break; + case _ActionType.unpublish: + context.read().add( + SettingsSitesEvent.unpublishView(publishInfoView.info.viewId), + ); + PopoverContainer.maybeOf(builderContext)?.close(); + break; + case _ActionType.customUrl: + _showSettingsDialog( + context, + builderContext, + ); + break; } PopoverContainer.of(builderContext).closeAll(); @@ -143,17 +165,23 @@ class PublishedViewMoreAction extends StatelessWidget { enum _ActionType { viewSite, copySiteLink, - settings; + settings, + unpublish, + customUrl; String get name => switch (this) { _ActionType.viewSite => LocaleKeys.shareAction_visitSite.tr(), _ActionType.copySiteLink => LocaleKeys.shareAction_copyLink.tr(), _ActionType.settings => LocaleKeys.settings_popupMenuItem_settings.tr(), + _ActionType.unpublish => LocaleKeys.shareAction_unPublish.tr(), + _ActionType.customUrl => LocaleKeys.settings_sites_customUrl.tr(), }; FlowySvgData get leftIconSvg => switch (this) { _ActionType.viewSite => FlowySvgs.share_publish_s, _ActionType.copySiteLink => FlowySvgs.copy_s, _ActionType.settings => FlowySvgs.settings_s, + _ActionType.unpublish => FlowySvgs.delete_s, + _ActionType.customUrl => FlowySvgs.edit_s, }; } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/settings_sites_bloc.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/settings_sites_bloc.dart index 30109581b2fb..e03eed3f46ae 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/settings_sites_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/settings_sites_bloc.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/user/application/user_service.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; +import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; @@ -96,7 +97,10 @@ class SettingsSitesBloc extends Bloc { final result = await UserBackendService.getWorkspaceSubscriptionInfo( workspaceId, ); - return result.fold((s) => s, (_) => null); + return result.fold((s) => s, (f) { + Log.error('Failed to fetch user subscription info: $f'); + return null; + }); } Future _fetchPublishNamespace() async { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/settings_sites_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/settings_sites_view.dart index 8998f92226ea..7b00e652edc1 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/settings_sites_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/settings_sites_view.dart @@ -1,5 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; +import 'package:appflowy/workspace/presentation/settings/pages/sites/constants.dart'; import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/domain_header.dart'; import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/domain_item.dart'; import 'package:appflowy/workspace/presentation/settings/pages/sites/published_page/published_view_item.dart'; @@ -80,9 +81,18 @@ class _SettingsSitesPageView extends StatelessWidget { ), children: [ const DomainHeader(), - DomainItem( - namespace: state.namespace, - homepage: '', + ConstrainedBox( + constraints: const BoxConstraints(minHeight: 36.0), + child: Transform.translate( + offset: const Offset( + -SettingsPageSitesConstants.alignPadding, + 0, + ), + child: DomainItem( + namespace: state.namespace, + homepage: '', + ), + ), ), ], ); @@ -134,7 +144,13 @@ class _SettingsSitesPageView extends StatelessWidget { } else { children.addAll( publishedViews.map( - (view) => PublishedViewItem(publishInfoView: view), + (view) => Transform.translate( + offset: const Offset( + -SettingsPageSitesConstants.alignPadding, + 0, + ), + child: PublishedViewItem(publishInfoView: view), + ), ), ); } diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 8b7b19bbc96d..3d10c35a829a 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -444,23 +444,28 @@ "updateNamespace": "Update namespace", "removeHomepage": "Remove homepage", "selectHomePage": "Select a page", + "clearHomePage": "Clear the home page for this namespace", + "customUrl": "Custom url", "namespace": { "description": "This change will apply to all the published pages live on this namespace", "tooltip": "We reserve the rights to remove any inappropriate namespaces", "updateExistingNamespace": "Update existing namespace", "upgradeToPro": "Upgrade to Pro Plan to set a homepage", "redirectToPayment": "Redirecting to payment page...", - "onlyWorkspaceOwnerCanSetHomePage": "Only the workspace owner can set a homepage" + "onlyWorkspaceOwnerCanSetHomePage": "Only the workspace owner can set a homepage", + "pleaseAskOwnerToSetHomePage": "Please ask the workspace owner to upgrade to Pro Plan" }, "publishedPage": { "title": "All published pages", "description": "Manage your published pages", - "page": "page", + "page": "Page", "pathName": "Path name", "date": "Published date", "emptyHinText": "You have no published pages in this workspace", "noPublishedPages": "No published pages", - "settings": "Publish settings" + "settings": "Publish settings", + "clickToOpenPageInApp": "Open page in app", + "clickToOpenPageInBrowser": "Open page in browser" }, "error": { "failedToGeneratePaymentLink": "Failed to generate payment link for pro plan", diff --git a/frontend/scripts/code_generation/freezed/generate_freezed.cmd b/frontend/scripts/code_generation/freezed/generate_freezed.cmd index f543e08fd56e..798c3bc1dca3 100644 --- a/frontend/scripts/code_generation/freezed/generate_freezed.cmd +++ b/frontend/scripts/code_generation/freezed/generate_freezed.cmd @@ -26,6 +26,8 @@ for /D %%d in (*) do ( if exist "pubspec.yaml" ( echo Generating freezed files in %%d... echo Please wait while we clean the project and fetch the dependencies. + call flutter packages pub get + call flutter pub get call dart run build_runner clean && call dart run build_runner build -d echo Done running build command in %%d ) else (