Skip to content

Commit

Permalink
Support for automatic mode in color selection buttons (#187)
Browse files Browse the repository at this point in the history
  • Loading branch information
amantoux authored Dec 8, 2023
1 parent 416244c commit cd886f0
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 72 deletions.
134 changes: 69 additions & 65 deletions packages/fleather/lib/src/widgets/editor_toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -350,12 +350,14 @@ Widget defaultToggleStyleButtonBuilder(
);
}

/// Signature of callbacks that return a [Color] picked from a [BuildContext].
typedef PickColor = Future<Color?> Function(BuildContext);
/// Signature of callbacks that return a [Color] picked from a palette built in
/// a [BuildContext] with a [String] specifying the label of the `null` selection
/// option
typedef PickColor = Future<Color?> Function(BuildContext, String);

/// Signature of callbacks the return a [Widget] from a [BuildContext]
/// and a [Color].
typedef ColorButtonBuilder = Widget Function(BuildContext, Color);
/// Signature of callbacks the returns a [Widget] from a [BuildContext]
/// and a [Color] (`null` color to use the default color of the text - copes with dark mode).
typedef ColorButtonBuilder = Widget Function(BuildContext, Color?);

/// Toolbar button which allows to apply background color style to a portion of text.
///
Expand All @@ -365,14 +367,14 @@ class ColorButton extends StatefulWidget {
{Key? key,
required this.controller,
required this.attributeKey,
required this.defaultColor,
required this.nullColorLabel,
required this.builder,
this.pickColor})
: super(key: key);

final FleatherController controller;
final ColorParchmentAttributeBuilder attributeKey;
final Color defaultColor;
final String nullColorLabel;
final ColorButtonBuilder builder;
final PickColor? pickColor;

Expand All @@ -383,18 +385,20 @@ class ColorButton extends StatefulWidget {
class _ColorButtonState extends State<ColorButton> {
static double buttonSize = 32;

late Color _value;
late Color? _value;

ParchmentStyle get _selectionStyle => widget.controller.getSelectionStyle();

void _didChangeEditingValue() {
setState(() {
_value = Color(_selectionStyle.get(widget.attributeKey)?.value ??
widget.defaultColor.value);
final selectionColor = _selectionStyle.get(widget.attributeKey);
_value =
selectionColor?.value != null ? Color(selectionColor!.value!) : null;
});
}

Future<Color?> _defaultPickColor(BuildContext context) async {
Future<Color?> _defaultPickColor(
BuildContext context, String nullColorLabel) async {
// kIsWeb important here as Platform.xxx will cause a crash en web
final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS);
final maxWidth = isMobile ? 200.0 : 100.0;
Expand All @@ -408,7 +412,7 @@ class _ColorButtonState extends State<ColorButton> {
child: Container(
constraints: BoxConstraints(maxWidth: maxWidth),
padding: const EdgeInsets.all(8.0),
child: _ColorPalette(defaultColor: widget.defaultColor)),
child: _ColorPalette(nullColorLabel)),
);

return Navigator.of(context).push<Color>(
Expand All @@ -433,8 +437,9 @@ class _ColorButtonState extends State<ColorButton> {
@override
void initState() {
super.initState();
_value = Color(_selectionStyle.get(widget.attributeKey)?.value ??
widget.defaultColor.value);
final selectionColor = _selectionStyle.get(widget.attributeKey);
_value =
selectionColor?.value != null ? Color(selectionColor!.value!) : null;
widget.controller.addListener(_didChangeEditingValue);
}

Expand All @@ -444,8 +449,9 @@ class _ColorButtonState extends State<ColorButton> {
if (oldWidget.controller != widget.controller) {
oldWidget.controller.removeListener(_didChangeEditingValue);
widget.controller.addListener(_didChangeEditingValue);
_value = Color(_selectionStyle.get(widget.attributeKey)?.value ??
widget.defaultColor.value);
final selectionColor = _selectionStyle.get(widget.attributeKey);
_value =
selectionColor?.value != null ? Color(selectionColor!.value!) : null;
}
}

Expand All @@ -469,10 +475,12 @@ class _ColorButtonState extends State<ColorButton> {
highlightElevation: 0,
hoverElevation: 0,
onPressed: () async {
final selectedColor =
await (widget.pickColor ?? _defaultPickColor)(context);
widget.controller.formatSelection(widget.attributeKey
.withColor(selectedColor?.value ?? widget.defaultColor.value));
final selectedColor = await (widget.pickColor ?? _defaultPickColor)(
context, widget.nullColorLabel);
final attribute = selectedColor != null
? widget.attributeKey.withColor(selectedColor.value)
: widget.attributeKey.unset;
widget.controller.formatSelection(attribute);
},
child: Builder(builder: (context) => widget.builder(context, _value)),
),
Expand All @@ -482,6 +490,7 @@ class _ColorButtonState extends State<ColorButton> {

class _ColorPalette extends StatelessWidget {
static const colors = [
null,
Colors.indigo,
Colors.blue,
Colors.cyan,
Expand All @@ -496,12 +505,13 @@ class _ColorPalette extends StatelessWidget {
Colors.purple,
Colors.brown,
Colors.grey,
Colors.white
Colors.white,
Colors.black,
];

const _ColorPalette({required this.defaultColor});
const _ColorPalette(this.nullColorLabel);

final Color defaultColor;
final String nullColorLabel;

@override
Widget build(BuildContext context) {
Expand All @@ -510,36 +520,39 @@ class _ColorPalette extends StatelessWidget {
alignment: WrapAlignment.start,
runSpacing: 4,
spacing: 4,
children: [defaultColor, ...colors]
.map((e) => _ColorPaletteElement(color: e))
children: [...colors]
.map((e) => _ColorPaletteElement(e, nullColorLabel))
.toList(),
);
}
}

class _ColorPaletteElement extends StatelessWidget {
const _ColorPaletteElement({required this.color});
const _ColorPaletteElement(this.color, this.nullColorLabel);

final Color color;
final Color? color;
final String nullColorLabel;

@override
Widget build(BuildContext context) {
// kIsWeb important here as Platform.xxx will cause a crash en web
final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS);
final size = isMobile ? 32.0 : 16.0;
return Container(
width: size,
width: (color == null ? 4 : 1) * size + (color == null ? 3 * 4 : 0),
height: size,
decoration: BoxDecoration(
color: color,
border: color == Colors.transparent
? Border.all(
color: Colors.black,
strokeAlign: BorderSide.strokeAlignInside,
),
child: RawMaterialButton(
onPressed: () => Navigator.pop(context, color),
child: color == null
? Text(
nullColorLabel,
style: Theme.of(context).textTheme.bodySmall,
)
: null,
),
child: RawMaterialButton(onPressed: () => Navigator.pop(context, color)),
);
}
}
Expand Down Expand Up @@ -849,39 +862,30 @@ class FleatherToolbar extends StatefulWidget implements PreferredSizeWidget {
Container(
width: 18,
height: 4,
decoration: BoxDecoration(
color: value,
border: value == Colors.transparent
? Border.all(
color:
Theme.of(context).iconTheme.color ?? Colors.black)
: null,
),
)
],
);
Widget textColorBuilder(context, value) => Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.text_fields_sharp,
size: 16,
),
Container(
width: 18,
height: 4,
decoration: BoxDecoration(
color: value,
border: value == Colors.transparent
? Border.all(
color:
Theme.of(context).iconTheme.color ?? Colors.black)
: null,
),
decoration: BoxDecoration(color: value),
)
],
);
Widget textColorBuilder(context, value) {
Color effectiveColor =
value ?? DefaultTextStyle.of(context).style.color ?? Colors.black;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.text_fields_sharp,
size: 16,
),
Container(
width: 18,
height: 4,
decoration: BoxDecoration(color: effectiveColor),
)
],
);
}

