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/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();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user