feat: refactor space icon picker (#5878)

* feat: refactor space icon picker

* chore: optimize the _loadIconGroups function

* feat: refactor emoji picker

* feat: integrate icon picker into flowy_icon_emoji_picker

* feat: support searching icon

* feat: support displaying new icons

* fix: flutter analyze

* chore: join lines

* feat: support space icon in view title

* feat: support customzing icon when creating space or managing space

* feat: customize the emoji picker and icon picker padding

* feat: shuffle icon

* fix: expand popup menu font size

* fix: flutter integration test
This commit is contained in:
Lucas.Xu 2024-08-06 11:47:38 +08:00 committed by GitHub
parent 4041724980
commit 453e6309d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
73 changed files with 1180 additions and 305 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,13 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart'; import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart'; import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart';
@ -15,8 +11,11 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/header/doc
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart';
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart'; import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart'; import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -85,7 +84,7 @@ class EditorOperations {
final Finder button = !isInPicker final Finder button = !isInPicker
? find.text(LocaleKeys.document_plugins_cover_removeIcon.tr()) ? find.text(LocaleKeys.document_plugins_cover_removeIcon.tr())
: find.descendant( : find.descendant(
of: find.byType(FlowyIconPicker), of: find.byType(FlowyIconEmojiPicker),
matching: find.text(LocaleKeys.button_remove.tr()), matching: find.text(LocaleKeys.button_remove.tr()),
); );
await tester.tapButton(button); await tester.tapButton(button);

View File

@ -1,5 +1,5 @@
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
@ -58,7 +58,7 @@ extension AppFlowyWorkspace on WidgetTester {
); );
expect(iconButton, findsOneWidget); expect(iconButton, findsOneWidget);
await tapButton(iconButton); await tapButton(iconButton);
final iconPicker = find.byType(FlowyIconPicker); final iconPicker = find.byType(FlowyIconEmojiPicker);
expect(iconPicker, findsOneWidget); expect(iconPicker, findsOneWidget);
await tapButton(find.findTextInFlowyText(icon)); await tapButton(find.findTextInFlowyText(icon));
} }

View File

