TF-4195 Fix comments of coderabbit

This commit is contained in:
dab246
2025-12-26 12:22:18 +07:00
committed by Dat H. Pham
parent 0076a7af57
commit e8b2c1d2a5
32 changed files with 235 additions and 146 deletions
@@ -206,7 +206,7 @@ void main() {
expect(parsed.notFound, isEmpty); expect(parsed.notFound, isEmpty);
}); });
test('should throw DioException when server returns 500', () async { test('should throw DioError when server returns 500', () async {
// Arrange // Arrange
final dio = createDio(); final dio = createDio();
final adapter = DioAdapter(dio: dio); final adapter = DioAdapter(dio: dio);
@@ -86,7 +86,7 @@ void main() {
// Assert // Assert
expect(parsed, isNotNull); expect(parsed, isNotNull);
expect(parsed!.created![Id('4f29')]!.keyword, equals('important')); expect(parsed!.created![Id('4f29')]!.keyword?.value, equals('important'));
}); });
test('should process multiple created labels', () async { test('should process multiple created labels', () async {
@@ -143,8 +143,8 @@ void main() {
// Assert // Assert
expect(parsed, isNotNull); expect(parsed, isNotNull);
expect(parsed!.created!.length, equals(2)); expect(parsed!.created!.length, equals(2));
expect(parsed.created![Id('A')]!.keyword, equals('tagA')); expect(parsed.created![Id('A')]!.keyword?.value, equals('tagA'));
expect(parsed.created![Id('B')]!.keyword, equals('tagB')); expect(parsed.created![Id('B')]!.keyword?.value, equals('tagB'));
}); });
test('should throw DioException when backend returns 500', () async { test('should throw DioException when backend returns 500', () async {
@@ -0,0 +1,42 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
class HoverSubmenuController {
HoverSubmenuController({
this.exitDelay = const Duration(milliseconds: 120),
});
final Duration exitDelay;
final ValueNotifier<bool> isHovering = ValueNotifier(false);
int _hoverRefCount = 0;
Timer? _exitTimer;
void enter() {
_exitTimer?.cancel();
_hoverRefCount++;
if (!isHovering.value) {
isHovering.value = true;
}
}
void exit() {
_hoverRefCount = (_hoverRefCount - 1).clamp(0, 999);
if (_hoverRefCount == 0) {
_exitTimer?.cancel();
_exitTimer = Timer(exitDelay, () {
if (_hoverRefCount == 0) {
isHovering.value = false;
}
});
}
}
void dispose() {
_exitTimer?.cancel();
isHovering.dispose();
}
}
@@ -37,6 +37,7 @@ class PopupMenuActionGroupWidget with PopupContextMenuActionMixin {
child: PopupMenuItemActionWidget( child: PopupMenuItemActionWidget(
menuAction: menuAction, menuAction: menuAction,
menuActionClick: (menuAction) { menuActionClick: (menuAction) {
submenuController?.hide();
Navigator.pop(context); Navigator.pop(context);
onActionSelected(menuAction); onActionSelected(menuAction);
}, },
@@ -60,7 +61,11 @@ class PopupMenuActionGroupWidget with PopupContextMenuActionMixin {
], ],
]; ];
return openPopupMenuAction(context, position, popupMenuItems); try {
await openPopupMenuAction(context, position, popupMenuItems);
} finally {
submenuController?.hide();
}
} }
void _showPopupSubmenu({ void _showPopupSubmenu({
@@ -69,8 +74,9 @@ class PopupMenuActionGroupWidget with PopupContextMenuActionMixin {
required PopupSubmenuController submenuController, required PopupSubmenuController submenuController,
required Widget submenu, required Widget submenu,
}) { }) {
final renderBox = itemKey.currentContext?.findRenderObject() as RenderBox?; final renderObject = itemKey.currentContext?.findRenderObject();
if (renderBox == null) return; if (renderObject is! RenderBox) return;
final renderBox = renderObject;
final offset = renderBox.localToGlobal(Offset.zero); final offset = renderBox.localToGlobal(Offset.zero);
final rect = offset & renderBox.size; final rect = offset & renderBox.size;
@@ -5,6 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:model/email/email_action_type.dart'; import 'package:model/email/email_action_type.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/base/model/popup_menu_item_action.dart'; import 'package:tmail_ui_user/features/base/model/popup_menu_item_action.dart';
import 'package:tmail_ui_user/features/base/widget/popup_menu/hover_submenu_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/model/popup_menu_item_email_action.dart'; import 'package:tmail_ui_user/features/email/presentation/model/popup_menu_item_email_action.dart';
class PopupMenuItemActionWidget extends StatefulWidget { class PopupMenuItemActionWidget extends StatefulWidget {
@@ -28,14 +29,15 @@ class PopupMenuItemActionWidget extends StatefulWidget {
class _PopupMenuItemActionWidgetState extends State<PopupMenuItemActionWidget> { class _PopupMenuItemActionWidgetState extends State<PopupMenuItemActionWidget> {
GlobalKey? _itemKey; GlobalKey? _itemKey;
ValueNotifier<bool>? _hoverItemNotifier; HoverSubmenuController? _hoverController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (widget.menuAction is PopupMenuItemEmailAction) { if (widget.menuAction is PopupMenuItemEmailAction &&
widget.menuAction.action == EmailActionType.labelAs) {
_itemKey = GlobalKey(); _itemKey = GlobalKey();
_hoverItemNotifier = ValueNotifier(false); _hoverController = HoverSubmenuController();
} }
} }
@@ -133,9 +135,8 @@ class _PopupMenuItemActionWidgetState extends State<PopupMenuItemActionWidget> {
return PointerInterceptor( return PointerInterceptor(
child: MouseRegion( child: MouseRegion(
onEnter: (_) { onEnter: (_) {
if (_hoverItemNotifier != null) { _hoverController?.enter();
_hoverItemNotifier?.value = true;
}
if (_itemKey != null) { if (_itemKey != null) {
widget.onHoverShowSubmenu?.call(_itemKey!); widget.onHoverShowSubmenu?.call(_itemKey!);
} else { } else {
@@ -143,9 +144,7 @@ class _PopupMenuItemActionWidgetState extends State<PopupMenuItemActionWidget> {
} }
}, },
onExit: (_) { onExit: (_) {
if (_hoverItemNotifier != null) { _hoverController?.exit();
_hoverItemNotifier?.value = false;
}
}, },
child: Material( child: Material(
type: MaterialType.transparency, type: MaterialType.transparency,
@@ -172,26 +171,23 @@ class _PopupMenuItemActionWidgetState extends State<PopupMenuItemActionWidget> {
), ),
if (isSelected && selectedIconWidget != null) if (isSelected && selectedIconWidget != null)
selectedIconWidget, selectedIconWidget,
if (_hoverItemNotifier != null) if (_hoverController != null)
ValueListenableBuilder( ValueListenableBuilder(
valueListenable: _hoverItemNotifier!, valueListenable: _hoverController!.isHovering,
builder: (_, value, __) { builder: (_, isHovering, __) {
if (value) { if (!isHovering) return const SizedBox.shrink();
return Padding( return Padding(
padding: padding:
const EdgeInsetsDirectional.only(start: 16), const EdgeInsetsDirectional.only(start: 16),
child: SvgPicture.asset( child: SvgPicture.asset(
specificMenuAction.hoverIcon, specificMenuAction.hoverIcon,
width: specificMenuAction.hoverIconSize, width: specificMenuAction.hoverIconSize,
height: specificMenuAction.hoverIconSize, height: specificMenuAction.hoverIconSize,
colorFilter: specificMenuAction.hoverIconColor colorFilter: specificMenuAction.hoverIconColor
.asFilter(), .asFilter(),
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
); );
} else {
return const SizedBox.shrink();
}
}, },
) )
], ],
@@ -210,9 +206,7 @@ class _PopupMenuItemActionWidgetState extends State<PopupMenuItemActionWidget> {
onTap: () => widget.menuAction.onClick(widget.menuActionClick), onTap: () => widget.menuAction.onClick(widget.menuActionClick),
hoverColor: AppColor.popupMenuItemHovered, hoverColor: AppColor.popupMenuItemHovered,
onHover: (_) { onHover: (_) {
if (_hoverItemNotifier != null) { _hoverController?.exit();
_hoverItemNotifier?.value = false;
}
widget.onHoverOtherItem?.call(); widget.onHoverOtherItem?.call();
}, },
child: Container( child: Container(
@@ -245,9 +239,9 @@ class _PopupMenuItemActionWidgetState extends State<PopupMenuItemActionWidget> {
@override @override
void dispose() { void dispose() {
if (_hoverItemNotifier != null) { if (_hoverController != null) {
_hoverItemNotifier?.dispose(); _hoverController?.dispose();
_hoverItemNotifier = null; _hoverController = null;
} }
super.dispose(); super.dispose();
} }
@@ -1,3 +1,5 @@
import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
enum SubmenuDirection { left, right, auto } enum SubmenuDirection { left, right, auto }
@@ -18,7 +20,12 @@ class PopupSubmenuController {
}) { }) {
hide(); hide();
final screenWidth = MediaQuery.of(context).size.width; final overlayState = Overlay.maybeOf(context, rootOverlay: true);
if (overlayState == null) return;
final mediaSize = MediaQuery.sizeOf(context);
final screenWidth = mediaSize.width;
final screenHeight = mediaSize.height;
final rightPosition = anchor.right + offset; final rightPosition = anchor.right + offset;
final leftPosition = anchor.left - submenuWidth - offset; final leftPosition = anchor.left - submenuWidth - offset;
@@ -42,10 +49,16 @@ class PopupSubmenuController {
} }
} }
final clampedLeft = finalLeft
.clamp(0.0, math.max(0.0, screenWidth - submenuWidth))
.toDouble();
final availableHeight = math.max(0.0, screenHeight - anchor.top);
final finalHeight = math.min(submenuMaxHeight, availableHeight);
_submenuEntry = OverlayEntry( _submenuEntry = OverlayEntry(
builder: (_) { builder: (_) {
return PositionedDirectional( return PositionedDirectional(
start: finalLeft, start: clampedLeft,
top: anchor.top, top: anchor.top,
child: MouseRegion( child: MouseRegion(
onExit: (_) => hide(), onExit: (_) => hide(),
@@ -57,7 +70,7 @@ class PopupSubmenuController {
), ),
child: SizedBox( child: SizedBox(
width: submenuWidth, width: submenuWidth,
height: submenuMaxHeight, height: finalHeight,
child: submenu, child: submenu,
), ),
), ),
@@ -66,7 +79,7 @@ class PopupSubmenuController {
}, },
); );
Overlay.maybeOf(context)?.insert(_submenuEntry!); overlayState.insert(_submenuEntry!);
} }
void hide() { void hide() {
@@ -55,12 +55,14 @@ class ComposerView extends GetWidget<ComposerController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final iframeOverlay = Obx(() { final iframeOverlay = Obx(() {
final dialogRouter = DialogRouter();
bool isOverlayEnabled = controller.mailboxDashBoardController.isDisplayedOverlayViewOnIFrame || bool isOverlayEnabled = controller.mailboxDashBoardController.isDisplayedOverlayViewOnIFrame ||
MessageDialogActionManager().isDialogOpened || MessageDialogActionManager().isDialogOpened ||
EmailActionReactor.isDialogOpened || EmailActionReactor.isDialogOpened ||
ColorDialogPicker().isOpened.isTrue || ColorDialogPicker().isOpened.isTrue ||
DialogRouter().isRuleFilterDialogOpened.isTrue || dialogRouter.isRuleFilterDialogOpened.isTrue ||
DialogRouter().isDialogOpened; dialogRouter.isDialogOpened;
if (isOverlayEnabled) { if (isOverlayEnabled) {
return Positioned.fill( return Positioned.fill(
@@ -206,6 +206,7 @@ abstract class EmailDataSource {
Future<String> generateEntireMessageAsDocument(ViewEntireMessageRequest entireMessageRequest); Future<String> generateEntireMessageAsDocument(ViewEntireMessageRequest entireMessageRequest);
Future<void> addLabelToEmail( Future<void> addLabelToEmail(
Session session,
AccountId accountId, AccountId accountId,
EmailId emailId, EmailId emailId,
KeyWordIdentifier labelKeyword, KeyWordIdentifier labelKeyword,
@@ -556,12 +556,18 @@ class EmailDataSourceImpl extends EmailDataSource {
@override @override
Future<void> addLabelToEmail( Future<void> addLabelToEmail(
Session session,
AccountId accountId, AccountId accountId,
EmailId emailId, EmailId emailId,
KeyWordIdentifier labelKeyword, KeyWordIdentifier labelKeyword,
) { ) {
return Future.sync(() async { return Future.sync(() async {
return await emailAPI.addLabelToEmail(accountId, emailId, labelKeyword); return await emailAPI.addLabelToEmail(
session,
accountId,
emailId,
labelKeyword,
);
}).catchError(_exceptionThrower.throwException); }).catchError(_exceptionThrower.throwException);
} }
} }
@@ -577,7 +577,7 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource {
} }
@override @override
Future<void> addLabelToEmail(AccountId accountId, EmailId emailId, KeyWordIdentifier labelKeyword) { Future<void> addLabelToEmail(Session session, AccountId accountId, EmailId emailId, KeyWordIdentifier labelKeyword) {
throw UnimplementedError(); throw UnimplementedError();
} }
} }
@@ -353,7 +353,7 @@ class EmailLocalStorageDataSourceImpl extends EmailDataSource {
} }
@override @override
Future<void> addLabelToEmail(AccountId accountId, EmailId emailId, KeyWordIdentifier labelKeyword) { Future<void> addLabelToEmail(Session session, AccountId accountId, EmailId emailId, KeyWordIdentifier labelKeyword) {
throw UnimplementedError(); throw UnimplementedError();
} }
} }
@@ -267,7 +267,7 @@ class EmailSessionStorageDatasourceImpl extends EmailDataSource {
} }
@override @override
Future<void> addLabelToEmail(AccountId accountId, EmailId emailId, KeyWordIdentifier labelKeyword) { Future<void> addLabelToEmail(Session session, AccountId accountId, EmailId emailId, KeyWordIdentifier labelKeyword) {
throw UnimplementedError(); throw UnimplementedError();
} }
} }
@@ -938,6 +938,7 @@ class EmailAPI with HandleSetErrorMixin, MailAPIMixin {
} }
Future<void> addLabelToEmail( Future<void> addLabelToEmail(
Session session,
AccountId accountId, AccountId accountId,
EmailId emailId, EmailId emailId,
KeyWordIdentifier labelKeyword, KeyWordIdentifier labelKeyword,
@@ -948,8 +949,9 @@ class EmailAPI with HandleSetErrorMixin, MailAPIMixin {
final builder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); final builder = JmapRequestBuilder(_httpClient, ProcessingInvocation());
final invocation = builder.invocation(method); final invocation = builder.invocation(method);
final result = final capabilities = method.requiredCapabilities
await (builder..usings(method.requiredCapabilities)).build().execute(); .toCapabilitiesSupportTeamMailboxes(session, accountId);
final result = await (builder..usings(capabilities)).build().execute();
final response = result.parse<SetEmailResponse>( final response = result.parse<SetEmailResponse>(
invocation.methodCallId, invocation.methodCallId,
@@ -483,11 +483,13 @@ class EmailRepositoryImpl extends EmailRepository {
@override @override
Future<void> addLabelToEmail( Future<void> addLabelToEmail(
Session session,
AccountId accountId, AccountId accountId,
EmailId emailId, EmailId emailId,
KeyWordIdentifier labelKeyword, KeyWordIdentifier labelKeyword,
) { ) {
return emailDataSource[DataSourceType.network]!.addLabelToEmail( return emailDataSource[DataSourceType.network]!.addLabelToEmail(
session,
accountId, accountId,
emailId, emailId,
labelKeyword, labelKeyword,
@@ -163,6 +163,7 @@ abstract class EmailRepository {
Future<String> generateEntireMessageAsDocument(ViewEntireMessageRequest entireMessageRequest); Future<String> generateEntireMessageAsDocument(ViewEntireMessageRequest entireMessageRequest);
Future<void> addLabelToEmail( Future<void> addLabelToEmail(
Session session,
AccountId accountId, AccountId accountId,
EmailId emailId, EmailId emailId,
KeyWordIdentifier labelKeyword, KeyWordIdentifier labelKeyword,
@@ -2,6 +2,7 @@ import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart'; import 'package:core/presentation/state/success.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart';
@@ -13,6 +14,7 @@ class AddALabelToAnEmailInteractor {
AddALabelToAnEmailInteractor(this._emailRepository); AddALabelToAnEmailInteractor(this._emailRepository);
Stream<Either<Failure, Success>> execute( Stream<Either<Failure, Success>> execute(
Session session,
AccountId accountId, AccountId accountId,
EmailId emailId, EmailId emailId,
KeyWordIdentifier labelKeyword, KeyWordIdentifier labelKeyword,
@@ -21,6 +23,7 @@ class AddALabelToAnEmailInteractor {
try { try {
yield Right(AddingALabelToAnEmail()); yield Right(AddingALabelToAnEmail());
await _emailRepository.addLabelToEmail( await _emailRepository.addLabelToEmail(
session,
accountId, accountId,
emailId, emailId,
labelKeyword, labelKeyword,
@@ -118,7 +118,7 @@ class EmailInteractorBindings extends InteractorsBindings {
Get.find<EmailRepository>(), Get.find<EmailRepository>(),
)); ));
} }
Get.lazyPut( Get.lazyPut<AddALabelToAnEmailInteractor>(
() => AddALabelToAnEmailInteractor(Get.find<EmailRepository>()), () => AddALabelToAnEmailInteractor(Get.find<EmailRepository>()),
); );
} }
@@ -221,7 +221,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
@override @override
void handleSuccessViewState(Success success) { void handleSuccessViewState(Success success) {
log('SingleEmailController::handleSuccessViewState(): $success');
if (success is GetEmailContentSuccess) { if (success is GetEmailContentSuccess) {
_getEmailContentSuccess(success); _getEmailContentSuccess(success);
} else if (success is GetEmailContentFromCacheSuccess) { } else if (success is GetEmailContentFromCacheSuccess) {
@@ -255,7 +254,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
@override @override
void handleFailureViewState(Failure failure) { void handleFailureViewState(Failure failure) {
logError('SingleEmailController::handleFailureViewState(): $failure');
if (failure is MarkAsEmailReadFailure) { if (failure is MarkAsEmailReadFailure) {
_handleMarkAsEmailReadFailure(failure); _handleMarkAsEmailReadFailure(failure);
} else if (failure is ParseCalendarEventFailure) { } else if (failure is ParseCalendarEventFailure) {
@@ -899,6 +897,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
pressEmailAction(actionType, presentationEmail); pressEmailAction(actionType, presentationEmail);
break; break;
case EmailActionType.labelAs: case EmailActionType.labelAs:
if (!isLabelFeatureEnabled) return;
openAddLabelToEmailDialogModal(presentationEmail); openAddLabelToEmailDialogModal(presentationEmail);
break; break;
default: default:
@@ -596,12 +596,13 @@ class EmailView extends GetWidget<SingleEmailController> {
), ),
), ),
Obx(() { Obx(() {
final dialogRouter = DialogRouter();
bool isOverlayEnabled = controller.mailboxDashBoardController.isDisplayedOverlayViewOnIFrame || bool isOverlayEnabled = controller.mailboxDashBoardController.isDisplayedOverlayViewOnIFrame ||
MessageDialogActionManager().isDialogOpened || MessageDialogActionManager().isDialogOpened ||
EmailActionReactor.isDialogOpened || EmailActionReactor.isDialogOpened ||
ColorDialogPicker().isOpened.isTrue || ColorDialogPicker().isOpened.isTrue ||
DialogRouter().isRuleFilterDialogOpened.isTrue || dialogRouter.isRuleFilterDialogOpened.isTrue ||
DialogRouter().isDialogOpened; dialogRouter.isDialogOpened;
if (isOverlayEnabled) { if (isOverlayEnabled) {
return Positioned.fill( return Positioned.fill(
@@ -1,11 +1,13 @@
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/extensions/email_extension.dart'; import 'package:model/extensions/email_extension.dart';
import 'package:model/extensions/list_email_header_extension.dart'; import 'package:model/extensions/list_email_header_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/model/smime_signature_status.dart'; import 'package:tmail_ui_user/features/email/presentation/model/smime_signature_status.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/smime_signature_constant.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/smime_signature_constant.dart';
import 'package:tmail_ui_user/features/thread/data/extensions/map_keywords_extension.dart';
extension EmailExtension on Email { extension EmailExtension on Email {
@@ -39,7 +41,7 @@ extension EmailExtension on Email {
bool fromMe(String ownEmailAddress) { bool fromMe(String ownEmailAddress) {
return from?.any( return from?.any(
(emailAdress) => emailAdress.email == ownEmailAddress (emailAddress) => emailAddress.email == ownEmailAddress
) == true; ) == true;
} }
@@ -50,7 +52,15 @@ extension EmailExtension on Email {
...bcc ?? {}, ...bcc ?? {},
}; };
return recipients.any( return recipients.any(
(emailAdress) => emailAdress.email == ownEmailAddress (emailAddress) => emailAddress.email == ownEmailAddress
) == true; ) == true;
} }
Email toggleKeyword(KeyWordIdentifier keyword, bool remove) {
return copyWith(
keywords: remove
? keywords.withoutKeyword(keyword)
: keywords.withKeyword(keyword),
);
}
} }
@@ -1,31 +1,20 @@
import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:model/extensions/email_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/email_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart'; import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
import 'package:tmail_ui_user/features/thread/data/extensions/map_keywords_extension.dart';
extension EmailLoadedExtension on EmailLoaded { extension EmailLoadedExtension on EmailLoaded {
EmailLoaded addEmailKeyword({ EmailLoaded toggleEmailKeyword({
required EmailId emailId, required EmailId emailId,
required KeyWordIdentifier keyword, required KeyWordIdentifier keyword,
required bool remove,
}) { }) {
if (emailCurrent == null || emailCurrent?.id != emailId) { final current = emailCurrent;
if (current == null || current.id != emailId) {
return this; return this;
} }
final newKeyword = emailCurrent?.keywords?.withKeyword(keyword); return copyWith(
final updatedEmail = emailCurrent?.copyWith(keywords: newKeyword); emailCurrent: current.toggleKeyword(keyword, remove),
return copyWith(emailCurrent: updatedEmail); );
}
EmailLoaded removeEmailKeyword({
required EmailId emailId,
required KeyWordIdentifier keyword,
}) {
if (emailCurrent == null || emailCurrent?.id != emailId) {
return this;
}
final newKeyword = emailCurrent?.keywords?.withoutKeyword(keyword);
final updatedEmail = emailCurrent?.copyWith(keywords: newKeyword);
return copyWith(emailCurrent: updatedEmail);
} }
} }
@@ -2,6 +2,7 @@ import 'package:core/utils/platform_info.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:labels/extensions/label_extension.dart'; import 'package:labels/extensions/label_extension.dart';
@@ -29,8 +30,10 @@ extension HandleLabelForEmailExtension on SingleEmailController {
void toggleLabelToEmail(EmailId emailId, Label label, bool isSelected) { void toggleLabelToEmail(EmailId emailId, Label label, bool isSelected) {
if (isSelected) { if (isSelected) {
final accountId = mailboxDashBoardController.accountId.value; final accountId = mailboxDashBoardController.accountId.value;
final session = mailboxDashBoardController.sessionCurrent;
_addALabelToAnEmail( _addALabelToAnEmail(
session: session,
accountId: accountId, accountId: accountId,
emailId: emailId, emailId: emailId,
label: label, label: label,
@@ -39,12 +42,25 @@ extension HandleLabelForEmailExtension on SingleEmailController {
} }
void _addALabelToAnEmail({ void _addALabelToAnEmail({
required Session? session,
required AccountId? accountId, required AccountId? accountId,
required Label label, required Label label,
required EmailId emailId, required EmailId emailId,
}) { }) {
final labelDisplay = label.safeDisplayName; final labelDisplay = label.safeDisplayName;
if (session == null) {
consumeState(
Stream.value(
Left(AddALabelToAnEmailFailure(
exception: NotFoundSessionException(),
labelDisplay: labelDisplay,
)),
),
);
return;
}
if (accountId == null) { if (accountId == null) {
consumeState( consumeState(
Stream.value( Stream.value(
@@ -71,6 +87,7 @@ extension HandleLabelForEmailExtension on SingleEmailController {
} }
consumeState(addALabelToAnEmailInteractor.execute( consumeState(addALabelToAnEmailInteractor.execute(
session,
accountId, accountId,
emailId, emailId,
labelKeyword, labelKeyword,
@@ -117,24 +134,27 @@ extension HandleLabelForEmailExtension on SingleEmailController {
final controller = threadDetailController; final controller = threadDetailController;
if (controller != null) { if (controller != null) {
controller.emailIdsPresentation.value = controller.emailIdsPresentation.value =
controller.emailIdsPresentation.addEmailKeywordById( controller.emailIdsPresentation.toggleEmailKeywordById(
emailId: emailId, emailId: emailId,
keyword: labelKeyword, keyword: labelKeyword,
remove: false,
); );
controller.emailsInThreadDetailInfo.value = controller.emailsInThreadDetailInfo.value =
controller.emailsInThreadDetailInfo.addEmailKeywordById( controller.emailsInThreadDetailInfo.toggleEmailKeywordById(
emailId: emailId, emailId: emailId,
keyword: labelKeyword, keyword: labelKeyword,
remove: false,
); );
} }
} }
final emailLoaded = currentEmailLoaded.value; final emailLoaded = currentEmailLoaded.value;
if (emailLoaded != null && emailLoaded.emailCurrent?.id == emailId) { if (emailLoaded != null && emailLoaded.emailCurrent?.id == emailId) {
currentEmailLoaded.value = emailLoaded.addEmailKeyword( currentEmailLoaded.value = emailLoaded.toggleEmailKeyword(
emailId: emailId, emailId: emailId,
keyword: labelKeyword, keyword: labelKeyword,
remove: false,
); );
} }
@@ -148,6 +168,7 @@ extension HandleLabelForEmailExtension on SingleEmailController {
} }
Future<void> openAddLabelToEmailDialogModal(PresentationEmail email) async { Future<void> openAddLabelToEmailDialogModal(PresentationEmail email) async {
if (!isLabelFeatureEnabled) return;
final labels = mailboxDashBoardController.labelController.labels; final labels = mailboxDashBoardController.labelController.labels;
final emailLabels = email.getLabelList(labels); final emailLabels = email.getLabelList(labels);
final emailId = email.id; final emailId = email.id;
@@ -170,15 +170,13 @@ extension PresentationEmailExtension on PresentationEmail {
.toList(); .toList();
} }
PresentationEmail addKeyword(KeyWordIdentifier keyword) { PresentationEmail toggleKeyword(KeyWordIdentifier keyword, bool remove) {
return copyWith( return copyWith(
keywords: keywords.withKeyword(keyword), keywords: remove
); ? keywords.withoutKeyword(keyword)
} : keywords.withKeyword(keyword),
)
PresentationEmail removeKeyword(KeyWordIdentifier keyword) { ..searchSnippetSubject = searchSnippetSubject
return copyWith( ..searchSnippetPreview = searchSnippetPreview;
keywords: keywords.withoutKeyword(keyword),
);
} }
} }
@@ -1 +1,4 @@
class LabelKeywordIsNull implements Exception {} class LabelKeywordIsNull implements Exception {
@override
String toString() => 'Label keyword is null';
}
@@ -71,6 +71,13 @@ class LabelController extends BaseController {
} }
Future<void> openCreateNewLabelModal(AccountId? accountId) async { Future<void> openCreateNewLabelModal(AccountId? accountId) async {
if (accountId == null) {
consumeState(
Stream.value(Left(CreateNewLabelFailure(NotFoundAccountIdException()))),
);
return;
}
await DialogRouter().openDialogModal( await DialogRouter().openDialogModal(
child: CreateNewLabelModal( child: CreateNewLabelModal(
labels: labels, labels: labels,
@@ -80,13 +87,9 @@ class LabelController extends BaseController {
); );
} }
void _createNewLabel(AccountId? accountId, Label label) { void _createNewLabel(AccountId accountId, Label label) {
log('LabelController::_createNewLabel:Label: $label'); log('LabelController::_createNewLabel:Label: $label');
if (accountId == null) { if (_createNewLabelInteractor == null) {
consumeState(
Stream.value(Left(CreateNewLabelFailure(NotFoundAccountIdException()))),
);
} else if (_createNewLabelInteractor == null) {
consumeState( consumeState(
Stream.value(Left(CreateNewLabelFailure(InteractorNotInitialized()))), Stream.value(Left(CreateNewLabelFailure(InteractorNotInitialized()))),
); );
@@ -12,7 +12,7 @@ import 'package:tmail_ui_user/features/labels/presentation/widgets/label_item_co
import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart';
typedef OnAddLabelToEmailCallback = Function( typedef OnAddLabelToEmailCallback = void Function(
EmailId emailId, EmailId emailId,
Label label, Label label,
bool isSelected, bool isSelected,
@@ -29,9 +29,10 @@ class SignatureBuilder extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
final iframeOverlay = Obx(() { final iframeOverlay = Obx(() {
final dialogRouter = DialogRouter();
if (MessageDialogActionManager().isDialogOpened || if (MessageDialogActionManager().isDialogOpened ||
DialogRouter().isDialogOpened || dialogRouter.isDialogOpened ||
DialogRouter().isRuleFilterDialogOpened.isTrue) { dialogRouter.isRuleFilterDialogOpened.isTrue) {
return Positioned.fill( return Positioned.fill(
child: PointerInterceptor( child: PointerInterceptor(
child: const SizedBox.expand(), child: const SizedBox.expand(),
@@ -4,27 +4,16 @@ import 'package:model/email/presentation_email.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';
extension PresentationEmailMapExtension on Map<EmailId, PresentationEmail?> { extension PresentationEmailMapExtension on Map<EmailId, PresentationEmail?> {
Map<EmailId, PresentationEmail?> addEmailKeywordById({ Map<EmailId, PresentationEmail?> toggleEmailKeywordById({
required EmailId emailId, required EmailId emailId,
required KeyWordIdentifier keyword, required KeyWordIdentifier keyword,
required bool remove,
}) { }) {
return map((id, email) { final newMap = Map<EmailId, PresentationEmail?>.from(this);
if (id != emailId || email == null) { final email = newMap[emailId];
return MapEntry(id, email); if (email != null) {
} newMap[emailId] = email.toggleKeyword(keyword, remove);
return MapEntry(id, email.addKeyword(keyword)); }
}); return newMap;
}
Map<EmailId, PresentationEmail?> removeEmailKeywordById({
required EmailId emailId,
required KeyWordIdentifier keyword,
}) {
return map((id, email) {
if (id != emailId || email == null) {
return MapEntry(id, email);
}
return MapEntry(id, email.removeKeyword(keyword));
});
} }
} }
@@ -0,0 +1,16 @@
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:tmail_ui_user/features/thread/data/extensions/map_keywords_extension.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
extension EmailInThreadDetailInfoExtension on EmailInThreadDetailInfo {
EmailInThreadDetailInfo toggleKeyword(
KeyWordIdentifier keyword,
bool remove,
) {
return copyWith(
keywords: remove
? keywords.withoutKeyword(keyword)
: keywords.withKeyword(keyword),
);
}
}
@@ -1,6 +1,6 @@
import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:tmail_ui_user/features/thread/data/extensions/map_keywords_extension.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/extensions/email_in_thread_detail_info_extension.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart';
extension ListEmailInThreadDetailInfoExtension on List<EmailInThreadDetailInfo> { extension ListEmailInThreadDetailInfoExtension on List<EmailInThreadDetailInfo> {
@@ -10,29 +10,16 @@ extension ListEmailInThreadDetailInfoExtension on List<EmailInThreadDetailInfo>
.map((email) => email.emailId) .map((email) => email.emailId)
.toList(); .toList();
List<EmailInThreadDetailInfo> addEmailKeywordById({ List<EmailInThreadDetailInfo> toggleEmailKeywordById({
required EmailId emailId, required EmailId emailId,
required KeyWordIdentifier keyword, required KeyWordIdentifier keyword,
required bool remove,
}) { }) {
return map((email) { return map((emailInfo) {
if (email.emailId != emailId) { if (emailInfo.emailId != emailId) {
return email; return emailInfo;
} }
final newKeywords = email.keywords.withKeyword(keyword); return emailInfo.toggleKeyword(keyword, remove);
return email.copyWith(keywords: newKeywords);
}).toList();
}
List<EmailInThreadDetailInfo> removeEmailKeywordById({
required EmailId emailId,
required KeyWordIdentifier keyword,
}) {
return map((email) {
if (email.emailId != emailId) {
return email;
}
final newKeywords = email.keywords.withoutKeyword(keyword);
return email.copyWith(keywords: newKeywords);
}).toList(); }).toList();
} }
} }
+4 -2
View File
@@ -60,7 +60,9 @@ class DialogRouter {
MailboxCreatorBindings().dependencies(); MailboxCreatorBindings().dependencies();
break; break;
case AppRoutes.rulesFilterCreator: case AppRoutes.rulesFilterCreator:
isRuleFilterDialogOpened.value = true; if (PlatformInfo.isWeb) {
isRuleFilterDialogOpened.value = true;
}
RulesFilterCreatorBindings().dependencies(); RulesFilterCreatorBindings().dependencies();
break; break;
case AppRoutes.identityCreator: case AppRoutes.identityCreator:
@@ -107,7 +109,7 @@ class DialogRouter {
await Get.generalDialog( await Get.generalDialog(
barrierDismissible: true, barrierDismissible: true,
barrierLabel: dialogLabel, barrierLabel: dialogLabel ?? 'dialog-modal',
pageBuilder: (_, __, ___) => child, pageBuilder: (_, __, ___) => child,
).whenComplete(() { ).whenComplete(() {
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
@@ -15,13 +15,11 @@ extension KeyWordIdentifierExtension on KeyWordIdentifier {
} }
PatchObject generateReadActionPath(ReadActions action) { PatchObject generateReadActionPath(ReadActions action) {
final isRead = action == ReadActions.markAsRead; return _boolPatch(action == ReadActions.markAsRead ? true : null);
return _boolPatch(isRead ? true : null);
} }
PatchObject generateMarkStarActionPath(MarkStarAction action) { PatchObject generateMarkStarActionPath(MarkStarAction action) {
final isStar = action == MarkStarAction.markStar; return _boolPatch(action == MarkStarAction.markStar ? true : null);
return _boolPatch(isStar ? true : null);
} }
PatchObject generateAnsweredActionPath() => _boolPatch(true); PatchObject generateAnsweredActionPath() => _boolPatch(true);