TF-4227 Fix conflict with master branch

This commit is contained in:
dab246
2026-01-16 14:49:13 +07:00
committed by Dat H. Pham
parent 56aa776a25
commit f54250eb26
13 changed files with 329 additions and 125 deletions
+23
View File
@@ -176,6 +176,8 @@ PODS:
- nanopb/encode (= 2.30909.1) - nanopb/encode (= 2.30909.1)
- nanopb/decode (2.30909.1) - nanopb/decode (2.30909.1)
- nanopb/encode (2.30909.1) - nanopb/encode (2.30909.1)
- objective_c (0.0.1):
- Flutter
- open_file_ios (0.0.1): - open_file_ios (0.0.1):
- Flutter - Flutter
- OrderedSet (6.0.3) - OrderedSet (6.0.3)
@@ -210,11 +212,18 @@ PODS:
- SDWebImageWebPCoder (0.14.6): - SDWebImageWebPCoder (0.14.6):
- libwebp (~> 1.0) - libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17) - SDWebImage/Core (~> 5.17)
- Sentry/HybridSDK (8.56.2)
- sentry_flutter (9.8.0):
- Flutter
- FlutterMacOS
- Sentry/HybridSDK (= 8.56.2)
- share_plus (0.0.1): - share_plus (0.0.1):
- Flutter - Flutter
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- super_dns_client (0.0.1):
- Flutter
- SwiftyGif (5.4.5) - SwiftyGif (5.4.5)
- UniversalDetector2 (2.0.1) - UniversalDetector2 (2.0.1)
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
@@ -247,6 +256,7 @@ DEPENDENCIES:
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`) - flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`)
- lottie_native (from `.symlinks/plugins/lottie_native/ios`) - lottie_native (from `.symlinks/plugins/lottie_native/ios`)
- objective_c (from `.symlinks/plugins/objective_c/ios`)
- open_file_ios (from `.symlinks/plugins/open_file_ios/ios`) - open_file_ios (from `.symlinks/plugins/open_file_ios/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
@@ -257,8 +267,10 @@ DEPENDENCIES:
- pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`) - pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`)
- printing (from `.symlinks/plugins/printing/ios`) - printing (from `.symlinks/plugins/printing/ios`)
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`) - receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- super_dns_client (from `.symlinks/plugins/super_dns_client/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- workmanager_apple (from `.symlinks/plugins/workmanager_apple/ios`) - workmanager_apple (from `.symlinks/plugins/workmanager_apple/ios`)
@@ -284,6 +296,7 @@ SPEC REPOS:
- ReachabilitySwift - ReachabilitySwift
- SDWebImage - SDWebImage
- SDWebImageWebPCoder - SDWebImageWebPCoder
- Sentry
- SwiftyGif - SwiftyGif
- UniversalDetector2 - UniversalDetector2
@@ -336,6 +349,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_web_auth_2/ios" :path: ".symlinks/plugins/flutter_web_auth_2/ios"
lottie_native: lottie_native:
:path: ".symlinks/plugins/lottie_native/ios" :path: ".symlinks/plugins/lottie_native/ios"
objective_c:
:path: ".symlinks/plugins/objective_c/ios"
open_file_ios: open_file_ios:
:path: ".symlinks/plugins/open_file_ios/ios" :path: ".symlinks/plugins/open_file_ios/ios"
package_info_plus: package_info_plus:
@@ -356,10 +371,14 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/printing/ios" :path: ".symlinks/plugins/printing/ios"
receive_sharing_intent: receive_sharing_intent:
:path: ".symlinks/plugins/receive_sharing_intent/ios" :path: ".symlinks/plugins/receive_sharing_intent/ios"
sentry_flutter:
:path: ".symlinks/plugins/sentry_flutter/ios"
share_plus: share_plus:
:path: ".symlinks/plugins/share_plus/ios" :path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation: shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin" :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
super_dns_client:
:path: ".symlinks/plugins/super_dns_client/ios"
url_launcher_ios: url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
workmanager_apple: workmanager_apple:
@@ -405,6 +424,7 @@ SPEC CHECKSUMS:
lottie_native: c2e590a297861fc32a0188cf8dab39aa97f86d81 lottie_native: c2e590a297861fc32a0188cf8dab39aa97f86d81
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5 nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
open_file_ios: 5ff7526df64e4394b4fe207636b67a95e83078bb open_file_ios: 5ff7526df64e4394b4fe207636b67a95e83078bb
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: 580e9a5f1b6ca5594e7c9ed5f92d1dfb2a66b5e1 package_info_plus: 580e9a5f1b6ca5594e7c9ed5f92d1dfb2a66b5e1
@@ -420,8 +440,11 @@ SPEC CHECKSUMS:
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00 receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
SDWebImage: f29024626962457f3470184232766516dee8dfea SDWebImage: f29024626962457f3470184232766516dee8dfea
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
Sentry: b53951377b78e21a734f5dc8318e333dbfc682d7
sentry_flutter: 4c33648b7e83310aa1fdb1b10c5491027d9643f0
share_plus: de6030e33b4e106470e09322d87cf2a4258d2d1d share_plus: de6030e33b4e106470e09322d87cf2a4258d2d1d
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
super_dns_client: 03e8ebf4df47d1335d1027299f8d2365b7a7bb59
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
UniversalDetector2: 7c9ffd935cf050eeb19edf7e90f6febe3743a1af UniversalDetector2: 7c9ffd935cf050eeb19edf7e90f6febe3743a1af
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
+59 -3
View File
@@ -268,10 +268,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "9fdbf71baeb250fc9da847f6cb2052196f62c19906a3657adfc18631a667d316" sha256: e44ce32c01f02c54a101551def8586f15d6189b4f999d4420aad38995ac62e6d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.2.0"
equatable: equatable:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -381,6 +381,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
flutter_dotenv:
dependency: transitive
description:
name: flutter_dotenv
sha256: d9283d92059a22e9834bc0a31336658ffba77089fb6f3cc36751f1fc7c6661a3
url: "https://pub.dev"
source: hosted
version: "5.0.2"
flutter_hsvcolor_picker: flutter_hsvcolor_picker:
dependency: transitive dependency: transitive
description: description:
@@ -684,10 +692,18 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "446efbbccdde981371b4d2a636762184adf6e926" resolved-ref: "8e2bd9cb1bbd8236be895aa04bf0d0da8e2ff3b2"
url: "https://github.com/linagora/jmap-dart-client.git" url: "https://github.com/linagora/jmap-dart-client.git"
source: git source: git
version: "0.3.6" version: "0.3.6"
jni:
dependency: transitive
description:
name: jni
sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1
url: "https://pub.dev"
source: hosted
version: "0.14.2"
js: js:
dependency: transitive dependency: transitive
description: description:
@@ -792,6 +808,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.6" version: "1.0.6"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "64e35e1e2e79da4e83f2ace3bf4e5437cef523f46c7db2eba9a1419c49573790"
url: "https://pub.dev"
source: hosted
version: "8.0.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@@ -1025,6 +1049,30 @@ packages:
url: "https://github.com/linagora/dart-neats.git" url: "https://github.com/linagora/dart-neats.git"
source: git source: git
version: "2.1.0" version: "2.1.0"
sentry:
dependency: transitive
description:
name: sentry
sha256: "10a0bc25f5f21468e3beeae44e561825aaa02cdc6829438e73b9b64658ff88d9"
url: "https://pub.dev"
source: hosted
version: "9.8.0"
sentry_dio:
dependency: transitive
description:
name: sentry_dio
sha256: bee438fd790c534da77f0a6c9cd04c54c818184b8a54bbe7d916489c8aad56a0
url: "https://pub.dev"
source: hosted
version: "9.8.0"
sentry_flutter:
dependency: transitive
description:
name: sentry_flutter
sha256: aafbf41c63c98a30b17bdbf3313424d5102db62b08735c44bff810f277e786a5
url: "https://pub.dev"
source: hosted
version: "9.8.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@@ -1222,6 +1270,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.5" version: "3.1.5"
uuid:
dependency: transitive
description:
name: uuid
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
url: "https://pub.dev"
source: hosted
version: "4.5.2"
vector_graphics: vector_graphics:
dependency: transitive dependency: transitive
description: description:
+1 -1
View File
@@ -25,7 +25,7 @@ dependencies:
json_annotation: 4.8.0 json_annotation: 4.8.0
dio: 5.0.0 dio: 5.2.0
quiver: 3.2.1 quiver: 3.2.1
@@ -205,7 +205,7 @@ void main() {
(builder..usings(method.requiredCapabilities)).build().execute(); (builder..usings(method.requiredCapabilities)).build().execute();
// Assert // Assert
expect(call, throwsA(isA<DioError>())); expect(call, throwsA(isA<DioException>()));
}); });
}); });
} }
@@ -226,7 +226,7 @@ void main() {
(builder..usings(method.requiredCapabilities)).build().execute(); (builder..usings(method.requiredCapabilities)).build().execute();
// Assert // Assert
expect(future, throwsA(isA<DioError>())); expect(future, throwsA(isA<DioException>()));
}); });
}); });
} }
@@ -174,7 +174,7 @@ void main() {
(builder..usings(method.requiredCapabilities)).build().execute(); (builder..usings(method.requiredCapabilities)).build().execute();
// Assert // Assert
expect(call, throwsA(isA<DioError>())); expect(call, throwsA(isA<DioException>()));
}); });
}); });
} }
@@ -54,13 +54,4 @@ class TagWidget extends StatelessWidget {
child: labelText, child: labelText,
); );
} }
String _truncateName(String name, {int maxLength = 16}) {
try {
if (name.length <= maxLength) return name;
return '${name.substring(0, maxLength - 1)}...';
} catch (_) {
return name;
}
}
} }
@@ -150,21 +150,14 @@ extension PresentationMailboxExtension on PresentationMailbox {
String? get filterKeyword { String? get filterKeyword {
if (isFavorite) { if (isFavorite) {
return KeyWordIdentifier.emailFlagged.value; return KeyWordIdentifier.emailFlagged.value;
} else if (isLabelMailbox) {
return (this as PresentationLabelMailbox).label.keyword?.value;
} else {
return null;
} }
return null;
} }
bool get isCacheable => !isFavorite && this is! PresentationLabelMailbox; bool get isCacheable => !isVirtualFolder && this is! PresentationLabelMailbox;
bool get isLabelMailbox => this is PresentationLabelMailbox; bool get isLabelMailbox => this is PresentationLabelMailbox;
String? get filterKeyword {
if (isFavorite) {
return KeyWordIdentifier.emailFlagged.value;
}
if (isLabelMailbox) {
return (this as PresentationLabelMailbox).label.keyword?.value;
}
return null;
}
} }
@@ -52,11 +52,11 @@ class PreferencesController extends BaseController {
final accountDashboardController = Get.find<ManageAccountDashBoardController>(); final accountDashboardController = Get.find<ManageAccountDashBoardController>();
bool get isAICapabilitySupported { bool get isAICapabilitySupported {
return _manageAccountDashBoardController.isAICapabilitySupported; return accountDashboardController.isAICapabilitySupported;
} }
bool get isAIScribeCapabilityAvailable { bool get isAIScribeCapabilityAvailable {
return _manageAccountDashBoardController.isAIScribeCapabilityAvailable; return accountDashboardController.isAIScribeCapabilityAvailable;
} }
@override @override
@@ -2,8 +2,6 @@ import 'package:jmap_dart_client/jmap/core/filter/filter.dart';
import 'package:jmap_dart_client/jmap/core/filter/filter_operator.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter_operator.dart';
import 'package:jmap_dart_client/jmap/core/filter/operator/logic_filter_operator.dart'; import 'package:jmap_dart_client/jmap/core/filter/operator/logic_filter_operator.dart';
import 'package:jmap_dart_client/jmap/core/utc_date.dart'; import 'package:jmap_dart_client/jmap/core/utc_date.dart';
import 'package:jmap_dart_client/jmap/core/filter/filter_operator.dart';
import 'package:jmap_dart_client/jmap/core/filter/operator/logic_filter_operator.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.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:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
@@ -26,10 +24,20 @@ class MailboxFilterBuilder {
MailboxId? get _mailboxId => selectedMailbox?.id; MailboxId? get _mailboxId => selectedMailbox?.id;
bool get _isVirtualFolder => selectedMailbox?.isVirtualFolder == true;
bool get _isActionRequired => selectedMailbox?.isActionRequired == true;
bool get _isFavorite => selectedMailbox?.isFavorite == true;
bool get _isLabelMailbox => selectedMailbox?.isLabelMailbox == true;
String? get _filterKeyword => selectedMailbox?.filterKeyword;
EmailFilter buildEmailFilterForLoadMailbox() { EmailFilter buildEmailFilterForLoadMailbox() {
final filterCondition = buildFilterCondition(); final filterCondition = buildFilterCondition();
if (selectedMailbox?.isVirtualFolder == true) { if (_isVirtualFolder) {
return EmailFilter(filter: filterCondition); return EmailFilter(filter: filterCondition);
} }
@@ -43,48 +51,17 @@ class MailboxFilterBuilder {
Filter buildFilterCondition({PresentationEmail? oldestEmail}) { Filter buildFilterCondition({PresentationEmail? oldestEmail}) {
final before = oldestEmail?.receivedAt; final before = oldestEmail?.receivedAt;
if (selectedMailbox?.isFavorite == true) { if (_isActionRequired) {
return _buildFavoriteMailboxFilter(before: before);
}
if (selectedMailbox?.isActionRequired == true) {
return _buildActionRequiredMailboxFilter(before: before); return _buildActionRequiredMailboxFilter(before: before);
} }
if (selectedMailbox?.isLabelMailbox == true) { if (_isFavorite || _isLabelMailbox) {
return _buildKeywordBasedFilter( return _buildKeywordBasedFilter(keyword: _filterKeyword, before: before);
selectedMailbox?.filterKeyword,
before: before,
);
} }
return buildDefaultMailboxFilter(before: before); return buildDefaultMailboxFilter(before: before);
} }
Filter _buildFavoriteMailboxFilter({UTCDate? before}) {
switch (filterMessageOption) {
case FilterMessageOption.unread:
return EmailFilterCondition(
notKeyword: KeyWordIdentifier.emailSeen.value,
hasKeyword: KeyWordIdentifier.emailFlagged.value,
before: before,
);
case FilterMessageOption.attachments:
return EmailFilterCondition(
hasAttachment: true,
hasKeyword: KeyWordIdentifier.emailFlagged.value,
before: before,
);
default:
return EmailFilterCondition(
hasKeyword: KeyWordIdentifier.emailFlagged.value,
before: before,
);
}
}
Filter _buildActionRequiredMailboxFilter({UTCDate? before}) { Filter _buildActionRequiredMailboxFilter({UTCDate? before}) {
switch (filterMessageOption) { switch (filterMessageOption) {
case FilterMessageOption.starred: case FilterMessageOption.starred:
@@ -98,7 +75,6 @@ class MailboxFilterBuilder {
EmailFilterCondition( EmailFilterCondition(
notKeyword: KeyWordIdentifier.emailSeen.value, notKeyword: KeyWordIdentifier.emailSeen.value,
hasKeyword: KeyWordIdentifierExtension.needsActionMail.value, hasKeyword: KeyWordIdentifierExtension.needsActionMail.value,
before: before,
), ),
}, },
); );
@@ -121,7 +97,7 @@ class MailboxFilterBuilder {
} }
Filter _buildKeywordBasedFilter({ Filter _buildKeywordBasedFilter({
required String keyword, required String? keyword,
UTCDate? before, UTCDate? before,
}) { }) {
switch (filterMessageOption) { switch (filterMessageOption) {
@@ -146,18 +122,12 @@ class MailboxFilterBuilder {
EmailFilterCondition( EmailFilterCondition(
hasKeyword: KeyWordIdentifier.emailFlagged.value, hasKeyword: KeyWordIdentifier.emailFlagged.value,
), ),
EmailFilterCondition( EmailFilterCondition(hasKeyword: keyword, before: before),
hasKeyword: keyword,
before: oldestEmail?.receivedAt,
),
}, },
); );
default: default:
return EmailFilterCondition( return EmailFilterCondition(hasKeyword: keyword, before: before);
hasKeyword: keyword,
before: before,
);
} }
} }
@@ -168,7 +138,6 @@ class MailboxFilterBuilder {
case FilterMessageOption.unread: case FilterMessageOption.unread:
return EmailFilterCondition( return EmailFilterCondition(
inMailbox: mailboxId,
notKeyword: KeyWordIdentifier.emailSeen.value, notKeyword: KeyWordIdentifier.emailSeen.value,
before: before, before: before,
inMailbox: _mailboxId, inMailbox: _mailboxId,
@@ -176,7 +145,6 @@ class MailboxFilterBuilder {
case FilterMessageOption.attachments: case FilterMessageOption.attachments:
return EmailFilterCondition( return EmailFilterCondition(
inMailbox: mailboxId,
hasAttachment: true, hasAttachment: true,
before: before, before: before,
inMailbox: _mailboxId, inMailbox: _mailboxId,
@@ -184,7 +152,6 @@ class MailboxFilterBuilder {
case FilterMessageOption.starred: case FilterMessageOption.starred:
return EmailFilterCondition( return EmailFilterCondition(
inMailbox: mailboxId,
hasKeyword: KeyWordIdentifier.emailFlagged.value, hasKeyword: KeyWordIdentifier.emailFlagged.value,
before: before, before: before,
inMailbox: _mailboxId, inMailbox: _mailboxId,
@@ -64,7 +64,6 @@ import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_ema
import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart'; import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/list_presentation_email_extensions.dart'; import 'package:tmail_ui_user/features/thread/presentation/extensions/list_presentation_email_extensions.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/refresh_thread_detail_extension.dart'; import 'package:tmail_ui_user/features/thread/presentation/extensions/refresh_thread_detail_extension.dart';
import 'package:tmail_ui_user/features/thread/presentation/filters/mailbox_filter_builder.dart';
import 'package:tmail_ui_user/features/thread/presentation/mixin/email_action_controller.dart'; import 'package:tmail_ui_user/features/thread/presentation/mixin/email_action_controller.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/loading_more_status.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/loading_more_status.dart';
@@ -822,13 +821,13 @@ class ThreadController extends BaseController with EmailActionController {
} }
void _loadMoreEmailsSuccess(LoadMoreEmailsSuccess success) { void _loadMoreEmailsSuccess(LoadMoreEmailsSuccess success) {
canLoadMore = success.emailList.isNotEmpty;
loadingMoreStatus.value = LoadingMoreStatus.completed; loadingMoreStatus.value = LoadingMoreStatus.completed;
final appendableList = validateListEmailsLoadMore(success.emailList); final appendableList = validateListEmailsLoadMore(success.emailList);
log('ThreadController::_loadMoreEmailsSuccess: emailList = ${success.emailList.length} | appendableList = ${appendableList.length}'); log('ThreadController::_loadMoreEmailsSuccess: emailList = ${success.emailList.length} | appendableList = ${appendableList.length}');
if (appendableList.isNotEmpty) { if (appendableList.isNotEmpty) {
mailboxDashBoardController.emailsInCurrentMailbox.addAll(appendableList); mailboxDashBoardController.emailsInCurrentMailbox.addAll(appendableList);
} }
canLoadMore = success.emailList.isNotEmpty && appendableList.isNotEmpty;
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
_validateBrowserHeight(); _validateBrowserHeight();
} }
@@ -618,6 +618,23 @@ class ThreadView extends GetWidget<ThreadController>
final isAINeedsActionEnabled = dashboardController.isAINeedsActionEnabled; final isAINeedsActionEnabled = dashboardController.isAINeedsActionEnabled;
final isLabelCapabilitySupported =
dashboardController.isLabelCapabilitySupported;
final labelController =
controller.mailboxDashBoardController.labelController;
final isLabelSettingEnabled =
labelController.isLabelSettingEnabled.isTrue;
List<Label>? emailLabels;
if (isLabelCapabilitySupported && isLabelSettingEnabled) {
emailLabels = presentationEmail.getLabelList(
labelController.labels,
);
}
return Dismissible( return Dismissible(
key: ValueKey<EmailId?>(presentationEmail.id), key: ValueKey<EmailId?>(presentationEmail.id),
direction: controller.getSwipeDirection( direction: controller.getSwipeDirection(
@@ -631,40 +648,21 @@ class ThreadView extends GetWidget<ThreadController>
presentationEmail, presentationEmail,
direction, direction,
), ),
child: Obx(() { child: EmailTileBuilder(
final isLabelCapabilitySupported = controller key: Key('email_tile_builder_${presentationEmail.id?.asString}'),
.mailboxDashBoardController.isLabelCapabilitySupported; presentationEmail: presentationEmail,
selectAllMode: selectModeAll,
final labelController = isShowingEmailContent: isShowingEmailContent,
controller.mailboxDashBoardController.labelController; isSenderImportantFlagEnabled: isSenderImportantFlagEnabled,
searchQuery: controller.searchQuery,
final isLabelSettingEnabled = mailboxContain: presentationEmail.mailboxContain,
labelController.isLabelSettingEnabled.isTrue; isSearchEmailRunning: isSearchEmailRunning,
isAINeedsActionEnabled: isAINeedsActionEnabled,
List<Label>? emailLabels; labels: emailLabels,
emailActionClick: _handleEmailActionClicked,
if (isLabelCapabilitySupported && isLabelSettingEnabled) { onMoreActionClick: (email, position) =>
emailLabels = presentationEmail.getLabelList( _handleEmailContextMenuAction(context, email, position),
labelController.labels, ),
);
}
return EmailTileBuilder(
key: Key('email_tile_builder_${presentationEmail.id?.asString}'),
presentationEmail: presentationEmail,
selectAllMode: selectModeAll,
isShowingEmailContent: isShowingEmailContent,
isSenderImportantFlagEnabled: isSenderImportantFlagEnabled,
searchQuery: controller.searchQuery,
mailboxContain: presentationEmail.mailboxContain,
isSearchEmailRunning: controller.searchController.isSearchEmailRunning,
isAINeedsActionEnabled: isAINeedsActionEnabled,
labels: emailLabels,
emailActionClick: _handleEmailActionClicked,
onMoreActionClick: (email, position) =>
_handleEmailContextMenuAction(context, email, position),
);
}),
); );
}); });
} }
@@ -7,9 +7,11 @@ import 'package:jmap_dart_client/jmap/core/utc_date.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.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:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:labels/model/label.dart';
import 'package:model/email/presentation_email.dart'; import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/keyword_identifier_extension.dart'; import 'package:model/extensions/keyword_identifier_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/presentation_label_mailbox.dart';
import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart';
import 'package:tmail_ui_user/features/thread/presentation/filters/mailbox_filter_builder.dart'; import 'package:tmail_ui_user/features/thread/presentation/filters/mailbox_filter_builder.dart';
@@ -99,17 +101,23 @@ void main() {
); );
test( test(
'Starred filter → flagged only', 'Starred filter → flagged only (deduplicated by Set)',
() { () {
final builder = MailboxFilterBuilder( final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(favorite: true), selectedMailbox: mailbox(favorite: true),
filterMessageOption: FilterMessageOption.starred, filterMessageOption: FilterMessageOption.starred,
); );
final filter = builder.buildFilterCondition() as EmailFilterCondition; final filter = builder.buildFilterCondition();
expect(filter, isA<LogicFilterOperator>());
expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value); final logic = filter as LogicFilterOperator;
expect(filter.notKeyword, isNull); expect(logic.operator, Operator.AND);
expect(logic.conditions.length, 1);
final condition = logic.conditions.first as EmailFilterCondition;
expect(condition.hasKeyword, KeyWordIdentifier.emailFlagged.value);
}, },
); );
}); });
@@ -318,7 +326,7 @@ void main() {
); );
test( test(
'Before date propagated into all conditions of LogicFilterOperator', 'Before date propagated into at least one condition of LogicFilterOperator',
() { () {
final before = beforeDate(); final before = beforeDate();
@@ -331,10 +339,13 @@ void main() {
oldestEmail: PresentationEmail(receivedAt: before), oldestEmail: PresentationEmail(receivedAt: before),
) as LogicFilterOperator; ) as LogicFilterOperator;
for (final condition in filter.conditions) { final conditions = filter.conditions.cast<EmailFilterCondition>();
final emailFilter = condition as EmailFilterCondition;
expect(emailFilter.before, before); expect(
} conditions.any((c) => c.before == before),
true,
reason: 'At least one condition must contain before date',
);
}, },
); );
@@ -440,4 +451,170 @@ void main() {
}, },
); );
}); });
group('Label mailbox - buildFilterCondition', () {
PresentationMailbox labelMailbox(KeyWordIdentifier keyword) {
final label = Label(
id: Id('label-1'),
keyword: keyword,
displayName: keyword.value,
);
return PresentationLabelMailbox.initial(label);
}
test(
'Label + All filter → has label keyword',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: labelMailbox(KeyWordIdentifier.emailFlagged),
filterMessageOption: FilterMessageOption.all,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value);
expect(filter.notKeyword, isNull);
expect(filter.hasAttachment, isNull);
expect(filter.inMailbox, isNull);
},
);
test(
'Label + Unread → not seen AND label keyword',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: labelMailbox(KeyWordIdentifier.emailFlagged),
filterMessageOption: FilterMessageOption.unread,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value);
expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value);
},
);
test(
'Label + Attachments → hasAttachment AND label keyword',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: labelMailbox(KeyWordIdentifier.emailFlagged),
filterMessageOption: FilterMessageOption.attachments,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.hasAttachment, true);
expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value);
},
);
test(
'Label + Starred → AND(flagged, label keyword) with dedup',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: labelMailbox(KeyWordIdentifier.emailFlagged),
filterMessageOption: FilterMessageOption.starred,
);
final filter = builder.buildFilterCondition();
expect(filter, isA<LogicFilterOperator>());
final logic = filter as LogicFilterOperator;
expect(logic.conditions.length, 1);
final c = logic.conditions.first as EmailFilterCondition;
expect(c.hasKeyword, KeyWordIdentifier.emailFlagged.value);
},
);
test(
'Label mailbox propagates before date',
() {
final before = beforeDate();
final builder = MailboxFilterBuilder(
selectedMailbox: labelMailbox(KeyWordIdentifier.emailFlagged),
filterMessageOption: FilterMessageOption.unread,
);
final filter = builder.buildFilterCondition(
oldestEmail: PresentationEmail(receivedAt: before),
) as EmailFilterCondition;
expect(filter.before, before);
},
);
});
group('Edge - Label mailbox with null keyword', () {
PresentationMailbox labelMailboxWithNullKeyword() {
final label = Label(
id: Id('label-null'),
keyword: null,
displayName: 'NoKeyword',
);
return PresentationLabelMailbox.initial(label);
}
test(
'Label with null keyword → filter has no hasKeyword',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: labelMailboxWithNullKeyword(),
filterMessageOption: FilterMessageOption.all,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.hasKeyword, isNull);
expect(filter.notKeyword, isNull);
expect(filter.hasAttachment, isNull);
},
);
});
group('Edge - ActionRequired + Attachments + before', () {
test(
'ActionRequired + Attachments + before → hasAttachment, needsAction, notSeen, before',
() {
final before = beforeDate();
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(actionRequired: true),
filterMessageOption: FilterMessageOption.attachments,
);
final filter = builder.buildFilterCondition(
oldestEmail: PresentationEmail(receivedAt: before),
) as EmailFilterCondition;
expect(filter.hasAttachment, true);
expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value);
expect(
filter.hasKeyword,
KeyWordIdentifierExtension.needsActionMail.value,
);
expect(filter.before, before);
},
);
});
group('Edge - Null mailbox + Starred', () {
test(
'Null mailbox + Starred → only flagged, no inMailbox',
() {
const builder = MailboxFilterBuilder(
selectedMailbox: null,
filterMessageOption: FilterMessageOption.starred,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value);
expect(filter.inMailbox, isNull);
expect(filter.notKeyword, isNull);
},
);
});
} }