@ -5,7 +5,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/home/mobile_home_setting_page.dart'; import 'package:appflowy/mobile/presentation/home/mobile_home_setting_page.dart';
import 'package:appflowy/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart'; import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/built_in_svgs.dart'; import 'package:appflowy/util/built_in_svgs.dart';
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart'; import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
@ -133,6 +133,7 @@ class _MobileWorkspace extends StatelessWidget {
fontSize: 16.0, fontSize: 16.0,
enableEdit: false, enableEdit: false,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
figmaLineHeight: 16.0,
onSelected: (result) => context.read<UserWorkspaceBloc>().add( onSelected: (result) => context.read<UserWorkspaceBloc>().add(
UserWorkspaceEvent.updateWorkspaceIcon( UserWorkspaceEvent.updateWorkspaceIcon(
currentWorkspace.workspaceId, currentWorkspace.workspaceId,

View File

@ -144,6 +144,7 @@ class _WorkspaceMenuItem extends StatelessWidget {
enableEdit: false, enableEdit: false,
iconSize: 26, iconSize: 26,
fontSize: 16.0, fontSize: 16.0,
figmaLineHeight: 16.0,
workspace: workspace, workspace: workspace,
onSelected: (result) => context.read<UserWorkspaceBloc>().add( onSelected: (result) => context.read<UserWorkspaceBloc>().add(
UserWorkspaceEvent.updateWorkspaceIcon( UserWorkspaceEvent.updateWorkspaceIcon(

View File

@ -68,17 +68,16 @@ class MobileNotificationTabBar extends StatelessWidget {
controller: tabController, controller: tabController,
tabs: tabs.map((e) => Tab(text: e.tr)).toList(), tabs: tabs.map((e) => Tab(text: e.tr)).toList(),
indicatorSize: TabBarIndicatorSize.label, indicatorSize: TabBarIndicatorSize.label,
indicatorColor: Theme.of(context).primaryColor,
isScrollable: true, isScrollable: true,
labelStyle: labelStyle, labelStyle: labelStyle,
labelColor: baseStyle?.color, labelColor: baseStyle?.color,
labelPadding: const EdgeInsets.symmetric(horizontal: 12.0), labelPadding: const EdgeInsets.symmetric(horizontal: 12.0),
unselectedLabelStyle: unselectedLabelStyle, unselectedLabelStyle: unselectedLabelStyle,
overlayColor: WidgetStateProperty.all(Colors.transparent), overlayColor: WidgetStateProperty.all(Colors.transparent),
indicator: RoundUnderlineTabIndicator( indicator: const RoundUnderlineTabIndicator(
width: 28.0, width: 28.0,
borderSide: BorderSide( borderSide: BorderSide(
color: Theme.of(context).primaryColor, color: Color(0xFF00C8FF),
width: 3, width: 3,
), ),
), ),

View File

@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker_header.dart'; 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/plugins/base/emoji/emoji_skin_tone.dart';
import 'package:appflowy/shared/icon_emoji_picker/emoji_search_bar.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.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:flutter_emoji_mart/flutter_emoji_mart.dart';
// use a global value to store the selected emoji to prevent reloading every time. // use a global value to store the selected emoji to prevent reloading every time.
@ -65,36 +64,43 @@ class _FlowyEmojiPickerState extends State<FlowyEmojiPicker> {
perLine: widget.emojiPerLine, perLine: widget.emojiPerLine,
), ),
onEmojiSelected: widget.onEmojiSelected, onEmojiSelected: widget.onEmojiSelected,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
headerBuilder: (context, category) { headerBuilder: (context, category) {
return FlowyEmojiHeader( return FlowyEmojiHeader(
category: category, category: category,
); );
}, },
itemBuilder: (context, emojiId, emoji, callback) { itemBuilder: (context, emojiId, emoji, callback) {
return SizedBox( final name = emojiData?.emojis[emojiId]?.name ?? '';
width: 36, return SizedBox.square(
height: 36, dimension: 36.0,
child: FlowyButton( child: FlowyButton(
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
radius: Corners.s8Border, radius: Corners.s8Border,
text: FlowyText.emoji( text: FlowyTooltip(
emoji, message: name,
fontSize: 24.0, child: FlowyText.emoji(
emoji,
fontSize: 24.0,
),
), ),
onTap: () => callback(emojiId, emoji), onTap: () => callback(emojiId, emoji),
), ),
); );
}, },
searchBarBuilder: (context, keyword, skinTone) { searchBarBuilder: (context, keyword, skinTone) {
return FlowyEmojiSearchBar( return Padding(
emojiData: emojiData!, padding: const EdgeInsets.symmetric(horizontal: 16.0),
onKeywordChanged: (value) { child: FlowyEmojiSearchBar(
keyword.value = value; emojiData: emojiData!,
}, onKeywordChanged: (value) {
onSkinToneChanged: (value) { keyword.value = value;
skinTone.value = value; },
}, onSkinToneChanged: (value) {
onRandomEmojiSelected: widget.onEmojiSelected, skinTone.value = value;
},
onRandomEmojiSelected: widget.onEmojiSelected,
),
); );
}, },
); );

View File

@ -1,5 +1,5 @@
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
import 'package:appflowy/plugins/base/icon/icon_picker_page.dart'; import 'package:appflowy/plugins/base/icon/icon_picker_page.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';

View File

@ -2,7 +2,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart'; import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';

View File

@ -1,111 +0,0 @@
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:flutter/material.dart';
extension ToProto on FlowyIconType {
ViewIconTypePB toProto() {
switch (this) {
case FlowyIconType.emoji:
return ViewIconTypePB.Emoji;
case FlowyIconType.icon:
return ViewIconTypePB.Icon;
case FlowyIconType.custom:
return ViewIconTypePB.Url;
}
}
}
enum FlowyIconType {
emoji,
icon,
custom;
}
class EmojiPickerResult {
factory EmojiPickerResult.none() =>
const EmojiPickerResult(FlowyIconType.icon, '');
factory EmojiPickerResult.emoji(String emoji) =>
EmojiPickerResult(FlowyIconType.emoji, emoji);
const EmojiPickerResult(
this.type,
this.emoji,
);
final FlowyIconType type;
final String emoji;
}
class FlowyIconPicker extends StatelessWidget {
const FlowyIconPicker({
super.key,
required this.onSelected,
});
final void Function(EmojiPickerResult result) onSelected;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const VSpace(8.0),
Row(
children: [
FlowyText(LocaleKeys.newSettings_workplace_chooseAnIcon.tr()),
const Spacer(),
_RemoveIconButton(
onTap: () => onSelected(EmojiPickerResult.none()),
),
],
),
const VSpace(12.0),
const Divider(height: 0.5),
Expanded(
child: FlowyEmojiPicker(
emojiPerLine: _getEmojiPerLine(context),
onEmojiSelected: (_, emoji) =>
onSelected(EmojiPickerResult.emoji(emoji)),
),
),
],
),
);
}
int _getEmojiPerLine(BuildContext context) {
if (PlatformExtension.isDesktopOrWeb) {
return 9;
}
final width = MediaQuery.of(context).size.width;
return width ~/ 40.0; // the size of the emoji
}
}
class _RemoveIconButton extends StatelessWidget {
const _RemoveIconButton({required this.onTap});
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 24,
child: FlowyButton(
onTap: onTap,
useIntrinsicWidth: true,
text: FlowyText.regular(
LocaleKeys.button_remove.tr(),
color: Theme.of(context).hintColor,
),
),
);
}
}

View File

@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart'; import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
class IconPickerPage extends StatelessWidget { class IconPickerPage extends StatelessWidget {
const IconPickerPage({ const IconPickerPage({
@ -22,7 +21,7 @@ class IconPickerPage extends StatelessWidget {
titleText: title ?? LocaleKeys.titleBar_pageIcon.tr(), titleText: title ?? LocaleKeys.titleBar_pageIcon.tr(),
), ),
body: SafeArea( body: SafeArea(
child: FlowyIconPicker(onSelected: onSelected), child: FlowyIconEmojiPicker(onSelectedEmoji: onSelected),
), ),
); );
} }

View File

@ -24,7 +24,6 @@ import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart' hide Card; import 'package:flutter/material.dart' hide Card;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -9,7 +9,6 @@ import 'package:appflowy_board/appflowy_board.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -17,7 +17,6 @@ import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -21,7 +21,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class CalendarEventEditor extends StatelessWidget { class CalendarEventEditor extends StatelessWidget {

View File

@ -20,7 +20,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';

View File

@ -13,7 +13,6 @@ import 'package:appflowy_backend/protobuf/flowy-database2/number_entities.pb.dar
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -6,7 +6,6 @@ import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -7,7 +7,6 @@ import 'package:appflowy/plugins/database/widgets/cell_editor/date_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import '../editable_cell_skeleton/date.dart'; import '../editable_cell_skeleton/date.dart';

View File

@ -8,7 +8,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';

View File

@ -7,7 +7,6 @@ import 'package:appflowy/plugins/database/widgets/cell_editor/date_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class DesktopRowDetailDateCellSkin extends IEditableDateCellSkin { class DesktopRowDetailDateCellSkin extends IEditableDateCellSkin {

View File

@ -13,7 +13,6 @@ import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/em
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -12,7 +12,6 @@ import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -15,7 +15,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -5,7 +5,6 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:avatar_stack/avatar_stack.dart'; import 'package:avatar_stack/avatar_stack.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:string_validator/string_validator.dart'; import 'package:string_validator/string_validator.dart';

View File

@ -4,7 +4,6 @@ import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
const String leftAlignmentKey = 'left'; const String leftAlignmentKey = 'left';

View File

@ -1,5 +1,5 @@
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart'; import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';

View File

@ -1,6 +1,5 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class MenuBlockButton extends StatelessWidget { class MenuBlockButton extends StatelessWidget {

View File

@ -1,13 +1,10 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart'; import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/desktop_cover.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/desktop_cover.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
@ -17,6 +14,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/uplo
import 'package:appflowy/plugins/document/presentation/editor_plugins/migration/editor_migration.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/migration/editor_migration.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/shared/appflowy_network_image.dart'; import 'package:appflowy/shared/appflowy_network_image.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/application/view/view_listener.dart'; import 'package:appflowy/workspace/application/view/view_listener.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
@ -25,6 +23,7 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:string_validator/string_validator.dart'; import 'package:string_validator/string_validator.dart';
@ -352,11 +351,12 @@ class _DocumentHeaderToolbarState extends State<DocumentHeaderToolbar> {
offset: const Offset(0, 8), offset: const Offset(0, 8),
direction: PopoverDirection.bottomWithCenterAligned, direction: PopoverDirection.bottomWithCenterAligned,
constraints: BoxConstraints.loose(const Size(360, 380)), constraints: BoxConstraints.loose(const Size(360, 380)),
margin: EdgeInsets.zero,
child: child, child: child,
popupBuilder: (BuildContext popoverContext) { popupBuilder: (BuildContext popoverContext) {
isPopoverOpen = true; isPopoverOpen = true;
return FlowyIconPicker( return FlowyIconEmojiPicker(
onSelected: (result) { onSelectedEmoji: (result) {
widget.onIconOrCoverChanged(icon: result.emoji); widget.onIconOrCoverChanged(icon: result.emoji);
_popoverController.close(); _popoverController.close();
}, },
@ -725,10 +725,11 @@ class _DocumentIconState extends State<DocumentIcon> {
controller: _popoverController, controller: _popoverController,
offset: const Offset(0, 8), offset: const Offset(0, 8),
constraints: BoxConstraints.loose(const Size(360, 380)), constraints: BoxConstraints.loose(const Size(360, 380)),
margin: EdgeInsets.zero,
child: child, child: child,
popupBuilder: (BuildContext popoverContext) { popupBuilder: (BuildContext popoverContext) {
return FlowyIconPicker( return FlowyIconEmojiPicker(
onSelected: (result) { onSelectedEmoji: (result) {
widget.onChangeIcon(result.emoji); widget.onChangeIcon(result.emoji);
_popoverController.close(); _popoverController.close();
}, },

View File

@ -15,7 +15,6 @@ import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -4,7 +4,6 @@ import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart'; import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
@ -142,6 +141,7 @@ class _SearchTextFieldState extends State<_SearchTextField> {
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Theme.of(context).hintColor, color: Theme.of(context).hintColor,
), ),
enableBorderColor: const Color(0x1E171717),
controller: controller, controller: controller,
onChanged: widget.onKeywordChanged, onChanged: widget.onKeywordChanged,
prefixIcon: const Padding( prefixIcon: const Padding(

View File

@ -0,0 +1,180 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/tab.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:flutter/material.dart' hide Icon;
import 'icon.dart';
extension ToProto on FlowyIconType {
ViewIconTypePB toProto() {
switch (this) {
case FlowyIconType.emoji:
return ViewIconTypePB.Emoji;
case FlowyIconType.icon:
return ViewIconTypePB.Icon;
case FlowyIconType.custom:
return ViewIconTypePB.Url;
}
}
}
enum FlowyIconType {
emoji,
icon,
custom;
}
class EmojiPickerResult {
factory EmojiPickerResult.none() =>
const EmojiPickerResult(FlowyIconType.icon, '');
factory EmojiPickerResult.emoji(String emoji) =>
EmojiPickerResult(FlowyIconType.emoji, emoji);
const EmojiPickerResult(
this.type,
this.emoji,
);
final FlowyIconType type;
final String emoji;
}
class FlowyIconEmojiPicker extends StatefulWidget {
const FlowyIconEmojiPicker({
super.key,
this.onSelectedEmoji,
this.onSelectedIcon,
this.tabs = const [PickerTabType.emoji],
});
final void Function(EmojiPickerResult result)? onSelectedEmoji;
final void Function(IconGroup? group, Icon? icon, String? color)?
onSelectedIcon;
final List<PickerTabType> tabs;
@override
State<FlowyIconEmojiPicker> createState() => _FlowyIconEmojiPickerState();
}
class _FlowyIconEmojiPickerState extends State<FlowyIconEmojiPicker>
with SingleTickerProviderStateMixin {
late final controller = TabController(
length: widget.tabs.length,
vsync: this,
);
int currentIndex = 0;
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 46,
padding: const EdgeInsets.only(left: 4.0, right: 12.0),
child: Row(
children: [
Expanded(
child: PickerTab(
controller: controller,
tabs: widget.tabs,
onTap: (index) => currentIndex = index,
),
),
_RemoveIconButton(
onTap: () {
final currentTab = widget.tabs[currentIndex];
if (currentTab == PickerTabType.emoji) {
widget.onSelectedEmoji?.call(
EmojiPickerResult.none(),
);
} else {
widget.onSelectedIcon?.call(null, null, null);
}
},
),
],
),
),
const FlowyDivider(),
Expanded(
child: TabBarView(
controller: controller,
children: widget.tabs.map((tab) {
switch (tab) {
case PickerTabType.emoji:
return _buildEmojiPicker();
case PickerTabType.icon:
return _buildIconPicker();
}
}).toList(),
),
),
],
);
}
Widget _buildEmojiPicker() {
return FlowyEmojiPicker(
emojiPerLine: _getEmojiPerLine(context),
onEmojiSelected: (_, emoji) => widget.onSelectedEmoji?.call(
EmojiPickerResult.emoji(emoji),
),
);
}
int _getEmojiPerLine(BuildContext context) {
if (PlatformExtension.isDesktopOrWeb) {
return 9;
}
final width = MediaQuery.of(context).size.width;
return width ~/ 40.0; // the size of the emoji
}
Widget _buildIconPicker() {
return Expanded(
child: FlowyIconPicker(
onSelectedIcon: (iconGroup, icon, color) {
debugPrint('icon: ${icon.toJson()}, color: $color');
widget.onSelectedIcon?.call(iconGroup, icon, color);
},
),
);
}
}
class _RemoveIconButton extends StatelessWidget {
const _RemoveIconButton({required this.onTap});
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 32,
child: FlowyButton(
onTap: onTap,
useIntrinsicWidth: true,
text: FlowyText(
fontSize: 14.0,
figmaLineHeight: 16.0,
fontWeight: FontWeight.w500,
LocaleKeys.button_remove.tr(),
color: Theme.of(context).hintColor,
),
),
);
}
}

View File

@ -0,0 +1,62 @@
import 'package:json_annotation/json_annotation.dart';
part 'icon.g.dart';
@JsonSerializable()
class IconGroup {
factory IconGroup.fromJson(Map<String, dynamic> json) =>
_$IconGroupFromJson(json);
factory IconGroup.fromMapEntry(MapEntry<String, dynamic> entry) =>
IconGroup.fromJson({
'name': entry.key,
'icons': entry.value,
});
IconGroup({
required this.name,
required this.icons,
});
final String name;
final List<Icon> icons;
String get displayName => name.replaceAll('_', ' ');
IconGroup filter(String keyword) {
final filteredIcons = icons
.where(
(icon) => icon.keywords.any((k) => k.contains(keyword.toLowerCase())),
)
.toList();
return IconGroup(name: name, icons: filteredIcons);
}
String? getSvgContent(String iconName) {
final icon = icons.firstWhere(
(icon) => icon.name == iconName,
);
return icon.content;
}
Map<String, dynamic> toJson() => _$IconGroupToJson(this);
}
@JsonSerializable()
class Icon {
factory Icon.fromJson(Map<String, dynamic> json) => _$IconFromJson(json);
Icon({
required this.name,
required this.keywords,
required this.content,
});
final String name;
final List<String> keywords;
final String content;
String get displayName => name.replaceAll('-', ' ');
Map<String, dynamic> toJson() => _$IconToJson(this);
}

View File

@ -0,0 +1,44 @@
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter/material.dart';
class IconColorPicker extends StatelessWidget {
const IconColorPicker({
super.key,
required this.onSelected,
});
final void Function(String color) onSelected;
@override
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
crossAxisCount: 6,
mainAxisSpacing: 4.0,
children: builtInSpaceColors.map((color) {
return FlowyHover(
style: HoverStyle(borderRadius: BorderRadius.circular(8.0)),
child: GestureDetector(
onTap: () => onSelected(color),
child: Container(
width: 34,
height: 34,
padding: const EdgeInsets.all(5.0),
child: Container(
clipBehavior: Clip.antiAlias,
decoration: ShapeDecoration(
color: Color(int.parse(color)),
shape: RoundedRectangleBorder(
side: const BorderSide(color: Color(0x2D333333)),
borderRadius: BorderRadius.circular(8),
),
),
),
),
),
);
}).toList(),
);
}
}

View File

@ -0,0 +1,271 @@
import 'dart:convert';
import 'dart:math';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_search_bar.dart';
import 'package:appflowy/util/debounce.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart' hide Icon;
import 'package:flutter/services.dart';
import 'icon_color_picker.dart';
// cache the icon groups to avoid loading them multiple times
List<IconGroup>? kIconGroups;
extension IconGroupFilter on List<IconGroup> {
String? findSvgContent(String key) {
final values = key.split('/');
if (values.length != 2) {
return null;
}
final groupName = values[0];
final iconName = values[1];
final svgString = kIconGroups
?.firstWhereOrNull(
(group) => group.name == groupName,
)
?.icons
.firstWhereOrNull(
(icon) => icon.name == iconName,
)
?.content;
return svgString;
}
(IconGroup, Icon) randomIcon() {
final random = Random();
final group = this[random.nextInt(length)];
final icon = group.icons[random.nextInt(group.icons.length)];
return (group, icon);
}
}
Future<List<IconGroup>> loadIconGroups() async {
if (kIconGroups != null) {
return kIconGroups!;
}
final stopwatch = Stopwatch()..start();
final jsonString = await rootBundle.loadString('assets/icons/icons.json');
try {
final json = jsonDecode(jsonString) as Map<String, dynamic>;
final iconGroups = json.entries.map(IconGroup.fromMapEntry).toList();
kIconGroups = iconGroups;
return iconGroups;
} catch (e) {
Log.error('Failed to decode icons.json', e);
return [];
} finally {
stopwatch.stop();
Log.info('Loaded icon groups in ${stopwatch.elapsedMilliseconds}ms');
}
}
class FlowyIconPicker extends StatefulWidget {
const FlowyIconPicker({
super.key,
required this.onSelectedIcon,
});
final void Function(IconGroup group, Icon icon, String color) onSelectedIcon;
@override
State<FlowyIconPicker> createState() => _FlowyIconPickerState();
}
class _FlowyIconPickerState extends State<FlowyIconPicker> {
late final Future<List<IconGroup>> iconGroups;
final ValueNotifier<String> keyword = ValueNotifier('');
final debounce = Debounce(duration: const Duration(milliseconds: 150));
@override
void initState() {
super.initState();
iconGroups = loadIconGroups();
}
@override
void dispose() {
keyword.dispose();
debounce.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: IconSearchBar(
onRandomTap: () {
final value = kIconGroups?.randomIcon();
if (value == null) {
return;
}
final color = generateRandomSpaceColor();
widget.onSelectedIcon(value.$1, value.$2, color);
},
onKeywordChanged: (keyword) => {
debounce.call(() {
this.keyword.value = keyword;
}),
},
),
),
Expanded(
child: kIconGroups != null
? _buildIcons(kIconGroups!)
: FutureBuilder(
future: iconGroups,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const Center(
child: SizedBox.square(
dimension: 24.0,
child: CircularProgressIndicator(
strokeWidth: 2.0,
),
),
);
}
final iconGroups = snapshot.data as List<IconGroup>;
return _buildIcons(iconGroups);
},
),
),
],
);
}
Widget _buildIcons(List<IconGroup> iconGroups) {
return ValueListenableBuilder(
valueListenable: keyword,
builder: (_, keyword, __) {
if (keyword.isNotEmpty) {
final filteredIconGroups = iconGroups
.map((iconGroup) => iconGroup.filter(keyword))
.where((iconGroup) => iconGroup.icons.isNotEmpty)
.toList();
return IconPicker(
iconGroups: filteredIconGroups,
onSelectedIcon: widget.onSelectedIcon,
);
}
return IconPicker(
iconGroups: iconGroups,
onSelectedIcon: widget.onSelectedIcon,
);
},
);
}
}
class IconPicker extends StatefulWidget {
const IconPicker({
super.key,
required this.onSelectedIcon,
required this.iconGroups,
});
final List<IconGroup> iconGroups;
final void Function(IconGroup group, Icon icon, String color) onSelectedIcon;
@override
State<IconPicker> createState() => _IconPickerState();
}
class _IconPickerState extends State<IconPicker> {
final mutex = PopoverMutex();
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: widget.iconGroups.length,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
itemBuilder: (context, index) {
final iconGroup = widget.iconGroups[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FlowyText(
iconGroup.displayName,
fontSize: 12,
figmaLineHeight: 18.0,
color: const Color(0x80171717),
),
const VSpace(4.0),
Wrap(
children: iconGroup.icons.map(
(icon) {
return _Icon(
icon: icon,
mutex: mutex,
onSelectedColor: (context, color) {
widget.onSelectedIcon(iconGroup, icon, color);
PopoverContainer.of(context).close();
},
);
},
).toList(),
),
const VSpace(12.0),
],
);
},
);
}
}
class _Icon extends StatelessWidget {
const _Icon({
required this.icon,
required this.mutex,
required this.onSelectedColor,
});
final Icon icon;
final PopoverMutex mutex;
final void Function(BuildContext context, String color) onSelectedColor;
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
direction: PopoverDirection.bottomWithCenterAligned,
offset: const Offset(0, 6),
mutex: mutex,
child: FlowyTooltip(
message: icon.displayName,
preferBelow: false,
child: FlowyButton(
useIntrinsicWidth: true,
margin: const EdgeInsets.all(8.0),
text: Center(
child: FlowySvg.string(
icon.content,
size: const Size.square(20),
color: const Color(0xFF171717),
opacity: 0.7,
),
),
),
),
popupBuilder: (context) {
return Container(
padding: const EdgeInsets.all(6.0),
child: IconColorPicker(
onSelected: (color) => onSelectedColor(context, color),
),
);
},
);
}
}

