TF-4236 Implement remove a label from an email when click on the cross on a tag in the opened mail
This commit is contained in:
@@ -11,6 +11,8 @@ class TagWidget extends StatelessWidget {
|
|||||||
final bool isTruncateText;
|
final bool isTruncateText;
|
||||||
final bool showTooltip;
|
final bool showTooltip;
|
||||||
final EdgeInsetsGeometry? margin;
|
final EdgeInsetsGeometry? margin;
|
||||||
|
final Widget? actionWidget;
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
const TagWidget({
|
const TagWidget({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -22,6 +24,8 @@ class TagWidget extends StatelessWidget {
|
|||||||
this.textColor,
|
this.textColor,
|
||||||
this.maxWidth,
|
this.maxWidth,
|
||||||
this.margin,
|
this.margin,
|
||||||
|
this.actionWidget,
|
||||||
|
this.padding,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -42,16 +46,32 @@ class TagWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
if (actionWidget != null) {
|
||||||
|
labelText = Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Flexible(child: labelText),
|
||||||
|
actionWidget!,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
labelText = Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: backgroundColor,
|
color: backgroundColor,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
),
|
),
|
||||||
constraints:
|
constraints:
|
||||||
maxWidth != null ? BoxConstraints(maxWidth: maxWidth!) : null,
|
maxWidth != null ? BoxConstraints(maxWidth: maxWidth!) : null,
|
||||||
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
|
padding: padding ?? EdgeInsets.symmetric(horizontal: horizontalPadding),
|
||||||
margin: margin,
|
margin: margin,
|
||||||
child: labelText,
|
child: labelText,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (actionWidget != null) {
|
||||||
|
labelText = GestureDetector(onTap: () {}, child: labelText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return labelText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
import 'package:jmap_dart_client/jmap/core/state.dart' as jmap;
|
import 'package:jmap_dart_client/jmap/core/state.dart' as jmap;
|
||||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
|
import 'package:labels/model/label.dart';
|
||||||
import 'package:model/email/email_action_type.dart';
|
import 'package:model/email/email_action_type.dart';
|
||||||
import 'package:model/email/presentation_email.dart';
|
import 'package:model/email/presentation_email.dart';
|
||||||
import 'package:tmail_ui_user/features/base/action/ui_action.dart';
|
import 'package:tmail_ui_user/features/base/action/ui_action.dart';
|
||||||
@@ -116,3 +117,13 @@ class TriggerMailViewKeyboardShortcutAction extends EmailUIAction {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get props => [actionType, email];
|
List<Object?> get props => [actionType, email];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RemoveLabelFromEmailAction extends EmailUIAction {
|
||||||
|
RemoveLabelFromEmailAction(this.emailId, this.label);
|
||||||
|
|
||||||
|
final EmailId emailId;
|
||||||
|
final Label label;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [emailId, label];
|
||||||
|
}
|
||||||
|
|||||||
@@ -357,6 +357,13 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
actionType: action.actionType,
|
actionType: action.actionType,
|
||||||
email: action.email,
|
email: action.email,
|
||||||
);
|
);
|
||||||
|
} else if (action is RemoveLabelFromEmailAction) {
|
||||||
|
mailboxDashBoardController.clearEmailUIAction();
|
||||||
|
if (_currentEmailId == null ||
|
||||||
|
action.emailId != _currentEmailId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toggleLabelToEmail(action.emailId, action.label, false);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -275,8 +275,14 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
presentationEmail: presentationEmail.copyWith(
|
presentationEmail: presentationEmail.copyWith(
|
||||||
subject: threadSubject,
|
subject: threadSubject,
|
||||||
),
|
),
|
||||||
|
imagePaths: controller.imagePaths,
|
||||||
isMobileResponsive: isMobileResponsive,
|
isMobileResponsive: isMobileResponsive,
|
||||||
labels: emailLabels,
|
labels: emailLabels,
|
||||||
|
onDeleteLabelAction: (label) => controller.toggleLabelToEmail(
|
||||||
|
presentationEmail.id!,
|
||||||
|
label,
|
||||||
|
false,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
Obx(() => InformationSenderAndReceiverBuilder(
|
Obx(() => InformationSenderAndReceiverBuilder(
|
||||||
|
|||||||
@@ -1,28 +1,49 @@
|
|||||||
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
|
import 'package:core/presentation/views/button/tmail_button_widget.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:labels/model/label.dart';
|
import 'package:labels/model/label.dart';
|
||||||
import 'package:model/email/presentation_email.dart';
|
import 'package:model/email/presentation_email.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/styles/email_subject_styles.dart';
|
import 'package:tmail_ui_user/features/email/presentation/styles/email_subject_styles.dart';
|
||||||
import 'package:tmail_ui_user/features/labels/presentation/widgets/label_widget.dart';
|
import 'package:tmail_ui_user/features/labels/presentation/widgets/label_widget.dart';
|
||||||
|
|
||||||
class EmailSubjectWidget extends StatelessWidget {
|
typedef OnDeleteLabelAction = void Function(Label label);
|
||||||
|
|
||||||
|
class EmailSubjectWidget extends StatefulWidget {
|
||||||
final PresentationEmail presentationEmail;
|
final PresentationEmail presentationEmail;
|
||||||
|
final ImagePaths imagePaths;
|
||||||
final bool isMobileResponsive;
|
final bool isMobileResponsive;
|
||||||
final List<Label>? labels;
|
final List<Label>? labels;
|
||||||
|
final OnDeleteLabelAction? onDeleteLabelAction;
|
||||||
|
|
||||||
const EmailSubjectWidget({
|
const EmailSubjectWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.presentationEmail,
|
required this.presentationEmail,
|
||||||
|
required this.imagePaths,
|
||||||
this.isMobileResponsive = false,
|
this.isMobileResponsive = false,
|
||||||
this.labels,
|
this.labels,
|
||||||
|
this.onDeleteLabelAction,
|
||||||
});
|
});
|
||||||
|
|
||||||
String get _title => presentationEmail.getEmailTitle();
|
@override
|
||||||
|
State<EmailSubjectWidget> createState() => _EmailSubjectWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
bool get _hasLabels => labels?.isNotEmpty == true;
|
class _EmailSubjectWidgetState extends State<EmailSubjectWidget> {
|
||||||
|
String get _title => widget.presentationEmail.getEmailTitle();
|
||||||
|
|
||||||
|
bool get _hasLabels => _currentLabels?.isNotEmpty == true;
|
||||||
|
|
||||||
|
List<Label>? _currentLabels;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_currentLabels = widget.labels;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final padding = isMobileResponsive
|
final padding = widget.isMobileResponsive
|
||||||
? EmailSubjectStyles.mobilePadding
|
? EmailSubjectStyles.mobilePadding
|
||||||
: EmailSubjectStyles.padding;
|
: EmailSubjectStyles.padding;
|
||||||
|
|
||||||
@@ -77,6 +98,35 @@ class EmailSubjectWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildLabelWidgets() {
|
List<Widget> _buildLabelWidgets() {
|
||||||
return labels?.map(LabelWidget.create).toList() ?? const [];
|
return _currentLabels
|
||||||
|
?.map((label) => LabelWidget.create(
|
||||||
|
label: label,
|
||||||
|
removeLabelAction: _buildRemoveLabelWidget(label),
|
||||||
|
padding: const EdgeInsetsDirectional.only(start: 4, end: 2),
|
||||||
|
))
|
||||||
|
.toList() ??
|
||||||
|
const [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRemoveLabelWidget(Label label) {
|
||||||
|
return TMailButtonWidget.fromIcon(
|
||||||
|
icon: widget.imagePaths.icDeleteSelection,
|
||||||
|
iconSize: 8,
|
||||||
|
iconColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
onTapActionCallback: () => _onDeleteLabelAction(label),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onDeleteLabelAction(Label labelRemoved) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_currentLabels = _currentLabels
|
||||||
|
?.where((label) => label.id != labelRemoved.id)
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
|
widget.onDeleteLabelAction?.call(labelRemoved);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ class LabelWidget extends StatelessWidget {
|
|||||||
final double horizontalPadding;
|
final double horizontalPadding;
|
||||||
final double? maxWidth;
|
final double? maxWidth;
|
||||||
final bool isTruncateLabel;
|
final bool isTruncateLabel;
|
||||||
|
final Widget? actionWidget;
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
const LabelWidget({
|
const LabelWidget({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -16,9 +18,20 @@ class LabelWidget extends StatelessWidget {
|
|||||||
this.horizontalPadding = 4,
|
this.horizontalPadding = 4,
|
||||||
this.maxWidth,
|
this.maxWidth,
|
||||||
this.isTruncateLabel = false,
|
this.isTruncateLabel = false,
|
||||||
|
this.actionWidget,
|
||||||
|
this.padding,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory LabelWidget.create(Label label) => LabelWidget(label: label);
|
factory LabelWidget.create({
|
||||||
|
required Label label,
|
||||||
|
required Widget removeLabelAction,
|
||||||
|
EdgeInsetsGeometry? padding,
|
||||||
|
}) =>
|
||||||
|
LabelWidget(
|
||||||
|
label: label,
|
||||||
|
actionWidget: removeLabelAction,
|
||||||
|
padding: padding,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -33,6 +46,8 @@ class LabelWidget extends StatelessWidget {
|
|||||||
maxWidth: maxWidth,
|
maxWidth: maxWidth,
|
||||||
isTruncateText: isTruncateLabel,
|
isTruncateText: isTruncateLabel,
|
||||||
showTooltip: showTooltip,
|
showTooltip: showTooltip,
|
||||||
|
actionWidget: actionWidget,
|
||||||
|
padding: padding,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -2,10 +2,12 @@ import 'package:core/utils/platform_info.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:labels/model/label.dart';
|
import 'package:labels/model/label.dart';
|
||||||
import 'package:model/email/email_in_thread_status.dart';
|
import 'package:model/email/email_in_thread_status.dart';
|
||||||
|
import 'package:model/email/presentation_email.dart';
|
||||||
import 'package:model/extensions/presentation_email_extension.dart';
|
import 'package:model/extensions/presentation_email_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/email_view.dart';
|
import 'package:tmail_ui_user/features/email/presentation/email_view.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/extensions/presentation_email_extension.dart';
|
import 'package:tmail_ui_user/features/email/presentation/extensions/presentation_email_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_mail_shortcut_actions_extension.dart';
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_mail_shortcut_actions_extension.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/labels/remove_label_from_email_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/load_more_thread_detail_emails.dart';
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/load_more_thread_detail_emails.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_load_more_segments.dart';
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_load_more_segments.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_email_action_click.dart';
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_email_action_click.dart';
|
||||||
@@ -46,7 +48,10 @@ extension GetThreadDetailEmailViews on ThreadDetailController {
|
|||||||
final isFirstEmailInThreadDetail = indexOfEmailId == 0;
|
final isFirstEmailInThreadDetail = indexOfEmailId == 0;
|
||||||
|
|
||||||
if (presentationEmail.emailInThreadStatus == EmailInThreadStatus.collapsed) {
|
if (presentationEmail.emailInThreadStatus == EmailInThreadStatus.collapsed) {
|
||||||
|
final lastEmail = emailIdsPresentation.values.last;
|
||||||
|
|
||||||
final emailLabels = _getLabelsForFirstEmail(
|
final emailLabels = _getLabelsForFirstEmail(
|
||||||
|
lastEmail: lastEmail,
|
||||||
isFirstEmailInThreadDetail: isFirstEmailInThreadDetail,
|
isFirstEmailInThreadDetail: isFirstEmailInThreadDetail,
|
||||||
availableLabels: labels,
|
availableLabels: labels,
|
||||||
);
|
);
|
||||||
@@ -54,7 +59,7 @@ extension GetThreadDetailEmailViews on ThreadDetailController {
|
|||||||
return ThreadDetailCollapsedEmail(
|
return ThreadDetailCollapsedEmail(
|
||||||
presentationEmail: presentationEmail.copyWith(
|
presentationEmail: presentationEmail.copyWith(
|
||||||
subject: isFirstEmailInThreadDetail
|
subject: isFirstEmailInThreadDetail
|
||||||
? emailIdsPresentation.values.last?.subject
|
? lastEmail?.subject
|
||||||
: null
|
: null
|
||||||
),
|
),
|
||||||
showSubject: isFirstEmailInThreadDetail,
|
showSubject: isFirstEmailInThreadDetail,
|
||||||
@@ -72,6 +77,10 @@ extension GetThreadDetailEmailViews on ThreadDetailController {
|
|||||||
onToggleThreadDetailCollapseExpand: () {
|
onToggleThreadDetailCollapseExpand: () {
|
||||||
toggleThreadDetailCollapseExpand(presentationEmail);
|
toggleThreadDetailCollapseExpand(presentationEmail);
|
||||||
},
|
},
|
||||||
|
onDeleteLabelAction: (label) => removeLabelFromEmail(
|
||||||
|
lastEmail!.id!,
|
||||||
|
label,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +121,7 @@ extension GetThreadDetailEmailViews on ThreadDetailController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Label>? _getLabelsForFirstEmail({
|
List<Label>? _getLabelsForFirstEmail({
|
||||||
|
required PresentationEmail? lastEmail,
|
||||||
required bool isFirstEmailInThreadDetail,
|
required bool isFirstEmailInThreadDetail,
|
||||||
required List<Label>? availableLabels,
|
required List<Label>? availableLabels,
|
||||||
}) {
|
}) {
|
||||||
@@ -121,7 +131,6 @@ extension GetThreadDetailEmailViews on ThreadDetailController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final lastEmail = emailIdsPresentation.values.last;
|
|
||||||
return lastEmail?.getLabelList(availableLabels);
|
return lastEmail?.getLabelList(availableLabels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
|
import 'package:labels/labels.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
|
||||||
|
extension RemoveLabelFromEmailExtension on ThreadDetailController {
|
||||||
|
void removeLabelFromEmail(EmailId emailId, Label label) {
|
||||||
|
mailboxDashBoardController.dispatchEmailUIAction(
|
||||||
|
RemoveLabelFromEmailAction(emailId, label),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ class ThreadDetailCollapsedEmail extends StatelessWidget {
|
|||||||
this.labels,
|
this.labels,
|
||||||
this.onEmailActionClick,
|
this.onEmailActionClick,
|
||||||
this.onToggleThreadDetailCollapseExpand,
|
this.onToggleThreadDetailCollapseExpand,
|
||||||
|
this.onDeleteLabelAction,
|
||||||
});
|
});
|
||||||
|
|
||||||
final PresentationEmail presentationEmail;
|
final PresentationEmail presentationEmail;
|
||||||
@@ -37,6 +38,7 @@ class ThreadDetailCollapsedEmail extends StatelessWidget {
|
|||||||
final OnEmailActionClick? onEmailActionClick;
|
final OnEmailActionClick? onEmailActionClick;
|
||||||
final List<Label>? labels;
|
final List<Label>? labels;
|
||||||
final VoidCallback? onToggleThreadDetailCollapseExpand;
|
final VoidCallback? onToggleThreadDetailCollapseExpand;
|
||||||
|
final OnDeleteLabelAction? onDeleteLabelAction;
|
||||||
|
|
||||||
String get preview => presentationEmail.getPartialContent();
|
String get preview => presentationEmail.getPartialContent();
|
||||||
|
|
||||||
@@ -63,8 +65,10 @@ class ThreadDetailCollapsedEmail extends StatelessWidget {
|
|||||||
if (showSubject)
|
if (showSubject)
|
||||||
EmailSubjectWidget(
|
EmailSubjectWidget(
|
||||||
presentationEmail: presentationEmail,
|
presentationEmail: presentationEmail,
|
||||||
|
imagePaths: imagePaths,
|
||||||
isMobileResponsive: isMobileResponsive,
|
isMobileResponsive: isMobileResponsive,
|
||||||
labels: labels,
|
labels: labels,
|
||||||
|
onDeleteLabelAction: onDeleteLabelAction,
|
||||||
),
|
),
|
||||||
InformationSenderAndReceiverBuilder(
|
InformationSenderAndReceiverBuilder(
|
||||||
emailSelected: presentationEmail,
|
emailSelected: presentationEmail,
|
||||||
|
|||||||
Reference in New Issue
Block a user