From 3edcc085435962299149fd2f407549a60da28664 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Fri, 21 Jun 2024 15:13:17 +0800 Subject: [PATCH] feat: publish page to the web --- .../presentation/share/publish_tab.dart | 215 ++++++++++++++++++ .../presentation/share/share_button.dart | 20 +- .../presentation/share/share_menu.dart | 120 ++++++++++ .../lib/widget/rounded_button.dart | 3 + frontend/appflowy_flutter/pubspec.lock | 8 + frontend/appflowy_flutter/pubspec.yaml | 1 + .../flowy_icons/16x/share_publish.svg | 3 + frontend/resources/translations/en.json | 7 +- 8 files changed, 375 insertions(+), 2 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/share/publish_tab.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_menu.dart create mode 100644 frontend/resources/flowy_icons/16x/share_publish.svg diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/share/publish_tab.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/share/publish_tab.dart new file mode 100644 index 0000000000..50e4ac7f9a --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/share/publish_tab.dart @@ -0,0 +1,215 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/widget/rounded_button.dart'; +import 'package:flutter/material.dart'; + +class PublishTab extends StatefulWidget { + const PublishTab({super.key}); + + @override + State createState() => _PublishTabState(); +} + +class _PublishTabState extends State { + bool isPublished = false; + + @override + Widget build(BuildContext context) { + return isPublished + ? _PublishedWidget(onUnPublish: () {}, onVisitSite: () {}) + : _UnPublishWidget(onPublish: () => setState(() => isPublished = true)); + } +} + +class _PublishedWidget extends StatefulWidget { + const _PublishedWidget({ + required this.onVisitSite, + required this.onUnPublish, + }); + + final VoidCallback onVisitSite; + final VoidCallback onUnPublish; + + @override + State<_PublishedWidget> createState() => _PublishedWidgetState(); +} + +class _PublishedWidgetState extends State<_PublishedWidget> { + final controller = TextEditingController(); + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const VSpace(16), + const _PublishTabHeader(), + const VSpace(16), + _PublishUrl( + controller: controller, + onCopy: (url) {}, + onSubmitted: (url) {}, + ), + const VSpace(16), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildButton( + context, + name: LocaleKeys.shareAction_unPublish.tr(), + borderColor: const Color(0x1E14171B), + onTap: widget.onUnPublish, + ), + const Spacer(), + _buildButton( + context, + name: LocaleKeys.shareAction_visitSite.tr(), + backgroundColor: Theme.of(context).colorScheme.primary, + textColor: Colors.white, + onTap: () {}, + ), + ], + ), + ], + ); + } + + Widget _buildButton( + BuildContext context, { + required String name, + Color? backgroundColor, + Color borderColor = Colors.transparent, + Color? textColor, + required VoidCallback onTap, + }) { + return SizedBox( + height: 36, + width: 189, + child: FlowyButton( + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: borderColor), + ), + text: FlowyText.regular( + name, + textAlign: TextAlign.center, + color: textColor, + ), + onTap: onTap, + ), + ); + } +} + +class _UnPublishWidget extends StatelessWidget { + const _UnPublishWidget({ + required this.onPublish, + }); + + final VoidCallback onPublish; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const VSpace(16), + const _PublishTabHeader(), + const VSpace(16), + RoundedTextButton( + height: 36, + title: LocaleKeys.shareAction_publish.tr(), + padding: const EdgeInsets.symmetric(vertical: 9.0), + fontSize: 14.0, + textColor: Theme.of(context).colorScheme.onPrimary, + onPressed: onPublish, + ), + ], + ); + } +} + +class _PublishTabHeader extends StatelessWidget { + const _PublishTabHeader(); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const FlowySvg(FlowySvgs.share_publish_s), + const HSpace(6), + FlowyText(LocaleKeys.shareAction_publishToTheWeb.tr()), + ], + ), + const VSpace(4), + FlowyText.regular( + LocaleKeys.shareAction_publishToTheWebHint.tr(), + fontSize: 12, + maxLines: 3, + color: Theme.of(context).hintColor, + ), + ], + ); + } +} + +class _PublishUrl extends StatelessWidget { + const _PublishUrl({ + required this.controller, + required this.onCopy, + required this.onSubmitted, + }); + + final TextEditingController controller; + final void Function(String url) onCopy; + final void Function(String url) onSubmitted; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 36, + child: FlowyTextField( + autoFocus: false, + controller: controller, + text: 'http:/appflowy.com/vinh/open-positions', + suffixIcon: _buildCopyLinkIcon(), + ), + ); + } + + Widget _buildCopyLinkIcon() { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => onCopy(controller.text), + child: Container( + width: 36, + height: 36, + alignment: Alignment.center, + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( + border: Border(left: BorderSide(color: Color(0x141F2329))), + ), + child: const FlowySvg( + FlowySvgs.m_toolbar_link_m, + ), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart index eb82f3d1fa..5a45fb1750 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart @@ -1,6 +1,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/document_share_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; +import 'package:appflowy/plugins/document/presentation/share/share_menu.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy/workspace/application/export/document_exporter.dart'; @@ -13,6 +14,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/file_picker/file_picker_service.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -43,7 +45,23 @@ class DocumentShareButton extends StatelessWidget { child: BlocBuilder( builder: (context, state) => SizedBox( height: 32.0, - child: IntrinsicWidth(child: ShareActionList(view: view)), + child: IntrinsicWidth( + child: AppFlowyPopover( + direction: PopoverDirection.bottomWithRightAligned, + constraints: const BoxConstraints( + maxWidth: 422, + ), + margin: const EdgeInsets.all(16), + offset: const Offset(0, 8), + popupBuilder: (context) => const ShareMenu(), + child: RoundedTextButton( + title: LocaleKeys.shareAction_buttonText.tr(), + padding: const EdgeInsets.symmetric(horizontal: 12.0), + fontSize: 14.0, + textColor: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), ), ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_menu.dart new file mode 100644 index 0000000000..de60dba041 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_menu.dart @@ -0,0 +1,120 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +import 'publish_tab.dart'; + +enum ShareMenuTab { + share, + publish, + exportAs; + + static List supportedTabs = [ + ShareMenuTab.share, + ShareMenuTab.publish, + ShareMenuTab.exportAs, + ]; + + String get i18n { + switch (this) { + case ShareMenuTab.share: + return 'Share'; + case ShareMenuTab.publish: + return LocaleKeys.shareAction_publish; + case ShareMenuTab.exportAs: + return 'Export as'; + } + } +} + +class ShareMenu extends StatefulWidget { + const ShareMenu({super.key}); + + @override + State createState() => _ShareMenuState(); +} + +class _ShareMenuState extends State { + ShareMenuTab selectedTab = ShareMenuTab.publish; + + @override + Widget build(BuildContext context) { + final children = { + for (final tab in ShareMenuTab.supportedTabs) + tab: _Segment( + title: tab.i18n.tr(), + isSelected: selectedTab == tab, + ), + }; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + CustomSlidingSegmentedControl( + initialValue: selectedTab, + curve: Curves.linear, + padding: 0, + innerPadding: const EdgeInsets.all(3.0), + children: children, + decoration: BoxDecoration( + color: const Color(0xFFEEF0F3), + borderRadius: BorderRadius.circular(8), + ), + thumbDecoration: BoxDecoration( + color: Colors.white, + boxShadow: const [ + BoxShadow( + color: Color(0x141F2225), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + borderRadius: BorderRadius.circular(8), + ), + onValueChanged: (v) { + setState(() { + selectedTab = v; + }); + }, + ), + _buildTab(context), + ], + ); + } + + Widget _buildTab(BuildContext context) { + switch (selectedTab) { + case ShareMenuTab.publish: + return const PublishTab(); + + default: + return const Center( + child: FlowyText('coming soon'), + ); + } + } +} + +class _Segment extends StatelessWidget { + const _Segment({ + required this.title, + required this.isSelected, + }); + + final String title; + final bool isSelected; + + @override + Widget build(BuildContext context) { + final textColor = isSelected ? null : Theme.of(context).hintColor; + return SizedBox( + width: 128, + child: FlowyText( + title, + textAlign: TextAlign.center, + color: textColor, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/rounded_button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/rounded_button.dart index a73d96f454..0fa181a1dd 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/rounded_button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/rounded_button.dart @@ -13,6 +13,7 @@ class RoundedTextButton extends StatelessWidget { final Color? hoverColor; final Color? textColor; final double? fontSize; + final FontWeight? fontWeight; final EdgeInsets padding; const RoundedTextButton({ @@ -27,6 +28,7 @@ class RoundedTextButton extends StatelessWidget { this.hoverColor, this.textColor, this.fontSize, + this.fontWeight, this.padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 6), }); @@ -42,6 +44,7 @@ class RoundedTextButton extends StatelessWidget { child: SizedBox.expand( child: FlowyTextButton( title ?? '', + fontWeight: fontWeight, onPressed: onPressed, fontSize: fontSize, mainAxisAlignment: MainAxisAlignment.center, diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index e066d6e0dd..ab1e26e02b 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -394,6 +394,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + custom_sliding_segmented_control: + dependency: "direct main" + description: + name: custom_sliding_segmented_control + sha256: "53c3e931c3ae1f696085d1ec70ac8e934da836595a9b7d9b88fdd0fcbf2a5574" + url: "https://pub.dev" + source: hosted + version: "1.8.3" dart_style: dependency: transitive description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index dffab82055..cadea60f33 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -149,6 +149,7 @@ dependencies: bitsdojo_window: ^0.1.6 flutter_highlight: ^0.7.0 + custom_sliding_segmented_control: ^1.8.3 dev_dependencies: flutter_lints: ^3.0.1 diff --git a/frontend/resources/flowy_icons/16x/share_publish.svg b/frontend/resources/flowy_icons/16x/share_publish.svg new file mode 100644 index 0000000000..345208ab90 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/share_publish.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index fd6c1aa38a..1889b527e8 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -106,7 +106,12 @@ "html": "HTML", "clipboard": "Copy to clipboard", "csv": "CSV", - "copyLink": "Copy Link" + "copyLink": "Copy Link", + "publishToTheWeb": "Publish to the web", + "publishToTheWebHint": "Publish a static website of this page and control who see it", + "publish": "Publish", + "unPublish": "UnPublish", + "visitSite": "Visit site" }, "moreAction": { "small": "small",