View File

@ -0,0 +1,162 @@
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';
import 'package:flutter/material.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
typedef IconKeywordChangedCallback = void Function(String keyword);
typedef EmojiSkinToneChanged = void Function(EmojiSkinTone skinTone);
class IconSearchBar extends StatefulWidget {
const IconSearchBar({
super.key,
required this.onRandomTap,
required this.onKeywordChanged,
});
final VoidCallback onRandomTap;
final IconKeywordChangedCallback onKeywordChanged;
@override
State<IconSearchBar> createState() => _IconSearchBarState();
}
class _IconSearchBarState extends State<IconSearchBar> {
final TextEditingController controller = TextEditingController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(
vertical: 12.0,
horizontal: PlatformExtension.isDesktopOrWeb ? 0.0 : 8.0,
),
child: Row(
children: [
Expanded(
child: _SearchTextField(
onKeywordChanged: widget.onKeywordChanged,
),
),
const HSpace(8.0),
_RandomIconButton(
onRandomTap: widget.onRandomTap,
),
],
),
);
}
}
class _RandomIconButton extends StatelessWidget {
const _RandomIconButton({
required this.onRandomTap,
});
final VoidCallback onRandomTap;
@override
Widget build(BuildContext context) {
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: onRandomTap,
),
),
);
}
}
class _SearchTextField extends StatefulWidget {
const _SearchTextField({
required this.onKeywordChanged,
});
final IconKeywordChangedCallback onKeywordChanged;
@override
State<_SearchTextField> createState() => _SearchTextFieldState();
}
class _SearchTextFieldState extends State<_SearchTextField> {
final TextEditingController controller = TextEditingController();
final FocusNode focusNode = FocusNode();
@override
void dispose() {
controller.dispose();
focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 36.0,
child: FlowyTextField(
focusNode: focusNode,
hintText: LocaleKeys.search_label.tr(),
hintStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 14.0,
fontWeight: FontWeight.w400,
color: Theme.of(context).hintColor,
),
enableBorderColor: const Color(0x1E171717),
controller: controller,
onChanged: widget.onKeywordChanged,
prefixIcon: const Padding(
padding: EdgeInsets.only(
left: 14.0,
right: 8.0,
),
child: FlowySvg(
FlowySvgs.search_s,
),
),
prefixIconConstraints: const BoxConstraints(
maxHeight: 20.0,
),
suffixIcon: Padding(
padding: const EdgeInsets.all(4.0),
child: FlowyButton(
text: const FlowySvg(
FlowySvgs.m_app_bar_close_s,
),
margin: EdgeInsets.zero,
useIntrinsicWidth: true,
onTap: () {
if (controller.text.isNotEmpty) {
controller.clear();
widget.onKeywordChanged('');
} else {
focusNode.unfocus();
}
},
),
),
),
);
}
}

