TF-3769 Enable toggle thread detail feature in setting

TF-3769 Thread Detail fallback to single email when get thread fails

TF-3769 Thread Detail fix font height thread

TF-3769 Thread Detail init with get status

TF-3769 Thread Detail fix font height email receiver and sender

TF-3769 Thread Detail refactor reset thread detail ui action UpdatedThreadDetailSettingAction

TF-3769 Thread Detail refresh settings on app lifecycle changed

TF-3769 Thread Detail fix font height email receiver and sender

TF-3769 Thread Detail fix scroll view exception of single email

TF-3769 Thread Detail refactor local setting

TF-3769 Thread Detail optimize get cache setting

TF-3769 Thread Detail fix font height email receiver

TF-3769 Thread Detail refactor local setting cache

TF-3769 Thread Detail fix font height email receiver and sender

TF-3769 Thread Detail use CacheExceptionThrower for ManageAccountDataSourceImpl

TF-3769 Thread Detail fix changing email in tablet view

TF-3769 Thread Detail refresh on setting changed

TF-3769 Thread Detail fix receiver alignments

TF-3769 Thread Detail fix receiver alignments

TF-3769 Thread Detail default setting enabled

TF-3769 Thread Detail fix font style email receiver and sender

TF-3769 Thread Detail fix toggle setting when responsive

TF-3769 Thread Detail adjust receive time position when single email

TF-3769 Thread Detail revert initializeThreadDetailEmails to void

TF-3769 Thread Detail fix CI on initializeThreadDetailEmails test

TF-3769 Thread Detail fix spacing email sender

TF-3769 Thread Detail update receiver color mobile

TF-3769 Thread Detail update collapsed preview letter spacing

TF-3769 Thread Detail update receiver color mobile

TF-3769 Thread Detail fix receive time position

TF-3769 Thread Detail add mark read button mobile collapsed email

TF-3769 Thread Detail fix font style email receiver and sender

TF-3769 Thread Detail update collapsed preview font weight

