From d3029ec4defe3492360be438ca6b8fbb9d3860eb Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 26 Nov 2025 12:10:55 +0700 Subject: [PATCH] TF-4178 Add create new label modal --- assets/images/ic_color_picker.svg | 17 ++ core/lib/core.dart | 2 + .../extensions/color_extension.dart | 1 + .../presentation/resources/image_paths.dart | 1 + .../views/button/tmail_button_widget.dart | 2 + .../views/color/color_circle_widget.dart | 49 +++ .../views/color/colors_map_widget.dart | 131 ++++++++ .../lib/extensions/list_label_extension.dart | 4 + .../labels/presentation/label_controller.dart | 19 ++ .../label_interactor_bindings.dart | 2 + .../widgets/create_new_label_modal.dart | 280 ++++++++++++++++++ .../presentation/base_mailbox_view.dart | 1 + .../validator_failure_extension.dart | 14 + ...play_overlay_view_on_iframe_extension.dart | 3 +- lib/l10n/intl_messages.arb | 48 +++ lib/main/localizations/app_localizations.dart | 56 ++++ 16 files changed, 629 insertions(+), 1 deletion(-) create mode 100644 assets/images/ic_color_picker.svg create mode 100644 core/lib/presentation/views/color/color_circle_widget.dart create mode 100644 core/lib/presentation/views/color/colors_map_widget.dart create mode 100644 lib/features/labels/presentation/widgets/create_new_label_modal.dart diff --git a/assets/images/ic_color_picker.svg b/assets/images/ic_color_picker.svg new file mode 100644 index 000000000..dbfe32dc2 --- /dev/null +++ b/assets/images/ic_color_picker.svg @@ -0,0 +1,17 @@ + + + + + + + diff --git a/core/lib/core.dart b/core/lib/core.dart index 89fd9e18d..6d435e079 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -117,6 +117,8 @@ export 'presentation/views/semantics/text_field_semantics.dart'; export 'presentation/views/semantics/icon_semantics.dart'; export 'presentation/views/shortcut/key_shortcut.dart'; export 'presentation/views/tooltip/iframe_tooltip_overlay.dart'; +export 'presentation/views/color/color_circle_widget.dart'; +export 'presentation/views/color/colors_map_widget.dart'; // Resources export 'presentation/resources/assets_paths.dart'; diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index e8dcfcbcf..d0297f409 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -280,6 +280,7 @@ extension AppColor on Color { static const gray49454F = Color(0xFF49454F); static const blue00B7FF = Color(0xFF00B7FF); static const blueD2E9FF = Color(0xFFD2E9FF); + static const grayCDCDCD = Color(0xFFCDCDCD); static const lightGrayF9FAFB = Color(0xFFF9FAFB); static const black4D4D4D = Color(0xFF4D4D4D); static const black1A1A1A = Color(0xFF1A1A1A); diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index ef8b9ea2c..d220549a7 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -276,6 +276,7 @@ class ImagePaths { String get icAiMoreProfessional => _getImagePath('ic_ai_more_professional.svg'); String get icAiMorePolite => _getImagePath('ic_ai_more_polite.svg'); String get icTag => _getImagePath('ic_tag.svg'); + String get icColorPicker => _getImagePath('ic_color_picker.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/core/lib/presentation/views/button/tmail_button_widget.dart b/core/lib/presentation/views/button/tmail_button_widget.dart index 44ee04545..0b7a75f4d 100644 --- a/core/lib/presentation/views/button/tmail_button_widget.dart +++ b/core/lib/presentation/views/button/tmail_button_widget.dart @@ -110,6 +110,7 @@ class TMailButtonWidget extends StatelessWidget { EdgeInsetsGeometry? margin, Color? hoverColor, Alignment? alignment, + BoxBorder? border, }) { return TMailButtonWidget( key: key, @@ -137,6 +138,7 @@ class TMailButtonWidget extends StatelessWidget { margin: margin, hoverColor: hoverColor, alignment: alignment, + border: border, ); } diff --git a/core/lib/presentation/views/color/color_circle_widget.dart b/core/lib/presentation/views/color/color_circle_widget.dart new file mode 100644 index 000000000..0dfd8fb0e --- /dev/null +++ b/core/lib/presentation/views/color/color_circle_widget.dart @@ -0,0 +1,49 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class ColorCircleWidget extends StatelessWidget { + final Color color; + final bool isSelected; + final ImagePaths imagePaths; + final VoidCallback onTap; + + const ColorCircleWidget({ + super.key, + required this.color, + required this.isSelected, + required this.imagePaths, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Material( + type: MaterialType.transparency, + child: InkWell( + onTap: onTap, + customBorder: const CircleBorder(), + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + ), + child: isSelected + ? Center( + child: SvgPicture.asset( + imagePaths.icCheck, + colorFilter: Colors.white.asFilter(), + width: 18.46, + height: 18.46, + fit: BoxFit.fill, + ), + ) + : null, + ), + ), + ); + } +} diff --git a/core/lib/presentation/views/color/colors_map_widget.dart b/core/lib/presentation/views/color/colors_map_widget.dart new file mode 100644 index 000000000..90520355d --- /dev/null +++ b/core/lib/presentation/views/color/colors_map_widget.dart @@ -0,0 +1,131 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:core/presentation/views/color/color_circle_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class ColorsMapWidget extends StatefulWidget { + final Color? initialColor; + + const ColorsMapWidget({ + super.key, + this.initialColor, + }); + + @override + State createState() => _ColorsMapWidgetState(); +} + +class _ColorsMapWidgetState extends State { + static const List _defaultColors = [ + Color(0xFF273891), + Color(0xFF7E57E3), + Color(0xFF9E83E8), + Color(0xFF617ADB), + Color(0xFF4896E5), + Color(0xFF038199), + Color(0xFF457D6C), + Color(0xFF1EBBCC), + Color(0xFF51B588), + Color(0xFFE0465C), + Color(0xFFED20A4), + Color(0xFFED768D), + Color(0xFFB85B17), + Color(0xFFBA8144), + Color(0xFFED916B), + Color(0xFFEDA91D), + Color(0xFFEDC661), + Color(0xFF131326), + Color(0xFF2C2C42), + Color(0xFF646580), + ]; + + final _imagePaths = Get.find(); + + final ValueNotifier _selectedColor = ValueNotifier(null); + + @override + void initState() { + super.initState(); + _selectedColor.value = widget.initialColor; + } + + @override + Widget build(BuildContext context) { + return Wrap( + spacing: 10, + runSpacing: 10, + children: [ + TMailButtonWidget.fromIcon( + icon: _imagePaths.icCloseDialog, + iconColor: AppColor.m3SurfaceBackground.withValues(alpha: 0.48), + iconSize: 18.46, + width: 40, + height: 40, + borderRadius: 100, + backgroundColor: Colors.transparent, + border: Border.all(width: 2, color: AppColor.grayCDCDCD), + onTapActionCallback: _clearColor, + ), + ..._defaultColors + .map( + (color) => ValueListenableBuilder( + valueListenable: _selectedColor, + builder: (context, value, child) { + return ColorCircleWidget( + color: color, + isSelected: color == value, + onTap: () => _selectColor(color), + imagePaths: _imagePaths, + ); + }, + ), + ) + .toList(), + Container( + padding: const EdgeInsets.all(2), + decoration: const BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + colors: [ + Color(0xFFFB2C36), + Color(0xFFAD46FF), + Color(0xFF2B7FFF), + ], + stops: [0, 0.5, 1], + begin: AlignmentDirectional.topStart, + end: AlignmentDirectional.bottomEnd, + ), + ), + child: TMailButtonWidget.fromIcon( + icon: _imagePaths.icColorPicker, + iconColor: AppColor.textSecondary, + iconSize: 18.46, + width: 40, + height: 40, + borderRadius: 100, + backgroundColor: Colors.white, + onTapActionCallback: _openColorPicker, + ), + ), + ], + ); + } + + void _selectColor(Color color) { + _selectedColor.value = color; + } + + void _clearColor() { + _selectedColor.value = null; + } + + void _openColorPicker() {} + + @override + void dispose() { + _selectedColor.dispose(); + super.dispose(); + } +} diff --git a/labels/lib/extensions/list_label_extension.dart b/labels/lib/extensions/list_label_extension.dart index 141aa1740..9a3c0c504 100644 --- a/labels/lib/extensions/list_label_extension.dart +++ b/labels/lib/extensions/list_label_extension.dart @@ -9,4 +9,8 @@ extension ListLabelExtension on List