return FleatherToolbar(key: key, padding: padding, children: [
...leading,
Visibility(
Expand Down Expand Up @@ -925,7 +929,7 @@ class FleatherToolbar extends StatefulWidget implements PreferredSizeWidget {
child: ColorButton(
controller: controller,
attributeKey: ParchmentAttribute.foregroundColor,
defaultColor: Colors.black,
nullColorLabel: 'Automatic',
builder: textColorBuilder,
),
),
Expand All @@ -934,7 +938,7 @@ class FleatherToolbar extends StatefulWidget implements PreferredSizeWidget {
child: ColorButton(
controller: controller,
attributeKey: ParchmentAttribute.backgroundColor,
defaultColor: Colors.transparent,
nullColorLabel: 'No color',
builder: backgroundColorBuilder,
),
),
Expand Down
14 changes: 7 additions & 7 deletions packages/fleather/test/widgets/editor_toolbar_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:fleather/fleather.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:quill_delta/quill_delta.dart';

Expand Down Expand Up @@ -84,13 +84,13 @@ Widget widget(FleatherController controller, {bool withBasic = false}) {
ColorButton(
controller: controller,
attributeKey: ParchmentAttribute.backgroundColor,
defaultColor: Colors.transparent,
nullColorLabel: 'No color',
builder: backgroundColorBuilder,
),
ColorButton(
controller: controller,
attributeKey: ParchmentAttribute.foregroundColor,
defaultColor: Colors.black,
nullColorLabel: 'Automatic',
builder: textColorBuilder,
),
IndentationButton(controller: controller),
Expand Down Expand Up @@ -325,7 +325,7 @@ void main() {
matching: find.byType(RawMaterialButton));
expect(
colorElement,
findsNWidgets(16),
findsNWidgets(17),
);

await tester.tap(find
Expand All @@ -335,7 +335,7 @@ void main() {
.last);
await tester.pumpAndSettle(throttleDuration);
expect(controller.document.toDelta().first,
Operation.insert('Hello', {'bg': Colors.white.value}));
Operation.insert('Hello', {'bg': Colors.black.value}));
});

testWidgets('Text color', (tester) async {
Expand All @@ -356,7 +356,7 @@ void main() {
matching: find.byType(RawMaterialButton));
expect(
colorElement,
findsNWidgets(16),
findsNWidgets(17),
);

await tester.tap(find
Expand All @@ -366,7 +366,7 @@ void main() {
.last);
await tester.pumpAndSettle(throttleDuration);
expect(controller.document.toDelta().first,
Operation.insert('Hello', {'fg': Colors.white.value}));
Operation.insert('Hello', {'fg': Colors.black.value}));
});
}

Expand Down

0 comments on commit cd886f0

Please sign in to comment.