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:
DatDang
2025-05-12 15:22:29 +07:00
committed by Dat H. Pham
parent 7c3af832e4
commit 58a576adeb
84 changed files with 2845 additions and 558 deletions
@@ -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),
),
),
),
],
),
);
}
}