TF-313 Apply new ui for subject, address, body EmailView

This commit is contained in:
dab246
2022-03-09 17:45:34 +07:00
committed by Dat H. Pham
parent 9ce398d937
commit a59ef34df4
12 changed files with 269 additions and 66 deletions
@@ -39,4 +39,15 @@ extension DateTimeNullableExtension on DateTime? {
}
return 'yMMMd';
}
String toPatternForEmailView() {
if (this != null) {
if (this!.isThisYear()) {
return 'dd.MM, HH:mm';
} else {
return 'dd/MM/yyyy';
}
}
return 'dd/MM/yyyy';
}
}
@@ -90,6 +90,9 @@ extension AppColor on Color {
static const colorDividerComposer = Color(0xFFC6C6C8);
static const colorDividerEmailView = Color(0xFFD7D8D9);
static const colorButton = Color(0xFF959DAD);
static const colorTime = Color(0xFF92A1B4);
static const colorEmailAddressPrefix = Color(0xFF9AA7B6);
static const colorEmailAddressTag = Color(0x146D7885);
static const mapGradientColor = [
[Color(0xFF21D4FD), Color(0xFFB721FF)],
@@ -12,6 +12,7 @@ class AvatarBuilder {
OnTapAvatarActionClick? _onTapAvatarActionClick;
List<Color>? _avatarColors;
List<BoxShadow>? _boxShadows;
TextStyle? _textStyle;
void key(Key key) {
_key = key;
@@ -41,6 +42,10 @@ class AvatarBuilder {
_boxShadows = boxShadows;
}
void addTextStyle(TextStyle? textStyle) {
_textStyle = textStyle;
}
void addOnTapActionClick(OnTapAvatarActionClick onTapAvatarActionClick) {
_onTapAvatarActionClick = onTapAvatarActionClick;
}
@@ -81,7 +86,7 @@ class AvatarBuilder {
),
child: Text(
'${_text ?? ''}',
style: TextStyle(fontSize: 20, color: _textColor ?? AppColor.avatarTextColor, fontWeight: FontWeight.w500)
style: _textStyle ?? TextStyle(fontSize: 20, color: _textColor ?? AppColor.avatarTextColor, fontWeight: FontWeight.w500)
)
),
],
@@ -1,5 +1,6 @@
import 'package:flutter/cupertino.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
@@ -13,6 +14,21 @@ extension PrefixEmailAddressExtension on PrefixEmailAddress {
return AppLocalizations.of(context).cc_email_address_prefix;
case PrefixEmailAddress.bcc:
return AppLocalizations.of(context).bcc_email_address_prefix;
case PrefixEmailAddress.from:
return AppLocalizations.of(context).from_email_address_prefix;
}
}
List<EmailAddress> listEmailAddress(PresentationEmail email) {
switch(this) {
case PrefixEmailAddress.to:
return email.to?.toList() ?? List.empty();
case PrefixEmailAddress.cc:
return email.cc?.toList() ?? List.empty();
case PrefixEmailAddress.bcc:
return email.bcc?.toList() ?? List.empty();
case PrefixEmailAddress.from:
return email.from?.toList() ?? List.empty();
}
}
}
@@ -50,6 +50,7 @@ class EmailController extends BaseController {
final DownloadAttachmentForWebInteractor _downloadAttachmentForWebInteractor;
final emailAddressExpandMode = ExpandMode.COLLAPSE.obs;
final isDisplayFullEmailAddress = false.obs;
final attachmentsExpandMode = ExpandMode.COLLAPSE.obs;
final emailContents = <EmailContent>[].obs;
final attachments = <Attachment>[].obs;
@@ -76,8 +77,8 @@ class EmailController extends BaseController {
super.onReady();
mailboxDashBoardController.selectedEmail.listen((presentationEmail) {
if (_currentEmailId != presentationEmail?.id) {
_currentEmailId = presentationEmail?.id;
_clearEmailContent();
_currentEmailId = presentationEmail?.id;
final accountId = mailboxDashBoardController.accountId.value;
if (accountId != null && presentationEmail != null) {
_getEmailContentAction(accountId, presentationEmail.id);
@@ -146,16 +147,28 @@ class EmailController extends BaseController {
}
void _clearEmailContent() {
toggleDisplayEmailAddressAction(expandMode: ExpandMode.COLLAPSE);
attachmentsExpandMode.value = ExpandMode.COLLAPSE;
emailAddressExpandMode.value = ExpandMode.COLLAPSE;
isDisplayFullEmailAddress.value = false;
emailContents.clear();
attachments.clear();
}
void toggleDisplayEmailAddressAction({required ExpandMode expandMode}) {
emailAddressExpandMode.value = expandMode;
void toggleDisplayEmailAddressAction({ExpandMode? expandMode}) {
if (expandMode != null) {
emailAddressExpandMode.value = expandMode;
} else {
final newExpandMode = emailAddressExpandMode.value == ExpandMode.EXPAND ? ExpandMode.COLLAPSE : ExpandMode.EXPAND;
emailAddressExpandMode.value = newExpandMode;
}
if (emailAddressExpandMode.value == ExpandMode.COLLAPSE) {
isDisplayFullEmailAddress.value = false;
}
}
bool get isExpandEmailAddress => emailAddressExpandMode.value == ExpandMode.EXPAND;
void markAsEmailRead(PresentationEmail presentationEmail, ReadActions readActions) async {
final accountId = mailboxDashBoardController.accountId.value;
final mailboxCurrent = mailboxDashBoardController.selectedMailbox.value;
@@ -423,6 +436,10 @@ class EmailController extends BaseController {
items: popupMenuItems);
}
void showFullEmailAddress() {
isDisplayFullEmailAddress.value = true;
}
void closeMoreMenu() {
popBack();
}
@@ -433,6 +450,9 @@ class EmailController extends BaseController {
&& mailboxDashBoardController.selectedEmail.value != null;
void backToThreadView() {
attachmentsExpandMode.value = ExpandMode.COLLAPSE;
emailAddressExpandMode.value = ExpandMode.COLLAPSE;
isDisplayFullEmailAddress.value = false;
popBack();
}
+175 -45
View File
@@ -6,7 +6,9 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_email_address_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/email_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/app_bar_mail_widget_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_file_tile_builder.dart';
@@ -15,7 +17,6 @@ import 'package:tmail_ui_user/features/email/presentation/widgets/bottom_bar_mai
import 'package:tmail_ui_user/features/email/presentation/widgets/email_action_cupertino_action_sheet_action_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_content_item_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_content_place_holder_loading_widget.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/sender_and_receiver_information_tile_builder.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:filesize/filesize.dart';
@@ -25,6 +26,8 @@ class EmailView extends GetView {
final responsiveUtils = Get.find<ResponsiveUtils>();
final imagePaths = Get.find<ImagePaths>();
static const int LIMIT_ADDRESS_DISPLAY = 1;
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -45,9 +48,9 @@ class EmailView extends GetView {
));
}
Widget _buildDivider(){
Widget _buildDivider({EdgeInsets? edgeInsets}){
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
padding: edgeInsets ?? EdgeInsets.symmetric(horizontal: 16),
child: Divider(color: AppColor.colorDividerEmailView, height: 0.5));
}
@@ -91,26 +94,16 @@ class EmailView extends GetView {
color: Colors.white,
margin: EdgeInsets.zero,
alignment: Alignment.topCenter,
child: Obx(() => emailController.mailboxDashBoardController.selectedEmail.value != null
child: Obx(() => emailController.currentEmail != null
? SingleChildScrollView(
physics : ClampingScrollPhysics(),
child: Container(
margin: EdgeInsets.zero,
width: double.infinity,
alignment: Alignment.center,
padding: EdgeInsets.only(
left: 16,
right: 16,
bottom: 16,
top: 16),
padding: EdgeInsets.zero,
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
_buildEmailMessage(context),
])
child: _buildEmailMessage(context)
))
: Center(child: _buildEmailEmpty(context))
)
@@ -126,48 +119,38 @@ class EmailView extends GetView {
Widget _buildEmailSubject() {
return Padding(
padding: EdgeInsets.only(left: 8, top: 25, bottom: 16),
padding: EdgeInsets.only(right: 16),
child: Text(
'${emailController.mailboxDashBoardController.selectedEmail.value?.getEmailTitle()}',
style: TextStyle(fontSize: 22, color: AppColor.colorNameEmail, fontWeight: FontWeight.w700)
style: TextStyle(fontSize: 20, color: AppColor.colorNameEmail)
));
}
Widget _buildEmailMessage(BuildContext context) {
return Container(
padding: EdgeInsets.only(
bottom: 16,
left: 16,
right: 16,
top: 10),
padding: EdgeInsets.only(bottom: 16, left: 16, right: 16, top: 10),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: AppColor.colorShadowBgContentEmail,
spreadRadius: 3,
blurRadius: 3,
offset: Offset(0, 2), // changes position of shadow
),
],
color: Colors.white),
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Obx(() => SenderAndReceiverInformationTileBuilder(
context,
imagePaths,
emailController.mailboxDashBoardController.selectedEmail.value,
emailController.emailAddressExpandMode.value)
.onOpenExpandAddressReceiverActionClick(() => emailController.toggleDisplayEmailAddressAction(expandMode: ExpandMode.EXPAND))
.build()),
Padding(
padding: EdgeInsets.zero,
child: Divider(color: AppColor.lineItemListColor, height: 1, thickness: 0.1)),
_buildEmailSubject(),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(child: _buildEmailSubject()),
_buildEmailTime(context),
]),
_buildDivider(edgeInsets: EdgeInsets.only(top: 16)),
Obx(() => emailController.currentEmail != null
? _buildEmailAddress(
context,
emailController.currentEmail!,
emailController.emailAddressExpandMode.value,
emailController.isDisplayFullEmailAddress.value)
: SizedBox.shrink()),
_buildDivider(edgeInsets: EdgeInsets.only(top: 4)),
_buildAttachments(context),
_buildListEmailContent(),
],
@@ -175,6 +158,153 @@ class EmailView extends GetView {
);
}
Widget _buildEmailTime(BuildContext context) {
return Transform(
transform: Matrix4.translationValues(0.0, 12.0, 0.0),
child: Text(
'${emailController.currentEmail?.getReceivedAt(
Localizations.localeOf(context).toLanguageTag(),
pattern: emailController.currentEmail?.receivedAt?.value.toLocal().toPatternForEmailView())}',
maxLines: 1,
overflow:TextOverflow.ellipsis,
style: TextStyle(fontSize: 13, color: AppColor.colorTime)));
}
Widget _buildEmailAddress(BuildContext context, PresentationEmail email, ExpandMode expandMode, bool isDisplayFull) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (email.from.numberEmailAddress() > 0)
_buildEmailAddressByPrefix(context, email, PrefixEmailAddress.from, isDisplayFull),
if (email.to.numberEmailAddress() > 0 && expandMode == ExpandMode.EXPAND)
_buildDivider(edgeInsets: EdgeInsets.only(top: 4)),
if (email.to.numberEmailAddress() > 0 && expandMode == ExpandMode.EXPAND)
_buildEmailAddressByPrefix(context, email, PrefixEmailAddress.to, isDisplayFull),
if (email.cc.numberEmailAddress() > 0 && expandMode == ExpandMode.EXPAND && isDisplayFull)
_buildDivider(edgeInsets: EdgeInsets.only(top: 4)),
if (email.cc.numberEmailAddress() > 0 && expandMode == ExpandMode.EXPAND && isDisplayFull)
_buildEmailAddressByPrefix(context, email, PrefixEmailAddress.cc, isDisplayFull),
if (email.bcc.numberEmailAddress() > 0 && expandMode == ExpandMode.EXPAND && isDisplayFull)
_buildDivider(edgeInsets: EdgeInsets.only(top: 4)),
if (email.bcc.numberEmailAddress() > 0 && expandMode == ExpandMode.EXPAND && isDisplayFull)
_buildEmailAddressByPrefix(context, email, PrefixEmailAddress.bcc, isDisplayFull),
],
);
}
Widget _buildEmailAddressByPrefix(
BuildContext context,
PresentationEmail presentationEmail,
PrefixEmailAddress prefixEmailAddress,
bool isDisplayFull,
) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(top: 20),
child: Text(
'${prefixEmailAddress.asName(context)}:',
style: TextStyle(fontSize: 14, color: AppColor.colorEmailAddressPrefix))),
Expanded(child: _buildEmailAddressWidget(
context,
presentationEmail,
prefixEmailAddress.listEmailAddress(presentationEmail),
prefixEmailAddress,
isDisplayFull
)),
if (prefixEmailAddress == PrefixEmailAddress.from) _buildEmailAddressDetailButton(context),
]
);
}
Widget _buildEmailAddressWidget(
BuildContext context,
PresentationEmail presentationEmail,
List<EmailAddress> listEmailAddress,
PrefixEmailAddress prefixEmailAddress,
bool isDisplayFull,
) {
final displayedEmailAddress = isDisplayFull ? listEmailAddress : listEmailAddress.sublist(0, LIMIT_ADDRESS_DISPLAY);
return Padding(
padding: EdgeInsets.only(top: 4),
child: Wrap(
children: [
...displayedEmailAddress.map((emailAddress) {
return Padding(
padding: EdgeInsets.only(left: 10),
child: Chip(
labelPadding: EdgeInsets.only(left: 8, right: 8, bottom: 2),
label: Text('${emailAddress.emailAddress}', maxLines: 1, overflow: TextOverflow.ellipsis),
labelStyle: TextStyle(color: AppColor.colorNameEmail, fontSize: 15, fontWeight: FontWeight.normal),
backgroundColor: AppColor.colorEmailAddressTag,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(width: 0, color: AppColor.colorEmailAddressTag),
),
avatar: (AvatarBuilder()
..text('${emailAddress.emailAddress.characters.first.toUpperCase()}')
..addTextStyle(TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.w600))
..avatarColor(emailAddress.avatarColors))
.build(),
)
);
}).toList(),
if (prefixEmailAddress == PrefixEmailAddress.to
&& presentationEmail.numberOfAllEmailAddress() > 1
&& !isDisplayFull)
_buildEmailAddressCounter(context, presentationEmail),
]
));
}
Widget _buildEmailAddressDetailButton(BuildContext context) {
return Material(
borderRadius: BorderRadius.circular(12),
color: Colors.transparent,
child: Padding(
padding: EdgeInsets.only(top: 4, left: 16),
child: TextButton(
onPressed: () => emailController.toggleDisplayEmailAddressAction(),
child: Text(
emailController.isExpandEmailAddress
? AppLocalizations.of(context).hide
: AppLocalizations.of(context).details,
style: TextStyle(fontSize: 15, color: AppColor.colorTextButton, fontWeight: FontWeight.normal),
),
)
)
);
}
String _getRemainCountAddressReceiver(PresentationEmail email) {
if (email.numberOfAllEmailAddress() - LIMIT_ADDRESS_DISPLAY >= 999) {
return '999';
}
return '${email.numberOfAllEmailAddress() - LIMIT_ADDRESS_DISPLAY}';
}
Widget _buildEmailAddressCounter(BuildContext context, PresentationEmail email) {
return GestureDetector(
onTap: () => emailController.showFullEmailAddress(),
child: Padding(
padding: EdgeInsets.only(left: 8),
child: Chip(
labelPadding: EdgeInsets.symmetric(horizontal: 8),
label: Text('+${_getRemainCountAddressReceiver(email)}', maxLines: 1, overflow: TextOverflow.ellipsis),
labelStyle: TextStyle(color: AppColor.colorTextButton, fontSize: 15, fontWeight: FontWeight.normal),
backgroundColor: AppColor.colorEmailAddressTag,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(width: 0, color: AppColor.colorEmailAddressTag),
),
),
),
);
}
Widget _buildAttachments(BuildContext context) {
return Obx(() => emailController.viewState.value.fold(
(failure) => SizedBox.shrink(),
@@ -45,7 +45,7 @@ class BottomBarMailWidgetBuilder {
if (presentationEmail.numberOfAllEmailAddress() > 1)
(ButtonBuilder(_imagePaths.icReplyAll)
..key(Key('button_reply_all_message'))
..size(20)
..size(15)
..paddingIcon(EdgeInsets.only(
top: _responsiveUtils.isMobileDevice(_context) ? 10 : 16,
bottom: _responsiveUtils.isMobileDevice(_context) ? 8 : 16,
@@ -211,7 +211,6 @@ class EmailTileBuilder {
..size(GetPlatform.isWeb ? 48 : 56)
..textColor(Colors.white)
..avatarColor(_presentationEmail.avatarColors))
// .iconStatus(_imagePaths.icOffline)
.build();
}
}
@@ -743,4 +743,16 @@ class AppLocalizations {
name: 'new_message',
);
}
String get details {
return Intl.message(
'Details',
name: 'details');
}
String get hide {
return Intl.message(
'Hide',
name: 'hide');
}
}
@@ -1,5 +1,6 @@
enum PrefixEmailAddress {
from,
to,
cc,
bcc
@@ -1,6 +1,9 @@
import 'dart:ui';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:model/email/email_address_cache.dart';
import 'package:core/core.dart';
import 'package:collection/collection.dart';
extension EmailAddressExtension on EmailAddress {
@@ -33,4 +36,20 @@ extension EmailAddressExtension on EmailAddress {
String get displayName => name ?? '';
EmailAddressCache toEmailAddressCache() => EmailAddressCache(displayName, emailAddress);
List<Color> get avatarColors {
return AppColor.mapGradientColor[_generateIndex()];
}
int _generateIndex() {
if (emailAddress.isNotEmpty) {
final codeUnits = emailAddress.codeUnits;
if (codeUnits.isNotEmpty) {
final sumCodeUnits = codeUnits.sum;
final index = sumCodeUnits % AppColor.mapGradientColor.length;
return index;
}
}
return 0;
}
}
@@ -6,24 +6,11 @@ import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/model.dart';
import 'package:collection/collection.dart';
extension PresentationEmailExtension on PresentationEmail {
List<Color> get avatarColors {
return AppColor.mapGradientColor[_generateIndexFromSender()];
}
int _generateIndexFromSender() {
if (from != null && from?.isNotEmpty == true) {
final codeUnits = from?.first.email?.codeUnits ?? List.empty();
if (codeUnits.isNotEmpty) {
final sumCodeUnits = codeUnits.sum;
final index = sumCodeUnits % AppColor.mapGradientColor.length;
return index;
}
}
return 0;
return from?.first.avatarColors ?? AppColor.mapGradientColor.first;
}
int numberOfAllEmailAddress() => to.numberEmailAddress() + cc.numberEmailAddress() + bcc.numberEmailAddress();