View File

@ -0,0 +1,67 @@
import 'package:appflowy/mobile/presentation/home/tab/_round_underline_tab_indicator.dart';
import 'package:flutter/material.dart';
enum PickerTabType {
emoji,
icon;
String get tr {
switch (this) {
case PickerTabType.emoji:
return 'Emojis';
case PickerTabType.icon:
return 'Icons';
}
}
}
class PickerTab extends StatelessWidget {
const PickerTab({
super.key,
this.onTap,
required this.controller,
required this.tabs,
});
final List<PickerTabType> tabs;
final TabController controller;
final ValueChanged<int>? onTap;
@override
Widget build(BuildContext context) {
final baseStyle = Theme.of(context).textTheme.bodyMedium;
final style = baseStyle?.copyWith(
fontWeight: FontWeight.w500,
fontSize: 14.0,
height: 16.0 / 14.0,
);
return TabBar(
controller: controller,
indicatorSize: TabBarIndicatorSize.label,
indicatorColor: Theme.of(context).colorScheme.primary,
isScrollable: true,
labelStyle: style,
labelColor: baseStyle?.color,
labelPadding: const EdgeInsets.symmetric(horizontal: 12.0),
unselectedLabelStyle: style?.copyWith(
color: Theme.of(context).hintColor,
),
overlayColor: WidgetStateProperty.all(Colors.transparent),
indicator: RoundUnderlineTabIndicator(
width: 34.0,
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 3,
),
),
onTap: onTap,
tabs: tabs
.map(
(tab) => Tab(
text: tab.tr,
),
)
.toList(),
);
}
}

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/shared/feature_flags.dart'; import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/user_settings_service.dart'; import 'package:appflowy/user/application/user_settings_service.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
@ -41,6 +42,8 @@ class InitAppWidgetTask extends LaunchTask {
await NotificationService.initialize(); await NotificationService.initialize();
await loadIconGroups();
final widget = context.getIt<EntryPoint>().create(context.config); final widget = context.getIt<EntryPoint>().create(context.config);
final appearanceSetting = final appearanceSetting =
await UserSettingsBackendService().getAppearanceSetting(); await UserSettingsBackendService().getAppearanceSetting();

View File

@ -183,6 +183,21 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
} catch (e) { } catch (e) {
Log.error('Failed to migrating cover: $e'); Log.error('Failed to migrating cover: $e');
} }
} else if (icon == null) {
try {
final extra = space.extra;
final Map<String, dynamic> current = extra.isNotEmpty == true
? jsonDecode(extra)
: <String, dynamic>{};
current.remove(ViewExtKeys.spaceIconKey);
current.remove(ViewExtKeys.spaceIconColorKey);
await ViewBackendService.updateView(
viewId: space.id,
extra: jsonEncode(current),
);
} catch (e) {
Log.error('Failed to migrating cover: $e');
}
} }
if (permission != null) { if (permission != null) {
@ -691,8 +706,10 @@ class SpaceEvent with _$SpaceEvent {
required bool createNewPageByDefault, required bool createNewPageByDefault,
}) = _Create; }) = _Create;
const factory SpaceEvent.rename(ViewPB space, String name) = _Rename; const factory SpaceEvent.rename(ViewPB space, String name) = _Rename;
const factory SpaceEvent.changeIcon(String icon, String iconColor) = const factory SpaceEvent.changeIcon(
_ChangeIcon; String? icon,
String? iconColor,
) = _ChangeIcon;
const factory SpaceEvent.duplicate() = _Duplicate; const factory SpaceEvent.duplicate() = _Duplicate;
const factory SpaceEvent.update({ const factory SpaceEvent.update({
String? name, String? name,

View File

@ -9,10 +9,12 @@ import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/database/grid/presentation/mobile_grid_page.dart'; import 'package:appflowy/plugins/database/grid/presentation/mobile_grid_page.dart';
import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart';
import 'package:appflowy/plugins/document/document.dart'; import 'package:appflowy/plugins/document/document.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class PluginArgumentKeys { class PluginArgumentKeys {
@ -134,7 +136,7 @@ extension ViewExtension on ViewPB {
} }
} }
FlowySvg? get spaceIconSvg { FlowySvg? buildSpaceIconSvg(BuildContext context, {Size? size}) {
try { try {
final ext = jsonDecode(extra); final ext = jsonDecode(extra);
final icon = ext[ViewExtKeys.spaceIconKey]; final icon = ext[ViewExtKeys.spaceIconKey];
@ -142,10 +144,36 @@ extension ViewExtension on ViewPB {
if (icon == null || color == null) { if (icon == null || color == null) {
return null; return null;
} }
return FlowySvg( // before version 0.6.7
FlowySvgData('assets/flowy_icons/16x/$icon.svg'), if (icon.contains('space_icon')) {
color: Color(int.parse(color)), return FlowySvg(
blendMode: BlendMode.srcOut, FlowySvgData('assets/flowy_icons/16x/$icon.svg'),
color: Theme.of(context).colorScheme.surface,
);
}
final values = icon.split('/');
if (values.length != 2) {
return null;
}
final groupName = values[0];
final iconName = values[1];
final svgString = kIconGroups
?.firstWhereOrNull(
(group) => group.name == groupName,
)
?.icons
.firstWhereOrNull(
(icon) => icon.name == iconName,
)
?.content;
if (svgString == null) {
return null;
}
return FlowySvg.string(
svgString,
color: Theme.of(context).colorScheme.surface,
size: size,
); );
} catch (e) { } catch (e) {
return null; return null;

View File

@ -5,7 +5,6 @@ import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -11,7 +11,6 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -15,7 +15,6 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hotkey_manager/hotkey_manager.dart'; import 'package:hotkey_manager/hotkey_manager.dart';

View File

@ -17,8 +17,8 @@ class CreateSpacePopup extends StatefulWidget {
class _CreateSpacePopupState extends State<CreateSpacePopup> { class _CreateSpacePopupState extends State<CreateSpacePopup> {
String spaceName = LocaleKeys.space_defaultSpaceName.tr(); String spaceName = LocaleKeys.space_defaultSpaceName.tr();
String spaceIcon = builtInSpaceIcons.first; String? spaceIcon = builtInSpaceIcons.first;
String spaceIconColor = builtInSpaceColors.first; String? spaceIconColor = builtInSpaceColors.first;
SpacePermission spacePermission = SpacePermission.publicToAll; SpacePermission spacePermission = SpacePermission.publicToAll;
@override @override
@ -80,8 +80,9 @@ class _CreateSpacePopupState extends State<CreateSpacePopup> {
context.read<SpaceBloc>().add( context.read<SpaceBloc>().add(
SpaceEvent.create( SpaceEvent.create(
name: spaceName, name: spaceName,
icon: spaceIcon, // fixme: space issue
iconColor: spaceIconColor, icon: spaceIcon!,
iconColor: spaceIconColor!,
permission: spacePermission, permission: spacePermission,
createNewPageByDefault: true, createNewPageByDefault: true,
), ),

View File

@ -77,7 +77,7 @@ class _SpaceNameTextField extends StatelessWidget {
}); });
final void Function(String name) onNameChanged; final void Function(String name) onNameChanged;
final void Function(String icon, String color) onIconChanged; final void Function(String? icon, String? color) onIconChanged;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -1,7 +1,3 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy/util/theme_extension.dart';
@ -21,7 +17,9 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class SpacePermissionSwitch extends StatefulWidget { class SpacePermissionSwitch extends StatefulWidget {
@ -280,9 +278,9 @@ class ConfirmPopupColor {
static Color descriptionColor(BuildContext context) { static Color descriptionColor(BuildContext context) {
if (Theme.of(context).isLightMode) { if (Theme.of(context).isLightMode) {
return const Color(0xFF171717).withOpacity(0.8); return const Color(0xFF171717).withOpacity(0.7);
} }
return const Color(0xFFffffff).withOpacity(0.72); return const Color(0xFFffffff).withOpacity(0.7);
} }
} }
@ -374,15 +372,21 @@ class _ConfirmPopupState extends State<ConfirmPopup> {
Expanded( Expanded(
child: FlowyText( child: FlowyText(
widget.title, widget.title,
fontSize: 14.0, fontSize: 16.0,
figmaLineHeight: 22.0,
fontWeight: FontWeight.w500,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
color: ConfirmPopupColor.titleColor(context), color: ConfirmPopupColor.titleColor(context),
), ),
), ),
const HSpace(6.0), const HSpace(6.0),
FlowyButton( FlowyButton(
margin: const EdgeInsets.all(3),
useIntrinsicWidth: true, useIntrinsicWidth: true,
text: const FlowySvg(FlowySvgs.upgrade_close_s), text: const FlowySvg(
FlowySvgs.upgrade_close_s,
size: Size.square(18.0),
),
onTap: () => Navigator.of(context).pop(), onTap: () => Navigator.of(context).pop(),
), ),
], ],
@ -392,10 +396,10 @@ class _ConfirmPopupState extends State<ConfirmPopup> {
Widget _buildDescription() { Widget _buildDescription() {
return FlowyText.regular( return FlowyText.regular(
widget.description, widget.description,
fontSize: 12.0, fontSize: 16.0,
color: ConfirmPopupColor.descriptionColor(context), color: ConfirmPopupColor.descriptionColor(context),
maxLines: 3, maxLines: 5,
lineHeight: 1.4, figmaLineHeight: 22.0,
); );
} }
@ -492,16 +496,22 @@ class CurrentSpace extends StatelessWidget {
final child = Row( final child = Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
SpaceIcon( if (space.spaceIcon != null) ...[
dimension: 20, SpaceIcon(
space: space, dimension: 22,
cornerRadius: 6.0, space: space,
), svgSize: 13,
const HSpace(10), cornerRadius: 8.0,
),
const HSpace(10),
] else ...[
const HSpace(2),
],
Flexible( Flexible(
child: FlowyText.medium( child: FlowyText.medium(
space.name, space.name,
fontSize: 14.0, fontSize: 14.0,
figmaLineHeight: 18.0,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),

View File

@ -1,6 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart'; import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/manage_space_popup.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/manage_space_popup.dart';
@ -13,8 +14,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flutter/material.dart' hide Icon;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class SidebarSpaceHeader extends StatefulWidget { class SidebarSpaceHeader extends StatefulWidget {
@ -171,8 +171,19 @@ class _SidebarSpaceHeaderState extends State<SidebarSpaceHeader> {
await _showRenameDialog(); await _showRenameDialog();
break; break;
case SpaceMoreActionType.changeIcon: case SpaceMoreActionType.changeIcon:
final (String icon, String iconColor) = data; final (IconGroup? group, Icon? icon, String? iconColor) = data;
context.read<SpaceBloc>().add(SpaceEvent.changeIcon(icon, iconColor));
final groupName = group?.name;
final iconName = icon?.name;
final name = groupName != null && iconName != null
? '$groupName/$iconName'
: null;
context.read<SpaceBloc>().add(
SpaceEvent.changeIcon(
name,
iconColor,
),
);
break; break;
case SpaceMoreActionType.manage: case SpaceMoreActionType.manage:
_showManageSpaceDialog(context); _showManageSpaceDialog(context);

View File

@ -9,7 +9,6 @@ import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -1,4 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -8,19 +10,71 @@ class SpaceIcon extends StatelessWidget {
required this.dimension, required this.dimension,
this.cornerRadius = 0, this.cornerRadius = 0,
required this.space, required this.space,
this.svgSize,
}); });
final double dimension; final double dimension;
final double cornerRadius; final double cornerRadius;
final ViewPB space; final ViewPB space;
final double? svgSize;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox.square( final spaceIconColor = space.spaceIconColor;
dimension: dimension, final color = spaceIconColor != null
child: ClipRRect( ? Color(int.parse(spaceIconColor))
borderRadius: BorderRadius.circular(cornerRadius), : Colors.transparent;
child: space.spaceIconSvg, final svg = space.buildSpaceIconSvg(
context,
size: svgSize != null ? Size.square(svgSize!) : null,
);
if (svg == null) {
return const SizedBox.shrink();
}
return ClipRRect(
borderRadius: BorderRadius.circular(cornerRadius),
child: Container(
width: dimension,
height: dimension,
color: color,
child: Center(
child: svgSize == null
? svg
: SizedBox.square(dimension: svgSize!, child: svg),
),
),
);
}
}
class DefaultSpaceIcon extends StatelessWidget {
const DefaultSpaceIcon({
super.key,
required this.dimension,
required this.iconDimension,
this.cornerRadius = 0,
});
final double dimension;
final double cornerRadius;
final double iconDimension;
@override
Widget build(BuildContext context) {
final svg = builtInSpaceIcons.first;
final color = Color(int.parse(builtInSpaceColors.first));
return ClipRRect(
borderRadius: BorderRadius.circular(cornerRadius),
child: Container(
width: dimension,
height: dimension,
color: color,
child: FlowySvg(
FlowySvgData('assets/flowy_icons/16x/$svg.svg'),
color: Theme.of(context).colorScheme.surface,
size: Size.square(iconDimension),
),
), ),
); );
} }

View File

@ -1,9 +1,15 @@
import 'dart:math';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/tab.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' hide Icon;
final builtInSpaceColors = [ final builtInSpaceColors = [
'0xFFA34AFD', '0xFFA34AFD',
@ -20,6 +26,11 @@ final builtInSpaceColors = [
'0xFFFF8933', '0xFFFF8933',
]; ];
String generateRandomSpaceColor() {
final random = Random();
return builtInSpaceColors[random.nextInt(builtInSpaceColors.length)];
}
final builtInSpaceIcons = final builtInSpaceIcons =
List.generate(15, (index) => 'space_icon_${index + 1}'); List.generate(15, (index) => 'space_icon_${index + 1}');
@ -34,7 +45,7 @@ class SpaceIconPopup extends StatefulWidget {
final String? icon; final String? icon;
final String? iconColor; final String? iconColor;
final void Function(String icon, String color) onIconChanged; final void Function(String? icon, String? color) onIconChanged;
final double cornerRadius; final double cornerRadius;
@override @override
@ -42,10 +53,12 @@ class SpaceIconPopup extends StatefulWidget {
} }
class _SpaceIconPopupState extends State<SpaceIconPopup> { class _SpaceIconPopupState extends State<SpaceIconPopup> {
late ValueNotifier<String> selectedColor = late ValueNotifier<String?> selectedIcon = ValueNotifier<String?>(
ValueNotifier<String>(widget.iconColor ?? builtInSpaceColors.first); widget.icon,
late ValueNotifier<String> selectedIcon = );
ValueNotifier<String>(widget.icon ?? builtInSpaceIcons.first); late ValueNotifier<String> selectedColor = ValueNotifier<String>(
widget.iconColor ?? builtInSpaceColors.first,
);
@override @override
void dispose() { void dispose() {
@ -58,19 +71,30 @@ class _SpaceIconPopupState extends State<SpaceIconPopup> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppFlowyPopover( return AppFlowyPopover(
offset: const Offset(0, 4), offset: const Offset(0, 4),
constraints: const BoxConstraints(maxWidth: 220), constraints: BoxConstraints.loose(const Size(380, 432)),
margin: const EdgeInsets.symmetric(horizontal: 14.0, vertical: 12.0), margin: const EdgeInsets.all(0),
direction: PopoverDirection.bottomWithCenterAligned, direction: PopoverDirection.bottomWithCenterAligned,
child: _buildPreview(), child: _buildPreview(),
popupBuilder: (_) => SpaceIconPicker( popupBuilder: (context) {
icon: selectedIcon.value, return FlowyIconEmojiPicker(
iconColor: selectedColor.value, tabs: const [PickerTabType.icon],
onIconChanged: (icon, iconColor) { onSelectedIcon: (group, icon, color) {
selectedIcon.value = icon; if (group == null || icon == null) {
selectedColor.value = iconColor; selectedIcon.value = null;
widget.onIconChanged(icon, iconColor); } else {
}, selectedIcon.value = '${group.name}/${icon.name}';
), }
if (color != null) {
selectedColor.value = color;
}
widget.onIconChanged(selectedIcon.value, selectedColor.value);
PopoverContainer.of(context).close();
},
);
},
); );
} }
@ -87,15 +111,36 @@ class _SpaceIconPopupState extends State<SpaceIconPopup> {
builder: (_, color, __) { builder: (_, color, __) {
return ValueListenableBuilder( return ValueListenableBuilder(
valueListenable: selectedIcon, valueListenable: selectedIcon,
builder: (_, icon, __) { builder: (_, value, __) {
final child = ClipRRect( Widget child;
borderRadius: BorderRadius.circular(widget.cornerRadius), if (value == null) {
child: FlowySvg( child = const DefaultSpaceIcon(
FlowySvgData('assets/flowy_icons/16x/$icon.svg'), cornerRadius: 16.0,
color: Color(int.parse(color)), dimension: 32,
blendMode: BlendMode.srcOut, iconDimension: 32,
), );
); } else {
final content = kIconGroups?.findSvgContent(value);
if (content == null) {
child = const SizedBox.shrink();
} else {
child = ClipRRect(
borderRadius:
BorderRadius.circular(widget.cornerRadius),
child: Container(
color: Color(int.parse(color)),
child: Align(
child: FlowySvg.string(
content,
size: const Size.square(24),
color: Theme.of(context).colorScheme.surface,
),
),
),
);
}
}
if (onHover) { if (onHover) {
return Stack( return Stack(
children: [ children: [

View File

@ -1,16 +1,15 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/tab.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_action_type.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_action_type.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -108,20 +107,19 @@ class SpaceMoreActionTypeWrapper extends CustomActionCell {
PopoverController controller, PopoverController controller,
) { ) {
final child = _buildActionButton(context, null); final child = _buildActionButton(context, null);
final spaceBloc = context.read<SpaceBloc>();
final color = spaceBloc.state.currentSpace?.spaceIconColor;
return AppFlowyPopover( return AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(216, 256)), constraints: BoxConstraints.loose(const Size(380, 432)),
margin: const EdgeInsets.symmetric(horizontal: 14.0, vertical: 12.0), margin: const EdgeInsets.all(0),
clickHandler: PopoverClickHandler.gestureDetector, clickHandler: PopoverClickHandler.gestureDetector,
popupBuilder: (_) => SpaceIconPicker( offset: const Offset(0, -40),
iconColor: color, popupBuilder: (context) {
skipFirstNotification: true, return FlowyIconEmojiPicker(
onIconChanged: (icon, color) { tabs: const [PickerTabType.icon],
onTap(controller, (icon, color)); onSelectedIcon: (group, icon, color) {
}, onTap(controller, (group, icon, color));
), },
);
},
child: child, child: child,
); );
} }
@ -172,6 +170,8 @@ class SpaceMoreActionTypeWrapper extends CustomActionCell {
rightIconBuilder: (_) => inner.rightIcon, rightIconBuilder: (_) => inner.rightIcon,
textBuilder: (onHover) => FlowyText.regular( textBuilder: (onHover) => FlowyText.regular(
inner.name, inner.name,
fontSize: 14.0,
figmaLineHeight: 18.0,
color: inner == SpaceMoreActionType.delete && onHover color: inner == SpaceMoreActionType.delete && onHover
? Theme.of(context).colorScheme.error ? Theme.of(context).colorScheme.error
: null, : null,

View File

@ -1,6 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:appflowy/plugins/base/icon/icon_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/util/color_generator/color_generator.dart'; import 'package:appflowy/util/color_generator/color_generator.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
@ -18,6 +18,7 @@ class WorkspaceIcon extends StatefulWidget {
this.borderRadius = 4, this.borderRadius = 4,
this.emojiSize, this.emojiSize,
this.alignment, this.alignment,
required this.figmaLineHeight,
}); });
final UserWorkspacePB workspace; final UserWorkspacePB workspace;
@ -28,6 +29,7 @@ class WorkspaceIcon extends StatefulWidget {
final void Function(EmojiPickerResult) onSelected; final void Function(EmojiPickerResult) onSelected;
final double borderRadius; final double borderRadius;
final Alignment? alignment; final Alignment? alignment;
final double figmaLineHeight;
@override @override
State<WorkspaceIcon> createState() => _WorkspaceIconState(); State<WorkspaceIcon> createState() => _WorkspaceIconState();
@ -45,7 +47,8 @@ class _WorkspaceIconState extends State<WorkspaceIcon> {
child: FlowyText.emoji( child: FlowyText.emoji(
widget.workspace.icon, widget.workspace.icon,
fontSize: widget.emojiSize ?? widget.iconSize, fontSize: widget.emojiSize ?? widget.iconSize,
figmaLineHeight: 21.0, figmaLineHeight: widget.figmaLineHeight,
optimizeEmojiAlign: true,
), ),
) )
: Container( : Container(
@ -76,8 +79,9 @@ class _WorkspaceIconState extends State<WorkspaceIcon> {
direction: PopoverDirection.bottomWithLeftAligned, direction: PopoverDirection.bottomWithLeftAligned,
constraints: BoxConstraints.loose(const Size(364, 356)), constraints: BoxConstraints.loose(const Size(364, 356)),
clickHandler: PopoverClickHandler.gestureDetector, clickHandler: PopoverClickHandler.gestureDetector,
popupBuilder: (_) => FlowyIconPicker( margin: const EdgeInsets.all(0),
onSelected: (result) { popupBuilder: (_) => FlowyIconEmojiPicker(
onSelectedEmoji: (result) {
widget.onSelected(result); widget.onSelected(result);
controller.close(); controller.close();
}, },

View File

@ -14,7 +14,6 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -172,6 +171,7 @@ class _WorkspaceMenuItemState extends State<WorkspaceMenuItem> {
workspace: widget.workspace, workspace: widget.workspace,
iconSize: 22, iconSize: 22,
fontSize: 16, fontSize: 16,
figmaLineHeight: 32.0,
enableEdit: true, enableEdit: true,
onSelected: (result) => context.read<UserWorkspaceBloc>().add( onSelected: (result) => context.read<UserWorkspaceBloc>().add(
UserWorkspaceEvent.updateWorkspaceIcon( UserWorkspaceEvent.updateWorkspaceIcon(

View File

@ -242,6 +242,7 @@ class _SidebarSwitchWorkspaceButtonState
emojiSize: 18, emojiSize: 18,
enableEdit: false, enableEdit: false,
borderRadius: 8.0, borderRadius: 8.0,
figmaLineHeight: 21.0,
onSelected: (result) => context.read<UserWorkspaceBloc>().add( onSelected: (result) => context.read<UserWorkspaceBloc>().add(
UserWorkspaceEvent.updateWorkspaceIcon( UserWorkspaceEvent.updateWorkspaceIcon(
widget.currentWorkspace.workspaceId, widget.currentWorkspace.workspaceId,

View File

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
@ -28,7 +28,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -583,6 +582,7 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
controller: controller, controller: controller,
direction: PopoverDirection.rightWithCenterAligned, direction: PopoverDirection.rightWithCenterAligned,
constraints: BoxConstraints.loose(const Size(364, 356)), constraints: BoxConstraints.loose(const Size(364, 356)),
margin: const EdgeInsets.all(0),
onClose: () => setState(() => isIconPickerOpened = false), onClose: () => setState(() => isIconPickerOpened = false),
child: GestureDetector( child: GestureDetector(
// prevent the tap event from being passed to the parent widget // prevent the tap event from being passed to the parent widget
@ -594,8 +594,8 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
), ),
popupBuilder: (context) { popupBuilder: (context) {
isIconPickerOpened = true; isIconPickerOpened = true;
return FlowyIconPicker( return FlowyIconEmojiPicker(
onSelected: (result) { onSelectedEmoji: (result) {
ViewBackendService.updateViewIcon( ViewBackendService.updateViewIcon(
viewId: widget.view.id, viewId: widget.view.id,
viewIcon: result.emoji, viewIcon: result.emoji,

View File

@ -1,5 +1,5 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/move_to/move_page_menu.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/move_to/move_page_menu.dart';
@ -161,10 +161,10 @@ class ViewMoreActionTypeWrapper extends CustomActionCell {
return AppFlowyPopover( return AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(364, 356)), constraints: BoxConstraints.loose(const Size(364, 356)),
margin: const EdgeInsets.symmetric(horizontal: 14.0, vertical: 12.0), margin: const EdgeInsets.all(0),
clickHandler: PopoverClickHandler.gestureDetector, clickHandler: PopoverClickHandler.gestureDetector,
popupBuilder: (_) => FlowyIconPicker( popupBuilder: (_) => FlowyIconEmojiPicker(
onSelected: (result) => onTap(controller, result), onSelectedEmoji: (result) => onTap(controller, result),
), ),
child: child, child: child,
); );
@ -256,6 +256,8 @@ class ViewMoreActionTypeWrapper extends CustomActionCell {
iconPadding: 10.0, iconPadding: 10.0,
textBuilder: (onHover) => FlowyText.regular( textBuilder: (onHover) => FlowyText.regular(
inner.name, inner.name,
fontSize: 14.0,
figmaLineHeight: 18.0,
color: inner == ViewMoreActionType.delete && onHover color: inner == ViewMoreActionType.delete && onHover
? Theme.of(context).colorScheme.error ? Theme.of(context).colorScheme.error
: null, : null,

View File

@ -11,7 +11,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class NotificationButton extends StatefulWidget { class NotificationButton extends StatefulWidget {

View File

@ -1,7 +1,7 @@
import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/prelude.dart'; import 'package:appflowy/user/application/prelude.dart';
@ -18,7 +18,6 @@ import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -466,9 +465,9 @@ class _UserProfileSettingState extends State<UserProfileSetting> {
Container( Container(
height: 380, height: 380,
width: 360, width: 360,
margin: const EdgeInsets.symmetric(horizontal: 12), margin: const EdgeInsets.all(0),
child: FlowyIconPicker( child: FlowyIconEmojiPicker(
onSelected: (r) { onSelectedEmoji: (r) {
context context
.read<SettingsUserViewBloc>() .read<SettingsUserViewBloc>()
.add(SettingsUserEvent.updateUserIcon(iconUrl: r.emoji)); .add(SettingsUserEvent.updateUserIcon(iconUrl: r.emoji));

View File

@ -11,7 +11,6 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../generated/locale_keys.g.dart'; import '../../../../generated/locale_keys.g.dart';

View File

@ -23,7 +23,6 @@ import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class SettingsShortcutsView extends StatefulWidget { class SettingsShortcutsView extends StatefulWidget {

View File

@ -356,6 +356,7 @@ class _WorkspaceIconSetting extends StatelessWidget {
workspace: workspace!, workspace: workspace!,
iconSize: workspace!.icon.isNotEmpty == true ? 46 : 20, iconSize: workspace!.icon.isNotEmpty == true ? 46 : 20,
fontSize: 16.0, fontSize: 16.0,
figmaLineHeight: 46,
enableEdit: true, enableEdit: true,
onSelected: (r) => context onSelected: (r) => context
.read<WorkspaceSettingsBloc>() .read<WorkspaceSettingsBloc>()

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
/// Renders a simple category taking a title and the list /// Renders a simple category taking a title and the list
/// of children (settings) to be rendered. /// of children (settings) to be rendered.

View File

@ -5,7 +5,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
/// This is used to describe a settings input field /// This is used to describe a settings input field
/// ///

View File

@ -16,7 +16,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:string_validator/string_validator.dart'; import 'package:string_validator/string_validator.dart';

View File

@ -18,7 +18,6 @@ import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class SettingSupabaseCloudView extends StatelessWidget { class SettingSupabaseCloudView extends StatelessWidget {

View File

@ -15,7 +15,6 @@ import 'package:flowy_infra/file_picker/file_picker_impl.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/style_widget/snap_bar.dart'; import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:path/path.dart'; import 'package:path/path.dart';

View File

@ -11,7 +11,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -8,7 +8,6 @@ import 'package:appflowy/workspace/presentation/widgets/rename_view_popover.dart
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -173,7 +172,7 @@ class _ViewTitleState extends State<_ViewTitle> {
return Container( return Container(
alignment: Alignment.center, alignment: Alignment.center,
margin: const EdgeInsets.symmetric(horizontal: 6.0), margin: const EdgeInsets.symmetric(horizontal: 6.0),
child: _buildIconAndName(state, false), child: _buildIconAndName(context, state, false),
); );
} }
@ -185,7 +184,7 @@ class _ViewTitleState extends State<_ViewTitle> {
child: FlowyButton( child: FlowyButton(
useIntrinsicWidth: true, useIntrinsicWidth: true,
margin: const EdgeInsets.symmetric(horizontal: 6.0), margin: const EdgeInsets.symmetric(horizontal: 6.0),
text: _buildIconAndName(state, false), text: _buildIconAndName(context, state, false),
), ),
), ),
); );
@ -216,13 +215,18 @@ class _ViewTitleState extends State<_ViewTitle> {
child: FlowyButton( child: FlowyButton(
useIntrinsicWidth: true, useIntrinsicWidth: true,
margin: const EdgeInsets.symmetric(horizontal: 6.0), margin: const EdgeInsets.symmetric(horizontal: 6.0),
text: _buildIconAndName(state, true), text: _buildIconAndName(context, state, true),
), ),
), ),
); );
} }
Widget _buildIconAndName(ViewTitleState state, bool isEditable) { Widget _buildIconAndName(
BuildContext context,
ViewTitleState state,
bool isEditable,
) {
final spaceIcon = state.view?.buildSpaceIconSvg(context);
return SingleChildScrollView( return SingleChildScrollView(
child: Row( child: Row(
children: [ children: [
@ -234,10 +238,10 @@ class _ViewTitleState extends State<_ViewTitle> {
), ),
const HSpace(4.0), const HSpace(4.0),
], ],
if (state.view?.isSpace == true && if (state.view?.isSpace == true && spaceIcon != null) ...[
state.view?.spaceIconSvg != null) ...[
SpaceIcon( SpaceIcon(
dimension: 14, dimension: 14,
svgSize: 8.5,
space: state.view!, space: state.view!,
cornerRadius: 4, cornerRadius: 4,
), ),

View File

@ -1,4 +1,5 @@
// Basis // Basis
export '/widget/flowy_tooltip.dart';
export '/widget/separated_flex.dart'; export '/widget/separated_flex.dart';
export '/widget/spacing.dart'; export '/widget/spacing.dart';
export 'basis.dart'; export 'basis.dart';

View File

@ -7,7 +7,6 @@ import 'package:flutter/services.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flowy_svg/flowy_svg.dart'; import 'package:flowy_svg/flowy_svg.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';

View File

@ -24,8 +24,29 @@ class FlowySvg extends StatelessWidget {
this.color, this.color,
this.blendMode = BlendMode.srcIn, this.blendMode = BlendMode.srcIn,
this.opacity = 1.0, this.opacity = 1.0,
this.svgString,
}); });
/// Construct a FlowySvg Widget from a string
factory FlowySvg.string(
String svgString, {
Key? key,
Size? size,
Color? color,
BlendMode? blendMode = BlendMode.srcIn,
double opacity = 1.0,
}) {
return FlowySvg(
const FlowySvgData(''),
key: key,
size: size,
color: color,
blendMode: blendMode,
opacity: opacity,
svgString: svgString,
);
}
/// The data for the flowy svg. Will be generated by the generator in this /// The data for the flowy svg. Will be generated by the generator in this
/// package within bin/flowy_svg.dart /// package within bin/flowy_svg.dart
final FlowySvgData svg; final FlowySvgData svg;
@ -33,6 +54,9 @@ class FlowySvg extends StatelessWidget {
/// The size of the svg /// The size of the svg
final Size? size; final Size? size;
/// The svg string
final String? svgString;
/// The color of the svg. /// The color of the svg.
/// ///
/// This property will not be applied to the underlying svg widget if the /// This property will not be applied to the underlying svg widget if the
@ -57,22 +81,41 @@ class FlowySvg extends StatelessWidget {
final iconColor = final iconColor =
(color ?? Theme.of(context).iconTheme.color)?.withOpacity(opacity); (color ?? Theme.of(context).iconTheme.color)?.withOpacity(opacity);
final textScaleFactor = MediaQuery.textScalerOf(context).scale(1); final textScaleFactor = MediaQuery.textScalerOf(context).scale(1);
final Widget svg;
if (svgString != null) {
svg = SvgPicture.string(
svgString!,
width: size?.width,
height: size?.height,
colorFilter: iconColor != null && blendMode != null
? ColorFilter.mode(
iconColor,
blendMode!,
)
: null,
);
} else {
svg = SvgPicture.asset(
_normalized(),
width: size?.width,
height: size?.height,
colorFilter: iconColor != null && blendMode != null
? ColorFilter.mode(
iconColor,
blendMode!,
)
: null,
);
}
return Transform.scale( return Transform.scale(
scale: textScaleFactor, scale: textScaleFactor,
child: SizedBox( child: SizedBox(
width: size?.width, width: size?.width,
height: size?.height, height: size?.height,
child: SvgPicture.asset( child: svg,
_normalized(),
width: size?.width,
height: size?.height,
colorFilter: iconColor != null && blendMode != null
? ColorFilter.mode(
iconColor,
blendMode!,
)
: null,
),
), ),
); );
} }

View File

@ -740,8 +740,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "4a5cac" ref: "38c2c42"
resolved-ref: "4a5cac57e31c0ffd49cd6257a9e078f084ae342c" resolved-ref: "38c2c429212af6b72a0af829bb0dd3f3eb4ce2c7"
url: "https://github.com/LucasXu0/emoji_mart.git" url: "https://github.com/LucasXu0/emoji_mart.git"
source: git source: git
version: "1.0.2" version: "1.0.2"

View File

@ -111,7 +111,7 @@ dependencies:
flutter_emoji_mart: flutter_emoji_mart:
git: git:
url: https://github.com/LucasXu0/emoji_mart.git url: https://github.com/LucasXu0/emoji_mart.git
ref: "4a5cac" ref: "38c2c42"
# Notifications # Notifications
# TODO: Consider implementing custom package # TODO: Consider implementing custom package
@ -283,6 +283,7 @@ flutter:
- assets/images/emoji/ - assets/images/emoji/
- assets/images/login/ - assets/images/login/
- assets/translations/ - assets/translations/
- assets/icons/icons.json
# The following assets will be excluded in release. # The following assets will be excluded in release.
# BEGIN: EXCLUDE_IN_RELEASE # BEGIN: EXCLUDE_IN_RELEASE