mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
parent
4041724980
commit
453e6309d5
1
frontend/appflowy_flutter/assets/icons/icons.json
Normal file
1
frontend/appflowy_flutter/assets/icons/icons.json
Normal file
File diff suppressed because one or more lines are too long
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
},
|
||||
|
@ -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';
|
||||
|
@ -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(
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
@ -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);
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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: [
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
},
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
|
@ -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';
|
||||
|
@ -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 {
|
||||
|
@ -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>()
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
///
|
||||
|
@ -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';
|
||||
|
@ -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 {
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Basis
|
||||
export '/widget/flowy_tooltip.dart';
|
||||
export '/widget/separated_flex.dart';
|
||||
export '/widget/spacing.dart';
|
||||
export 'basis.dart';
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user