Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TF-3005 [SEARCH] Easily clear "from" and "to" search result header #3158

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/lib/presentation/extensions/color_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ extension AppColor on Color {
static const colorFilterMessageTitle = Color(0xFF686E76);
static const colorStarredSearchFilterIcon = Color(0xFFFFCC00);
static const colorMobileSearchFilterButton = Color(0xFFEBEDF0);
static const colorContactViewClearFilterButton = Color(0x001C3D0D);

static const mapGradientColor = [
[Color(0xFF21D4FD), Color(0xFFB721FF)],
Expand Down
129 changes: 96 additions & 33 deletions lib/features/contact/presentation/contact_controller.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@

import 'package:core/presentation/utils/keyboard_utils.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/core/session/session.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/composer/domain/model/contact_suggestion_source.dart';
Expand All @@ -20,6 +25,8 @@ import 'package:tmail_ui_user/features/composer/domain/usecases/get_device_conta
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';

class ContactController extends BaseController {
Expand All @@ -29,47 +36,48 @@ class ContactController extends BaseController {
ContactSuggestionSource _contactSuggestionSource = ContactSuggestionSource.tMailContact;

final searchQuery = SearchQuery.initial().obs;
final session = Rxn<Session>();
final listContactSearched = RxList<EmailAddress>();
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: '');
final Debouncer<String> _deBouncerTime = Debouncer<String>(
const Duration(milliseconds: 300),
initialValue: ''
);
AccountId? _accountId;

ContactArguments? arguments;
EmailAddress? contactSelected;
SelectedContactCallbackAction? onSelectedContactCallback;
VoidCallback? onDismissContactView;
StreamSubscription<String>? _deBouncerTimeStreamSubscription;

@override
void onInit() {
super.onInit();
ThemeUtils.setStatusBarTransparentColor();
log('ContactController::onInit():arguments: ${Get.arguments}');
arguments = Get.arguments;
_deBouncerTime.values.listen((value) {
searchQuery.value = SearchQuery(value);
_searchContactByNameOrEmail(searchQuery.value.value);
});
contactArguments.value = Get.arguments;
_deBouncerTimeStreamSubscription = _deBouncerTime.values.listen(_handleDeBounceTimeSearchContact);
textInputSearchFocus.addListener(_onSearchInputTextFocusListener);
}

@override
void onReady() async {
void onReady() {
super.onReady();
log('ContactController::onReady():');
textInputSearchFocus.requestFocus();
if (arguments != null) {
_accountId = arguments!.accountId;
session.value = arguments!.session;
final listContactSelected = arguments!.listContactSelected;
log('ContactController::onReady(): arguments: $arguments');
log('ContactController::onReady(): listContactSelected: $listContactSelected');
if (listContactSelected.isNotEmpty) {
contactSelected = EmailAddress(listContactSelected.first, listContactSelected.first);
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();
}
injectAutoCompleteBindings(session.value, _accountId);
}
if (PlatformInfo.isMobile) {
Future.delayed(
Expand All @@ -81,12 +89,21 @@ class ContactController extends BaseController {
@override
void onClose() {
log('ContactController::onClose():');
searchViewState.value = Right(UIState.idle);
textInputSearchFocus.removeListener(_onSearchInputTextFocusListener);
textInputSearchFocus.dispose();
textInputSearchController.dispose();
_deBouncerTimeStreamSubscription?.cancel();
_deBouncerTime.cancel();
super.onClose();
}

void _onSearchInputTextFocusListener() {
if (textInputSearchFocus.hasFocus && textInputSearchController.text.trim().isEmpty) {
searchStatus.value = SearchStatus.INACTIVE;
}
}

void onTextSearchChange(String text) {
_deBouncerTime.value = text;
}
Expand All @@ -95,9 +112,23 @@ class ContactController extends BaseController {
_deBouncerTime.value = text;
}

Future<void> _handleDeBounceTimeSearchContact(String value) async {
if (value.trim().isEmpty) {
searchStatus.value = SearchStatus.INACTIVE;
return;
}

searchStatus.value = SearchStatus.ACTIVE;
searchViewState.value = Right(SearchingState());
searchQuery.value = SearchQuery(value);
await _searchContactByNameOrEmail(searchQuery.value.value);
searchViewState.value = Right(UIState.idle);
}

void clearAllTextInputSearchForm() {
textInputSearchController.clear();
searchQuery.value = SearchQuery.initial();
searchedContactList.clear();
textInputSearchFocus.requestFocus();
}

Expand All @@ -113,10 +144,10 @@ class ContactController extends BaseController {
}
}

void _searchContactByNameOrEmail(String query) async {
Future<void> _searchContactByNameOrEmail(String query) async {
log('ContactController::_searchContactByNameOrEmail(): query: $query');
final listContact = await _getAutoCompleteSuggestion(query);
listContactSearched.value = listContact;
searchedContactList.value = listContact;
}

Future<List<EmailAddress>> _getAutoCompleteSuggestion(String query) async {
Expand Down Expand Up @@ -162,14 +193,46 @@ class ContactController extends BaseController {
}
}

void selectContact(BuildContext context, EmailAddress emailAddress) {
KeyboardUtils.hideKeyboard(context);
popBack(result: 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) {
tddang-linagora marked this conversation as resolved.
Show resolved Hide resolved
selectedContactList.add(emailAddress);
}
}

void closeContactView(BuildContext context) {
clearAllTextInputSearchForm();
KeyboardUtils.hideKeyboard(context);
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);
}
hoangdat marked this conversation as resolved.
Show resolved Hide resolved

void handleOnSearchBackAction() {
textInputSearchController.clear();
searchQuery.value = SearchQuery.initial();
searchedContactList.clear();
textInputSearchFocus.unfocus();
FocusManager.instance.primaryFocus?.unfocus();
searchStatus.value = SearchStatus.INACTIVE;
}
}
Loading
Loading