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:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.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_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_option_button.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/image/upload_image_menu/widgets/embed_image_url_widget.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: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_test/flutter_test.dart';
@ -85,7 +84,7 @@ class EditorOperations {
final Finder button = !isInPicker
? find.text(LocaleKeys.document_plugins_cover_removeIcon.tr())
: find.descendant(
of: find.byType(FlowyIconPicker),
of: find.byType(FlowyIconEmojiPicker),
matching: find.text(LocaleKeys.button_remove.tr()),
);
await tester.tapButton(button);

View File

@ -1,5 +1,5 @@
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_icon.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);
await tapButton(iconButton);
final iconPicker = find.byType(FlowyIconPicker);
final iconPicker = find.byType(FlowyIconEmojiPicker);
expect(iconPicker, findsOneWidget);
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/workspaces/workspace_menu_bottom_sheet.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/util/built_in_svgs.dart';
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
@ -133,6 +133,7 @@ class _MobileWorkspace extends StatelessWidget {
fontSize: 16.0,
enableEdit: false,
alignment: Alignment.centerLeft,
figmaLineHeight: 16.0,
onSelected: (result) => context.read<UserWorkspaceBloc>().add(
UserWorkspaceEvent.updateWorkspaceIcon(
currentWorkspace.workspaceId,

View File

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

View File

@ -68,17 +68,16 @@ class MobileNotificationTabBar extends StatelessWidget {
controller: tabController,
tabs: tabs.map((e) => Tab(text: e.tr)).toList(),
indicatorSize: TabBarIndicatorSize.label,
indicatorColor: Theme.of(context).primaryColor,
isScrollable: true,
labelStyle: labelStyle,
labelColor: baseStyle?.color,
labelPadding: const EdgeInsets.symmetric(horizontal: 12.0),
unselectedLabelStyle: unselectedLabelStyle,
overlayColor: WidgetStateProperty.all(Colors.transparent),
indicator: RoundUnderlineTabIndicator(
indicator: const RoundUnderlineTabIndicator(
width: 28.0,
borderSide: BorderSide(
color: Theme.of(context).primaryColor,
color: Color(0xFF00C8FF),
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_search_bar.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_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
// use a global value to store the selected emoji to prevent reloading every time.
@ -65,28 +64,34 @@ class _FlowyEmojiPickerState extends State<FlowyEmojiPicker> {
perLine: widget.emojiPerLine,
),
onEmojiSelected: widget.onEmojiSelected,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
headerBuilder: (context, category) {
return FlowyEmojiHeader(
category: category,
);
},
itemBuilder: (context, emojiId, emoji, callback) {
return SizedBox(
width: 36,
height: 36,
final name = emojiData?.emojis[emojiId]?.name ?? '';
return SizedBox.square(
dimension: 36.0,
child: FlowyButton(
margin: EdgeInsets.zero,
radius: Corners.s8Border,
text: FlowyText.emoji(
text: FlowyTooltip(
message: name,
child: FlowyText.emoji(
emoji,
fontSize: 24.0,
),
),
onTap: () => callback(emojiId, emoji),
),
);
},
searchBarBuilder: (context, keyword, skinTone) {
return FlowyEmojiSearchBar(
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: FlowyEmojiSearchBar(
emojiData: emojiData!,
onKeywordChanged: (value) {
keyword.value = value;
@ -95,6 +100,7 @@ class _FlowyEmojiPickerState extends State<FlowyEmojiPicker> {
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/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:flutter/material.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:easy_localization/easy_localization.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_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/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:flutter/material.dart';
class IconPickerPage extends StatelessWidget {
const IconPickerPage({
@ -22,7 +21,7 @@ class IconPickerPage extends StatelessWidget {
titleText: title ?? LocaleKeys.titleBar_pageIcon.tr(),
),
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/style_widget/hover.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/services.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:easy_localization/easy_localization.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/services.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:flowy_infra_ui/flowy_infra_ui.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_bloc/flutter_bloc.dart';

View File

@ -21,7 +21,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.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';
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/theme_extension.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: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:flowy_infra/theme_extension.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_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:easy_localization/easy_localization.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/services.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:easy_localization/easy_localization.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 '../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:easy_localization/easy_localization.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: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:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart';
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:easy_localization/easy_localization.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_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:easy_localization/easy_localization.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_bloc/flutter_bloc.dart';

View File

@ -15,7 +15,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.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_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:avatar_stack/avatar_stack.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_bloc/flutter_bloc.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:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart';
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/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_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';

View File

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

View File

@ -1,13 +1,10 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.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/presentation/bottom_sheet/bottom_sheet.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/presentation/editor_plugins/header/desktop_cover.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_style.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_listener.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:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:string_validator/string_validator.dart';
@ -352,11 +351,12 @@ class _DocumentHeaderToolbarState extends State<DocumentHeaderToolbar> {
offset: const Offset(0, 8),
direction: PopoverDirection.bottomWithCenterAligned,
constraints: BoxConstraints.loose(const Size(360, 380)),
margin: EdgeInsets.zero,
child: child,
popupBuilder: (BuildContext popoverContext) {
isPopoverOpen = true;
return FlowyIconPicker(
onSelected: (result) {
return FlowyIconEmojiPicker(
onSelectedEmoji: (result) {
widget.onIconOrCoverChanged(icon: result.emoji);
_popoverController.close();
},
@ -725,10 +725,11 @@ class _DocumentIconState extends State<DocumentIcon> {
controller: _popoverController,
offset: const Offset(0, 8),
constraints: BoxConstraints.loose(const Size(360, 380)),
margin: EdgeInsets.zero,
child: child,
popupBuilder: (BuildContext popoverContext) {
return FlowyIconPicker(
onSelected: (result) {
return FlowyIconEmojiPicker(
onSelectedEmoji: (result) {
widget.onChangeIcon(result.emoji);
_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:flowy_infra_ui/flowy_infra_ui.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:flutter/material.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:easy_localization/easy_localization.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_emoji_mart/flutter_emoji_mart.dart';
@ -142,6 +141,7 @@ class _SearchTextFieldState extends State<_SearchTextField> {
fontWeight: FontWeight.w400,
color: Theme.of(context).hintColor,
),
enableBorderColor: const Color(0x1E171717),
controller: controller,
onChanged: widget.onKeywordChanged,
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/plugins/document/application/document_appearance_cubit.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/user/application/user_settings_service.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
@ -41,6 +42,8 @@ class InitAppWidgetTask extends LaunchTask {
await NotificationService.initialize();
await loadIconGroups();
final widget = context.getIt<EntryPoint>().create(context.config);
final appearanceSetting =
await UserSettingsBackendService().getAppearanceSetting();

View File

@ -183,6 +183,21 @@ class SpaceBloc extends Bloc<SpaceEvent, SpaceState> {
} catch (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) {
@ -691,8 +706,10 @@ class SpaceEvent with _$SpaceEvent {
required bool createNewPageByDefault,
}) = _Create;
const factory SpaceEvent.rename(ViewPB space, String name) = _Rename;
const factory SpaceEvent.changeIcon(String icon, String iconColor) =
_ChangeIcon;
const factory SpaceEvent.changeIcon(
String? icon,
String? iconColor,
) = _ChangeIcon;
const factory SpaceEvent.duplicate() = _Duplicate;
const factory SpaceEvent.update({
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/tab_bar/tab_bar_view.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/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
class PluginArgumentKeys {
@ -134,7 +136,7 @@ extension ViewExtension on ViewPB {
}
}
FlowySvg? get spaceIconSvg {
FlowySvg? buildSpaceIconSvg(BuildContext context, {Size? size}) {
try {
final ext = jsonDecode(extra);
final icon = ext[ViewExtKeys.spaceIconKey];
@ -142,10 +144,36 @@ extension ViewExtension on ViewPB {
if (icon == null || color == null) {
return null;
}
// before version 0.6.7
if (icon.contains('space_icon')) {
return FlowySvg(
FlowySvgData('assets/flowy_icons/16x/$icon.svg'),
color: Color(int.parse(color)),
blendMode: BlendMode.srcOut,
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) {
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:easy_localization/easy_localization.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_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:easy_localization/easy_localization.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_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:easy_localization/easy_localization.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:hotkey_manager/hotkey_manager.dart';

View File

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

View File

@ -77,7 +77,7 @@ class _SpaceNameTextField extends StatelessWidget {
});
final void Function(String name) onNameChanged;
final void Function(String icon, String color) onIconChanged;
final void Function(String? icon, String? color) onIconChanged;
@override
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/locale_keys.g.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:flowy_infra_ui/flowy_infra_ui.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';
class SpacePermissionSwitch extends StatefulWidget {
@ -280,9 +278,9 @@ class ConfirmPopupColor {
static Color descriptionColor(BuildContext context) {
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(
child: FlowyText(
widget.title,
fontSize: 14.0,
fontSize: 16.0,
figmaLineHeight: 22.0,
fontWeight: FontWeight.w500,
overflow: TextOverflow.ellipsis,
color: ConfirmPopupColor.titleColor(context),
),
),
const HSpace(6.0),
FlowyButton(
margin: const EdgeInsets.all(3),
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(),
),
],
@ -392,10 +396,10 @@ class _ConfirmPopupState extends State<ConfirmPopup> {
Widget _buildDescription() {
return FlowyText.regular(
widget.description,
fontSize: 12.0,
fontSize: 16.0,
color: ConfirmPopupColor.descriptionColor(context),
maxLines: 3,
lineHeight: 1.4,
maxLines: 5,
figmaLineHeight: 22.0,
);
}
@ -492,16 +496,22 @@ class CurrentSpace extends StatelessWidget {
final child = Row(
mainAxisSize: MainAxisSize.min,
children: [
if (space.spaceIcon != null) ...[
SpaceIcon(
dimension: 20,
dimension: 22,
space: space,
cornerRadius: 6.0,
svgSize: 13,
cornerRadius: 8.0,
),
const HSpace(10),
] else ...[
const HSpace(2),
],
Flexible(
child: FlowyText.medium(
space.name,
fontSize: 14.0,
figmaLineHeight: 18.0,
overflow: TextOverflow.ellipsis,
),
),

View File

@ -1,6 +1,7 @@
import 'dart:io';
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/presentation/home/home_sizes.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:flowy_infra_ui/flowy_infra_ui.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' hide Icon;
import 'package:flutter_bloc/flutter_bloc.dart';
class SidebarSpaceHeader extends StatefulWidget {
@ -171,8 +171,19 @@ class _SidebarSpaceHeaderState extends State<SidebarSpaceHeader> {
await _showRenameDialog();
break;
case SpaceMoreActionType.changeIcon:
final (String icon, String iconColor) = data;
context.read<SpaceBloc>().add(SpaceEvent.changeIcon(icon, iconColor));
final (IconGroup? group, Icon? icon, String? iconColor) = data;
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;
case SpaceMoreActionType.manage:
_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:easy_localization/easy_localization.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_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/presentation/home/menu/sidebar/space/space_icon_popup.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter/material.dart';
@ -8,19 +10,71 @@ class SpaceIcon extends StatelessWidget {
required this.dimension,
this.cornerRadius = 0,
required this.space,
this.svgSize,
});
final double dimension;
final double cornerRadius;
final ViewPB space;
final double? svgSize;
@override
Widget build(BuildContext context) {
return SizedBox.square(
dimension: dimension,
child: ClipRRect(
final spaceIconColor = space.spaceIconColor;
final color = spaceIconColor != null
? Color(int.parse(spaceIconColor))
: Colors.transparent;
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: space.spaceIconSvg,
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/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:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' hide Icon;
final builtInSpaceColors = [
'0xFFA34AFD',
@ -20,6 +26,11 @@ final builtInSpaceColors = [
'0xFFFF8933',
];
String generateRandomSpaceColor() {
final random = Random();
return builtInSpaceColors[random.nextInt(builtInSpaceColors.length)];
}
final builtInSpaceIcons =
List.generate(15, (index) => 'space_icon_${index + 1}');
@ -34,7 +45,7 @@ class SpaceIconPopup extends StatefulWidget {
final String? icon;
final String? iconColor;
final void Function(String icon, String color) onIconChanged;
final void Function(String? icon, String? color) onIconChanged;
final double cornerRadius;
@override
@ -42,10 +53,12 @@ class SpaceIconPopup extends StatefulWidget {
}
class _SpaceIconPopupState extends State<SpaceIconPopup> {
late ValueNotifier<String> selectedColor =
ValueNotifier<String>(widget.iconColor ?? builtInSpaceColors.first);
late ValueNotifier<String> selectedIcon =
ValueNotifier<String>(widget.icon ?? builtInSpaceIcons.first);
late ValueNotifier<String?> selectedIcon = ValueNotifier<String?>(
widget.icon,
);
late ValueNotifier<String> selectedColor = ValueNotifier<String>(
widget.iconColor ?? builtInSpaceColors.first,
);
@override
void dispose() {
@ -58,19 +71,30 @@ class _SpaceIconPopupState extends State<SpaceIconPopup> {
Widget build(BuildContext context) {
return AppFlowyPopover(
offset: const Offset(0, 4),
constraints: const BoxConstraints(maxWidth: 220),
margin: const EdgeInsets.symmetric(horizontal: 14.0, vertical: 12.0),
constraints: BoxConstraints.loose(const Size(380, 432)),
margin: const EdgeInsets.all(0),
direction: PopoverDirection.bottomWithCenterAligned,
child: _buildPreview(),
popupBuilder: (_) => SpaceIconPicker(
icon: selectedIcon.value,
iconColor: selectedColor.value,
onIconChanged: (icon, iconColor) {
selectedIcon.value = icon;
selectedColor.value = iconColor;
widget.onIconChanged(icon, iconColor);
popupBuilder: (context) {
return FlowyIconEmojiPicker(
tabs: const [PickerTabType.icon],
onSelectedIcon: (group, icon, color) {
if (group == null || icon == null) {
selectedIcon.value = null;
} 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, __) {
return ValueListenableBuilder(
valueListenable: selectedIcon,
builder: (_, icon, __) {
final child = ClipRRect(
borderRadius: BorderRadius.circular(widget.cornerRadius),
child: FlowySvg(
FlowySvgData('assets/flowy_icons/16x/$icon.svg'),
builder: (_, value, __) {
Widget child;
if (value == null) {
child = const DefaultSpaceIcon(
cornerRadius: 16.0,
dimension: 32,
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)),
blendMode: BlendMode.srcOut,
child: Align(
child: FlowySvg.string(
content,
size: const Size.square(24),
color: Theme.of(context).colorScheme.surface,
),
),
),
);
}
}
if (onHover) {
return Stack(
children: [

View File

@ -1,16 +1,15 @@
import 'package:appflowy/generated/flowy_svgs.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/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_icon_popup.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-user/user_profile.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.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_bloc/flutter_bloc.dart';
@ -108,20 +107,19 @@ class SpaceMoreActionTypeWrapper extends CustomActionCell {
PopoverController controller,
) {
final child = _buildActionButton(context, null);
final spaceBloc = context.read<SpaceBloc>();
final color = spaceBloc.state.currentSpace?.spaceIconColor;
return AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(216, 256)),
margin: const EdgeInsets.symmetric(horizontal: 14.0, vertical: 12.0),
constraints: BoxConstraints.loose(const Size(380, 432)),
margin: const EdgeInsets.all(0),
clickHandler: PopoverClickHandler.gestureDetector,
popupBuilder: (_) => SpaceIconPicker(
iconColor: color,
skipFirstNotification: true,
onIconChanged: (icon, color) {
onTap(controller, (icon, color));
offset: const Offset(0, -40),
popupBuilder: (context) {
return FlowyIconEmojiPicker(
tabs: const [PickerTabType.icon],
onSelectedIcon: (group, icon, color) {
onTap(controller, (group, icon, color));
},
);
},
),
child: child,
);
}
@ -172,6 +170,8 @@ class SpaceMoreActionTypeWrapper extends CustomActionCell {
rightIconBuilder: (_) => inner.rightIcon,
textBuilder: (onHover) => FlowyText.regular(
inner.name,
fontSize: 14.0,
figmaLineHeight: 18.0,
color: inner == SpaceMoreActionType.delete && onHover
? Theme.of(context).colorScheme.error
: null,

View File

@ -1,6 +1,6 @@
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_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
@ -18,6 +18,7 @@ class WorkspaceIcon extends StatefulWidget {
this.borderRadius = 4,
this.emojiSize,
this.alignment,
required this.figmaLineHeight,
});
final UserWorkspacePB workspace;
@ -28,6 +29,7 @@ class WorkspaceIcon extends StatefulWidget {
final void Function(EmojiPickerResult) onSelected;
final double borderRadius;
final Alignment? alignment;
final double figmaLineHeight;
@override
State<WorkspaceIcon> createState() => _WorkspaceIconState();
@ -45,7 +47,8 @@ class _WorkspaceIconState extends State<WorkspaceIcon> {
child: FlowyText.emoji(
widget.workspace.icon,
fontSize: widget.emojiSize ?? widget.iconSize,
figmaLineHeight: 21.0,
figmaLineHeight: widget.figmaLineHeight,
optimizeEmojiAlign: true,
),
)
: Container(
@ -76,8 +79,9 @@ class _WorkspaceIconState extends State<WorkspaceIcon> {
direction: PopoverDirection.bottomWithLeftAligned,
constraints: BoxConstraints.loose(const Size(364, 356)),
clickHandler: PopoverClickHandler.gestureDetector,
popupBuilder: (_) => FlowyIconPicker(
onSelected: (result) {
margin: const EdgeInsets.all(0),
popupBuilder: (_) => FlowyIconEmojiPicker(
onSelectedEmoji: (result) {
widget.onSelected(result);
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:easy_localization/easy_localization.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_bloc/flutter_bloc.dart';
@ -172,6 +171,7 @@ class _WorkspaceMenuItemState extends State<WorkspaceMenuItem> {
workspace: widget.workspace,
iconSize: 22,
fontSize: 16,
figmaLineHeight: 32.0,
enableEdit: true,
onSelected: (result) => context.read<UserWorkspaceBloc>().add(
UserWorkspaceEvent.updateWorkspaceIcon(

View File

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

View File

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:appflowy/generated/flowy_svgs.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/workspace/application/favorite/favorite_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:flowy_infra_ui/flowy_infra_ui.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_bloc/flutter_bloc.dart';
@ -583,6 +582,7 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
controller: controller,
direction: PopoverDirection.rightWithCenterAligned,
constraints: BoxConstraints.loose(const Size(364, 356)),
margin: const EdgeInsets.all(0),
onClose: () => setState(() => isIconPickerOpened = false),
child: GestureDetector(
// prevent the tap event from being passed to the parent widget
@ -594,8 +594,8 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
),
popupBuilder: (context) {
isIconPickerOpened = true;
return FlowyIconPicker(
onSelected: (result) {
return FlowyIconEmojiPicker(
onSelectedEmoji: (result) {
ViewBackendService.updateViewIcon(
viewId: widget.view.id,
viewIcon: result.emoji,

View File

@ -1,5 +1,5 @@
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/space/space_bloc.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(
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,
popupBuilder: (_) => FlowyIconPicker(
onSelected: (result) => onTap(controller, result),
popupBuilder: (_) => FlowyIconEmojiPicker(
onSelectedEmoji: (result) => onTap(controller, result),
),
child: child,
);
@ -256,6 +256,8 @@ class ViewMoreActionTypeWrapper extends CustomActionCell {
iconPadding: 10.0,
textBuilder: (onHover) => FlowyText.regular(
inner.name,
fontSize: 14.0,
figmaLineHeight: 18.0,
color: inner == ViewMoreActionType.delete && onHover
? Theme.of(context).colorScheme.error
: null,

View File

@ -11,7 +11,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.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';
class NotificationButton extends StatefulWidget {

View File

@ -1,7 +1,7 @@
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/flowy_svgs.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/user/application/auth/auth_service.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:flowy_infra_ui/flowy_infra_ui.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/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -466,9 +465,9 @@ class _UserProfileSettingState extends State<UserProfileSetting> {
Container(
height: 380,
width: 360,
margin: const EdgeInsets.symmetric(horizontal: 12),
child: FlowyIconPicker(
onSelected: (r) {
margin: const EdgeInsets.all(0),
child: FlowyIconEmojiPicker(
onSelectedEmoji: (r) {
context
.read<SettingsUserViewBloc>()
.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:flowy_infra/theme_extension.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 '../../../../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/style_widget/hover.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';
class SettingsShortcutsView extends StatefulWidget {

View File

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

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.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
/// 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:flowy_infra/theme_extension.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
///

View File

@ -16,7 +16,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.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:flutter_bloc/flutter_bloc.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_ui/flowy_infra_ui.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';
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/style_widget/hover.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: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:flowy_infra_ui/flowy_infra_ui.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_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_popover/appflowy_popover.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_bloc/flutter_bloc.dart';
@ -173,7 +172,7 @@ class _ViewTitleState extends State<_ViewTitle> {
return Container(
alignment: Alignment.center,
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(
useIntrinsicWidth: true,
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(
useIntrinsicWidth: true,
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(
child: Row(
children: [
@ -234,10 +238,10 @@ class _ViewTitleState extends State<_ViewTitle> {
),
const HSpace(4.0),
],
if (state.view?.isSpace == true &&
state.view?.spaceIconSvg != null) ...[
if (state.view?.isSpace == true && spaceIcon != null) ...[
SpaceIcon(
dimension: 14,
svgSize: 8.5,
space: state.view!,
cornerRadius: 4,
),

View File

@ -1,4 +1,5 @@
// Basis
export '/widget/flowy_tooltip.dart';
export '/widget/separated_flex.dart';
export '/widget/spacing.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_ui/flowy_infra_ui.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:url_launcher/url_launcher.dart';

View File

@ -24,8 +24,29 @@ class FlowySvg extends StatelessWidget {
this.color,
this.blendMode = BlendMode.srcIn,
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
/// package within bin/flowy_svg.dart
final FlowySvgData svg;
@ -33,6 +54,9 @@ class FlowySvg extends StatelessWidget {
/// The size of the svg
final Size? size;
/// The svg string
final String? svgString;
/// The color of the svg.
///
/// This property will not be applied to the underlying svg widget if the
@ -57,12 +81,23 @@ class FlowySvg extends StatelessWidget {
final iconColor =
(color ?? Theme.of(context).iconTheme.color)?.withOpacity(opacity);
final textScaleFactor = MediaQuery.textScalerOf(context).scale(1);
return Transform.scale(
scale: textScaleFactor,
child: SizedBox(
final Widget svg;
if (svgString != null) {
svg = SvgPicture.string(
svgString!,
width: size?.width,
height: size?.height,
child: SvgPicture.asset(
colorFilter: iconColor != null && blendMode != null
? ColorFilter.mode(
iconColor,
blendMode!,
)
: null,
);
} else {
svg = SvgPicture.asset(
_normalized(),
width: size?.width,
height: size?.height,
@ -72,7 +107,15 @@ class FlowySvg extends StatelessWidget {
blendMode!,
)
: null,
),
);
}
return Transform.scale(
scale: textScaleFactor,
child: SizedBox(
width: size?.width,
height: size?.height,
child: svg,
),
);
}

View File

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

View File

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