Skip to content

Commit

Permalink
feat: text toolbar styles combine (#522)
Browse files Browse the repository at this point in the history
## Description
This PR introduces the ability to combine text style

## Type of Change
- [ ] Bug fix
- [x] New feature
- [ ] Breaking change
- [ ] Refactoring
- [ ] Documentation
- [ ] Chore

## Screenshots (if applicable)
<img width="180" alt="Screenshot 2025-01-08 at 18 03 13"
src="https://github.com/user-attachments/assets/51133318-dca2-44e0-8e56-604af3eeb133"
/>
  • Loading branch information
ice-hector authored Jan 9, 2025
1 parent 918405c commit e487ee7
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,38 @@ import 'package:flutter_quill/flutter_quill.dart';

enum FontType { regular, bold, italic, h1, h2, h3, underline }

FontType useTextEditorFontStyle(QuillController textEditorController) {
final fontType = useState<FontType>(FontType.regular);
Set<FontType> useTextEditorFontStyles(QuillController textEditorController) {
final fontStyles = useState<Set<FontType>>({});

final textEditorListener = useCallback(
() {
final style = textEditorController.getSelectionStyle();
final activeStyles = <FontType>{};

if (style.attributes.containsKey(Attribute.header.key)) {
final headerValue = style.attributes[Attribute.header.key]!.value;

if (headerValue == 1) {
fontType.value = FontType.h1;
activeStyles.add(FontType.h1);
} else if (headerValue == 2) {
fontType.value = FontType.h2;
activeStyles.add(FontType.h2);
} else if (headerValue == 3) {
fontType.value = FontType.h3;
activeStyles.add(FontType.h3);
}
} else if (style.attributes.containsKey(Attribute.bold.key)) {
fontType.value = FontType.bold;
} else if (style.attributes.containsKey(Attribute.italic.key)) {
fontType.value = FontType.italic;
} else if (style.attributes.containsKey(Attribute.underline.key)) {
fontType.value = FontType.underline;
} else {
fontType.value = FontType.regular;
activeStyles.add(FontType.regular);
}

if (style.attributes.containsKey(Attribute.bold.key)) {
activeStyles.add(FontType.bold);
}
if (style.attributes.containsKey(Attribute.italic.key)) {
activeStyles.add(FontType.italic);
}
if (style.attributes.containsKey(Attribute.underline.key)) {
activeStyles.add(FontType.underline);
}

fontStyles.value = activeStyles;
},
[textEditorController],
);
Expand All @@ -45,5 +51,5 @@ FontType useTextEditorFontStyle(QuillController textEditorController) {
[textEditorListener, textEditorController],
);

return fontType.value;
return fontStyles.value;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: ice License 1.0

import 'package:flutter_quill/flutter_quill.dart';

class QuillStyleManager {
QuillStyleManager(this.controller);
final QuillController controller;

void toggleHeaderStyle(Attribute<dynamic> headerAttribute) {
final currentStyle = controller.getSelectionStyle();

final isSameHeaderStyle =
currentStyle.attributes[headerAttribute.key]?.value == headerAttribute.value;

wipeAllStyles();

if (!isSameHeaderStyle) {
controller.formatSelection(headerAttribute);
}
}

void toggleTextStyle(Attribute<dynamic> textAttribute) {
final currentStyle = controller.getSelectionStyle();

final mutuallyExclusiveStyles = [Attribute.bold.key, Attribute.italic.key];

final hasHeaderOrLink = currentStyle.attributes.keys.any(
(key) => ['h1', 'h2', 'h3', Attribute.link.key].contains(key),
);

if (hasHeaderOrLink) {
wipeAllStyles(retainStyles: {Attribute.underline.key});
controller.formatSelection(textAttribute);
return;
}

if (mutuallyExclusiveStyles.contains(textAttribute.key)) {
toggleRegularStyle();
for (final key in mutuallyExclusiveStyles) {
if (currentStyle.attributes.containsKey(key) && key != textAttribute.key) {
final attribute = Attribute.fromKeyValue(key, null);
if (attribute != null) {
controller.formatSelection(Attribute.clone(attribute, null));
}
}
}
}

if (currentStyle.attributes.containsKey(textAttribute.key)) {
controller.formatSelection(Attribute.clone(textAttribute, null));
} else {
controller.formatSelection(textAttribute);
}
}

void toggleLinkStyle(String? link) {
if (link == null || link.isEmpty) {
wipeAllStyles();
controller.formatSelection(const LinkAttribute(null));
} else {
wipeAllStyles(retainStyles: {Attribute.link.key});
controller.formatSelection(LinkAttribute(link));
}
}

void toggleRegularStyle() {
controller
..formatSelection(Attribute.clone(Attribute.h1, null))
..formatSelection(Attribute.clone(Attribute.h2, null))
..formatSelection(Attribute.clone(Attribute.h3, null));
}

void wipeAllStyles({Set<String> retainStyles = const {}}) {
final allStyles = {
Attribute.bold.key: Attribute.bold,
Attribute.italic.key: Attribute.italic,
Attribute.underline.key: Attribute.underline,
Attribute.link.key: Attribute.link,
Attribute.h1.key: Attribute.h1,
Attribute.h2.key: Attribute.h2,
Attribute.h3.key: Attribute.h3,
};

for (final entry in allStyles.entries) {
if (!retainStyles.contains(entry.key)) {
final attribute = entry.value as Attribute<dynamic>;
controller.formatSelection(Attribute.clone(attribute, null));
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:ion/app/features/feed/views/components/actions_toolbar_button/actions_toolbar_button.dart';
import 'package:ion/app/features/feed/views/components/text_editor/hooks/use_text_editor_font_style.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/wipe_styles.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/quill_style_manager.dart';
import 'package:ion/generated/assets.gen.dart';

class ToolbarBoldButton extends HookWidget {
Expand All @@ -14,16 +14,17 @@ class ToolbarBoldButton extends HookWidget {

@override
Widget build(BuildContext context) {
final fontType = useTextEditorFontStyle(textEditorController);
final fontStyles = useTextEditorFontStyles(textEditorController);
final styleManager =
useMemoized(() => QuillStyleManager(textEditorController), [textEditorController]);

return ActionsToolbarButton(
icon: Assets.svg.iconPostBoldtextOff,
iconSelected: Assets.svg.iconPostBoldtextOn,
onPressed: () {
wipeAllStyles(textEditorController);
textEditorController.formatSelection(Attribute.bold);
styleManager.toggleTextStyle(Attribute.bold);
},
selected: fontType == FontType.bold,
selected: fontStyles.contains(FontType.bold),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:ion/app/features/feed/views/components/actions_toolbar_button/actions_toolbar_button.dart';
import 'package:ion/app/features/feed/views/components/text_editor/hooks/use_text_editor_font_style.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/wipe_styles.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/quill_style_manager.dart';
import 'package:ion/generated/assets.gen.dart';

class ToolbarH1Button extends HookWidget {
Expand All @@ -14,16 +14,17 @@ class ToolbarH1Button extends HookWidget {

@override
Widget build(BuildContext context) {
final fontType = useTextEditorFontStyle(textEditorController);
final fontStyles = useTextEditorFontStyles(textEditorController);
final styleManager =
useMemoized(() => QuillStyleManager(textEditorController), [textEditorController]);

return ActionsToolbarButton(
icon: Assets.svg.iconArticleH1Off,
iconSelected: Assets.svg.iconArticleH1On,
onPressed: () {
wipeAllStyles(textEditorController);
textEditorController.formatSelection(Attribute.h1);
styleManager.toggleHeaderStyle(Attribute.h1);
},
selected: fontType == FontType.h1,
selected: fontStyles.contains(FontType.h1),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:ion/app/features/feed/views/components/actions_toolbar_button/actions_toolbar_button.dart';
import 'package:ion/app/features/feed/views/components/text_editor/hooks/use_text_editor_font_style.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/wipe_styles.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/quill_style_manager.dart';
import 'package:ion/generated/assets.gen.dart';

class ToolbarH2Button extends HookWidget {
Expand All @@ -14,16 +14,17 @@ class ToolbarH2Button extends HookWidget {

@override
Widget build(BuildContext context) {
final fontType = useTextEditorFontStyle(textEditorController);
final fontStyles = useTextEditorFontStyles(textEditorController);
final styleManager =
useMemoized(() => QuillStyleManager(textEditorController), [textEditorController]);

return ActionsToolbarButton(
icon: Assets.svg.iconArticleH2Off,
iconSelected: Assets.svg.iconArticleH2On,
onPressed: () {
wipeAllStyles(textEditorController);
textEditorController.formatSelection(Attribute.h2);
styleManager.toggleHeaderStyle(Attribute.h2);
},
selected: fontType == FontType.h2,
selected: fontStyles.contains(FontType.h2),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:ion/app/features/feed/views/components/actions_toolbar_button/actions_toolbar_button.dart';
import 'package:ion/app/features/feed/views/components/text_editor/hooks/use_text_editor_font_style.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/wipe_styles.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/quill_style_manager.dart';
import 'package:ion/generated/assets.gen.dart';

class ToolbarH3Button extends HookWidget {
Expand All @@ -14,16 +14,17 @@ class ToolbarH3Button extends HookWidget {

@override
Widget build(BuildContext context) {
final fontType = useTextEditorFontStyle(textEditorController);
final fontStyles = useTextEditorFontStyles(textEditorController);
final styleManager =
useMemoized(() => QuillStyleManager(textEditorController), [textEditorController]);

return ActionsToolbarButton(
icon: Assets.svg.iconArticleH3Off,
iconSelected: Assets.svg.iconArticleH3On,
onPressed: () {
wipeAllStyles(textEditorController);
textEditorController.formatSelection(Attribute.h3);
styleManager.toggleHeaderStyle(Attribute.h3);
},
selected: fontType == FontType.h3,
selected: fontStyles.contains(FontType.h3),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:ion/app/features/feed/views/components/actions_toolbar_button/actions_toolbar_button.dart';
import 'package:ion/app/features/feed/views/components/text_editor/hooks/use_text_editor_font_style.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/wipe_styles.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/quill_style_manager.dart';
import 'package:ion/generated/assets.gen.dart';

class ToolbarItalicButton extends HookWidget {
Expand All @@ -14,16 +14,17 @@ class ToolbarItalicButton extends HookWidget {

@override
Widget build(BuildContext context) {
final fontType = useTextEditorFontStyle(textEditorController);
final fontStyles = useTextEditorFontStyles(textEditorController);
final styleManager =
useMemoized(() => QuillStyleManager(textEditorController), [textEditorController]);

return ActionsToolbarButton(
icon: Assets.svg.iconPostItalictextOff,
iconSelected: Assets.svg.iconPostItalictextOn,
onPressed: () {
wipeAllStyles(textEditorController);
textEditorController.formatSelection(Attribute.italic);
styleManager.toggleTextStyle(Attribute.italic);
},
selected: fontType == FontType.italic,
selected: fontStyles.contains(FontType.italic),
);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// SPDX-License-Identifier: ice License 1.0

import 'package:flutter/cupertino.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/features/feed/views/components/actions_toolbar_button/actions_toolbar_button.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/wipe_styles.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/quill_style_manager.dart';
import 'package:ion/generated/assets.gen.dart';

class ToolbarLinkButton extends StatelessWidget {
class ToolbarLinkButton extends HookWidget {
const ToolbarLinkButton({
required this.textEditorController,
super.key,
Expand All @@ -16,6 +17,9 @@ class ToolbarLinkButton extends StatelessWidget {

@override
Widget build(BuildContext context) {
final styleManager =
useMemoized(() => QuillStyleManager(textEditorController), [textEditorController]);

return ActionsToolbarButton(
icon: Assets.svg.iconArticleLink,
onPressed: () async {
Expand All @@ -36,12 +40,7 @@ class ToolbarLinkButton extends StatelessWidget {
if (!localContext.mounted) return;

if (resultLink != null) {
if (resultLink.isNotEmpty) {
wipeAllStyles(textEditorController);
textEditorController.formatSelection(LinkAttribute(resultLink));
} else {
textEditorController.formatSelection(const LinkAttribute(null));
}
styleManager.toggleLinkStyle(resultLink);
}
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:ion/app/features/feed/views/components/actions_toolbar_button/actions_toolbar_button.dart';
import 'package:ion/app/features/feed/views/components/text_editor/hooks/use_text_editor_font_style.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/wipe_styles.dart';
import 'package:ion/app/features/feed/views/components/text_editor/utils/quill_style_manager.dart';
import 'package:ion/generated/assets.gen.dart';

class ToolbarRegularButton extends HookWidget {
Expand All @@ -14,15 +14,15 @@ class ToolbarRegularButton extends HookWidget {

@override
Widget build(BuildContext context) {
final fontType = useTextEditorFontStyle(textEditorController);
final fontStyles = useTextEditorFontStyles(textEditorController);
final styleManager =
useMemoized(() => QuillStyleManager(textEditorController), [textEditorController]);

return ActionsToolbarButton(
icon: Assets.svg.iconPostRegulartextOff,
iconSelected: Assets.svg.iconPostRegulartextOn,
onPressed: () {
wipeAllStyles(textEditorController);
},
selected: fontType == FontType.regular,
onPressed: styleManager.toggleRegularStyle,
selected: fontStyles.contains(FontType.regular),
);
}
}
Loading

0 comments on commit e487ee7

Please sign in to comment.