Files
workavia-mail-front/lib/features/contact/presentation/contact_controller.dart
T

255 lines
9.8 KiB
Dart

import 'dart:async';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:dartz/dartz.dart';
import 'package:debounce_throttle/debounce_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:model/autocomplete/auto_complete_pattern.dart';
import 'package:model/extensions/email_address_extension.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/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/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_device_contact_suggestions_interactor.dart';
import 'package:tmail_ui_user/features/contact/presentation/model/contact_arguments.dart';
import 'package:tmail_ui_user/features/contact/presentation/widgets/contact_suggestion_box_item.dart';
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/presentation/model/search_status.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 with AutoCompleteResultMixin {
final TextEditingController textInputSearchController = TextEditingController();
final FocusNode textInputSearchFocus = FocusNode();
ContactSuggestionSource _contactSuggestionSource = ContactSuggestionSource.tMailContact;
final searchQuery = SearchQuery.initial().obs;
final searchedContactList = RxList<EmailAddress>();
final selectedContactList = RxList<EmailAddress>();
final contactArguments = Rxn<ContactArguments>();
final searchStatus = SearchStatus.INACTIVE.obs;
final searchViewState = Rx<Either<Failure, Success>>(Right(UIState.idle));
GetAllAutoCompleteInteractor? _getAllAutoCompleteInteractor;
GetAutoCompleteInteractor? _getAutoCompleteInteractor;
GetDeviceContactSuggestionsInteractor? _getDeviceContactSuggestionsInteractor;
final Debouncer<String> _deBouncerTime = Debouncer<String>(
const Duration(milliseconds: 300),
initialValue: ''
);
AccountId? _accountId;
SelectedContactCallbackAction? onSelectedContactCallback;
VoidCallback? onDismissContactView;
StreamSubscription<String>? _deBouncerTimeStreamSubscription;
late int _minInputLengthAutocomplete;
@override
void onInit() {
super.onInit();
ThemeUtils.setStatusBarTransparentColor();
log('ContactController::onInit():arguments: ${Get.arguments}');
contactArguments.value = Get.arguments;
_deBouncerTimeStreamSubscription = _deBouncerTime.values.listen(_handleDeBounceTimeSearchContact);
textInputSearchFocus.addListener(_onSearchInputTextFocusListener);
}
@override
void onReady() {
super.onReady();
if (contactArguments.value != null) {
_accountId = contactArguments.value!.accountId;
selectedContactList.value = contactArguments.value!.selectedContactList
.map((mailAddress) => EmailAddress(null, mailAddress))
.toList();
injectAutoCompleteBindings(contactArguments.value!.session, _accountId);
if (selectedContactList.isEmpty) {
textInputSearchFocus.requestFocus();
}
}
_minInputLengthAutocomplete = _fetchMinInputLengthAutocomplete();
if (PlatformInfo.isMobile) {
Future.delayed(
const Duration(milliseconds: 500),
() => _checkContactPermission());
}
}
@override
void onClose() {
log('ContactController::onClose():');
searchViewState.value = Right(UIState.idle);
textInputSearchFocus.removeListener(_onSearchInputTextFocusListener);
textInputSearchFocus.dispose();
textInputSearchController.dispose();
_deBouncerTimeStreamSubscription?.cancel();
_deBouncerTime.cancel();
_accountId = null;
super.onClose();
}
void _onSearchInputTextFocusListener() {
if (textInputSearchFocus.hasFocus && textInputSearchController.text.trim().isEmpty) {
searchStatus.value = SearchStatus.INACTIVE;
}
}
void onTextSearchChange(String text) {
_deBouncerTime.value = text;
}
void onSearchTextAction(String text) {
_deBouncerTime.value = text;
}
Future<void> _handleDeBounceTimeSearchContact(String value) async {
final queryStringTrimmed = value.trim();
if (queryStringTrimmed.length < _minInputLengthAutocomplete) {
searchStatus.value = SearchStatus.INACTIVE;
return;
}
searchStatus.value = SearchStatus.ACTIVE;
searchViewState.value = Right(SearchingState());
searchQuery.value = SearchQuery(queryStringTrimmed);
await _searchContactByNameOrEmail(searchQuery.value.value);
searchViewState.value = Right(UIState.idle);
}
void clearAllTextInputSearchForm() {
textInputSearchController.clear();
searchQuery.value = SearchQuery.initial();
searchedContactList.clear();
textInputSearchFocus.requestFocus();
}
void _checkContactPermission() async {
final permissionStatus = await Permission.contacts.status;
if (permissionStatus.isGranted) {
_contactSuggestionSource = ContactSuggestionSource.all;
} else if (!permissionStatus.isPermanentlyDenied) {
final requestedPermission = await Permission.contacts.request();
_contactSuggestionSource = requestedPermission == PermissionStatus.granted
? ContactSuggestionSource.all
: _contactSuggestionSource;
}
}
Future<void> _searchContactByNameOrEmail(String query) async {
log('ContactController::_searchContactByNameOrEmail(): query: $query');
final listContact = await _getAutoCompleteSuggestion(query);
if (query.isEmail && !listContact.any((emailAddress) => emailAddress.email == query)) {
listContact.add(EmailAddress(null, query));
}
searchedContactList.value = listContact;
}
Future<List<EmailAddress>> _getAutoCompleteSuggestion(String queryString) async {
_getAllAutoCompleteInteractor = getBinding<GetAllAutoCompleteInteractor>();
_getAutoCompleteInteractor = getBinding<GetAutoCompleteInteractor>();
_getDeviceContactSuggestionsInteractor = getBinding<GetDeviceContactSuggestionsInteractor>();
final autoCompletePattern = AutoCompletePattern(
word: queryString,
accountId: _accountId);
if (_contactSuggestionSource == ContactSuggestionSource.all) {
if (_getAllAutoCompleteInteractor != null) {
return await _getAllAutoCompleteInteractor!
.execute(autoCompletePattern)
.then((value) => handleAutoCompleteResultState(
resultState: value,
queryString: queryString,
onFailureCallback: (failure) => consumeState(Stream.value(Left(failure))),
)
);
} else if (_getDeviceContactSuggestionsInteractor != null) {
return await _getDeviceContactSuggestionsInteractor!
.execute(autoCompletePattern)
.then((value) => handleAutoCompleteResultState(
resultState: value,
queryString: queryString,
)
);
} else {
return <EmailAddress>[];
}
} else {
return await _getAutoCompleteInteractor
?.execute(autoCompletePattern)
.then((value) => handleAutoCompleteResultState(
resultState: value,
queryString: queryString,
onFailureCallback: (failure) => consumeState(Stream.value(Left(failure))),
)
) ?? <EmailAddress>[];
}
}
void handleOnSelectContactAction(EmailAddress emailAddress) {
log('ContactController::selectContact:emailAddress = $emailAddress');
final isEmailAddressExist = selectedContactList
.any((contact) => contact.emailAddress == emailAddress.emailAddress);
log('ContactController::selectContact:isEmailAddressExist = $isEmailAddressExist');
if (!isEmailAddressExist) {
selectedContactList.add(emailAddress);
}
}
void handleOnDeleteContactAction(EmailAddress emailAddress) {
log('ContactController::handleOnDeleteContactAction:emailAddress = $emailAddress');
selectedContactList.removeWhere((contact) => contact.emailAddress == emailAddress.emailAddress);
}
void closeContactView() {
textInputSearchFocus.unfocus();
FocusManager.instance.primaryFocus?.unfocus();
popBack();
}
void handleOnClearFilterAction() {
selectedContactList.clear();
textInputSearchFocus.unfocus();
FocusManager.instance.primaryFocus?.unfocus();
popBack(result: selectedContactList);
}
void handleOnDoneAction() {
textInputSearchFocus.unfocus();
FocusManager.instance.primaryFocus?.unfocus();
popBack(result: selectedContactList);
}
void handleOnSearchBackAction() {
textInputSearchController.clear();
searchQuery.value = SearchQuery.initial();
searchedContactList.clear();
textInputSearchFocus.unfocus();
FocusManager.instance.primaryFocus?.unfocus();
searchStatus.value = SearchStatus.INACTIVE;
}
int _fetchMinInputLengthAutocomplete() {
if (contactArguments.value?.session == null || _accountId == null) {
return AppConfig.defaultMinInputLengthAutocomplete;
}
return getMinInputLengthAutocomplete(
session: contactArguments.value!.session,
accountId: _accountId!);
}
}