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,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(
|
||||
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: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/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/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/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/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/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 {
|
||||
final ThreadDetailArguments arguments;
|
||||
final GetThreadByIdInteractor _getEmailIdsByThreadIdInteractor;
|
||||
final GetEmailsByIdsInteractor getEmailsByIdsInteractor;
|
||||
|
||||
ThreadDetailController(
|
||||
this.arguments,
|
||||
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 searchEmailController = Get.find<SearchEmailController>();
|
||||
|
||||
ScrollController? scrollController;
|
||||
|
||||
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
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (accountId != null) {
|
||||
consumeState(_getEmailIdsByThreadIdInteractor.execute(
|
||||
arguments.threadId,
|
||||
accountId!,
|
||||
));
|
||||
}
|
||||
ever(mailboxDashBoardController.selectedEmail, (presentationEmail) {
|
||||
if (presentationEmail?.threadId == null) {
|
||||
closeThreadDetailAction(currentContext);
|
||||
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
|
||||
void handleSuccessViewState(success) {
|
||||
if (success is GetThreadByIdSuccess) {
|
||||
handleGetEmailIdsByThreadIdSuccess(success);
|
||||
initializeThreadDetailEmails();
|
||||
} else if (success is GetEmailsByIdsSuccess) {
|
||||
handleGetEmailsByIdsSuccess(success);
|
||||
} else {
|
||||
super.handleSuccessViewState(success);
|
||||
}
|
||||
@@ -46,7 +111,11 @@ class ThreadDetailController extends BaseController {
|
||||
@override
|
||||
void handleFailureViewState(failure) {
|
||||
if (failure is GetThreadByIdFailure) {
|
||||
// TODO: handle failure
|
||||
showRetryToast(failure);
|
||||
return;
|
||||
}
|
||||
if (failure is GetEmailsByIdsFailure) {
|
||||
showRetryToast(failure);
|
||||
return;
|
||||
}
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user