mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
* 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:
parent
079b9888a8
commit
e500c89978
@ -1,12 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/util/color_to_hex_string.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/shared/settings_alert_dialog.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/utils/hex_opacity_string_extension.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/utils/hex_opacity_string_extension.dart';
|
||||||
import 'package:easy_localization/easy_localization.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/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class DocumentColorSettingButton extends StatefulWidget {
|
class DocumentColorSettingButton extends StatefulWidget {
|
||||||
const DocumentColorSettingButton({
|
const DocumentColorSettingButton({
|
||||||
@ -90,24 +92,11 @@ class DocumentColorSettingDialogState
|
|||||||
late TextEditingController hexController;
|
late TextEditingController hexController;
|
||||||
late TextEditingController opacityController;
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
selectedColorOnDialog = widget.currentColor;
|
selectedColorOnDialog = widget.currentColor;
|
||||||
currentColorHexString = widget.currentColor.toHexString();
|
currentColorHexString = ColorExtension(widget.currentColor).toHexString();
|
||||||
hexController = TextEditingController(
|
hexController = TextEditingController(
|
||||||
text: currentColorHexString.extractHex(),
|
text: currentColorHexString.extractHex(),
|
||||||
);
|
);
|
||||||
@ -145,17 +134,25 @@ class DocumentColorSettingDialogState
|
|||||||
controller: hexController,
|
controller: hexController,
|
||||||
labelText: LocaleKeys.editor_hexValue.tr(),
|
labelText: LocaleKeys.editor_hexValue.tr(),
|
||||||
hintText: '6fc9e7',
|
hintText: '6fc9e7',
|
||||||
onChanged: (_) => updateSelectedColor(),
|
onChanged: (_) => _updateSelectedColor(),
|
||||||
onFieldSubmitted: (_) => updateSelectedColor(),
|
onFieldSubmitted: (_) => _updateSelectedColor(),
|
||||||
validator: (v) => validateHexValue(v, opacityController.text),
|
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),
|
const VSpace(8),
|
||||||
_ColorSettingTextField(
|
_ColorSettingTextField(
|
||||||
controller: opacityController,
|
controller: opacityController,
|
||||||
labelText: LocaleKeys.editor_opacity.tr(),
|
labelText: LocaleKeys.editor_opacity.tr(),
|
||||||
hintText: '50',
|
hintText: '50',
|
||||||
onChanged: (_) => updateSelectedColor(),
|
onChanged: (_) => _updateSelectedColor(),
|
||||||
onFieldSubmitted: (_) => updateSelectedColor(),
|
onFieldSubmitted: (_) => _updateSelectedColor(),
|
||||||
validator: (value) => validateOpacityValue(value),
|
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 {
|
class _ColorSettingTextField extends StatelessWidget {
|
||||||
@ -172,6 +191,7 @@ class _ColorSettingTextField extends StatelessWidget {
|
|||||||
required this.labelText,
|
required this.labelText,
|
||||||
required this.hintText,
|
required this.hintText,
|
||||||
required this.onFieldSubmitted,
|
required this.onFieldSubmitted,
|
||||||
|
this.suffixIcon,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.validator,
|
this.validator,
|
||||||
});
|
});
|
||||||
@ -180,6 +200,7 @@ class _ColorSettingTextField extends StatelessWidget {
|
|||||||
final String labelText;
|
final String labelText;
|
||||||
final String hintText;
|
final String hintText;
|
||||||
final void Function(String) onFieldSubmitted;
|
final void Function(String) onFieldSubmitted;
|
||||||
|
final Widget? suffixIcon;
|
||||||
final void Function(String)? onChanged;
|
final void Function(String)? onChanged;
|
||||||
final String? Function(String?)? validator;
|
final String? Function(String?)? validator;
|
||||||
|
|
||||||
@ -191,6 +212,7 @@ class _ColorSettingTextField extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: labelText,
|
labelText: labelText,
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
|
suffixIcon: suffixIcon,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderSide: BorderSide(color: style.colorScheme.outline),
|
borderSide: BorderSide(color: style.colorScheme.outline),
|
||||||
),
|
),
|
||||||
@ -241,3 +263,145 @@ String? validateOpacityValue(String? value) {
|
|||||||
}
|
}
|
||||||
return null;
|
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();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -602,6 +602,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
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:
|
flowy_infra:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -66,6 +66,7 @@ dependencies:
|
|||||||
styled_widget: ^0.4.1
|
styled_widget: ^0.4.1
|
||||||
expandable: ^5.0.1
|
expandable: ^5.0.1
|
||||||
flutter_colorpicker: ^1.0.3
|
flutter_colorpicker: ^1.0.3
|
||||||
|
flex_color_picker: ^3.5.1
|
||||||
highlight: ^0.7.0
|
highlight: ^0.7.0
|
||||||
package_info_plus: ^6.0.0
|
package_info_plus: ^6.0.0
|
||||||
url_launcher: ^6.1.11
|
url_launcher: ^6.1.11
|
||||||
|
@ -857,6 +857,9 @@
|
|||||||
"documentSettings": {
|
"documentSettings": {
|
||||||
"cursorColor": "Document cursor color",
|
"cursorColor": "Document cursor color",
|
||||||
"selectionColor": "Document selection color",
|
"selectionColor": "Document selection color",
|
||||||
|
"pickColor": "Select a color",
|
||||||
|
"colorShade": "Color shade",
|
||||||
|
"opacity": "Opacity",
|
||||||
"hexEmptyError": "Hex color cannot be empty",
|
"hexEmptyError": "Hex color cannot be empty",
|
||||||
"hexLengthError": "Hex value must be 6 digits long",
|
"hexLengthError": "Hex value must be 6 digits long",
|
||||||
"hexInvalidError": "Invalid hex value",
|
"hexInvalidError": "Invalid hex value",
|
||||||
|
Loading…
Reference in New Issue
Block a user