From cab53453b535c9caacf397fcad20c70b236da42b Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 27 Jul 2021 12:56:27 +0700 Subject: [PATCH] Init mailbox feature add presentation layer --- lib/features/base/base_controller.dart | 23 ++ .../presentation/mailbox_bindings.dart | 22 ++ .../presentation/mailbox_controller.dart | 60 +++++ .../mailbox/presentation/mailbox_view.dart | 161 +++++++++++++ .../presentation/model/mailbox_node.dart | 48 ++++ .../presentation/model/mailbox_tree.dart | 43 ++++ .../model/mailbox_tree_builder.dart | 58 +++++ .../mailbox_button_new_folder_builder.dart | 69 ++++++ .../widgets/mailbox_folder_tile_builder.dart | 57 +++++ .../widgets/mailbox_tile_builder.dart | 86 +++++++ .../widgets/search_form_widget_builder.dart | 67 ++++++ .../widgets/storage_widget_builder.dart | 51 +++++ .../user_information_widget_builder.dart | 59 +++++ lib/l10n/intl_messages.arb | 2 +- .../mailbox/mock/mock_mailbox_repository.dart | 4 + .../presentation/model/mailbox_node_test.dart | 48 ++++ .../model/mailbox_tree_builder_test.dart | 212 ++++++++++++++++++ .../presentation/model/mailbox_tree_test.dart | 101 +++++++++ 18 files changed, 1170 insertions(+), 1 deletion(-) create mode 100644 lib/features/base/base_controller.dart create mode 100644 lib/features/mailbox/presentation/mailbox_bindings.dart create mode 100644 lib/features/mailbox/presentation/mailbox_controller.dart create mode 100644 lib/features/mailbox/presentation/mailbox_view.dart create mode 100644 lib/features/mailbox/presentation/model/mailbox_node.dart create mode 100644 lib/features/mailbox/presentation/model/mailbox_tree.dart create mode 100644 lib/features/mailbox/presentation/model/mailbox_tree_builder.dart create mode 100644 lib/features/mailbox/presentation/widgets/mailbox_button_new_folder_builder.dart create mode 100644 lib/features/mailbox/presentation/widgets/mailbox_folder_tile_builder.dart create mode 100644 lib/features/mailbox/presentation/widgets/mailbox_tile_builder.dart create mode 100644 lib/features/mailbox/presentation/widgets/search_form_widget_builder.dart create mode 100644 lib/features/mailbox/presentation/widgets/storage_widget_builder.dart create mode 100644 lib/features/mailbox/presentation/widgets/user_information_widget_builder.dart create mode 100644 test/features/mailbox/mock/mock_mailbox_repository.dart create mode 100644 test/features/mailbox/presentation/model/mailbox_node_test.dart create mode 100644 test/features/mailbox/presentation/model/mailbox_tree_builder_test.dart create mode 100644 test/features/mailbox/presentation/model/mailbox_tree_test.dart diff --git a/lib/features/base/base_controller.dart b/lib/features/base/base_controller.dart new file mode 100644 index 000000000..ba9847e02 --- /dev/null +++ b/lib/features/base/base_controller.dart @@ -0,0 +1,23 @@ +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:get/get.dart'; + +abstract class BaseController extends GetxController { + final viewState = Rx>(Right(UIState.idle)); + + void consumeState(Stream> newStateStream) async { + newStateStream.listen( + (state) => onData(state), + onError: (error) => onError(error), + onDone: () => onDone() + ); + } + + void onData(Either newState) { + viewState.value = newState; + } + + void onError(dynamic error); + + void onDone(); +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/mailbox_bindings.dart b/lib/features/mailbox/presentation/mailbox_bindings.dart new file mode 100644 index 000000000..bb1ffa400 --- /dev/null +++ b/lib/features/mailbox/presentation/mailbox_bindings.dart @@ -0,0 +1,22 @@ +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasource.dart'; +import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart'; +import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_api.dart'; +import 'package:tmail_ui_user/features/mailbox/data/repository/mailbox_repository_impl.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_controller.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart'; + +class MailboxBindings extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => MailboxDataSourceImpl(Get.find())); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => MailboxRepositoryImpl(Get.find())); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => GetAllMailboxInteractor(Get.find())); + Get.lazyPut(() => TreeBuilder()); + Get.lazyPut(() => MailboxController(Get.find(), Get.find())); + } +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart new file mode 100644 index 000000000..406ff4ee5 --- /dev/null +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -0,0 +1,60 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:get/get.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/get_all_mailboxes_state.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart'; +import 'package:tmail_ui_user/main/routes/app_routes.dart'; + +class MailboxController extends BaseController { + + final GetAllMailboxInteractor _getAllMailboxInteractor; + final TreeBuilder _treeBuilder; + + MailboxController(this._getAllMailboxInteractor, this._treeBuilder); + + final folderMailboxTree = MailboxTree(MailboxNode.root()).obs; + + @override + void onReady() { + super.onReady(); + getAllMailboxAction(); + } + + void getAllMailboxAction() async { + final AccountId accountId = AccountId(Id('93c56f4408cff66f0a929aea8e3940e753c3275e5622582ae3010e7277b7696c')); + consumeState(_getAllMailboxInteractor.execute(accountId)); + } + + @override + void onData(Either newState) { + super.onData(newState); + newState + .map((success) => success is GetAllMailboxSuccess + ? _buildTree(success.folderMailboxList) + : null); + } + + @override + void onDone() { + } + + @override + void onError(error) { + } + + void _buildTree(List folderMailboxList) async { + folderMailboxTree.value = await _treeBuilder.generateMailboxTree(folderMailboxList); + } + + void closeMailboxScreen() { + Get.offAllNamed(AppRoutes.LOGIN); + } +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/mailbox_view.dart b/lib/features/mailbox/presentation/mailbox_view.dart new file mode 100644 index 000000000..26e2e813a --- /dev/null +++ b/lib/features/mailbox/presentation/mailbox_view.dart @@ -0,0 +1,161 @@ +import 'package:built_collection/built_collection.dart'; +import 'package:core/core.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/get_all_mailboxes_state.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_controller.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_button_new_folder_builder.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_folder_tile_builder.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_tile_builder.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/widgets/search_form_widget_builder.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/widgets/storage_widget_builder.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/widgets/user_information_widget_builder.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class MailboxView extends GetWidget { + + final mailboxController = Get.find(); + final imagePaths = Get.find(); + final responsiveUtils = Get.find(); + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: AppColor.primaryLightColor, + body: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildCloseScreenButton(), + _buildUserInformationWidget(context), + _buildSearchFormWidget(context), + _buildLoadingView(), + Expanded(child: RefreshIndicator( + color: AppColor.primaryColor, + onRefresh: () async => mailboxController.getAllMailboxAction(), + child: _buildListMailbox(context))) + ])), + bottomNavigationBar: _buildStorageWidget(context), + ); + } + + Widget _buildCloseScreenButton() { + return Padding( + padding: EdgeInsets.only(left: 24, top: 12, right: 16), + child: IconButton( + key: Key('mailbox_close_button'), + onPressed: () => mailboxController.closeMailboxScreen(), + icon: SvgPicture.asset(imagePaths.icCloseMailbox, width: 30, height: 30, fit: BoxFit.fill))); + } + + Widget _buildUserInformationWidget(BuildContext context) { + return Padding( + padding: EdgeInsets.only(left: 16, top: 8.0, right: 16), + child: UserInformationWidgetBuilder() + .onOpenUserInformationAction(() => {}) + .build()); + } + + Widget _buildSearchFormWidget(BuildContext context) { + return Padding( + padding: EdgeInsets.only(top: 16, bottom: 12, left: 16, right: 16), + child: SearchFormWidgetBuilder(context) + .onNewSearchQuery((searchQuery) => {}) + .build()); + } + + Widget _buildLoadingView() { + return Obx(() => mailboxController.viewState.value.fold( + (failure) => SizedBox.shrink(), + (success) => success == UIState.loading + ? Center(child: Padding( + padding: EdgeInsets.only(top: 16), + child: SizedBox( + width: 30, + height: 30, + child: CircularProgressIndicator(color: AppColor.primaryColor)))) + : SizedBox.shrink())); + } + + Widget _buildListMailbox(BuildContext context) { + return ListView( + key: Key('mailbox_list'), + primary: true, + children: [ + Obx(() => mailboxController.viewState.value.fold( + (failure) => SizedBox.shrink(), + (success) => success is GetAllMailboxSuccess + ? _buildDefaultMailbox(success.defaultMailboxList) + : SizedBox.shrink()) + ), + Padding( + padding: EdgeInsets.only(left: 40, top: 20), + child: Text( + AppLocalizations.of(context).my_folders, + maxLines: 1, + style: TextStyle(fontSize: 12, color: AppColor.myFolderTitleColor, fontWeight: FontWeight.w500)) + ), + Padding( + padding: EdgeInsets.only(left: 16, right: 16, top: 10), + child: MailboxNewFolderTileBuilder() + .addIcon(imagePaths.icMailboxNewFolder) + .addName(AppLocalizations.of(context).new_folder) + .build() + ), + Obx(() => mailboxController.folderMailboxTree.value.root.childrenItems.isNotEmpty + ? _buildFolderMailbox(context, mailboxController.folderMailboxTree.value) + : SizedBox.shrink() + ), + ] + ); + } + + Widget _buildDefaultMailbox(List defaultMailbox) { + return ListView.builder( + padding: EdgeInsets.only(top: 16, left: 16, right: 16), + key: Key('default_mailbox_list'), + itemCount: defaultMailbox.length, + shrinkWrap: true, + primary: false, + itemBuilder: (context, index) => + MailboxTileBuilder(defaultMailbox[index]) + .build()); + } + + Widget _buildFolderMailbox(BuildContext context, MailboxTree mailboxTree) { + return Padding( + padding: EdgeInsets.only(left: 4, right: 16), + child: TreeView( + startExpanded: false, + key: Key('folder_mailbox_list'), + children: _buildListChildTileWidget(context, mailboxTree.root.childrenItems)) + ); + } + + List _buildListChildTileWidget(BuildContext context, BuiltList listMailboxNode) { + return listMailboxNode + .map((mailboxNode) => mailboxNode.hasChildren() + ? Padding( + padding: EdgeInsets.only(left: 16), + child: TreeViewChild( + key: Key('children_tree_mailbox_child'), + parent: MailBoxFolderTileBuilder(mailboxNode).build(context), + children: _buildListChildTileWidget(context, mailboxNode.childrenItems))) + : Padding( + padding: EdgeInsets.only(left: 16), + child: MailBoxFolderTileBuilder(mailboxNode).build(context))) + .toList(); + } + + Widget _buildStorageWidget(BuildContext context) => StorageWidgetBuilder(context).build(); +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/model/mailbox_node.dart b/lib/features/mailbox/presentation/model/mailbox_node.dart new file mode 100644 index 000000000..b92af2f20 --- /dev/null +++ b/lib/features/mailbox/presentation/model/mailbox_node.dart @@ -0,0 +1,48 @@ +import 'package:built_collection/built_collection.dart'; +import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; + +class MailboxNode with EquatableMixin{ + static PresentationMailbox _root = PresentationMailbox(MailboxId(Id('root'))); + + PresentationMailbox item; + late BuiltList childrenItems; + + factory MailboxNode.root() => MailboxNode(_root); + + bool hasChildren() => childrenItems.isNotEmpty; + + MailboxNode(this.item, {List child = const []}) { + this.childrenItems = BuiltList(child); + } + + MailboxNode? addChild(PresentationMailbox mailbox) { + if (_validateChild(mailbox)) { + final node = MailboxNode(mailbox); + _addChildNode(node); + return node; + } + return null; + } + + void _addChildNode(MailboxNode node) { + childrenItems = (ListBuilder(childrenItems) + ..add(node)) + .build(); + } + + bool _validateChild(PresentationMailbox mailbox) { + if (mailbox.parentId == item.id) { + return true; + } + if (item == _root) { + return true; + } + return false; + } + + @override + List get props => [item, childrenItems]; +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/model/mailbox_tree.dart b/lib/features/mailbox/presentation/model/mailbox_tree.dart new file mode 100644 index 000000000..78e4f2b95 --- /dev/null +++ b/lib/features/mailbox/presentation/model/mailbox_tree.dart @@ -0,0 +1,43 @@ +import 'dart:collection'; + +import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; + +import 'mailbox_node.dart'; + +class MailboxTree with EquatableMixin { + MailboxNode root; + MailboxTree(this.root); + + MailboxNode? findNode(MailboxId? mailboxId) { + var result; + final queue = ListQueue(); + queue.addLast(root); + while (queue.isNotEmpty && mailboxId != null) { + final currentNode = queue.removeFirst(); + if (mailboxId == currentNode.item.id) { + result = currentNode; + break; + } + currentNode.childrenItems.forEach((child) { + queue.addLast(child); + }); + } + return result; + } + + MailboxNode? insertNode(PresentationMailbox mailbox) { + if (mailbox.parentId == null) { + return root.addChild(mailbox); + } + + final foundParent = findNode(mailbox.parentId); + return foundParent != null + ? foundParent.addChild(mailbox) + : root.addChild(mailbox); + } + + @override + List get props => [root]; +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/model/mailbox_tree_builder.dart b/lib/features/mailbox/presentation/model/mailbox_tree_builder.dart new file mode 100644 index 000000000..f169ab4ee --- /dev/null +++ b/lib/features/mailbox/presentation/model/mailbox_tree_builder.dart @@ -0,0 +1,58 @@ +import 'dart:collection'; + +import 'package:built_collection/built_collection.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; + +import 'mailbox_node.dart'; +import 'mailbox_tree.dart'; + +class TreeBuilder { + Future generateMailboxTree(List mailboxesList) async { + final Map> mapNotFoundParentMailbox = HashMap(); + + final tree = MailboxTree(MailboxNode.root()); + mailboxesList.forEach((mailbox) { + if (mailbox.hasParentId()) { + final parentId = mailbox.parentId!; + final foundParent = tree.findNode(parentId); + if (foundParent != null) { + _addDescendant(tree, mailbox, maybeDescendant: mapNotFoundParentMailbox); + } else { + final siblingTrees = mapNotFoundParentMailbox[mailbox.parentId]; + if (siblingTrees != null) { + mapNotFoundParentMailbox.addAll({ + parentId: (ListBuilder(siblingTrees)..add(mailbox)).build() + }); + } else { + mapNotFoundParentMailbox.addAll({parentId: (ListBuilder()..add(mailbox)).build()}); + } + } + } else { + _addDescendant(tree, mailbox, maybeDescendant: mapNotFoundParentMailbox); + } + }); + mapNotFoundParentMailbox.forEach((_, listMailbox) { + listMailbox.forEach((mailbox) { + _addDescendant(tree, mailbox); + }); + }); + return tree; + } + + MailboxNode? _addDescendant( + MailboxTree tree, + PresentationMailbox mailboxChild, + {Map>? maybeDescendant} + ) { + final queue = ListQueue(); + queue.addLast(mailboxChild); + while (queue.isNotEmpty) { + final current = queue.removeFirst(); + tree.insertNode(current); + maybeDescendant?.remove(current.id)?.forEach((child) { + queue.addLast(child); + }); + } + } +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/widgets/mailbox_button_new_folder_builder.dart b/lib/features/mailbox/presentation/widgets/mailbox_button_new_folder_builder.dart new file mode 100644 index 000000000..300f737d3 --- /dev/null +++ b/lib/features/mailbox/presentation/widgets/mailbox_button_new_folder_builder.dart @@ -0,0 +1,69 @@ +import 'dart:ui'; + +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; + +typedef OnOpenMailboxNewFolderActionClick = void Function(); + +class MailboxNewFolderTileBuilder { + final imagePath = Get.find(); + + String? _icon; + String? _name; + + OnOpenMailboxNewFolderActionClick? _onOpenMailboxFolderActionClick; + + MailboxNewFolderTileBuilder(); + + MailboxNewFolderTileBuilder addIcon(String icon) { + _icon = icon; + return this; + } + + MailboxNewFolderTileBuilder addName(String name) { + _name = name; + return this; + } + + MailboxNewFolderTileBuilder onOpenMailboxFolderAction(OnOpenMailboxNewFolderActionClick onOpenMailboxFolderActionClick) { + _onOpenMailboxFolderActionClick = onOpenMailboxFolderActionClick; + return this; + } + + Widget build() { + return Theme( + data: ThemeData( + splashColor: Colors.transparent, + highlightColor: Colors.transparent), + child: Container( + key: Key('mailbox_new_folder_tile'), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: AppColor.mailboxBackgroundColor), + child: ListTile( + onTap: () => { + if (_onOpenMailboxFolderActionClick != null) { + _onOpenMailboxFolderActionClick!() + } + }, + leading: Transform( + transform: Matrix4.translationValues(24.0, 0.0, 0.0), + child: _icon != null + ? SvgPicture.asset(_icon!, width: 24, height: 24, color: AppColor.mailboxIconColor, fit: BoxFit.fill) + : SizedBox.shrink()), + title: Transform( + transform: Matrix4.translationValues(10.0, 0.0, 0.0), + child: Text( + _name ?? '', + maxLines: 1, + style: TextStyle(fontSize: 15, color: AppColor.mailboxTextColor, fontWeight: FontWeight.bold), + )), + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/widgets/mailbox_folder_tile_builder.dart b/lib/features/mailbox/presentation/widgets/mailbox_folder_tile_builder.dart new file mode 100644 index 000000000..184dbb76c --- /dev/null +++ b/lib/features/mailbox/presentation/widgets/mailbox_folder_tile_builder.dart @@ -0,0 +1,57 @@ +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; + +typedef OnOpenMailBoxFolderActionClick = void Function(MailboxTree mailboxTree); + +class MailBoxFolderTileBuilder extends StatelessWidget { + + final imagePaths = Get.find(); + + final MailboxNode _mailboxNode; + + final OnOpenMailBoxFolderActionClick? onOpenMailBoxFolderActionClick; + + MailBoxFolderTileBuilder( + this._mailboxNode, + { + this.onOpenMailBoxFolderActionClick + } + ); + + @override + Widget build(BuildContext context) { + return Container( + key: Key('mailbox_folder_tile'), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: AppColor.mailboxBackgroundColor), + child: ListTile( + leading: Transform( + transform: Matrix4.translationValues(20.0, 0.0, 0.0), + child: SvgPicture.asset(imagePaths.icMailboxFolder, width: 24, height: 24, color: AppColor.mailboxIconColor, fit: BoxFit.fill)), + title: Transform( + transform: Matrix4.translationValues(10.0, 0.0, 0.0), + child: Text( + _mailboxNode.item.name!.name, + maxLines: 1, + style: TextStyle(fontSize: 15, color: AppColor.mailboxTextColor, fontWeight: FontWeight.bold), + )), + trailing: Transform( + transform: Matrix4.translationValues(-16.0, 0.0, 0.0), + child: _mailboxNode.hasChildren() + ? SvgPicture.asset( + imagePaths.icFolderArrow, + width: 12, + height: 12, + color: AppColor.mailboxIconColor, + fit: BoxFit.fill) + : SizedBox.shrink())) + ); + } +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/widgets/mailbox_tile_builder.dart b/lib/features/mailbox/presentation/widgets/mailbox_tile_builder.dart new file mode 100644 index 000000000..f17168c26 --- /dev/null +++ b/lib/features/mailbox/presentation/widgets/mailbox_tile_builder.dart @@ -0,0 +1,86 @@ +import 'dart:ui'; + +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/extensions/presentation_mailbox_extension.dart'; + +typedef OnOpenMailboxActionClick = void Function(); + +class MailboxTileBuilder { + final imagePaths = Get.find(); + + final PresentationMailbox _presentationMailbox; + + OnOpenMailboxActionClick? _onOpenMailboxActionClick; + + MailboxTileBuilder(this._presentationMailbox); + + MailboxTileBuilder onOpenMailboxAction(OnOpenMailboxActionClick onOpenMailboxActionClick) { + _onOpenMailboxActionClick = onOpenMailboxActionClick; + return this; + } + + Widget build() { + return Theme( + data: ThemeData( + splashColor: Colors.transparent, + highlightColor: Colors.transparent), + child: Container( + key: Key('mailbox_list_tile'), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: _presentationMailbox.selectMode == SelectMode.ACTIVE + ? AppColor.mailboxSelectedBackgroundColor + : AppColor.mailboxBackgroundColor), + child: ListTile( + focusColor: AppColor.primaryLightColor, + hoverColor: AppColor.primaryLightColor, + onTap: () => { + if (_onOpenMailboxActionClick != null) { + _onOpenMailboxActionClick!() + } + }, + leading: Transform( + transform: Matrix4.translationValues(20.0, 0.0, 0.0), + child: SvgPicture.asset( + '${_presentationMailbox.getMailboxIcon(imagePaths)}', + width: 24, + height: 24, + color: _presentationMailbox.selectMode == SelectMode.ACTIVE + ? AppColor.mailboxSelectedIconColor + : AppColor.mailboxIconColor, + fit: BoxFit.fill)), + title: Transform( + transform: Matrix4.translationValues(8.0, 0.0, 0.0), + child: Text( + _presentationMailbox.name!.name, + maxLines: 1, + overflow:TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15, + color: _presentationMailbox.selectMode == SelectMode.ACTIVE + ? AppColor.mailboxSelectedTextColor + : AppColor.mailboxTextColor, + fontWeight: FontWeight.bold), + )), + trailing: Transform( + transform: Matrix4.translationValues(-16.0, 0.0, 0.0), + child: Text( + '${_presentationMailbox.getCountUnReadEmails()}', + maxLines: 1, + style: TextStyle( + fontSize: 15, + color: _presentationMailbox.selectMode == SelectMode.ACTIVE + ? AppColor.mailboxSelectedTextNumberColor + : AppColor.mailboxTextNumberColor, + fontWeight: FontWeight.bold)) + )), + ) + ); + } +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/widgets/search_form_widget_builder.dart b/lib/features/mailbox/presentation/widgets/search_form_widget_builder.dart new file mode 100644 index 000000000..e06f55b4d --- /dev/null +++ b/lib/features/mailbox/presentation/widgets/search_form_widget_builder.dart @@ -0,0 +1,67 @@ +import 'dart:ui'; + +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +typedef OnNewSearchQuery = Function(String); + +class SearchFormWidgetBuilder { + final imagePath = Get.find(); + final TextEditingController _typeAheadController = TextEditingController(); + + final BuildContext _context; + + OnNewSearchQuery? _onNewSearchQuery; + + SearchFormWidgetBuilder(this._context); + + SearchFormWidgetBuilder onNewSearchQuery(OnNewSearchQuery onNewSearchQuery) { + _onNewSearchQuery = onNewSearchQuery; + return this; + } + + Widget build() { + return Container( + key: Key('search_folder_form'), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColor.searchBorderColor), + color: AppColor.primaryLightColor), + child: TypeAheadFormField( + textFieldConfiguration: TextFieldConfiguration( + controller: _typeAheadController, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + contentPadding: EdgeInsets.only(left: 0, top: 15, bottom: 15, right: 15), + hintText: AppLocalizations.of(_context).search_folder, + hintStyle: TextStyle(color: AppColor.searchHintTextColor, fontSize: 15.0, fontWeight: FontWeight.w500), + icon: Padding( + padding: EdgeInsets.only(left: 20), + child: SvgPicture.asset(imagePath.icSearch, width: 24, height: 24, fit: BoxFit.fill), + ))), + debounceDuration: Duration(milliseconds: 300), + suggestionsCallback: (pattern) async { + if (_onNewSearchQuery != null) { + _onNewSearchQuery!(pattern); + } + return []; + }, + itemBuilder: (BuildContext context, itemData) => SizedBox.shrink(), + onSuggestionSelected: (suggestion) {}, + noItemsFoundBuilder: (context) => SizedBox(), + hideOnEmpty: true, + hideOnError: true, + hideOnLoading: true, + hideSuggestionsOnKeyboardHide: true, + )); + } +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/widgets/storage_widget_builder.dart b/lib/features/mailbox/presentation/widgets/storage_widget_builder.dart new file mode 100644 index 000000000..e986a27bf --- /dev/null +++ b/lib/features/mailbox/presentation/widgets/storage_widget_builder.dart @@ -0,0 +1,51 @@ +import 'dart:ui'; + +import 'package:core/core.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class StorageWidgetBuilder { + final imagePath = Get.find(); + final BuildContext _context; + + StorageWidgetBuilder(this._context); + + Widget build() { + return Container( + key: Key('storage_widget'), + padding: EdgeInsets.only(left: 40, top: 16, bottom: 20, right: 40), + color: AppColor.storageBackgroundColor, + alignment: Alignment.bottomLeft, + height: 112, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(_context).storage, + maxLines: 1, + style: TextStyle(fontSize: 12, color: AppColor.storageTitleColor, fontWeight: FontWeight.w500)), + Padding( + padding: EdgeInsets.only(top: 8.0), + child: RichText( + text: TextSpan( + style: TextStyle(fontSize: 16, color: AppColor.storageMaxSizeColor, fontWeight: FontWeight.w700), + children: [ + TextSpan( + text: '299.6MB', + style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16, color: AppColor.storageUseSizeColor)), + TextSpan( + text: ' / unlimited', + style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16, color: AppColor.storageMaxSizeColor)) + ] + ) + ) + ) + ] + ) + ); + } +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/widgets/user_information_widget_builder.dart b/lib/features/mailbox/presentation/widgets/user_information_widget_builder.dart new file mode 100644 index 000000000..165cc0ea5 --- /dev/null +++ b/lib/features/mailbox/presentation/widgets/user_information_widget_builder.dart @@ -0,0 +1,59 @@ +import 'dart:ui'; + +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; + +typedef OnOpenUserInformationActionClick = void Function(); + +class UserInformationWidgetBuilder { + final imagePath = Get.find(); + + OnOpenUserInformationActionClick? _onOpenUserInformationActionClick; + + UserInformationWidgetBuilder(); + + UserInformationWidgetBuilder onOpenUserInformationAction( + OnOpenUserInformationActionClick onOpenUserInformationActionClick) { + _onOpenUserInformationActionClick = onOpenUserInformationActionClick; + return this; + } + + Widget build() { + return Container( + key: Key('user_information_widget'), + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: AppColor.userInformationBackgroundColor), + child: ListTile( + onTap: () { + if (_onOpenUserInformationActionClick != null) { + _onOpenUserInformationActionClick!(); + } + }, + leading: SvgPicture.asset(imagePath.icTMailLogo, width: 40, height: 40, fit: BoxFit.fill), + title: Transform( + transform: Matrix4.translationValues(0.0, 0.0, 0.0), + child: Text( + 'John Doe', + maxLines: 1, + style: TextStyle(fontSize: 16, color: AppColor.nameUserColor, fontWeight: FontWeight.w500), + )), + subtitle: Transform( + transform: Matrix4.translationValues(0.0, 4.0, 0.0), + child: Text( + 'user@example.com', + maxLines: 1, + style: TextStyle(fontSize: 12, color: AppColor.emailUserColor, fontWeight: FontWeight.w500), + )), + trailing: Transform( + transform: Matrix4.translationValues(0.0, 0.0, 0.0), + child: IconButton( + icon: SvgPicture.asset(imagePath.icNextArrow, width: 7, height: 12, fit: BoxFit.fill), + onPressed: () => {}))), + ); + } +} \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 42bf1a0dd..f3ae662bc 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2021-07-23T09:09:13.997889", + "@@last_modified": "2021-07-27T15:14:36.105457", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", diff --git a/test/features/mailbox/mock/mock_mailbox_repository.dart b/test/features/mailbox/mock/mock_mailbox_repository.dart new file mode 100644 index 000000000..717d8d88b --- /dev/null +++ b/test/features/mailbox/mock/mock_mailbox_repository.dart @@ -0,0 +1,4 @@ +import 'package:mockito/mockito.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; + +class MockMailboxRepository extends Mock implements MailboxRepository {} \ No newline at end of file diff --git a/test/features/mailbox/presentation/model/mailbox_node_test.dart b/test/features/mailbox/presentation/model/mailbox_node_test.dart new file mode 100644 index 000000000..ddf22baa3 --- /dev/null +++ b/test/features/mailbox/presentation/model/mailbox_node_test.dart @@ -0,0 +1,48 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; + +void main() { + group('mailbox node test', () { + + test('addChild should add mailbox to a node', () async { + final parentMailbox = PresentationMailbox(MailboxId(Id('1'))); + final node = MailboxNode(parentMailbox); + final childMailbox = PresentationMailbox(MailboxId(Id('11')), parentId: MailboxId(Id('1'))); + final childNode = node.addChild(childMailbox); + + expect(node.childrenItems.length, equals(1)); + expect(node.childrenItems[0], equals(childNode)); + }); + + test('addChild should add new mailbox to a node', () async { + final parentMailbox = PresentationMailbox(MailboxId(Id('1'))); + final node = MailboxNode(parentMailbox); + final childMailbox = PresentationMailbox(MailboxId(Id('11')), parentId: MailboxId(Id('1'))); + final childNode = node.addChild(childMailbox); + final childMailbox2 = PresentationMailbox(MailboxId(Id('12')), parentId: MailboxId(Id('1'))); + final childNode2 = node.addChild(childMailbox2); + + expect(node.childrenItems.length, equals(2)); + expect(node.childrenItems, containsAll({childNode, childNode2})); + }); + + test('addChild add new mailbox without parentId to a node should return null', () async { + final parentMailbox = PresentationMailbox(MailboxId(Id('1'))); + final node = MailboxNode(parentMailbox); + final orphanMailbox = PresentationMailbox(MailboxId(Id('22'))); + + expect(node.addChild(orphanMailbox), null); + }); + + test('addChild add new mailbox with other parentId to a node should return null', () async { + final parentMailbox = PresentationMailbox(MailboxId(Id('1'))); + final node = MailboxNode(parentMailbox); + final orphanMailbox = PresentationMailbox(MailboxId(Id('21')), parentId: MailboxId(Id('2'))); + + expect(node.addChild(orphanMailbox), null); + }); + }); +} \ No newline at end of file diff --git a/test/features/mailbox/presentation/model/mailbox_tree_builder_test.dart b/test/features/mailbox/presentation/model/mailbox_tree_builder_test.dart new file mode 100644 index 000000000..d85627985 --- /dev/null +++ b/test/features/mailbox/presentation/model/mailbox_tree_builder_test.dart @@ -0,0 +1,212 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart'; + +void main() { + group('generate mailbox tree: ', () { + final expectedTree = MailboxTree( + MailboxNode( + PresentationMailbox(MailboxId(Id('root'))), + child: [ + MailboxNode( + PresentationMailbox(MailboxId(Id("1")), parentId: null), + child: [ + MailboxNode(PresentationMailbox(MailboxId(Id("1_1")), parentId: MailboxId(Id('1')))), + MailboxNode( + PresentationMailbox(MailboxId(Id("1_2")), parentId: MailboxId(Id('1'))), + child: [ + MailboxNode(PresentationMailbox(MailboxId(Id("1_2_1")), parentId: MailboxId(Id('1_2')))) + ] + ), + ] + ), + MailboxNode( + PresentationMailbox(MailboxId(Id("2")), parentId: null), + child: [ + MailboxNode(PresentationMailbox(MailboxId(Id("2_1")), parentId: MailboxId(Id('2'))), + child: [ + MailboxNode( + PresentationMailbox(MailboxId(Id("2_1_1")), parentId: MailboxId(Id('2_1'))), + child: [MailboxNode(PresentationMailbox(MailboxId(Id("2_1_1_1")), parentId: MailboxId(Id('2_1_1'))))] + ), + MailboxNode(PresentationMailbox(MailboxId(Id("2_1_2")), parentId: MailboxId(Id('2_1')))) + ] + ), + MailboxNode(PresentationMailbox(MailboxId(Id("2_2")), parentId: MailboxId(Id('2')))) + ] + ), + MailboxNode( + PresentationMailbox(MailboxId(Id("3")), parentId: null), + child: [ + MailboxNode( + PresentationMailbox(MailboxId(Id("3_1")), parentId: MailboxId(Id('3'))), + child: [ + MailboxNode(PresentationMailbox(MailboxId(Id("3_1_1")), parentId: MailboxId(Id('3_1')))) + ] + ), + MailboxNode( + PresentationMailbox(MailboxId(Id("3_2")), parentId: MailboxId(Id('3'))), + child: [MailboxNode(PresentationMailbox(MailboxId(Id("3_2_1")), parentId: MailboxId(Id('3_2'))))] + ) + ] + ), + ] + ) + ); + + test('list mailbox is in ordered, parent come first, then children', () async { + final testCase = [ + PresentationMailbox(MailboxId(Id("1")), parentId: null), + PresentationMailbox(MailboxId(Id("2")), parentId: null), + PresentationMailbox(MailboxId(Id("3")), parentId: null), + PresentationMailbox(MailboxId(Id("1_1")), parentId: MailboxId(Id('1'))), + PresentationMailbox(MailboxId(Id("1_2")), parentId: MailboxId(Id('1'))), + PresentationMailbox(MailboxId(Id("1_2_1")), parentId: MailboxId(Id('1_2'))), + PresentationMailbox(MailboxId(Id("2_1")), parentId: MailboxId(Id('2'))), + PresentationMailbox(MailboxId(Id("2_2")), parentId: MailboxId(Id('2'))), + PresentationMailbox(MailboxId(Id("2_1_1")), parentId: MailboxId(Id('2_1'))), + PresentationMailbox(MailboxId(Id("2_1_1_1")), parentId: MailboxId(Id('2_1_1'))), + PresentationMailbox(MailboxId(Id("2_1_2")), parentId: MailboxId(Id('2_1'))), + PresentationMailbox(MailboxId(Id("3_1")), parentId: MailboxId(Id('3'))), + PresentationMailbox(MailboxId(Id("3_2")), parentId: MailboxId(Id('3'))), + PresentationMailbox(MailboxId(Id("3_1_1")), parentId: MailboxId(Id('3_1'))), + PresentationMailbox(MailboxId(Id("3_2_1")), parentId: MailboxId(Id('3_2'))), + ]; + + final generatedTree = await TreeBuilder().generateMailboxTree(testCase); + + expect(generatedTree, equals(expectedTree)); + }); + + test('list mailbox is not in ordered, parent come first, then children, then grandpa', () async { + final testCase = [ + PresentationMailbox(MailboxId(Id("2")), parentId: null), + PresentationMailbox(MailboxId(Id("3")), parentId: null), + PresentationMailbox(MailboxId(Id("1_1")), parentId: MailboxId(Id('1'))), + PresentationMailbox(MailboxId(Id("1_2")), parentId: MailboxId(Id('1'))), + PresentationMailbox(MailboxId(Id("1_2_1")), parentId: MailboxId(Id('1_2'))), + PresentationMailbox(MailboxId(Id("1")), parentId: null), + PresentationMailbox(MailboxId(Id("2_1")), parentId: MailboxId(Id('2'))), + PresentationMailbox(MailboxId(Id("2_2")), parentId: MailboxId(Id('2'))), + PresentationMailbox(MailboxId(Id("2_1_1")), parentId: MailboxId(Id('2_1'))), + PresentationMailbox(MailboxId(Id("2_1_1_1")), parentId: MailboxId(Id('2_1_1'))), + PresentationMailbox(MailboxId(Id("2_1_2")), parentId: MailboxId(Id('2_1'))), + PresentationMailbox(MailboxId(Id("3_1")), parentId: MailboxId(Id('3'))), + PresentationMailbox(MailboxId(Id("3_2")), parentId: MailboxId(Id('3'))), + PresentationMailbox(MailboxId(Id("3_1_1")), parentId: MailboxId(Id('3_1'))), + PresentationMailbox(MailboxId(Id("3_2_1")), parentId: MailboxId(Id('3_2'))), + ]; + + final generatedTree = await TreeBuilder().generateMailboxTree(testCase); + + expect(generatedTree.root.childrenItems, containsAll(expectedTree.root.childrenItems)); + expect(generatedTree.root.childrenItems[2], equals(expectedTree.root.childrenItems[0])); + }); + + test('list mailbox is not in ordered, parent come first, then grandpa, then children', () async { + final testCase = [ + PresentationMailbox(MailboxId(Id("2")), parentId: null), + PresentationMailbox(MailboxId(Id("3")), parentId: null), + PresentationMailbox(MailboxId(Id("1_1")), parentId: MailboxId(Id('1'))), + PresentationMailbox(MailboxId(Id("1_2")), parentId: MailboxId(Id('1'))), + PresentationMailbox(MailboxId(Id("1")), parentId: null), + PresentationMailbox(MailboxId(Id("1_2_1")), parentId: MailboxId(Id('1_2'))), + PresentationMailbox(MailboxId(Id("2_1")), parentId: MailboxId(Id('2'))), + PresentationMailbox(MailboxId(Id("2_2")), parentId: MailboxId(Id('2'))), + PresentationMailbox(MailboxId(Id("2_1_1")), parentId: MailboxId(Id('2_1'))), + PresentationMailbox(MailboxId(Id("2_1_1_1")), parentId: MailboxId(Id('2_1_1'))), + PresentationMailbox(MailboxId(Id("2_1_2")), parentId: MailboxId(Id('2_1'))), + PresentationMailbox(MailboxId(Id("3_1")), parentId: MailboxId(Id('3'))), + PresentationMailbox(MailboxId(Id("3_2")), parentId: MailboxId(Id('3'))), + PresentationMailbox(MailboxId(Id("3_1_1")), parentId: MailboxId(Id('3_1'))), + PresentationMailbox(MailboxId(Id("3_2_1")), parentId: MailboxId(Id('3_2'))), + ]; + + final generatedTree = await TreeBuilder().generateMailboxTree(testCase); + + expect(generatedTree.root.childrenItems, containsAll(expectedTree.root.childrenItems)); + expect(generatedTree.root.childrenItems[2], equals(expectedTree.root.childrenItems[0])); + }); + + test('list mailbox is not in ordered, children come first, then grandpa, then parent', () async { + final testCase = [ + PresentationMailbox(MailboxId(Id("2")), parentId: null), + PresentationMailbox(MailboxId(Id("3")), parentId: null), + PresentationMailbox(MailboxId(Id("1_2_1")), parentId: MailboxId(Id('1_2'))), + PresentationMailbox(MailboxId(Id("1")), parentId: null), + PresentationMailbox(MailboxId(Id("1_1")), parentId: MailboxId(Id('1'))), + PresentationMailbox(MailboxId(Id("1_2")), parentId: MailboxId(Id('1'))), + PresentationMailbox(MailboxId(Id("2_1")), parentId: MailboxId(Id('2'))), + PresentationMailbox(MailboxId(Id("2_2")), parentId: MailboxId(Id('2'))), + PresentationMailbox(MailboxId(Id("2_1_1")), parentId: MailboxId(Id('2_1'))), + PresentationMailbox(MailboxId(Id("2_1_1_1")), parentId: MailboxId(Id('2_1_1'))), + PresentationMailbox(MailboxId(Id("2_1_2")), parentId: MailboxId(Id('2_1'))), + PresentationMailbox(MailboxId(Id("3_1")), parentId: MailboxId(Id('3'))), + PresentationMailbox(MailboxId(Id("3_2")), parentId: MailboxId(Id('3'))), + PresentationMailbox(MailboxId(Id("3_1_1")), parentId: MailboxId(Id('3_1'))), + PresentationMailbox(MailboxId(Id("3_2_1")), parentId: MailboxId(Id('3_2'))), + ]; + + final generatedTree = await TreeBuilder().generateMailboxTree(testCase); + + expect(generatedTree.root.childrenItems, containsAll(expectedTree.root.childrenItems)); + expect(generatedTree.root.childrenItems[2], equals(expectedTree.root.childrenItems[0])); + }); + + test('list mailbox is not in ordered, children come first, then parent, then grandpa', () async { + final testCase = [ + PresentationMailbox(MailboxId(Id("2")), parentId: null), + PresentationMailbox(MailboxId(Id("3")), parentId: null), + PresentationMailbox(MailboxId(Id("1_2_1")), parentId: MailboxId(Id('1_2'))), + PresentationMailbox(MailboxId(Id("1_1")), parentId: MailboxId(Id('1'))), + PresentationMailbox(MailboxId(Id("1_2")), parentId: MailboxId(Id('1'))), + PresentationMailbox(MailboxId(Id("1")), parentId: null), + PresentationMailbox(MailboxId(Id("2_1")), parentId: MailboxId(Id('2'))), + PresentationMailbox(MailboxId(Id("2_2")), parentId: MailboxId(Id('2'))), + PresentationMailbox(MailboxId(Id("2_1_1")), parentId: MailboxId(Id('2_1'))), + PresentationMailbox(MailboxId(Id("2_1_1_1")), parentId: MailboxId(Id('2_1_1'))), + PresentationMailbox(MailboxId(Id("2_1_2")), parentId: MailboxId(Id('2_1'))), + PresentationMailbox(MailboxId(Id("3_1")), parentId: MailboxId(Id('3'))), + PresentationMailbox(MailboxId(Id("3_2")), parentId: MailboxId(Id('3'))), + PresentationMailbox(MailboxId(Id("3_1_1")), parentId: MailboxId(Id('3_1'))), + PresentationMailbox(MailboxId(Id("3_2_1")), parentId: MailboxId(Id('3_2'))), + ]; + + final generatedTree = await TreeBuilder().generateMailboxTree(testCase); + + expect(generatedTree.root.childrenItems, containsAll(expectedTree.root.childrenItems)); + expect(generatedTree.root.childrenItems[2], equals(expectedTree.root.childrenItems[0])); + }); + + test('item have parent but not found in tree will become root child', () async { + final testCase = [ + PresentationMailbox(MailboxId(Id("2")), parentId: null), + PresentationMailbox(MailboxId(Id("3")), parentId: null), + PresentationMailbox(MailboxId(Id("1_2_1")), parentId: MailboxId(Id('1_2'))), + PresentationMailbox(MailboxId(Id("1_1")), parentId: MailboxId(Id('1'))), + PresentationMailbox(MailboxId(Id("1_2")), parentId: MailboxId(Id('1'))), + PresentationMailbox(MailboxId(Id("1")), parentId: null), + PresentationMailbox(MailboxId(Id("2_1")), parentId: MailboxId(Id('2'))), + PresentationMailbox(MailboxId(Id("2_2")), parentId: MailboxId(Id('2'))), + PresentationMailbox(MailboxId(Id("2_1_1")), parentId: MailboxId(Id('2_1'))), + PresentationMailbox(MailboxId(Id("2_1_1_1")), parentId: MailboxId(Id('2_1_1'))), + PresentationMailbox(MailboxId(Id("2_1_2")), parentId: MailboxId(Id('2_1'))), + PresentationMailbox(MailboxId(Id("3_1")), parentId: MailboxId(Id('3'))), + PresentationMailbox(MailboxId(Id("3_2")), parentId: MailboxId(Id('3'))), + PresentationMailbox(MailboxId(Id("3_1_1")), parentId: MailboxId(Id('3_1'))), + PresentationMailbox(MailboxId(Id("3_2_1")), parentId: MailboxId(Id('3_2'))), + PresentationMailbox(MailboxId(Id("e3_2_1")), parentId: MailboxId(Id('id42'))) + ]; + + final generatedTree = await TreeBuilder().generateMailboxTree(testCase); + + expect(generatedTree.root.childrenItems.length, equals(4)); + expect(generatedTree.root.childrenItems, + contains(MailboxNode(PresentationMailbox(MailboxId(Id("e3_2_1")), parentId: MailboxId(Id('id42')))))); + }); + }); +} \ No newline at end of file diff --git a/test/features/mailbox/presentation/model/mailbox_tree_test.dart b/test/features/mailbox/presentation/model/mailbox_tree_test.dart new file mode 100644 index 000000000..7cbb22d91 --- /dev/null +++ b/test/features/mailbox/presentation/model/mailbox_tree_test.dart @@ -0,0 +1,101 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; + +void main() { + group('test findNode in mailbox tree', () { + late MailboxTree? tree; + final level1Node = PresentationMailbox(MailboxId(Id('1'))); + final level1Node1 = PresentationMailbox(MailboxId(Id('1_1'))); + final level1Node2 = PresentationMailbox(MailboxId(Id('1_2'))); + final level2Node1 = PresentationMailbox(MailboxId(Id('2_1')), parentId: MailboxId(Id('1'))); + final level3Node1 = PresentationMailbox(MailboxId(Id('3_1')), parentId: MailboxId(Id('2_1'))); + + setUp(() { + tree = MailboxTree(MailboxNode.root()); + tree!.insertNode(level1Node); + tree!.insertNode(level1Node1); + tree!.insertNode(level1Node2); + tree!.insertNode(level2Node1); + tree!.insertNode(level3Node1); + }); + + test('findNode should find a node in tree at level 1', () { + final node1 = tree!.findNode(MailboxId(Id('1'))); + expect(node1!.item, equals(level1Node)); + }); + + test('findNode should find a node in tree at level 2', () { + final node1 = tree!.findNode(MailboxId(Id('1_2'))); + expect(node1!.item, equals(level1Node2)); + }); + + test('findNode should find a leaf in tree', () { + final node1 = tree!.findNode(MailboxId(Id('3_1'))); + expect(node1!.item, equals(level3Node1)); + }); + + test('findNode should find a node which have children in tree', () { + final node1 = tree!.findNode(MailboxId(Id('2_1'))); + expect(node1!.item, equals(level2Node1)); + expect(node1.childrenItems.length, equals(1)); + expect(node1.childrenItems[0].item, equals(level3Node1)); + }); + + test('findNode should not find a node not in tree', () { + expect(tree!.findNode(MailboxId(Id('4_1'))), null); + }); + + tearDown(() { + tree = null; + }); + }); + + group('test insertNode in mailbox tree', () { + + test('insertNode should insert a node without parent at level 1', () { + final tree = MailboxTree(MailboxNode.root()); + final mailbox = PresentationMailbox(MailboxId(Id("1"))); + final insertedNode = tree.insertNode(mailbox); + + expect(tree.root.childrenItems, contains(insertedNode)); + }); + + test('insertNode should insert a node to correctly parent', () { + final tree = MailboxTree(MailboxNode.root()); + final level1Node = PresentationMailbox(MailboxId(Id('1'))); + final level1Node1 = PresentationMailbox(MailboxId(Id('1_1'))); + final level1Node2 = PresentationMailbox(MailboxId(Id('1_2'))); + tree.insertNode(level1Node); + tree.insertNode(level1Node1); + tree.insertNode(level1Node2); + + final insertedNode = tree.insertNode(PresentationMailbox(MailboxId(Id('2_1_2')), parentId: MailboxId(Id('1_2')))); + + final foundLevel1Node2 = tree.findNode(MailboxId(Id('1_2'))); + expect(foundLevel1Node2?.childrenItems.length, equals(1)); + expect(foundLevel1Node2?.childrenItems, contains(insertedNode)); + }); + + test('insertNode should insert a node to root if parent not found', () { + final tree = MailboxTree(MailboxNode.root()); + final level1Node = PresentationMailbox(MailboxId(Id('1'))); + final level1Node1 = PresentationMailbox(MailboxId(Id('1_1'))); + final level1Node2 = PresentationMailbox(MailboxId(Id('1_2'))); + tree.insertNode(level1Node); + tree.insertNode(level1Node1); + tree.insertNode(level1Node2); + + final insertedNode = tree.insertNode(PresentationMailbox(MailboxId(Id('1_1_1_exp')), parentId: MailboxId(Id('e1_2')))); + + expect(tree.root.childrenItems.length, equals(4)); + expect(tree.root.childrenItems.map((childNode) => childNode.item).toList(), + containsAll({level1Node, level1Node2, level1Node1, insertedNode!.item})); + }); + }); +} \ No newline at end of file