TF-3699 Thread Detail Next previous actions (#3843)
This commit is contained in:
@@ -360,6 +360,7 @@ class HtmlContentViewState extends State<HtmlContentViewer> with AutomaticKeepAl
|
||||
void dispose() {
|
||||
_loadingBarNotifier.dispose();
|
||||
_htmlData = null;
|
||||
_webViewController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_
|
||||
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/focus_thread_detail_expanded_email.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark_collapsed_email_unread_success.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/on_thread_page_changed.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/update_cached_list_email_loaded.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/main/error/capability_validator.dart';
|
||||
@@ -204,6 +205,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
: _threadDetailController?.emailIdsPresentation[_currentEmailId];
|
||||
}
|
||||
|
||||
bool get isOnlyEmailInThread =>
|
||||
_threadDetailController?.emailIdsPresentation.length == 1;
|
||||
|
||||
bool get calendarEventProcessing => viewState.value.fold(
|
||||
(failure) => false,
|
||||
(success) => success is CalendarEventReplying);
|
||||
@@ -680,14 +684,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
emailUnsubscribe.value = null;
|
||||
}
|
||||
}
|
||||
if (currentEmail?.threadId != null &&
|
||||
currentEmail?.id == mailboxDashBoardController.selectedEmail.value?.id) {
|
||||
mailboxDashBoardController.dispatchThreadDetailUIAction(
|
||||
LoadThreadDetailAfterSelectedEmailAction(
|
||||
currentEmail!.threadId!,
|
||||
)
|
||||
);
|
||||
}
|
||||
_loadThreadOnGetEmailContentSuccess();
|
||||
}
|
||||
|
||||
void _getEmailContentSuccess(GetEmailContentSuccess success) {
|
||||
@@ -758,9 +755,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
if ((_threadDetailController?.emailIdsPresentation.keys.length ?? 0) > 1 == true) {
|
||||
_jumpScrollViewToTopOfEmail();
|
||||
}
|
||||
_loadThreadOnGetEmailContentSuccess();
|
||||
}
|
||||
|
||||
void _loadThreadOnGetEmailContentSuccess() {
|
||||
if (currentEmail?.threadId != null &&
|
||||
currentEmail?.id == mailboxDashBoardController.selectedEmail.value?.id &&
|
||||
success is! GetEmailContentFromThreadCacheSuccess) {
|
||||
_threadDetailController?.loadThreadOnThreadChanged == true) {
|
||||
_threadDetailController?.loadThreadOnThreadChanged = false;
|
||||
mailboxDashBoardController.dispatchThreadDetailUIAction(
|
||||
LoadThreadDetailAfterSelectedEmailAction(
|
||||
currentEmail!.threadId!,
|
||||
@@ -2391,4 +2393,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void onScrollHorizontalEnd(bool isLeftDirection) {
|
||||
if (isLeftDirection) {
|
||||
_threadDetailController?.onPreviousMobile();
|
||||
} else {
|
||||
_threadDetailController?.onNextMobile();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,7 +214,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
||||
);
|
||||
}
|
||||
|
||||
if (controller.currentEmail == null || !isInsideThreadDetailView) {
|
||||
if (controller.currentEmail == null || isFirstEmailInThreadDetail) {
|
||||
return const BoxDecoration(color: Colors.white);
|
||||
}
|
||||
|
||||
@@ -278,35 +278,38 @@ class EmailView extends GetWidget<SingleEmailController> {
|
||||
onEmailActionClick: (presentationEmail, actionType) => controller.handleEmailAction(context, presentationEmail, actionType),
|
||||
isInsideThreadDetailView: isInsideThreadDetailView,
|
||||
emailLoaded: controller.currentEmailLoaded.value,
|
||||
onMoreActionClick: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
|
||||
mailboxContain: controller.getMailboxContain(presentationEmail),
|
||||
presentationEmail: presentationEmail,
|
||||
position: position,
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
imagePaths: controller.imagePaths,
|
||||
username: controller.session?.username,
|
||||
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
|
||||
additionalActions: [
|
||||
EmailActionType.forward,
|
||||
EmailActionType.replyAll,
|
||||
EmailActionType.replyToList,
|
||||
EmailActionType.printAll,
|
||||
if (controller.responsiveUtils.isMobile(context))
|
||||
EmailActionType.moveToMailbox,
|
||||
if (!controller.responsiveUtils.isDesktop(context)) ...[
|
||||
EmailActionType.markAsStarred,
|
||||
EmailActionType.unMarkAsStarred,
|
||||
EmailActionType.moveToTrash,
|
||||
EmailActionType.deletePermanently,
|
||||
],
|
||||
],
|
||||
emailIsRead: presentationEmail.hasRead,
|
||||
),
|
||||
onMoreActionClick: controller.isOnlyEmailInThread
|
||||
? null
|
||||
: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
|
||||
mailboxContain: controller.getMailboxContain(presentationEmail),
|
||||
presentationEmail: presentationEmail,
|
||||
position: position,
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
imagePaths: controller.imagePaths,
|
||||
username: controller.session?.username,
|
||||
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
|
||||
additionalActions: [
|
||||
EmailActionType.forward,
|
||||
EmailActionType.replyAll,
|
||||
EmailActionType.replyToList,
|
||||
EmailActionType.printAll,
|
||||
if (controller.responsiveUtils.isMobile(context))
|
||||
EmailActionType.moveToMailbox,
|
||||
if (!controller.responsiveUtils.isDesktop(context)) ...[
|
||||
EmailActionType.markAsStarred,
|
||||
EmailActionType.unMarkAsStarred,
|
||||
EmailActionType.moveToTrash,
|
||||
EmailActionType.deletePermanently,
|
||||
],
|
||||
],
|
||||
emailIsRead: presentationEmail.hasRead,
|
||||
),
|
||||
onToggleThreadDetailCollapseExpand: onToggleThreadDetailCollapseExpand,
|
||||
onTapAvatarActionClick: onToggleThreadDetailCollapseExpand,
|
||||
mailboxContain: presentationEmail.findMailboxContain(
|
||||
controller.mailboxDashBoardController.mapMailboxById,
|
||||
),
|
||||
isOnlyEmailInThread: controller.isOnlyEmailInThread,
|
||||
)),
|
||||
Obx(() => MailUnsubscribedBanner(
|
||||
presentationEmail: controller.currentEmail,
|
||||
@@ -449,6 +452,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
||||
maxHtmlContentHeight: ConstantsUI.htmlContentMaxHeight,
|
||||
onMailtoDelegateAction: controller.openMailToLink,
|
||||
onHtmlContentClippedAction: controller.onHtmlContentClippedAction,
|
||||
onScrollHorizontalEnd: controller.onScrollHorizontalEnd,
|
||||
keepAlive: isInsideThreadDetailView,
|
||||
enableQuoteToggle: isInsideThreadDetailView,
|
||||
);
|
||||
@@ -489,6 +493,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
||||
onMailtoDelegateAction: controller.openMailToLink,
|
||||
keepAlive: isInsideThreadDetailView,
|
||||
enableQuoteToggle: isInsideThreadDetailView,
|
||||
onScrollHorizontalEnd: controller.onScrollHorizontalEnd,
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
+8
-3
@@ -548,7 +548,7 @@ class EmailActionReactor with MessageDialogActionMixin, PopupContextMenuActionMi
|
||||
: EmailActionType.markAsStarred,
|
||||
if (additionalActions.contains(EmailActionType.moveToTrash) &&
|
||||
additionalActions.contains(EmailActionType.deletePermanently))
|
||||
_canDeletePermanently(presentationEmail)
|
||||
_canDeletePermanently(presentationEmail, mailboxContain)
|
||||
? EmailActionType.deletePermanently
|
||||
: EmailActionType.moveToTrash,
|
||||
if (emailIsRead)
|
||||
@@ -598,8 +598,13 @@ class EmailActionReactor with MessageDialogActionMixin, PopupContextMenuActionMi
|
||||
}
|
||||
}
|
||||
|
||||
bool _canDeletePermanently(PresentationEmail email) {
|
||||
return email.mailboxContain?.isTrash
|
||||
bool _canDeletePermanently(
|
||||
PresentationEmail email,
|
||||
PresentationMailbox? mailboxContain,
|
||||
) {
|
||||
return mailboxContain?.isTrash
|
||||
?? mailboxContain?.isSpam
|
||||
?? email.mailboxContain?.isTrash
|
||||
?? email.mailboxContain?.isSpam
|
||||
?? false;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ class EmailViewAppBarWidget extends StatelessWidget {
|
||||
final EdgeInsetsGeometry? iconPadding;
|
||||
final EdgeInsetsGeometry? iconMargin;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final bool isOnlyEmailInThread;
|
||||
|
||||
EmailViewAppBarWidget({
|
||||
Key? key,
|
||||
@@ -51,6 +52,7 @@ class EmailViewAppBarWidget extends StatelessWidget {
|
||||
this.iconPadding,
|
||||
this.iconMargin,
|
||||
this.padding,
|
||||
this.isOnlyEmailInThread = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -251,19 +253,22 @@ class EmailViewAppBarWidget extends StatelessWidget {
|
||||
_getMoveEmailButton(appLocalizations),
|
||||
_getMarkStarButton(appLocalizations),
|
||||
_getDeleteButton(appLocalizations),
|
||||
_getMoreButton(appLocalizations, isScreenWithShortestSide),
|
||||
if (onMoreActionClick != null)
|
||||
_getMoreButton(appLocalizations, isScreenWithShortestSide),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
_getReplyButton(appLocalizations),
|
||||
if (!isOnlyEmailInThread)
|
||||
_getReplyButton(appLocalizations),
|
||||
if (!isResponsiveMobile)
|
||||
_getMoveEmailButton(appLocalizations),
|
||||
if (isResponsiveDesktop) ...[
|
||||
_getMarkStarButton(appLocalizations),
|
||||
_getDeleteButton(appLocalizations),
|
||||
],
|
||||
_getMoreButton(appLocalizations, isScreenWithShortestSide),
|
||||
if (onMoreActionClick != null)
|
||||
_getMoreButton(appLocalizations, isScreenWithShortestSide),
|
||||
];
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -5,7 +5,6 @@ import 'package:core/presentation/utils/responsive_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/image/avatar_builder.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
@@ -42,6 +41,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
||||
final OnTapAvatarActionClick? onTapAvatarActionClick;
|
||||
final PresentationMailbox? mailboxContain;
|
||||
final bool showUnreadVisualization;
|
||||
final bool isOnlyEmailInThread;
|
||||
|
||||
const InformationSenderAndReceiverBuilder({
|
||||
Key? key,
|
||||
@@ -61,6 +61,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
||||
this.onTapAvatarActionClick,
|
||||
this.mailboxContain,
|
||||
this.showUnreadVisualization = false,
|
||||
this.isOnlyEmailInThread = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -164,9 +165,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
||||
SizedBox(
|
||||
height: IconUtils.defaultIconSize,
|
||||
child: OverflowBox(
|
||||
maxHeight: PlatformInfo.isIOS
|
||||
? EmailViewAppBarWidgetStyles.heightIOS(context, responsiveUtils)
|
||||
: EmailViewAppBarWidgetStyles.height,
|
||||
maxHeight: EmailViewAppBarWidgetStyles.height,
|
||||
fit: OverflowBoxFit.deferToChild,
|
||||
child: EmailViewAppBarWidget(
|
||||
key: const Key('email_view_app_bar_widget'),
|
||||
@@ -183,6 +182,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
|
||||
iconPadding: const EdgeInsets.all(8),
|
||||
iconMargin: EdgeInsets.zero,
|
||||
padding: EdgeInsets.zero,
|
||||
isOnlyEmailInThread: isOnlyEmailInThread,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -13,16 +13,28 @@ class GettingThreadById extends LoadingState {
|
||||
|
||||
class GetThreadByIdSuccess extends UIState {
|
||||
final List<EmailId> emailIds;
|
||||
final ThreadId? threadId;
|
||||
final bool updateCurrentThreadDetail;
|
||||
final bool skipLoadingMetadata;
|
||||
|
||||
GetThreadByIdSuccess(this.emailIds, {this.updateCurrentThreadDetail = false});
|
||||
GetThreadByIdSuccess(
|
||||
this.emailIds, {
|
||||
required this.threadId,
|
||||
this.updateCurrentThreadDetail = false,
|
||||
this.skipLoadingMetadata = false,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [emailIds, updateCurrentThreadDetail];
|
||||
List<Object?> get props => [
|
||||
emailIds,
|
||||
threadId,
|
||||
updateCurrentThreadDetail,
|
||||
skipLoadingMetadata,
|
||||
];
|
||||
}
|
||||
|
||||
class PreloadEmailIdsInThreadSuccess extends GetThreadByIdSuccess {
|
||||
PreloadEmailIdsInThreadSuccess(super.emailIds);
|
||||
PreloadEmailIdsInThreadSuccess(super.emailIds, {required super.threadId});
|
||||
}
|
||||
|
||||
class GetThreadByIdFailure extends FeatureFailure {
|
||||
|
||||
@@ -38,6 +38,7 @@ class GetThreadByIdInteractor {
|
||||
|
||||
yield Right(GetThreadByIdSuccess(
|
||||
result,
|
||||
threadId: threadId,
|
||||
updateCurrentThreadDetail: updateCurrentThreadDetail,
|
||||
));
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import 'package:core/presentation/views/loading/cupertino_loading_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
|
||||
extension GetThreadDetailLoadingView on ThreadDetailController {
|
||||
Widget getThreadDetailLoadingView({
|
||||
required bool isResponsiveDesktop,
|
||||
required bool isLoading,
|
||||
bool isExpanded = true,
|
||||
}) {
|
||||
if (!isLoading) return const SizedBox.shrink();
|
||||
|
||||
final 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(),
|
||||
),
|
||||
);
|
||||
|
||||
if (isExpanded) {
|
||||
return Expanded(child: child);
|
||||
} else {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-5
@@ -98,16 +98,29 @@ extension GetThreadDetailEmailViews on ThreadDetailController {
|
||||
);
|
||||
}
|
||||
|
||||
if (isFirstEmailInThreadDetail) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(bottom: 16),
|
||||
child: EmailView(
|
||||
key: GlobalObjectKey('${presentationEmail.id?.id.value ?? ''}firstInThread'),
|
||||
isInsideThreadDetailView: true,
|
||||
emailId: presentationEmail.id,
|
||||
isFirstEmailInThreadDetail: true,
|
||||
threadSubject: emailIdsPresentation.values.last?.subject,
|
||||
onToggleThreadDetailCollapseExpand: () {
|
||||
toggleThreadDetailCollapeExpand(presentationEmail);
|
||||
},
|
||||
scrollController: scrollController,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(bottom: 16),
|
||||
child: EmailView(
|
||||
key: GlobalObjectKey(presentationEmail.id?.id.value ?? ''),
|
||||
isInsideThreadDetailView: emailIdsPresentation.length > 1,
|
||||
isInsideThreadDetailView: true,
|
||||
emailId: presentationEmail.id,
|
||||
isFirstEmailInThreadDetail: isFirstEmailInThreadDetail,
|
||||
threadSubject: isFirstEmailInThreadDetail
|
||||
? emailIdsPresentation.values.last?.subject
|
||||
: null,
|
||||
onToggleThreadDetailCollapseExpand: () {
|
||||
toggleThreadDetailCollapeExpand(presentationEmail);
|
||||
},
|
||||
|
||||
+2
-1
@@ -5,7 +5,8 @@ extension HandleGetEmailIdsByThreadIdSuccess on ThreadDetailController {
|
||||
void handleGetEmailIdsByThreadIdSuccess(
|
||||
GetThreadByIdSuccess success,
|
||||
) {
|
||||
if (success.emailIds.isEmpty) {
|
||||
final currentThreadId = mailboxDashBoardController.selectedEmail.value?.threadId;
|
||||
if (success.emailIds.isEmpty || success.threadId != currentThreadId) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -37,6 +37,7 @@ extension HandleGetEmailsByIdsSuccess on ThreadDetailController {
|
||||
: EmailInThreadStatus.collapsed,
|
||||
);
|
||||
}
|
||||
threadDetailManager.currentMobilePageViewIndex.refresh();
|
||||
|
||||
if (_skipScrollJump(isLoadMore)) return;
|
||||
|
||||
|
||||
+2
@@ -32,6 +32,7 @@ extension HandleRefreshThreadDetailAction on ThreadDetailController {
|
||||
consumeState(Stream.value(Right(GetThreadByIdSuccess(
|
||||
[currentEmailId],
|
||||
updateCurrentThreadDetail: true,
|
||||
threadId: mailboxDashBoardController.selectedEmail.value?.threadId,
|
||||
))));
|
||||
}
|
||||
|
||||
@@ -74,6 +75,7 @@ extension HandleRefreshThreadDetailAction on ThreadDetailController {
|
||||
|
||||
consumeState(Stream.value(Right(GetThreadByIdSuccess(
|
||||
afterRefreshedEmailIds,
|
||||
threadId: currentThreadId,
|
||||
updateCurrentThreadDetail: true,
|
||||
))));
|
||||
consumeState(Stream.value(Right(GetEmailsByIdsSuccess(
|
||||
|
||||
@@ -9,6 +9,9 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_d
|
||||
|
||||
extension InitializeThreadDetailEmails on ThreadDetailController {
|
||||
void initializeThreadDetailEmails(GetThreadByIdSuccess success) {
|
||||
final currentThreadId = mailboxDashBoardController.selectedEmail.value?.threadId;
|
||||
if (success.skipLoadingMetadata || currentThreadId != success.threadId) return;
|
||||
|
||||
final selectedEmailId = mailboxDashBoardController.selectedEmail.value?.id;
|
||||
if (skipLoadThreadMetaData(
|
||||
selectedEmailId: selectedEmailId,
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_manager.dart';
|
||||
|
||||
extension InitializeThreadDetailManager on ThreadDetailManager {
|
||||
void initializeThreadDetailManager(
|
||||
List<PresentationEmail> currentDisplayedEmails,
|
||||
) {
|
||||
availableThreadIds.value = currentDisplayedEmails
|
||||
.map((presentationEmail) => presentationEmail.threadId)
|
||||
.toSet()
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
if (availableThreadIds.isEmpty) return;
|
||||
|
||||
final selectedEmail = mailboxDashBoardController.selectedEmail.value;
|
||||
if (isThreadDetailEnabled && selectedEmail?.threadId != null) {
|
||||
final currentThreadIndex = availableThreadIds.indexOf(
|
||||
selectedEmail!.threadId!,
|
||||
);
|
||||
currentMobilePageViewIndex.value = currentThreadIndex;
|
||||
} else if (selectedEmail?.id != null) {
|
||||
final currentEmailIndex = currentDisplayedEmails.indexOf(
|
||||
selectedEmail!,
|
||||
);
|
||||
currentMobilePageViewIndex.value = currentEmailIndex;
|
||||
}
|
||||
|
||||
pageController ??= PageController(
|
||||
initialPage: currentMobilePageViewIndex.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_next_previous_actions.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
|
||||
extension OnThreadPageChanged on ThreadDetailController {
|
||||
void onThreadPageChanged(int index) {
|
||||
if (index > threadDetailManager.currentMobilePageViewIndex.value) {
|
||||
onNext();
|
||||
} else if (index < threadDetailManager.currentMobilePageViewIndex.value) {
|
||||
onPrevious();
|
||||
}
|
||||
}
|
||||
|
||||
bool get nextAvailable =>
|
||||
PlatformInfo.isWeb && threadDetailManager.nextAvailable;
|
||||
bool get previousAvailable =>
|
||||
PlatformInfo.isWeb && threadDetailManager.previousAvailable;
|
||||
|
||||
void onNext() {
|
||||
if (currentExpandedEmailId.value != null) {
|
||||
mailboxDashBoardController.dispatchEmailUIAction(
|
||||
CollapseEmailInThreadDetailAction(currentExpandedEmailId.value!),
|
||||
);
|
||||
}
|
||||
loadThreadOnThreadChanged = isThreadDetailEnabled;
|
||||
threadDetailManager.onNext();
|
||||
}
|
||||
|
||||
void onNextMobile() {
|
||||
if (currentExpandedEmailId.value != null) {
|
||||
mailboxDashBoardController.dispatchEmailUIAction(
|
||||
CollapseEmailInThreadDetailAction(currentExpandedEmailId.value!),
|
||||
);
|
||||
}
|
||||
loadThreadOnThreadChanged = isThreadDetailEnabled;
|
||||
threadDetailManager.pageController?.nextPage(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.linear,
|
||||
);
|
||||
}
|
||||
|
||||
void onPrevious() {
|
||||
if (currentExpandedEmailId.value != null) {
|
||||
mailboxDashBoardController.dispatchEmailUIAction(
|
||||
CollapseEmailInThreadDetailAction(currentExpandedEmailId.value!),
|
||||
);
|
||||
}
|
||||
loadThreadOnThreadChanged = isThreadDetailEnabled;
|
||||
threadDetailManager.onPrevious();
|
||||
}
|
||||
|
||||
void onPreviousMobile() {
|
||||
if (currentExpandedEmailId.value != null) {
|
||||
mailboxDashBoardController.dispatchEmailUIAction(
|
||||
CollapseEmailInThreadDetailAction(currentExpandedEmailId.value!),
|
||||
);
|
||||
}
|
||||
loadThreadOnThreadChanged = isThreadDetailEnabled;
|
||||
threadDetailManager.pageController?.previousPage(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.linear,
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_manager.dart';
|
||||
|
||||
extension RefreshThreadDetailOnSettingChanged on ThreadDetailController {
|
||||
extension RefreshThreadDetailOnSettingChanged on ThreadDetailManager {
|
||||
void refreshThreadDetailOnSettingChanged() {
|
||||
if (threadDetailWasEnabled != isThreadDetailEnabled) {
|
||||
threadDetailWasEnabled = isThreadDetailEnabled;
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_manager.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_utils.dart';
|
||||
|
||||
extension ThreadDetailNextPreviousActions on ThreadDetailManager {
|
||||
bool get nextAvailable => isThreadDetailEnabled
|
||||
? currentThreadId != availableThreadIds.lastOrNull
|
||||
: currentEmailId != currentDisplayedEmails.lastOrNull?.id;
|
||||
|
||||
void onNext() {
|
||||
if (!nextAvailable) return;
|
||||
|
||||
final currentIndex = isThreadDetailEnabled
|
||||
? availableThreadIds.indexOf(currentThreadId!)
|
||||
: currentDisplayedEmails
|
||||
.indexOf(mailboxDashBoardController.selectedEmail.value!);
|
||||
_navigate(currentIndex + 1, isThreadDetailEnabled);
|
||||
}
|
||||
|
||||
bool get previousAvailable => isThreadDetailEnabled
|
||||
? currentThreadId != availableThreadIds.firstOrNull
|
||||
: currentEmailId != currentDisplayedEmails.firstOrNull?.id;
|
||||
|
||||
void onPrevious() {
|
||||
if (!previousAvailable) return;
|
||||
|
||||
final currentIndex = isThreadDetailEnabled
|
||||
? availableThreadIds.indexOf(currentThreadId!)
|
||||
: currentDisplayedEmails
|
||||
.indexOf(mailboxDashBoardController.selectedEmail.value!);
|
||||
_navigate(currentIndex - 1, isThreadDetailEnabled);
|
||||
}
|
||||
|
||||
void _preparePageWithIndex(int index) {
|
||||
currentMobilePageViewIndex.value = index;
|
||||
pageController?.dispose();
|
||||
pageController = PageController(initialPage: index);
|
||||
}
|
||||
|
||||
void _goToPageWithEmail(PresentationEmail? presentationEmail) {
|
||||
mailboxDashBoardController.setSelectedEmail(presentationEmail);
|
||||
if (PlatformInfo.isWeb && presentationEmail?.routeWeb != null) {
|
||||
RouteUtils.replaceBrowserHistory(
|
||||
title: 'Email-${presentationEmail?.id?.id.value ?? ''}',
|
||||
url: presentationEmail!.routeWeb!);
|
||||
}
|
||||
}
|
||||
|
||||
void _navigateToEmail(int emailIndex) {
|
||||
final email = currentDisplayedEmails[emailIndex];
|
||||
_preparePageWithIndex(emailIndex);
|
||||
_goToPageWithEmail(email);
|
||||
}
|
||||
|
||||
void _navigateToThread(int threadIndex) {
|
||||
final threadId = availableThreadIds[threadIndex];
|
||||
final email = currentDisplayedEmails.firstWhereOrNull(
|
||||
(presentationEmail) => presentationEmail.threadId == threadId,
|
||||
);
|
||||
_preparePageWithIndex(threadIndex);
|
||||
_goToPageWithEmail(email);
|
||||
}
|
||||
|
||||
void _navigate(int index, bool isThreadDetailEnabled) {
|
||||
isThreadDetailEnabled ? _navigateToThread(index) : _navigateToEmail(index);
|
||||
}
|
||||
}
|
||||
+32
-8
@@ -5,6 +5,7 @@ import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action
|
||||
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/state/get_thread_by_id_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/presentation/action/thread_detail_ui_action.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/thread_detail_controller.dart';
|
||||
|
||||
@@ -19,20 +20,43 @@ extension ThreadDetailOnSelectedEmailUpdated on ThreadDetailController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentExpandedEmailId.value != null) {
|
||||
mailboxDashBoardController.dispatchEmailUIAction(
|
||||
DisposePreviousExpandedEmailAction(
|
||||
currentExpandedEmailId.value!,
|
||||
),
|
||||
);
|
||||
scrollController ??= ScrollController();
|
||||
|
||||
if (currentExpandedEmailId.value == null) {
|
||||
loadThreadOnThreadChanged = isThreadDetailEnabled;
|
||||
_preloadSelectedEmail(selectedEmail!);
|
||||
return;
|
||||
}
|
||||
|
||||
_preloadSelectedEmail(selectedEmail!);
|
||||
// Thread setting updated, no need to dispose current single email controller
|
||||
if (currentExpandedEmailId.value == selectedEmail!.id) {
|
||||
_preloadSelectedEmail(selectedEmail);
|
||||
|
||||
if (isThreadDetailEnabled && selectedEmail.threadId != null) {
|
||||
mailboxDashBoardController.dispatchThreadDetailUIAction(
|
||||
LoadThreadDetailAfterSelectedEmailAction(
|
||||
selectedEmail.threadId!,
|
||||
)
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
mailboxDashBoardController.dispatchEmailUIAction(
|
||||
DisposePreviousExpandedEmailAction(
|
||||
currentExpandedEmailId.value!,
|
||||
),
|
||||
);
|
||||
loadThreadOnThreadChanged = isThreadDetailEnabled;
|
||||
_preloadSelectedEmail(selectedEmail);
|
||||
}
|
||||
|
||||
void _preloadSelectedEmail(PresentationEmail selectedEmail) {
|
||||
consumeState(Stream.fromIterable([
|
||||
Right(PreloadEmailIdsInThreadSuccess([selectedEmail.id!])),
|
||||
Right(PreloadEmailIdsInThreadSuccess(
|
||||
[selectedEmail.id!],
|
||||
threadId: selectedEmail.threadId,
|
||||
)),
|
||||
Right(PreloadEmailsByIdsSuccess([selectedEmail])),
|
||||
]));
|
||||
}
|
||||
|
||||
+4
@@ -11,6 +11,8 @@ extension ToggleThreadDetailCollapeExpand on ThreadDetailController {
|
||||
if (emailId == null || expansionStatus == null) return;
|
||||
|
||||
if (expansionStatus == EmailInThreadStatus.expanded) {
|
||||
if (emailIdsPresentation.length == 1) return;
|
||||
|
||||
emailIdsPresentation[emailId] = presentationEmail.copyWith(
|
||||
emailInThreadStatus: EmailInThreadStatus.collapsed,
|
||||
);
|
||||
@@ -18,6 +20,7 @@ extension ToggleThreadDetailCollapeExpand on ThreadDetailController {
|
||||
CollapseEmailInThreadDetailAction(emailId),
|
||||
);
|
||||
currentExpandedEmailId.value = null;
|
||||
threadDetailManager.currentMobilePageViewIndex.refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,5 +45,6 @@ extension ToggleThreadDetailCollapeExpand on ThreadDetailController {
|
||||
);
|
||||
}
|
||||
}
|
||||
threadDetailManager.currentMobilePageViewIndex.refresh();
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_
|
||||
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_detail_status_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_manager.dart';
|
||||
import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart';
|
||||
import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart';
|
||||
|
||||
@@ -31,8 +32,10 @@ class ThreadDetailBindings extends BaseBindings {
|
||||
|
||||
@override
|
||||
void bindingsController() {
|
||||
Get.put(ThreadDetailController(
|
||||
Get.put(ThreadDetailManager(
|
||||
Get.find<GetThreadDetailStatusInteractor>(),
|
||||
));
|
||||
Get.put(ThreadDetailController(
|
||||
Get.find<GetThreadByIdInteractor>(),
|
||||
Get.find<GetEmailsByIdsInteractor>(),
|
||||
Get.find<MarkAsEmailReadInteractor>(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:debounce_throttle/debounce_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
@@ -38,20 +39,17 @@ import 'package:tmail_ui_user/features/network_connection/presentation/network_c
|
||||
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_emails_by_ids_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_detail_status_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_emails_by_ids_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_detail_status_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_email_moved_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_emails_by_ids_success.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_thread_by_id_failure.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_refresh_thread_detail_action.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/extension/refresh_thread_detail_on_setting_changed.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/model/thread_detail_setting_status.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_manager.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/features/thread_detail/presentation/extension/handle_collapsed_email_download_states.dart';
|
||||
@@ -60,7 +58,6 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/quick_create_rule_from_collapsed_email_success.dart';
|
||||
|
||||
class ThreadDetailController extends BaseController {
|
||||
final GetThreadDetailStatusInteractor _getThreadDetailStatusInteractor;
|
||||
final GetThreadByIdInteractor _getEmailIdsByThreadIdInteractor;
|
||||
final GetEmailsByIdsInteractor getEmailsByIdsInteractor;
|
||||
final MarkAsEmailReadInteractor _markAsEmailReadInteractor;
|
||||
@@ -70,7 +67,6 @@ class ThreadDetailController extends BaseController {
|
||||
final DownloadAttachmentForWebInteractor _downloadAttachmentForWebInteractor;
|
||||
|
||||
ThreadDetailController(
|
||||
this._getThreadDetailStatusInteractor,
|
||||
this._getEmailIdsByThreadIdInteractor,
|
||||
this.getEmailsByIdsInteractor,
|
||||
this._markAsEmailReadInteractor,
|
||||
@@ -90,18 +86,34 @@ class ThreadDetailController extends BaseController {
|
||||
IndividualHeaderIdentifier.listUnsubscribeHeader.value,
|
||||
});
|
||||
final cachedEmailLoaded = <EmailId, EmailLoaded>{};
|
||||
late final _threadGetDebouncer = Debouncer<ThreadId?>(
|
||||
const Duration(milliseconds: 500),
|
||||
initialValue: null,
|
||||
checkEquality: true,
|
||||
onChanged: (threadId) {
|
||||
if (_validateLoadThread(threadId)) {
|
||||
consumeState(_getEmailIdsByThreadIdInteractor.execute(
|
||||
threadId!,
|
||||
session!,
|
||||
accountId!,
|
||||
sentMailboxId!,
|
||||
ownEmailAddress!,
|
||||
selectedEmailId: mailboxDashBoardController.selectedEmail.value?.id,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
||||
final searchEmailController = Get.find<SearchEmailController>();
|
||||
final networkConnectionController = Get.find<NetworkConnectionController>();
|
||||
final threadDetailManager = Get.find<ThreadDetailManager>();
|
||||
final downloadManager = Get.find<DownloadManager>();
|
||||
final downloadProgressState = StreamController<Either<Failure, Success>>();
|
||||
|
||||
ScrollController? scrollController;
|
||||
CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
|
||||
AppLifecycleListener? appLifecycleListener;
|
||||
ThreadDetailSettingStatus threadDetailSettingStatus = ThreadDetailSettingStatus.loading;
|
||||
bool threadDetailWasEnabled = true;
|
||||
bool loadThreadOnThreadChanged = false;
|
||||
|
||||
AccountId? get accountId => mailboxDashBoardController.accountId.value;
|
||||
Session? get session => mailboxDashBoardController.sessionCurrent;
|
||||
@@ -121,21 +133,11 @@ class ThreadDetailController extends BaseController {
|
||||
bool get networkConnected =>
|
||||
networkConnectionController.isNetworkConnectionAvailable();
|
||||
bool get isThreadDetailEnabled =>
|
||||
threadDetailSettingStatus == ThreadDetailSettingStatus.enabled;
|
||||
threadDetailManager.isThreadDetailEnabled;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
consumeState(_getThreadDetailStatusInteractor.execute());
|
||||
appLifecycleListener = AppLifecycleListener(
|
||||
onResume: () {
|
||||
if (threadDetailSettingStatus == ThreadDetailSettingStatus.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
consumeState(_getThreadDetailStatusInteractor.execute());
|
||||
},
|
||||
);
|
||||
ever(mailboxDashBoardController.accountId, (accountId) {
|
||||
if (accountId == null) return;
|
||||
|
||||
@@ -172,22 +174,10 @@ class ThreadDetailController extends BaseController {
|
||||
})
|
||||
..listUnsubscribeHeader?.clear();
|
||||
}
|
||||
} else if (action is UpdatedThreadDetailSettingAction) {
|
||||
consumeState(_getThreadDetailStatusInteractor.execute());
|
||||
} else if (action is EmailMovedAction) {
|
||||
handleEmailMovedAction(action);
|
||||
} else if (action is LoadThreadDetailAfterSelectedEmailAction) {
|
||||
if (_validateLoadThread(action)) {
|
||||
scrollController = ScrollController();
|
||||
consumeState(_getEmailIdsByThreadIdInteractor.execute(
|
||||
action.threadId,
|
||||
session!,
|
||||
accountId!,
|
||||
sentMailboxId!,
|
||||
ownEmailAddress!,
|
||||
selectedEmailId: mailboxDashBoardController.selectedEmail.value?.id,
|
||||
));
|
||||
}
|
||||
_threadGetDebouncer.value = action.threadId;
|
||||
}
|
||||
// Reset [threadDetailUIAction] to original value
|
||||
mailboxDashBoardController.dispatchThreadDetailUIAction(
|
||||
@@ -205,9 +195,9 @@ class ThreadDetailController extends BaseController {
|
||||
});
|
||||
}
|
||||
|
||||
bool _validateLoadThread(LoadThreadDetailAfterSelectedEmailAction action) {
|
||||
bool _validateLoadThread(ThreadId? threadId) {
|
||||
return mailboxDashBoardController.selectedEmail.value?.threadId != null &&
|
||||
action.threadId == mailboxDashBoardController.selectedEmail.value?.threadId &&
|
||||
threadId == mailboxDashBoardController.selectedEmail.value?.threadId &&
|
||||
session != null &&
|
||||
accountId != null &&
|
||||
sentMailboxId != null &&
|
||||
@@ -223,6 +213,7 @@ class ThreadDetailController extends BaseController {
|
||||
currentExpandedEmailId.value = null;
|
||||
currentEmailLoaded.value = null;
|
||||
cachedEmailLoaded.clear();
|
||||
_threadGetDebouncer.value = null;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -240,13 +231,6 @@ class ThreadDetailController extends BaseController {
|
||||
quickCreateRuleFromCollapsedEmailSuccess(success);
|
||||
} else if (success is DownloadAttachmentForWebSuccess) {
|
||||
handleDownloadSuccess(success);
|
||||
} else if (success is GetThreadDetailStatusSuccess) {
|
||||
threadDetailSettingStatus = success.threadDetailEnabled
|
||||
? ThreadDetailSettingStatus.enabled
|
||||
: ThreadDetailSettingStatus.disabled;
|
||||
refreshThreadDetailOnSettingChanged();
|
||||
} else if (success is GettingThreadDetailStatus) {
|
||||
threadDetailSettingStatus = ThreadDetailSettingStatus.loading;
|
||||
} else {
|
||||
super.handleSuccessViewState(success);
|
||||
}
|
||||
@@ -266,10 +250,6 @@ class ThreadDetailController extends BaseController {
|
||||
handleDownloadFailure(failure);
|
||||
return;
|
||||
}
|
||||
if (failure is GetThreadDetailStatusFailure) {
|
||||
threadDetailSettingStatus = ThreadDetailSettingStatus.enabled;
|
||||
refreshThreadDetailOnSettingChanged();
|
||||
}
|
||||
if (failure is PrintEmailFailure) {
|
||||
if (currentOverlayContext != null && currentContext != null) {
|
||||
appToast.showToastErrorMessage(
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get/get_instance/get_instance.dart';
|
||||
import 'package:get/state_manager.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/base/base_controller.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_detail_status_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_detail_status_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/initialize_thread_detail_manager.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/refresh_thread_detail_on_setting_changed.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/model/thread_detail_setting_status.dart';
|
||||
|
||||
class ThreadDetailManager extends BaseController {
|
||||
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
||||
|
||||
final GetThreadDetailStatusInteractor _getThreadDetailStatusInteractor;
|
||||
|
||||
ThreadDetailManager(this._getThreadDetailStatusInteractor);
|
||||
|
||||
final availableThreadIds = RxList<ThreadId>();
|
||||
final currentMobilePageViewIndex = 0.obs;
|
||||
PageController? pageController;
|
||||
|
||||
final threadDetailSettingStatus = ThreadDetailSettingStatus.loading.obs;
|
||||
AppLifecycleListener? appLifecycleListener;
|
||||
bool threadDetailWasEnabled = true;
|
||||
|
||||
bool get isSearchingOnMobile =>
|
||||
mailboxDashBoardController.searchController.isSearchEmailRunning &&
|
||||
PlatformInfo.isMobile;
|
||||
|
||||
RxList<PresentationEmail> get currentDisplayedEmails => isSearchingOnMobile
|
||||
? mailboxDashBoardController.listResultSearch
|
||||
: mailboxDashBoardController.emailsInCurrentMailbox;
|
||||
|
||||
ThreadId? get currentThreadId =>
|
||||
mailboxDashBoardController.selectedEmail.value?.threadId;
|
||||
EmailId? get currentEmailId =>
|
||||
mailboxDashBoardController.selectedEmail.value?.id;
|
||||
|
||||
bool get isThreadDetailEnabled =>
|
||||
threadDetailSettingStatus.value == ThreadDetailSettingStatus.enabled;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
consumeState(_getThreadDetailStatusInteractor.execute());
|
||||
appLifecycleListener = AppLifecycleListener(
|
||||
onResume: () {
|
||||
if (threadDetailSettingStatus.value == ThreadDetailSettingStatus.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
consumeState(_getThreadDetailStatusInteractor.execute());
|
||||
},
|
||||
);
|
||||
ever(mailboxDashBoardController.threadDetailUIAction, (action) {
|
||||
if (action is UpdatedThreadDetailSettingAction) {
|
||||
consumeState(_getThreadDetailStatusInteractor.execute());
|
||||
}
|
||||
// Reset [threadDetailUIAction] to original value
|
||||
mailboxDashBoardController.dispatchThreadDetailUIAction(
|
||||
ThreadDetailUIAction(),
|
||||
);
|
||||
});
|
||||
ever(
|
||||
currentDisplayedEmails,
|
||||
(emails) {
|
||||
if (mailboxDashBoardController.dashboardRoute.value != DashboardRoutes.threadDetailed) {
|
||||
return;
|
||||
}
|
||||
initializeThreadDetailManager(emails);
|
||||
},
|
||||
);
|
||||
ever(
|
||||
mailboxDashBoardController.dashboardRoute,
|
||||
(route) {
|
||||
final selectedEmail = mailboxDashBoardController.selectedEmail.value;
|
||||
if (route != DashboardRoutes.threadDetailed || selectedEmail == null) {
|
||||
currentMobilePageViewIndex.value = -1;
|
||||
pageController?.dispose();
|
||||
pageController = null;
|
||||
availableThreadIds.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
initializeThreadDetailManager(currentDisplayedEmails);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void handleSuccessViewState(Success success) {
|
||||
if (success is GetThreadDetailStatusSuccess) {
|
||||
threadDetailSettingStatus.value = success.threadDetailEnabled
|
||||
? ThreadDetailSettingStatus.enabled
|
||||
: ThreadDetailSettingStatus.disabled;
|
||||
refreshThreadDetailOnSettingChanged();
|
||||
} else if (success is GettingThreadDetailStatus) {
|
||||
threadDetailSettingStatus.value = ThreadDetailSettingStatus.loading;
|
||||
} else {
|
||||
super.handleSuccessViewState(success);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void handleFailureViewState(Failure failure) {
|
||||
if (failure is GetThreadDetailStatusFailure) {
|
||||
threadDetailSettingStatus.value = ThreadDetailSettingStatus.enabled;
|
||||
refreshThreadDetailOnSettingChanged();
|
||||
} else {
|
||||
super.handleFailureViewState(failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,37 +2,37 @@ import 'package:collection/collection.dart';
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/presentation/views/button/tmail_button_widget.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:model/email/email_in_thread_status.dart';
|
||||
import 'package:model/extensions/session_extension.dart';
|
||||
import 'package:model/email/email_action_type.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/email_view.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_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_email_mailbox_contains.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/extension/on_thread_page_changed.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_email_action_click.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/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/localizations/app_localizations.dart';
|
||||
|
||||
class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
||||
const ThreadDetailView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final multipleEmailsView = SelectionArea(child: SafeArea(
|
||||
final child = SelectionArea(child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
Obx(() {
|
||||
if (showLoadingView(controller.viewState.value)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final isLoading = showLoadingView(controller.viewState.value);
|
||||
|
||||
return ThreadDetailAppBar(
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
@@ -42,39 +42,84 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
||||
lastEmailOfThread: controller.emailIdsPresentation.values.lastOrNull,
|
||||
ownUserName: controller.session?.getOwnEmailAddress() ?? '',
|
||||
mailboxContain: _getMailboxContain(),
|
||||
onEmailActionClick: controller.threadDetailOnEmailActionClick,
|
||||
onMoreActionClick: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
|
||||
mailboxContain: controller.getThreadDetailEmailMailboxContains(presentationEmail),
|
||||
presentationEmail: presentationEmail,
|
||||
position: position,
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
imagePaths: controller.imagePaths,
|
||||
username: controller.session?.username,
|
||||
handleEmailAction: controller.threadDetailOnEmailActionClick,
|
||||
additionalActions: [
|
||||
if (controller.responsiveUtils.isMobile(context)) ...[
|
||||
EmailActionType.forward,
|
||||
EmailActionType.replyAll,
|
||||
EmailActionType.replyToList,
|
||||
],
|
||||
EmailActionType.markAsStarred,
|
||||
EmailActionType.unMarkAsStarred,
|
||||
EmailActionType.moveToTrash,
|
||||
EmailActionType.deletePermanently,
|
||||
EmailActionType.printAll,
|
||||
EmailActionType.moveToMailbox,
|
||||
],
|
||||
emailIsRead: presentationEmail.hasRead,
|
||||
),
|
||||
onEmailActionClick: isLoading
|
||||
? null
|
||||
: controller.threadDetailOnEmailActionClick,
|
||||
onMoreActionClick: (presentationEmail, position) => isLoading
|
||||
? null
|
||||
: controller.emailActionReactor.handleMoreEmailAction(
|
||||
mailboxContain: controller.getThreadDetailEmailMailboxContains(
|
||||
presentationEmail,
|
||||
),
|
||||
presentationEmail: presentationEmail,
|
||||
position: position,
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
imagePaths: controller.imagePaths,
|
||||
username: controller.session?.username,
|
||||
handleEmailAction: controller.threadDetailOnEmailActionClick,
|
||||
additionalActions: [
|
||||
if (controller.responsiveUtils.isMobile(context)) ...[
|
||||
EmailActionType.forward,
|
||||
EmailActionType.replyAll,
|
||||
EmailActionType.replyToList,
|
||||
],
|
||||
EmailActionType.markAsStarred,
|
||||
EmailActionType.unMarkAsStarred,
|
||||
EmailActionType.moveToTrash,
|
||||
EmailActionType.deletePermanently,
|
||||
EmailActionType.printAll,
|
||||
EmailActionType.moveToMailbox,
|
||||
],
|
||||
emailIsRead: presentationEmail.hasRead,
|
||||
),
|
||||
optionWidgets: [
|
||||
if (controller.previousAvailable)
|
||||
TMailButtonWidget.fromIcon(
|
||||
icon: controller.imagePaths.icNewer,
|
||||
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||
tooltipMessage: AppLocalizations.of(context).newer,
|
||||
backgroundColor: Colors.transparent,
|
||||
onTapActionCallback: controller.onPrevious,
|
||||
),
|
||||
if (controller.nextAvailable)
|
||||
TMailButtonWidget.fromIcon(
|
||||
icon: controller.imagePaths.icOlder,
|
||||
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||
tooltipMessage: AppLocalizations.of(context).older,
|
||||
backgroundColor: Colors.transparent,
|
||||
onTapActionCallback: controller.onNext,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
Obx(() => controller.getThreadDetailLoadingView(
|
||||
isResponsiveDesktop: controller.responsiveUtils.isDesktop(context),
|
||||
isLoading: showLoadingView(controller.viewState.value),
|
||||
)),
|
||||
Obx(() {
|
||||
if (showLoadingView(controller.viewState.value)) {
|
||||
return const SizedBox.shrink();
|
||||
if (PlatformInfo.isMobile) {
|
||||
final manager = controller.threadDetailManager;
|
||||
final currentIndex = manager.currentMobilePageViewIndex.value;
|
||||
|
||||
return Expanded(
|
||||
child: PageView.builder(
|
||||
controller: manager.pageController,
|
||||
itemCount: manager.isThreadDetailEnabled
|
||||
? manager.availableThreadIds.length
|
||||
: manager.currentDisplayedEmails.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index != currentIndex) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: controller.getThreadDetailEmailViews()
|
||||
),
|
||||
);
|
||||
},
|
||||
onPageChanged: controller.onThreadPageChanged,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Expanded(
|
||||
@@ -98,28 +143,18 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
||||
);
|
||||
}),
|
||||
Obx(() {
|
||||
if (showLoadingView(controller.viewState.value)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final expandedEmailId = controller.currentExpandedEmailId.value;
|
||||
if (expandedEmailId == null) {
|
||||
return _roundBottomPlaceHolder(
|
||||
isDesktop: controller.responsiveUtils.isDesktop(context),
|
||||
);
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final expandedPresentationEmail = controller.emailIdsPresentation[expandedEmailId];
|
||||
if (expandedPresentationEmail == null) {
|
||||
return _roundBottomPlaceHolder(
|
||||
isDesktop: controller.responsiveUtils.isDesktop(context),
|
||||
);
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final currentEmailLoaded = controller.currentEmailLoaded.value;
|
||||
if (currentEmailLoaded == null) {
|
||||
return _roundBottomPlaceHolder(
|
||||
isDesktop: controller.responsiveUtils.isDesktop(context),
|
||||
);
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Padding(
|
||||
@@ -162,29 +197,10 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
||||
),
|
||||
));
|
||||
|
||||
return Obx(() {
|
||||
if (showLoadingView(controller.viewState.value) &&
|
||||
controller.emailIdsPresentation.isEmpty &&
|
||||
controller.responsiveUtils.isTabletLarge(context)) {
|
||||
return controller.getThreadDetailLoadingView(
|
||||
isResponsiveDesktop: false,
|
||||
isLoading: true,
|
||||
isExpanded: false,
|
||||
);
|
||||
}
|
||||
|
||||
if (controller.emailIdsPresentation.length == 1 &&
|
||||
controller.emailIdsPresentation.values.firstOrNull?.emailInThreadStatus == EmailInThreadStatus.expanded) {
|
||||
final emailId = controller.emailIdsPresentation.values.firstOrNull!.id;
|
||||
|
||||
return EmailView(
|
||||
key: ValueKey(emailId?.id.value),
|
||||
emailId: emailId,
|
||||
);
|
||||
}
|
||||
|
||||
return multipleEmailsView;
|
||||
});
|
||||
return ClipRRect(
|
||||
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(20)),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
EdgeInsetsGeometry _padding(BuildContext context) {
|
||||
@@ -202,21 +218,6 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
||||
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),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
bool showLoadingView(Either<Failure, Success> viewState) {
|
||||
final viewStateValue = viewState.fold(id, id);
|
||||
return viewStateValue is GettingThreadById &&
|
||||
|
||||
@@ -139,21 +139,24 @@ class ThreadDetailAppBar extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
if (!responsiveUtils.isMobile(context)) const Spacer(),
|
||||
...optionWidgets,
|
||||
] else const Spacer(),
|
||||
...optionWidgets,
|
||||
if (lastEmailOfThread != null)
|
||||
TMailButtonWidget.fromIcon(
|
||||
icon: imagePaths.icMoreVertical,
|
||||
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
|
||||
iconColor: EmailViewAppBarWidgetStyles.iconColor,
|
||||
backgroundColor: Colors.transparent,
|
||||
tooltipMessage: AppLocalizations.of(context).more,
|
||||
onTapActionCallback: responsiveUtils.isScreenWithShortestSide(context)
|
||||
onTapActionCallback: responsiveUtils.isScreenWithShortestSide(context) &&
|
||||
lastEmailOfThread != null
|
||||
? () => onMoreActionClick?.call(lastEmailOfThread!, null)
|
||||
: null,
|
||||
onTapActionAtPositionCallback: !responsiveUtils.isScreenWithShortestSide(context)
|
||||
onTapActionAtPositionCallback: !responsiveUtils.isScreenWithShortestSide(context) &&
|
||||
lastEmailOfThread != null
|
||||
? (position) => onMoreActionClick?.call(lastEmailOfThread!, position)
|
||||
: null,
|
||||
),
|
||||
] else const Spacer(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
+12
-1
@@ -31,10 +31,14 @@ void main() {
|
||||
};
|
||||
when(threadDetailController.emailIdsPresentation)
|
||||
.thenReturn(initialEmailIdsPresentation.obs);
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
.thenReturn(Rxn(PresentationEmail(id: EmailId(Id('1')))));
|
||||
|
||||
// act
|
||||
threadDetailController.handleGetEmailIdsByThreadIdSuccess(
|
||||
GetThreadByIdSuccess([]),
|
||||
GetThreadByIdSuccess([], threadId: null),
|
||||
);
|
||||
|
||||
// assert
|
||||
@@ -58,6 +62,10 @@ void main() {
|
||||
};
|
||||
when(threadDetailController.emailIdsPresentation)
|
||||
.thenReturn(initialEmailIdsPresentation.obs);
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
.thenReturn(Rxn(PresentationEmail(id: EmailId(Id('1')))));
|
||||
|
||||
// act
|
||||
threadDetailController.handleGetEmailIdsByThreadIdSuccess(
|
||||
@@ -67,6 +75,7 @@ void main() {
|
||||
EmailId(Id('3')),
|
||||
EmailId(Id('4')),
|
||||
],
|
||||
threadId: null,
|
||||
updateCurrentThreadDetail: true,
|
||||
),
|
||||
);
|
||||
@@ -113,6 +122,7 @@ void main() {
|
||||
EmailId(Id('3')),
|
||||
EmailId(Id('4')),
|
||||
],
|
||||
threadId: null,
|
||||
updateCurrentThreadDetail: false,
|
||||
),
|
||||
);
|
||||
@@ -156,6 +166,7 @@ void main() {
|
||||
EmailId(Id('3')),
|
||||
EmailId(Id('4')),
|
||||
],
|
||||
threadId: null,
|
||||
updateCurrentThreadDetail: false,
|
||||
),
|
||||
);
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ void main() {
|
||||
|
||||
// Act
|
||||
threadDetailController.initializeThreadDetailEmails(
|
||||
GetThreadByIdSuccess(emailIds),
|
||||
GetThreadByIdSuccess(emailIds, threadId: null),
|
||||
);
|
||||
|
||||
// Assert
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ void main() {
|
||||
expect(
|
||||
streamsConsumed,
|
||||
emitsInOrder([
|
||||
Right(PreloadEmailIdsInThreadSuccess([selectedEmail.id!])),
|
||||
Right(PreloadEmailIdsInThreadSuccess([selectedEmail.id!], threadId: ThreadId(Id('1')))),
|
||||
Right(PreloadEmailsByIdsSuccess([selectedEmail])),
|
||||
]),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user