TF-3643 Thread Detail get emails meta data method
TF-3643 Thread Detail cache email with thread id TF-3643 Thread Detail view thread detail TF-3643 Thread Detail [Part 2 fixup] remove ThreadDetailController dependency from MailboxDashboardController TF-3643 Thread Detail [Part 2 fixup] remove EmailSupervisorController dependency from ThreadDetailController TF-3643 Thread Detail [Part 2 fixup] move isSearchRunning check into ThreadDetailController TF-3643 Thread Detail [Part 2 fixup] ensure GetEmailsByIdsInteractor performance TF-3643 Thread Detail [Part 2 fixup] unit test loadMoreThreadDetailEmails TF-3643 Thread Detail [Part 2 fixup] unit test getEmailIdsToLoad TF-3643 Thread Detail [Part 2 fixup] optimize ThreadDetailBinding bindingsDataSource TF-3643 Thread Detail [Part 2 fixup] optimize getThreadDetailLoadingView TF-3643 Thread Detail [Part 2 fixup] readd unique ValueKey for HtmlContentViewerOnWebWidget TF-3643 Thread Detail [Part 2 fixup] unit test initializeThreadDetailEmails TF-3643 Thread Detail [Part 3 fixup] Update private action button methods TF-3643 Thread Detail [Part 4 fixup] Collapsed email preview 65 characters max TF-3643 Thread Detail [Part 4 fixup] Remove personal scripts TF-3643 Thread Detail move EmailInThreadStatus into PresentationEmail TF-3643 Thread Detail refactor toggle collapse expand TF-3643 Thread Detail EmailViewAppBarWidget required parameters TF-3643 Thread Detail tap avatar toggle expansion TF-3643 Thread Detail fix cannot open single email TF-3643 Thread Detail fix open thread first time blank view TF-3643 Thread Detail fix thread detail UI TF-3643 Thread Detail fix round bottom view TF-3643 Thread Detail fix round bottom view TF-3643 Thread Detail bottom actions depend on current expanded email TF-3643 Thread Detail hide next previous actions TF-3643 Thread Detail fix load more show initial loading view TF-3643 Thread Detail fix cannot view single email TF-3643 Thread Detail fix bottom place holder not aligned TF-3643 Thread Detail fix back to search view TF-3643 Thread Detail fix display 65 character preview collapsed email TF-3643 Thread Detail fix search not work TF-3643 Thread Detail fix bottom place holder UI TF-3643 Thread Detail fix expand view TF-3643 Thread Detail jump to top of last email view TF-3643 Thread Detail remove toMe condition of filtering thread TF-3643 Thread Detail fix sort thread detail TF-3643 Thread Detail fix assert not work in release TF-3643 Thread Detail add load more test case <20 TF-3643 Thread Detail remove the use of currentThreadId TF-3643 Thread Detail remove the use of selectedEmail in SingleEmailController TF-3643 Thread Detail fix unit test SingleEmailController calendar TF-3643 Thread Detail disable 2 way scroll control TF-3643 Thread Detail allow thread to load sent email incase thread only contains 1 email TF-3643 Thread Detail disable single email page view TF-3643 Thread Detail update emailsNotLoadedCount name TF-3643 Thread Detail remove collapsed email char limit TF-3643 Thread Detail fix CI TF-3643 Thread Detail unit test handleGetEmailIdsByThreadIdSuccess TF-3643 Thread Detail make _currentEmailId private TF-3643 Thread Detail fix search click users TF-3643 Thread Detail fix scroll jump TF-3643 Thread Detail fix scroll jump TF-3643 Thread Detail fix web back action TF-3643 Thread Detail round bottom actions TF-3643 Thread Detail fix tablet large empty email TF-3643 Thread Detail fix email subject selectable TF-3643 Thread Detail fix email single view with top divider TF-3643 Thread Detail fix email cache threadId missing TF-3643 Thread Detail fix filter sent email in thread details TF-3643 Thread Detail fix select text TF-3643 Thread Detail fix subject alignment TF-3643 Thread Detail fix subject alignment TF-3643 Thread Detail fix tablet large empty email TF-3643 Thread Detail add nullcheck for _jumpScrollViewToTopOfEmail TF-3643 Thread Detail decouple ThreadDetailController from SingleEmailController TF-3643 Thread Detail combine emailIds to emailIdsPresentation TF-3643 Thread Detail remove currentEmailLoaded obx listener of SingleEmailController TF-3643 Thread Detail replace threadAction with an email ui action TF-3643 Thread Detail fix top and bottom bar delay displaying TF-3643 Thread Detail update ReplyForwardBottomBarAction to PerformEmailActionInThreadDetailAction TF-3643 Thread Detail use getOwnEmailAddress in ThreadDetailAppBar TF-3643 Thread Detail add TextOverflow.ellipsis to EmailSubjectWidget TF-3643 Thread Detail fix round border threadDetailLoadingView
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="13" height="22" viewBox="0 0 13 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M1.7929 6.8808C1.40312 7.27058 0.769019 7.26846 0.381054 6.88049L0.293208 6.79265C-0.0967477 6.40269 -0.0993609 5.77306 0.295118 5.37858L5.37914 0.294558C5.77015 -0.096453 6.40064 -0.099921 6.79512 0.294558L11.8791 5.37858C12.2702 5.76959 12.269 6.40468 11.8811 6.79265L11.7932 6.88049C11.4033 7.27045 10.7744 7.2738 10.3814 6.8808L6.08713 2.58657L1.7929 6.8808Z" fill="#B8C1CC"/>
|
||||||
|
<path d="M10.3792 14.2935C10.769 13.9037 11.4031 13.9058 11.7911 14.2938L11.8789 14.3816C12.2689 14.7716 12.2715 15.4012 11.877 15.7957L6.79298 20.8797C6.40196 21.2707 5.77148 21.2742 5.377 20.8797L0.292975 15.7957C-0.0980349 15.4047 -0.0968991 14.7696 0.291066 14.3816L0.378911 14.2938C0.768867 13.9038 1.39776 13.9005 1.79076 14.2935L6.08499 18.5877L10.3792 14.2935Z" fill="#B8C1CC"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 880 B |
@@ -245,6 +245,7 @@ extension AppColor on Color {
|
|||||||
static const blue400 = Color(0xFF80BDFF);
|
static const blue400 = Color(0xFF80BDFF);
|
||||||
static const blue900 = Color(0xFF0F76E7);
|
static const blue900 = Color(0xFF0F76E7);
|
||||||
static const m3Tertiary = Color(0xFF8C9CAF);
|
static const m3Tertiary = Color(0xFF8C9CAF);
|
||||||
|
static const lightIconTertiary = Color(0xFFB8C1CC);
|
||||||
static const m3Neutral70 = Color(0xFFAEAAAE);
|
static const m3Neutral70 = Color(0xFFAEAAAE);
|
||||||
static const m3Neutral90 = Color(0xFFE6E1E5);
|
static const m3Neutral90 = Color(0xFFE6E1E5);
|
||||||
static const grayBackgroundColor = Color(0xFFF3F6F9);
|
static const grayBackgroundColor = Color(0xFFF3F6F9);
|
||||||
|
|||||||
@@ -20,4 +20,21 @@ extension ListExtensions<T> on List<T> {
|
|||||||
int countOccurrences(T value) {
|
int countOccurrences(T value) {
|
||||||
return where((element) => element == value).length;
|
return where((element) => element == value).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<List<T>> chunks(int chunkSize) {
|
||||||
|
if (chunkSize <= 0) {
|
||||||
|
throw ArgumentError('Chunk size must be greater than 0', 'chunkSize');
|
||||||
|
}
|
||||||
|
if (isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
final result = <List<T>>[];
|
||||||
|
final totalChunks = (length / chunkSize).ceil();
|
||||||
|
for (var i = 0; i < totalChunks; i++) {
|
||||||
|
final start = i * chunkSize;
|
||||||
|
final end = (start + chunkSize).clamp(0, length);
|
||||||
|
result.add(sublist(start, end));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -236,6 +236,7 @@ class ImagePaths {
|
|||||||
String get icTwakeWorkplace => _getIconPath('icon_twp.png');
|
String get icTwakeWorkplace => _getIconPath('icon_twp.png');
|
||||||
|
|
||||||
String get animLottieTmail => _getAnimationPath('lottie-tmail.json');
|
String get animLottieTmail => _getAnimationPath('lottie-tmail.json');
|
||||||
|
String get icExpandArrows => _getImagePath('ic_expand_arrows.svg');
|
||||||
|
|
||||||
String _getImagePath(String imageName) {
|
String _getImagePath(String imageName) {
|
||||||
return AssetsPaths.images + imageName;
|
return AssetsPaths.images + imageName;
|
||||||
|
|||||||
@@ -194,6 +194,11 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
var height = document.body.scrollHeight;
|
||||||
|
window.parent.postMessage(JSON.stringify({"view": "$_createdViewId", "type": "toDart: htmlHeight", "height": height}), "*");
|
||||||
|
});
|
||||||
|
|
||||||
${widget.mailtoDelegate != null
|
${widget.mailtoDelegate != null
|
||||||
? '''
|
? '''
|
||||||
function handleOnClickEmailLink(e) {
|
function handleOnClickEmailLink(e) {
|
||||||
@@ -238,6 +243,8 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
|
|||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
: ''}
|
: ''}
|
||||||
|
|
||||||
|
resizeObserver.observe(document.body);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
''';
|
''';
|
||||||
@@ -303,7 +310,7 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(builder: (context, constraint) {
|
return LayoutBuilder(builder: (context, constraint) {
|
||||||
minHeight = math.max(constraint.maxHeight, minHeight);
|
minHeight = math.min(constraint.maxHeight, minHeight);
|
||||||
final child = Stack(
|
final child = Stack(
|
||||||
children: [
|
children: [
|
||||||
if (_htmlData?.isNotEmpty == false)
|
if (_htmlData?.isNotEmpty == false)
|
||||||
@@ -317,7 +324,7 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
|
|||||||
height: _actualHeight,
|
height: _actualHeight,
|
||||||
width: _actualWidth,
|
width: _actualWidth,
|
||||||
child: HtmlElementView(
|
child: HtmlElementView(
|
||||||
key: ValueKey(_htmlData),
|
key: ValueKey('$_htmlData-${widget.key}'),
|
||||||
viewType: _createdViewId,
|
viewType: _createdViewId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class HtmlContentViewer extends StatefulWidget {
|
|||||||
final double? maxHtmlContentHeight;
|
final double? maxHtmlContentHeight;
|
||||||
final double minHtmlContentHeight;
|
final double minHtmlContentHeight;
|
||||||
final double offsetHtmlContentHeight;
|
final double offsetHtmlContentHeight;
|
||||||
|
final bool keepAlive;
|
||||||
|
|
||||||
final OnLoadWidthHtmlViewerAction? onLoadWidthHtmlViewer;
|
final OnLoadWidthHtmlViewerAction? onLoadWidthHtmlViewer;
|
||||||
final OnMailtoDelegateAction? onMailtoDelegateAction;
|
final OnMailtoDelegateAction? onMailtoDelegateAction;
|
||||||
@@ -47,6 +48,7 @@ class HtmlContentViewer extends StatefulWidget {
|
|||||||
this.direction,
|
this.direction,
|
||||||
this.minHtmlContentHeight = ConstantsUI.htmlContentMinHeight,
|
this.minHtmlContentHeight = ConstantsUI.htmlContentMinHeight,
|
||||||
this.offsetHtmlContentHeight = ConstantsUI.htmlContentOffsetHeight,
|
this.offsetHtmlContentHeight = ConstantsUI.htmlContentOffsetHeight,
|
||||||
|
this.keepAlive = false,
|
||||||
this.keepWidthWhileLoading = false,
|
this.keepWidthWhileLoading = false,
|
||||||
this.contentPadding,
|
this.contentPadding,
|
||||||
this.useDefaultFont = false,
|
this.useDefaultFont = false,
|
||||||
@@ -63,7 +65,7 @@ class HtmlContentViewer extends StatefulWidget {
|
|||||||
State<StatefulWidget> createState() => HtmlContentViewState();
|
State<StatefulWidget> createState() => HtmlContentViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class HtmlContentViewState extends State<HtmlContentViewer> {
|
class HtmlContentViewState extends State<HtmlContentViewer> with AutomaticKeepAliveClientMixin {
|
||||||
|
|
||||||
late InAppWebViewController _webViewController;
|
late InAppWebViewController _webViewController;
|
||||||
late double _actualHeight;
|
late double _actualHeight;
|
||||||
@@ -130,6 +132,7 @@ class HtmlContentViewState extends State<HtmlContentViewer> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
final child = Stack(children: [
|
final child = Stack(children: [
|
||||||
if (_htmlData == null)
|
if (_htmlData == null)
|
||||||
const SizedBox.shrink()
|
const SizedBox.shrink()
|
||||||
@@ -352,4 +355,7 @@ class HtmlContentViewState extends State<HtmlContentViewer> {
|
|||||||
_htmlData = null;
|
_htmlData = null;
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => widget.keepAlive;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import 'package:core/presentation/extensions/list_extensions.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('list extensions test:', () {
|
||||||
|
group('chunks test:', () {
|
||||||
|
test('should return evenly sized chunks when length is divisible by chunkSize', () {
|
||||||
|
// Arrange
|
||||||
|
final sourceList = [1, 2, 3, 4, 5, 6];
|
||||||
|
const chunkSize = 3;
|
||||||
|
final expectedOutput = [
|
||||||
|
[1, 2, 3],
|
||||||
|
[4, 5, 6]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final result = sourceList.chunks(chunkSize);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result, equals(expectedOutput));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return chunks with the last chunk smaller when length is not divisible', () {
|
||||||
|
// Arrange
|
||||||
|
final sourceList = [1, 2, 3, 4, 5];
|
||||||
|
const chunkSize = 3;
|
||||||
|
final expectedOutput = [
|
||||||
|
[1, 2, 3],
|
||||||
|
[4, 5]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final result = sourceList.chunks(chunkSize);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result, equals(expectedOutput));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return chunks of size 1 when chunkSize is 1', () {
|
||||||
|
// Arrange
|
||||||
|
final sourceList = [1, 2, 3];
|
||||||
|
const chunkSize = 1;
|
||||||
|
final expectedOutput = [
|
||||||
|
[1],
|
||||||
|
[2],
|
||||||
|
[3]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final result = sourceList.chunks(chunkSize);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result, equals(expectedOutput));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return a single chunk when chunkSize equals list length', () {
|
||||||
|
// Arrange
|
||||||
|
final sourceList = [1, 2, 3, 4];
|
||||||
|
const chunkSize = 4;
|
||||||
|
final expectedOutput = [
|
||||||
|
[1, 2, 3, 4]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final result = sourceList.chunks(chunkSize);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result, equals(expectedOutput));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return a single chunk when chunkSize is greater than list length', () {
|
||||||
|
// Arrange
|
||||||
|
final sourceList = [1, 2, 3];
|
||||||
|
const chunkSize = 5;
|
||||||
|
final expectedOutput = [
|
||||||
|
[1, 2, 3]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final result = sourceList.chunks(chunkSize);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result, equals(expectedOutput));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return an empty list when the source list is empty', () {
|
||||||
|
// Arrange
|
||||||
|
final sourceList = <int>[]; // Explicitly typed empty list
|
||||||
|
const chunkSize = 3;
|
||||||
|
final expectedOutput = <List<int>>[]; // Expected empty list of lists
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final result = sourceList.chunks(chunkSize);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result, isEmpty); // Check if it's empty
|
||||||
|
expect(result, equals(expectedOutput)); // Also check for structural equality
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should work with different list types (e.g., String)', () {
|
||||||
|
// Arrange
|
||||||
|
final sourceList = ['a', 'b', 'c', 'd', 'e'];
|
||||||
|
const chunkSize = 2;
|
||||||
|
final expectedOutput = [
|
||||||
|
['a', 'b'],
|
||||||
|
['c', 'd'],
|
||||||
|
['e']
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
final result = sourceList.chunks(chunkSize);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result, equals(expectedOutput));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw ArgumentError when chunkSize is 0', () {
|
||||||
|
// Arrange
|
||||||
|
final sourceList = [1, 2, 3];
|
||||||
|
const chunkSize = 0;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
// We wrap the call that is expected to throw in a zero-argument function.
|
||||||
|
expect(() => sourceList.chunks(chunkSize), throwsArgumentError);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw ArgumentError when chunkSize is negative', () {
|
||||||
|
// Arrange
|
||||||
|
final sourceList = [1, 2, 3];
|
||||||
|
const chunkSize = -1;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
expect(() => sourceList.chunks(chunkSize), throwsArgumentError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_database_steps.dart';
|
||||||
|
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
|
||||||
|
|
||||||
|
class UpgradeHiveDatabaseStepsV18 extends UpgradeDatabaseSteps {
|
||||||
|
|
||||||
|
final CachingManager _cachingManager;
|
||||||
|
|
||||||
|
UpgradeHiveDatabaseStepsV18(this._cachingManager);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onUpgrade(int oldVersion, int newVersion) async {
|
||||||
|
if (oldVersion > 0 && oldVersion < newVersion && newVersion == 18) {
|
||||||
|
await _cachingManager.clearAllEmailAndStateCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,10 +9,12 @@ import 'package:model/extensions/presentation_email_extension.dart';
|
|||||||
class EmailAvatarBuilder extends StatelessWidget {
|
class EmailAvatarBuilder extends StatelessWidget {
|
||||||
|
|
||||||
final PresentationEmail emailSelected;
|
final PresentationEmail emailSelected;
|
||||||
|
final OnTapAvatarActionClick? onTapAvatarActionClick;
|
||||||
|
|
||||||
const EmailAvatarBuilder({
|
const EmailAvatarBuilder({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.emailSelected
|
required this.emailSelected,
|
||||||
|
this.onTapAvatarActionClick,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -22,6 +24,7 @@ class EmailAvatarBuilder extends StatelessWidget {
|
|||||||
..size(50)
|
..size(50)
|
||||||
..addTextStyle(ThemeUtils.textStyleHeadingH4(color: Colors.white))
|
..addTextStyle(ThemeUtils.textStyleHeadingH4(color: Colors.white))
|
||||||
..backgroundColor(AppColor.colorAvatar)
|
..backgroundColor(AppColor.colorAvatar)
|
||||||
|
..addOnTapActionClick(onTapAvatarActionClick ?? () {})
|
||||||
..avatarColor(emailSelected.avatarColors))
|
..avatarColor(emailSelected.avatarColors))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class OptionalExpanded extends StatelessWidget {
|
||||||
|
const OptionalExpanded({
|
||||||
|
super.key,
|
||||||
|
required this.expandedEnabled,
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool expandedEnabled;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return expandedEnabled ? Expanded(child: child) : child;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class OptionalScroll extends StatelessWidget {
|
||||||
|
const OptionalScroll({
|
||||||
|
super.key,
|
||||||
|
required this.scrollEnabled,
|
||||||
|
required this.child,
|
||||||
|
this.scrollPhysics = const ClampingScrollPhysics(),
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool scrollEnabled;
|
||||||
|
final Widget child;
|
||||||
|
final ScrollPhysics scrollPhysics;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return scrollEnabled
|
||||||
|
? SingleChildScrollView(
|
||||||
|
physics : scrollPhysics,
|
||||||
|
child: child,
|
||||||
|
)
|
||||||
|
: child;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
class CacheVersion {
|
class CacheVersion {
|
||||||
static const int hiveDBVersion = 17;
|
static const int hiveDBVersion = 18;
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@ import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_st
|
|||||||
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v15.dart';
|
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v15.dart';
|
||||||
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v16.dart';
|
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v16.dart';
|
||||||
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v17.dart';
|
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v17.dart';
|
||||||
|
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v18.dart';
|
||||||
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v7.dart';
|
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v7.dart';
|
||||||
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
|
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
|
||||||
import 'package:tmail_ui_user/features/caching/config/cache_version.dart';
|
import 'package:tmail_ui_user/features/caching/config/cache_version.dart';
|
||||||
@@ -91,6 +92,7 @@ class HiveCacheConfig {
|
|||||||
await UpgradeHiveDatabaseStepsV15(cachingManager).onUpgrade(oldVersion, newVersion);
|
await UpgradeHiveDatabaseStepsV15(cachingManager).onUpgrade(oldVersion, newVersion);
|
||||||
await UpgradeHiveDatabaseStepsV16(cachingManager).onUpgrade(oldVersion, newVersion);
|
await UpgradeHiveDatabaseStepsV16(cachingManager).onUpgrade(oldVersion, newVersion);
|
||||||
await UpgradeHiveDatabaseStepsV17(cachingManager).onUpgrade(oldVersion, newVersion);
|
await UpgradeHiveDatabaseStepsV17(cachingManager).onUpgrade(oldVersion, newVersion);
|
||||||
|
await UpgradeHiveDatabaseStepsV18(cachingManager).onUpgrade(oldVersion, newVersion);
|
||||||
|
|
||||||
if (oldVersion != newVersion) {
|
if (oldVersion != newVersion) {
|
||||||
await cachingManager.storeCacheVersion(newVersion);
|
await cachingManager.storeCacheVersion(newVersion);
|
||||||
|
|||||||
@@ -153,6 +153,23 @@ extension EmailActionTypeExtension on EmailActionType {
|
|||||||
return imagePaths.icEdit;
|
return imagePaths.icEdit;
|
||||||
case EmailActionType.openInNewTab:
|
case EmailActionType.openInNewTab:
|
||||||
return imagePaths.icOpenInNewTab;
|
return imagePaths.icOpenInNewTab;
|
||||||
|
case EmailActionType.printAll:
|
||||||
|
return imagePaths.icPrinter;
|
||||||
|
case EmailActionType.forward:
|
||||||
|
return imagePaths.icForward;
|
||||||
|
case EmailActionType.replyAll:
|
||||||
|
return imagePaths.icReplyAll;
|
||||||
|
case EmailActionType.replyToList:
|
||||||
|
return imagePaths.icReply;
|
||||||
|
case EmailActionType.moveToMailbox:
|
||||||
|
return imagePaths.icMoveEmail;
|
||||||
|
case EmailActionType.markAsStarred:
|
||||||
|
return imagePaths.icStar;
|
||||||
|
case EmailActionType.unMarkAsStarred:
|
||||||
|
return imagePaths.icUnStar;
|
||||||
|
case EmailActionType.moveToTrash:
|
||||||
|
case EmailActionType.deletePermanently:
|
||||||
|
return imagePaths.icDeleteComposer;
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -178,6 +195,24 @@ extension EmailActionTypeExtension on EmailActionType {
|
|||||||
return appLocalizations.editAsNewEmail;
|
return appLocalizations.editAsNewEmail;
|
||||||
case EmailActionType.openInNewTab:
|
case EmailActionType.openInNewTab:
|
||||||
return appLocalizations.openInNewTab;
|
return appLocalizations.openInNewTab;
|
||||||
|
case EmailActionType.printAll:
|
||||||
|
return appLocalizations.printAll;
|
||||||
|
case EmailActionType.forward:
|
||||||
|
return appLocalizations.forward;
|
||||||
|
case EmailActionType.replyAll:
|
||||||
|
return appLocalizations.reply_all;
|
||||||
|
case EmailActionType.replyToList:
|
||||||
|
return appLocalizations.replyToList;
|
||||||
|
case EmailActionType.moveToMailbox:
|
||||||
|
return appLocalizations.move_message;
|
||||||
|
case EmailActionType.markAsStarred:
|
||||||
|
return appLocalizations.mark_as_starred;
|
||||||
|
case EmailActionType.unMarkAsStarred:
|
||||||
|
return appLocalizations.not_starred;
|
||||||
|
case EmailActionType.moveToTrash:
|
||||||
|
return appLocalizations.move_to_trash;
|
||||||
|
case EmailActionType.deletePermanently:
|
||||||
|
return appLocalizations.delete_permanently;
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
|
||||||
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:model/email/email_action_type.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';
|
||||||
|
|
||||||
class EmailUIAction extends UIAction {
|
class EmailUIAction extends UIAction {
|
||||||
@@ -29,3 +32,27 @@ class HideEmailContentViewAction extends EmailUIAction {}
|
|||||||
class ShowEmailContentViewAction extends EmailUIAction {}
|
class ShowEmailContentViewAction extends EmailUIAction {}
|
||||||
|
|
||||||
class RefreshAllEmailAction extends EmailUIAction {}
|
class RefreshAllEmailAction extends EmailUIAction {}
|
||||||
|
|
||||||
|
class CloseEmailInThreadDetailAction extends EmailUIAction {
|
||||||
|
final EmailId emailId;
|
||||||
|
|
||||||
|
CloseEmailInThreadDetailAction(this.emailId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [emailId];
|
||||||
|
}
|
||||||
|
class PerformEmailActionInThreadDetailAction extends EmailUIAction {
|
||||||
|
PerformEmailActionInThreadDetailAction({
|
||||||
|
required this.emailActionType,
|
||||||
|
required this.presentationEmail,
|
||||||
|
});
|
||||||
|
|
||||||
|
final EmailActionType emailActionType;
|
||||||
|
final PresentationEmail presentationEmail;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
emailActionType,
|
||||||
|
presentationEmail,
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:core/core.dart';
|
import 'package:core/core.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
import 'package:tmail_ui_user/features/base/base_bindings.dart';
|
import 'package:tmail_ui_user/features/base/base_bindings.dart';
|
||||||
import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart';
|
import 'package:tmail_ui_user/features/caching/utils/local_storage_manager.dart';
|
||||||
import 'package:tmail_ui_user/features/caching/utils/session_storage_manager.dart';
|
import 'package:tmail_ui_user/features/caching/utils/session_storage_manager.dart';
|
||||||
@@ -32,7 +33,6 @@ import 'package:tmail_ui_user/features/email/domain/usecases/preview_email_from_
|
|||||||
import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart';
|
import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart';
|
import 'package:tmail_ui_user/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart';
|
import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/controller/email_supervisor_controller.dart';
|
|
||||||
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
|
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/login/data/network/interceptors/authorization_interceptors.dart';
|
import 'package:tmail_ui_user/features/login/data/network/interceptors/authorization_interceptors.dart';
|
||||||
import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart';
|
import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart';
|
||||||
@@ -62,10 +62,14 @@ import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart';
|
|||||||
import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart';
|
import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart';
|
||||||
|
|
||||||
class EmailBindings extends BaseBindings {
|
class EmailBindings extends BaseBindings {
|
||||||
|
final EmailId? currentEmailId;
|
||||||
|
|
||||||
|
EmailBindings({this.currentEmailId});
|
||||||
|
|
||||||
|
String? get tag => currentEmailId?.id.value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void bindingsController() {
|
void bindingsController() {
|
||||||
Get.put(EmailSupervisorController());
|
|
||||||
Get.put(SingleEmailController(
|
Get.put(SingleEmailController(
|
||||||
Get.find<GetEmailContentInteractor>(),
|
Get.find<GetEmailContentInteractor>(),
|
||||||
Get.find<MarkAsEmailReadInteractor>(),
|
Get.find<MarkAsEmailReadInteractor>(),
|
||||||
@@ -83,7 +87,8 @@ class EmailBindings extends BaseBindings {
|
|||||||
Get.find<GetHtmlContentFromAttachmentInteractor>(),
|
Get.find<GetHtmlContentFromAttachmentInteractor>(),
|
||||||
Get.find<DownloadAllAttachmentsForWebInteractor>(),
|
Get.find<DownloadAllAttachmentsForWebInteractor>(),
|
||||||
Get.find<ExportAllAttachmentsInteractor>(),
|
Get.find<ExportAllAttachmentsInteractor>(),
|
||||||
));
|
currentEmailId: currentEmailId,
|
||||||
|
), tag: tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ import 'package:tmail_ui_user/features/email/domain/usecases/send_receipt_to_sen
|
|||||||
import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart';
|
import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/bindings/calendar_event_interactor_bindings.dart';
|
import 'package:tmail_ui_user/features/email/presentation/bindings/calendar_event_interactor_bindings.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/controller/email_supervisor_controller.dart';
|
|
||||||
import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart';
|
import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_attendee_extension.dart';
|
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_attendee_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_organizer_extension.dart';
|
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_organizer_extension.dart';
|
||||||
@@ -135,8 +134,10 @@ import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new
|
|||||||
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
|
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart';
|
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart';
|
import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
|
import 'package:tmail_ui_user/features/search/email/presentation/search_email_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_detail/presentation/extension/focus_thread_detail_expanded_email.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
import 'package:tmail_ui_user/main/error/capability_validator.dart';
|
import 'package:tmail_ui_user/main/error/capability_validator.dart';
|
||||||
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/localizations/localization_service.dart';
|
import 'package:tmail_ui_user/main/localizations/localization_service.dart';
|
||||||
@@ -156,7 +157,6 @@ import 'package:twake_previewer_flutter/twake_plain_text_previewer/twake_plain_t
|
|||||||
class SingleEmailController extends BaseController with AppLoaderMixin {
|
class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||||
|
|
||||||
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
||||||
final emailSupervisorController = Get.find<EmailSupervisorController>();
|
|
||||||
final _downloadManager = Get.find<DownloadManager>();
|
final _downloadManager = Get.find<DownloadManager>();
|
||||||
final _printUtils = Get.find<PrintUtils>();
|
final _printUtils = Get.find<PrintUtils>();
|
||||||
final _attachmentListScrollController = ScrollController();
|
final _attachmentListScrollController = ScrollController();
|
||||||
@@ -177,6 +177,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
final GetHtmlContentFromAttachmentInteractor _getHtmlContentFromAttachmentInteractor;
|
final GetHtmlContentFromAttachmentInteractor _getHtmlContentFromAttachmentInteractor;
|
||||||
final DownloadAllAttachmentsForWebInteractor _downloadAllAttachmentsForWebInteractor;
|
final DownloadAllAttachmentsForWebInteractor _downloadAllAttachmentsForWebInteractor;
|
||||||
final ExportAllAttachmentsInteractor _exportAllAttachmentsInteractor;
|
final ExportAllAttachmentsInteractor _exportAllAttachmentsInteractor;
|
||||||
|
final EmailId? _currentEmailId;
|
||||||
|
|
||||||
CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
|
CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
|
||||||
SendReceiptToSenderInteractor? _sendReceiptToSenderInteractor;
|
SendReceiptToSenderInteractor? _sendReceiptToSenderInteractor;
|
||||||
@@ -185,6 +186,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
MaybeCalendarEventInteractor? _maybeCalendarEventInteractor;
|
MaybeCalendarEventInteractor? _maybeCalendarEventInteractor;
|
||||||
RejectCalendarEventInteractor? _rejectCalendarEventInteractor;
|
RejectCalendarEventInteractor? _rejectCalendarEventInteractor;
|
||||||
AcceptCounterCalendarEventInteractor? _acceptCounterCalendarEventInteractor;
|
AcceptCounterCalendarEventInteractor? _acceptCounterCalendarEventInteractor;
|
||||||
|
ThreadDetailController? _threadDetailController;
|
||||||
|
|
||||||
final emailContents = RxnString();
|
final emailContents = RxnString();
|
||||||
final attachments = <Attachment>[].obs;
|
final attachments = <Attachment>[].obs;
|
||||||
@@ -198,15 +200,19 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
final attendanceStatus = Rxn<AttendanceStatus>();
|
final attendanceStatus = Rxn<AttendanceStatus>();
|
||||||
final htmlContentViewKey = GlobalKey<HtmlContentViewState>();
|
final htmlContentViewKey = GlobalKey<HtmlContentViewState>();
|
||||||
|
|
||||||
EmailId? _currentEmailId;
|
|
||||||
Identity? _identitySelected;
|
Identity? _identitySelected;
|
||||||
ButtonState? _printEmailButtonState;
|
ButtonState? _printEmailButtonState;
|
||||||
|
final obxListeners = <Worker>[];
|
||||||
|
|
||||||
final StreamController<Either<Failure, Success>> _downloadProgressStateController =
|
final StreamController<Either<Failure, Success>> _downloadProgressStateController =
|
||||||
StreamController<Either<Failure, Success>>.broadcast();
|
StreamController<Either<Failure, Success>>.broadcast();
|
||||||
Stream<Either<Failure, Success>> get downloadProgressState => _downloadProgressStateController.stream;
|
Stream<Either<Failure, Success>> get downloadProgressState => _downloadProgressStateController.stream;
|
||||||
|
|
||||||
PresentationEmail? get currentEmail => mailboxDashBoardController.selectedEmail.value;
|
PresentationEmail? get currentEmail {
|
||||||
|
return _currentEmailId == null
|
||||||
|
? null
|
||||||
|
: _threadDetailController?.emailIdsPresentation[_currentEmailId];
|
||||||
|
}
|
||||||
|
|
||||||
bool get calendarEventProcessing => viewState.value.fold(
|
bool get calendarEventProcessing => viewState.value.fold(
|
||||||
(failure) => false,
|
(failure) => false,
|
||||||
@@ -238,11 +244,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
this._previewEmailFromEmlFileInteractor,
|
this._previewEmailFromEmlFileInteractor,
|
||||||
this._getHtmlContentFromAttachmentInteractor,
|
this._getHtmlContentFromAttachmentInteractor,
|
||||||
this._downloadAllAttachmentsForWebInteractor,
|
this._downloadAllAttachmentsForWebInteractor,
|
||||||
this._exportAllAttachmentsInteractor,
|
this._exportAllAttachmentsInteractor, {
|
||||||
);
|
EmailId? currentEmailId,
|
||||||
|
}) : _currentEmailId = currentEmailId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
|
_threadDetailController = getBinding<ThreadDetailController>();
|
||||||
|
_injectCalendarEventBindings(session, accountId);
|
||||||
_registerObxStreamListener();
|
_registerObxStreamListener();
|
||||||
_listenDownloadAttachmentProgressState();
|
_listenDownloadAttachmentProgressState();
|
||||||
super.onInit();
|
super.onInit();
|
||||||
@@ -250,6 +259,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
|
_threadDetailController = null;
|
||||||
_downloadProgressStateController.close();
|
_downloadProgressStateController.close();
|
||||||
_attachmentListScrollController.dispose();
|
_attachmentListScrollController.dispose();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
@@ -357,28 +367,15 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _registerObxStreamListener() {
|
void _registerObxStreamListener() {
|
||||||
ever(mailboxDashBoardController.accountId, (accountId) {
|
if (accountId != null) {
|
||||||
if (accountId is AccountId) {
|
_injectAndGetInteractorBindings(session, accountId!);
|
||||||
_injectAndGetInteractorBindings(
|
}
|
||||||
session,
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
accountId
|
_handleOpenEmailDetailedView();
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ever<PresentationEmail?>(
|
obxListeners.add(ever(mailboxDashBoardController.emailUIAction, (action) {
|
||||||
mailboxDashBoardController.selectedEmail,
|
|
||||||
_handleOpenEmailDetailedView
|
|
||||||
);
|
|
||||||
|
|
||||||
ever(mailboxDashBoardController.emailUIAction, (action) {
|
|
||||||
if (action is CloseEmailDetailedViewToRedirectToTheInboxAction) {
|
if (action is CloseEmailDetailedViewToRedirectToTheInboxAction) {
|
||||||
if (emailSupervisorController.supportedPageView.isTrue) {
|
|
||||||
emailSupervisorController.popEmailQueue(_currentEmailId);
|
|
||||||
emailSupervisorController.setCurrentEmailIndex(-1);
|
|
||||||
emailSupervisorController.disposePageViewController();
|
|
||||||
}
|
|
||||||
_updateCurrentEmailId(null);
|
|
||||||
_resetToOriginalValue(isEmailClosing: true);
|
_resetToOriginalValue(isEmailClosing: true);
|
||||||
mailboxDashBoardController.clearSelectedEmail();
|
mailboxDashBoardController.clearSelectedEmail();
|
||||||
mailboxDashBoardController.dispatchRoute(DashboardRoutes.thread);
|
mailboxDashBoardController.dispatchRoute(DashboardRoutes.thread);
|
||||||
@@ -392,42 +389,54 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
} else if (action is ShowEmailContentViewAction) {
|
} else if (action is ShowEmailContentViewAction) {
|
||||||
isEmailContentHidden.value = false;
|
isEmailContentHidden.value = false;
|
||||||
mailboxDashBoardController.clearEmailUIAction();
|
mailboxDashBoardController.clearEmailUIAction();
|
||||||
|
} else if (action is PerformEmailActionInThreadDetailAction) {
|
||||||
|
if (action.presentationEmail.id != _currentEmailId) return;
|
||||||
|
pressEmailAction(
|
||||||
|
action.emailActionType,
|
||||||
|
action.presentationEmail,
|
||||||
|
);
|
||||||
|
} else if (action is CloseEmailInThreadDetailAction) {
|
||||||
|
if (action.emailId != _currentEmailId) return;
|
||||||
|
closeEmailView(context: currentContext);
|
||||||
|
for (var worker in obxListeners) {
|
||||||
|
worker.dispose();
|
||||||
|
}
|
||||||
|
Get.delete<SingleEmailController>(tag: _currentEmailId?.id.value);
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
ever(mailboxDashBoardController.viewState, (viewState) {
|
obxListeners.add(ever(mailboxDashBoardController.viewState, (viewState) {
|
||||||
viewState.map((success) {
|
viewState.map((success) {
|
||||||
if (success is UnsubscribeEmailSuccess) {
|
if (success is UnsubscribeEmailSuccess) {
|
||||||
emailUnsubscribe.value = null;
|
emailUnsubscribe.value = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}));
|
||||||
|
if (_threadDetailController != null) {
|
||||||
|
obxListeners.add(ever(
|
||||||
|
_threadDetailController!.currentExpandedEmailId,
|
||||||
|
(emailId) {
|
||||||
|
if (emailId == null || emailId != _currentEmailId) return;
|
||||||
|
|
||||||
|
_threadDetailController!.currentEmailLoaded.value = currentEmailLoaded.value;
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isListEmailContainSelectedEmail(PresentationEmail selectedEmail) {
|
void _handleOpenEmailDetailedView() {
|
||||||
return emailSupervisorController.currentListEmail.isNotEmpty
|
if (currentEmail == null) {
|
||||||
&& emailSupervisorController.currentListEmail.listEmailIds.contains(selectedEmail.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleOpenEmailDetailedView(PresentationEmail? selectedEmail) {
|
|
||||||
if (selectedEmail == null || _currentEmailId == selectedEmail.id) {
|
|
||||||
log('SingleEmailController::_handleOpenEmailDetailedView(): email unselected');
|
log('SingleEmailController::_handleOpenEmailDetailedView(): email unselected');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emailLoadedViewState.value = Right<Failure, Success>(GetEmailContentLoading());
|
emailLoadedViewState.value = Right<Failure, Success>(GetEmailContentLoading());
|
||||||
|
|
||||||
emailSupervisorController.updateNewCurrentListEmail();
|
|
||||||
_updateCurrentEmailId(selectedEmail.id);
|
|
||||||
_resetToOriginalValue();
|
_resetToOriginalValue();
|
||||||
|
|
||||||
if (isListEmailContainSelectedEmail(selectedEmail)) {
|
_createSingleEmailView(currentEmail!.id!);
|
||||||
_createMultipleEmailViewAsPageView(selectedEmail.id!);
|
|
||||||
} else {
|
|
||||||
_createSingleEmailView(selectedEmail.id!);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!selectedEmail.hasRead) {
|
if (!currentEmail!.hasRead) {
|
||||||
markAsEmailRead(selectedEmail, ReadActions.markAsRead, MarkReadAction.tap);
|
markAsEmailRead(currentEmail!, ReadActions.markAsRead, MarkReadAction.tap);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mailboxDashBoardController.listIdentities.isEmpty) {
|
if (mailboxDashBoardController.listIdentities.isEmpty) {
|
||||||
@@ -437,20 +446,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateCurrentEmailId(EmailId? emailId) {
|
|
||||||
_currentEmailId = emailId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _createMultipleEmailViewAsPageView(EmailId emailId) {
|
|
||||||
log('SingleEmailController::_createMultipleEmailViewAsPageView():');
|
|
||||||
emailSupervisorController.supportedPageView.value = true;
|
|
||||||
emailSupervisorController.createPageControllerAndJumpToEmailById(emailId);
|
|
||||||
_getEmailContentAction(emailId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _createSingleEmailView(EmailId emailId) {
|
void _createSingleEmailView(EmailId emailId) {
|
||||||
log('SingleEmailController::_createSingleEmailView():');
|
log('SingleEmailController::_createSingleEmailView():');
|
||||||
emailSupervisorController.supportedPageView.value = false;
|
|
||||||
_getEmailContentAction(emailId);
|
_getEmailContentAction(emailId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,7 +458,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
(failure) => null,
|
(failure) => null,
|
||||||
(success) {
|
(success) {
|
||||||
if (success is StartDownloadAttachmentForWeb && !success.previewerSupported) {
|
if (success is StartDownloadAttachmentForWeb && !success.previewerSupported) {
|
||||||
emailSupervisorController.mailboxDashBoardController.addDownloadTask(
|
mailboxDashBoardController.addDownloadTask(
|
||||||
DownloadTaskState(
|
DownloadTaskState(
|
||||||
taskId: success.taskId,
|
taskId: success.taskId,
|
||||||
attachment: success.attachment,
|
attachment: success.attachment,
|
||||||
@@ -480,7 +477,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
final percent = success.progress.round();
|
final percent = success.progress.round();
|
||||||
log('SingleEmailController::DownloadingAttachmentForWeb(): $percent%');
|
log('SingleEmailController::DownloadingAttachmentForWeb(): $percent%');
|
||||||
|
|
||||||
emailSupervisorController.mailboxDashBoardController.updateDownloadTask(
|
mailboxDashBoardController.updateDownloadTask(
|
||||||
success.taskId,
|
success.taskId,
|
||||||
(currentTask) {
|
(currentTask) {
|
||||||
final newTask = currentTask.copyWith(
|
final newTask = currentTask.copyWith(
|
||||||
@@ -491,7 +488,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
return newTask;
|
return newTask;
|
||||||
});
|
});
|
||||||
} else if (success is StartDownloadAllAttachmentsForWeb) {
|
} else if (success is StartDownloadAllAttachmentsForWeb) {
|
||||||
emailSupervisorController.mailboxDashBoardController.addDownloadTask(
|
mailboxDashBoardController.addDownloadTask(
|
||||||
DownloadTaskState(
|
DownloadTaskState(
|
||||||
taskId: success.taskId,
|
taskId: success.taskId,
|
||||||
attachment: success.attachment,
|
attachment: success.attachment,
|
||||||
@@ -510,7 +507,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
final percent = success.progress.round();
|
final percent = success.progress.round();
|
||||||
log('SingleEmailController::DownloadingAttachmentForWeb(): $percent%');
|
log('SingleEmailController::DownloadingAttachmentForWeb(): $percent%');
|
||||||
|
|
||||||
emailSupervisorController.mailboxDashBoardController.updateDownloadTask(
|
mailboxDashBoardController.updateDownloadTask(
|
||||||
success.taskId,
|
success.taskId,
|
||||||
(currentTask) {
|
(currentTask) {
|
||||||
final newTask = currentTask.copyWith(
|
final newTask = currentTask.copyWith(
|
||||||
@@ -590,59 +587,42 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _getEmailContentAction(EmailId emailId) {
|
void _getEmailContentAction(EmailId emailId) {
|
||||||
final emailLoaded = emailSupervisorController.getEmailInQueueByEmailId(emailId);
|
if (session != null && accountId != null) {
|
||||||
|
try {
|
||||||
|
final baseDownloadUrl = session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl);
|
||||||
|
TransformConfiguration transformConfiguration = PlatformInfo.isWeb
|
||||||
|
? TransformConfiguration.forPreviewEmailOnWeb()
|
||||||
|
: TransformConfiguration.forPreviewEmail();
|
||||||
|
|
||||||
if (emailLoaded != null) {
|
consumeState(_getEmailContentInteractor.execute(
|
||||||
consumeState(Stream.value(Right<Failure, Success>(
|
session!,
|
||||||
GetEmailContentSuccess(
|
accountId!,
|
||||||
htmlEmailContent: emailLoaded.htmlContent,
|
emailId,
|
||||||
attachments: emailLoaded.attachments,
|
baseDownloadUrl,
|
||||||
inlineImages: emailLoaded.inlineImages,
|
transformConfiguration
|
||||||
emailCurrent: emailLoaded.emailCurrent
|
));
|
||||||
)
|
} catch (e) {
|
||||||
)));
|
logError('SingleEmailController::_getEmailContentAction(): $e');
|
||||||
} else {
|
consumeState(Stream.value(Left(GetEmailContentFailure(
|
||||||
if (session != null && accountId != null) {
|
e,
|
||||||
try {
|
onRetry: e is UnknownUriException
|
||||||
final baseDownloadUrl = session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl);
|
? null
|
||||||
TransformConfiguration transformConfiguration = PlatformInfo.isWeb
|
: _getEmailContentInteractor.execute(
|
||||||
? TransformConfiguration.forPreviewEmailOnWeb()
|
session!,
|
||||||
: TransformConfiguration.forPreviewEmail();
|
accountId!,
|
||||||
|
emailId,
|
||||||
consumeState(_getEmailContentInteractor.execute(
|
session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl),
|
||||||
session!,
|
PlatformInfo.isWeb
|
||||||
accountId!,
|
? TransformConfiguration.forPreviewEmailOnWeb()
|
||||||
emailId,
|
: TransformConfiguration.forPreviewEmail(),
|
||||||
baseDownloadUrl,
|
),
|
||||||
transformConfiguration
|
))));
|
||||||
));
|
|
||||||
} catch (e) {
|
|
||||||
logError('SingleEmailController::_getEmailContentAction(): $e');
|
|
||||||
consumeState(Stream.value(Left(GetEmailContentFailure(
|
|
||||||
e,
|
|
||||||
onRetry: e is UnknownUriException
|
|
||||||
? null
|
|
||||||
: _getEmailContentInteractor.execute(
|
|
||||||
session!,
|
|
||||||
accountId!,
|
|
||||||
emailId,
|
|
||||||
session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl),
|
|
||||||
PlatformInfo.isWeb
|
|
||||||
? TransformConfiguration.forPreviewEmailOnWeb()
|
|
||||||
: TransformConfiguration.forPreviewEmail(),
|
|
||||||
),
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _getEmailContentOfflineSuccess(GetEmailContentFromCacheSuccess success) {
|
void _getEmailContentOfflineSuccess(GetEmailContentFromCacheSuccess success) {
|
||||||
emailLoadedViewState.value = Right<Failure, Success>(success);
|
emailLoadedViewState.value = Right<Failure, Success>(success);
|
||||||
if (emailSupervisorController.presentationEmailsLoaded.length > ThreadConstants.defaultLimit.value.toInt()) {
|
|
||||||
emailSupervisorController.popFirstEmailQueue();
|
|
||||||
}
|
|
||||||
emailSupervisorController.popEmailQueue(success.emailCurrent.id);
|
|
||||||
|
|
||||||
currentEmailLoaded.value = EmailLoaded(
|
currentEmailLoaded.value = EmailLoaded(
|
||||||
htmlContent: success.htmlEmailContent,
|
htmlContent: success.htmlEmailContent,
|
||||||
@@ -650,7 +630,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
inlineImages: List.of(success.inlineImages ?? []),
|
inlineImages: List.of(success.inlineImages ?? []),
|
||||||
emailCurrent: success.emailCurrent,
|
emailCurrent: success.emailCurrent,
|
||||||
);
|
);
|
||||||
emailSupervisorController.pushEmailQueue(currentEmailLoaded.value!);
|
_threadDetailController?.currentEmailLoaded.value = currentEmailLoaded.value;
|
||||||
|
|
||||||
if (success.emailCurrent.id == currentEmail?.id) {
|
if (success.emailCurrent.id == currentEmail?.id) {
|
||||||
attachments.value = success.attachments ?? [];
|
attachments.value = success.attachments ?? [];
|
||||||
@@ -683,10 +663,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
|
|
||||||
void _getEmailContentSuccess(GetEmailContentSuccess success) {
|
void _getEmailContentSuccess(GetEmailContentSuccess success) {
|
||||||
emailLoadedViewState.value = Right<Failure, Success>(success);
|
emailLoadedViewState.value = Right<Failure, Success>(success);
|
||||||
if (emailSupervisorController.presentationEmailsLoaded.length > ThreadConstants.defaultLimit.value.toInt()) {
|
|
||||||
emailSupervisorController.popFirstEmailQueue();
|
|
||||||
}
|
|
||||||
emailSupervisorController.popEmailQueue(success.emailCurrent?.id);
|
|
||||||
|
|
||||||
currentEmailLoaded.value = EmailLoaded(
|
currentEmailLoaded.value = EmailLoaded(
|
||||||
htmlContent: success.htmlEmailContent,
|
htmlContent: success.htmlEmailContent,
|
||||||
@@ -694,7 +670,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
inlineImages: List.of(success.inlineImages ?? []),
|
inlineImages: List.of(success.inlineImages ?? []),
|
||||||
emailCurrent: success.emailCurrent,
|
emailCurrent: success.emailCurrent,
|
||||||
);
|
);
|
||||||
emailSupervisorController.pushEmailQueue(currentEmailLoaded.value!);
|
_threadDetailController?.currentEmailLoaded.value = currentEmailLoaded.value;
|
||||||
|
|
||||||
if (success.emailCurrent?.id == currentEmail?.id) {
|
if (success.emailCurrent?.id == currentEmail?.id) {
|
||||||
attachments.value = success.attachments ?? [];
|
attachments.value = success.attachments ?? [];
|
||||||
@@ -745,6 +721,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
emailUnsubscribe.value = null;
|
emailUnsubscribe.value = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ((_threadDetailController?.emailIdsPresentation.keys.length ?? 0) > 1 == true) {
|
||||||
|
_jumpScrollViewToTopOfEmail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _jumpScrollViewToTopOfEmail() {
|
||||||
|
if (_currentEmailId == null) return;
|
||||||
|
_threadDetailController?.focusExpandedEmail(_currentEmailId!);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleUnsubscribe(String listUnsubscribe) {
|
void _handleUnsubscribe(String listUnsubscribe) {
|
||||||
@@ -809,9 +793,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleMarkAsEmailReadCompleted(ReadActions readActions) {
|
void _handleMarkAsEmailReadCompleted(ReadActions readActions) {
|
||||||
if (_currentEmailId != null) {
|
if (currentEmail?.id != null) {
|
||||||
mailboxDashBoardController.updateEmailFlagByEmailIds(
|
mailboxDashBoardController.updateEmailFlagByEmailIds(
|
||||||
[_currentEmailId!],
|
[currentEmail!.id!],
|
||||||
readAction: readActions,
|
readAction: readActions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -952,7 +936,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final downloadAllSupported = session!.isDownloadAllSupported(accountId);
|
final downloadAllSupported = session!.isDownloadAllSupported(accountId);
|
||||||
if (!downloadAllSupported || _currentEmailId == null) {
|
if (!downloadAllSupported) {
|
||||||
consumeState(Stream.value(Left(ExportAllAttachmentsFailure())));
|
consumeState(Stream.value(Left(ExportAllAttachmentsFailure())));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -960,7 +944,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
final baseDownloadAllUrl = session!.getDownloadAllCapability(accountId)!.endpoint!;
|
final baseDownloadAllUrl = session!.getDownloadAllCapability(accountId)!.endpoint!;
|
||||||
consumeState(_exportAllAttachmentsInteractor.execute(
|
consumeState(_exportAllAttachmentsInteractor.execute(
|
||||||
accountId!,
|
accountId!,
|
||||||
_currentEmailId!,
|
currentEmail!.id!,
|
||||||
baseDownloadAllUrl,
|
baseDownloadAllUrl,
|
||||||
outputFileName,
|
outputFileName,
|
||||||
cancelToken: cancelToken,
|
cancelToken: cancelToken,
|
||||||
@@ -1071,7 +1055,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final downloadAllSupported = session.isDownloadAllSupported(accountId);
|
final downloadAllSupported = session.isDownloadAllSupported(accountId);
|
||||||
final emailId = _currentEmailId;
|
final emailId = currentEmail?.id;
|
||||||
|
|
||||||
if (!downloadAllSupported || emailId == null) {
|
if (!downloadAllSupported || emailId == null) {
|
||||||
consumeState(Stream.value(Left(DownloadAllAttachmentsForWebFailure(
|
consumeState(Stream.value(Left(DownloadAllAttachmentsForWebFailure(
|
||||||
@@ -1454,10 +1438,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
final newEmail = currentEmail?.updateKeywords({
|
final newEmail = currentEmail?.updateKeywords({
|
||||||
KeyWordIdentifier.emailFlagged: success.markStarAction == MarkStarAction.markStar,
|
KeyWordIdentifier.emailFlagged: success.markStarAction == MarkStarAction.markStar,
|
||||||
});
|
});
|
||||||
mailboxDashBoardController.setSelectedEmail(newEmail);
|
|
||||||
|
|
||||||
final emailId = newEmail?.id;
|
final emailId = newEmail?.id;
|
||||||
if (emailId == null) return;
|
if (emailId == null) return;
|
||||||
|
_threadDetailController?.emailIdsPresentation[emailId] = newEmail;
|
||||||
mailboxDashBoardController.updateEmailFlagByEmailIds(
|
mailboxDashBoardController.updateEmailFlagByEmailIds(
|
||||||
[emailId],
|
[emailId],
|
||||||
markStarAction: success.markStarAction,
|
markStarAction: success.markStarAction,
|
||||||
@@ -1508,6 +1492,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
case EmailActionType.editAsNewEmail:
|
case EmailActionType.editAsNewEmail:
|
||||||
_editAsNewEmail(presentationEmail);
|
_editAsNewEmail(presentationEmail);
|
||||||
break;
|
break;
|
||||||
|
case EmailActionType.reply:
|
||||||
|
_replyEmail(presentationEmail);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1639,7 +1626,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentEmail == null || _currentEmailId == null) {
|
if (currentEmail == null) {
|
||||||
appToast.showToastErrorMessage(
|
appToast.showToastErrorMessage(
|
||||||
currentOverlayContext!,
|
currentOverlayContext!,
|
||||||
AppLocalizations.of(context).toastMessageCannotFoundEmailIdWhenSendReceipt);
|
AppLocalizations.of(context).toastMessageCannotFoundEmailIdWhenSendReceipt);
|
||||||
@@ -1700,16 +1687,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void closeEmailView({BuildContext? context}) {
|
void closeEmailView({BuildContext? context}) {
|
||||||
if (emailSupervisorController.supportedPageView.isTrue) {
|
|
||||||
emailSupervisorController.popEmailQueue(_currentEmailId);
|
|
||||||
emailSupervisorController.setCurrentEmailIndex(-1);
|
|
||||||
emailSupervisorController.disposePageViewController();
|
|
||||||
}
|
|
||||||
mailboxDashBoardController.clearSelectedEmail();
|
mailboxDashBoardController.clearSelectedEmail();
|
||||||
_updateCurrentEmailId(null);
|
|
||||||
_resetToOriginalValue(isEmailClosing: true);
|
_resetToOriginalValue(isEmailClosing: true);
|
||||||
_replaceBrowserHistory();
|
_replaceBrowserHistory();
|
||||||
if (mailboxDashBoardController.searchController.isSearchEmailRunning) {
|
if (mailboxDashBoardController.searchController.isSearchEmailRunning
|
||||||
|
|| getBinding<SearchEmailController>()?.searchIsRunning.value == true
|
||||||
|
) {
|
||||||
if (context != null && responsiveUtils.isWebDesktop(context)) {
|
if (context != null && responsiveUtils.isWebDesktop(context)) {
|
||||||
mailboxDashBoardController.dispatchRoute(DashboardRoutes.thread);
|
mailboxDashBoardController.dispatchRoute(DashboardRoutes.thread);
|
||||||
} else {
|
} else {
|
||||||
@@ -1723,8 +1706,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isOpenEmailNotMailboxFromRoute => emailSupervisorController.supportedPageView.isFalse
|
bool get isOpenEmailNotMailboxFromRoute => mailboxDashBoardController.selectedMailbox.value == null;
|
||||||
&& mailboxDashBoardController.selectedMailbox.value == null;
|
|
||||||
|
|
||||||
void _replaceBrowserHistory() {
|
void _replaceBrowserHistory() {
|
||||||
if (PlatformInfo.isWeb) {
|
if (PlatformInfo.isWeb) {
|
||||||
@@ -1761,17 +1743,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
mailboxDashBoardController.openComposer(ComposerArguments());
|
mailboxDashBoardController.openComposer(ComposerArguments());
|
||||||
break;
|
break;
|
||||||
case EmailActionType.reply:
|
case EmailActionType.reply:
|
||||||
mailboxDashBoardController.openComposer(
|
_replyEmail(presentationEmail);
|
||||||
ComposerArguments.replyEmail(
|
|
||||||
presentationEmail: presentationEmail,
|
|
||||||
content: currentEmailLoaded.value?.htmlContent ?? '',
|
|
||||||
inlineImages: currentEmailLoaded.value?.inlineImages ?? [],
|
|
||||||
mailboxRole: presentationEmail.mailboxContain?.role,
|
|
||||||
messageId: currentEmailLoaded.value?.emailCurrent?.messageId,
|
|
||||||
references: currentEmailLoaded.value?.emailCurrent?.references,
|
|
||||||
listPost: currentEmailLoaded.value?.emailCurrent?.listPost,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case EmailActionType.replyToList:
|
case EmailActionType.replyToList:
|
||||||
log('SingleEmailController::pressEmailAction:replyToList');
|
log('SingleEmailController::pressEmailAction:replyToList');
|
||||||
@@ -1860,15 +1832,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleScrollPhysicsPagerView(bool leftDirection) {
|
|
||||||
log('SingleEmailController::toggleScrollPhysicsPagerView():leftDirection: $leftDirection');
|
|
||||||
if (leftDirection) {
|
|
||||||
emailSupervisorController.moveToNextEmail();
|
|
||||||
} else {
|
|
||||||
emailSupervisorController.backToPreviousEmail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _storeOpenedEmailAction(Session? session, AccountId? accountId, DetailedEmail detailedEmail) async {
|
void _storeOpenedEmailAction(Session? session, AccountId? accountId, DetailedEmail detailedEmail) async {
|
||||||
if (session != null && accountId != null) {
|
if (session != null && accountId != null) {
|
||||||
consumeState(_storeOpenedEmailInteractor.execute(session, accountId, detailedEmail));
|
consumeState(_storeOpenedEmailInteractor.execute(session, accountId, detailedEmail));
|
||||||
@@ -1913,9 +1876,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
void _handleParseCalendarEventSuccess(ParseCalendarEventSuccess success) {
|
void _handleParseCalendarEventSuccess(ParseCalendarEventSuccess success) {
|
||||||
emailLoadedViewState.value = Right<Failure, Success>(success);
|
emailLoadedViewState.value = Right<Failure, Success>(success);
|
||||||
blobCalendarEvent.value = success.blobCalendarEventList.firstOrNull;
|
blobCalendarEvent.value = success.blobCalendarEventList.firstOrNull;
|
||||||
if (PlatformInfo.isMobile) {
|
|
||||||
_enableScrollPageView();
|
|
||||||
}
|
|
||||||
updateAttendanceStatus(success);
|
updateAttendanceStatus(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1924,10 +1884,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
emailContents.value = currentEmailLoaded.value?.htmlContent;
|
emailContents.value = currentEmailLoaded.value?.htmlContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _enableScrollPageView() {
|
|
||||||
emailSupervisorController.scrollPhysicsPageView.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void openNewTabAction(String link) {
|
void openNewTabAction(String link) {
|
||||||
AppUtils.launchLink(link);
|
AppUtils.launchLink(link);
|
||||||
}
|
}
|
||||||
@@ -1938,8 +1894,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void openAttachmentList(BuildContext context, List<Attachment> attachments) {
|
void openAttachmentList(BuildContext context, List<Attachment> attachments) {
|
||||||
|
final tag = _currentEmailId?.id.value;
|
||||||
if (responsiveUtils.isMobile(context)) {
|
if (responsiveUtils.isMobile(context)) {
|
||||||
(AttachmentListBottomSheetBuilder(context, attachments, imagePaths, _attachmentListScrollController)
|
(AttachmentListBottomSheetBuilder(context, attachments, imagePaths, _attachmentListScrollController, tag)
|
||||||
..onCloseButtonAction(() => popBack())
|
..onCloseButtonAction(() => popBack())
|
||||||
..onDownloadAttachmentFileAction((attachment) => handleDownloadAttachmentAction(context, attachment))
|
..onDownloadAttachmentFileAction((attachment) => handleDownloadAttachmentAction(context, attachment))
|
||||||
..onViewAttachmentFileAction((attachment) => handleViewAttachmentAction(context, attachment))
|
..onViewAttachmentFileAction((attachment) => handleViewAttachmentAction(context, attachment))
|
||||||
@@ -1963,6 +1920,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
onDownloadAllButtonAction: isDownloadAllSupported()
|
onDownloadAllButtonAction: isDownloadAllSupported()
|
||||||
? () => downloadAllAttachmentsForWeb('TwakeMail-${DateTime.now()}')
|
? () => downloadAllAttachmentsForWeb('TwakeMail-${DateTime.now()}')
|
||||||
: null,
|
: null,
|
||||||
|
singleEmailControllerTag: tag,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
|
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
|
||||||
@@ -2240,6 +2198,20 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _replyEmail(PresentationEmail presentationEmail) {
|
||||||
|
mailboxDashBoardController.openComposer(
|
||||||
|
ComposerArguments.replyEmail(
|
||||||
|
presentationEmail: presentationEmail,
|
||||||
|
content: currentEmailLoaded.value?.htmlContent ?? '',
|
||||||
|
inlineImages: currentEmailLoaded.value?.inlineImages ?? [],
|
||||||
|
mailboxRole: presentationEmail.mailboxContain?.role,
|
||||||
|
messageId: currentEmailLoaded.value?.emailCurrent?.messageId,
|
||||||
|
references: currentEmailLoaded.value?.emailCurrent?.references,
|
||||||
|
listPost: currentEmailLoaded.value?.emailCurrent?.listPost,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void handleDownloadAttachmentAction(
|
void handleDownloadAttachmentAction(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
Attachment attachment,
|
Attachment attachment,
|
||||||
|
|||||||
@@ -1,26 +1,32 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:core/presentation/constants/constants_ui.dart';
|
import 'package:core/presentation/constants/constants_ui.dart';
|
||||||
import 'package:core/presentation/extensions/color_extension.dart';
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
import 'package:core/presentation/views/button/tmail_button_widget.dart';
|
|
||||||
import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart';
|
import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart';
|
||||||
import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart';
|
import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart';
|
||||||
import 'package:core/utils/direction_utils.dart';
|
|
||||||
import 'package:core/utils/platform_info.dart';
|
import 'package:core/utils/platform_info.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart';
|
import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email.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:model/extensions/email_extension.dart';
|
||||||
import 'package:model/extensions/list_email_address_extension.dart';
|
import 'package:model/extensions/list_email_address_extension.dart';
|
||||||
import 'package:model/extensions/presentation_email_extension.dart';
|
import 'package:model/extensions/presentation_email_extension.dart';
|
||||||
import 'package:model/extensions/presentation_mailbox_extension.dart';
|
import 'package:model/extensions/presentation_mailbox_extension.dart';
|
||||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
||||||
|
import 'package:tmail_ui_user/features/base/widget/optional_expanded.dart';
|
||||||
|
import 'package:tmail_ui_user/features/base/widget/optional_scroll.dart';
|
||||||
import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart';
|
import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
|
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_event_extension.dart';
|
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_event_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/model/context_item_email_action.dart';
|
import 'package:tmail_ui_user/features/email/presentation/model/context_item_email_action.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';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_styles.dart';
|
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_styles.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_action_banner_widget.dart';
|
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_action_banner_widget.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_detail_widget.dart';
|
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_detail_widget.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_information_widget.dart';
|
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_information_widget.dart';
|
||||||
@@ -43,56 +49,45 @@ import 'package:tmail_ui_user/main/utils/app_utils.dart';
|
|||||||
|
|
||||||
class EmailView extends GetWidget<SingleEmailController> {
|
class EmailView extends GetWidget<SingleEmailController> {
|
||||||
|
|
||||||
const EmailView({Key? key}) : super(key: key);
|
const EmailView({
|
||||||
|
super.key,
|
||||||
|
this.isInsideThreadDetailView = false,
|
||||||
|
this.emailId,
|
||||||
|
this.isFirstEmailInThreadDetail = false,
|
||||||
|
this.threadSubject,
|
||||||
|
this.onToggleThreadDetailCollapseExpand,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool isInsideThreadDetailView;
|
||||||
|
final EmailId? emailId;
|
||||||
|
final bool isFirstEmailInThreadDetail;
|
||||||
|
final String? threadSubject;
|
||||||
|
final VoidCallback? onToggleThreadDetailCollapseExpand;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get tag => emailId?.id.value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SelectionArea(
|
return SelectionArea(
|
||||||
child: Scaffold(
|
child: ColoredBox(
|
||||||
backgroundColor: controller.responsiveUtils.isWebDesktop(context)
|
color: controller.responsiveUtils.isWebDesktop(context)
|
||||||
? AppColor.colorBgDesktop
|
? AppColor.colorBgDesktop
|
||||||
: Colors.white,
|
: Colors.white,
|
||||||
appBar: PlatformInfo.isIOS
|
child: SafeArea(
|
||||||
? PreferredSize(
|
|
||||||
preferredSize: const Size(double.infinity, 100),
|
|
||||||
child: Obx(() {
|
|
||||||
if (controller.currentEmail != null) {
|
|
||||||
return SafeArea(
|
|
||||||
top: false,
|
|
||||||
bottom: false,
|
|
||||||
child: EmailViewAppBarWidget(
|
|
||||||
key: const Key('email_view_app_bar_widget'),
|
|
||||||
presentationEmail: controller.currentEmail!,
|
|
||||||
mailboxContain: _getMailboxContain(controller.currentEmail!),
|
|
||||||
isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning,
|
|
||||||
onBackAction: () => controller.closeEmailView(context: context),
|
|
||||||
onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action),
|
|
||||||
onMoreActionClick: (presentationEmail, position) => _handleMoreEmailAction(context: context, presentationEmail: presentationEmail, position: position)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
body: SafeArea(
|
|
||||||
right: controller.responsiveUtils.isLandscapeMobile(context),
|
right: controller.responsiveUtils.isLandscapeMobile(context),
|
||||||
left: controller.responsiveUtils.isLandscapeMobile(context),
|
left: controller.responsiveUtils.isLandscapeMobile(context),
|
||||||
bottom: !PlatformInfo.isIOS,
|
bottom: !PlatformInfo.isIOS,
|
||||||
child: Container(
|
child: Container(
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
decoration: controller.responsiveUtils.isWebDesktop(context)
|
decoration: _getDecorationEmailView(context),
|
||||||
? const BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
|
||||||
color: Colors.white)
|
|
||||||
: const BoxDecoration(color: Colors.white),
|
|
||||||
margin: _getMarginEmailView(context),
|
margin: _getMarginEmailView(context),
|
||||||
child: Obx(() {
|
child: Obx(() {
|
||||||
final currentEmail = controller.currentEmail;
|
final currentEmailListener = Rxn(controller.currentEmail);
|
||||||
|
final currentEmail = currentEmailListener.value;
|
||||||
if (currentEmail != null) {
|
if (currentEmail != null) {
|
||||||
return Column(children: [
|
return Column(children: [
|
||||||
if (!PlatformInfo.isIOS)
|
if (!isInsideThreadDetailView)
|
||||||
Obx(() => EmailViewAppBarWidget(
|
Obx(() => EmailViewAppBarWidget(
|
||||||
key: const Key('email_view_app_bar_widget'),
|
key: const Key('email_view_app_bar_widget'),
|
||||||
presentationEmail: currentEmail,
|
presentationEmail: currentEmail,
|
||||||
@@ -100,10 +95,20 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning,
|
isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning,
|
||||||
onBackAction: () => controller.closeEmailView(context: context),
|
onBackAction: () => controller.closeEmailView(context: context),
|
||||||
onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action),
|
onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action),
|
||||||
onMoreActionClick: (presentationEmail, position) => _handleMoreEmailAction(context: context, presentationEmail: presentationEmail, position: position),
|
onMoreActionClick: (presentationEmail, position) => _handleMoreEmailAction(
|
||||||
optionsWidget: PlatformInfo.isWeb && controller.emailSupervisorController.supportedPageView.isTrue
|
context: context,
|
||||||
? _buildNavigatorPageViewWidgets(context)
|
presentationEmail: presentationEmail,
|
||||||
|
position: position,
|
||||||
|
emailLoaded: controller.currentEmailLoaded.value,
|
||||||
|
),
|
||||||
|
supportBackAction: !isInsideThreadDetailView,
|
||||||
|
appBarDecoration: isInsideThreadDetailView
|
||||||
|
? const BoxDecoration(border: Border(bottom: BorderSide(
|
||||||
|
color: AppColor.colorDividerEmailView,
|
||||||
|
)))
|
||||||
: null,
|
: null,
|
||||||
|
emailLoaded: controller.currentEmailLoaded.value,
|
||||||
|
isInsideThreadDetailView: isInsideThreadDetailView,
|
||||||
)),
|
)),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
final vacation = controller.mailboxDashBoardController.vacationResponse.value;
|
final vacation = controller.mailboxDashBoardController.vacationResponse.value;
|
||||||
@@ -124,123 +129,63 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Expanded(
|
OptionalExpanded(
|
||||||
|
expandedEnabled: !isInsideThreadDetailView,
|
||||||
child: LayoutBuilder(builder: (context, constraints) {
|
child: LayoutBuilder(builder: (context, constraints) {
|
||||||
return Obx(() {
|
if (PlatformInfo.isMobile) {
|
||||||
bool supportedPageView = controller.emailSupervisorController.supportedPageView.isTrue && PlatformInfo.isMobile;
|
return OptionalScroll(
|
||||||
final currentListEmail = controller.emailSupervisorController.currentListEmail;
|
scrollEnabled: !isInsideThreadDetailView,
|
||||||
|
child: Container(
|
||||||
if (supportedPageView) {
|
width: double.infinity,
|
||||||
return PageView.builder(
|
alignment: Alignment.center,
|
||||||
physics: controller.emailSupervisorController.scrollPhysicsPageView.value,
|
color: Colors.white,
|
||||||
itemCount: currentListEmail.length,
|
child: Obx(() => _buildEmailMessage(
|
||||||
allowImplicitScrolling: true,
|
context: context,
|
||||||
controller: controller.emailSupervisorController.pageController,
|
presentationEmail: currentEmail,
|
||||||
onPageChanged: controller.emailSupervisorController.onPageChanged,
|
calendarEvent: controller.calendarEvent,
|
||||||
itemBuilder: (context, index) {
|
maxBodyHeight: constraints.maxHeight
|
||||||
final currentEmail = currentListEmail[index];
|
))
|
||||||
if (PlatformInfo.isMobile) {
|
)
|
||||||
return SingleChildScrollView(
|
);
|
||||||
physics : const ClampingScrollPhysics(),
|
} else {
|
||||||
child: Container(
|
return Obx(() {
|
||||||
width: double.infinity,
|
final calendarEvent = controller.calendarEvent;
|
||||||
alignment: Alignment.center,
|
if (currentEmail.hasCalendarEvent && calendarEvent != null) {
|
||||||
color: Colors.white,
|
return Padding(
|
||||||
child: Obx(() => _buildEmailMessage(
|
padding: const EdgeInsetsDirectional.symmetric(horizontal: 4),
|
||||||
context: context,
|
child: OptionalScroll(
|
||||||
presentationEmail: currentEmail,
|
scrollEnabled: !isInsideThreadDetailView,
|
||||||
calendarEvent: controller.calendarEvent,
|
child: Container(
|
||||||
maxBodyHeight: constraints.maxHeight
|
width: double.infinity,
|
||||||
))
|
alignment: Alignment.center,
|
||||||
|
color: Colors.white,
|
||||||
|
child: _buildEmailMessage(
|
||||||
|
context: context,
|
||||||
|
presentationEmail: currentEmail,
|
||||||
|
calendarEvent: calendarEvent,
|
||||||
|
emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(),
|
||||||
|
maxBodyHeight: constraints.maxHeight
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
} else {
|
),
|
||||||
return Obx(() {
|
|
||||||
final calendarEvent = controller.calendarEvent;
|
|
||||||
if (currentEmail.hasCalendarEvent && calendarEvent != null) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsetsDirectional.symmetric(horizontal: 4),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
physics : const ClampingScrollPhysics(),
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
color: Colors.white,
|
|
||||||
child: _buildEmailMessage(
|
|
||||||
context: context,
|
|
||||||
presentationEmail: currentEmail,
|
|
||||||
calendarEvent: calendarEvent,
|
|
||||||
emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return _buildEmailMessage(
|
|
||||||
context: context,
|
|
||||||
presentationEmail: currentEmail,
|
|
||||||
maxBodyHeight: constraints.maxHeight
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (PlatformInfo.isMobile) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
physics : const ClampingScrollPhysics(),
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
color: Colors.white,
|
|
||||||
child: Obx(() => _buildEmailMessage(
|
|
||||||
context: context,
|
|
||||||
presentationEmail: currentEmail,
|
|
||||||
calendarEvent: controller.calendarEvent,
|
|
||||||
maxBodyHeight: constraints.maxHeight
|
|
||||||
))
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Obx(() {
|
return _buildEmailMessage(
|
||||||
final calendarEvent = controller.calendarEvent;
|
context: context,
|
||||||
if (currentEmail.hasCalendarEvent && calendarEvent != null) {
|
presentationEmail: currentEmail,
|
||||||
return Padding(
|
maxBodyHeight: constraints.maxHeight
|
||||||
padding: const EdgeInsetsDirectional.symmetric(horizontal: 4),
|
);
|
||||||
child: SingleChildScrollView(
|
|
||||||
physics : const ClampingScrollPhysics(),
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
color: Colors.white,
|
|
||||||
child: _buildEmailMessage(
|
|
||||||
context: context,
|
|
||||||
presentationEmail: currentEmail,
|
|
||||||
calendarEvent: calendarEvent,
|
|
||||||
emailAddressSender: currentEmail.listEmailAddressSender.getListAddress(),
|
|
||||||
maxBodyHeight: constraints.maxHeight
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return _buildEmailMessage(
|
|
||||||
context: context,
|
|
||||||
presentationEmail: currentEmail,
|
|
||||||
maxBodyHeight: constraints.maxHeight
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
final emailLoaded = controller.currentEmailLoaded.value;
|
final emailLoaded = controller.currentEmailLoaded.value;
|
||||||
|
|
||||||
if (emailLoaded == null) return const SizedBox.shrink();
|
if (emailLoaded == null || isInsideThreadDetailView) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
return EmailViewBottomBarWidget(
|
return EmailViewBottomBarWidget(
|
||||||
key: const Key('email_view_button_bar'),
|
key: const Key('email_view_button_bar'),
|
||||||
@@ -263,52 +208,48 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
EdgeInsetsGeometry? _getMarginEmailView(BuildContext context) {
|
BoxDecoration _getDecorationEmailView(BuildContext context) {
|
||||||
if (PlatformInfo.isWeb) {
|
if (controller.responsiveUtils.isWebDesktop(context) && !isInsideThreadDetailView) {
|
||||||
if (controller.responsiveUtils.isDesktop(context)) {
|
return const BoxDecoration(
|
||||||
return const EdgeInsetsDirectional.only(
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
end: 16,
|
color: Colors.white,
|
||||||
bottom: 16
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const EdgeInsets.symmetric(vertical: 16);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (controller.currentEmail == null || !isInsideThreadDetailView) {
|
||||||
|
return const BoxDecoration(color: Colors.white);
|
||||||
|
}
|
||||||
|
|
||||||
|
return const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border(top: BorderSide(
|
||||||
|
color: AppColor.colorDividerEmailView,
|
||||||
|
width: 0.5,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
EdgeInsetsGeometry? _getMarginEmailView(BuildContext context) {
|
||||||
|
if (PlatformInfo.isMobile) return null;
|
||||||
|
|
||||||
|
if (isInsideThreadDetailView) {
|
||||||
|
return EdgeInsets.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!controller.responsiveUtils.isDesktop(context)) {
|
||||||
|
return EdgeInsets.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
return const EdgeInsetsDirectional.only(
|
||||||
|
end: 16,
|
||||||
|
bottom: 16
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
PresentationMailbox? _getMailboxContain(PresentationEmail currentEmail) {
|
PresentationMailbox? _getMailboxContain(PresentationEmail currentEmail) {
|
||||||
return currentEmail.findMailboxContain(controller.mailboxDashBoardController.mapMailboxById);
|
return currentEmail.findMailboxContain(controller.mailboxDashBoardController.mapMailboxById);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildNavigatorPageViewWidgets(BuildContext context) {
|
|
||||||
return [
|
|
||||||
if (controller.emailSupervisorController.nextEmailActivated)
|
|
||||||
TMailButtonWidget.fromIcon(
|
|
||||||
icon: DirectionUtils.isDirectionRTLByLanguage(context)
|
|
||||||
? controller.imagePaths.icOlder
|
|
||||||
: controller.imagePaths.icNewer,
|
|
||||||
iconColor: EmailViewStyles.iconColor,
|
|
||||||
iconSize: EmailViewStyles.pageViewIconSize,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
tooltipMessage: AppLocalizations.of(context).newer,
|
|
||||||
onTapActionCallback: controller.emailSupervisorController.moveToNextEmail
|
|
||||||
),
|
|
||||||
if (controller.emailSupervisorController.previousEmailActivated)
|
|
||||||
TMailButtonWidget.fromIcon(
|
|
||||||
icon: DirectionUtils.isDirectionRTLByLanguage(context)
|
|
||||||
? controller.imagePaths.icNewer
|
|
||||||
: controller.imagePaths.icOlder,
|
|
||||||
iconColor: EmailViewStyles.iconColor,
|
|
||||||
iconSize: EmailViewStyles.pageViewIconSize,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
tooltipMessage: AppLocalizations.of(context).older,
|
|
||||||
onTapActionCallback: controller.emailSupervisorController.backToPreviousEmail
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildEmailMessage({
|
Widget _buildEmailMessage({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required PresentationEmail presentationEmail,
|
required PresentationEmail presentationEmail,
|
||||||
@@ -318,8 +259,16 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
}) {
|
}) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
EmailSubjectWidget(presentationEmail: presentationEmail),
|
if (!isInsideThreadDetailView || isFirstEmailInThreadDetail)
|
||||||
|
EmailSubjectWidget(
|
||||||
|
presentationEmail: presentationEmail.copyWith(
|
||||||
|
subject: threadSubject,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const SizedBox(height: 16),
|
||||||
Obx(() => InformationSenderAndReceiverBuilder(
|
Obx(() => InformationSenderAndReceiverBuilder(
|
||||||
emailSelected: presentationEmail,
|
emailSelected: presentationEmail,
|
||||||
imagePaths: controller.imagePaths,
|
imagePaths: controller.imagePaths,
|
||||||
@@ -329,6 +278,16 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
maxBodyHeight: maxBodyHeight,
|
maxBodyHeight: maxBodyHeight,
|
||||||
openEmailAddressDetailAction: controller.openEmailAddressDialog,
|
openEmailAddressDetailAction: controller.openEmailAddressDialog,
|
||||||
onEmailActionClick: (presentationEmail, actionType) => controller.handleEmailAction(context, presentationEmail, actionType),
|
onEmailActionClick: (presentationEmail, actionType) => controller.handleEmailAction(context, presentationEmail, actionType),
|
||||||
|
isInsideThreadDetailView: isInsideThreadDetailView,
|
||||||
|
emailLoaded: controller.currentEmailLoaded.value,
|
||||||
|
onMoreActionClick: (email, position) => _handleMoreEmailAction(
|
||||||
|
context: context,
|
||||||
|
presentationEmail: email,
|
||||||
|
position: position,
|
||||||
|
emailLoaded: controller.currentEmailLoaded.value,
|
||||||
|
),
|
||||||
|
onToggleThreadDetailCollapseExpand: onToggleThreadDetailCollapseExpand,
|
||||||
|
onTapAvatarActionClick: onToggleThreadDetailCollapseExpand,
|
||||||
)),
|
)),
|
||||||
Obx(() => MailUnsubscribedBanner(
|
Obx(() => MailUnsubscribedBanner(
|
||||||
presentationEmail: controller.currentEmail,
|
presentationEmail: controller.currentEmail,
|
||||||
@@ -349,6 +308,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
onTapShowAllAttachmentFile: () => controller.openAttachmentList(context, controller.attachments),
|
onTapShowAllAttachmentFile: () => controller.openAttachmentList(context, controller.attachments),
|
||||||
showDownloadAllAttachmentsButton: controller.downloadAllButtonIsEnabled(),
|
showDownloadAllAttachmentsButton: controller.downloadAllButtonIsEnabled(),
|
||||||
onTapDownloadAllButton: () => controller.handleDownloadAllAttachmentsAction(context, 'TwakeMail-${DateTime.now()}'),
|
onTapDownloadAllButton: () => controller.handleDownloadAllAttachmentsAction(context, 'TwakeMail-${DateTime.now()}'),
|
||||||
|
singleEmailControllerTag: tag,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
@@ -408,7 +368,8 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
final allEmailContents = controller.emailContents.value ?? '';
|
final allEmailContents = controller.emailContents.value ?? '';
|
||||||
|
|
||||||
if (PlatformInfo.isWeb) {
|
if (PlatformInfo.isWeb) {
|
||||||
return Expanded(
|
return OptionalExpanded(
|
||||||
|
expandedEnabled: !isInsideThreadDetailView,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EmailViewStyles.emailContentPadding,
|
padding: EmailViewStyles.emailContentPadding,
|
||||||
child: LayoutBuilder(builder: (context, constraints) {
|
child: LayoutBuilder(builder: (context, constraints) {
|
||||||
@@ -416,8 +377,12 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
HtmlContentViewerOnWeb(
|
HtmlContentViewerOnWeb(
|
||||||
|
key: ValueKey(tag),
|
||||||
widthContent: constraints.maxWidth,
|
widthContent: constraints.maxWidth,
|
||||||
heightContent: constraints.maxHeight,
|
heightContent: min(
|
||||||
|
constraints.maxHeight,
|
||||||
|
EmailViewStyles.initialHtmlViewHeight,
|
||||||
|
),
|
||||||
contentHtml: allEmailContents,
|
contentHtml: allEmailContents,
|
||||||
mailtoDelegate: controller.openMailToLink,
|
mailtoDelegate: controller.openMailToLink,
|
||||||
direction: AppUtils.getCurrentDirection(context),
|
direction: AppUtils.getCurrentDirection(context),
|
||||||
@@ -462,9 +427,8 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
useDefaultFont: true,
|
useDefaultFont: true,
|
||||||
maxHtmlContentHeight: ConstantsUI.htmlContentMaxHeight,
|
maxHtmlContentHeight: ConstantsUI.htmlContentMaxHeight,
|
||||||
onMailtoDelegateAction: controller.openMailToLink,
|
onMailtoDelegateAction: controller.openMailToLink,
|
||||||
onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView,
|
|
||||||
onLoadWidthHtmlViewer: controller.emailSupervisorController.updateScrollPhysicPageView,
|
|
||||||
onHtmlContentClippedAction: controller.onHtmlContentClippedAction,
|
onHtmlContentClippedAction: controller.onHtmlContentClippedAction,
|
||||||
|
keepAlive: isInsideThreadDetailView,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -503,8 +467,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
contentPadding: 0,
|
contentPadding: 0,
|
||||||
useDefaultFont: true,
|
useDefaultFont: true,
|
||||||
onMailtoDelegateAction: controller.openMailToLink,
|
onMailtoDelegateAction: controller.openMailToLink,
|
||||||
onScrollHorizontalEnd: controller.toggleScrollPhysicsPagerView,
|
keepAlive: isInsideThreadDetailView,
|
||||||
onLoadWidthHtmlViewer: controller.emailSupervisorController.updateScrollPhysicPageView,
|
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -531,10 +494,17 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
return usernameEvent.isNotEmpty && titleEvent.isNotEmpty;
|
return usernameEvent.isNotEmpty && titleEvent.isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool canDeletePermanently(PresentationEmail email) {
|
||||||
|
return email.mailboxContain?.isTrash
|
||||||
|
?? email.mailboxContain?.isSpam
|
||||||
|
?? false;
|
||||||
|
}
|
||||||
|
|
||||||
void _handleMoreEmailAction({
|
void _handleMoreEmailAction({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required PresentationEmail presentationEmail,
|
required PresentationEmail presentationEmail,
|
||||||
RelativeRect? position
|
RelativeRect? position,
|
||||||
|
EmailLoaded? emailLoaded,
|
||||||
}) {
|
}) {
|
||||||
final mailboxContain = controller.getMailboxContain(presentationEmail);
|
final mailboxContain = controller.getMailboxContain(presentationEmail);
|
||||||
final isChildOfTeamMailboxes = mailboxContain?.isChildOfTeamMailboxes ?? false;
|
final isChildOfTeamMailboxes = mailboxContain?.isChildOfTeamMailboxes ?? false;
|
||||||
@@ -548,6 +518,25 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
PlatformInfo.isWeb && PlatformInfo.isCanvasKit;
|
PlatformInfo.isWeb && PlatformInfo.isCanvasKit;
|
||||||
|
|
||||||
final emailActions = [
|
final emailActions = [
|
||||||
|
if (isInsideThreadDetailView) ...[
|
||||||
|
EmailActionType.forward,
|
||||||
|
if (presentationEmail.getCountMailAddressWithoutMe(controller.session?.username.value ?? '') > 1)
|
||||||
|
EmailActionType.replyAll,
|
||||||
|
if (EmailUtils.isReplyToListEnabled(emailLoaded?.emailCurrent?.listPost ?? ''))
|
||||||
|
EmailActionType.replyToList,
|
||||||
|
if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit)
|
||||||
|
EmailActionType.printAll,
|
||||||
|
if (controller.responsiveUtils.isMobile(context))
|
||||||
|
EmailActionType.moveToMailbox,
|
||||||
|
if (!controller.responsiveUtils.isDesktop(context)) ...[
|
||||||
|
presentationEmail.hasStarred
|
||||||
|
? EmailActionType.unMarkAsStarred
|
||||||
|
: EmailActionType.markAsStarred,
|
||||||
|
canDeletePermanently(presentationEmail)
|
||||||
|
? EmailActionType.deletePermanently
|
||||||
|
: EmailActionType.moveToTrash,
|
||||||
|
],
|
||||||
|
],
|
||||||
EmailActionType.markAsUnread,
|
EmailActionType.markAsUnread,
|
||||||
if (!isChildOfTeamMailboxes)
|
if (!isChildOfTeamMailboxes)
|
||||||
isSpam ? EmailActionType.unSpam : EmailActionType.moveToSpam,
|
isSpam ? EmailActionType.unSpam : EmailActionType.moveToSpam,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
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/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';
|
||||||
@@ -21,4 +22,25 @@ extension EmailExtension on Email {
|
|||||||
return SMimeSignatureStatus.notSigned;
|
return SMimeSignatureStatus.notSigned;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool inSentMailbox(MailboxId sentMailboxId) {
|
||||||
|
return mailboxIds?[sentMailboxId] == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fromMe(String ownEmailAddress) {
|
||||||
|
return from?.any(
|
||||||
|
(emailAdress) => emailAdress.email == ownEmailAddress
|
||||||
|
) == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool recipientsHasMe(String ownEmailAddress) {
|
||||||
|
final recipients = {
|
||||||
|
...to ?? {},
|
||||||
|
...cc ?? {},
|
||||||
|
...bcc ?? {},
|
||||||
|
};
|
||||||
|
return recipients.any(
|
||||||
|
(emailAdress) => emailAdress.email == ownEmailAddress
|
||||||
|
) == true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ class EmailViewAppBarWidgetStyles {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static const Color bottomBorderColor = AppColor.colorDividerHorizontal;
|
static const Color bottomBorderColor = AppColor.colorDividerEmailView;
|
||||||
static const Color backgroundColor = Colors.white;
|
static const Color backgroundColor = Colors.white;
|
||||||
static const Color iconColor = AppColor.steelGrayA540;
|
static const Color iconColor = AppColor.steelGrayA540;
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ class EmailViewAppBarWidgetStyles {
|
|||||||
} else if (responsiveUtils.isPortraitTablet(context) || responsiveUtils.isLandscapeTablet(context)) {
|
} else if (responsiveUtils.isPortraitTablet(context) || responsiveUtils.isLandscapeTablet(context)) {
|
||||||
return const EdgeInsetsDirectional.only(top: 40, start: 16, end: 16, bottom: 4);
|
return const EdgeInsetsDirectional.only(top: 40, start: 16, end: 16, bottom: 4);
|
||||||
} else {
|
} else {
|
||||||
return const EdgeInsetsDirectional.only(top: 60, start: 16, end: 16, bottom: 4);
|
return const EdgeInsetsDirectional.only(start: 16, end: 16, bottom: 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static const EdgeInsetsGeometry padding = EdgeInsetsDirectional.symmetric(horizontal: 16);
|
static const EdgeInsetsGeometry padding = EdgeInsetsDirectional.symmetric(horizontal: 16);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class EmailViewStyles {
|
|||||||
static const double mobileContentHorizontalMargin = 16;
|
static const double mobileContentHorizontalMargin = 16;
|
||||||
static const double mobileContentVerticalMargin = 12;
|
static const double mobileContentVerticalMargin = 12;
|
||||||
static double pageViewIconSize = IconUtils.defaultIconSize;
|
static double pageViewIconSize = IconUtils.defaultIconSize;
|
||||||
|
static const double initialHtmlViewHeight = 200;
|
||||||
|
|
||||||
static const Color iconColor = AppColor.steelGrayA540;
|
static const Color iconColor = AppColor.steelGrayA540;
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class AttachmentItemWidget extends StatelessWidget {
|
|||||||
final Attachment attachment;
|
final Attachment attachment;
|
||||||
final OnDownloadAttachmentFileAction? downloadAttachmentAction;
|
final OnDownloadAttachmentFileAction? downloadAttachmentAction;
|
||||||
final OnViewAttachmentFileAction? viewAttachmentAction;
|
final OnViewAttachmentFileAction? viewAttachmentAction;
|
||||||
|
final String? singleEmailControllerTag;
|
||||||
|
|
||||||
final _imagePaths = Get.find<ImagePaths>();
|
final _imagePaths = Get.find<ImagePaths>();
|
||||||
final _responsiveUtils = Get.find<ResponsiveUtils>();
|
final _responsiveUtils = Get.find<ResponsiveUtils>();
|
||||||
@@ -30,13 +31,14 @@ class AttachmentItemWidget extends StatelessWidget {
|
|||||||
required this.attachment,
|
required this.attachment,
|
||||||
this.downloadAttachmentAction,
|
this.downloadAttachmentAction,
|
||||||
this.viewAttachmentAction,
|
this.viewAttachmentAction,
|
||||||
|
this.singleEmailControllerTag,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Obx(
|
return Obx(
|
||||||
() {
|
() {
|
||||||
final controller = Get.find<SingleEmailController>();
|
final controller = Get.find<SingleEmailController>(tag: singleEmailControllerTag);
|
||||||
final attachmentsViewState = controller.attachmentsViewState;
|
final attachmentsViewState = controller.attachmentsViewState;
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
if (attachment.blobId != null) {
|
if (attachment.blobId != null) {
|
||||||
|
|||||||
+3
@@ -21,6 +21,7 @@ class AttachmentListBottomSheetBodyBuilder extends StatelessWidget {
|
|||||||
final OnViewAttachmentFileAction? onViewAttachmentFileAction;
|
final OnViewAttachmentFileAction? onViewAttachmentFileAction;
|
||||||
final OnCancelButtonAction? onCancelButtonAction;
|
final OnCancelButtonAction? onCancelButtonAction;
|
||||||
final OnCloseButtonAction? onCloseButtonAction;
|
final OnCloseButtonAction? onCloseButtonAction;
|
||||||
|
final String? singleEmailControllerTag;
|
||||||
|
|
||||||
const AttachmentListBottomSheetBodyBuilder({
|
const AttachmentListBottomSheetBodyBuilder({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -33,6 +34,7 @@ class AttachmentListBottomSheetBodyBuilder extends StatelessWidget {
|
|||||||
this.onViewAttachmentFileAction,
|
this.onViewAttachmentFileAction,
|
||||||
this.onCancelButtonAction,
|
this.onCancelButtonAction,
|
||||||
this.onCloseButtonAction,
|
this.onCloseButtonAction,
|
||||||
|
this.singleEmailControllerTag,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -105,6 +107,7 @@ class AttachmentListBottomSheetBodyBuilder extends StatelessWidget {
|
|||||||
attachment: attachments[index],
|
attachment: attachments[index],
|
||||||
downloadAttachmentAction: onDownloadAttachmentFileAction,
|
downloadAttachmentAction: onDownloadAttachmentFileAction,
|
||||||
viewAttachmentAction: onViewAttachmentFileAction,
|
viewAttachmentAction: onViewAttachmentFileAction,
|
||||||
|
singleEmailControllerTag: singleEmailControllerTag,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) {
|
separatorBuilder: (context, index) {
|
||||||
|
|||||||
+3
@@ -15,6 +15,7 @@ class AttachmentListBottomSheetBuilder {
|
|||||||
final List<Attachment> _attachments;
|
final List<Attachment> _attachments;
|
||||||
final ImagePaths _imagePaths;
|
final ImagePaths _imagePaths;
|
||||||
final ScrollController _scrollController;
|
final ScrollController _scrollController;
|
||||||
|
final String? _singleEmailControllerTag;
|
||||||
|
|
||||||
late double _statusBarHeight;
|
late double _statusBarHeight;
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ class AttachmentListBottomSheetBuilder {
|
|||||||
this._attachments,
|
this._attachments,
|
||||||
this._imagePaths,
|
this._imagePaths,
|
||||||
this._scrollController,
|
this._scrollController,
|
||||||
|
this._singleEmailControllerTag,
|
||||||
) {
|
) {
|
||||||
_statusBarHeight = Get.statusBarHeight / MediaQuery.of(_context).devicePixelRatio;
|
_statusBarHeight = Get.statusBarHeight / MediaQuery.of(_context).devicePixelRatio;
|
||||||
}
|
}
|
||||||
@@ -70,6 +72,7 @@ class AttachmentListBottomSheetBuilder {
|
|||||||
onViewAttachmentFileAction: _onViewAttachmentFileAction,
|
onViewAttachmentFileAction: _onViewAttachmentFileAction,
|
||||||
onCancelButtonAction: _onCancelButtonAction,
|
onCancelButtonAction: _onCancelButtonAction,
|
||||||
onCloseButtonAction: _onCloseButtonAction,
|
onCloseButtonAction: _onCloseButtonAction,
|
||||||
|
singleEmailControllerTag: _singleEmailControllerTag,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -22,6 +22,7 @@ class AttachmentListDialogBodyBuilder extends StatelessWidget {
|
|||||||
final OnViewAttachmentFileAction? onViewAttachmentFileAction;
|
final OnViewAttachmentFileAction? onViewAttachmentFileAction;
|
||||||
final OnCancelButtonAction? onCancelButtonAction;
|
final OnCancelButtonAction? onCancelButtonAction;
|
||||||
final OnCloseButtonAction? onCloseButtonAction;
|
final OnCloseButtonAction? onCloseButtonAction;
|
||||||
|
final String? singleEmailControllerTag;
|
||||||
|
|
||||||
const AttachmentListDialogBodyBuilder({
|
const AttachmentListDialogBodyBuilder({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -35,7 +36,8 @@ class AttachmentListDialogBodyBuilder extends StatelessWidget {
|
|||||||
this.onDownloadAttachmentFileAction,
|
this.onDownloadAttachmentFileAction,
|
||||||
this.onViewAttachmentFileAction,
|
this.onViewAttachmentFileAction,
|
||||||
this.onCancelButtonAction,
|
this.onCancelButtonAction,
|
||||||
this.onCloseButtonAction
|
this.onCloseButtonAction,
|
||||||
|
this.singleEmailControllerTag,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -113,6 +115,7 @@ class AttachmentListDialogBodyBuilder extends StatelessWidget {
|
|||||||
attachment: attachments[index],
|
attachment: attachments[index],
|
||||||
downloadAttachmentAction: onDownloadAttachmentFileAction,
|
downloadAttachmentAction: onDownloadAttachmentFileAction,
|
||||||
viewAttachmentAction: onViewAttachmentFileAction,
|
viewAttachmentAction: onViewAttachmentFileAction,
|
||||||
|
singleEmailControllerTag: singleEmailControllerTag,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
separatorBuilder: (context, index) {
|
separatorBuilder: (context, index) {
|
||||||
|
|||||||
+3
@@ -22,6 +22,7 @@ class AttachmentListDialogBuilder extends StatelessWidget {
|
|||||||
final OnViewAttachmentFileAction? onViewAttachmentFileAction;
|
final OnViewAttachmentFileAction? onViewAttachmentFileAction;
|
||||||
final OnCancelButtonAction? onCancelButtonAction;
|
final OnCancelButtonAction? onCancelButtonAction;
|
||||||
final OnCloseButtonAction? onCloseButtonAction;
|
final OnCloseButtonAction? onCloseButtonAction;
|
||||||
|
final String? singleEmailControllerTag;
|
||||||
|
|
||||||
const AttachmentListDialogBuilder({
|
const AttachmentListDialogBuilder({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -37,6 +38,7 @@ class AttachmentListDialogBuilder extends StatelessWidget {
|
|||||||
this.onViewAttachmentFileAction,
|
this.onViewAttachmentFileAction,
|
||||||
this.onCancelButtonAction,
|
this.onCancelButtonAction,
|
||||||
this.onCloseButtonAction,
|
this.onCloseButtonAction,
|
||||||
|
this.singleEmailControllerTag,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -61,6 +63,7 @@ class AttachmentListDialogBuilder extends StatelessWidget {
|
|||||||
onViewAttachmentFileAction: onViewAttachmentFileAction,
|
onViewAttachmentFileAction: onViewAttachmentFileAction,
|
||||||
onCancelButtonAction: onCancelButtonAction,
|
onCancelButtonAction: onCancelButtonAction,
|
||||||
onCloseButtonAction: onCloseButtonAction,
|
onCloseButtonAction: onCloseButtonAction,
|
||||||
|
singleEmailControllerTag: singleEmailControllerTag,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -19,6 +19,7 @@ class AttachmentListItemWidget extends StatelessWidget {
|
|||||||
final Attachment attachment;
|
final Attachment attachment;
|
||||||
final OnDownloadAttachmentFileAction? downloadAttachmentAction;
|
final OnDownloadAttachmentFileAction? downloadAttachmentAction;
|
||||||
final OnViewAttachmentFileAction? viewAttachmentAction;
|
final OnViewAttachmentFileAction? viewAttachmentAction;
|
||||||
|
final String? singleEmailControllerTag;
|
||||||
|
|
||||||
final _imagePaths = Get.find<ImagePaths>();
|
final _imagePaths = Get.find<ImagePaths>();
|
||||||
|
|
||||||
@@ -27,13 +28,14 @@ class AttachmentListItemWidget extends StatelessWidget {
|
|||||||
required this.attachment,
|
required this.attachment,
|
||||||
this.downloadAttachmentAction,
|
this.downloadAttachmentAction,
|
||||||
this.viewAttachmentAction,
|
this.viewAttachmentAction,
|
||||||
|
this.singleEmailControllerTag,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Obx(
|
return Obx(
|
||||||
() {
|
() {
|
||||||
final controller = Get.find<SingleEmailController>();
|
final controller = Get.find<SingleEmailController>(tag: singleEmailControllerTag);
|
||||||
final attachmentsViewState = controller.attachmentsViewState;
|
final attachmentsViewState = controller.attachmentsViewState;
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
if (attachment.blobId != null) {
|
if (attachment.blobId != null) {
|
||||||
|
|||||||
+7
-3
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:core/presentation/constants/constants_ui.dart';
|
import 'package:core/presentation/constants/constants_ui.dart';
|
||||||
import 'package:core/presentation/extensions/color_extension.dart';
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
@@ -9,6 +10,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_styles.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/styles/event_description_detail_widget_styles.dart';
|
import 'package:tmail_ui_user/features/email/presentation/styles/event_description_detail_widget_styles.dart';
|
||||||
import 'package:tmail_ui_user/main/utils/app_utils.dart';
|
import 'package:tmail_ui_user/main/utils/app_utils.dart';
|
||||||
|
|
||||||
@@ -44,15 +46,17 @@ class EventBodyContentWidget extends StatelessWidget {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
if (PlatformInfo.isWeb)
|
if (PlatformInfo.isWeb)
|
||||||
Container(
|
Padding(
|
||||||
constraints: const BoxConstraints(maxHeight: EventDescriptionDetailWidgetStyles.maxHeight),
|
|
||||||
padding: const EdgeInsetsDirectional.only(end: EventDescriptionDetailWidgetStyles.webContentPadding),
|
padding: const EdgeInsetsDirectional.only(end: EventDescriptionDetailWidgetStyles.webContentPadding),
|
||||||
child: LayoutBuilder(builder: (context, constraints) {
|
child: LayoutBuilder(builder: (context, constraints) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
HtmlContentViewerOnWeb(
|
HtmlContentViewerOnWeb(
|
||||||
widthContent: constraints.maxWidth,
|
widthContent: constraints.maxWidth,
|
||||||
heightContent: constraints.maxHeight,
|
heightContent: min(
|
||||||
|
constraints.maxHeight,
|
||||||
|
EmailViewStyles.initialHtmlViewHeight,
|
||||||
|
),
|
||||||
contentHtml: content,
|
contentHtml: content,
|
||||||
mailtoDelegate: onMailtoDelegateAction,
|
mailtoDelegate: onMailtoDelegateAction,
|
||||||
direction: AppUtils.getCurrentDirection(context),
|
direction: AppUtils.getCurrentDirection(context),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class DraggableAttachmentItemWidget extends StatelessWidget{
|
|||||||
final OnDragAttachmentEnd? onDragEnd;
|
final OnDragAttachmentEnd? onDragEnd;
|
||||||
final OnDownloadAttachmentFileAction? downloadAttachmentAction;
|
final OnDownloadAttachmentFileAction? downloadAttachmentAction;
|
||||||
final OnViewAttachmentFileAction? viewAttachmentAction;
|
final OnViewAttachmentFileAction? viewAttachmentAction;
|
||||||
|
final String? singleEmailControllerTag;
|
||||||
|
|
||||||
const DraggableAttachmentItemWidget({
|
const DraggableAttachmentItemWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -22,6 +23,7 @@ class DraggableAttachmentItemWidget extends StatelessWidget{
|
|||||||
this.onDragEnd,
|
this.onDragEnd,
|
||||||
this.downloadAttachmentAction,
|
this.downloadAttachmentAction,
|
||||||
this.viewAttachmentAction,
|
this.viewAttachmentAction,
|
||||||
|
this.singleEmailControllerTag,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -35,6 +37,7 @@ class DraggableAttachmentItemWidget extends StatelessWidget{
|
|||||||
attachment: attachment,
|
attachment: attachment,
|
||||||
downloadAttachmentAction: downloadAttachmentAction,
|
downloadAttachmentAction: downloadAttachmentAction,
|
||||||
viewAttachmentAction: viewAttachmentAction,
|
viewAttachmentAction: viewAttachmentAction,
|
||||||
|
singleEmailControllerTag: singleEmailControllerTag,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class EmailAttachmentsWidget extends StatelessWidget {
|
|||||||
final OnTapActionCallback? onTapShowAllAttachmentFile;
|
final OnTapActionCallback? onTapShowAllAttachmentFile;
|
||||||
final bool showDownloadAllAttachmentsButton;
|
final bool showDownloadAllAttachmentsButton;
|
||||||
final OnTapActionCallback? onTapDownloadAllButton;
|
final OnTapActionCallback? onTapDownloadAllButton;
|
||||||
|
final String? singleEmailControllerTag;
|
||||||
|
|
||||||
const EmailAttachmentsWidget({
|
const EmailAttachmentsWidget({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -40,6 +41,7 @@ class EmailAttachmentsWidget extends StatelessWidget {
|
|||||||
this.onTapShowAllAttachmentFile,
|
this.onTapShowAllAttachmentFile,
|
||||||
this.showDownloadAllAttachmentsButton = false,
|
this.showDownloadAllAttachmentsButton = false,
|
||||||
this.onTapDownloadAllButton,
|
this.onTapDownloadAllButton,
|
||||||
|
this.singleEmailControllerTag,
|
||||||
});
|
});
|
||||||
|
|
||||||
Widget _buildMoreAttachmentButton(
|
Widget _buildMoreAttachmentButton(
|
||||||
@@ -108,17 +110,19 @@ class EmailAttachmentsWidget extends StatelessWidget {
|
|||||||
children: attachmentDisplayed.map((attachment) {
|
children: attachmentDisplayed.map((attachment) {
|
||||||
if (PlatformInfo.isWeb) {
|
if (PlatformInfo.isWeb) {
|
||||||
return DraggableAttachmentItemWidget(
|
return DraggableAttachmentItemWidget(
|
||||||
attachment: attachment,
|
attachment: attachment,
|
||||||
onDragStarted: onDragStarted,
|
onDragStarted: onDragStarted,
|
||||||
onDragEnd: onDragEnd,
|
onDragEnd: onDragEnd,
|
||||||
downloadAttachmentAction: downloadAttachmentAction,
|
downloadAttachmentAction: downloadAttachmentAction,
|
||||||
viewAttachmentAction: viewAttachmentAction,
|
viewAttachmentAction: viewAttachmentAction,
|
||||||
|
singleEmailControllerTag: singleEmailControllerTag,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return AttachmentItemWidget(
|
return AttachmentItemWidget(
|
||||||
attachment: attachment,
|
attachment: attachment,
|
||||||
downloadAttachmentAction: downloadAttachmentAction,
|
downloadAttachmentAction: downloadAttachmentAction,
|
||||||
viewAttachmentAction: viewAttachmentAction,
|
viewAttachmentAction: viewAttachmentAction,
|
||||||
|
singleEmailControllerTag: singleEmailControllerTag,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}).toList(),
|
}).toList(),
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class _EmailReceiverWidgetState extends State<EmailReceiverWidget> {
|
|||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Flexible(
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(maxHeight: _maxHeight),
|
constraints: BoxConstraints(maxHeight: _maxHeight),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
@@ -149,7 +149,7 @@ class _EmailReceiverWidgetState extends State<EmailReceiverWidget> {
|
|||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Flexible(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ class EmailSenderBuilder extends StatelessWidget {
|
|||||||
|
|
||||||
final EmailAddress emailAddress;
|
final EmailAddress emailAddress;
|
||||||
final OnOpenEmailAddressDetailAction? openEmailAddressDetailAction;
|
final OnOpenEmailAddressDetailAction? openEmailAddressDetailAction;
|
||||||
|
final bool showSenderEmail;
|
||||||
|
|
||||||
const EmailSenderBuilder({
|
const EmailSenderBuilder({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.emailAddress,
|
required this.emailAddress,
|
||||||
this.openEmailAddressDetailAction,
|
this.openEmailAddressDetailAction,
|
||||||
}) : super(key: key);
|
this.showSenderEmail = true,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -27,7 +29,7 @@ class EmailSenderBuilder extends StatelessWidget {
|
|||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (emailAddress.displayName.isNotEmpty)
|
if (emailAddress.displayName.isNotEmpty)
|
||||||
@@ -38,32 +40,30 @@ class EmailSenderBuilder extends StatelessWidget {
|
|||||||
AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress);
|
AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress);
|
||||||
},
|
},
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
padding: const EdgeInsets.all(3),
|
padding: EdgeInsets.zero,
|
||||||
customStyle: ThemeUtils.textStyleHeadingHeadingSmall(
|
customStyle: ThemeUtils.textStyleHeadingHeadingSmall(
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
),
|
),
|
||||||
overflow: CommonTextStyle.defaultTextOverFlow,
|
overflow: CommonTextStyle.defaultTextOverFlow,
|
||||||
softWrap: CommonTextStyle.defaultSoftWrap
|
softWrap: CommonTextStyle.defaultSoftWrap
|
||||||
),
|
),
|
||||||
Padding(
|
if (showSenderEmail)
|
||||||
padding: const EdgeInsets.only(top: 2),
|
MaterialTextButton(
|
||||||
child: MaterialTextButton(
|
|
||||||
label: '<${emailAddress.emailAddress}>',
|
label: '<${emailAddress.emailAddress}>',
|
||||||
onTap: () => openEmailAddressDetailAction?.call(context, emailAddress),
|
onTap: () => openEmailAddressDetailAction?.call(context, emailAddress),
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress);
|
AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress);
|
||||||
},
|
},
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
padding: const EdgeInsets.all(3),
|
padding: EdgeInsets.zero,
|
||||||
customStyle: ThemeUtils.textStyleBodyBody1(
|
customStyle: ThemeUtils.textStyleBodyBody1(
|
||||||
color: AppColor.steelGray400,
|
color: AppColor.steelGray400,
|
||||||
),
|
),
|
||||||
overflow: CommonTextStyle.defaultTextOverFlow,
|
overflow: CommonTextStyle.defaultTextOverFlow,
|
||||||
softWrap: CommonTextStyle.defaultSoftWrap
|
softWrap: CommonTextStyle.defaultSoftWrap
|
||||||
),
|
)
|
||||||
)
|
|
||||||
]
|
]
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,9 @@ class EmailSubjectWidget extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
presentationEmail.getEmailTitle(),
|
presentationEmail.getEmailTitle(),
|
||||||
maxLines: EmailSubjectStyles.maxLines,
|
maxLines: EmailSubjectStyles.maxLines,
|
||||||
style: ThemeUtils.textStyleHeadingH5(color: Colors.black)
|
style: ThemeUtils.textStyleHeadingH5(color: Colors.black).copyWith(
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'package:model/email/email_action_type.dart';
|
|||||||
import 'package:model/email/presentation_email.dart';
|
import 'package:model/email/presentation_email.dart';
|
||||||
import 'package:model/extensions/presentation_mailbox_extension.dart';
|
import 'package:model/extensions/presentation_mailbox_extension.dart';
|
||||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
|
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_app_bar_widget_styles.dart';
|
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_app_bar_widget_styles.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_back_button.dart';
|
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_back_button.dart';
|
||||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||||
@@ -19,7 +19,6 @@ typedef OnMoreActionClick = void Function(PresentationEmail, RelativeRect?);
|
|||||||
class EmailViewAppBarWidget extends StatelessWidget {
|
class EmailViewAppBarWidget extends StatelessWidget {
|
||||||
final _imagePaths = Get.find<ImagePaths>();
|
final _imagePaths = Get.find<ImagePaths>();
|
||||||
final _responsiveUtils = Get.find<ResponsiveUtils>();
|
final _responsiveUtils = Get.find<ResponsiveUtils>();
|
||||||
final _singleEmailController = Get.find<SingleEmailController>();
|
|
||||||
|
|
||||||
final PresentationEmail presentationEmail;
|
final PresentationEmail presentationEmail;
|
||||||
final List<Widget>? optionsWidget;
|
final List<Widget>? optionsWidget;
|
||||||
@@ -28,29 +27,45 @@ class EmailViewAppBarWidget extends StatelessWidget {
|
|||||||
final VoidCallback onBackAction;
|
final VoidCallback onBackAction;
|
||||||
final OnEmailActionClick? onEmailActionClick;
|
final OnEmailActionClick? onEmailActionClick;
|
||||||
final OnMoreActionClick? onMoreActionClick;
|
final OnMoreActionClick? onMoreActionClick;
|
||||||
|
final bool supportBackAction;
|
||||||
|
final BoxDecoration? appBarDecoration;
|
||||||
|
final EmailLoaded? emailLoaded;
|
||||||
|
final bool isInsideThreadDetailView;
|
||||||
|
final double? height;
|
||||||
|
final EdgeInsetsGeometry? iconPadding;
|
||||||
|
final EdgeInsetsGeometry? iconMargin;
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
EmailViewAppBarWidget({
|
EmailViewAppBarWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.presentationEmail,
|
required this.presentationEmail,
|
||||||
required this.onBackAction,
|
required this.onBackAction,
|
||||||
required this.isSearchActivated,
|
required this.isSearchActivated,
|
||||||
|
required this.emailLoaded,
|
||||||
this.mailboxContain,
|
this.mailboxContain,
|
||||||
this.onEmailActionClick,
|
this.onEmailActionClick,
|
||||||
this.onMoreActionClick,
|
this.onMoreActionClick,
|
||||||
this.optionsWidget,
|
this.optionsWidget,
|
||||||
|
this.supportBackAction = true,
|
||||||
|
this.appBarDecoration,
|
||||||
|
this.isInsideThreadDetailView = false,
|
||||||
|
this.height,
|
||||||
|
this.iconPadding,
|
||||||
|
this.iconMargin,
|
||||||
|
this.padding,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final child = LayoutBuilder(builder: (context, constraints) {
|
final child = LayoutBuilder(builder: (context, constraints) {
|
||||||
return Container(
|
return Container(
|
||||||
height: PlatformInfo.isIOS
|
height: height ?? (PlatformInfo.isIOS
|
||||||
? EmailViewAppBarWidgetStyles.heightIOS(context, _responsiveUtils)
|
? EmailViewAppBarWidgetStyles.heightIOS(context, _responsiveUtils)
|
||||||
: EmailViewAppBarWidgetStyles.height,
|
: EmailViewAppBarWidgetStyles.height),
|
||||||
padding: PlatformInfo.isIOS
|
padding: padding ?? (PlatformInfo.isIOS
|
||||||
? EmailViewAppBarWidgetStyles.paddingIOS(context, _responsiveUtils)
|
? EmailViewAppBarWidgetStyles.paddingIOS(context, _responsiveUtils)
|
||||||
: EmailViewAppBarWidgetStyles.padding,
|
: EmailViewAppBarWidgetStyles.padding),
|
||||||
decoration: const BoxDecoration(
|
decoration: appBarDecoration ?? const BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
color: EmailViewAppBarWidgetStyles.bottomBorderColor,
|
color: EmailViewAppBarWidgetStyles.bottomBorderColor,
|
||||||
@@ -63,89 +78,30 @@ class EmailViewAppBarWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
color: EmailViewAppBarWidgetStyles.backgroundColor,
|
color: EmailViewAppBarWidgetStyles.backgroundColor,
|
||||||
),
|
),
|
||||||
child: Row(children: [
|
child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [
|
||||||
if (_supportDisplayMailboxNameTitle(context))
|
if (_supportDisplayMailboxNameTitle(context) && supportBackAction)
|
||||||
EmailViewBackButton(
|
Expanded(
|
||||||
imagePaths: _imagePaths,
|
child: Align(
|
||||||
onBackAction: onBackAction,
|
alignment: Alignment.centerLeft,
|
||||||
mailboxContain: mailboxContain,
|
child: EmailViewBackButton(
|
||||||
isSearchActivated: isSearchActivated,
|
imagePaths: _imagePaths,
|
||||||
maxWidth: constraints.maxWidth,
|
onBackAction: onBackAction,
|
||||||
|
mailboxContain: mailboxContain,
|
||||||
|
isSearchActivated: isSearchActivated,
|
||||||
|
maxWidth: constraints.maxWidth,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
if (optionsWidget != null) ... optionsWidget!,
|
if (optionsWidget != null) ... optionsWidget!,
|
||||||
TMailButtonWidget.fromIcon(
|
..._buildActionButtons(
|
||||||
icon: _imagePaths.icMoveEmail,
|
appLocalizations: AppLocalizations.of(context),
|
||||||
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
isScreenWithShortestSide: _responsiveUtils.isScreenWithShortestSide(context),
|
||||||
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
isResponsiveMobile: _responsiveUtils.isMobile(context),
|
||||||
tooltipMessage: AppLocalizations.of(context).move_message,
|
isResponsiveDesktop: _responsiveUtils.isDesktop(context),
|
||||||
backgroundColor: Colors.transparent,
|
),
|
||||||
onTapActionCallback: () => onEmailActionClick?.call(presentationEmail, EmailActionType.moveToMailbox)
|
]
|
||||||
),
|
|
||||||
TMailButtonWidget.fromIcon(
|
|
||||||
icon: presentationEmail.hasStarred
|
|
||||||
? _imagePaths.icStar
|
|
||||||
: _imagePaths.icUnStar,
|
|
||||||
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
|
||||||
iconColor: presentationEmail.hasStarred
|
|
||||||
? null
|
|
||||||
: EmailViewAppBarWidgetStyles.iconColor,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
tooltipMessage: presentationEmail.hasStarred
|
|
||||||
? AppLocalizations.of(context).not_starred
|
|
||||||
: AppLocalizations.of(context).mark_as_starred,
|
|
||||||
onTapActionCallback: () => onEmailActionClick?.call(
|
|
||||||
presentationEmail,
|
|
||||||
presentationEmail.hasStarred ? EmailActionType.unMarkAsStarred : EmailActionType.markAsStarred
|
|
||||||
)
|
|
||||||
),
|
|
||||||
if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit)
|
|
||||||
Obx(() => AbsorbPointer(
|
|
||||||
absorbing: _singleEmailController.currentEmailLoaded.value == null,
|
|
||||||
child: TMailButtonWidget.fromIcon(
|
|
||||||
icon: _imagePaths.icPrinter,
|
|
||||||
iconSize: EmailViewAppBarWidgetStyles.deleteButtonIconSize,
|
|
||||||
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
tooltipMessage: AppLocalizations.of(context).printAll,
|
|
||||||
onTapActionCallback: () => onEmailActionClick?.call(
|
|
||||||
presentationEmail,
|
|
||||||
EmailActionType.printAll,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
TMailButtonWidget.fromIcon(
|
|
||||||
icon: _imagePaths.icDeleteComposer,
|
|
||||||
iconSize: EmailViewAppBarWidgetStyles.deleteButtonIconSize,
|
|
||||||
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
tooltipMessage: canDeletePermanently
|
|
||||||
? AppLocalizations.of(context).delete_permanently
|
|
||||||
: AppLocalizations.of(context).move_to_trash,
|
|
||||||
onTapActionCallback: () {
|
|
||||||
if (canDeletePermanently) {
|
|
||||||
onEmailActionClick?.call(presentationEmail, EmailActionType.deletePermanently);
|
|
||||||
} else {
|
|
||||||
onEmailActionClick?.call(presentationEmail, EmailActionType.moveToTrash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
TMailButtonWidget.fromIcon(
|
|
||||||
icon: _imagePaths.icMoreVertical,
|
|
||||||
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
|
||||||
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
tooltipMessage: AppLocalizations.of(context).more,
|
|
||||||
onTapActionCallback: _responsiveUtils.isScreenWithShortestSide(context)
|
|
||||||
? () => onMoreActionClick?.call(presentationEmail, null)
|
|
||||||
: null,
|
|
||||||
onTapActionAtPositionCallback: !_responsiveUtils.isScreenWithShortestSide(context)
|
|
||||||
? (position) => onMoreActionClick?.call(presentationEmail, position)
|
|
||||||
: null
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
@@ -154,7 +110,7 @@ class EmailViewAppBarWidget extends StatelessWidget {
|
|||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: false,
|
canPop: false,
|
||||||
onPopInvokedWithResult: (_, __) {
|
onPopInvokedWithResult: (_, __) {
|
||||||
if (!PlatformInfo.isAndroid) return;
|
if (PlatformInfo.isAndroid) return;
|
||||||
onBackAction();
|
onBackAction();
|
||||||
},
|
},
|
||||||
child: child,
|
child: child,
|
||||||
@@ -178,4 +134,137 @@ class EmailViewAppBarWidget extends StatelessWidget {
|
|||||||
bool get canDeletePermanently {
|
bool get canDeletePermanently {
|
||||||
return mailboxContain?.isTrash == true || mailboxContain?.isSpam == true;
|
return mailboxContain?.isTrash == true || mailboxContain?.isSpam == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _getReplyButton(AppLocalizations appLocalizations) => TMailButtonWidget.fromIcon(
|
||||||
|
icon: _imagePaths.icReply,
|
||||||
|
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||||
|
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||||
|
tooltipMessage: appLocalizations.reply,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
onTapActionCallback: () => onEmailActionClick?.call(
|
||||||
|
presentationEmail,
|
||||||
|
EmailActionType.reply,
|
||||||
|
),
|
||||||
|
padding: iconPadding,
|
||||||
|
margin: iconMargin,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _getPrintButton(AppLocalizations appLocalizations) => AbsorbPointer(
|
||||||
|
absorbing: emailLoaded == null,
|
||||||
|
child: TMailButtonWidget.fromIcon(
|
||||||
|
icon: _imagePaths.icPrinter,
|
||||||
|
iconSize: EmailViewAppBarWidgetStyles.deleteButtonIconSize,
|
||||||
|
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
tooltipMessage: appLocalizations.printAll,
|
||||||
|
onTapActionCallback: () => onEmailActionClick?.call(
|
||||||
|
presentationEmail,
|
||||||
|
EmailActionType.printAll,
|
||||||
|
),
|
||||||
|
padding: iconPadding,
|
||||||
|
margin: iconMargin,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _getMoveEmailButton(AppLocalizations appLocalizations) => TMailButtonWidget.fromIcon(
|
||||||
|
icon: _imagePaths.icMoveEmail,
|
||||||
|
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||||
|
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||||
|
tooltipMessage: appLocalizations.move_message,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
onTapActionCallback: () => onEmailActionClick?.call(
|
||||||
|
presentationEmail,
|
||||||
|
EmailActionType.moveToMailbox,
|
||||||
|
),
|
||||||
|
padding: iconPadding,
|
||||||
|
margin: iconMargin,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _getMarkStarButton(AppLocalizations applocalizations) => TMailButtonWidget.fromIcon(
|
||||||
|
icon: presentationEmail.hasStarred
|
||||||
|
? _imagePaths.icStar
|
||||||
|
: _imagePaths.icUnStar,
|
||||||
|
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||||
|
iconColor: presentationEmail.hasStarred
|
||||||
|
? null
|
||||||
|
: EmailViewAppBarWidgetStyles.iconColor,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
tooltipMessage: presentationEmail.hasStarred
|
||||||
|
? applocalizations.not_starred
|
||||||
|
: applocalizations.mark_as_starred,
|
||||||
|
onTapActionCallback: () => onEmailActionClick?.call(
|
||||||
|
presentationEmail,
|
||||||
|
presentationEmail.hasStarred
|
||||||
|
? EmailActionType.unMarkAsStarred
|
||||||
|
: EmailActionType.markAsStarred
|
||||||
|
),
|
||||||
|
padding: iconPadding,
|
||||||
|
margin: iconMargin,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _getDeleteButton(AppLocalizations applocalizations) => TMailButtonWidget.fromIcon(
|
||||||
|
icon: _imagePaths.icDeleteComposer,
|
||||||
|
iconSize: EmailViewAppBarWidgetStyles.deleteButtonIconSize,
|
||||||
|
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
tooltipMessage: canDeletePermanently
|
||||||
|
? applocalizations.delete_permanently
|
||||||
|
: applocalizations.move_to_trash,
|
||||||
|
onTapActionCallback: () {
|
||||||
|
if (canDeletePermanently) {
|
||||||
|
onEmailActionClick?.call(presentationEmail, EmailActionType.deletePermanently);
|
||||||
|
} else {
|
||||||
|
onEmailActionClick?.call(presentationEmail, EmailActionType.moveToTrash);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
padding: iconPadding,
|
||||||
|
margin: iconMargin,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _getMoreButton(
|
||||||
|
AppLocalizations applocalizations,
|
||||||
|
bool isScreenWithShortestSide,
|
||||||
|
) => TMailButtonWidget.fromIcon(
|
||||||
|
icon: _imagePaths.icMoreVertical,
|
||||||
|
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||||
|
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
tooltipMessage: applocalizations.more,
|
||||||
|
onTapActionCallback: isScreenWithShortestSide
|
||||||
|
? () => onMoreActionClick?.call(presentationEmail, null)
|
||||||
|
: null,
|
||||||
|
onTapActionAtPositionCallback: !isScreenWithShortestSide
|
||||||
|
? (position) => onMoreActionClick?.call(presentationEmail, position)
|
||||||
|
: null,
|
||||||
|
padding: iconPadding,
|
||||||
|
margin: iconMargin,
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildActionButtons({
|
||||||
|
required AppLocalizations appLocalizations,
|
||||||
|
required bool isScreenWithShortestSide,
|
||||||
|
required bool isResponsiveMobile,
|
||||||
|
required bool isResponsiveDesktop,
|
||||||
|
}) {
|
||||||
|
if (!isInsideThreadDetailView) {
|
||||||
|
return [
|
||||||
|
_getPrintButton(appLocalizations),
|
||||||
|
_getMoveEmailButton(appLocalizations),
|
||||||
|
_getMarkStarButton(appLocalizations),
|
||||||
|
_getDeleteButton(appLocalizations),
|
||||||
|
_getMoreButton(appLocalizations, isScreenWithShortestSide),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
_getReplyButton(appLocalizations),
|
||||||
|
if (!isResponsiveMobile)
|
||||||
|
_getMoveEmailButton(appLocalizations),
|
||||||
|
if (isResponsiveDesktop) ...[
|
||||||
|
_getMarkStarButton(appLocalizations),
|
||||||
|
_getDeleteButton(appLocalizations),
|
||||||
|
],
|
||||||
|
_getMoreButton(appLocalizations, isScreenWithShortestSide),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,8 @@ class EmailViewBottomBarWidget extends StatelessWidget {
|
|||||||
final EmailLoaded emailLoaded;
|
final EmailLoaded emailLoaded;
|
||||||
final String userName;
|
final String userName;
|
||||||
final OnEmailActionCallback emailActionCallback;
|
final OnEmailActionCallback emailActionCallback;
|
||||||
|
final BoxDecoration? bottomBarDecoration;
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
const EmailViewBottomBarWidget({
|
const EmailViewBottomBarWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -30,6 +32,8 @@ class EmailViewBottomBarWidget extends StatelessWidget {
|
|||||||
required this.emailLoaded,
|
required this.emailLoaded,
|
||||||
required this.userName,
|
required this.userName,
|
||||||
required this.emailActionCallback,
|
required this.emailActionCallback,
|
||||||
|
this.bottomBarDecoration,
|
||||||
|
this.padding,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -40,7 +44,7 @@ class EmailViewBottomBarWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: bottomBarDecoration ?? const BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
top: BorderSide(
|
top: BorderSide(
|
||||||
color: EmailViewBottomBarWidgetStyles.topBorderColor,
|
color: EmailViewBottomBarWidgetStyles.topBorderColor,
|
||||||
@@ -53,7 +57,7 @@ class EmailViewBottomBarWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
color: EmailViewBottomBarWidgetStyles.backgroundColor
|
color: EmailViewBottomBarWidgetStyles.backgroundColor
|
||||||
),
|
),
|
||||||
padding: EmailViewBottomBarWidgetStyles.padding,
|
padding: padding ?? EmailViewBottomBarWidgetStyles.padding,
|
||||||
child: IntrinsicHeight(
|
child: IntrinsicHeight(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
+96
-43
@@ -1,14 +1,17 @@
|
|||||||
import 'package:core/presentation/extensions/color_extension.dart';
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
import 'package:core/presentation/resources/image_paths.dart';
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
|
import 'package:core/presentation/utils/icon_utils.dart';
|
||||||
import 'package:core/presentation/utils/responsive_utils.dart';
|
import 'package:core/presentation/utils/responsive_utils.dart';
|
||||||
import 'package:core/presentation/utils/theme_utils.dart';
|
import 'package:core/presentation/utils/theme_utils.dart';
|
||||||
import 'package:core/presentation/views/button/tmail_button_widget.dart';
|
import 'package:core/presentation/views/button/tmail_button_widget.dart';
|
||||||
|
import 'package:core/presentation/views/image/avatar_builder.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
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:model/email/presentation_email.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/base/widget/email_avatar_builder.dart';
|
import 'package:tmail_ui_user/features/base/widget/email_avatar_builder.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart';
|
import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.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/widgets/email_receiver_widget.dart';
|
import 'package:tmail_ui_user/features/email/presentation/widgets/email_receiver_widget.dart';
|
||||||
@@ -27,6 +30,12 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
|||||||
final OnEmailActionClick? onEmailActionClick;
|
final OnEmailActionClick? onEmailActionClick;
|
||||||
final double? maxBodyHeight;
|
final double? maxBodyHeight;
|
||||||
final SMimeSignatureStatus? sMimeStatus;
|
final SMimeSignatureStatus? sMimeStatus;
|
||||||
|
final bool isInsideThreadDetailView;
|
||||||
|
final EmailLoaded? emailLoaded;
|
||||||
|
final OnMoreActionClick? onMoreActionClick;
|
||||||
|
final bool showRecipients;
|
||||||
|
final VoidCallback? onToggleThreadDetailCollapseExpand;
|
||||||
|
final OnTapAvatarActionClick? onTapAvatarActionClick;
|
||||||
|
|
||||||
const InformationSenderAndReceiverBuilder({
|
const InformationSenderAndReceiverBuilder({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -38,66 +47,110 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
|||||||
this.maxBodyHeight,
|
this.maxBodyHeight,
|
||||||
this.openEmailAddressDetailAction,
|
this.openEmailAddressDetailAction,
|
||||||
this.onEmailActionClick,
|
this.onEmailActionClick,
|
||||||
|
this.isInsideThreadDetailView = false,
|
||||||
|
this.emailLoaded,
|
||||||
|
this.onMoreActionClick,
|
||||||
|
this.showRecipients = true,
|
||||||
|
this.onToggleThreadDetailCollapseExpand,
|
||||||
|
this.onTapAvatarActionClick,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsetsDirectional.only(start: 16, end: 16, top: 16),
|
padding: const EdgeInsetsDirectional.symmetric(horizontal: 16),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: emailSelected.countRecipients > 0
|
crossAxisAlignment: emailSelected.countRecipients > 0 && (showRecipients || responsiveUtils.isMobile(context))
|
||||||
? CrossAxisAlignment.start
|
? CrossAxisAlignment.start
|
||||||
: CrossAxisAlignment.center,
|
: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
EmailAvatarBuilder(emailSelected: emailSelected),
|
EmailAvatarBuilder(
|
||||||
|
emailSelected: emailSelected,
|
||||||
|
onTapAvatarActionClick: onTapAvatarActionClick,
|
||||||
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(child: LayoutBuilder(builder: (context, constraints) {
|
Expanded(child: LayoutBuilder(builder: (context, constraints) {
|
||||||
return Transform(
|
return InkWell(
|
||||||
transform: Matrix4.translationValues(0.0, -5.0, 0.0),
|
onTap: onToggleThreadDetailCollapseExpand,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (emailSelected.from?.isNotEmpty == true)
|
Row(
|
||||||
Row(children: [
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
Expanded(child: Row(
|
children: [
|
||||||
children: [
|
Expanded(
|
||||||
Flexible(child: Transform(
|
child: Row(
|
||||||
transform: Matrix4.translationValues(-5.0, 0.0, 0.0),
|
children: [
|
||||||
child: EmailSenderBuilder(
|
if (emailSelected.from?.isNotEmpty == true)
|
||||||
emailAddress: emailSelected.from!.first,
|
Flexible(
|
||||||
openEmailAddressDetailAction: openEmailAddressDetailAction,
|
child: EmailSenderBuilder(
|
||||||
)
|
emailAddress: emailSelected.from!.first,
|
||||||
)),
|
openEmailAddressDetailAction: openEmailAddressDetailAction,
|
||||||
if (sMimeStatus != null && sMimeStatus != SMimeSignatureStatus.notSigned)
|
|
||||||
Tooltip(
|
|
||||||
key: const Key('smime_signature_status_icon'),
|
|
||||||
message: sMimeStatus!.getTooltipMessage(context),
|
|
||||||
child: MouseRegion(
|
|
||||||
cursor: SystemMouseCursors.click,
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
sMimeStatus!.getIcon(imagePaths),
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (sMimeStatus != null && sMimeStatus != SMimeSignatureStatus.notSigned)
|
||||||
if (!emailSelected.isSubscribed && emailUnsubscribe != null && !responsiveUtils.isPortraitMobile(context))
|
Tooltip(
|
||||||
TMailButtonWidget.fromText(
|
key: const Key('smime_signature_status_icon'),
|
||||||
text: AppLocalizations.of(context).unsubscribe,
|
message: sMimeStatus!.getTooltipMessage(context),
|
||||||
textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith(
|
child: MouseRegion(
|
||||||
fontWeight: FontWeight.normal,
|
cursor: SystemMouseCursors.click,
|
||||||
fontSize: 14,
|
child: SvgPicture.asset(
|
||||||
color: AppColor.colorTextBody,
|
sMimeStatus!.getIcon(imagePaths),
|
||||||
decoration: TextDecoration.underline,
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsetsDirectional.symmetric(vertical: 5, horizontal: 8),
|
if (!emailSelected.isSubscribed && emailUnsubscribe != null && !responsiveUtils.isPortraitMobile(context))
|
||||||
backgroundColor: Colors.transparent,
|
TMailButtonWidget.fromText(
|
||||||
onTapActionCallback: () => onEmailActionClick?.call(emailSelected, EmailActionType.unsubscribe),
|
text: AppLocalizations.of(context).unsubscribe,
|
||||||
),
|
textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith(
|
||||||
]
|
fontWeight: FontWeight.normal,
|
||||||
)),
|
fontSize: 14,
|
||||||
ReceivedTimeBuilder(emailSelected: emailSelected),
|
color: AppColor.colorTextBody,
|
||||||
]),
|
decoration: TextDecoration.underline,
|
||||||
if (emailSelected.countRecipients > 0)
|
),
|
||||||
|
padding: const EdgeInsetsDirectional.symmetric(vertical: 5, horizontal: 8),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
onTapActionCallback: () => onEmailActionClick?.call(emailSelected, EmailActionType.unsubscribe),
|
||||||
|
),
|
||||||
|
if (isInsideThreadDetailView && !responsiveUtils.isMobile(context))
|
||||||
|
ReceivedTimeBuilder(
|
||||||
|
emailSelected: emailSelected,
|
||||||
|
padding: const EdgeInsetsDirectional.only(start: 16, top: 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!isInsideThreadDetailView && !responsiveUtils.isMobile(context))
|
||||||
|
ReceivedTimeBuilder(
|
||||||
|
emailSelected: emailSelected,
|
||||||
|
padding: const EdgeInsetsDirectional.only(start: 16, top: 2),
|
||||||
|
),
|
||||||
|
if (isInsideThreadDetailView)
|
||||||
|
EmailViewAppBarWidget(
|
||||||
|
key: const Key('email_view_app_bar_widget'),
|
||||||
|
presentationEmail: emailSelected,
|
||||||
|
isSearchActivated: false,
|
||||||
|
onBackAction: () {},
|
||||||
|
onEmailActionClick: onEmailActionClick,
|
||||||
|
onMoreActionClick: onMoreActionClick,
|
||||||
|
supportBackAction: false,
|
||||||
|
appBarDecoration: const BoxDecoration(),
|
||||||
|
emailLoaded: emailLoaded,
|
||||||
|
isInsideThreadDetailView: isInsideThreadDetailView,
|
||||||
|
height: IconUtils.defaultIconSize,
|
||||||
|
iconPadding: EdgeInsets.zero,
|
||||||
|
iconMargin: const EdgeInsetsDirectional.only(start: 16),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (responsiveUtils.isMobile(context))
|
||||||
|
ReceivedTimeBuilder(
|
||||||
|
emailSelected: emailSelected,
|
||||||
|
padding: const EdgeInsetsDirectional.only(top: 5),
|
||||||
|
),
|
||||||
|
if (emailSelected.countRecipients > 0 && showRecipients)
|
||||||
EmailReceiverWidget(
|
EmailReceiverWidget(
|
||||||
emailSelected: emailSelected,
|
emailSelected: emailSelected,
|
||||||
maxWidth: constraints.maxWidth,
|
maxWidth: constraints.maxWidth,
|
||||||
|
|||||||
@@ -10,16 +10,18 @@ import 'package:model/extensions/presentation_email_extension.dart';
|
|||||||
class ReceivedTimeBuilder extends StatelessWidget {
|
class ReceivedTimeBuilder extends StatelessWidget {
|
||||||
|
|
||||||
final PresentationEmail emailSelected;
|
final PresentationEmail emailSelected;
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
const ReceivedTimeBuilder({
|
const ReceivedTimeBuilder({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.emailSelected,
|
required this.emailSelected,
|
||||||
|
this.padding,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsetsDirectional.only(start: 16),
|
padding: padding ?? const EdgeInsetsDirectional.only(start: 16),
|
||||||
child: Text(
|
child: Text(
|
||||||
emailSelected.getReceivedAt(
|
emailSelected.getReceivedAt(
|
||||||
Localizations.localeOf(context).toLanguageTag(),
|
Localizations.localeOf(context).toLanguageTag(),
|
||||||
|
|||||||
+3
-1
@@ -137,6 +137,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_emai
|
|||||||
import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart';
|
import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/domain/usecases/search_more_email_interactor.dart';
|
import 'package:tmail_ui_user/features/thread/domain/usecases/search_more_email_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/presentation/thread_bindings.dart';
|
import 'package:tmail_ui_user/features/thread/presentation/thread_bindings.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_bindings.dart';
|
||||||
import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart';
|
import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart';
|
||||||
import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart';
|
import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart';
|
||||||
import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart';
|
import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart';
|
||||||
@@ -150,8 +151,9 @@ class MailboxDashBoardBindings extends BaseBindings {
|
|||||||
SendingQueueBindings().dependencies();
|
SendingQueueBindings().dependencies();
|
||||||
MailboxBindings().dependencies();
|
MailboxBindings().dependencies();
|
||||||
ThreadBindings().dependencies();
|
ThreadBindings().dependencies();
|
||||||
EmailBindings().dependencies();
|
|
||||||
SearchEmailBindings().dependencies();
|
SearchEmailBindings().dependencies();
|
||||||
|
ThreadDetailBindings().dependencies();
|
||||||
|
EmailBindings().dependencies();
|
||||||
QuotasBindings().dependencies();
|
QuotasBindings().dependencies();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-2
@@ -861,9 +861,13 @@ class MailboxDashBoardController extends ReloadableController
|
|||||||
selectedEmail.value = null;
|
selectedEmail.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void openEmailDetailedView(PresentationEmail presentationEmail) {
|
void openEmailDetailedView(PresentationEmail presentationEmail, {bool singleEmail = false}) {
|
||||||
setSelectedEmail(presentationEmail);
|
setSelectedEmail(presentationEmail);
|
||||||
dispatchRoute(DashboardRoutes.emailDetailed);
|
if (singleEmail) {
|
||||||
|
dispatchRoute(DashboardRoutes.emailDetailed);
|
||||||
|
} else {
|
||||||
|
dispatchRoute(DashboardRoutes.threadDetailed);
|
||||||
|
}
|
||||||
if (PlatformInfo.isWeb && presentationEmail.routeWeb != null) {
|
if (PlatformInfo.isWeb && presentationEmail.routeWeb != null) {
|
||||||
RouteUtils.replaceBrowserHistory(
|
RouteUtils.replaceBrowserHistory(
|
||||||
title: 'Email-${presentationEmail.id?.id.value ?? ''}',
|
title: 'Email-${presentationEmail.id?.id.value ?? ''}',
|
||||||
@@ -898,6 +902,8 @@ class MailboxDashBoardController extends ReloadableController
|
|||||||
log('MailboxDashBoardController::handleAdvancedSearchEmail:');
|
log('MailboxDashBoardController::handleAdvancedSearchEmail:');
|
||||||
if (_searchInsideEmailDetailedViewIsActive()) {
|
if (_searchInsideEmailDetailedViewIsActive()) {
|
||||||
_closeEmailDetailedView();
|
_closeEmailDetailedView();
|
||||||
|
} else if (_searchInsideThreadDetailViewIsActive()) {
|
||||||
|
_closeEmailDetailedView();
|
||||||
}
|
}
|
||||||
_unSelectedMailbox();
|
_unSelectedMailbox();
|
||||||
searchController.clearFilterSuggestion();
|
searchController.clearFilterSuggestion();
|
||||||
@@ -923,6 +929,8 @@ class MailboxDashBoardController extends ReloadableController
|
|||||||
clearFilterMessageOption();
|
clearFilterMessageOption();
|
||||||
if (_searchInsideEmailDetailedViewIsActive()) {
|
if (_searchInsideEmailDetailedViewIsActive()) {
|
||||||
_closeEmailDetailedView();
|
_closeEmailDetailedView();
|
||||||
|
} else if (_searchInsideThreadDetailViewIsActive()) {
|
||||||
|
_closeEmailDetailedView();
|
||||||
}
|
}
|
||||||
_unSelectedMailbox();
|
_unSelectedMailbox();
|
||||||
searchController.clearFilterSuggestion();
|
searchController.clearFilterSuggestion();
|
||||||
@@ -951,6 +959,13 @@ class MailboxDashBoardController extends ReloadableController
|
|||||||
&& dashboardRoute.value == DashboardRoutes.emailDetailed;
|
&& dashboardRoute.value == DashboardRoutes.emailDetailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _searchInsideThreadDetailViewIsActive() {
|
||||||
|
return PlatformInfo.isWeb
|
||||||
|
&& currentContext != null
|
||||||
|
&& responsiveUtils.isDesktop(currentContext!)
|
||||||
|
&& dashboardRoute.value == DashboardRoutes.threadDetailed;
|
||||||
|
}
|
||||||
|
|
||||||
void clearSearchEmail() {
|
void clearSearchEmail() {
|
||||||
dispatchAction(ClearSearchEmailAction());
|
dispatchAction(ClearSearchEmailAction());
|
||||||
searchController.disableSimpleSearch();
|
searchController.disableSimpleSearch();
|
||||||
@@ -2814,6 +2829,8 @@ class MailboxDashBoardController extends ReloadableController
|
|||||||
searchController.clearFilterSuggestion();
|
searchController.clearFilterSuggestion();
|
||||||
if (_searchInsideEmailDetailedViewIsActive()) {
|
if (_searchInsideEmailDetailedViewIsActive()) {
|
||||||
_closeEmailDetailedView();
|
_closeEmailDetailedView();
|
||||||
|
} else if (_searchInsideThreadDetailViewIsActive()) {
|
||||||
|
_closeEmailDetailedView();
|
||||||
}
|
}
|
||||||
_unSelectedMailbox();
|
_unSelectedMailbox();
|
||||||
dispatchAction(QuickSearchEmailByFromAction(emailAddress));
|
dispatchAction(QuickSearchEmailByFromAction(emailAddress));
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dash
|
|||||||
import 'package:tmail_ui_user/features/search/email/presentation/search_email_view.dart';
|
import 'package:tmail_ui_user/features/search/email/presentation/search_email_view.dart';
|
||||||
import 'package:tmail_ui_user/features/sending_queue/presentation/sending_queue_view.dart';
|
import 'package:tmail_ui_user/features/sending_queue/presentation/sending_queue_view.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/presentation/thread_view.dart';
|
import 'package:tmail_ui_user/features/thread/presentation/thread_view.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_view.dart';
|
||||||
|
|
||||||
class MailboxDashBoardView extends BaseMailboxDashBoardView {
|
class MailboxDashBoardView extends BaseMailboxDashBoardView {
|
||||||
|
|
||||||
@@ -54,6 +55,13 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
|
|||||||
tabletLarge: bodyView,
|
tabletLarge: bodyView,
|
||||||
landscapeTablet: bodyView,
|
landscapeTablet: bodyView,
|
||||||
mobile: const EmailView());
|
mobile: const EmailView());
|
||||||
|
case DashboardRoutes.threadDetailed:
|
||||||
|
return ResponsiveWidget(
|
||||||
|
responsiveUtils: controller.responsiveUtils,
|
||||||
|
desktop: bodyView,
|
||||||
|
tabletLarge: bodyView,
|
||||||
|
landscapeTablet: bodyView,
|
||||||
|
mobile: const ThreadDetailView());
|
||||||
case DashboardRoutes.searchEmail:
|
case DashboardRoutes.searchEmail:
|
||||||
return SafeArea(child: SearchEmailView());
|
return SafeArea(child: SearchEmailView());
|
||||||
case DashboardRoutes.sendingQueue:
|
case DashboardRoutes.sendingQueue:
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option
|
|||||||
import 'package:tmail_ui_user/features/thread/presentation/model/popup_menu_item_filter_message_action.dart';
|
import 'package:tmail_ui_user/features/thread/presentation/model/popup_menu_item_filter_message_action.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/presentation/styles/spam_banner/spam_report_banner_web_styles.dart';
|
import 'package:tmail_ui_user/features/thread/presentation/styles/spam_banner/spam_report_banner_web_styles.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/presentation/thread_view.dart';
|
import 'package:tmail_ui_user/features/thread/presentation/thread_view.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_view.dart';
|
||||||
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';
|
||||||
|
|
||||||
@@ -269,6 +270,8 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
|
|||||||
return _buildThreadViewForWebDesktop(context);
|
return _buildThreadViewForWebDesktop(context);
|
||||||
case DashboardRoutes.emailDetailed:
|
case DashboardRoutes.emailDetailed:
|
||||||
return const EmailView();
|
return const EmailView();
|
||||||
|
case DashboardRoutes.threadDetailed:
|
||||||
|
return const ThreadDetailView();
|
||||||
default:
|
default:
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
@@ -298,6 +301,19 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
case DashboardRoutes.threadDetailed:
|
||||||
|
return _buildScaffoldHaveDrawer(
|
||||||
|
body: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: ResponsiveUtils.defaultSizeLeftMenuMobile,
|
||||||
|
child: ThreadView()),
|
||||||
|
const VerticalDivider(width: 1),
|
||||||
|
const Expanded(child: ThreadDetailView()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return _buildScaffoldHaveDrawer(
|
return _buildScaffoldHaveDrawer(
|
||||||
body: Row(
|
body: Row(
|
||||||
@@ -317,6 +333,8 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
|
|||||||
switch(controller.dashboardRoute.value) {
|
switch(controller.dashboardRoute.value) {
|
||||||
case DashboardRoutes.thread:
|
case DashboardRoutes.thread:
|
||||||
return _buildScaffoldHaveDrawer(body: ThreadView());
|
return _buildScaffoldHaveDrawer(body: ThreadView());
|
||||||
|
case DashboardRoutes.threadDetailed:
|
||||||
|
return const ThreadDetailView();
|
||||||
case DashboardRoutes.emailDetailed:
|
case DashboardRoutes.emailDetailed:
|
||||||
return const EmailView();
|
return const EmailView();
|
||||||
case DashboardRoutes.searchEmail:
|
case DashboardRoutes.searchEmail:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
enum DashboardRoutes {
|
enum DashboardRoutes {
|
||||||
thread,
|
thread,
|
||||||
emailDetailed,
|
emailDetailed,
|
||||||
|
threadDetailed,
|
||||||
searchEmail,
|
searchEmail,
|
||||||
waiting,
|
waiting,
|
||||||
sendingQueue;
|
sendingQueue;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ extension EmailCacheExtension on EmailCache {
|
|||||||
mailboxIds: mailboxIds != null
|
mailboxIds: mailboxIds != null
|
||||||
? Map.fromIterables(mailboxIds!.keys.map((value) => MailboxId(Id(value))), mailboxIds!.values)
|
? Map.fromIterables(mailboxIds!.keys.map((value) => MailboxId(Id(value))), mailboxIds!.values)
|
||||||
: null,
|
: null,
|
||||||
|
threadId: threadId == null ? null : ThreadId(Id(threadId!)),
|
||||||
headerCalendarEvent: headerCalendarEvent != null
|
headerCalendarEvent: headerCalendarEvent != null
|
||||||
? Map.fromIterables(headerCalendarEvent!.keys.map((value) => IndividualHeaderIdentifier(value)), headerCalendarEvent!.values)
|
? Map.fromIterables(headerCalendarEvent!.keys.map((value) => IndividualHeaderIdentifier(value)), headerCalendarEvent!.values)
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ extension EmailExtension on Email {
|
|||||||
bcc: bcc?.map((emailAddress) => emailAddress.toEmailAddressHiveCache()).toList(),
|
bcc: bcc?.map((emailAddress) => emailAddress.toEmailAddressHiveCache()).toList(),
|
||||||
replyTo: replyTo?.map((emailAddress) => emailAddress.toEmailAddressHiveCache()).toList(),
|
replyTo: replyTo?.map((emailAddress) => emailAddress.toEmailAddressHiveCache()).toList(),
|
||||||
mailboxIds: mailboxIds?.toMapString(),
|
mailboxIds: mailboxIds?.toMapString(),
|
||||||
|
threadId: threadId?.id.value,
|
||||||
headerCalendarEvent: headerCalendarEvent?.toMapString(),
|
headerCalendarEvent: headerCalendarEvent?.toMapString(),
|
||||||
blobId: blobId?.value,
|
blobId: blobId?.value,
|
||||||
xPriorityHeader: xPriorityHeader?.toMapString(),
|
xPriorityHeader: xPriorityHeader?.toMapString(),
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ class EmailCache extends HiveObject with EquatableMixin {
|
|||||||
@HiveField(18)
|
@HiveField(18)
|
||||||
Map<String, String?>? priorityHeader;
|
Map<String, String?>? priorityHeader;
|
||||||
|
|
||||||
|
@HiveField(19)
|
||||||
|
String? threadId;
|
||||||
|
|
||||||
EmailCache(
|
EmailCache(
|
||||||
this.id,
|
this.id,
|
||||||
{
|
{
|
||||||
@@ -87,6 +90,7 @@ class EmailCache extends HiveObject with EquatableMixin {
|
|||||||
this.xPriorityHeader,
|
this.xPriorityHeader,
|
||||||
this.importanceHeader,
|
this.importanceHeader,
|
||||||
this.priorityHeader,
|
this.priorityHeader,
|
||||||
|
this.threadId,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -111,5 +115,6 @@ class EmailCache extends HiveObject with EquatableMixin {
|
|||||||
xPriorityHeader,
|
xPriorityHeader,
|
||||||
importanceHeader,
|
importanceHeader,
|
||||||
priorityHeader,
|
priorityHeader,
|
||||||
|
threadId,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ class ThreadConstants {
|
|||||||
EmailProperty.hasAttachment,
|
EmailProperty.hasAttachment,
|
||||||
EmailProperty.replyTo,
|
EmailProperty.replyTo,
|
||||||
EmailProperty.mailboxIds,
|
EmailProperty.mailboxIds,
|
||||||
|
EmailProperty.threadId,
|
||||||
IndividualHeaderIdentifier.xPriorityHeader.value,
|
IndividualHeaderIdentifier.xPriorityHeader.value,
|
||||||
IndividualHeaderIdentifier.importanceHeader.value,
|
IndividualHeaderIdentifier.importanceHeader.value,
|
||||||
IndividualHeaderIdentifier.priorityHeader.value,
|
IndividualHeaderIdentifier.priorityHeader.value,
|
||||||
@@ -56,6 +57,7 @@ class ThreadConstants {
|
|||||||
EmailProperty.hasAttachment,
|
EmailProperty.hasAttachment,
|
||||||
EmailProperty.replyTo,
|
EmailProperty.replyTo,
|
||||||
EmailProperty.mailboxIds,
|
EmailProperty.mailboxIds,
|
||||||
|
EmailProperty.threadId,
|
||||||
EmailProperty.bodyValues,
|
EmailProperty.bodyValues,
|
||||||
EmailProperty.htmlBody,
|
EmailProperty.htmlBody,
|
||||||
EmailProperty.attachments,
|
EmailProperty.attachments,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
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/properties/properties.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';
|
||||||
|
|
||||||
abstract class ThreadDetailDataSource {
|
abstract class ThreadDetailDataSource {
|
||||||
@@ -6,4 +8,11 @@ abstract class ThreadDetailDataSource {
|
|||||||
ThreadId threadId,
|
ThreadId threadId,
|
||||||
AccountId accountId,
|
AccountId accountId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Future<List<Email>> getEmailsByIds(
|
||||||
|
Session session,
|
||||||
|
AccountId accountId,
|
||||||
|
List<EmailId> emailIds, {
|
||||||
|
Properties? properties,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
+19
@@ -1,4 +1,6 @@
|
|||||||
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/properties/properties.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:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_data_source.dart';
|
import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_data_source.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/data/network/thread_detail_api.dart';
|
import 'package:tmail_ui_user/features/thread_detail/data/network/thread_detail_api.dart';
|
||||||
@@ -22,4 +24,21 @@ class ThreadDetailRemoteDataSourceImpl implements ThreadDetailDataSource {
|
|||||||
return threadDetailApi.getThreadById(threadId, accountId);
|
return threadDetailApi.getThreadById(threadId, accountId);
|
||||||
}).catchError(exceptionThrower.throwException);
|
}).catchError(exceptionThrower.throwException);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Email>> getEmailsByIds(
|
||||||
|
Session session,
|
||||||
|
AccountId accountId,
|
||||||
|
List<EmailId> emailIds, {
|
||||||
|
Properties? properties,
|
||||||
|
}) {
|
||||||
|
return Future.sync(() async {
|
||||||
|
return threadDetailApi.getEmailsByIds(
|
||||||
|
session,
|
||||||
|
accountId,
|
||||||
|
emailIds,
|
||||||
|
properties: properties,
|
||||||
|
);
|
||||||
|
}).catchError(exceptionThrower.throwException);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,18 @@
|
|||||||
|
import 'package:core/presentation/extensions/list_extensions.dart';
|
||||||
import 'package:get/get_utils/get_utils.dart';
|
import 'package:get/get_utils/get_utils.dart';
|
||||||
import 'package:jmap_dart_client/http/http_client.dart';
|
import 'package:jmap_dart_client/http/http_client.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/properties/properties.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/session/session.dart';
|
||||||
import 'package:jmap_dart_client/jmap/jmap_request.dart';
|
import 'package:jmap_dart_client/jmap/jmap_request.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/get/get_email_method.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/get/get_email_response.dart';
|
||||||
import 'package:jmap_dart_client/jmap/thread/get/get_thread_method.dart';
|
import 'package:jmap_dart_client/jmap/thread/get/get_thread_method.dart';
|
||||||
import 'package:jmap_dart_client/jmap/thread/get/get_thread_response.dart';
|
import 'package:jmap_dart_client/jmap/thread/get/get_thread_response.dart';
|
||||||
|
import 'package:model/extensions/session_extension.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
|
||||||
|
import 'package:tmail_ui_user/main/error/capability_validator.dart';
|
||||||
|
|
||||||
class ThreadDetailApi {
|
class ThreadDetailApi {
|
||||||
const ThreadDetailApi(this._httpClient);
|
const ThreadDetailApi(this._httpClient);
|
||||||
@@ -34,4 +42,49 @@ class ThreadDetailApi {
|
|||||||
(thread) => thread.id == threadId,
|
(thread) => thread.id == threadId,
|
||||||
)!.emailIds;
|
)!.emailIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Email>> getEmailsByIds(
|
||||||
|
Session session,
|
||||||
|
AccountId accountId,
|
||||||
|
List<EmailId> emailIds, {
|
||||||
|
Properties? properties,
|
||||||
|
}) async {
|
||||||
|
final jmapRequestBuilder = JmapRequestBuilder(
|
||||||
|
_httpClient,
|
||||||
|
ProcessingInvocation(),
|
||||||
|
);
|
||||||
|
final getLimit = session.getMaxObjectsInGet(accountId)
|
||||||
|
?? ThreadConstants.defaultLimit;
|
||||||
|
|
||||||
|
final listOfListEmails = await Future.wait(
|
||||||
|
emailIds.chunks(getLimit.value.toInt()).map((chunkEmailIds) async {
|
||||||
|
final getEmailMethod = GetEmailMethod(accountId)
|
||||||
|
..addIds(chunkEmailIds.map((emailId) => emailId.id).toSet());
|
||||||
|
|
||||||
|
if (properties != null) {
|
||||||
|
getEmailMethod.addProperties(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
final getEmailInvocation = jmapRequestBuilder.invocation(getEmailMethod);
|
||||||
|
|
||||||
|
final capabilities = getEmailMethod.requiredCapabilities
|
||||||
|
.toCapabilitiesSupportTeamMailboxes(session, accountId);
|
||||||
|
|
||||||
|
final result = await (jmapRequestBuilder
|
||||||
|
..usings(capabilities))
|
||||||
|
.build()
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
final resultList = result.parse<GetEmailResponse>(
|
||||||
|
getEmailInvocation.methodCallId,
|
||||||
|
GetEmailResponse.deserialize);
|
||||||
|
|
||||||
|
return resultList!.list;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return listOfListEmails.reduce(
|
||||||
|
(listAllEmails, nextListEmails) => listAllEmails..addAll(nextListEmails),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,17 @@
|
|||||||
import 'package:core/data/model/source_type/data_source_type.dart';
|
import 'package:core/data/model/source_type/data_source_type.dart';
|
||||||
|
import 'package:core/presentation/extensions/list_extensions.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/properties/properties.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/email_comparator.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email_comparator_property.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||||
|
import 'package:model/email/email_property.dart';
|
||||||
|
import 'package:model/extensions/list_email_extension.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/extensions/email_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_data_source.dart';
|
import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_data_source.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/exceptions/empty_thread_detail_exception.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/domain/repository/thread_detail_repository.dart';
|
import 'package:tmail_ui_user/features/thread_detail/domain/repository/thread_detail_repository.dart';
|
||||||
|
|
||||||
class ThreadDetailRepositoryImpl implements ThreadDetailRepository {
|
class ThreadDetailRepositoryImpl implements ThreadDetailRepository {
|
||||||
@@ -12,9 +22,100 @@ class ThreadDetailRepositoryImpl implements ThreadDetailRepository {
|
|||||||
@override
|
@override
|
||||||
Future<List<EmailId>> getThreadById(
|
Future<List<EmailId>> getThreadById(
|
||||||
ThreadId threadId,
|
ThreadId threadId,
|
||||||
AccountId accountId
|
Session session,
|
||||||
) {
|
AccountId accountId,
|
||||||
return threadDetailDataSource[DataSourceType.network]!
|
MailboxId sentMailboxId,
|
||||||
|
String ownEmailAddress,
|
||||||
|
) async {
|
||||||
|
final originalEmailIds = await threadDetailDataSource[DataSourceType.network]!
|
||||||
.getThreadById(threadId, accountId);
|
.getThreadById(threadId, accountId);
|
||||||
|
|
||||||
|
if (originalEmailIds.isEmpty) {
|
||||||
|
throw EmptyThreadDetailException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final filteredEmailIds = await Future.wait(
|
||||||
|
originalEmailIds
|
||||||
|
.chunks(20)
|
||||||
|
.map((emailIds) => _filterBadEmails(
|
||||||
|
session,
|
||||||
|
accountId,
|
||||||
|
emailIds,
|
||||||
|
sentMailboxId,
|
||||||
|
ownEmailAddress,
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
return filteredEmailIds
|
||||||
|
.reduce((prev, curr) => prev + curr)
|
||||||
|
.sortWithResult(EmailComparator(
|
||||||
|
EmailComparatorProperty.receivedAt
|
||||||
|
)..setIsAscending(true))
|
||||||
|
.map((e) => e.id!)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Email>> _filterBadEmails(
|
||||||
|
Session session,
|
||||||
|
AccountId accountId,
|
||||||
|
List<EmailId> emailIds,
|
||||||
|
MailboxId sentMailboxId,
|
||||||
|
String ownEmailAddress,
|
||||||
|
) async {
|
||||||
|
int retry = 3;
|
||||||
|
while (retry > 0) {
|
||||||
|
try {
|
||||||
|
final emails = await threadDetailDataSource[DataSourceType.network]!
|
||||||
|
.getEmailsByIds(
|
||||||
|
session,
|
||||||
|
accountId,
|
||||||
|
emailIds,
|
||||||
|
properties: Properties({
|
||||||
|
EmailProperty.id,
|
||||||
|
EmailProperty.mailboxIds,
|
||||||
|
EmailProperty.from,
|
||||||
|
EmailProperty.to,
|
||||||
|
EmailProperty.cc,
|
||||||
|
EmailProperty.bcc,
|
||||||
|
EmailProperty.receivedAt,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return emails
|
||||||
|
.where((email) => checkEmailValidForThreadDetail(
|
||||||
|
email,
|
||||||
|
sentMailboxId,
|
||||||
|
ownEmailAddress,
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
} catch (e) {
|
||||||
|
retry--;
|
||||||
|
|
||||||
|
if (retry <= 0) rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkEmailValidForThreadDetail(
|
||||||
|
Email email,
|
||||||
|
MailboxId sentMailboxId,
|
||||||
|
String ownEmailAddress,
|
||||||
|
) {
|
||||||
|
return email.id != null && (
|
||||||
|
!email.inSentMailbox(sentMailboxId)
|
||||||
|
|| !email.fromMe(ownEmailAddress)
|
||||||
|
|| !email.recipientsHasMe(ownEmailAddress)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Email>> getEmailsByIds(
|
||||||
|
Session session,
|
||||||
|
AccountId accountId,
|
||||||
|
List<EmailId> emailIds, {
|
||||||
|
Properties? properties,
|
||||||
|
}) {
|
||||||
|
return threadDetailDataSource[DataSourceType.network]!
|
||||||
|
.getEmailsByIds(session, accountId, emailIds, properties: properties);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
class ThreadDetailOverloadException implements Exception {}
|
||||||
@@ -1,9 +1,22 @@
|
|||||||
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/properties/properties.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/mailbox/mailbox.dart';
|
||||||
|
|
||||||
abstract class ThreadDetailRepository {
|
abstract class ThreadDetailRepository {
|
||||||
Future<List<EmailId>> getThreadById(
|
Future<List<EmailId>> getThreadById(
|
||||||
ThreadId threadId,
|
ThreadId threadId,
|
||||||
|
Session session,
|
||||||
AccountId accountId,
|
AccountId accountId,
|
||||||
|
MailboxId sentMailboxId,
|
||||||
|
String ownEmailAddress,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Future<List<Email>> getEmailsByIds(
|
||||||
|
Session session,
|
||||||
|
AccountId accountId,
|
||||||
|
List<EmailId> emailIds, {
|
||||||
|
Properties? properties,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:core/presentation/state/failure.dart';
|
||||||
|
import 'package:core/presentation/state/success.dart';
|
||||||
|
import 'package:model/email/presentation_email.dart';
|
||||||
|
|
||||||
|
class GettingEmailsByIds extends LoadingState {}
|
||||||
|
|
||||||
|
class GetEmailsByIdsSuccess extends UIState {
|
||||||
|
GetEmailsByIdsSuccess(this.presentationEmails);
|
||||||
|
|
||||||
|
final List<PresentationEmail> presentationEmails;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [presentationEmails];
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetEmailsByIdsFailure extends FeatureFailure {
|
||||||
|
GetEmailsByIdsFailure({super.exception, super.onRetry});
|
||||||
|
}
|
||||||
@@ -14,5 +14,5 @@ class GetThreadByIdSuccess extends UIState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GetThreadByIdFailure extends FeatureFailure {
|
class GetThreadByIdFailure extends FeatureFailure {
|
||||||
GetThreadByIdFailure({super.exception});
|
GetThreadByIdFailure({super.exception, super.onRetry});
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import 'package:core/presentation/state/failure.dart';
|
||||||
|
import 'package:core/presentation/state/success.dart';
|
||||||
|
import 'package:core/utils/app_logger.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/session/session.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
|
import 'package:model/email/email_property.dart';
|
||||||
|
import 'package:model/extensions/email_extension.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/exceptions/thread_detail_overload_exception.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/repository/thread_detail_repository.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||||
|
|
||||||
|
class GetEmailsByIdsInteractor {
|
||||||
|
const GetEmailsByIdsInteractor(this._threadDetailRepository);
|
||||||
|
|
||||||
|
final ThreadDetailRepository _threadDetailRepository;
|
||||||
|
|
||||||
|
Stream<Either<Failure, Success>> execute(
|
||||||
|
Session session,
|
||||||
|
AccountId accountId,
|
||||||
|
List<EmailId> emailIds, {
|
||||||
|
Properties? properties,
|
||||||
|
}) async* {
|
||||||
|
try {
|
||||||
|
if (emailIds.length > 1 && (
|
||||||
|
properties?.value.contains(EmailProperty.htmlBody) == true ||
|
||||||
|
properties?.value.contains(EmailProperty.bodyValues) == true ||
|
||||||
|
properties?.value.contains(EmailProperty.attachments) == true)) {
|
||||||
|
throw ThreadDetailOverloadException();
|
||||||
|
}
|
||||||
|
|
||||||
|
yield Right(GettingEmailsByIds());
|
||||||
|
final result = await _threadDetailRepository.getEmailsByIds(
|
||||||
|
session,
|
||||||
|
accountId,
|
||||||
|
emailIds,
|
||||||
|
properties: properties,
|
||||||
|
);
|
||||||
|
yield Right(GetEmailsByIdsSuccess(
|
||||||
|
result.map((e) => e.toPresentationEmail()).toList(),
|
||||||
|
));
|
||||||
|
} catch (e) {
|
||||||
|
logError('GetEmailsByIdsInteractor::execute(): Exception: $e');
|
||||||
|
yield Left(GetEmailsByIdsFailure(
|
||||||
|
exception: e,
|
||||||
|
onRetry: execute(session, accountId, emailIds, properties: properties),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,9 @@ import 'package:core/presentation/state/success.dart';
|
|||||||
import 'package:core/utils/app_logger.dart';
|
import 'package:core/utils/app_logger.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:tmail_ui_user/features/thread_detail/domain/exceptions/empty_thread_detail_exception.dart';
|
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/domain/repository/thread_detail_repository.dart';
|
import 'package:tmail_ui_user/features/thread_detail/domain/repository/thread_detail_repository.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||||
|
|
||||||
@@ -15,23 +16,34 @@ class GetThreadByIdInteractor {
|
|||||||
|
|
||||||
Stream<Either<Failure, Success>> execute(
|
Stream<Either<Failure, Success>> execute(
|
||||||
ThreadId threadId,
|
ThreadId threadId,
|
||||||
|
Session session,
|
||||||
AccountId accountId,
|
AccountId accountId,
|
||||||
|
MailboxId sentMailboxId,
|
||||||
|
String ownEmailAddress,
|
||||||
) async* {
|
) async* {
|
||||||
try {
|
try {
|
||||||
yield Right(GettingThreadById());
|
yield Right(GettingThreadById());
|
||||||
final result = await _threadDetailRepository.getThreadById(
|
final result = await _threadDetailRepository.getThreadById(
|
||||||
threadId,
|
threadId,
|
||||||
|
session,
|
||||||
accountId,
|
accountId,
|
||||||
|
sentMailboxId,
|
||||||
|
ownEmailAddress,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.isEmpty) {
|
|
||||||
throw EmptyThreadDetailException();
|
|
||||||
}
|
|
||||||
|
|
||||||
yield Right(GetThreadByIdSuccess(result));
|
yield Right(GetThreadByIdSuccess(result));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError('GetEmailIdsByThreadIdInteractor::execute(): Exception: $e');
|
logError('GetEmailIdsByThreadIdInteractor::execute(): Exception: $e');
|
||||||
yield Left(GetThreadByIdFailure(exception: e));
|
yield Left(GetThreadByIdFailure(
|
||||||
|
exception: e,
|
||||||
|
onRetry: execute(
|
||||||
|
threadId,
|
||||||
|
session,
|
||||||
|
accountId,
|
||||||
|
sentMailboxId,
|
||||||
|
ownEmailAddress,
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||||
|
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
|
||||||
|
extension CloseThreadDetailAction on ThreadDetailController {
|
||||||
|
void closeThreadDetailAction(BuildContext? context) {
|
||||||
|
if (isSearchRunning) {
|
||||||
|
mailboxDashBoardController.dispatchRoute(DashboardRoutes.searchEmail);
|
||||||
|
} else {
|
||||||
|
mailboxDashBoardController.dispatchRoute(DashboardRoutes.thread);
|
||||||
|
}
|
||||||
|
for (var emailId in emailIdsPresentation.keys) {
|
||||||
|
mailboxDashBoardController.dispatchEmailUIAction(
|
||||||
|
CloseEmailInThreadDetailAction(emailId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
|
||||||
|
extension FocusThreadDetailExpandedEmail on ThreadDetailController {
|
||||||
|
void focusExpandedEmail(EmailId emailId) {
|
||||||
|
Future.delayed(const Duration(milliseconds: 200), () {
|
||||||
|
final context = GlobalObjectKey(emailId.id.value).currentContext;
|
||||||
|
if (context != null && context.mounted) {
|
||||||
|
Scrollable.ensureVisible(context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import 'package:core/presentation/views/loading/cupertino_loading_widget.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
|
||||||
|
extension GetThreadDetailLoadingView on ThreadDetailController {
|
||||||
|
Widget getThreadDetailLoadingView({
|
||||||
|
required bool isResponsiveDesktop,
|
||||||
|
}) {
|
||||||
|
final isLoading = viewState.value.fold(
|
||||||
|
(failure) => false,
|
||||||
|
(success) => success is GettingThreadById
|
||||||
|
);
|
||||||
|
if (!isLoading) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
|
),
|
||||||
|
margin: isResponsiveDesktop
|
||||||
|
? const EdgeInsetsDirectional.only(end: 16)
|
||||||
|
: null,
|
||||||
|
padding: const EdgeInsets.only(top: 16),
|
||||||
|
child: const SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: CupertinoLoadingWidget(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/email_view.dart';
|
||||||
|
import 'package:model/email/email_in_thread_status.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/toggle_thread_detail_collape_expand.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_collapsed_email.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_load_more_circle.dart';
|
||||||
|
|
||||||
|
extension GetThreadDetailEmailViews on ThreadDetailController {
|
||||||
|
List<Widget> getThreadDetailEmailViews() {
|
||||||
|
int? firstEmailNotLoadedIndex;
|
||||||
|
if (emailsToLoadMoreCount > 0) {
|
||||||
|
final firstNotLoadedEmailId = emailIdsPresentation.entries.firstWhereOrNull(
|
||||||
|
(entry) => entry.value == null
|
||||||
|
)?.key;
|
||||||
|
if (firstNotLoadedEmailId == null) {
|
||||||
|
firstEmailNotLoadedIndex = -1;
|
||||||
|
} else {
|
||||||
|
firstEmailNotLoadedIndex = emailIdsPresentation
|
||||||
|
.keys
|
||||||
|
.toList()
|
||||||
|
.indexOf(firstNotLoadedEmailId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return emailIdsPresentation.entries.map((entry) {
|
||||||
|
final emailId = entry.key;
|
||||||
|
final presentationEmail = entry.value;
|
||||||
|
final indexOfEmailId = emailIdsPresentation.keys.toList().indexOf(emailId);
|
||||||
|
if (presentationEmail == null) {
|
||||||
|
if (indexOfEmailId != firstEmailNotLoadedIndex) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ThreadDetailLoadMoreCircle(
|
||||||
|
count: emailsToLoadMoreCount,
|
||||||
|
onTap: loadMoreThreadDetailEmails,
|
||||||
|
imagePaths: imagePaths,
|
||||||
|
isLoading: loadingThreadDetail,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (presentationEmail.emailInThreadStatus == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
final isFirstEmailInThreadDetail = indexOfEmailId == 0;
|
||||||
|
|
||||||
|
if (presentationEmail.emailInThreadStatus == EmailInThreadStatus.collapsed) {
|
||||||
|
return ThreadDetailCollapsedEmail(
|
||||||
|
presentationEmail: presentationEmail.copyWith(
|
||||||
|
subject: isFirstEmailInThreadDetail
|
||||||
|
? emailIdsPresentation.values.last?.subject
|
||||||
|
: null
|
||||||
|
),
|
||||||
|
showSubject: isFirstEmailInThreadDetail,
|
||||||
|
imagePaths: imagePaths,
|
||||||
|
responsiveUtils: responsiveUtils,
|
||||||
|
mailboxContain: presentationEmail.mailboxContain,
|
||||||
|
emailLoaded: null,
|
||||||
|
openEmailAddressDetailAction: (context, emailAddress) {
|
||||||
|
// TODO: Next PR
|
||||||
|
},
|
||||||
|
onToggleThreadDetailCollapseExpand: () {
|
||||||
|
toggleThreadDetailCollapeExpand(presentationEmail);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(bottom: 16),
|
||||||
|
child: EmailView(
|
||||||
|
key: GlobalObjectKey(presentationEmail.id?.id.value ?? ''),
|
||||||
|
isInsideThreadDetailView: true,
|
||||||
|
emailId: presentationEmail.id,
|
||||||
|
isFirstEmailInThreadDetail: isFirstEmailInThreadDetail,
|
||||||
|
threadSubject: isFirstEmailInThreadDetail
|
||||||
|
? emailIdsPresentation.values.last?.subject
|
||||||
|
: null,
|
||||||
|
onToggleThreadDetailCollapseExpand: () {
|
||||||
|
toggleThreadDetailCollapeExpand(presentationEmail);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-1
@@ -5,6 +5,14 @@ extension HandleGetEmailIdsByThreadIdSuccess on ThreadDetailController {
|
|||||||
void handleGetEmailIdsByThreadIdSuccess(
|
void handleGetEmailIdsByThreadIdSuccess(
|
||||||
GetThreadByIdSuccess success,
|
GetThreadByIdSuccess success,
|
||||||
) {
|
) {
|
||||||
emailIds.value = success.emailIds;
|
if (success.emailIds.isNotEmpty) {
|
||||||
|
emailIdsPresentation.value = Map.fromEntries(success.emailIds.map(
|
||||||
|
(emailId) => MapEntry(emailId, null),
|
||||||
|
));
|
||||||
|
} else if (mailboxDashBoardController.selectedEmail.value?.id != null) {
|
||||||
|
emailIdsPresentation.value = {
|
||||||
|
mailboxDashBoardController.selectedEmail.value!.id!: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/bindings/email_bindings.dart';
|
||||||
|
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
|
||||||
|
import 'package:model/email/email_in_thread_status.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
|
||||||
|
extension HandleGetEmailsByIdsSuccess on ThreadDetailController {
|
||||||
|
void handleGetEmailsByIdsSuccess(GetEmailsByIdsSuccess success) {
|
||||||
|
final currentRoute = mailboxDashBoardController.dashboardRoute.value;
|
||||||
|
if (currentRoute != DashboardRoutes.threadDetailed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final isLoadMore = emailIdsPresentation.values.whereNotNull().isNotEmpty;
|
||||||
|
final currentScrollPosition = scrollController?.position.pixels;
|
||||||
|
final maxScrollExtent = scrollController?.position.maxScrollExtent;
|
||||||
|
final currentBottomScrollPosition = currentScrollPosition != null
|
||||||
|
&& maxScrollExtent != null
|
||||||
|
? maxScrollExtent - currentScrollPosition
|
||||||
|
: null;
|
||||||
|
|
||||||
|
for (var presentationEmail in success.presentationEmails) {
|
||||||
|
if (presentationEmail.id == null) continue;
|
||||||
|
if (presentationEmail.id == emailIdsPresentation.keys.last) {
|
||||||
|
EmailBindings(currentEmailId: presentationEmail.id).dependencies();
|
||||||
|
currentExpandedEmailId.value = presentationEmail.id;
|
||||||
|
}
|
||||||
|
emailIdsPresentation[presentationEmail.id!] = presentationEmail.copyWith(
|
||||||
|
emailInThreadStatus: presentationEmail.id == emailIdsPresentation.keys.last
|
||||||
|
? EmailInThreadStatus.expanded
|
||||||
|
: EmailInThreadStatus.collapsed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLoadMore) return;
|
||||||
|
|
||||||
|
final currentExpandedEmailIndex = currentExpandedEmailId.value == null
|
||||||
|
? -1
|
||||||
|
: emailIdsPresentation.keys.toList().indexOf(currentExpandedEmailId.value!);
|
||||||
|
final firstLoadedMoreEmailId = success.presentationEmails.firstOrNull?.id;
|
||||||
|
final firstLoadedMoreEmailIndex = firstLoadedMoreEmailId == null
|
||||||
|
? -1
|
||||||
|
: emailIdsPresentation.keys.toList().indexOf(firstLoadedMoreEmailId);
|
||||||
|
if (currentExpandedEmailIndex == -1 || firstLoadedMoreEmailIndex == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentBottomScrollPosition != null) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final newMaxScrollExtent = scrollController?.position.maxScrollExtent;
|
||||||
|
if (newMaxScrollExtent == null) return;
|
||||||
|
|
||||||
|
if (currentExpandedEmailIndex < firstLoadedMoreEmailIndex) {
|
||||||
|
return;
|
||||||
|
} else if (newMaxScrollExtent != maxScrollExtent!) {
|
||||||
|
scrollController?.jumpTo(newMaxScrollExtent - currentBottomScrollPosition);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
|
||||||
|
import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_detail_presentation_utils.dart';
|
||||||
|
|
||||||
|
extension InitializeThreadDetailEmails on ThreadDetailController {
|
||||||
|
void initializeThreadDetailEmails() {
|
||||||
|
final emailIdToLoadContent = emailIdsPresentation.keys.last;
|
||||||
|
final emailIdsToLoadMetaData = ThreadDetailPresentationUtils.getEmailIdsToLoad(
|
||||||
|
Map.from(emailIdsPresentation)..remove(emailIdToLoadContent),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (accountId == null || session == null) {
|
||||||
|
consumeState(Stream.value(Left(GetEmailsByIdsFailure(
|
||||||
|
exception: NotFoundSessionException(),
|
||||||
|
))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
consumeState(getEmailsByIdsInteractor.execute(
|
||||||
|
session!,
|
||||||
|
accountId!,
|
||||||
|
[...emailIdsToLoadMetaData, emailIdToLoadContent],
|
||||||
|
properties: EmailUtils.getPropertiesForEmailGetMethod(
|
||||||
|
session!,
|
||||||
|
accountId!,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
|
||||||
|
import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_detail_presentation_utils.dart';
|
||||||
|
|
||||||
|
extension LoadMoreThreadDetailEmails on ThreadDetailController {
|
||||||
|
void loadMoreThreadDetailEmails() {
|
||||||
|
if (accountId == null || session == null) {
|
||||||
|
consumeState(Stream.value(Left(GetEmailsByIdsFailure(
|
||||||
|
exception: NotFoundSessionException(),
|
||||||
|
))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final emailIdsToLoadMetaData = ThreadDetailPresentationUtils
|
||||||
|
.getEmailIdsToLoad(emailIdsPresentation);
|
||||||
|
if (emailIdsToLoadMetaData.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
consumeState(getEmailsByIdsInteractor.execute(
|
||||||
|
session!,
|
||||||
|
accountId!,
|
||||||
|
emailIdsToLoadMetaData,
|
||||||
|
properties: EmailUtils.getPropertiesForEmailGetMethod(
|
||||||
|
session!,
|
||||||
|
accountId!,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:model/email/presentation_email.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/bindings/email_bindings.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
|
||||||
|
import 'package:model/email/email_in_thread_status.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/focus_thread_detail_expanded_email.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
|
||||||
|
extension ToggleThreadDetailCollapeExpand on ThreadDetailController {
|
||||||
|
void toggleThreadDetailCollapeExpand(PresentationEmail presentationEmail) {
|
||||||
|
final emailId = presentationEmail.id;
|
||||||
|
final expansionStatus = presentationEmail.emailInThreadStatus;
|
||||||
|
if (emailId == null || expansionStatus == null) return;
|
||||||
|
|
||||||
|
if (expansionStatus == EmailInThreadStatus.expanded) {
|
||||||
|
emailIdsPresentation[emailId] = presentationEmail.copyWith(
|
||||||
|
emailInThreadStatus: EmailInThreadStatus.collapsed,
|
||||||
|
);
|
||||||
|
currentExpandedEmailId.value = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final isInitialized = Get.isRegistered<SingleEmailController>(
|
||||||
|
tag: emailId.id.value,
|
||||||
|
);
|
||||||
|
if (!isInitialized) {
|
||||||
|
EmailBindings(currentEmailId: presentationEmail.id).dependencies();
|
||||||
|
} else {
|
||||||
|
focusExpandedEmail(emailId);
|
||||||
|
}
|
||||||
|
for (var key in emailIdsPresentation.keys) {
|
||||||
|
if (emailIdsPresentation[key] == null) continue;
|
||||||
|
|
||||||
|
if (key == emailId) {
|
||||||
|
emailIdsPresentation[key] = presentationEmail.copyWith(
|
||||||
|
emailInThreadStatus: EmailInThreadStatus.expanded,
|
||||||
|
);
|
||||||
|
currentExpandedEmailId.value = key;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emailIdsPresentation[key]?.emailInThreadStatus == EmailInThreadStatus.expanded) {
|
||||||
|
emailIdsPresentation[key] = emailIdsPresentation[key]?.copyWith(
|
||||||
|
emailInThreadStatus: EmailInThreadStatus.collapsed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import 'package:core/data/model/source_type/data_source_type.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:jmap_dart_client/http/http_client.dart';
|
||||||
|
import 'package:tmail_ui_user/features/base/base_bindings.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_data_source.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_remote_data_source_impl.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/data/network/thread_detail_api.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/data/repository/thread_detail_repository_impl.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/repository/thread_detail_repository.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart';
|
||||||
|
|
||||||
|
class ThreadDetailBindings extends BaseBindings {
|
||||||
|
@override
|
||||||
|
void dependencies() {
|
||||||
|
Get.lazyPut(() => ThreadDetailApi(Get.find<HttpClient>()));
|
||||||
|
|
||||||
|
super.dependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void bindingsController() {
|
||||||
|
Get.put(ThreadDetailController(
|
||||||
|
Get.find<GetThreadByIdInteractor>(),
|
||||||
|
Get.find<GetEmailsByIdsInteractor>(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void bindingsDataSourceImpl() {
|
||||||
|
Get.lazyPut(() => ThreadDetailRemoteDataSourceImpl(
|
||||||
|
Get.find<ThreadDetailApi>(),
|
||||||
|
Get.find<RemoteExceptionThrower>(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void bindingsDataSource() {
|
||||||
|
Get.lazyPut<ThreadDetailDataSource>(() => Get.find<ThreadDetailRemoteDataSourceImpl>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void bindingsRepositoryImpl() {
|
||||||
|
Get.lazyPut(() => ThreadDetailRepositoryImpl({
|
||||||
|
DataSourceType.network: Get.find<ThreadDetailDataSource>(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void bindingsRepository() {
|
||||||
|
Get.lazyPut<ThreadDetailRepository>(() => Get.find<ThreadDetailRepositoryImpl>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void bindingsInteractor() {
|
||||||
|
Get.lazyPut(() => GetThreadByIdInteractor(Get.find<ThreadDetailRepository>()));
|
||||||
|
Get.lazyPut(() => GetEmailsByIdsInteractor(Get.find<ThreadDetailRepository>()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +1,108 @@
|
|||||||
|
import 'package:flutter/material.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/mailbox/mailbox.dart';
|
||||||
|
import 'package:model/email/presentation_email.dart';
|
||||||
|
import 'package:model/extensions/session_extension.dart';
|
||||||
|
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||||
import 'package:tmail_ui_user/features/base/base_controller.dart';
|
import 'package:tmail_ui_user/features/base/base_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/search/email/presentation/search_email_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
|
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_email_ids_by_thread_id_success.dart';
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_email_ids_by_thread_id_success.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/model/thread_detail_arguments.dart';
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart';
|
||||||
|
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||||
|
|
||||||
class ThreadDetailController extends BaseController {
|
class ThreadDetailController extends BaseController {
|
||||||
final ThreadDetailArguments arguments;
|
|
||||||
final GetThreadByIdInteractor _getEmailIdsByThreadIdInteractor;
|
final GetThreadByIdInteractor _getEmailIdsByThreadIdInteractor;
|
||||||
|
final GetEmailsByIdsInteractor getEmailsByIdsInteractor;
|
||||||
|
|
||||||
ThreadDetailController(
|
ThreadDetailController(
|
||||||
this.arguments,
|
|
||||||
this._getEmailIdsByThreadIdInteractor,
|
this._getEmailIdsByThreadIdInteractor,
|
||||||
|
this.getEmailsByIdsInteractor,
|
||||||
);
|
);
|
||||||
|
|
||||||
final emailIds = <EmailId>[].obs;
|
final emailIdsPresentation = <EmailId, PresentationEmail?>{}.obs;
|
||||||
|
final currentExpandedEmailId = Rxn<EmailId>();
|
||||||
|
final currentEmailLoaded = Rxn<EmailLoaded>();
|
||||||
|
|
||||||
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
||||||
|
final searchEmailController = Get.find<SearchEmailController>();
|
||||||
|
|
||||||
|
ScrollController? scrollController;
|
||||||
|
|
||||||
AccountId? get accountId => mailboxDashBoardController.accountId.value;
|
AccountId? get accountId => mailboxDashBoardController.accountId.value;
|
||||||
|
Session? get session => mailboxDashBoardController.sessionCurrent;
|
||||||
|
MailboxId? get sentMailboxId => mailboxDashBoardController.getMailboxIdByRole(
|
||||||
|
PresentationMailbox.roleSent,
|
||||||
|
);
|
||||||
|
String? get ownEmailAddress => session?.getOwnEmailAddress();
|
||||||
|
int get emailsToLoadMoreCount => emailIdsPresentation
|
||||||
|
.values
|
||||||
|
.where((email) => email == null)
|
||||||
|
.length;
|
||||||
|
bool get loadingThreadDetail => viewState.value.fold(
|
||||||
|
(failure) => false,
|
||||||
|
(success) => success is GettingThreadById
|
||||||
|
|| success is GettingEmailsByIds,
|
||||||
|
);
|
||||||
|
bool get isSearchRunning {
|
||||||
|
final isWebSearchRunning = mailboxDashBoardController
|
||||||
|
.searchController
|
||||||
|
.isSearchEmailRunning;
|
||||||
|
final isMobileSearchRunning = searchEmailController
|
||||||
|
.searchIsRunning
|
||||||
|
.value == true;
|
||||||
|
return isWebSearchRunning || isMobileSearchRunning;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
if (accountId != null) {
|
ever(mailboxDashBoardController.selectedEmail, (presentationEmail) {
|
||||||
consumeState(_getEmailIdsByThreadIdInteractor.execute(
|
if (presentationEmail?.threadId == null) {
|
||||||
arguments.threadId,
|
closeThreadDetailAction(currentContext);
|
||||||
accountId!,
|
return;
|
||||||
));
|
}
|
||||||
}
|
if (session != null &&
|
||||||
|
accountId != null &&
|
||||||
|
sentMailboxId != null &&
|
||||||
|
ownEmailAddress != null) {
|
||||||
|
scrollController = ScrollController();
|
||||||
|
consumeState(_getEmailIdsByThreadIdInteractor.execute(
|
||||||
|
presentationEmail!.threadId!,
|
||||||
|
session!,
|
||||||
|
accountId!,
|
||||||
|
sentMailboxId!,
|
||||||
|
ownEmailAddress!,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
emailIdsPresentation.clear();
|
||||||
|
scrollController?.dispose();
|
||||||
|
scrollController = null;
|
||||||
|
currentExpandedEmailId.value = null;
|
||||||
|
currentEmailLoaded.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void handleSuccessViewState(success) {
|
void handleSuccessViewState(success) {
|
||||||
if (success is GetThreadByIdSuccess) {
|
if (success is GetThreadByIdSuccess) {
|
||||||
handleGetEmailIdsByThreadIdSuccess(success);
|
handleGetEmailIdsByThreadIdSuccess(success);
|
||||||
|
initializeThreadDetailEmails();
|
||||||
|
} else if (success is GetEmailsByIdsSuccess) {
|
||||||
|
handleGetEmailsByIdsSuccess(success);
|
||||||
} else {
|
} else {
|
||||||
super.handleSuccessViewState(success);
|
super.handleSuccessViewState(success);
|
||||||
}
|
}
|
||||||
@@ -46,7 +111,11 @@ class ThreadDetailController extends BaseController {
|
|||||||
@override
|
@override
|
||||||
void handleFailureViewState(failure) {
|
void handleFailureViewState(failure) {
|
||||||
if (failure is GetThreadByIdFailure) {
|
if (failure is GetThreadByIdFailure) {
|
||||||
// TODO: handle failure
|
showRetryToast(failure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (failure is GetEmailsByIdsFailure) {
|
||||||
|
showRetryToast(failure);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
super.handleFailureViewState(failure);
|
super.handleFailureViewState(failure);
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:model/extensions/session_extension.dart';
|
||||||
|
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/email_view.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_bottom_bar_widget.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/get_thread_detail_loading_view.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/get_thread_details_email_views.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_app_bar.dart';
|
||||||
|
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||||
|
|
||||||
|
class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
||||||
|
const ThreadDetailView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final multipleEmailsView = SelectionArea(child: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Obx(() {
|
||||||
|
final currentViewState = controller.viewState.value.fold(id, id);
|
||||||
|
if (currentViewState is GettingThreadById) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ThreadDetailAppBar(
|
||||||
|
responsiveUtils: controller.responsiveUtils,
|
||||||
|
imagePaths: controller.imagePaths,
|
||||||
|
isSearchRunning: controller.isSearchRunning,
|
||||||
|
closeThreadDetailAction: controller.closeThreadDetailAction,
|
||||||
|
lastEmailOfThread: controller.emailIdsPresentation.values.lastOrNull,
|
||||||
|
ownUserName: controller.session?.getOwnEmailAddress() ?? '',
|
||||||
|
mailboxContain: _getMailboxContain(),
|
||||||
|
emailLoaded: getBinding<SingleEmailController>(
|
||||||
|
tag: controller.emailIdsPresentation.keys.lastOrNull?.id.value
|
||||||
|
)?.currentEmailLoaded.value,
|
||||||
|
onEmailActionClick: (email, action) {
|
||||||
|
// TODO: Next PR
|
||||||
|
},
|
||||||
|
onMoreActionClick: (p0, p1) {
|
||||||
|
// TODO: Next PR
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Obx(() => controller.getThreadDetailLoadingView(
|
||||||
|
isResponsiveDesktop: controller.responsiveUtils.isDesktop(context),
|
||||||
|
)),
|
||||||
|
Obx(() {
|
||||||
|
return controller.viewState.value.fold(
|
||||||
|
(failure) => const SizedBox.shrink(),
|
||||||
|
(success) {
|
||||||
|
if (success is GettingThreadById) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: _padding(context),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
const Positioned.fill(
|
||||||
|
child: ColoredBox(color: Colors.white),
|
||||||
|
),
|
||||||
|
SingleChildScrollView(
|
||||||
|
controller: controller.scrollController,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: controller.getThreadDetailEmailViews()
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Obx(() {
|
||||||
|
final currentViewState = controller.viewState.value.fold(id, id);
|
||||||
|
if (currentViewState is GettingThreadById) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
final expandedEmailId = controller.currentExpandedEmailId.value;
|
||||||
|
if (expandedEmailId == null) {
|
||||||
|
return _roundBottomPlaceHolder(
|
||||||
|
isDesktop: controller.responsiveUtils.isDesktop(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final expandedPresentationEmail = controller.emailIdsPresentation[expandedEmailId];
|
||||||
|
if (expandedPresentationEmail == null) {
|
||||||
|
return _roundBottomPlaceHolder(
|
||||||
|
isDesktop: controller.responsiveUtils.isDesktop(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentEmailLoaded = controller.currentEmailLoaded.value;
|
||||||
|
if (currentEmailLoaded == null) {
|
||||||
|
return _roundBottomPlaceHolder(
|
||||||
|
isDesktop: controller.responsiveUtils.isDesktop(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: controller.responsiveUtils.isDesktop(context)
|
||||||
|
? const EdgeInsetsDirectional.only(end: 16)
|
||||||
|
: EdgeInsets.zero,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.vertical(
|
||||||
|
bottom: Radius.circular(20),
|
||||||
|
),
|
||||||
|
child: EmailViewBottomBarWidget(
|
||||||
|
key: const Key('email_view_button_bar'),
|
||||||
|
imagePaths: controller.imagePaths,
|
||||||
|
responsiveUtils: controller.responsiveUtils,
|
||||||
|
emailLoaded: currentEmailLoaded,
|
||||||
|
presentationEmail: expandedPresentationEmail,
|
||||||
|
userName: controller.session?.getOwnEmailAddress() ?? '',
|
||||||
|
emailActionCallback: (action, email) {
|
||||||
|
controller.mailboxDashBoardController
|
||||||
|
..dispatchEmailUIAction(PerformEmailActionInThreadDetailAction(
|
||||||
|
emailActionType: action,
|
||||||
|
presentationEmail: email,
|
||||||
|
))
|
||||||
|
..dispatchEmailUIAction(EmailUIAction());
|
||||||
|
},
|
||||||
|
bottomBarDecoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(color: AppColor.colorDividerEmailView),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
if (controller.responsiveUtils.isDesktop(context))
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
return Obx(() {
|
||||||
|
final currentViewState = controller.viewState.value.fold(id, id);
|
||||||
|
if (currentViewState is GettingThreadById &&
|
||||||
|
controller.responsiveUtils.isTabletLarge(context)) {
|
||||||
|
return controller.getThreadDetailLoadingView(isResponsiveDesktop: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller.emailIdsPresentation.length == 1 &&
|
||||||
|
controller.emailIdsPresentation.values.firstOrNull != null) {
|
||||||
|
return EmailView(
|
||||||
|
emailId: controller.emailIdsPresentation.values.firstOrNull?.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return multipleEmailsView;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
EdgeInsetsGeometry _padding(BuildContext context) {
|
||||||
|
if (controller.responsiveUtils.isDesktop(context)) {
|
||||||
|
return const EdgeInsetsDirectional.only(end: 16);
|
||||||
|
}
|
||||||
|
return EdgeInsets.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
PresentationMailbox? _getMailboxContain() {
|
||||||
|
if (controller.isSearchRunning) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return controller.mailboxDashBoardController.selectedMailbox.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _roundBottomPlaceHolder({required bool isDesktop}) {
|
||||||
|
return Container(
|
||||||
|
height: 40,
|
||||||
|
margin: isDesktop
|
||||||
|
? const EdgeInsetsDirectional.only(end: 16)
|
||||||
|
: EdgeInsets.zero,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
bottom: Radius.circular(20),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
|
import 'package:model/email/presentation_email.dart';
|
||||||
|
|
||||||
|
class ThreadDetailPresentationUtils {
|
||||||
|
const ThreadDetailPresentationUtils._();
|
||||||
|
|
||||||
|
static const _initialLoadSize = 2;
|
||||||
|
static const _defaultLoadSize = 20;
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
static get initialLoadSize => _initialLoadSize;
|
||||||
|
@visibleForTesting
|
||||||
|
static get defaultLoadSize => _defaultLoadSize;
|
||||||
|
|
||||||
|
static List<EmailId> getEmailIdsToLoad(
|
||||||
|
Map<EmailId, PresentationEmail?> emailIdsPresentation,
|
||||||
|
) {
|
||||||
|
final validEmailIdsToLoad = emailIdsPresentation.entries
|
||||||
|
.where((entry) => entry.value == null)
|
||||||
|
.map((entry) => entry.key)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (validEmailIdsToLoad.length == emailIdsPresentation.length) {
|
||||||
|
// No email loaded yet
|
||||||
|
return validEmailIdsToLoad.sublist(
|
||||||
|
0,
|
||||||
|
min(validEmailIdsToLoad.length, _initialLoadSize),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return validEmailIdsToLoad.sublist(
|
||||||
|
validEmailIdsToLoad.length - min(validEmailIdsToLoad.length, _defaultLoadSize),
|
||||||
|
validEmailIdsToLoad.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
|
import 'package:core/presentation/utils/responsive_utils.dart';
|
||||||
|
import 'package:core/presentation/views/button/tmail_button_widget.dart';
|
||||||
|
import 'package:core/utils/platform_info.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:model/email/email_action_type.dart';
|
||||||
|
import 'package:model/email/presentation_email.dart';
|
||||||
|
import 'package:model/extensions/email_extension.dart';
|
||||||
|
import 'package:model/extensions/presentation_email_extension.dart';
|
||||||
|
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_app_bar_widget_styles.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_app_bar_widget.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_back_button.dart';
|
||||||
|
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||||
|
|
||||||
|
class ThreadDetailAppBar extends StatelessWidget {
|
||||||
|
const ThreadDetailAppBar({
|
||||||
|
super.key,
|
||||||
|
required this.responsiveUtils,
|
||||||
|
required this.imagePaths,
|
||||||
|
required this.isSearchRunning,
|
||||||
|
required this.closeThreadDetailAction,
|
||||||
|
required this.lastEmailOfThread,
|
||||||
|
required this.ownUserName,
|
||||||
|
this.mailboxContain,
|
||||||
|
this.optionWidgets = const [],
|
||||||
|
this.onEmailActionClick,
|
||||||
|
this.onMoreActionClick,
|
||||||
|
this.emailLoaded,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ResponsiveUtils responsiveUtils;
|
||||||
|
final ImagePaths imagePaths;
|
||||||
|
final bool isSearchRunning;
|
||||||
|
final void Function(BuildContext context) closeThreadDetailAction;
|
||||||
|
final PresentationEmail? lastEmailOfThread;
|
||||||
|
final String ownUserName;
|
||||||
|
final PresentationMailbox? mailboxContain;
|
||||||
|
final List<Widget> optionWidgets;
|
||||||
|
final OnEmailActionClick? onEmailActionClick;
|
||||||
|
final OnMoreActionClick? onMoreActionClick;
|
||||||
|
final EmailLoaded? emailLoaded;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isReplyToListEnabled = EmailUtils.isReplyToListEnabled(
|
||||||
|
emailLoaded?.emailCurrent?.listPost ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
final child = LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return Container(
|
||||||
|
height: PlatformInfo.isIOS
|
||||||
|
? EmailViewAppBarWidgetStyles.heightIOS(context, responsiveUtils)
|
||||||
|
: EmailViewAppBarWidgetStyles.height,
|
||||||
|
padding: PlatformInfo.isIOS
|
||||||
|
? EmailViewAppBarWidgetStyles.paddingIOS(context, responsiveUtils)
|
||||||
|
: EmailViewAppBarWidgetStyles.padding,
|
||||||
|
margin: !PlatformInfo.isMobile && responsiveUtils.isDesktop(context)
|
||||||
|
? const EdgeInsetsDirectional.only(end: 16)
|
||||||
|
: EdgeInsets.zero,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: EmailViewAppBarWidgetStyles.bottomBorderColor,
|
||||||
|
width: EmailViewAppBarWidgetStyles.bottomBorderWidth,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
top: Radius.circular(EmailViewAppBarWidgetStyles.radius),
|
||||||
|
),
|
||||||
|
color: EmailViewAppBarWidgetStyles.backgroundColor,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
if (_supportDisplayMailboxNameTitle(context))
|
||||||
|
Expanded(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: EmailViewBackButton(
|
||||||
|
imagePaths: imagePaths,
|
||||||
|
onBackAction: () => closeThreadDetailAction(context),
|
||||||
|
mailboxContain: mailboxContain,
|
||||||
|
isSearchActivated: isSearchRunning,
|
||||||
|
maxWidth: constraints.maxWidth,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...optionWidgets,
|
||||||
|
if (lastEmailOfThread != null) ...[
|
||||||
|
TMailButtonWidget.fromIcon(
|
||||||
|
icon: imagePaths.icReply,
|
||||||
|
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||||
|
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||||
|
tooltipMessage: AppLocalizations.of(context).reply,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
onTapActionCallback: () => onEmailActionClick?.call(
|
||||||
|
lastEmailOfThread!,
|
||||||
|
EmailActionType.reply,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (lastEmailOfThread!.getCountMailAddressWithoutMe(ownUserName) > 1)
|
||||||
|
TMailButtonWidget.fromIcon(
|
||||||
|
icon: imagePaths.icReplyAll,
|
||||||
|
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||||
|
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||||
|
tooltipMessage: AppLocalizations.of(context).reply_all,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
onTapActionCallback: () => onEmailActionClick?.call(
|
||||||
|
lastEmailOfThread!,
|
||||||
|
EmailActionType.replyAll,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isReplyToListEnabled)
|
||||||
|
TMailButtonWidget.fromIcon(
|
||||||
|
icon: imagePaths.icReply,
|
||||||
|
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||||
|
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||||
|
tooltipMessage: AppLocalizations.of(context).replyToList,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
onTapActionCallback: () => onEmailActionClick?.call(
|
||||||
|
lastEmailOfThread!,
|
||||||
|
EmailActionType.replyToList,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TMailButtonWidget.fromIcon(
|
||||||
|
icon: imagePaths.icForward,
|
||||||
|
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||||
|
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||||
|
tooltipMessage: AppLocalizations.of(context).forward,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
onTapActionCallback: () => onEmailActionClick?.call(
|
||||||
|
lastEmailOfThread!,
|
||||||
|
EmailActionType.forward,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TMailButtonWidget.fromIcon(
|
||||||
|
icon: lastEmailOfThread!.hasStarred
|
||||||
|
? imagePaths.icStar
|
||||||
|
: imagePaths.icUnStar,
|
||||||
|
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||||
|
iconColor: lastEmailOfThread!.hasStarred
|
||||||
|
? null
|
||||||
|
: EmailViewAppBarWidgetStyles.iconColor,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
tooltipMessage: lastEmailOfThread!.hasStarred
|
||||||
|
? AppLocalizations.of(context).not_starred
|
||||||
|
: AppLocalizations.of(context).mark_as_starred,
|
||||||
|
onTapActionCallback: () => onEmailActionClick?.call(
|
||||||
|
lastEmailOfThread!,
|
||||||
|
lastEmailOfThread!.hasStarred
|
||||||
|
? EmailActionType.unMarkAsStarred
|
||||||
|
: EmailActionType.markAsStarred
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TMailButtonWidget.fromIcon(
|
||||||
|
icon: imagePaths.icMoreVertical,
|
||||||
|
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||||
|
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
tooltipMessage: AppLocalizations.of(context).more,
|
||||||
|
onTapActionCallback: responsiveUtils.isScreenWithShortestSide(context)
|
||||||
|
? () => onMoreActionClick?.call(lastEmailOfThread!, null)
|
||||||
|
: null,
|
||||||
|
onTapActionAtPositionCallback: !responsiveUtils.isScreenWithShortestSide(context)
|
||||||
|
? (position) => onMoreActionClick?.call(lastEmailOfThread!, position)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return PopScope(
|
||||||
|
canPop: false,
|
||||||
|
onPopInvokedWithResult: (_, __) {
|
||||||
|
closeThreadDetailAction(context);
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _supportDisplayMailboxNameTitle(BuildContext context) {
|
||||||
|
final isSupportedDevice = PlatformInfo.isWeb
|
||||||
|
? responsiveUtils.isDesktop(context)
|
||||||
|
|| responsiveUtils.isMobile(context)
|
||||||
|
|| responsiveUtils.isTablet(context)
|
||||||
|
: responsiveUtils.isPortraitMobile(context)
|
||||||
|
|| responsiveUtils.isLandscapeMobile(context)
|
||||||
|
|| responsiveUtils.isTablet(context);
|
||||||
|
return isSupportedDevice || isSearchRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
|
import 'package:core/presentation/utils/responsive_utils.dart';
|
||||||
|
import 'package:core/presentation/views/text/text_overflow_builder.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:model/email/presentation_email.dart';
|
||||||
|
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/widgets/email_sender_builder.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/widgets/email_subject_widget.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_app_bar_widget.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/widgets/information_sender_and_receiver_builder.dart';
|
||||||
|
|
||||||
|
class ThreadDetailCollapsedEmail extends StatelessWidget {
|
||||||
|
const ThreadDetailCollapsedEmail({
|
||||||
|
super.key,
|
||||||
|
required this.presentationEmail,
|
||||||
|
required this.showSubject,
|
||||||
|
required this.imagePaths,
|
||||||
|
required this.responsiveUtils,
|
||||||
|
this.openEmailAddressDetailAction,
|
||||||
|
this.mailboxContain,
|
||||||
|
this.emailLoaded,
|
||||||
|
this.onEmailActionClick,
|
||||||
|
this.onMoreActionClick,
|
||||||
|
this.onToggleThreadDetailCollapseExpand,
|
||||||
|
});
|
||||||
|
|
||||||
|
final PresentationEmail presentationEmail;
|
||||||
|
final bool showSubject;
|
||||||
|
final ImagePaths imagePaths;
|
||||||
|
final ResponsiveUtils responsiveUtils;
|
||||||
|
final OnOpenEmailAddressDetailAction? openEmailAddressDetailAction;
|
||||||
|
final PresentationMailbox? mailboxContain;
|
||||||
|
final EmailLoaded? emailLoaded;
|
||||||
|
final OnEmailActionClick? onEmailActionClick;
|
||||||
|
final OnMoreActionClick? onMoreActionClick;
|
||||||
|
final VoidCallback? onToggleThreadDetailCollapseExpand;
|
||||||
|
|
||||||
|
String get preview => presentationEmail.getPartialContent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
color: AppColor.colorDividerEmailView,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onToggleThreadDetailCollapseExpand,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (showSubject)
|
||||||
|
EmailSubjectWidget(presentationEmail: presentationEmail)
|
||||||
|
else
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
InformationSenderAndReceiverBuilder(
|
||||||
|
emailSelected: presentationEmail,
|
||||||
|
responsiveUtils: responsiveUtils,
|
||||||
|
imagePaths: imagePaths,
|
||||||
|
emailLoaded: emailLoaded,
|
||||||
|
isInsideThreadDetailView: true,
|
||||||
|
onEmailActionClick: onEmailActionClick,
|
||||||
|
onMoreActionClick: onMoreActionClick,
|
||||||
|
openEmailAddressDetailAction: openEmailAddressDetailAction,
|
||||||
|
showRecipients: false,
|
||||||
|
onTapAvatarActionClick: onToggleThreadDetailCollapseExpand,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: TextOverflowBuilder(
|
||||||
|
preview,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
|
import 'package:core/presentation/views/loading/cupertino_loading_widget.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
|
class ThreadDetailLoadMoreCircle extends StatefulWidget {
|
||||||
|
const ThreadDetailLoadMoreCircle({
|
||||||
|
super.key,
|
||||||
|
required this.count,
|
||||||
|
required this.onTap,
|
||||||
|
required this.imagePaths,
|
||||||
|
required this.isLoading,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int count;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final ImagePaths imagePaths;
|
||||||
|
final bool isLoading;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ThreadDetailLoadMoreCircle> createState() => _ThreadDetailLoadMoreCircleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ThreadDetailLoadMoreCircleState extends State<ThreadDetailLoadMoreCircle> {
|
||||||
|
final _isHover = ValueNotifier(false);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_isHover.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
color: Colors.white,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 8,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border.symmetric(horizontal: BorderSide(
|
||||||
|
color: AppColor.colorDividerEmailView,
|
||||||
|
width: 0.5,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: widget.onTap,
|
||||||
|
onHover: (value) => _isHover.value = value,
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsetsDirectional.only(start: 16),
|
||||||
|
width: 56,
|
||||||
|
height: 56,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: AppColor.colorDividerEmailView,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: ValueListenableBuilder(
|
||||||
|
valueListenable: _isHover,
|
||||||
|
builder: (context, isHover, child) {
|
||||||
|
if (widget.isLoading) {
|
||||||
|
return const SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: CupertinoLoadingWidget(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHover) {
|
||||||
|
return child ?? const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Text(
|
||||||
|
'${widget.count}',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
height: 24 / 16,
|
||||||
|
letterSpacing: -0.1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: SvgPicture.asset(widget.imagePaths.icExpandArrows),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
enum EmailInThreadStatus { collapsed, expanded }
|
||||||
@@ -15,6 +15,7 @@ class EmailProperty {
|
|||||||
static const String bcc = 'bcc';
|
static const String bcc = 'bcc';
|
||||||
static const String replyTo = 'replyTo';
|
static const String replyTo = 'replyTo';
|
||||||
static const String mailboxIds = 'mailboxIds';
|
static const String mailboxIds = 'mailboxIds';
|
||||||
|
static const String threadId = 'threadId';
|
||||||
static const String bodyValues = 'bodyValues';
|
static const String bodyValues = 'bodyValues';
|
||||||
static const String htmlBody = 'htmlBody';
|
static const String htmlBody = 'htmlBody';
|
||||||
static const String attachments = 'attachments';
|
static const String attachments = 'attachments';
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import 'package:jmap_dart_client/jmap/mail/email/individual_header_identifier.da
|
|||||||
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:model/email/email_content.dart';
|
import 'package:model/email/email_content.dart';
|
||||||
|
import 'package:model/email/email_in_thread_status.dart';
|
||||||
import 'package:model/email/mail_priority_header.dart';
|
import 'package:model/email/mail_priority_header.dart';
|
||||||
import 'package:model/extensions/email_address_extension.dart';
|
import 'package:model/extensions/email_address_extension.dart';
|
||||||
import 'package:model/extensions/keyword_identifier_extension.dart';
|
import 'package:model/extensions/keyword_identifier_extension.dart';
|
||||||
@@ -38,6 +39,7 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
|
|||||||
final Set<EmailAddress>? bcc;
|
final Set<EmailAddress>? bcc;
|
||||||
final Set<EmailAddress>? replyTo;
|
final Set<EmailAddress>? replyTo;
|
||||||
final Map<MailboxId, bool>? mailboxIds;
|
final Map<MailboxId, bool>? mailboxIds;
|
||||||
|
final ThreadId? threadId;
|
||||||
final SelectMode selectMode;
|
final SelectMode selectMode;
|
||||||
final Uri? routeWeb;
|
final Uri? routeWeb;
|
||||||
final PresentationMailbox? mailboxContain;
|
final PresentationMailbox? mailboxContain;
|
||||||
@@ -48,6 +50,7 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
|
|||||||
final Map<IndividualHeaderIdentifier, String?>? xPriorityHeader;
|
final Map<IndividualHeaderIdentifier, String?>? xPriorityHeader;
|
||||||
final Map<IndividualHeaderIdentifier, String?>? importanceHeader;
|
final Map<IndividualHeaderIdentifier, String?>? importanceHeader;
|
||||||
final Map<IndividualHeaderIdentifier, String?>? priorityHeader;
|
final Map<IndividualHeaderIdentifier, String?>? priorityHeader;
|
||||||
|
final EmailInThreadStatus? emailInThreadStatus;
|
||||||
|
|
||||||
PresentationEmail({
|
PresentationEmail({
|
||||||
this.id,
|
this.id,
|
||||||
@@ -65,6 +68,7 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
|
|||||||
this.bcc,
|
this.bcc,
|
||||||
this.replyTo,
|
this.replyTo,
|
||||||
this.mailboxIds,
|
this.mailboxIds,
|
||||||
|
this.threadId,
|
||||||
this.selectMode = SelectMode.INACTIVE,
|
this.selectMode = SelectMode.INACTIVE,
|
||||||
this.routeWeb,
|
this.routeWeb,
|
||||||
this.mailboxContain,
|
this.mailboxContain,
|
||||||
@@ -75,6 +79,7 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
|
|||||||
this.xPriorityHeader,
|
this.xPriorityHeader,
|
||||||
this.importanceHeader,
|
this.importanceHeader,
|
||||||
this.priorityHeader,
|
this.priorityHeader,
|
||||||
|
this.emailInThreadStatus,
|
||||||
});
|
});
|
||||||
|
|
||||||
String getSenderName() {
|
String getSenderName() {
|
||||||
@@ -183,6 +188,7 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
|
|||||||
bcc,
|
bcc,
|
||||||
replyTo,
|
replyTo,
|
||||||
mailboxIds,
|
mailboxIds,
|
||||||
|
threadId,
|
||||||
selectMode,
|
selectMode,
|
||||||
routeWeb,
|
routeWeb,
|
||||||
mailboxContain,
|
mailboxContain,
|
||||||
@@ -195,6 +201,7 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
|
|||||||
xPriorityHeader,
|
xPriorityHeader,
|
||||||
importanceHeader,
|
importanceHeader,
|
||||||
priorityHeader,
|
priorityHeader,
|
||||||
|
emailInThreadStatus,
|
||||||
];
|
];
|
||||||
|
|
||||||
PresentationEmail copyWith({
|
PresentationEmail copyWith({
|
||||||
@@ -213,6 +220,7 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
|
|||||||
Set<EmailAddress>? bcc,
|
Set<EmailAddress>? bcc,
|
||||||
Set<EmailAddress>? replyTo,
|
Set<EmailAddress>? replyTo,
|
||||||
Map<MailboxId, bool>? mailboxIds,
|
Map<MailboxId, bool>? mailboxIds,
|
||||||
|
ThreadId? threadId,
|
||||||
SelectMode? selectMode,
|
SelectMode? selectMode,
|
||||||
Uri? routeWeb,
|
Uri? routeWeb,
|
||||||
PresentationMailbox? mailboxContain,
|
PresentationMailbox? mailboxContain,
|
||||||
@@ -223,6 +231,7 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
|
|||||||
Map<IndividualHeaderIdentifier, String?>? xPriorityHeader,
|
Map<IndividualHeaderIdentifier, String?>? xPriorityHeader,
|
||||||
Map<IndividualHeaderIdentifier, String?>? importanceHeader,
|
Map<IndividualHeaderIdentifier, String?>? importanceHeader,
|
||||||
Map<IndividualHeaderIdentifier, String?>? priorityHeader,
|
Map<IndividualHeaderIdentifier, String?>? priorityHeader,
|
||||||
|
EmailInThreadStatus? emailInThreadStatus,
|
||||||
}) {
|
}) {
|
||||||
return PresentationEmail(
|
return PresentationEmail(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@@ -240,6 +249,7 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
|
|||||||
bcc: bcc ?? this.bcc,
|
bcc: bcc ?? this.bcc,
|
||||||
replyTo: replyTo ?? this.replyTo,
|
replyTo: replyTo ?? this.replyTo,
|
||||||
mailboxIds: mailboxIds ?? this.mailboxIds,
|
mailboxIds: mailboxIds ?? this.mailboxIds,
|
||||||
|
threadId: threadId ?? this.threadId,
|
||||||
selectMode: selectMode ?? this.selectMode,
|
selectMode: selectMode ?? this.selectMode,
|
||||||
routeWeb: routeWeb ?? this.routeWeb,
|
routeWeb: routeWeb ?? this.routeWeb,
|
||||||
mailboxContain: mailboxContain ?? this.mailboxContain,
|
mailboxContain: mailboxContain ?? this.mailboxContain,
|
||||||
@@ -250,6 +260,7 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
|
|||||||
xPriorityHeader: xPriorityHeader ?? this.xPriorityHeader,
|
xPriorityHeader: xPriorityHeader ?? this.xPriorityHeader,
|
||||||
importanceHeader: importanceHeader ?? this.importanceHeader,
|
importanceHeader: importanceHeader ?? this.importanceHeader,
|
||||||
priorityHeader: priorityHeader ?? this.priorityHeader,
|
priorityHeader: priorityHeader ?? this.priorityHeader,
|
||||||
|
emailInThreadStatus: emailInThreadStatus ?? this.emailInThreadStatus,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,6 +116,7 @@ extension EmailExtension on Email {
|
|||||||
bcc: bcc,
|
bcc: bcc,
|
||||||
replyTo: replyTo,
|
replyTo: replyTo,
|
||||||
mailboxIds: mailboxIds,
|
mailboxIds: mailboxIds,
|
||||||
|
threadId: threadId,
|
||||||
selectMode: selectMode,
|
selectMode: selectMode,
|
||||||
emailHeader: headers?.toList(),
|
emailHeader: headers?.toList(),
|
||||||
bodyValues: bodyValues,
|
bodyValues: bodyValues,
|
||||||
@@ -146,6 +147,7 @@ extension EmailExtension on Email {
|
|||||||
bcc: updatedProperties.contain(EmailProperty.bcc) ? newEmail.bcc : bcc,
|
bcc: updatedProperties.contain(EmailProperty.bcc) ? newEmail.bcc : bcc,
|
||||||
replyTo: updatedProperties.contain(EmailProperty.replyTo) ? newEmail.replyTo : replyTo,
|
replyTo: updatedProperties.contain(EmailProperty.replyTo) ? newEmail.replyTo : replyTo,
|
||||||
mailboxIds: updatedProperties.contain(EmailProperty.mailboxIds) ? newEmail.mailboxIds : mailboxIds,
|
mailboxIds: updatedProperties.contain(EmailProperty.mailboxIds) ? newEmail.mailboxIds : mailboxIds,
|
||||||
|
threadId: updatedProperties.contain(EmailProperty.threadId) ? newEmail.threadId : threadId,
|
||||||
headerCalendarEvent: updatedProperties.contain(IndividualHeaderIdentifier.headerCalendarEvent.value) ? newEmail.headerCalendarEvent : headerCalendarEvent,
|
headerCalendarEvent: updatedProperties.contain(IndividualHeaderIdentifier.headerCalendarEvent.value) ? newEmail.headerCalendarEvent : headerCalendarEvent,
|
||||||
xPriorityHeader: updatedProperties.contain(IndividualHeaderIdentifier.xPriorityHeader.value)
|
xPriorityHeader: updatedProperties.contain(IndividualHeaderIdentifier.xPriorityHeader.value)
|
||||||
? newEmail.xPriorityHeader
|
? newEmail.xPriorityHeader
|
||||||
|
|||||||
@@ -34,4 +34,8 @@ extension ListEmailExtension on List<Email> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Email> sortWithResult(Comparator comparator) {
|
||||||
|
return List.from(this..sortBy(comparator));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,10 +8,12 @@ import 'package:jmap_dart_client/jmap/account_id.dart';
|
|||||||
import 'package:jmap_dart_client/jmap/core/capability/calendar_event_capability.dart';
|
import 'package:jmap_dart_client/jmap/core/capability/calendar_event_capability.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart';
|
import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/capability/capability_properties.dart';
|
import 'package:jmap_dart_client/jmap/core/capability/capability_properties.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/capability/core_capability.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/capability/default_capability.dart';
|
import 'package:jmap_dart_client/jmap/core/capability/default_capability.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/capability/empty_capability.dart';
|
import 'package:jmap_dart_client/jmap/core/capability/empty_capability.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/id.dart';
|
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/session/session.dart';
|
import 'package:jmap_dart_client/jmap/core/session/session.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
|
||||||
import 'package:model/error_type_handler/account_exception.dart';
|
import 'package:model/error_type_handler/account_exception.dart';
|
||||||
import 'package:model/error_type_handler/unknown_address_exception.dart';
|
import 'package:model/error_type_handler/unknown_address_exception.dart';
|
||||||
import 'package:model/error_type_handler/unknown_uri_exception.dart';
|
import 'package:model/error_type_handler/unknown_uri_exception.dart';
|
||||||
@@ -181,4 +183,16 @@ extension SessionExtension on Session {
|
|||||||
return supportedLanguages.firstOrNull;
|
return supportedLanguages.firstOrNull;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UnsignedInt? getMaxObjectsInGet(AccountId accountId) {
|
||||||
|
try {
|
||||||
|
return getCapabilityProperties<CoreCapability>(
|
||||||
|
accountId,
|
||||||
|
CapabilityIdentifier.jmapCore
|
||||||
|
)?.maxObjectsInGet;
|
||||||
|
} catch (e) {
|
||||||
|
logError('SessionExtensions::getMaxObjectsInGet():Exception: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -239,14 +239,14 @@ void main() {
|
|||||||
when(mailboxDashboardController.emailUIAction).thenReturn(Rxn(null));
|
when(mailboxDashboardController.emailUIAction).thenReturn(Rxn(null));
|
||||||
when(mailboxDashboardController.viewState).thenReturn(Rx(Right(UIState.idle)));
|
when(mailboxDashboardController.viewState).thenReturn(Rx(Right(UIState.idle)));
|
||||||
when(mailboxDashboardController.sessionCurrent).thenReturn(testSession);
|
when(mailboxDashboardController.sessionCurrent).thenReturn(testSession);
|
||||||
singleEmailController.onInit();
|
|
||||||
Get.put<AcceptCalendarEventInteractor>(acceptCalendarEventInteractor);
|
Get.put<AcceptCalendarEventInteractor>(acceptCalendarEventInteractor);
|
||||||
mailboxDashboardController.accountId.refresh();
|
singleEmailController.onInit();
|
||||||
singleEmailController.handleSuccessViewState(
|
mailboxDashboardController.accountId.refresh();
|
||||||
ParseCalendarEventSuccess([
|
singleEmailController.handleSuccessViewState(
|
||||||
BlobCalendarEvent(
|
ParseCalendarEventSuccess([
|
||||||
blobId: blobId,
|
BlobCalendarEvent(
|
||||||
calendarEventList: [calendarEvent])]));
|
blobId: blobId,
|
||||||
|
calendarEventList: [calendarEvent])]));
|
||||||
|
|
||||||
// act
|
// act
|
||||||
singleEmailController.onCalendarEventReplyAction(EventActionType.yes, emailId);
|
singleEmailController.onCalendarEventReplyAction(EventActionType.yes, emailId);
|
||||||
@@ -267,8 +267,8 @@ void main() {
|
|||||||
when(mailboxDashboardController.emailUIAction).thenReturn(Rxn(null));
|
when(mailboxDashboardController.emailUIAction).thenReturn(Rxn(null));
|
||||||
when(mailboxDashboardController.viewState).thenReturn(Rx(Right(UIState.idle)));
|
when(mailboxDashboardController.viewState).thenReturn(Rx(Right(UIState.idle)));
|
||||||
when(mailboxDashboardController.sessionCurrent).thenReturn(testSession);
|
when(mailboxDashboardController.sessionCurrent).thenReturn(testSession);
|
||||||
singleEmailController.onInit();
|
|
||||||
Get.put<MaybeCalendarEventInteractor>(maybeCalendarEventInteractor);
|
Get.put<MaybeCalendarEventInteractor>(maybeCalendarEventInteractor);
|
||||||
|
singleEmailController.onInit();
|
||||||
mailboxDashboardController.accountId.refresh();
|
mailboxDashboardController.accountId.refresh();
|
||||||
singleEmailController.handleSuccessViewState(
|
singleEmailController.handleSuccessViewState(
|
||||||
ParseCalendarEventSuccess([
|
ParseCalendarEventSuccess([
|
||||||
@@ -295,8 +295,8 @@ void main() {
|
|||||||
when(mailboxDashboardController.emailUIAction).thenReturn(Rxn(null));
|
when(mailboxDashboardController.emailUIAction).thenReturn(Rxn(null));
|
||||||
when(mailboxDashboardController.viewState).thenReturn(Rx(Right(UIState.idle)));
|
when(mailboxDashboardController.viewState).thenReturn(Rx(Right(UIState.idle)));
|
||||||
when(mailboxDashboardController.sessionCurrent).thenReturn(testSession);
|
when(mailboxDashboardController.sessionCurrent).thenReturn(testSession);
|
||||||
singleEmailController.onInit();
|
|
||||||
Get.put<RejectCalendarEventInteractor>(rejectCalendarEventInteractor);
|
Get.put<RejectCalendarEventInteractor>(rejectCalendarEventInteractor);
|
||||||
|
singleEmailController.onInit();
|
||||||
mailboxDashboardController.accountId.refresh();
|
mailboxDashboardController.accountId.refresh();
|
||||||
singleEmailController.handleSuccessViewState(
|
singleEmailController.handleSuccessViewState(
|
||||||
ParseCalendarEventSuccess([
|
ParseCalendarEventSuccess([
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
|
import 'package:mockito/annotations.dart';
|
||||||
|
import 'package:model/email/email_property.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/exceptions/thread_detail_overload_exception.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/repository/thread_detail_repository.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.dart';
|
||||||
|
|
||||||
|
import '../../../../fixtures/account_fixtures.dart';
|
||||||
|
import '../../../../fixtures/session_fixtures.dart';
|
||||||
|
import 'get_emails_by_ids_interactor_test.mocks.dart';
|
||||||
|
|
||||||
|
@GenerateNiceMocks([MockSpec<ThreadDetailRepository>()])
|
||||||
|
void main() {
|
||||||
|
final repository = MockThreadDetailRepository();
|
||||||
|
final getEmailsByIdsInteractor = GetEmailsByIdsInteractor(repository);
|
||||||
|
|
||||||
|
group('get emails by ids interactor test:', () {
|
||||||
|
test(
|
||||||
|
'should throw ThreadDetailOverloadException '
|
||||||
|
'when there are more than 1 emailIds '
|
||||||
|
'and properties contains any of htmlBody, bodyValues or attachments',
|
||||||
|
() async {
|
||||||
|
// arrange
|
||||||
|
final emailIds = [EmailId(Id('1')), EmailId(Id('2'))];
|
||||||
|
final properties = Properties({
|
||||||
|
EmailProperty.htmlBody,
|
||||||
|
EmailProperty.bodyValues,
|
||||||
|
EmailProperty.attachments,
|
||||||
|
});
|
||||||
|
final result = await getEmailsByIdsInteractor.execute(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
emailIds,
|
||||||
|
properties: properties,
|
||||||
|
).last;
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(
|
||||||
|
result.fold(
|
||||||
|
(failure) => failure is GetEmailsByIdsFailure &&
|
||||||
|
failure.exception is ThreadDetailOverloadException,
|
||||||
|
(success) => false,
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
|
import 'package:mockito/annotations.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:model/email/presentation_email.dart';
|
||||||
|
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_email_ids_by_thread_id_success.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
|
||||||
|
import 'handle_get_email_ids_by_thread_id_success_test.mocks.dart';
|
||||||
|
|
||||||
|
@GenerateNiceMocks([
|
||||||
|
MockSpec<ThreadDetailController>(),
|
||||||
|
MockSpec<MailboxDashBoardController>(),
|
||||||
|
])
|
||||||
|
void main() {
|
||||||
|
final threadDetailController = MockThreadDetailController();
|
||||||
|
group('handle get email ids by thread id success test:', () {
|
||||||
|
test(
|
||||||
|
'should assign emailIds with result from success '
|
||||||
|
'when result from success is not empty',
|
||||||
|
() {
|
||||||
|
// arrange
|
||||||
|
final success = GetThreadByIdSuccess([
|
||||||
|
EmailId(Id('1')),
|
||||||
|
EmailId(Id('2')),
|
||||||
|
]);
|
||||||
|
when(threadDetailController.emailIdsPresentation).thenReturn(
|
||||||
|
<EmailId, PresentationEmail?>{}.obs,
|
||||||
|
);
|
||||||
|
|
||||||
|
// act
|
||||||
|
threadDetailController.handleGetEmailIdsByThreadIdSuccess(success);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(threadDetailController.emailIdsPresentation.keys, success.emailIds);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'should assign emailIds with result from mailbox dashboard controller '
|
||||||
|
'when result from success is empty '
|
||||||
|
'and mailbox dashboard controller selected email is not null',
|
||||||
|
() {
|
||||||
|
// arrange
|
||||||
|
final mailboxDashBoardController = MockMailboxDashBoardController();
|
||||||
|
final success = GetThreadByIdSuccess([]);
|
||||||
|
when(mailboxDashBoardController.selectedEmail).thenReturn(
|
||||||
|
Rxn(PresentationEmail(id: EmailId(Id('1')))),
|
||||||
|
);
|
||||||
|
when(threadDetailController.emailIdsPresentation).thenReturn(
|
||||||
|
<EmailId, PresentationEmail?>{}.obs,
|
||||||
|
);
|
||||||
|
when(threadDetailController.mailboxDashBoardController).thenReturn(mailboxDashBoardController);
|
||||||
|
|
||||||
|
// act
|
||||||
|
threadDetailController.handleGetEmailIdsByThreadIdSuccess(success);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(
|
||||||
|
threadDetailController.emailIdsPresentation.keys,
|
||||||
|
[mailboxDashBoardController.selectedEmail.value!.id!],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
|
import 'package:mockito/annotations.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
|
||||||
|
import '../../../../fixtures/session_fixtures.dart';
|
||||||
|
import 'initialize_thread_detail_emails_test.mocks.dart';
|
||||||
|
|
||||||
|
@GenerateNiceMocks([
|
||||||
|
MockSpec<GetEmailsByIdsInteractor>(),
|
||||||
|
MockSpec<ThreadDetailController>(),
|
||||||
|
])
|
||||||
|
void main() {
|
||||||
|
group('InitializeThreadDetailEmails', () {
|
||||||
|
late MockGetEmailsByIdsInteractor getEmailsByIdsInteractor;
|
||||||
|
late MockThreadDetailController threadDetailController;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
getEmailsByIdsInteractor = MockGetEmailsByIdsInteractor();
|
||||||
|
threadDetailController = MockThreadDetailController();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initializeThreadDetailEmails should call getEmailsByIdsInteractor with 3 elements', () async {
|
||||||
|
// Arrange
|
||||||
|
final emailIds = [
|
||||||
|
EmailId(Id('email1')),
|
||||||
|
EmailId(Id('email2')),
|
||||||
|
EmailId(Id('email3')),
|
||||||
|
EmailId(Id('email4')),
|
||||||
|
EmailId(Id('email5')),
|
||||||
|
];
|
||||||
|
|
||||||
|
when(threadDetailController.emailIdsPresentation).thenReturn(
|
||||||
|
Map.fromEntries(emailIds.map((emailId) => MapEntry(emailId, null))).obs,
|
||||||
|
);
|
||||||
|
when(threadDetailController.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor);
|
||||||
|
when(getEmailsByIdsInteractor.execute(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
properties: anyNamed('properties'),
|
||||||
|
)).thenAnswer((_) => const Stream.empty());
|
||||||
|
final accountId = AccountId(Id('accountId'));
|
||||||
|
final session = SessionFixtures.aliceSession;
|
||||||
|
when(threadDetailController.accountId).thenReturn(accountId);
|
||||||
|
when(threadDetailController.session).thenReturn(session);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
threadDetailController.initializeThreadDetailEmails();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
final captured = verify(getEmailsByIdsInteractor.execute(
|
||||||
|
captureAny,
|
||||||
|
captureAny,
|
||||||
|
captureAny,
|
||||||
|
properties: captureAnyNamed('properties'),
|
||||||
|
)).captured;
|
||||||
|
|
||||||
|
expect((captured[2] as List<EmailId>).length, 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
|
import 'package:mockito/annotations.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:model/email/presentation_email.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.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/thread_detail_controller.dart';
|
||||||
|
|
||||||
|
import '../../../../fixtures/account_fixtures.dart';
|
||||||
|
import '../../../../fixtures/session_fixtures.dart';
|
||||||
|
import 'load_more_thread_detail_emails_test.mocks.dart';
|
||||||
|
|
||||||
|
@GenerateNiceMocks([
|
||||||
|
MockSpec<ThreadDetailController>(),
|
||||||
|
MockSpec<GetEmailsByIdsInteractor>(),
|
||||||
|
])
|
||||||
|
void main() {
|
||||||
|
late MockThreadDetailController controller;
|
||||||
|
late MockGetEmailsByIdsInteractor getEmailsByIdsInteractor;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
controller = MockThreadDetailController();
|
||||||
|
getEmailsByIdsInteractor = MockGetEmailsByIdsInteractor();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Only call getEmailsByIdsInteractor.execute on emails where presentation email is null, max 20 emails', () async {
|
||||||
|
// Arrange
|
||||||
|
when(controller.emailIdsPresentation).thenReturn({
|
||||||
|
for (int i = 0; i < 40; i++)
|
||||||
|
EmailId(Id('$i')): null,
|
||||||
|
EmailId(Id('40')): PresentationEmail(),
|
||||||
|
}.obs);
|
||||||
|
when(controller.session).thenReturn(SessionFixtures.aliceSession);
|
||||||
|
when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId);
|
||||||
|
when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
controller.loadMoreThreadDetailEmails();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify(getEmailsByIdsInteractor.execute(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
List.generate(20, (i) => EmailId(Id('${i + 20}'))),
|
||||||
|
properties: EmailUtils.getPropertiesForEmailGetMethod(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
),
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('No getEmailsByIdsInteractor.execute call if emailIdsToLoadMetaData is empty', () async {
|
||||||
|
// Arrange
|
||||||
|
when(controller.emailIdsPresentation).thenReturn({
|
||||||
|
EmailId(Id('1')): PresentationEmail(),
|
||||||
|
EmailId(Id('2')): PresentationEmail(),
|
||||||
|
}.obs);
|
||||||
|
when(controller.session).thenReturn(SessionFixtures.aliceSession);
|
||||||
|
when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId);
|
||||||
|
when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
controller.loadMoreThreadDetailEmails();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verifyNever(getEmailsByIdsInteractor.execute(
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
properties: anyNamed('properties'),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Call getEmailsByIdsInteractor.execute when there are less than 20 emails', () async {
|
||||||
|
// Arrange
|
||||||
|
const limit = 15;
|
||||||
|
when(controller.emailIdsPresentation).thenReturn({
|
||||||
|
for (int i = 0; i < limit; i++)
|
||||||
|
EmailId(Id('$i')): null,
|
||||||
|
EmailId(Id('$limit')): PresentationEmail(),
|
||||||
|
}.obs);
|
||||||
|
when(controller.session).thenReturn(SessionFixtures.aliceSession);
|
||||||
|
when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId);
|
||||||
|
when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
controller.loadMoreThreadDetailEmails();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify(getEmailsByIdsInteractor.execute(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
List.generate(limit, (i) => EmailId(Id('$i'))),
|
||||||
|
properties: EmailUtils.getPropertiesForEmailGetMethod(
|
||||||
|
SessionFixtures.aliceSession,
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
),
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
|
import 'package:model/email/presentation_email.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_detail_presentation_utils.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ThreadDetailPresentationUtils', () {
|
||||||
|
group('getEmailIdsToLoad', () {
|
||||||
|
test('loads first time with length equal or more than initial load size', () {
|
||||||
|
final emailIdsPresentation = {
|
||||||
|
EmailId(Id('1')): null,
|
||||||
|
EmailId(Id('2')): null,
|
||||||
|
EmailId(Id('3')): null,
|
||||||
|
EmailId(Id('4')): null,
|
||||||
|
EmailId(Id('5')): null,
|
||||||
|
};
|
||||||
|
|
||||||
|
final result = ThreadDetailPresentationUtils.getEmailIdsToLoad(emailIdsPresentation);
|
||||||
|
|
||||||
|
expect(result.length, equals(ThreadDetailPresentationUtils.initialLoadSize));
|
||||||
|
expect(result, equals([EmailId(Id('1')), EmailId(Id('2'))]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loads first time with length smaller than initial load size', () {
|
||||||
|
final emailIdsPresentation = {
|
||||||
|
EmailId(Id('1')): null,
|
||||||
|
EmailId(Id('2')): null,
|
||||||
|
};
|
||||||
|
|
||||||
|
final result = ThreadDetailPresentationUtils.getEmailIdsToLoad(emailIdsPresentation);
|
||||||
|
|
||||||
|
expect(result.length, equals(emailIdsPresentation.length));
|
||||||
|
expect(result, equals([EmailId(Id('1')), EmailId(Id('2'))]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loads with length equal or more than default load size', () {
|
||||||
|
final emailIdsPresentation = {
|
||||||
|
EmailId(Id('1')): PresentationEmail(),
|
||||||
|
EmailId(Id('2')): null,
|
||||||
|
EmailId(Id('3')): null,
|
||||||
|
EmailId(Id('4')): null,
|
||||||
|
EmailId(Id('5')): null,
|
||||||
|
EmailId(Id('6')): null,
|
||||||
|
EmailId(Id('7')): null,
|
||||||
|
};
|
||||||
|
|
||||||
|
final result = ThreadDetailPresentationUtils.getEmailIdsToLoad(emailIdsPresentation);
|
||||||
|
|
||||||
|
expect(result.length, equals(6));
|
||||||
|
expect(result, equals([EmailId(Id('2')), EmailId(Id('3')), EmailId(Id('4')), EmailId(Id('5')), EmailId(Id('6')), EmailId(Id('7'))]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loads with length smaller than default load size', () {
|
||||||
|
final emailIdsPresentation = {
|
||||||
|
EmailId(Id('1')): PresentationEmail(),
|
||||||
|
EmailId(Id('2')): null,
|
||||||
|
EmailId(Id('3')): null,
|
||||||
|
};
|
||||||
|
|
||||||
|
final result = ThreadDetailPresentationUtils.getEmailIdsToLoad(emailIdsPresentation);
|
||||||
|
|
||||||
|
expect(result.length, equals(emailIdsPresentation.length - 1));
|
||||||
|
expect(result, equals([EmailId(Id('2')), EmailId(Id('3'))]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user