feat: choose cursor/selection color from palette or color picker #5041 (#5677)

* chore: show ai service error (#5675)

* feat: Implement color picker dialog for user color selection (#5041)

- Added `flex_color_picker` dependency.
- Implemented a new color picker dialog with the following features:
  - Display Material predefined colors.
  - Include primary, accent, and shade colors, as well as a color wheel for selection.
  - Add a graphical opacity selector.
  - Enhanced the previous dialog to include an icon in a text field for opening the palette.
- Added `suffixIcon` parameter to the `_ColorSettingTextField` class, making it reactive to tap gestures.
- Utilized `ColorExtension` on the `Color` class to avoid namespace conflicts when converting colors to hexadecimal strings.
- Added translation strings to english

This commit addresses issue #5041

* feat: Implement color picker dialog for user color selection (#5041)

- Added `flex_color_picker` dependency.
- Implemented a new color picker dialog with the following features:
      - Display Material predefined colors.
      - Include primary, accent, and shade colors, as well as a color wheel for selection.
      - Add a graphical opacity selector.
      - Enhanced the previous dialog to include an icon in a text field for opening the palette.
- Added `suffixIcon` parameter to the `_ColorSettingTextField` class, making it reactive to tap gestures.
- Utilized `ColorExtension` on the `Color` class to avoid namespace conflicts when converting colors to hexadecimal strings.
- Added translation strings to english

This commit addresses issue #5041

* chore: refactor code

---------

Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com>
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
Gustavo Moreno 2024-07-10 11:49:59 +02:00 committed by GitHub
parent 079b9888a8
commit e500c89978
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 204 additions and 20 deletions

View File

@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/util/color_to_hex_string.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/utils/hex_opacity_string_extension.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flex_color_picker/flex_color_picker.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/dialog/styled_dialogs.dart';
import 'package:flutter/material.dart';
class DocumentColorSettingButton extends StatefulWidget {
const DocumentColorSettingButton({
@ -90,24 +92,11 @@ class DocumentColorSettingDialogState
late TextEditingController hexController;
late TextEditingController opacityController;
void updateSelectedColor() {
if (widget.formKey.currentState!.validate()) {
setState(() {
final colorValue = int.tryParse(
hexController.text.combineHexWithOpacity(opacityController.text),
);
// colorValue has been validated in the _ColorSettingTextField for hex value and it won't be null as this point
selectedColorOnDialog = Color(colorValue!);
widget.onChanged(selectedColorOnDialog!);
});
}
}
@override
void initState() {
super.initState();
selectedColorOnDialog = widget.currentColor;
currentColorHexString = widget.currentColor.toHexString();
currentColorHexString = ColorExtension(widget.currentColor).toHexString();
hexController = TextEditingController(
text: currentColorHexString.extractHex(),
);
@ -145,17 +134,25 @@ class DocumentColorSettingDialogState
controller: hexController,
labelText: LocaleKeys.editor_hexValue.tr(),
hintText: '6fc9e7',
onChanged: (_) => updateSelectedColor(),
onFieldSubmitted: (_) => updateSelectedColor(),
onChanged: (_) => _updateSelectedColor(),
onFieldSubmitted: (_) => _updateSelectedColor(),
validator: (v) => validateHexValue(v, opacityController.text),
suffixIcon: GestureDetector(
onTap: () => _showColorPickerDialog(
context: context,
currentColor: widget.currentColor,
updateColor: _updateColor,
),
child: const Icon(Icons.color_lens_rounded),
),
),
const VSpace(8),
_ColorSettingTextField(
controller: opacityController,
labelText: LocaleKeys.editor_opacity.tr(),
hintText: '50',
onChanged: (_) => updateSelectedColor(),
onFieldSubmitted: (_) => updateSelectedColor(),
onChanged: (_) => _updateSelectedColor(),
onFieldSubmitted: (_) => _updateSelectedColor(),
validator: (value) => validateOpacityValue(value),
),
],
@ -164,6 +161,28 @@ class DocumentColorSettingDialogState
],
);
}
void _updateSelectedColor() {
if (widget.formKey.currentState!.validate()) {
setState(() {
final colorValue = int.tryParse(
hexController.text.combineHexWithOpacity(opacityController.text),
);
// colorValue has been validated in the _ColorSettingTextField for hex value and it won't be null as this point
selectedColorOnDialog = Color(colorValue!);
widget.onChanged(selectedColorOnDialog!);
});
}
}
void _updateColor(Color color) {
setState(() {
hexController.text = ColorExtension(color).toHexString().extractHex();
opacityController.text =
ColorExtension(color).toHexString().extractOpacity();
});
_updateSelectedColor();
}
}
class _ColorSettingTextField extends StatelessWidget {
@ -172,6 +191,7 @@ class _ColorSettingTextField extends StatelessWidget {
required this.labelText,
required this.hintText,
required this.onFieldSubmitted,
this.suffixIcon,
this.onChanged,
this.validator,
});
@ -180,6 +200,7 @@ class _ColorSettingTextField extends StatelessWidget {
final String labelText;
final String hintText;
final void Function(String) onFieldSubmitted;
final Widget? suffixIcon;
final void Function(String)? onChanged;
final String? Function(String?)? validator;
@ -191,6 +212,7 @@ class _ColorSettingTextField extends StatelessWidget {
decoration: InputDecoration(
labelText: labelText,
hintText: hintText,
suffixIcon: suffixIcon,
border: OutlineInputBorder(
borderSide: BorderSide(color: style.colorScheme.outline),
),
@ -241,3 +263,145 @@ String? validateOpacityValue(String? value) {
}
return null;
}
const _kColorCircleWidth = 46.0;
const _kColorCircleHeight = 46.0;
const _kColorCircleRadius = 23.0;
const _kColorOpacityThumbRadius = 23.0;
const _kDialogButtonPaddingHorizontal = 24.0;
const _kDialogButtonPaddingVertical = 12.0;
const _kColorsColumnSpacing = 3.0;
class _ColorPicker extends StatelessWidget {
const _ColorPicker({
required this.selectedColor,
required this.onColorChanged,
});
final Color selectedColor;
final void Function(Color) onColorChanged;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return SingleChildScrollView(
child: ColorPicker(
width: _kColorCircleWidth,
height: _kColorCircleHeight,
borderRadius: _kColorCircleRadius,
enableOpacity: true,
opacityThumbRadius: _kColorOpacityThumbRadius,
columnSpacing: _kColorsColumnSpacing,
enableTooltips: false,
pickersEnabled: const {
ColorPickerType.both: false,
ColorPickerType.primary: true,
ColorPickerType.accent: true,
ColorPickerType.wheel: true,
},
subheading: Text(
LocaleKeys.settings_appearance_documentSettings_colorShade.tr(),
style: theme.textTheme.labelLarge,
),
opacitySubheading: Text(
LocaleKeys.settings_appearance_documentSettings_opacity.tr(),
style: theme.textTheme.labelLarge,
),
onColorChanged: onColorChanged,
),
);
}
}
class _ColorPickerActions extends StatelessWidget {
const _ColorPickerActions({
required this.onReset,
required this.onUpdate,
});
final VoidCallback onReset;
final VoidCallback onUpdate;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: 24,
child: FlowyTextButton(
LocaleKeys.button_cancel.tr(),
padding: const EdgeInsets.symmetric(
horizontal: _kDialogButtonPaddingHorizontal,
vertical: _kDialogButtonPaddingVertical,
),
fontColor: AFThemeExtension.of(context).textColor,
fillColor: Colors.transparent,
hoverColor: Colors.transparent,
radius: Corners.s12Border,
onPressed: onReset,
),
),
const HSpace(8),
SizedBox(
height: 48,
child: FlowyTextButton(
LocaleKeys.button_done.tr(),
padding: const EdgeInsets.symmetric(
horizontal: _kDialogButtonPaddingHorizontal,
vertical: _kDialogButtonPaddingVertical,
),
radius: Corners.s12Border,
fontHoverColor: Colors.white,
fillColor: Theme.of(context).colorScheme.primary,
hoverColor: const Color(0xFF005483),
onPressed: onUpdate,
),
),
],
);
}
}
void _showColorPickerDialog({
required BuildContext context,
String? title,
required Color currentColor,
required void Function(Color) updateColor,
}) {
final style = Theme.of(context);
Color selectedColor = currentColor;
showDialog(
context: context,
barrierColor: const Color.fromARGB(128, 0, 0, 0),
builder: (context) {
return AlertDialog(
icon: const Icon(Icons.palette),
title: Text(
title ??
LocaleKeys.settings_appearance_documentSettings_pickColor.tr(),
style: style.textTheme.titleLarge,
),
content: _ColorPicker(
selectedColor: selectedColor,
onColorChanged: (color) => selectedColor = color,
),
actionsPadding: const EdgeInsets.all(8),
actions: [
_ColorPickerActions(
onReset: () {
updateColor(currentColor);
Navigator.of(context).pop();
},
onUpdate: () {
updateColor(selectedColor);
Navigator.of(context).pop();
},
),
],
);
},
);
}

