TF-2362 Add Load more button at bottom emails list

Signed-off-by: dab246 <tdvu@linagora.com>

(cherry picked from commit 9563f13e47f33e4ee5e351bdc108b82c160f1010)
This commit is contained in:
dab246
2024-01-18 20:12:30 +07:00
committed by Dat H. Pham
parent 6209a1eccd
commit af755ae6c6
3 changed files with 114 additions and 47 deletions
@@ -4,7 +4,8 @@ import 'package:jmap_dart_client/jmap/mail/email/individual_header_identifier.da
import 'package:model/email/email_property.dart'; import 'package:model/email/email_property.dart';
class ThreadConstants { class ThreadConstants {
static final defaultLimit = UnsignedInt(20); static const maxCountEmails = 20;
static final defaultLimit = UnsignedInt(maxCountEmails);
static final propertiesDefault = Properties({ static final propertiesDefault = Properties({
EmailProperty.id, EmailProperty.id,
EmailProperty.subject, EmailProperty.subject,
@@ -4,6 +4,7 @@ import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart'; import 'package:core/utils/platform_info.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
@@ -97,10 +98,10 @@ class ThreadController extends BaseController with EmailActionController {
final listEmailDrag = <PresentationEmail>[].obs; final listEmailDrag = <PresentationEmail>[].obs;
bool _rangeSelectionMode = false; bool _rangeSelectionMode = false;
final openingEmail = RxBool(false); final openingEmail = RxBool(false);
final loadingMoreStatus = Rx(LoadingMoreStatus.idle);
bool canLoadMore = true; bool canLoadMore = false;
bool canSearchMore = true; bool canSearchMore = false;
LoadingMoreStatus loadingMoreStatus = LoadingMoreStatus.idle;
MailboxId? _currentMailboxId; MailboxId? _currentMailboxId;
jmap.State? _currentEmailState; jmap.State? _currentEmailState;
final ScrollController listEmailController = ScrollController(); final ScrollController listEmailController = ScrollController();
@@ -161,7 +162,7 @@ class ThreadController extends BaseController with EmailActionController {
} else if (success is SearchMoreEmailSuccess) { } else if (success is SearchMoreEmailSuccess) {
_searchMoreEmailsSuccess(success); _searchMoreEmailsSuccess(success);
} else if (success is SearchingMoreState || success is LoadingMoreEmails) { } else if (success is SearchingMoreState || success is LoadingMoreEmails) {
loadingMoreStatus = LoadingMoreStatus.running; loadingMoreStatus.value = LoadingMoreStatus.running;
} else if (success is GetEmailByIdLoading) { } else if (success is GetEmailByIdLoading) {
openingEmail.value = true; openingEmail.value = true;
} else if (success is GetEmailByIdSuccess) { } else if (success is GetEmailByIdSuccess) {
@@ -190,13 +191,18 @@ class ThreadController extends BaseController with EmailActionController {
mailboxDashBoardController.refreshingMailboxState.value = Left(failure); mailboxDashBoardController.refreshingMailboxState.value = Left(failure);
canSearchMore = false; canSearchMore = false;
mailboxDashBoardController.emailsInCurrentMailbox.clear(); mailboxDashBoardController.emailsInCurrentMailbox.clear();
} else if (failure is SearchMoreEmailFailure || failure is LoadMoreEmailsFailure) { } else if (failure is SearchMoreEmailFailure) {
loadingMoreStatus = LoadingMoreStatus.completed; loadingMoreStatus.value = LoadingMoreStatus.completed;
canSearchMore = true;
} else if (failure is LoadMoreEmailsFailure) {
loadingMoreStatus.value = LoadingMoreStatus.completed;
canLoadMore = true;
} else if (failure is GetEmailByIdFailure) { } else if (failure is GetEmailByIdFailure) {
openingEmail.value = false; openingEmail.value = false;
popAndPush(AppRoutes.unknownRoutePage); popAndPush(AppRoutes.unknownRoutePage);
} else if (failure is GetAllEmailFailure) { } else if (failure is GetAllEmailFailure) {
mailboxDashBoardController.refreshingMailboxState.value = Left(failure); mailboxDashBoardController.refreshingMailboxState.value = Left(failure);
canLoadMore = true;
} }
} }
@@ -217,8 +223,8 @@ class ThreadController extends BaseController with EmailActionController {
} }
void _resetLoadingMore() { void _resetLoadingMore() {
if (loadingMoreStatus == LoadingMoreStatus.running) { if (loadingMoreStatus.value == LoadingMoreStatus.running) {
loadingMoreStatus = LoadingMoreStatus.idle; loadingMoreStatus.value = LoadingMoreStatus.idle;
} }
} }
@@ -386,15 +392,15 @@ class ThreadController extends BaseController with EmailActionController {
void _resetToOriginalValue() { void _resetToOriginalValue() {
dispatchState(Right(LoadingState())); dispatchState(Right(LoadingState()));
mailboxDashBoardController.emailsInCurrentMailbox.clear(); mailboxDashBoardController.emailsInCurrentMailbox.clear();
canLoadMore = true; canLoadMore = false;
loadingMoreStatus = LoadingMoreStatus.idle; loadingMoreStatus.value = LoadingMoreStatus.idle;
cancelSelectEmail(); cancelSelectEmail();
} }
void _getAllEmailSuccess(GetAllEmailSuccess success) { void _getAllEmailSuccess(GetAllEmailSuccess success) {
mailboxDashBoardController.refreshingMailboxState.value = Right(success); mailboxDashBoardController.refreshingMailboxState.value = Right(success);
_currentEmailState = success.currentEmailState; _currentEmailState = success.currentEmailState;
log('ThreadController::_getAllEmailSuccess():_currentEmailState: $_currentEmailState'); log('ThreadController::_getAllEmailSuccess():COUNT = ${success.emailList.length} | EMAIL_STATE = $_currentEmailState');
final newListEmail = success.emailList.syncPresentationEmail( final newListEmail = success.emailList.syncPresentationEmail(
mapMailboxById: mailboxDashBoardController.mapMailboxById, mapMailboxById: mailboxDashBoardController.mapMailboxById,
selectedMailbox: currentMailbox, selectedMailbox: currentMailbox,
@@ -402,14 +408,22 @@ class ThreadController extends BaseController with EmailActionController {
isSearchEmailRunning: searchController.isSearchEmailRunning isSearchEmailRunning: searchController.isSearchEmailRunning
); );
mailboxDashBoardController.updateEmailList(newListEmail); mailboxDashBoardController.updateEmailList(newListEmail);
if (listEmailController.hasClients) {
listEmailController.animateTo(0, duration: const Duration(milliseconds: 500), curve: Curves.fastOutSlowIn); canLoadMore = newListEmail.length >= ThreadConstants.maxCountEmails;
}
SchedulerBinding.instance.addPostFrameCallback((_) {
if (listEmailController.hasClients) {
listEmailController.animateTo(
0,
duration: const Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn);
}
});
} }
void _refreshChangesAllEmailSuccess(RefreshChangesAllEmailSuccess success) { void _refreshChangesAllEmailSuccess(RefreshChangesAllEmailSuccess success) {
_currentEmailState = success.currentEmailState; _currentEmailState = success.currentEmailState;
log('ThreadController::_refreshChangesAllEmailSuccess: COUNT = ${success.emailList.length}');
final emailsBeforeChanges = mailboxDashBoardController.emailsInCurrentMailbox; final emailsBeforeChanges = mailboxDashBoardController.emailsInCurrentMailbox;
final emailsAfterChanges = success.emailList; final emailsAfterChanges = success.emailList;
final newListEmail = emailsAfterChanges.combine(emailsBeforeChanges); final newListEmail = emailsAfterChanges.combine(emailsBeforeChanges);
@@ -421,6 +435,8 @@ class ThreadController extends BaseController with EmailActionController {
); );
mailboxDashBoardController.updateEmailList(emailListSynced); mailboxDashBoardController.updateEmailList(emailListSynced);
canLoadMore = newListEmail.length >= ThreadConstants.maxCountEmails;
if (mailboxDashBoardController.emailsInCurrentMailbox.isEmpty) { if (mailboxDashBoardController.emailsInCurrentMailbox.isEmpty) {
refreshAllEmail(); refreshAllEmail();
} }
@@ -474,8 +490,8 @@ class ThreadController extends BaseController with EmailActionController {
void refreshAllEmail() { void refreshAllEmail() {
dispatchState(Right(LoadingState())); dispatchState(Right(LoadingState()));
canLoadMore = true; canLoadMore = false;
loadingMoreStatus == LoadingMoreStatus.idle; loadingMoreStatus.value == LoadingMoreStatus.idle;
cancelSelectEmail(); cancelSelectEmail();
if (searchController.isSearchEmailRunning) { if (searchController.isSearchEmailRunning) {
@@ -551,7 +567,7 @@ class ThreadController extends BaseController with EmailActionController {
} }
void _loadMoreEmailsSuccess(LoadMoreEmailsSuccess success) { void _loadMoreEmailsSuccess(LoadMoreEmailsSuccess success) {
loadingMoreStatus = LoadingMoreStatus.completed; log('ThreadController::_loadMoreEmailsSuccess: COUNT = ${success.emailList.length}');
if (success.emailList.isNotEmpty) { if (success.emailList.isNotEmpty) {
final appendableList = success.emailList final appendableList = success.emailList
.where(_belongToCurrentMailboxId) .where(_belongToCurrentMailboxId)
@@ -565,9 +581,11 @@ class ThreadController extends BaseController with EmailActionController {
); );
mailboxDashBoardController.emailsInCurrentMailbox.addAll(appendableList); mailboxDashBoardController.emailsInCurrentMailbox.addAll(appendableList);
canLoadMore = true;
} else { } else {
canLoadMore = false; canLoadMore = false;
} }
loadingMoreStatus.value = LoadingMoreStatus.completed;
} }
SelectMode getSelectMode(PresentationEmail presentationEmail, PresentationEmail? selectedEmail) { SelectMode getSelectMode(PresentationEmail presentationEmail, PresentationEmail? selectedEmail) {
@@ -704,7 +722,7 @@ class ThreadController extends BaseController with EmailActionController {
} }
} }
bool isSearchActive() => searchController.isSearchEmailRunning; bool get isSearchActive => searchController.isSearchEmailRunning;
bool get isAllSearchInActive => !searchController.isSearchActive() && bool get isAllSearchInActive => !searchController.isSearchActive() &&
searchController.isAdvancedSearchViewOpen.isFalse; searchController.isAdvancedSearchViewOpen.isFalse;
@@ -719,7 +737,7 @@ class ThreadController extends BaseController with EmailActionController {
listEmailController.jumpTo(0); listEmailController.jumpTo(0);
} }
mailboxDashBoardController.emailsInCurrentMailbox.clear(); mailboxDashBoardController.emailsInCurrentMailbox.clear();
canSearchMore = true; canSearchMore = false;
if (searchController.sortOrderFiltered.value.isScrollByPosition()) { if (searchController.sortOrderFiltered.value.isScrollByPosition()) {
searchController.updateFilterEmail( searchController.updateFilterEmail(
@@ -770,7 +788,7 @@ class ThreadController extends BaseController with EmailActionController {
void _searchEmailsSuccess(SearchEmailSuccess success) { void _searchEmailsSuccess(SearchEmailSuccess success) {
mailboxDashBoardController.refreshingMailboxState.value = Right(success); mailboxDashBoardController.refreshingMailboxState.value = Right(success);
canSearchMore = true; log('ThreadController::_searchEmailsSuccess: COUNT = ${success.emailList.length}');
final resultEmailSearchList = success.emailList final resultEmailSearchList = success.emailList
.map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailboxById)) .map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailboxById))
.toList(); .toList();
@@ -785,9 +803,12 @@ class ThreadController extends BaseController with EmailActionController {
isSearchEmailRunning: searchController.isSearchEmailRunning isSearchEmailRunning: searchController.isSearchEmailRunning
); );
mailboxDashBoardController.updateEmailList(newEmailListSynced); mailboxDashBoardController.updateEmailList(newEmailListSynced);
canSearchMore = newEmailListSynced.length >= ThreadConstants.maxCountEmails;
} }
void searchMoreEmails() { void searchMoreEmails() {
log('ThreadController::searchMoreEmails:');
if (canSearchMore && _session != null && _accountId != null) { if (canSearchMore && _session != null && _accountId != null) {
final lastEmail = mailboxDashBoardController.emailsInCurrentMailbox.isNotEmpty final lastEmail = mailboxDashBoardController.emailsInCurrentMailbox.isNotEmpty
? mailboxDashBoardController.emailsInCurrentMailbox.last ? mailboxDashBoardController.emailsInCurrentMailbox.last
@@ -823,10 +844,8 @@ class ThreadController extends BaseController with EmailActionController {
} }
void _searchMoreEmailsSuccess(SearchMoreEmailSuccess success) { void _searchMoreEmailsSuccess(SearchMoreEmailSuccess success) {
loadingMoreStatus = LoadingMoreStatus.completed; log('ThreadController::_searchMoreEmailsSuccess: COUNT = ${success.emailList.length}');
if (success.emailList.isNotEmpty) { if (success.emailList.isNotEmpty) {
canSearchMore = true;
final resultEmailSearchList = success.emailList final resultEmailSearchList = success.emailList
.map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailboxById)) .map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailboxById))
.where((email) => mailboxDashBoardController.emailsInCurrentMailbox.every((emailInCurrentMailbox) => emailInCurrentMailbox.id != email.id)) .where((email) => mailboxDashBoardController.emailsInCurrentMailbox.every((emailInCurrentMailbox) => emailInCurrentMailbox.id != email.id))
@@ -838,9 +857,9 @@ class ThreadController extends BaseController with EmailActionController {
isSearchEmailRunning: searchController.isSearchEmailRunning isSearchEmailRunning: searchController.isSearchEmailRunning
); );
mailboxDashBoardController.emailsInCurrentMailbox.addAll(resultEmailSearchList); mailboxDashBoardController.emailsInCurrentMailbox.addAll(resultEmailSearchList);
} else {
canSearchMore = false;
} }
canSearchMore = success.emailList.isNotEmpty;
loadingMoreStatus.value = LoadingMoreStatus.completed;
} }
bool isSelectionEnabled() => mailboxDashBoardController.isSelectionEnabled(); bool isSelectionEnabled() => mailboxDashBoardController.isSelectionEnabled();
@@ -21,6 +21,7 @@ import 'package:tmail_ui_user/features/quotas/presentation/widget/quotas_banner_
import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.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/delete_action_type.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/loading_more_status.dart';
import 'package:tmail_ui_user/features/thread/presentation/styles/banner_delete_all_spam_emails_styles.dart'; import 'package:tmail_ui_user/features/thread/presentation/styles/banner_delete_all_spam_emails_styles.dart';
import 'package:tmail_ui_user/features/thread/presentation/styles/banner_empty_trash_styles.dart'; import 'package:tmail_ui_user/features/thread/presentation/styles/banner_empty_trash_styles.dart';
import 'package:tmail_ui_user/features/thread/presentation/styles/item_email_tile_styles.dart'; import 'package:tmail_ui_user/features/thread/presentation/styles/item_email_tile_styles.dart';
@@ -251,7 +252,7 @@ class ThreadView extends GetWidget<ThreadController>
Widget _buildListButtonSelectionForMobile(BuildContext context) { Widget _buildListButtonSelectionForMobile(BuildContext context) {
return Obx(() { return Obx(() {
if ((PlatformInfo.isMobile || (PlatformInfo.isWeb && controller.isSelectionEnabled() if ((PlatformInfo.isMobile || (PlatformInfo.isWeb && controller.isSelectionEnabled()
&& controller.isSearchActive() && !controller.responsiveUtils.isDesktop(context))) && controller.isSearchActive && !controller.responsiveUtils.isDesktop(context)))
&& controller.mailboxDashBoardController.emailsInCurrentMailbox.listEmailSelected.isNotEmpty) { && controller.mailboxDashBoardController.emailsInCurrentMailbox.listEmailSelected.isNotEmpty) {
return BottomBarThreadSelectionWidget( return BottomBarThreadSelectionWidget(
controller.imagePaths, controller.imagePaths,
@@ -348,26 +349,23 @@ class ThreadView extends GetWidget<ThreadController>
Widget _buildListEmailBody(BuildContext context, List<PresentationEmail> listPresentationEmail) { Widget _buildListEmailBody(BuildContext context, List<PresentationEmail> listPresentationEmail) {
return NotificationListener<ScrollNotification>( return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) { onNotification: _handleScrollNotificationListener,
if (scrollInfo is ScrollEndNotification
&& !controller.loadingMoreStatus.isRunning
&& scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent
) {
if (controller.isSearchActive() || controller.searchController.advancedSearchIsActivated.isTrue) {
controller.searchMoreEmails();
} else {
controller.loadMoreEmails();
}
}
return false;
},
child: PlatformInfo.isMobile child: PlatformInfo.isMobile
? ListView.separated( ? ListView.separated(
key: const PageStorageKey('list_presentation_email_in_threads'), key: const PageStorageKey('list_presentation_email_in_threads'),
controller: controller.listEmailController, controller: controller.listEmailController,
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
itemCount: listPresentationEmail.length, itemCount: listPresentationEmail.length + 1,
itemBuilder: (context, index) => Obx(() => _buildEmailItemNotDraggable(context, listPresentationEmail[index])), itemBuilder: (context, index) => Obx(() {
if (index == listPresentationEmail.length) {
return _buildLoadMoreButton(
context,
controller.loadingMoreStatus.value);
}
return _buildEmailItemNotDraggable(
context,
listPresentationEmail[index]);
}),
separatorBuilder: (context, index) { separatorBuilder: (context, index) {
if (index < listPresentationEmail.length - 1) { if (index < listPresentationEmail.length - 1) {
return Padding( return Padding(
@@ -388,8 +386,17 @@ class ThreadView extends GetWidget<ThreadController>
key: const PageStorageKey('list_presentation_email_in_threads'), key: const PageStorageKey('list_presentation_email_in_threads'),
controller: controller.listEmailController, controller: controller.listEmailController,
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
itemCount: listPresentationEmail.length, itemCount: listPresentationEmail.length + 1,
itemBuilder: (context, index) => Obx(() => _buildEmailItem(context, listPresentationEmail[index])), itemBuilder: (context, index) => Obx(() {
if (index == listPresentationEmail.length) {
return _buildLoadMoreButton(
context,
controller.loadingMoreStatus.value);
}
return _buildEmailItem(
context,
listPresentationEmail[index]);
}),
separatorBuilder: (context, index) { separatorBuilder: (context, index) {
return Padding( return Padding(
padding: ItemEmailTileStyles.getPaddingDividerWeb(context, controller.responsiveUtils), padding: ItemEmailTileStyles.getPaddingDividerWeb(context, controller.responsiveUtils),
@@ -407,6 +414,46 @@ class ThreadView extends GetWidget<ThreadController>
); );
} }
bool _handleScrollNotificationListener(ScrollNotification scrollInfo) {
if (scrollInfo is ScrollEndNotification &&
scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent &&
!controller.loadingMoreStatus.value.isRunning
) {
_handleLoadMoreEmailsRequest();
}
return false;
}
void _handleLoadMoreEmailsRequest() {
if (controller.isSearchActive) {
controller.searchMoreEmails();
} else {
controller.loadMoreEmails();
}
}
Widget _buildLoadMoreButton(BuildContext context, LoadingMoreStatus loadingMoreStatus) {
if (((controller.canLoadMore && !controller.isSearchActive) ||
(controller.canSearchMore && controller.isSearchActive)) &&
!loadingMoreStatus.isRunning) {
return Center(
child: OutlinedButton(
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
),
onPressed: _handleLoadMoreEmailsRequest,
child: Text(
AppLocalizations.of(context).loadMore,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.black
)
),
),
);
}
return const SizedBox.shrink();
}
Widget _buildEmailItem(BuildContext context, PresentationEmail presentationEmail) { Widget _buildEmailItem(BuildContext context, PresentationEmail presentationEmail) {
if (controller.responsiveUtils.isWebDesktop(context)) { if (controller.responsiveUtils.isWebDesktop(context)) {
return _buildEmailItemDraggable(context, presentationEmail); return _buildEmailItemDraggable(context, presentationEmail);
@@ -630,7 +677,7 @@ class ThreadView extends GetWidget<ThreadController>
} }
String _getMessageEmptyEmail(BuildContext context) { String _getMessageEmptyEmail(BuildContext context) {
if (controller.isSearchActive()) { if (controller.isSearchActive) {
return AppLocalizations.of(context).no_emails_matching_your_search; return AppLocalizations.of(context).no_emails_matching_your_search;
} else { } else {
if (controller.mailboxDashBoardController.filterMessageOption.value == FilterMessageOption.all && if (controller.mailboxDashBoardController.filterMessageOption.value == FilterMessageOption.all &&
@@ -643,7 +690,7 @@ class ThreadView extends GetWidget<ThreadController>
} }
String? _getSubMessageEmptyEmail(BuildContext context) { String? _getSubMessageEmptyEmail(BuildContext context) {
if (!controller.isSearchActive() if (!controller.isSearchActive
&& controller.mailboxDashBoardController.filterMessageOption.value != FilterMessageOption.all) { && controller.mailboxDashBoardController.filterMessageOption.value != FilterMessageOption.all) {
return AppLocalizations.of(context).reduceSomeFiltersAndTryAgain; return AppLocalizations.of(context).reduceSomeFiltersAndTryAgain;
} else if (controller.mailboxDashBoardController.filterMessageOption.value == FilterMessageOption.all && } else if (controller.mailboxDashBoardController.filterMessageOption.value == FilterMessageOption.all &&