From 6b31252e7a3364a9e9cbc76c616b5902e426d93e Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 18 Aug 2021 17:43:22 +0700 Subject: [PATCH] Add object for model and core module --- assets/images/ic_attachment_file.svg | 4 ++ assets/images/{ic_star.svg => ic_flag.svg} | 0 assets/images/ic_flagged.svg | 3 + assets/images/ic_more_receiver.svg | 3 + assets/images/ic_offline.svg | 3 + assets/images/ic_online.svg | 3 + core/lib/core.dart | 5 ++ .../domain/extensions/datetime_extension.dart | 18 ++++++ .../extensions/capitalize_extension.dart | 6 ++ .../extensions/color_extension.dart | 7 ++ .../presentation/resources/image_paths.dart | 7 +- .../views/button/button_builder.dart | 12 +++- .../views/image/avatar_builder.dart | 64 +++++++++++++++++++ .../sliver_grid_delegate_fixed_height.dart | 64 +++++++++++++++++++ model/lib/email/email_content.dart | 30 +++++++++ model/lib/email/presentation_email.dart | 19 +++--- .../list_email_address_extension.dart | 24 +++++++ model/lib/extensions/session_extension.dart | 16 +++++ model/lib/mailbox/presentation_mailbox.dart | 21 +++--- model/lib/model.dart | 5 +- model/pubspec.yaml | 3 + pubspec.yaml | 15 +++++ 22 files changed, 306 insertions(+), 26 deletions(-) create mode 100644 assets/images/ic_attachment_file.svg rename assets/images/{ic_star.svg => ic_flag.svg} (100%) create mode 100644 assets/images/ic_flagged.svg create mode 100644 assets/images/ic_more_receiver.svg create mode 100644 assets/images/ic_offline.svg create mode 100644 assets/images/ic_online.svg create mode 100644 core/lib/domain/extensions/datetime_extension.dart create mode 100644 core/lib/presentation/extensions/capitalize_extension.dart create mode 100644 core/lib/presentation/views/image/avatar_builder.dart create mode 100644 core/lib/presentation/views/list/sliver_grid_delegate_fixed_height.dart create mode 100644 model/lib/email/email_content.dart create mode 100644 model/lib/extensions/list_email_address_extension.dart create mode 100644 model/lib/extensions/session_extension.dart diff --git a/assets/images/ic_attachment_file.svg b/assets/images/ic_attachment_file.svg new file mode 100644 index 000000000..5dbd3c36c --- /dev/null +++ b/assets/images/ic_attachment_file.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/ic_star.svg b/assets/images/ic_flag.svg similarity index 100% rename from assets/images/ic_star.svg rename to assets/images/ic_flag.svg diff --git a/assets/images/ic_flagged.svg b/assets/images/ic_flagged.svg new file mode 100644 index 000000000..c7ab48776 --- /dev/null +++ b/assets/images/ic_flagged.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_more_receiver.svg b/assets/images/ic_more_receiver.svg new file mode 100644 index 000000000..ed84595bf --- /dev/null +++ b/assets/images/ic_more_receiver.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_offline.svg b/assets/images/ic_offline.svg new file mode 100644 index 000000000..17336fad9 --- /dev/null +++ b/assets/images/ic_offline.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_online.svg b/assets/images/ic_online.svg new file mode 100644 index 000000000..0578d1d15 --- /dev/null +++ b/assets/images/ic_online.svg @@ -0,0 +1,3 @@ + + + diff --git a/core/lib/core.dart b/core/lib/core.dart index 5b4de79d2..6e5d7965b 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -3,6 +3,9 @@ library core; // Extensions export 'presentation/extensions/color_extension.dart'; export 'presentation/extensions/url_extension.dart'; +export 'presentation/extensions/capitalize_extension.dart'; +export 'presentation/extensions/list_extensions.dart'; +export 'domain/extensions/datetime_extension.dart'; // Utils export 'presentation/utils/theme_utils.dart'; @@ -19,6 +22,8 @@ export 'presentation/views/text/text_builder.dart'; export 'presentation/views/responsive/responsive_widget.dart'; export 'presentation/views/list/tree_view.dart'; export 'presentation/views/button/button_builder.dart'; +export 'presentation/views/image/avatar_builder.dart'; +export 'presentation/views/list/sliver_grid_delegate_fixed_height.dart'; // Resources export 'presentation/resources/assets_paths.dart'; diff --git a/core/lib/domain/extensions/datetime_extension.dart b/core/lib/domain/extensions/datetime_extension.dart new file mode 100644 index 000000000..1b95a08e7 --- /dev/null +++ b/core/lib/domain/extensions/datetime_extension.dart @@ -0,0 +1,18 @@ + +extension DateTimeExtension on DateTime { + + bool isToday() { + final now = DateTime.now(); + return now.day == this.day && now.month == this.month && now.year == this.year; + } + + bool isYesterday() { + final yesterday = DateTime.now().subtract(Duration(days: 1)); + return yesterday.day == this.day && yesterday.month == this.month && yesterday.year == this.year; + } + + bool isThisYear() { + final now = DateTime.now(); + return now.year == this.year; + } +} diff --git a/core/lib/presentation/extensions/capitalize_extension.dart b/core/lib/presentation/extensions/capitalize_extension.dart new file mode 100644 index 000000000..339b20753 --- /dev/null +++ b/core/lib/presentation/extensions/capitalize_extension.dart @@ -0,0 +1,6 @@ + +extension CapitalizeExtension on String { + String get inCaps => this.length > 0 ?'${this[0].toUpperCase()}${this.substring(1)}':''; + String get allInCaps => this.toUpperCase(); + String get capitalizeFirstEach => this.replaceAll(RegExp(' +'), ' ').split(" ").map((str) => str.inCaps).join(" "); +} \ No newline at end of file diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 17b7c8f44..23f4f4556 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -39,4 +39,11 @@ extension AppColor on Color { static const bgMailboxListMail = Color(0xFFFBFBFF); static const bgMessenger = Color(0xFFF2F2F5); static const textButtonColor = Color(0xFF182952); + static const attachmentFileBorderColor = Color(0xFFEAEAEA); + static const attachmentFileNameColor = Color(0xFF182952); + static const attachmentFileSizeColor = Color(0xFF7E869B); + static const avatarColor = Color(0xFFE6E5FF); + static const avatarTextColor = Color(0xFF3840F7); + static const sentTimeTextColorUnRead = Color(0xFF182952); + static const subjectEmailTextColorUnRead = Color(0xFF3840F7); } diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index 64824ba8a..fdd3369fe 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -22,10 +22,15 @@ class ImagePaths { String get icForward => _getImagePath('ic_forward.svg'); String get icReply => _getImagePath('ic_reply.svg'); String get icReplyAll => _getImagePath('ic_reply_all.svg'); - String get icStar => _getImagePath('ic_star.svg'); + String get icFlag => _getImagePath('ic_flag.svg'); String get icTrash => _getImagePath('ic_trash.svg'); String get icCalendar => _getImagePath('ic_calendar.svg'); String get icShare => _getImagePath('ic_share.svg'); + String get icAttachmentFile => _getImagePath('ic_attachment_file.svg'); + String get icOnline => _getImagePath('ic_online.svg'); + String get icOffline => _getImagePath('ic_offline.svg'); + String get icFlagged => _getImagePath('ic_flagged.svg'); + String get icMoreReceiver => _getImagePath('ic_more_receiver.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/core/lib/presentation/views/button/button_builder.dart b/core/lib/presentation/views/button/button_builder.dart index 78ec79f4f..704543896 100644 --- a/core/lib/presentation/views/button/button_builder.dart +++ b/core/lib/presentation/views/button/button_builder.dart @@ -13,6 +13,7 @@ class ButtonBuilder { String? _icon; String? _text; double? _size; + double? _padding; bool? _isVertical; Key? _key; @@ -26,6 +27,11 @@ class ButtonBuilder { return this; } + ButtonBuilder padding(double padding) { + _padding = padding; + return this; + } + ButtonBuilder text(String text, {required bool isVertical}) { _text = text; _isVertical = isVertical; @@ -66,7 +72,6 @@ class ButtonBuilder { mainAxisAlignment: MainAxisAlignment.center, children: [ _buildIcon(), - SizedBox(height: 8), _buildText(), ]) : Row( @@ -74,7 +79,6 @@ class ButtonBuilder { mainAxisAlignment: MainAxisAlignment.center, children: [ _buildIcon(), - SizedBox(width: 8), _buildText(), ]); } else { @@ -82,7 +86,9 @@ class ButtonBuilder { } } - Widget _buildIcon() => SvgPicture.asset(_icon ?? '', width: _size ?? 24, height: _size ?? 24, fit: BoxFit.fill); + Widget _buildIcon() => Padding( + padding: EdgeInsets.all(_padding ?? 10), + child: SvgPicture.asset(_icon ?? '', width: _size ?? 24, height: _size ?? 24, fit: BoxFit.fill)); Widget _buildText() { return Text( diff --git a/core/lib/presentation/views/image/avatar_builder.dart b/core/lib/presentation/views/image/avatar_builder.dart new file mode 100644 index 000000000..e853eda60 --- /dev/null +++ b/core/lib/presentation/views/image/avatar_builder.dart @@ -0,0 +1,64 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class AvatarBuilder { + Key? _key; + String? _text; + double? _size; + String? _iconStatus; + Color? _bgColor; + + AvatarBuilder key(Key key) { + _key = key; + return this; + } + + AvatarBuilder size(double size) { + _size = size; + return this; + } + + AvatarBuilder text(String text) { + _text = text; + return this; + } + + AvatarBuilder iconStatus(String iconStatus) { + _iconStatus = iconStatus; + return this; + } + + AvatarBuilder backgroundColor(Color bgColor) { + _bgColor = bgColor; + return this; + } + + Widget build() { + return Container( + key: _key, + width: _size ?? 40, + height: _size ?? 40, + alignment: Alignment.center, + child: Stack( + children: [ + Container( + width: _size ?? 40, + height: _size ?? 40, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular((_size ?? 40) / 2), + border: Border.all(color: Colors.white), + color: _bgColor ?? AppColor.avatarColor), + child: Text( + '${_text ?? ''}', + style: TextStyle(fontSize: 20, color: AppColor.avatarTextColor, fontWeight: FontWeight.w500))), + if (_iconStatus != null && _iconStatus!.isNotEmpty) + Align( + child: SvgPicture.asset(_iconStatus!, width: 12, height: 12, fit: BoxFit.fill), + alignment: Alignment.bottomRight) + ], + ) + ); + } +} \ No newline at end of file diff --git a/core/lib/presentation/views/list/sliver_grid_delegate_fixed_height.dart b/core/lib/presentation/views/list/sliver_grid_delegate_fixed_height.dart new file mode 100644 index 000000000..85843486f --- /dev/null +++ b/core/lib/presentation/views/list/sliver_grid_delegate_fixed_height.dart @@ -0,0 +1,64 @@ +import 'package:flutter/rendering.dart'; + +class SliverGridDelegateFixedHeight + extends SliverGridDelegate { + /// Creates a delegate that makes grid layouts with a fixed number of tiles in + /// the cross axis. + /// + /// All of the arguments must not be null. The `mainAxisSpacing` and + /// `crossAxisSpacing` arguments must not be negative. The `crossAxisCount` + /// and `childAspectRatio` arguments must be greater than zero. + const SliverGridDelegateFixedHeight({ + required this.crossAxisCount, + this.mainAxisSpacing = 0.0, + this.crossAxisSpacing = 0.0, + this.height = 56.0, + }) : assert(crossAxisCount > 0), + assert(mainAxisSpacing >= 0), + assert(crossAxisSpacing >= 0), + assert(height > 0); + + /// The number of children in the cross axis. + final int crossAxisCount; + + /// The number of logical pixels between each child along the main axis. + final double mainAxisSpacing; + + /// The number of logical pixels between each child along the cross axis. + final double crossAxisSpacing; + + /// The height of the crossAxis. + final double height; + + bool _debugAssertIsValid() { + assert(crossAxisCount > 0); + assert(mainAxisSpacing >= 0.0); + assert(crossAxisSpacing >= 0.0); + assert(height > 0.0); + return true; + } + + @override + SliverGridLayout getLayout(SliverConstraints constraints) { + assert(_debugAssertIsValid()); + final double usableCrossAxisExtent = constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1); + final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount; + final double childMainAxisExtent = height; + return SliverGridRegularTileLayout( + crossAxisCount: crossAxisCount, + mainAxisStride: childMainAxisExtent + mainAxisSpacing, + crossAxisStride: childCrossAxisExtent + crossAxisSpacing, + childMainAxisExtent: childMainAxisExtent, + childCrossAxisExtent: childCrossAxisExtent, + reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection), + ); + } + + @override + bool shouldRelayout(SliverGridDelegateFixedHeight oldDelegate) { + return oldDelegate.crossAxisCount != crossAxisCount || + oldDelegate.mainAxisSpacing != mainAxisSpacing || + oldDelegate.crossAxisSpacing != crossAxisSpacing || + oldDelegate.height != height; + } +} diff --git a/model/lib/email/email_content.dart b/model/lib/email/email_content.dart new file mode 100644 index 000000000..af5c633a4 --- /dev/null +++ b/model/lib/email/email_content.dart @@ -0,0 +1,30 @@ + +import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_body_value.dart'; + +class EmailContent with EquatableMixin { + + final EmailId id; + final Set? htmlBody; + final Set? attachments; + final Map? bodyValues; + + EmailContent( + this.id, + { + this.htmlBody, + this.attachments, + this.bodyValues + } + ); + + @override + List get props => [ + id, + htmlBody, + attachments, + bodyValues + ]; +} \ No newline at end of file diff --git a/model/lib/email/presentation_email.dart b/model/lib/email/presentation_email.dart index abdd78e49..90d27f29f 100644 --- a/model/lib/email/presentation_email.dart +++ b/model/lib/email/presentation_email.dart @@ -1,7 +1,5 @@ import 'package:equatable/equatable.dart'; -import 'package:intl/intl.dart'; -import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/core/utc_date.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; @@ -12,8 +10,6 @@ import 'package:model/extensions/email_address_extension.dart'; class PresentationEmail with EquatableMixin { - static PresentationEmail presentationEmailEmpty = PresentationEmail(EmailId(Id('empty'))); - final EmailId id; final Map? keywords; final UnsignedInt? size; @@ -55,17 +51,20 @@ class PresentationEmail with EquatableMixin { return ''; } + String getAvatarText() { + if (getSenderName().isNotEmpty) { + return getSenderName().substring(0, 1).toUpperCase(); + } + return ''; + } + String getEmailTitle() => subject != null ? subject! : ''; String getPartialContent() => preview != null ? preview! : ''; - String getTimeForToday() => receivedAt != null ? DateFormat('h:mm a').format(receivedAt!.value) : ''; + bool isUnReadEmail() => !(keywords?.containsKey(KeyWordIdentifier.emailSeen) == true); - String getTimeForYesterday() => receivedAt != null ? DateFormat('EEE').format(receivedAt!.value) : ''; - - String getTimeThisYear() => receivedAt != null ? DateFormat('MMMd').format(receivedAt!.value) : ''; - - String getTimeOtherYear() => receivedAt != null ? DateFormat('yMMMd').format(receivedAt!.value) : ''; + bool isFlaggedEmail() => keywords?.containsKey(KeyWordIdentifier.emailFlagged) == true; @override List get props => [ diff --git a/model/lib/extensions/list_email_address_extension.dart b/model/lib/extensions/list_email_address_extension.dart new file mode 100644 index 000000000..c618186f5 --- /dev/null +++ b/model/lib/extensions/list_email_address_extension.dart @@ -0,0 +1,24 @@ +import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:model/mailbox/expand_mode.dart'; +import 'package:model/extensions/email_address_extension.dart'; + +extension ListEmailAddressExtension on Set? { + + List getListEmailAddress({ExpandMode expandMode = ExpandMode.EXPAND, int limitAddress = 1}) { + if (this != null) { + if (expandMode == ExpandMode.EXPAND) { + return this!.map((emailAddress) => emailAddress.asString()).toList(); + } else { + final address = this!.map((emailAddress) => emailAddress.asString()).toList(); + return address.length > limitAddress ? address.sublist(0, limitAddress) : address; + } + } + return []; + } + + String listEmailAddressToString({ExpandMode expandMode = ExpandMode.EXPAND, int limitAddress = 1}) { + return getListEmailAddress(expandMode: expandMode, limitAddress: limitAddress).join(', '); + } + + int numberEmailAddress() => this != null ? this!.length : 0; +} \ No newline at end of file diff --git a/model/lib/extensions/session_extension.dart b/model/lib/extensions/session_extension.dart new file mode 100644 index 000000000..93eda7f80 --- /dev/null +++ b/model/lib/extensions/session_extension.dart @@ -0,0 +1,16 @@ + +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:uri/uri.dart'; + +extension SessionExtension on Session { + + String getDownloadUrl(String accountId, String blobId, String name, String type) { + final downloadUriTemplate = UriTemplate('${downloadUrl.origin}'); + return downloadUriTemplate.expand({ + 'accountId' : '$accountId', + 'blobId' : '$blobId', + 'name' : '$name', + 'type' : '$type', + }); + } +} \ No newline at end of file diff --git a/model/lib/mailbox/presentation_mailbox.dart b/model/lib/mailbox/presentation_mailbox.dart index 89a044f23..7f6d38c0e 100644 --- a/model/lib/mailbox/presentation_mailbox.dart +++ b/model/lib/mailbox/presentation_mailbox.dart @@ -1,12 +1,13 @@ import 'package:equatable/equatable.dart'; -import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox_rights.dart'; import 'package:model/mailbox/select_mode.dart'; class PresentationMailbox with EquatableMixin { - static PresentationMailbox presentationMailboxEmpty = PresentationMailbox(MailboxId(Id('empty'))); + static final roleTrash = Role('trash'); + static final roleSent = Role('sent'); + static final roleTemplates = Role('templates'); final MailboxId id; final MailboxName? name; @@ -43,17 +44,15 @@ class PresentationMailbox with EquatableMixin { bool hasRole() => role != null && role!.value.isNotEmpty; String getCountUnReadEmails() { - if (unreadEmails == null) { + if (role == roleTrash || role == roleSent || role == roleTemplates) { return ''; - } else { - if (unreadEmails!.value.value <= 0) { - return ''; - } else { - return unreadEmails!.value.value <= 999 - ? '${unreadEmails!.value.value}' - : '999+'; - } } + + if (unreadEmails == null || unreadEmails!.value.value <= 0) { + return ''; + } + + return unreadEmails!.value.value <= 999 ? '${unreadEmails!.value.value}' : '999+'; } @override diff --git a/model/lib/model.dart b/model/lib/model.dart index 449879aa2..8e2ed3f9c 100644 --- a/model/lib/model.dart +++ b/model/lib/model.dart @@ -15,6 +15,9 @@ export 'mailbox/expand_mode.dart'; // Email export 'email/presentation_email.dart'; +export 'email/email_content.dart'; // Extensions -export 'extensions/email_address_extension.dart'; \ No newline at end of file +export 'extensions/email_address_extension.dart'; +export 'extensions/list_email_address_extension.dart'; +export 'extensions/session_extension.dart'; \ No newline at end of file diff --git a/model/pubspec.yaml b/model/pubspec.yaml index 4636fde62..3da4790d7 100644 --- a/model/pubspec.yaml +++ b/model/pubspec.yaml @@ -43,6 +43,9 @@ dependencies: url: git://github.com/linagora/jmap-dart-client.git ref: master + # uri + uri: 1.0.0 + dev_dependencies: flutter_test: sdk: flutter diff --git a/pubspec.yaml b/pubspec.yaml index f3b0c0835..96f708e18 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -80,6 +80,21 @@ dependencies: url: git://github.com/linagora/jmap-dart-client.git ref: master + # http_parser + http_parser: 4.0.0 + + # file_size + filesize: 2.0.1 + + # flutter_widget_from_html + flutter_widget_from_html: 0.6.2 + + # photo_view + photo_view: 0.12.0 + + # url_launcher + url_launcher: 6.0.9 + dev_dependencies: flutter_test: sdk: flutter