diff --git a/lib/features/thread_detail/data/data_source/thread_detail_data_source.dart b/lib/features/thread_detail/data/data_source/thread_detail_data_source.dart new file mode 100644 index 000000000..a4d8960fc --- /dev/null +++ b/lib/features/thread_detail/data/data_source/thread_detail_data_source.dart @@ -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> getThreadById( + ThreadId threadId, + AccountId accountId, + ); +} \ No newline at end of file diff --git a/lib/features/thread_detail/data/data_source/thread_detail_remote_data_source_impl.dart b/lib/features/thread_detail/data/data_source/thread_detail_remote_data_source_impl.dart new file mode 100644 index 000000000..2c2ad7dd0 --- /dev/null +++ b/lib/features/thread_detail/data/data_source/thread_detail_remote_data_source_impl.dart @@ -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> getThreadById( + ThreadId threadId, + AccountId accountId + ) { + return Future.sync(() async { + return threadDetailApi.getThreadById(threadId, accountId); + }).catchError(exceptionThrower.throwException); + } +} \ No newline at end of file diff --git a/lib/features/thread_detail/data/network/thread_detail_api.dart b/lib/features/thread_detail/data/network/thread_detail_api.dart new file mode 100644 index 000000000..98304e05c --- /dev/null +++ b/lib/features/thread_detail/data/network/thread_detail_api.dart @@ -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> 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( + getThreadInvocation.methodCallId, + GetThreadResponse.deserialize, + ); + + return getThreadResponse!.list.firstWhereOrNull( + (thread) => thread.id == threadId, + )!.emailIds; + } +} \ No newline at end of file diff --git a/lib/features/thread_detail/data/repository/thread_detail_repository_impl.dart b/lib/features/thread_detail/data/repository/thread_detail_repository_impl.dart new file mode 100644 index 000000000..3102e5b6a --- /dev/null +++ b/lib/features/thread_detail/data/repository/thread_detail_repository_impl.dart @@ -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 threadDetailDataSource; + + @override + Future> getThreadById( + ThreadId threadId, + AccountId accountId + ) { + return threadDetailDataSource[DataSourceType.network]! + .getThreadById(threadId, accountId); + } +} \ No newline at end of file diff --git a/lib/features/thread_detail/domain/exceptions/empty_thread_detail_exception.dart b/lib/features/thread_detail/domain/exceptions/empty_thread_detail_exception.dart new file mode 100644 index 000000000..699fa20b2 --- /dev/null +++ b/lib/features/thread_detail/domain/exceptions/empty_thread_detail_exception.dart @@ -0,0 +1 @@ +class EmptyThreadDetailException implements Exception {} \ No newline at end of file diff --git a/lib/features/thread_detail/domain/repository/thread_detail_repository.dart b/lib/features/thread_detail/domain/repository/thread_detail_repository.dart new file mode 100644 index 000000000..c05bd5abc --- /dev/null +++ b/lib/features/thread_detail/domain/repository/thread_detail_repository.dart @@ -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> getThreadById( + ThreadId threadId, + AccountId accountId, + ); +} \ No newline at end of file diff --git a/lib/features/thread_detail/domain/state/get_thread_by_id_state.dart b/lib/features/thread_detail/domain/state/get_thread_by_id_state.dart new file mode 100644 index 000000000..5b8828c20 --- /dev/null +++ b/lib/features/thread_detail/domain/state/get_thread_by_id_state.dart @@ -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 emailIds; + + GetThreadByIdSuccess(this.emailIds); + + @override + List get props => [emailIds]; +} + +class GetThreadByIdFailure extends FeatureFailure { + GetThreadByIdFailure({super.exception}); +} \ No newline at end of file diff --git a/lib/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart b/lib/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart new file mode 100644 index 000000000..51f6f43c8 --- /dev/null +++ b/lib/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart @@ -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> 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)); + } + } +} \ No newline at end of file diff --git a/lib/features/thread_detail/presentation/extension/handle_get_email_ids_by_thread_id_success.dart b/lib/features/thread_detail/presentation/extension/handle_get_email_ids_by_thread_id_success.dart new file mode 100644 index 000000000..21c4ebb5d --- /dev/null +++ b/lib/features/thread_detail/presentation/extension/handle_get_email_ids_by_thread_id_success.dart @@ -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; + } +} \ No newline at end of file diff --git a/lib/features/thread_detail/presentation/model/thread_detail_arguments.dart b/lib/features/thread_detail/presentation/model/thread_detail_arguments.dart new file mode 100644 index 000000000..3ef70c06d --- /dev/null +++ b/lib/features/thread_detail/presentation/model/thread_detail_arguments.dart @@ -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 get props => [threadId]; +} \ No newline at end of file diff --git a/lib/features/thread_detail/presentation/thread_detail_controller.dart b/lib/features/thread_detail/presentation/thread_detail_controller.dart new file mode 100644 index 000000000..f6546d6a7 --- /dev/null +++ b/lib/features/thread_detail/presentation/thread_detail_controller.dart @@ -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 = [].obs; + + final mailboxDashBoardController = Get.find(); + + 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); + } +} \ No newline at end of file