Add suggestion for From/To dialog when input is new email address
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:core/presentation/state/failure.dart';
|
||||||
|
import 'package:core/presentation/state/success.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:get/get_utils/src/get_utils/get_utils.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||||
|
import 'package:model/model.dart';
|
||||||
|
import 'package:tmail_ui_user/features/composer/domain/state/get_autocomplete_state.dart';
|
||||||
|
import 'package:tmail_ui_user/features/composer/domain/state/get_device_contact_suggestions_state.dart';
|
||||||
|
|
||||||
|
mixin AutoCompleteResultMixin {
|
||||||
|
|
||||||
|
FutureOr<List<EmailAddress>> handleAutoCompleteResultState({
|
||||||
|
required Either<Failure, Success> resultState,
|
||||||
|
required String queryString,
|
||||||
|
}) {
|
||||||
|
return resultState.fold(
|
||||||
|
(failure) => <EmailAddress>[],
|
||||||
|
(success) => handleAutoCompleteSuccess(success, queryString)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EmailAddress> handleAutoCompleteSuccess(Success success, String queryString) {
|
||||||
|
List<EmailAddress> listEmailAddress = [];
|
||||||
|
|
||||||
|
if (success is GetAutoCompleteSuccess) {
|
||||||
|
listEmailAddress = success.listEmailAddress;
|
||||||
|
} else if (success is GetDeviceContactSuggestionsSuccess) {
|
||||||
|
listEmailAddress = success.listEmailAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listEmailAddress.isEmpty && GetUtils.isEmail(queryString)) {
|
||||||
|
return [EmailAddress(queryString, queryString)];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEmailExist = listEmailAddress
|
||||||
|
.any((email) => email.emailAddress == queryString);
|
||||||
|
if (GetUtils.isEmail(queryString) && !isEmailExist) {
|
||||||
|
listEmailAddress.insert(0, EmailAddress(queryString, queryString));
|
||||||
|
}
|
||||||
|
|
||||||
|
return listEmailAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,14 +34,13 @@ import 'package:super_tag_editor/tag_editor.dart';
|
|||||||
import 'package:tmail_ui_user/features/base/base_controller.dart';
|
import 'package:tmail_ui_user/features/base/base_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/base/before_reconnect_handler.dart';
|
import 'package:tmail_ui_user/features/base/before_reconnect_handler.dart';
|
||||||
import 'package:tmail_ui_user/features/base/before_reconnect_manager.dart';
|
import 'package:tmail_ui_user/features/base/before_reconnect_manager.dart';
|
||||||
|
import 'package:tmail_ui_user/features/base/mixin/auto_complete_result_mixin.dart';
|
||||||
import 'package:tmail_ui_user/features/base/state/base_ui_state.dart';
|
import 'package:tmail_ui_user/features/base/state/base_ui_state.dart';
|
||||||
import 'package:tmail_ui_user/features/base/state/button_state.dart';
|
import 'package:tmail_ui_user/features/base/state/button_state.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/domain/exceptions/compose_email_exception.dart';
|
import 'package:tmail_ui_user/features/composer/domain/exceptions/compose_email_exception.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart';
|
import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/domain/state/download_image_as_base64_state.dart';
|
import 'package:tmail_ui_user/features/composer/domain/state/download_image_as_base64_state.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/domain/state/generate_email_state.dart';
|
import 'package:tmail_ui_user/features/composer/domain/state/generate_email_state.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/domain/state/get_autocomplete_state.dart';
|
|
||||||
import 'package:tmail_ui_user/features/composer/domain/state/get_device_contact_suggestions_state.dart';
|
|
||||||
import 'package:tmail_ui_user/features/composer/domain/state/restore_email_inline_images_state.dart';
|
import 'package:tmail_ui_user/features/composer/domain/state/restore_email_inline_images_state.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_drafts_state.dart';
|
import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_drafts_state.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart';
|
import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart';
|
||||||
@@ -106,7 +105,9 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
|||||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||||
import 'package:tmail_ui_user/main/universal_import/html_stub.dart' as html;
|
import 'package:tmail_ui_user/main/universal_import/html_stub.dart' as html;
|
||||||
|
|
||||||
class ComposerController extends BaseController with DragDropFileMixin implements BeforeReconnectHandler {
|
class ComposerController extends BaseController
|
||||||
|
with DragDropFileMixin, AutoCompleteResultMixin
|
||||||
|
implements BeforeReconnectHandler {
|
||||||
|
|
||||||
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
||||||
final networkConnectionController = Get.find<NetworkConnectionController>();
|
final networkConnectionController = Get.find<NetworkConnectionController>();
|
||||||
@@ -1105,75 +1106,44 @@ class ComposerController extends BaseController with DragDropFileMixin implement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<EmailAddress>> getAutoCompleteSuggestion(String word) async {
|
Future<List<EmailAddress>> getAutoCompleteSuggestion(String queryString) async {
|
||||||
log('ComposerController::getAutoCompleteSuggestion(): $word | $_contactSuggestionSource');
|
log('ComposerController::getAutoCompleteSuggestion(): $queryString | $_contactSuggestionSource');
|
||||||
_getAllAutoCompleteInteractor = getBinding<GetAllAutoCompleteInteractor>();
|
_getAllAutoCompleteInteractor = getBinding<GetAllAutoCompleteInteractor>();
|
||||||
_getAutoCompleteInteractor = getBinding<GetAutoCompleteInteractor>();
|
_getAutoCompleteInteractor = getBinding<GetAutoCompleteInteractor>();
|
||||||
_getDeviceContactSuggestionsInteractor = getBinding<GetDeviceContactSuggestionsInteractor>();
|
_getDeviceContactSuggestionsInteractor = getBinding<GetDeviceContactSuggestionsInteractor>();
|
||||||
|
|
||||||
|
final autoCompletePattern = AutoCompletePattern(
|
||||||
|
word: queryString,
|
||||||
|
accountId: mailboxDashBoardController.accountId.value);
|
||||||
|
|
||||||
if (_contactSuggestionSource == ContactSuggestionSource.all) {
|
if (_contactSuggestionSource == ContactSuggestionSource.all) {
|
||||||
if (_getAllAutoCompleteInteractor != null) {
|
if (_getAllAutoCompleteInteractor != null) {
|
||||||
return await _getAllAutoCompleteInteractor!
|
return await _getAllAutoCompleteInteractor!
|
||||||
.execute(AutoCompletePattern(word: word, accountId: mailboxDashBoardController.accountId.value))
|
.execute(autoCompletePattern)
|
||||||
.then((value) => value.fold(
|
.then((value) => handleAutoCompleteResultState(
|
||||||
(failure) => <EmailAddress>[],
|
resultState: value,
|
||||||
(success) => _getAutoCompleteSuccess(success, word)
|
queryString: queryString,
|
||||||
));
|
)
|
||||||
|
);
|
||||||
} else if (_getDeviceContactSuggestionsInteractor != null) {
|
} else if (_getDeviceContactSuggestionsInteractor != null) {
|
||||||
return await _getDeviceContactSuggestionsInteractor!
|
return await _getDeviceContactSuggestionsInteractor!
|
||||||
.execute(AutoCompletePattern(word: word, accountId: mailboxDashBoardController.accountId.value))
|
.execute(autoCompletePattern)
|
||||||
.then((value) => value.fold(
|
.then((value) => handleAutoCompleteResultState(
|
||||||
(failure) => <EmailAddress>[],
|
resultState: value,
|
||||||
(success) => _getAutoCompleteSuccess(success, word)
|
queryString: queryString,
|
||||||
));
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return <EmailAddress>[];
|
return <EmailAddress>[];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_getAutoCompleteInteractor == null) {
|
return await _getAutoCompleteInteractor
|
||||||
return <EmailAddress>[];
|
?.execute(autoCompletePattern)
|
||||||
} else {
|
.then((value) => handleAutoCompleteResultState(
|
||||||
return await _getAutoCompleteInteractor!
|
resultState: value,
|
||||||
.execute(AutoCompletePattern(word: word, accountId: mailboxDashBoardController.accountId.value))
|
queryString: queryString,
|
||||||
.then((value) => value.fold(
|
)
|
||||||
(failure) => <EmailAddress>[],
|
) ?? <EmailAddress>[];
|
||||||
(success) => _getAutoCompleteSuccess(success, word)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<EmailAddress> _getAutoCompleteSuccess(Success success, String word) {
|
|
||||||
if (success is GetAutoCompleteSuccess) {
|
|
||||||
if (success.listEmailAddress.isEmpty == true && GetUtils.isEmail(word)) {
|
|
||||||
final unknownEmailAddress = EmailAddress(word, word);
|
|
||||||
return <EmailAddress>[unknownEmailAddress];
|
|
||||||
}
|
|
||||||
if (GetUtils.isEmail(word)) {
|
|
||||||
bool isContainsTypedEmail = success.listEmailAddress.any((emailAddress) => emailAddress.email == word);
|
|
||||||
if (!isContainsTypedEmail) {
|
|
||||||
final unknownEmailAddress = EmailAddress(word, word);
|
|
||||||
success.listEmailAddress.insert(0, unknownEmailAddress);
|
|
||||||
return success.listEmailAddress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return success.listEmailAddress;
|
|
||||||
} else if (success is GetDeviceContactSuggestionsSuccess) {
|
|
||||||
if (success.listEmailAddress.isEmpty == true && GetUtils.isEmail(word)) {
|
|
||||||
final unknownEmailAddress = EmailAddress(word, word);
|
|
||||||
return <EmailAddress>[unknownEmailAddress];
|
|
||||||
}
|
|
||||||
if (GetUtils.isEmail(word)) {
|
|
||||||
bool isContainsTypedEmail = success.listEmailAddress.any((emailAddress) => emailAddress.email == word);
|
|
||||||
if (!isContainsTypedEmail) {
|
|
||||||
final unknownEmailAddress = EmailAddress(word, word);
|
|
||||||
success.listEmailAddress.insert(0, unknownEmailAddress);
|
|
||||||
return success.listEmailAddress;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return success.listEmailAddress;
|
|
||||||
} else {
|
|
||||||
return <EmailAddress>[];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ import 'package:model/autocomplete/auto_complete_pattern.dart';
|
|||||||
import 'package:model/extensions/email_address_extension.dart';
|
import 'package:model/extensions/email_address_extension.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:tmail_ui_user/features/base/base_controller.dart';
|
import 'package:tmail_ui_user/features/base/base_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/base/mixin/auto_complete_result_mixin.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart';
|
import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/domain/state/get_autocomplete_state.dart';
|
|
||||||
import 'package:tmail_ui_user/features/composer/domain/state/get_device_contact_suggestions_state.dart';
|
|
||||||
import 'package:tmail_ui_user/features/composer/domain/usecases/get_all_autocomplete_interactor.dart';
|
import 'package:tmail_ui_user/features/composer/domain/usecases/get_all_autocomplete_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_interactor.dart';
|
import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/domain/usecases/get_device_contact_suggestions_interactor.dart';
|
import 'package:tmail_ui_user/features/composer/domain/usecases/get_device_contact_suggestions_interactor.dart';
|
||||||
@@ -28,8 +27,9 @@ import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
|
|||||||
import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart';
|
import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart';
|
||||||
import 'package:tmail_ui_user/features/thread/presentation/model/search_status.dart';
|
import 'package:tmail_ui_user/features/thread/presentation/model/search_status.dart';
|
||||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||||
|
import 'package:tmail_ui_user/main/utils/app_config.dart';
|
||||||
|
|
||||||
class ContactController extends BaseController {
|
class ContactController extends BaseController with AutoCompleteResultMixin {
|
||||||
|
|
||||||
final TextEditingController textInputSearchController = TextEditingController();
|
final TextEditingController textInputSearchController = TextEditingController();
|
||||||
final FocusNode textInputSearchFocus = FocusNode();
|
final FocusNode textInputSearchFocus = FocusNode();
|
||||||
@@ -113,14 +113,16 @@ class ContactController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleDeBounceTimeSearchContact(String value) async {
|
Future<void> _handleDeBounceTimeSearchContact(String value) async {
|
||||||
if (value.trim().isEmpty) {
|
final queryStringTrimmed = value.trim();
|
||||||
|
|
||||||
|
if (queryStringTrimmed.length < AppConfig.limitCharToStartSearch) {
|
||||||
searchStatus.value = SearchStatus.INACTIVE;
|
searchStatus.value = SearchStatus.INACTIVE;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchStatus.value = SearchStatus.ACTIVE;
|
searchStatus.value = SearchStatus.ACTIVE;
|
||||||
searchViewState.value = Right(SearchingState());
|
searchViewState.value = Right(SearchingState());
|
||||||
searchQuery.value = SearchQuery(value);
|
searchQuery.value = SearchQuery(queryStringTrimmed);
|
||||||
await _searchContactByNameOrEmail(searchQuery.value.value);
|
await _searchContactByNameOrEmail(searchQuery.value.value);
|
||||||
searchViewState.value = Right(UIState.idle);
|
searchViewState.value = Right(UIState.idle);
|
||||||
}
|
}
|
||||||
@@ -150,46 +152,44 @@ class ContactController extends BaseController {
|
|||||||
searchedContactList.value = listContact;
|
searchedContactList.value = listContact;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<EmailAddress>> _getAutoCompleteSuggestion(String query) async {
|
Future<List<EmailAddress>> _getAutoCompleteSuggestion(String queryString) async {
|
||||||
_getAllAutoCompleteInteractor = getBinding<GetAllAutoCompleteInteractor>();
|
_getAllAutoCompleteInteractor = getBinding<GetAllAutoCompleteInteractor>();
|
||||||
_getAutoCompleteInteractor = getBinding<GetAutoCompleteInteractor>();
|
_getAutoCompleteInteractor = getBinding<GetAutoCompleteInteractor>();
|
||||||
_getDeviceContactSuggestionsInteractor = getBinding<GetDeviceContactSuggestionsInteractor>();
|
_getDeviceContactSuggestionsInteractor = getBinding<GetDeviceContactSuggestionsInteractor>();
|
||||||
|
|
||||||
|
final autoCompletePattern = AutoCompletePattern(
|
||||||
|
word: queryString,
|
||||||
|
limit: 30,
|
||||||
|
accountId: _accountId);
|
||||||
|
|
||||||
if (_contactSuggestionSource == ContactSuggestionSource.all) {
|
if (_contactSuggestionSource == ContactSuggestionSource.all) {
|
||||||
if (_getAllAutoCompleteInteractor != null) {
|
if (_getAllAutoCompleteInteractor != null) {
|
||||||
return await _getAllAutoCompleteInteractor!
|
return await _getAllAutoCompleteInteractor!
|
||||||
.execute(AutoCompletePattern(word: query, limit: 30, accountId: _accountId))
|
.execute(autoCompletePattern)
|
||||||
.then((value) => value.fold(
|
.then((value) => handleAutoCompleteResultState(
|
||||||
(failure) => <EmailAddress>[],
|
resultState: value,
|
||||||
(success) => success is GetAutoCompleteSuccess
|
queryString: queryString,
|
||||||
? success.listEmailAddress
|
)
|
||||||
: <EmailAddress>[]
|
);
|
||||||
));
|
|
||||||
} else if (_getDeviceContactSuggestionsInteractor != null) {
|
} else if (_getDeviceContactSuggestionsInteractor != null) {
|
||||||
return await _getDeviceContactSuggestionsInteractor!
|
return await _getDeviceContactSuggestionsInteractor!
|
||||||
.execute(AutoCompletePattern(word: query, limit: 30, accountId: _accountId))
|
.execute(autoCompletePattern)
|
||||||
.then((value) => value.fold(
|
.then((value) => handleAutoCompleteResultState(
|
||||||
(failure) => <EmailAddress>[],
|
resultState: value,
|
||||||
(success) => success is GetDeviceContactSuggestionsSuccess
|
queryString: queryString,
|
||||||
? success.listEmailAddress
|
)
|
||||||
: <EmailAddress>[]
|
);
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
return <EmailAddress>[];
|
return <EmailAddress>[];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_getAutoCompleteInteractor == null) {
|
return await _getAutoCompleteInteractor
|
||||||
return <EmailAddress>[];
|
?.execute(autoCompletePattern)
|
||||||
} else {
|
.then((value) => handleAutoCompleteResultState(
|
||||||
return await _getAutoCompleteInteractor!
|
resultState: value,
|
||||||
.execute(AutoCompletePattern(word: query, limit: 30, accountId: _accountId))
|
queryString: queryString,
|
||||||
.then((value) => value.fold(
|
)
|
||||||
(failure) => <EmailAddress>[],
|
) ?? <EmailAddress>[];
|
||||||
(success) => success is GetAutoCompleteSuccess
|
|
||||||
? success.listEmailAddress
|
|
||||||
: <EmailAddress>[]
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||||
|
import 'package:tmail_ui_user/features/base/mixin/auto_complete_result_mixin.dart';
|
||||||
|
import 'package:tmail_ui_user/features/composer/domain/state/get_autocomplete_state.dart';
|
||||||
|
import 'package:tmail_ui_user/features/composer/domain/state/get_device_contact_suggestions_state.dart';
|
||||||
|
|
||||||
|
class AutoCompleteResult with AutoCompleteResultMixin {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late AutoCompleteResult autoCompleteResult;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
autoCompleteResult = AutoCompleteResult();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('handleAutoCompleteSuccess::test', () {
|
||||||
|
test('SHOULD return list of EmailAddress from GetAutoCompleteSuccess', () {
|
||||||
|
final mockEmailAddress = EmailAddress('Test User', 'test@example.com');
|
||||||
|
final success = GetAutoCompleteSuccess([mockEmailAddress]);
|
||||||
|
final result = autoCompleteResult.handleAutoCompleteSuccess(success, 'query');
|
||||||
|
|
||||||
|
expect(result, [mockEmailAddress]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SHOULD return list of EmailAddress from GetDeviceContactSuggestionsSuccess', () {
|
||||||
|
final mockEmailAddress = EmailAddress('Contact User', 'contact@example.com');
|
||||||
|
final success = GetDeviceContactSuggestionsSuccess([mockEmailAddress]);
|
||||||
|
final result = autoCompleteResult.handleAutoCompleteSuccess(success, 'query');
|
||||||
|
|
||||||
|
expect(result, [mockEmailAddress]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SHOULD return query email address if list is empty and query is a valid email', () {
|
||||||
|
final success = GetAutoCompleteSuccess([]);
|
||||||
|
final result = autoCompleteResult.handleAutoCompleteSuccess(success, 'newemail@example.com');
|
||||||
|
|
||||||
|
expect(result, [EmailAddress('newemail@example.com', 'newemail@example.com')]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SHOULD add query email address to the start if not present and is a valid email', () {
|
||||||
|
final mockEmailAddress = EmailAddress('Existing User', 'existing@example.com');
|
||||||
|
final success = GetAutoCompleteSuccess([mockEmailAddress]);
|
||||||
|
final result = autoCompleteResult.handleAutoCompleteSuccess(success, 'newemail@example.com');
|
||||||
|
|
||||||
|
expect(result, [
|
||||||
|
EmailAddress('newemail@example.com', 'newemail@example.com'),
|
||||||
|
mockEmailAddress
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SHOULD not add query email address if already present', () {
|
||||||
|
final mockEmailAddress = EmailAddress('Existing User', 'existing@example.com');
|
||||||
|
final success = GetAutoCompleteSuccess([mockEmailAddress]);
|
||||||
|
final result = autoCompleteResult.handleAutoCompleteSuccess(success, 'existing@example.com');
|
||||||
|
|
||||||
|
expect(result, [mockEmailAddress]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user