View File

@ -602,6 +602,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
flex_color_picker:
dependency: "direct main"
description:
name: flex_color_picker
sha256: "809af4ec82ede3b140ed0219b97d548de99e47aa4b99b14a10f705a2dbbcba5e"
url: "https://pub.dev"
source: hosted
version: "3.5.1"
flex_seed_scheme:
dependency: transitive
description:
name: flex_seed_scheme
sha256: "6c595e545b0678e1fe17e8eec3d1fbca7237482da194fadc20ad8607dc7a7f3d"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
flowy_infra:
dependency: "direct main"
description:

View File

@ -66,6 +66,7 @@ dependencies:
styled_widget: ^0.4.1
expandable: ^5.0.1
flutter_colorpicker: ^1.0.3
flex_color_picker: ^3.5.1
highlight: ^0.7.0
package_info_plus: ^6.0.0
url_launcher: ^6.1.11

View File

@ -857,6 +857,9 @@
"documentSettings": {
"cursorColor": "Document cursor color",
"selectionColor": "Document selection color",
"pickColor": "Select a color",
"colorShade": "Color shade",
"opacity": "Opacity",
"hexEmptyError": "Hex color cannot be empty",
"hexLengthError": "Hex value must be 6 digits long",
"hexInvalidError": "Invalid hex value",