[Thread Detail] [Part 1] TF-3639 data layer and controller (#3645)

This commit is contained in:
Dat Dang
2025-05-07 11:10:39 +07:00
committed by Dat H. Pham
parent 1bd0705fb3
commit 7c3af832e4
11 changed files with 231 additions and 0 deletions
@@ -0,0 +1,9 @@
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
abstract class ThreadDetailDataSource {
Future<List<EmailId>> getThreadById(
ThreadId threadId,
AccountId accountId,
);
}
@@ -0,0 +1,25 @@
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.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/network/thread_detail_api.dart';
import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart';
class ThreadDetailRemoteDataSourceImpl implements ThreadDetailDataSource {
const ThreadDetailRemoteDataSourceImpl(
this.threadDetailApi,
this.exceptionThrower,
);
final ThreadDetailApi threadDetailApi;
final ExceptionThrower exceptionThrower;
@override
Future<List<EmailId>> getThreadById(
ThreadId threadId,
AccountId accountId
) {
return Future.sync(() async {
return threadDetailApi.getThreadById(threadId, accountId);
}).catchError(exceptionThrower.throwException);
}
}
@@ -0,0 +1,37 @@
import 'package:get/get_utils/get_utils.dart';
import 'package:jmap_dart_client/http/http_client.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/jmap_request.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/thread/get/get_thread_method.dart';
import 'package:jmap_dart_client/jmap/thread/get/get_thread_response.dart';
class ThreadDetailApi {
const ThreadDetailApi(this._httpClient);
final HttpClient _httpClient;
Future<List<EmailId>> getThreadById(
ThreadId threadId,
AccountId accountId,
) async {
final requestBuilder = JmapRequestBuilder(
_httpClient,
ProcessingInvocation(),
);
final getThreadMethod = GetThreadMethod(accountId)
..addIds({threadId.id});
final getThreadInvocation = requestBuilder.invocation(getThreadMethod);
final response = await (requestBuilder..usings(getThreadMethod.requiredCapabilities))
.build()
.execute();
final getThreadResponse = response.parse<GetThreadResponse>(
getThreadInvocation.methodCallId,
GetThreadResponse.deserialize,
);
return getThreadResponse!.list.firstWhereOrNull(
(thread) => thread.id == threadId,
)!.emailIds;
}
}
@@ -0,0 +1,20 @@
import 'package:core/data/model/source_type/data_source_type.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.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/domain/repository/thread_detail_repository.dart';
class ThreadDetailRepositoryImpl implements ThreadDetailRepository {
const ThreadDetailRepositoryImpl(this.threadDetailDataSource);
final Map<DataSourceType, ThreadDetailDataSource> threadDetailDataSource;
@override
Future<List<EmailId>> getThreadById(
ThreadId threadId,
AccountId accountId
) {
return threadDetailDataSource[DataSourceType.network]!
.getThreadById(threadId, accountId);
}
}
@@ -0,0 +1 @@
class EmptyThreadDetailException implements Exception {}
@@ -0,0 +1,9 @@
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
abstract class ThreadDetailRepository {
Future<List<EmailId>> getThreadById(
ThreadId threadId,
AccountId accountId,
);
}
@@ -0,0 +1,18 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
class GettingThreadById extends LoadingState {}
class GetThreadByIdSuccess extends UIState {
final List<EmailId> emailIds;
GetThreadByIdSuccess(this.emailIds);
@override
List<Object> get props => [emailIds];
}
class GetThreadByIdFailure extends FeatureFailure {
GetThreadByIdFailure({super.exception});
}
@@ -0,0 +1,37 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/exceptions/empty_thread_detail_exception.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/repository/thread_detail_repository.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
class GetThreadByIdInteractor {
const GetThreadByIdInteractor(this._threadDetailRepository);
final ThreadDetailRepository _threadDetailRepository;
Stream<Either<Failure, Success>> execute(
ThreadId threadId,
AccountId accountId,
) async* {
try {
yield Right(GettingThreadById());
final result = await _threadDetailRepository.getThreadById(
threadId,
accountId,
);
if (result.isEmpty) {
throw EmptyThreadDetailException();
}
yield Right(GetThreadByIdSuccess(result));
} catch (e) {
logError('GetEmailIdsByThreadIdInteractor::execute(): Exception: $e');
yield Left(GetThreadByIdFailure(exception: e));
}
}
}
@@ -0,0 +1,10 @@
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 HandleGetEmailIdsByThreadIdSuccess on ThreadDetailController {
void handleGetEmailIdsByThreadIdSuccess(
GetThreadByIdSuccess success,
) {
emailIds.value = success.emailIds;
}
}
@@ -0,0 +1,11 @@
import 'package:equatable/equatable.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
class ThreadDetailArguments with EquatableMixin {
final ThreadId threadId;
const ThreadDetailArguments({required this.threadId});
@override
List<Object?> get props => [threadId];
}
@@ -0,0 +1,54 @@
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_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/usecases/get_thread_by_id_interactor.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/model/thread_detail_arguments.dart';
class ThreadDetailController extends BaseController {
final ThreadDetailArguments arguments;
final GetThreadByIdInteractor _getEmailIdsByThreadIdInteractor;
ThreadDetailController(
this.arguments,
this._getEmailIdsByThreadIdInteractor,
);
final emailIds = <EmailId>[].obs;
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
AccountId? get accountId => mailboxDashBoardController.accountId.value;
@override
void onInit() {
super.onInit();
if (accountId != null) {
consumeState(_getEmailIdsByThreadIdInteractor.execute(
arguments.threadId,
accountId!,
));
}
}
@override
void handleSuccessViewState(success) {
if (success is GetThreadByIdSuccess) {
handleGetEmailIdsByThreadIdSuccess(success);
} else {
super.handleSuccessViewState(success);
}
}
@override
void handleFailureViewState(failure) {
if (failure is GetThreadByIdFailure) {
// TODO: handle failure
return;
}
super.handleFailureViewState(failure);
}
}