feat: sidebar UI Revamp on Desktop (#5343)

This commit is contained in:
Lucas.Xu
2024-05-27 08:51:49 +08:00
committed by GitHub
parent 13b3439bd6
commit a8f136eda2
138 changed files with 2678 additions and 1305 deletions

View File

@ -1,13 +1,10 @@
import 'dart:io';
import 'package:appflowy/plugins/base/emoji/emoji_picker_header.dart';
import 'package:appflowy/plugins/base/emoji/emoji_search_bar.dart';
import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
import 'package:google_fonts/google_fonts.dart';
// use a global value to store the selected emoji to prevent reloading every time.
EmojiData? kCachedEmojiData;
@ -28,7 +25,6 @@ class FlowyEmojiPicker extends StatefulWidget {
class _FlowyEmojiPickerState extends State<FlowyEmojiPicker> {
EmojiData? emojiData;
List<String>? fallbackFontFamily;
@override
void initState() {
@ -47,13 +43,6 @@ class _FlowyEmojiPickerState extends State<FlowyEmojiPicker> {
},
);
}
if (Platform.isAndroid || Platform.isLinux) {
final notoColorEmoji = GoogleFonts.notoColorEmoji().fontFamily;
if (notoColorEmoji != null) {
fallbackFontFamily = [notoColorEmoji];
}
}
}
@override
@ -83,16 +72,18 @@ class _FlowyEmojiPickerState extends State<FlowyEmojiPicker> {
);
},
itemBuilder: (context, emojiId, emoji, callback) {
return FlowyIconButton(
iconPadding: PlatformExtension.isWindows
? const EdgeInsets.only(bottom: 2.0)
: const EdgeInsets.all(2),
icon: FlowyText(
emoji,
fontSize: 28.0,
fallbackFontFamily: fallbackFontFamily,
return SizedBox(
width: 36,
height: 36,
child: FlowyButton(
margin: EdgeInsets.zero,
radius: Corners.s8Border,
text: FlowyText.emoji(
emoji,
fontSize: 24.0,
),
onTap: () => callback(emojiId, emoji),
),
onPressed: () => callback(emojiId, emoji),
);
},
searchBarBuilder: (context, keyword, skinTone) {

View File

@ -16,9 +16,14 @@ class FlowyEmojiHeader extends StatelessWidget {
if (PlatformExtension.isDesktopOrWeb) {
return Container(
height: 22,
padding: const EdgeInsets.symmetric(horizontal: 8.0),
color: Theme.of(context).cardColor,
child: FlowyText.regular(category.id),
child: Padding(
padding: const EdgeInsets.only(bottom: 4.0),
child: FlowyText.regular(
category.id,
color: Theme.of(context).hintColor,
),
),
);
} else {
return Column(

View File

@ -42,7 +42,7 @@ class _FlowyEmojiSearchBarState extends State<FlowyEmojiSearchBar> {
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(
vertical: 8.0,
vertical: 12.0,
horizontal: PlatformExtension.isDesktopOrWeb ? 0.0 : 8.0,
),
child: Row(
@ -52,16 +52,15 @@ class _FlowyEmojiSearchBarState extends State<FlowyEmojiSearchBar> {
onKeywordChanged: widget.onKeywordChanged,
),
),
const HSpace(6.0),
const HSpace(8.0),
_RandomEmojiButton(
emojiData: widget.emojiData,
onRandomEmojiSelected: widget.onRandomEmojiSelected,
),
const HSpace(6.0),
const HSpace(8.0),
FlowyEmojiSkinToneSelector(
onEmojiSkinToneChanged: widget.onSkinToneChanged,
),
const HSpace(6.0),
],
),
);
@ -79,20 +78,30 @@ class _RandomEmojiButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlowyTooltip(
message: LocaleKeys.emoji_random.tr(),
child: FlowyButton(
useIntrinsicWidth: true,
text: const Icon(
Icons.shuffle_rounded,
return Container(
width: 36,
height: 36,
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
side: const BorderSide(color: Color(0x1E171717)),
borderRadius: BorderRadius.circular(8),
),
),
child: FlowyTooltip(
message: LocaleKeys.emoji_random.tr(),
child: FlowyButton(
useIntrinsicWidth: true,
text: const FlowySvg(
FlowySvgs.icon_shuffle_s,
),
onTap: () {
final random = emojiData.random;
onRandomEmojiSelected(
random.$1,
random.$2,
);
},
),
onTap: () {
final random = emojiData.random;
onRandomEmojiSelected(
random.$1,
random.$2,
);
},
),
);
}
@ -123,32 +132,35 @@ class _SearchTextFieldState extends State<_SearchTextField> {
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: 32.0,
),
return SizedBox(
height: 36.0,
child: FlowyTextField(
focusNode: focusNode,
hintText: LocaleKeys.emoji_search.tr(),
hintText: LocaleKeys.search_label.tr(),
hintStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 14.0,
fontWeight: FontWeight.w400,
color: Theme.of(context).hintColor,
),
controller: controller,
onChanged: widget.onKeywordChanged,
prefixIcon: const Padding(
padding: EdgeInsets.only(
left: 8.0,
right: 4.0,
left: 14.0,
right: 8.0,
),
child: FlowySvg(
FlowySvgs.search_s,
),
),
prefixIconConstraints: const BoxConstraints(
maxHeight: 18.0,
maxHeight: 20.0,
),
suffixIcon: Padding(
padding: const EdgeInsets.all(4.0),
child: FlowyButton(
text: const FlowySvg(
FlowySvgs.close_lg,
FlowySvgs.m_app_bar_close_s,
),
margin: EdgeInsets.zero,
useIntrinsicWidth: true,

View File

@ -57,7 +57,7 @@ class _FlowyEmojiSkinToneSelectorState
child: FlowyTooltip(
message: LocaleKeys.emoji_selectSkinTone.tr(),
child: _buildIconButton(
lastSelectedEmojiSkinTone?.icon ?? '',
lastSelectedEmojiSkinTone?.icon ?? '👋',
() => controller.show(),
),
),
@ -65,19 +65,22 @@ class _FlowyEmojiSkinToneSelectorState
}
Widget _buildIconButton(String icon, VoidCallback onPressed) {
return FlowyIconButton(
key: emojiSkinToneKey(icon),
icon: Padding(
// add a left padding to align the emoji center
padding: const EdgeInsets.only(
left: 3.0,
),
child: FlowyText(
icon,
fontSize: 22.0,
),
return Container(
width: 36,
height: 36,
decoration: BoxDecoration(
border: Border.all(color: const Color(0x1E171717)),
borderRadius: BorderRadius.circular(8),
),
child: FlowyButton(
key: emojiSkinToneKey(icon),
margin: EdgeInsets.zero,
text: FlowyText.emoji(
icon,
fontSize: 24.0,
),
onTap: onPressed,
),
onPressed: onPressed,
);
}
}
@ -86,17 +89,17 @@ extension EmojiSkinToneIcon on EmojiSkinTone {
String get icon {
switch (this) {
case EmojiSkinTone.none:
return '';
return '👋';
case EmojiSkinTone.light:
return '🏻';
return '👋🏻';
case EmojiSkinTone.mediumLight:
return '🏼';
return '👋🏼';
case EmojiSkinTone.medium:
return '🏽';
return '👋🏽';
case EmojiSkinTone.mediumDark:
return '🏾';
return '👋🏾';
case EmojiSkinTone.dark:
return '🏿';
return '👋🏿';
}
}
}

View File

@ -29,6 +29,7 @@ class EmojiText extends StatelessWidget {
emoji,
fontSize: fontSize,
textAlign: textAlign,
strutStyle: const StrutStyle(forceStrutHeight: true),
fallbackFontFamily: _cachedFallbackFontFamily,
lineHeight: lineHeight,
);

View File

@ -1,13 +1,10 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/icon.pbenum.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter/material.dart';
extension ToProto on FlowyIconType {
ViewIconTypePB toProto() {
@ -54,57 +51,28 @@ class FlowyIconPicker extends StatelessWidget {
@override
Widget build(BuildContext context) {
// ONLY supports emoji picker for now
return DefaultTabController(
length: 1,
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const VSpace(8.0),
Row(
children: [
_buildTabs(context),
FlowyText(LocaleKeys.newSettings_workplace_chooseAnIcon.tr()),
const Spacer(),
_RemoveIconButton(
onTap: () => onSelected(EmojiPickerResult.none()),
),
],
),
const Divider(height: 2),
const VSpace(12.0),
const Divider(height: 0.5),
Expanded(
child: TabBarView(
children: [
FlowyEmojiPicker(
emojiPerLine: _getEmojiPerLine(context),
onEmojiSelected: (_, emoji) =>
onSelected(EmojiPickerResult.emoji(emoji)),
),
],
),
),
],
),
);
}
Widget _buildTabs(BuildContext context) {
return Align(
alignment: Alignment.centerLeft,
child: TabBar(
indicatorSize: TabBarIndicatorSize.label,
isScrollable: true,
overlayColor: WidgetStatePropertyAll(
Theme.of(context).colorScheme.secondary,
),
padding: EdgeInsets.zero,
tabs: [
FlowyHover(
style: const HoverStyle(borderRadius: BorderRadius.zero),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 8.0,
),
child: FlowyText(LocaleKeys.emoji_emojiTab.tr()),
child: FlowyEmojiPicker(
emojiPerLine: _getEmojiPerLine(context),
onEmojiSelected: (_, emoji) =>
onSelected(EmojiPickerResult.emoji(emoji)),
),
),
],
@ -117,7 +85,7 @@ class FlowyIconPicker extends StatelessWidget {
return 9;
}
final width = MediaQuery.of(context).size.width;
return width ~/ 46.0; // the size of the emoji
return width ~/ 40.0; // the size of the emoji
}
}
@ -129,14 +97,14 @@ class _RemoveIconButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox(
height: 28,
height: 24,
child: FlowyButton(
onTap: onTap,
useIntrinsicWidth: true,
text: FlowyText.small(
LocaleKeys.document_plugins_cover_removeIcon.tr(),
text: FlowyText.regular(
LocaleKeys.button_remove.tr(),
color: Theme.of(context).hintColor,
),
leftIcon: const FlowySvg(FlowySvgs.delete_s),
),
);
}

View File

@ -236,7 +236,8 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
final String? initialRowId;
@override
Widget get leftBarItem => ViewTitleBar(view: notifier.view);
Widget get leftBarItem =>
ViewTitleBar(key: ValueKey(notifier.view.id), view: notifier.view);
@override
Widget tabBarItem(String pluginId) => ViewTabBarItem(view: notifier.view);
@ -278,7 +279,7 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
]
: [],
DatabaseShareButton(key: ValueKey(view.id), view: view),
const HSpace(4),
const HSpace(10),
ViewFavoriteButton(view: view),
const HSpace(4),
MoreViewActions(view: view, isDocument: false),

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/share_bloc.dart';
import 'package:appflowy/startup/startup.dart';
@ -13,6 +11,7 @@ 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/widget/rounded_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class DatabaseShareButton extends StatelessWidget {
@ -39,11 +38,7 @@ class DatabaseShareButton extends StatelessWidget {
);
},
child: BlocBuilder<DatabaseShareBloc, DatabaseShareState>(
builder: (context, state) => ConstrainedBox(
constraints: const BoxConstraints.expand(
height: 30,
width: 100,
),
builder: (context, state) => IntrinsicWidth(
child: DatabaseShareActionList(view: view),
),
),
@ -106,6 +101,8 @@ class DatabaseShareActionListState extends State<DatabaseShareActionList> {
onPointerDown: (_) => controller.show(),
child: RoundedTextButton(
title: LocaleKeys.shareAction_buttonText.tr(),
padding: const EdgeInsets.symmetric(horizontal: 12.0),
fontSize: 14.0,
textColor: Theme.of(context).colorScheme.onPrimary,
onPressed: () {},
),

View File

@ -1,7 +1,5 @@
library document_plugin;
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
@ -22,6 +20,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.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';
class DocumentPluginBuilder extends PluginBuilder {
@ -130,7 +129,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
}
@override
Widget get leftBarItem => ViewTitleBar(view: view);
Widget get leftBarItem => ViewTitleBar(key: ValueKey(view.id), view: view);
@override
Widget tabBarItem(String pluginId) => ViewTabBarItem(view: notifier.view);
@ -162,7 +161,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
key: ValueKey('share_button_${view.id}'),
view: view,
),
const HSpace(4),
const HSpace(10),
ViewFavoriteButton(
key: ValueKey('favorite_button_${view.id}'),
view: view,

View File

@ -392,12 +392,6 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
if (widget.editorState.document.isEmpty) {
return (true, Selection.collapsed(Position(path: [0])));
}
final nodes =
widget.editorState.document.root.children.where((e) => e.delta != null);
final isAllEmpty = nodes.isNotEmpty && nodes.every((e) => e.delta!.isEmpty);
if (isAllEmpty) {
return (true, Selection.collapsed(Position(path: nodes.first.path)));
}
return const (false, null);
}

View File

@ -141,7 +141,7 @@ enum OptionDepthType {
class DividerOptionAction extends CustomActionCell {
@override
Widget buildWithContext(BuildContext context) {
Widget buildWithContext(BuildContext context, PopoverController controller) {
return const Divider(
height: 1.0,
thickness: 1.0,

View File

@ -1,11 +1,10 @@
import 'package:flutter/material.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class EmojiPickerButton extends StatelessWidget {
@ -19,6 +18,7 @@ class EmojiPickerButton extends StatelessWidget {
this.offset,
this.direction,
this.title,
this.showBorder = true,
});
final String emoji;
@ -30,6 +30,7 @@ class EmojiPickerButton extends StatelessWidget {
final Offset? offset;
final PopoverDirection? direction;
final String? title;
final bool showBorder;
@override
Widget build(BuildContext context) {
@ -51,22 +52,28 @@ class EmojiPickerButton extends StatelessWidget {
onExit: () {},
),
),
child: emoji.isEmpty && defaultIcon != null
? FlowyButton(
useIntrinsicWidth: true,
text: defaultIcon!,
onTap: popoverController.show,
)
: FlowyTextButton(
emoji,
overflow: TextOverflow.visible,
fontSize: emojiSize,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 35.0),
fillColor: Colors.transparent,
mainAxisAlignment: MainAxisAlignment.center,
onPressed: popoverController.show,
),
child: Container(
width: 30.0,
height: 30.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: showBorder
? Border.all(
color: Theme.of(context).dividerColor,
)
: null,
),
child: FlowyButton(
margin: emoji.isEmpty && defaultIcon != null
? EdgeInsets.zero
: const EdgeInsets.only(left: 2.0),
expandText: false,
text: emoji.isEmpty && defaultIcon != null
? defaultIcon!
: FlowyText.emoji(emoji, fontSize: emojiSize),
onTap: popoverController.show,
),
),
);
}
return FlowyTextButton(

View File

@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/text_input.dart';
import 'package:flutter/material.dart';

View File

@ -300,9 +300,10 @@ class _DocumentHeaderToolbarState extends State<DocumentHeaderToolbar> {
: (CoverType.color, '0xffe8e0ff'),
),
useIntrinsicWidth: true,
leftIcon: const FlowySvg(FlowySvgs.image_s),
leftIcon: const FlowySvg(FlowySvgs.add_cover_s),
text: FlowyText.small(
LocaleKeys.document_plugins_cover_addCover.tr(),
color: Theme.of(context).hintColor,
),
),
);
@ -311,28 +312,24 @@ class _DocumentHeaderToolbarState extends State<DocumentHeaderToolbar> {
if (widget.hasIcon) {
children.add(
FlowyButton(
leftIconSize: const Size.square(18),
onTap: () => widget.onIconOrCoverChanged(icon: ""),
useIntrinsicWidth: true,
leftIcon: const Icon(
Icons.emoji_emotions_outlined,
size: 18,
),
leftIcon: const FlowySvg(FlowySvgs.add_icon_s),
iconPadding: 4.0,
text: FlowyText.small(
LocaleKeys.document_plugins_cover_removeIcon.tr(),
color: Theme.of(context).hintColor,
),
),
);
} else {
Widget child = FlowyButton(
leftIconSize: const Size.square(18),
useIntrinsicWidth: true,
leftIcon: const Icon(
Icons.emoji_emotions_outlined,
size: 18,
),
leftIcon: const FlowySvg(FlowySvgs.add_icon_s),
iconPadding: 4.0,
text: FlowyText.small(
LocaleKeys.document_plugins_cover_addIcon.tr(),
color: Theme.of(context).hintColor,
),
onTap: PlatformExtension.isDesktop
? null

View File

@ -148,7 +148,7 @@ class _UnsplashImages extends StatelessWidget {
type: type,
photo: photo,
onTap: () => onSelectUnsplashImage(
photo.urls.regular.toString(),
photo.urls.full.toString(),
),
),
)

View File

@ -41,12 +41,9 @@ class DocumentShareButton extends StatelessWidget {
);
},
child: BlocBuilder<DocumentShareBloc, DocumentShareState>(
builder: (context, state) => ConstrainedBox(
constraints: const BoxConstraints.expand(
height: 30,
width: 100,
),
child: ShareActionList(view: view),
builder: (context, state) => SizedBox(
height: 32.0,
child: IntrinsicWidth(child: ShareActionList(view: view)),
),
),
),
@ -120,7 +117,9 @@ class ShareActionListState extends State<ShareActionList> {
onPointerDown: (_) => controller.show(),
child: RoundedTextButton(
title: LocaleKeys.shareAction_buttonText.tr(),
padding: const EdgeInsets.symmetric(horizontal: 12.0),
onPressed: () {},
fontSize: 14.0,
textColor: Theme.of(context).colorScheme.onPrimary,
),
),