TF-3769 Thread Detail fix position email receiver and sender
This commit is contained in:
DatDang
2025-05-29 17:07:44 +07:00
committed by Dat H. Pham
parent c9ca3925cb
commit 1bc45e35f3
57 changed files with 830 additions and 165 deletions
@@ -20,4 +20,6 @@ class UpdatedEmailKeywordsAction extends ThreadDetailUIAction {
@override
List<Object?> get props => [emailId, updatedKeyword, value];
}
}
class UpdatedThreadDetailSettingAction extends ThreadDetailUIAction {}
@@ -15,12 +15,6 @@ extension HandleGetEmailsByIdsSuccess on ThreadDetailController {
final selectedEmailId = mailboxDashBoardController.selectedEmail.value?.id;
final isLoadMore = emailIdsPresentation.values.whereNotNull().isNotEmpty;
final currentScrollPosition = scrollController?.position.pixels;
final maxScrollExtent = scrollController?.position.maxScrollExtent;
final currentBottomScrollPosition = currentScrollPosition != null
&& maxScrollExtent != null
? maxScrollExtent - currentScrollPosition
: null;
for (var presentationEmail in success.presentationEmails) {
if (presentationEmail.id == null) continue;
@@ -47,6 +41,13 @@ extension HandleGetEmailsByIdsSuccess on ThreadDetailController {
if (currentExpandedEmailIndex == -1 || firstLoadedMoreEmailIndex == -1) {
return;
}
final currentScrollPosition = scrollController?.position.pixels;
final maxScrollExtent = scrollController?.position.maxScrollExtent;
final currentBottomScrollPosition = currentScrollPosition != null
&& maxScrollExtent != null
? maxScrollExtent - currentScrollPosition
: null;
if (currentBottomScrollPosition != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -0,0 +1,15 @@
import 'package:dartz/dartz.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
extension HandleGetThreadByIdFailure on ThreadDetailController {
void handleGetThreadByIdFailure(GetThreadByIdFailure failure) {
final selectedEmail = mailboxDashBoardController.selectedEmail.value;
if (selectedEmail != null) {
consumeState(Stream.value(Right(GetEmailsByIdsSuccess([selectedEmail]))));
} else {
showRetryToast(failure);
}
}
}
@@ -7,6 +7,13 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_d
extension InitializeThreadDetailEmails on ThreadDetailController {
void initializeThreadDetailEmails() {
final threadDetailEnabled = isThreadDetailEnabled;
final selectedEmail = mailboxDashBoardController.selectedEmail.value;
if (!threadDetailEnabled && selectedEmail != null) {
consumeState(Stream.value(Right(GetEmailsByIdsSuccess([selectedEmail]))));
return;
}
final emailIdsToLoadMetaData = ThreadDetailPresentationUtils.getFirstLoadEmailIds(
emailIdsPresentation.keys.toList(),
selectedEmailId: mailboxDashBoardController.selectedEmail.value?.id,
@@ -0,0 +1,10 @@
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
extension RefreshThreadDetailOnSettingChanged on ThreadDetailController {
void refreshThreadDetailOnSettingChanged() {
if (threadDetailWasEnabled != isThreadDetailEnabled) {
threadDetailWasEnabled = isThreadDetailEnabled;
mailboxDashBoardController.selectedEmail.refresh();
}
}
}
@@ -0,0 +1 @@
enum ThreadDetailSettingStatus { loading, enabled, disabled }
@@ -7,14 +7,18 @@ import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_i
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/data/local/local_setting_cache_manager.dart';
import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_data_source.dart';
import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_local_data_source_impl.dart';
import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_remote_data_source_impl.dart';
import 'package:tmail_ui_user/features/thread_detail/data/network/thread_detail_api.dart';
import 'package:tmail_ui_user/features/thread_detail/data/repository/thread_detail_repository_impl.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/repository/thread_detail_repository.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_detail_status_interactor.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart';
class ThreadDetailBindings extends BaseBindings {
@@ -28,6 +32,7 @@ class ThreadDetailBindings extends BaseBindings {
@override
void bindingsController() {
Get.put(ThreadDetailController(
Get.find<GetThreadDetailStatusInteractor>(),
Get.find<GetThreadByIdInteractor>(),
Get.find<GetEmailsByIdsInteractor>(),
Get.find<MarkAsEmailReadInteractor>(),
@@ -44,6 +49,10 @@ class ThreadDetailBindings extends BaseBindings {
Get.find<ThreadDetailApi>(),
Get.find<RemoteExceptionThrower>(),
));
Get.lazyPut(() => ThreadDetailLocalDataSourceImpl(
Get.find<LocalSettingCacheManager>(),
Get.find<CacheExceptionThrower>(),
));
}
@override
@@ -55,6 +64,7 @@ class ThreadDetailBindings extends BaseBindings {
void bindingsRepositoryImpl() {
Get.lazyPut(() => ThreadDetailRepositoryImpl({
DataSourceType.network: Get.find<ThreadDetailDataSource>(),
DataSourceType.local: Get.find<ThreadDetailLocalDataSourceImpl>(),
}));
}
@@ -65,6 +75,7 @@ class ThreadDetailBindings extends BaseBindings {
@override
void bindingsInteractor() {
Get.lazyPut(() => GetThreadDetailStatusInteractor(Get.find<ThreadDetailRepository>()));
Get.lazyPut(() => GetThreadByIdInteractor(Get.find<ThreadDetailRepository>()));
Get.lazyPut(() => GetEmailsByIdsInteractor(Get.find<ThreadDetailRepository>()));
}
@@ -34,13 +34,18 @@ import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new
import 'package:tmail_ui_user/features/search/email/presentation/search_email_controller.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_detail_status_state.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_detail_status_interactor.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_email_ids_by_thread_id_success.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_thread_by_id_failure.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/refresh_thread_detail_on_setting_changed.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/model/thread_detail_setting_status.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_collapsed_email_download_states.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark_collapsed_email_star_success.dart';
@@ -48,6 +53,7 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/quick_create_rule_from_collapsed_email_success.dart';
class ThreadDetailController extends BaseController {
final GetThreadDetailStatusInteractor _getThreadDetailStatusInteractor;
final GetThreadByIdInteractor _getEmailIdsByThreadIdInteractor;
final GetEmailsByIdsInteractor getEmailsByIdsInteractor;
final MarkAsEmailReadInteractor _markAsEmailReadInteractor;
@@ -57,6 +63,7 @@ class ThreadDetailController extends BaseController {
final DownloadAttachmentForWebInteractor _downloadAttachmentForWebInteractor;
ThreadDetailController(
this._getThreadDetailStatusInteractor,
this._getEmailIdsByThreadIdInteractor,
this.getEmailsByIdsInteractor,
this._markAsEmailReadInteractor,
@@ -83,6 +90,9 @@ class ThreadDetailController extends BaseController {
ScrollController? scrollController;
CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
AppLifecycleListener? appLifecycleListener;
ThreadDetailSettingStatus threadDetailSettingStatus = ThreadDetailSettingStatus.loading;
bool threadDetailWasEnabled = true;
AccountId? get accountId => mailboxDashBoardController.accountId.value;
Session? get session => mailboxDashBoardController.sessionCurrent;
@@ -99,10 +109,22 @@ class ThreadDetailController extends BaseController {
.value == true;
return isWebSearchRunning || isMobileSearchRunning;
}
bool get isThreadDetailEnabled =>
threadDetailSettingStatus == ThreadDetailSettingStatus.enabled;
@override
void onInit() {
super.onInit();
consumeState(_getThreadDetailStatusInteractor.execute());
appLifecycleListener = AppLifecycleListener(
onResume: () {
if (threadDetailSettingStatus == ThreadDetailSettingStatus.loading) {
return;
}
consumeState(_getThreadDetailStatusInteractor.execute());
},
);
ever(mailboxDashBoardController.accountId, (accountId) {
if (accountId == null) return;
@@ -118,11 +140,19 @@ class ThreadDetailController extends BaseController {
);
});
downloadProgressState.stream.listen(handleDownloadProgressState);
ever(mailboxDashBoardController.selectedEmail, (presentationEmail) {
ever(mailboxDashBoardController.selectedEmail, (presentationEmail) async {
if (presentationEmail?.threadId == null) {
closeThreadDetailAction(currentContext);
return;
}
if (!isThreadDetailEnabled && presentationEmail?.id != null) {
consumeState(Stream.value(Right(GetThreadByIdSuccess([
presentationEmail!.id!,
]))));
return;
}
if (session != null &&
accountId != null &&
sentMailboxId != null &&
@@ -151,6 +181,11 @@ class ThreadDetailController extends BaseController {
})
..listUnsubscribeHeader?.clear();
}
} else if (action is UpdatedThreadDetailSettingAction) {
consumeState(_getThreadDetailStatusInteractor.execute());
mailboxDashBoardController.dispatchThreadDetailUIAction(
ThreadDetailUIAction(),
);
}
});
}
@@ -178,6 +213,13 @@ class ThreadDetailController extends BaseController {
quickCreateRuleFromCollapsedEmailSuccess(success);
} else if (success is DownloadAttachmentForWebSuccess) {
handleDownloadSuccess(success);
} else if (success is GetThreadDetailStatusSuccess) {
threadDetailSettingStatus = success.threadDetailEnabled
? ThreadDetailSettingStatus.enabled
: ThreadDetailSettingStatus.disabled;
refreshThreadDetailOnSettingChanged();
} else if (success is GettingThreadDetailStatus) {
threadDetailSettingStatus = ThreadDetailSettingStatus.loading;
} else {
super.handleSuccessViewState(success);
}
@@ -186,8 +228,7 @@ class ThreadDetailController extends BaseController {
@override
void handleFailureViewState(failure) {
if (failure is GetThreadByIdFailure) {
showRetryToast(failure);
return;
handleGetThreadByIdFailure(failure);
}
if (failure is GetEmailsByIdsFailure) {
showRetryToast(failure);
@@ -197,6 +238,10 @@ class ThreadDetailController extends BaseController {
handleDownloadFailure(failure);
return;
}
if (failure is GetThreadDetailStatusFailure) {
threadDetailSettingStatus = ThreadDetailSettingStatus.enabled;
refreshThreadDetailOnSettingChanged();
}
super.handleFailureViewState(failure);
}
}
@@ -174,8 +174,11 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
if (controller.emailIdsPresentation.length == 1 &&
controller.emailIdsPresentation.values.firstOrNull != null) {
final emailId = controller.emailIdsPresentation.values.firstOrNull!.id;
return EmailView(
emailId: controller.emailIdsPresentation.values.firstOrNull?.id,
key: ValueKey(emailId?.id.value),
emailId: emailId,
);
}
@@ -86,7 +86,11 @@ class ThreadDetailCollapsedEmail extends StatelessWidget {
fontWeight: presentationEmail.hasRead
? FontWeight.normal
: FontWeight.w600,
).copyWith(height: 24),
fontSize: presentationEmail.hasRead ? null : 15,
).copyWith(
height: presentationEmail.hasRead ? 24 / 16 : 20 / 15,
letterSpacing: presentationEmail.hasRead ? -0.16 : 0,
),
),
),
],