TF-138 Add domain layer for search mail with text pattern
This commit is contained in:
@@ -25,6 +25,7 @@ export 'presentation/views/text/slogan_builder.dart';
|
||||
export 'presentation/views/text/text_field_builder.dart';
|
||||
export 'presentation/views/text/input_decoration_builder.dart';
|
||||
export 'presentation/views/text/text_builder.dart';
|
||||
export 'presentation/views/text/rich_text_builder.dart';
|
||||
export 'presentation/views/responsive/responsive_widget.dart';
|
||||
export 'presentation/views/list/tree_view.dart';
|
||||
export 'presentation/views/button/button_builder.dart';
|
||||
|
||||
@@ -59,4 +59,7 @@ extension AppColor on Color {
|
||||
static const toastWithActionBackgroundColor = Color(0xFF3F3F3F);
|
||||
static const buttonActionToastWithActionColor = Color(0xFF7ADCF8);
|
||||
static const backgroundCountAttachment = Color(0x681C1C1C);
|
||||
static const bgStatusResultSearch = Color(0xFFF5F5F7);
|
||||
static const bgWordSearch = Color(0xFFD7D6FC);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RichTextBuilder {
|
||||
|
||||
final String _textOrigin;
|
||||
final String _wordToStyle;
|
||||
final TextStyle _styleOrigin;
|
||||
final TextStyle _styleWord;
|
||||
|
||||
Key? _key;
|
||||
int? _maxLines;
|
||||
TextOverflow? _overflow;
|
||||
|
||||
RichTextBuilder(
|
||||
this._textOrigin,
|
||||
this._wordToStyle,
|
||||
this._styleOrigin,
|
||||
this._styleWord,
|
||||
);
|
||||
|
||||
void key(Key key) {
|
||||
_key = key;
|
||||
}
|
||||
|
||||
void maxLines(int maxLines) {
|
||||
_maxLines = maxLines;
|
||||
}
|
||||
|
||||
void setOverflow(TextOverflow textOverflow) {
|
||||
_overflow = textOverflow;
|
||||
}
|
||||
|
||||
RichText build() {
|
||||
return RichText(
|
||||
key: _key,
|
||||
maxLines: _maxLines ?? 1,
|
||||
overflow: _overflow ?? TextOverflow.ellipsis,
|
||||
text: TextSpan(
|
||||
style: _styleOrigin,
|
||||
children: _getSpans(_textOrigin, _wordToStyle, _styleWord)));
|
||||
}
|
||||
|
||||
List<TextSpan> _getSpans(String text, String matchWord, TextStyle style) {
|
||||
List<TextSpan> spans = [];
|
||||
int spanBoundary = 0;
|
||||
do {
|
||||
// look for the next match
|
||||
final startIndex = text.toLowerCase().indexOf(matchWord.toLowerCase(), spanBoundary);
|
||||
// if no more matches then add the rest of the string without style
|
||||
if (startIndex == -1) {
|
||||
spans.add(TextSpan(text: text.substring(spanBoundary)));
|
||||
return spans;
|
||||
}
|
||||
// add any unStyled text before the next match
|
||||
if (startIndex > spanBoundary) {
|
||||
spans.add(TextSpan(text: text.substring(spanBoundary, startIndex)));
|
||||
}
|
||||
// style the matched text
|
||||
final endIndex = startIndex + matchWord.length;
|
||||
final spanText = text.substring(startIndex, endIndex);
|
||||
spans.add(TextSpan(text: spanText, style: style));
|
||||
// mark the boundary to start the next search from
|
||||
spanBoundary = endIndex;
|
||||
// continue until there are no more matches
|
||||
} while (spanBoundary < text.length);
|
||||
|
||||
return spans;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef OnOpenSearchMailActionClick = void Function();
|
||||
|
||||
class TextFieldBuilder {
|
||||
Key? _key;
|
||||
ValueChanged<String>? _onTextChange;
|
||||
ValueChanged<String>? _onTextSubmitted;
|
||||
TextStyle? _textStyle;
|
||||
TextInputAction? _textInputAction;
|
||||
InputDecoration? _inputDecoration;
|
||||
@@ -11,6 +14,9 @@ class TextFieldBuilder {
|
||||
int? _maxLines = 1;
|
||||
TextEditingController? _textController;
|
||||
TextInputType? _keyboardType;
|
||||
Color? _cursorColor;
|
||||
bool? _autoFocus;
|
||||
FocusNode? _focusNode;
|
||||
|
||||
void key(Key key) {
|
||||
_key = key;
|
||||
@@ -20,6 +26,10 @@ class TextFieldBuilder {
|
||||
_onTextChange = onChange;
|
||||
}
|
||||
|
||||
void onSubmitted(ValueChanged<String> onSubmitted) {
|
||||
_onTextSubmitted = onSubmitted;
|
||||
}
|
||||
|
||||
void textStyle(TextStyle style) {
|
||||
_textStyle = style;
|
||||
}
|
||||
@@ -40,7 +50,7 @@ class TextFieldBuilder {
|
||||
_textController = TextEditingController.fromValue(TextEditingValue(text: value));
|
||||
}
|
||||
|
||||
void addController(TextEditingController textEditingController) {
|
||||
void addController(TextEditingController? textEditingController) {
|
||||
_textController = textEditingController;
|
||||
}
|
||||
|
||||
@@ -52,11 +62,23 @@ class TextFieldBuilder {
|
||||
_keyboardType = value;
|
||||
}
|
||||
|
||||
void cursorColor(Color? color) {
|
||||
_cursorColor = color;
|
||||
}
|
||||
|
||||
void autoFocus(bool autoFocus) {
|
||||
_autoFocus = autoFocus;
|
||||
}
|
||||
|
||||
void addFocusNode(FocusNode? focusNode) {
|
||||
_focusNode = focusNode;
|
||||
}
|
||||
|
||||
TextField build() {
|
||||
return TextField(
|
||||
key: _key ?? Key('TextFieldBuilder'),
|
||||
onChanged: _onTextChange,
|
||||
cursorColor: AppColor.primaryColor,
|
||||
cursorColor: _cursorColor ?? AppColor.primaryColor,
|
||||
controller: _textController,
|
||||
autocorrect: false,
|
||||
textInputAction: _textInputAction,
|
||||
@@ -66,6 +88,9 @@ class TextFieldBuilder {
|
||||
style: _textStyle ?? TextStyle(color: AppColor.textFieldTextColor),
|
||||
obscureText: _obscureText ?? false,
|
||||
keyboardType: _keyboardType,
|
||||
onSubmitted: _onTextSubmitted,
|
||||
autofocus: _autoFocus ?? false,
|
||||
focusNode: _focusNode,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class SearchQuery with EquatableMixin {
|
||||
final String value;
|
||||
|
||||
SearchQuery(this.value);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [value];
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/sort/comparator.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/state.dart' as jmap;
|
||||
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:model/model.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart';
|
||||
@@ -40,4 +41,14 @@ abstract class ThreadRepository {
|
||||
Properties? properties,
|
||||
}
|
||||
);
|
||||
|
||||
Future<List<Email>> searchEmails(
|
||||
AccountId accountId,
|
||||
{
|
||||
UnsignedInt? limit,
|
||||
Set<Comparator>? sort,
|
||||
Filter? filter,
|
||||
Properties? properties,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
|
||||
|
||||
class SearchingState extends UIState {
|
||||
|
||||
SearchingState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class SearchEmailNewQuery extends UIState {
|
||||
final SearchQuery searchQuery;
|
||||
|
||||
SearchEmailNewQuery(this.searchQuery);
|
||||
|
||||
@override
|
||||
List<Object> get props => [searchQuery];
|
||||
}
|
||||
|
||||
class SearchEmailSuccess extends UIState {
|
||||
final List<PresentationEmail> emailList;
|
||||
|
||||
SearchEmailSuccess(this.emailList);
|
||||
|
||||
@override
|
||||
List<Object> get props => [emailList];
|
||||
}
|
||||
|
||||
class SearchEmailFailure extends FeatureFailure {
|
||||
final exception;
|
||||
|
||||
SearchEmailFailure(this.exception);
|
||||
|
||||
@override
|
||||
List<Object> get props => [exception];
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:model/model.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/filter/filter.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/sort/comparator.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart';
|
||||
|
||||
class SearchEmailInteractor {
|
||||
|
||||
final ThreadRepository threadRepository;
|
||||
|
||||
SearchEmailInteractor(this.threadRepository);
|
||||
|
||||
Stream<Either<Failure, Success>> execute(
|
||||
AccountId accountId,
|
||||
{
|
||||
UnsignedInt? limit,
|
||||
Set<Comparator>? sort,
|
||||
Filter? filter,
|
||||
Properties? properties,
|
||||
}
|
||||
) async* {
|
||||
try {
|
||||
yield Right(SearchingState());
|
||||
|
||||
final emailList = await threadRepository.searchEmails(
|
||||
accountId,
|
||||
limit: limit,
|
||||
sort: sort,
|
||||
filter: filter,
|
||||
properties: properties);
|
||||
|
||||
final presentationEmailList = emailList
|
||||
.map((email) => email.toPresentationEmail())
|
||||
.toList();
|
||||
|
||||
yield Right(SearchEmailSuccess(presentationEmailList));
|
||||
} catch (e) {
|
||||
yield Left(SearchEmailFailure(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import 'package:jmap_dart_client/jmap/core/utc_date.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
|
||||
import 'package:model/mailbox/select_mode.dart';
|
||||
import 'package:model/extensions/email_address_extension.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:model/model.dart';
|
||||
|
||||
class PresentationEmail with EquatableMixin {
|
||||
|
||||
@@ -23,6 +23,8 @@ class PresentationEmail with EquatableMixin {
|
||||
final Set<EmailAddress>? cc;
|
||||
final Set<EmailAddress>? bcc;
|
||||
final Set<EmailAddress>? replyTo;
|
||||
final Map<MailboxId, bool>? mailboxIds;
|
||||
final List<MailboxName?>? mailboxNames;
|
||||
final SelectMode selectMode;
|
||||
|
||||
PresentationEmail(
|
||||
@@ -40,6 +42,8 @@ class PresentationEmail with EquatableMixin {
|
||||
this.cc,
|
||||
this.bcc,
|
||||
this.replyTo,
|
||||
this.mailboxIds,
|
||||
this.mailboxNames,
|
||||
this.selectMode = SelectMode.INACTIVE
|
||||
}
|
||||
);
|
||||
@@ -65,6 +69,8 @@ class PresentationEmail with EquatableMixin {
|
||||
|
||||
bool isFlaggedEmail() => keywords?.containsKey(KeyWordIdentifier.emailFlagged) == true;
|
||||
|
||||
String get mailboxName => mailboxNames?.first?.name ?? '';
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
|
||||
@@ -57,6 +57,7 @@ extension EmailExtension on Email {
|
||||
cc: cc,
|
||||
bcc: bcc,
|
||||
replyTo: replyTo,
|
||||
mailboxIds: mailboxIds,
|
||||
selectMode: selectMode
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ extension PresentationEmailExtension on PresentationEmail {
|
||||
cc: cc,
|
||||
bcc: bcc,
|
||||
replyTo: replyTo,
|
||||
mailboxIds: mailboxIds,
|
||||
mailboxNames: mailboxNames,
|
||||
selectMode: selectMode == SelectMode.INACTIVE ? SelectMode.ACTIVE : SelectMode.INACTIVE
|
||||
);
|
||||
}
|
||||
@@ -53,6 +55,8 @@ extension PresentationEmailExtension on PresentationEmail {
|
||||
cc: cc,
|
||||
bcc: bcc,
|
||||
replyTo: replyTo,
|
||||
mailboxIds: mailboxIds,
|
||||
mailboxNames: mailboxNames,
|
||||
selectMode: selectMode
|
||||
);
|
||||
}
|
||||
@@ -102,4 +106,37 @@ extension PresentationEmailExtension on PresentationEmail {
|
||||
return Tuple3([], [], []);
|
||||
}
|
||||
}
|
||||
|
||||
PresentationEmail toSearchPresentationEmail(Map<MailboxId, PresentationMailbox> mapMailboxes) {
|
||||
mailboxIds?.removeWhere((key, value) => !value);
|
||||
|
||||
final listMailboxId = mailboxIds?.entries
|
||||
.where((entry) => entry.value)
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
|
||||
final listMailboxName = listMailboxId
|
||||
?.map((mailboxId) => mapMailboxes.containsKey(mailboxId) ? mapMailboxes[mailboxId]?.name : null)
|
||||
.where((mailboxName) => mailboxName != null)
|
||||
.toList();
|
||||
|
||||
return PresentationEmail(
|
||||
this.id,
|
||||
keywords: keywords,
|
||||
size: size,
|
||||
receivedAt: receivedAt,
|
||||
hasAttachment: hasAttachment,
|
||||
preview: preview,
|
||||
subject: subject,
|
||||
sentAt: sentAt,
|
||||
from: from,
|
||||
to: to,
|
||||
cc: cc,
|
||||
bcc: bcc,
|
||||
replyTo: replyTo,
|
||||
mailboxIds: mailboxIds,
|
||||
mailboxNames: listMailboxName,
|
||||
selectMode: selectMode
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user