TF-4229 Add widget for label search field

This commit is contained in:
dab246
2026-01-19 17:49:14 +07:00
committed by Dat H. Pham
parent e1d1f2e066
commit 7839e6b339
22 changed files with 293 additions and 87 deletions
@@ -5,6 +5,7 @@ import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/core/utc_date.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:labels/labels.dart';
import 'package:model/model.dart';
import 'package:super_tag_editor/tag_editor.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
@@ -40,6 +41,7 @@ class AdvancedFilterController extends BaseController {
final endDate = Rxn<DateTime>();
final sortOrderType = SearchEmailFilter.defaultSortOrder.obs;
final selectedFolderName = Rxn<String>();
final selectedLabel = Rxn<Label>();
final GlobalKey<TagsEditorState> keyFromEmailTagEditor = GlobalKey<TagsEditorState>();
final GlobalKey<TagsEditorState> keyToEmailTagEditor = GlobalKey<TagsEditorState>();
@@ -135,6 +135,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_save_email_as_draft_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_store_email_sort_order_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/initialize_app_language.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/labels/handle_logic_label_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/notify_thread_detail_setting_updated.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/reopen_composer_cache_extension.dart';
@@ -913,13 +914,6 @@ class MailboxDashBoardController extends ReloadableController
}
}
bool get isLabelCapabilitySupported {
if (accountId.value == null || sessionCurrent == null) return false;
return labelController
.isLabelCapabilitySupported(sessionCurrent!, accountId.value!);
}
void _handleMailtoURL(MailtoArguments arguments) {
log('MailboxDashBoardController::_handleMailtoURL:');
routerParameters.value = arguments.toMapRouter();
@@ -0,0 +1,12 @@
import 'package:labels/labels.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart';
extension UpdateLabelInAdvancedSearchExtension on AdvancedFilterController {
void setSelectedLabel(Label? newLabel) {
if (selectedLabel.value?.id == newLabel?.id) {
selectedLabel.value = null;
} else {
selectedLabel.value = newLabel;
}
}
}
@@ -1,9 +0,0 @@
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
extension CheckLabelAvailableExtension on MailboxDashBoardController {
bool get isLabelFeatureEnabled {
return isLabelCapabilitySupported &&
labelController.isLabelSettingEnabled.isTrue;
}
}
@@ -0,0 +1,18 @@
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
extension HandleLogicLabelExtension on MailboxDashBoardController {
bool get isLabelCapabilitySupported {
final accountId = this.accountId.value;
final session = sessionCurrent;
if (accountId == null || session == null) return false;
return labelController.isLabelCapabilitySupported(session, accountId);
}
bool get isLabelAvailable {
return labelController.isLabelSettingEnabled.isTrue &&
isLabelCapabilitySupported;
}
}
@@ -0,0 +1,42 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
class LabelDropDownStyle {
static const int dropdownElevation = 0;
static const double height = 48;
static const double dropdownMaxHeight = 210;
static const double scrollbarThickness = 6;
static const double checkedIconSize = 20;
static final Color unCheckedIconColor =
AppColor.gray424244.withValues(alpha: 0.72);
static const Color checkedIconColor = AppColor.primaryMain;
static const Offset dropdownOffset = Offset(0.0, -3.0);
static BoxDecoration dropdownDecoration = BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(16)),
color: Colors.white,
border: Border.all(color: AppColor.m3Tertiary60),
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 24)],
);
static const EdgeInsetsGeometry buttonPadding = EdgeInsetsDirectional.only(
start: 12,
end: 8,
);
static const EdgeInsetsGeometry dropdownPadding = EdgeInsets.all(12);
static const MenuItemStyleData menuItemStyleData = MenuItemStyleData(
height: height,
padding: EdgeInsets.symmetric(horizontal: 12),
);
static const Radius dropdownScrollbarRadius = Radius.circular(40);
static TextStyle menuItemStyle = ThemeUtils.textStyleBodyBody3(
color: Colors.black,
);
}
@@ -8,17 +8,19 @@ class AdvancedSearchFieldWidget extends StatelessWidget {
final FilterField filterField;
final Widget child;
final bool useHeight;
final EdgeInsetsGeometry? padding;
const AdvancedSearchFieldWidget({
super.key,
required this.filterField,
required this.child,
this.useHeight = true,
this.padding,
});
@override
Widget build(BuildContext context) {
final bodyWidget = Row(
Widget bodyWidget = Row(
children: [
SizedBox(
width: AdvancedSearchInputFormStyle.labelMaxWidth,
@@ -32,12 +34,16 @@ class AdvancedSearchFieldWidget extends StatelessWidget {
);
if (useHeight) {
return SizedBox(
bodyWidget = SizedBox(
height: AdvancedSearchInputFormStyle.inputFieldHeight,
child: bodyWidget,
);
} else {
return bodyWidget;
}
if (padding != null) {
bodyWidget = Padding(padding: padding!, child: bodyWidget);
}
return bodyWidget;
}
}
@@ -6,9 +6,12 @@ import 'package:tmail_ui_user/features/base/widget/default_field/default_date_dr
import 'package:tmail_ui_user/features/base/widget/default_field/default_input_field_with_tab_key_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart';
import 'package:tmail_ui_user/features/base/model/filter_filter.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/advanced_search/update_label_in_advanced_search_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/labels/handle_logic_label_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_field_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_form_bottom_view.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/label_drop_down_button.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/sort_by_drop_down_button.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
@@ -119,6 +122,30 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController> {
)),
),
const SizedBox(height: 12),
Obx(() {
final isLabelAvailable =
controller.mailboxDashBoardController.isLabelAvailable;
final labels =
controller.mailboxDashBoardController.labelController.labels;
final labelSelected = controller.selectedLabel.value;
if (isLabelAvailable) {
return AdvancedSearchFieldWidget(
filterField: FilterField.labels,
padding: const EdgeInsets.only(bottom: 12),
child: LabelDropDownButton(
imagePaths: controller.imagePaths,
labels: labels,
labelSelected: labelSelected,
onSelectLabelsActions: controller.setSelectedLabel,
),
);
} else {
return const SizedBox.shrink();
}
}),
AdvancedSearchFieldWidget(
filterField: FilterField.date,
child: Obx(() => DefaultDateDropDownFieldWidget(
@@ -0,0 +1,123 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:labels/labels.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/advanced_search_input_form_style.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/label_drop_down_style.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
typedef OnSelectLabelsActions = void Function(Label? label);
class LabelDropDownButton extends StatelessWidget {
final ImagePaths imagePaths;
final List<Label> labels;
final Label? labelSelected;
final OnSelectLabelsActions onSelectLabelsActions;
const LabelDropDownButton({
Key? key,
required this.imagePaths,
required this.labels,
required this.labelSelected,
required this.onSelectLabelsActions,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return DropdownButtonHideUnderline(
child: PointerInterceptor(
child: DropdownButton2<Label>(
isExpanded: true,
items: labels.map((label) {
final isSelected = labelSelected?.id == label.id;
return DropdownMenuItem<Label>(
value: label,
child: PointerInterceptor(
child: Row(
spacing: 16,
children: [
SvgPicture.asset(
isSelected
? imagePaths.icCheckboxSelected
: imagePaths.icCheckboxUnselected,
width: LabelDropDownStyle.checkedIconSize,
height: LabelDropDownStyle.checkedIconSize,
colorFilter: isSelected
? LabelDropDownStyle.checkedIconColor.asFilter()
: LabelDropDownStyle.unCheckedIconColor.asFilter(),
fit: BoxFit.fill,
),
Expanded(
child: Text(
label.safeDisplayName,
style: LabelDropDownStyle.menuItemStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
);
}).toList(),
value: labelSelected,
customButton: _buildButton(AppLocalizations.of(context)),
onChanged: onSelectLabelsActions,
dropdownStyleData: DropdownStyleData(
maxHeight: LabelDropDownStyle.dropdownMaxHeight,
decoration: LabelDropDownStyle.dropdownDecoration,
elevation: LabelDropDownStyle.dropdownElevation,
offset: LabelDropDownStyle.dropdownOffset,
padding: LabelDropDownStyle.dropdownPadding,
scrollbarTheme: ScrollbarThemeData(
radius: LabelDropDownStyle.dropdownScrollbarRadius,
thickness: WidgetStateProperty.all<double>(
LabelDropDownStyle.scrollbarThickness,
),
thumbVisibility: WidgetStateProperty.all<bool>(true),
),
),
menuItemStyleData: LabelDropDownStyle.menuItemStyleData,
),
),
);
}
Widget _buildButton(AppLocalizations appLocalizations) {
final displayedName =
labelSelected?.safeDisplayName ?? appLocalizations.allLabels;
return Container(
height: AdvancedSearchInputFormStyle.inputFieldHeight,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(
AdvancedSearchInputFormStyle.inputFieldBorderRadius,
),
),
border: Border.all(
color: AdvancedSearchInputFormStyle.inputFieldBorderColor,
width: AdvancedSearchInputFormStyle.inputFieldBorderWidth,
),
color: AdvancedSearchInputFormStyle.inputFieldBackgroundColor,
),
padding: LabelDropDownStyle.buttonPadding,
child: Row(
children: [
Expanded(
child: Text(
displayedName,
style: AdvancedSearchInputFormStyle.inputTextStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
SvgPicture.asset(imagePaths.icDropDown)
],
),
);
}
}