TF-1427 Implement search mailbox for mobile/tablet

This commit is contained in:
dab246
2023-02-10 03:11:35 +07:00
committed by Dat Vu
parent 181820ed45
commit 07e0401797
11 changed files with 316 additions and 64 deletions
@@ -560,8 +560,12 @@ class MailboxController extends BaseMailboxController with MailboxActionHandlerM
}
void openSearchViewAction(BuildContext context) {
SearchMailboxBindings().dependencies();
mailboxDashBoardController.searchMailboxActivated.value = true;
if (BuildUtils.isWeb) {
SearchMailboxBindings().dependencies();
mailboxDashBoardController.searchMailboxActivated.value = true;
} else {
push(AppRoutes.searchMailbox);
}
closeMailboxScreen(context);
}
@@ -191,7 +191,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
: const SizedBox.shrink()
),
Obx(() => controller.searchMailboxActivated.value == true && !responsiveUtils.isWebDesktop(context)
? const SearchMailboxView()
? const PointerInterceptor(child: SearchMailboxView())
: const SizedBox.shrink()
),
_buildDownloadTaskStateWidget(),
@@ -1,6 +1,18 @@
import 'package:core/data/model/source_type/data_source_type.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/base_bindings.dart';
import 'package:tmail_ui_user/features/caching/state_cache_client.dart';
import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasource.dart';
import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart';
import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart';
import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart';
import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/state_datasource_impl.dart';
import 'package:tmail_ui_user/features/mailbox/data/local/mailbox_cache_manager.dart';
import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_api.dart';
import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_isolate_worker.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/delete_multiple_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/mailbox/domain/usecases/move_mailbox_interactor.dart';
@@ -10,6 +22,8 @@ import 'package:tmail_ui_user/features/mailbox/domain/usecases/search_mailbox_in
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart';
import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart';
import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_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 SearchMailboxBindings extends BaseBindings {
@@ -28,10 +42,27 @@ class SearchMailboxBindings extends BaseBindings {
}
@override
void bindingsDataSource() {}
void bindingsDataSource() {
Get.lazyPut<MailboxDataSource>(() => Get.find<MailboxDataSourceImpl>());
Get.lazyPut<StateDataSource>(() => Get.find<StateDataSourceImpl>());
}
@override
void bindingsDataSourceImpl() {}
void bindingsDataSourceImpl() {
Get.lazyPut(() => MailboxDataSourceImpl(
Get.find<MailboxAPI>(),
Get.find<MailboxIsolateWorker>(),
Get.find<RemoteExceptionThrower>()
));
Get.lazyPut(() => MailboxCacheDataSourceImpl(
Get.find<MailboxCacheManager>(),
Get.find<CacheExceptionThrower>()
));
Get.lazyPut(() => StateDataSourceImpl(
Get.find<StateCacheClient>(),
Get.find<CacheExceptionThrower>()
));
}
@override
void bindingsInteractor() {
@@ -39,10 +70,20 @@ class SearchMailboxBindings extends BaseBindings {
}
@override
void bindingsRepository() {}
void bindingsRepository() {
Get.lazyPut<MailboxRepository>(() => Get.find<MailboxRepositoryImpl>());
}
@override
void bindingsRepositoryImpl() {}
void bindingsRepositoryImpl() {
Get.lazyPut(() => MailboxRepositoryImpl(
{
DataSourceType.network: Get.find<MailboxDataSource>(),
DataSourceType.local: Get.find<MailboxCacheDataSourceImpl>()
},
Get.find<StateDataSource>()
));
}
void disposeBindings() {
Get.delete<SearchMailboxController>();
@@ -1,5 +1,4 @@
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/state/failure.dart';
@@ -7,6 +6,7 @@ import 'package:core/presentation/state/success.dart';
import 'package:core/presentation/utils/app_toast.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/build_utils.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
@@ -168,6 +168,11 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa
}
}
void handleSearchButtonPressed(BuildContext context) {
FocusScope.of(context).unfocus();
searchMailboxAction();
}
void _handleSearchMailboxSuccess(SearchMailboxSuccess success) {
final mailboxesSearchedWithPath = findMailboxPath(success.mailboxesSearched);
listMailboxSearched.value = mailboxesSearchedWithPath;
@@ -186,11 +191,20 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa
}
void submitSearchAction(BuildContext context, String query) {
FocusScope.of(context).unfocus();
currentSearchQuery.value = query;
searchMailboxAction();
}
void handleMailboxAction(BuildContext context, MailboxActions actions, PresentationMailbox mailbox) {
popBack();
void handleMailboxAction(
BuildContext context,
MailboxActions actions,
PresentationMailbox mailbox,
{bool isFocusedMenu = false}
) {
if (!isFocusedMenu) {
popBack();
}
switch(actions) {
case MailboxActions.openInNewTab:
@@ -365,6 +379,7 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa
}
void openMailboxAction(BuildContext context, PresentationMailbox mailbox) {
FocusScope.of(context).unfocus();
dashboardController.openMailboxAction(context, mailbox);
if (!responsiveUtils.isWebDesktop(context)) {
@@ -379,9 +394,14 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa
}
void closeSearchView(BuildContext context) {
clearAllTextInputSearchForm();
dashboardController.searchMailboxActivated.value = false;
SearchMailboxBindings().disposeBindings();
FocusScope.of(context).unfocus();
if (BuildUtils.isWeb) {
dashboardController.searchMailboxActivated.value = false;
clearAllTextInputSearchForm();
SearchMailboxBindings().disposeBindings();
} else {
popBack();
}
}
@override
@@ -2,8 +2,10 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/views/button/icon_button_web.dart';
import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/utils/build_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:focused_menu_custom/modals.dart';
import 'package:get/get.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
@@ -32,24 +34,30 @@ class SearchMailboxView extends GetWidget<SearchMailboxController>
return Scaffold(
body: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Container(
color: backgroundColor ?? Colors.white,
child: Column(children: [
Container(
color: Colors.transparent,
padding: SearchMailboxUtils.getPaddingAppBar(context, controller.responsiveUtils),
child: _buildSearchInputForm(context)
),
if (!controller.responsiveUtils.isWebDesktop(context))
const Divider(color: AppColor.colorDividerComposer, height: 1),
_buildLoadingView(),
Expanded(child: _buildMailboxListView(context))
]),
),
child: BuildUtils.isWeb
? _buildSearchBody(context)
: SafeArea(child: _buildSearchBody(context)),
),
);
}
Widget _buildSearchBody(BuildContext context) {
return Container(
color: backgroundColor ?? Colors.white,
child: Column(children: [
Container(
color: Colors.transparent,
padding: SearchMailboxUtils.getPaddingAppBar(context, controller.responsiveUtils),
child: _buildSearchInputForm(context)
),
if (!controller.responsiveUtils.isWebDesktop(context))
const Divider(color: AppColor.colorDividerComposer, height: 1),
_buildLoadingView(),
Expanded(child: _buildMailboxListView(context))
]),
);
}
Widget _buildLoadingView() {
return Obx(() => controller.viewState.value.fold(
(failure) => const SizedBox.shrink(),
@@ -99,7 +107,7 @@ class SearchMailboxView extends GetWidget<SearchMailboxController>
fit: BoxFit.fill
),
tooltip: AppLocalizations.of(context).search,
onTap: controller.searchMailboxAction
onTap: () => controller.handleSearchButtonPressed(context)
),
),
Expanded(child: _buildTextFieldSearchInput(context)),
@@ -132,6 +140,7 @@ class SearchMailboxView extends GetWidget<SearchMailboxController>
return (TextFieldBuilder()
..onChange(controller.onTextSearchChange)
..textInputAction(TextInputAction.search)
..autoFocus(true)
..addController(controller.textInputSearchController)
..textStyle(const TextStyle(
color: Colors.black,
@@ -166,46 +175,121 @@ class SearchMailboxView extends GetWidget<SearchMailboxController>
shrinkWrap: true,
primary: false,
itemBuilder: (context, index) {
return MailboxSearchedItemBuilder(
controller.imagePaths,
controller.responsiveUtils,
controller.listMailboxSearched[index],
onDragEmailToMailboxAccepted: controller.dashboardController.dragSelectedMultipleEmailToMailboxAction,
onClickOpenMailboxAction: (mailbox) => controller.openMailboxAction(context, mailbox),
onClickOpenMenuMailboxAction: (position, mailbox) => _openMailboxMenuAction(context, mailbox, position: position),
onLongPressMailboxAction: (mailbox) => _openMailboxMenuAction(context, mailbox),
);
return LayoutBuilder(builder: (context, constraints) {
final mailboxCurrent = controller.listMailboxSearched[index];
return MailboxSearchedItemBuilder(
controller.imagePaths,
controller.responsiveUtils,
mailboxCurrent,
maxWidth: constraints.maxWidth,
onDragEmailToMailboxAccepted: controller.dashboardController.dragSelectedMultipleEmailToMailboxAction,
onClickOpenMailboxAction: (mailbox) => controller.openMailboxAction(context, mailbox),
onClickOpenMenuMailboxAction: (position, mailbox) => _openMailboxMenuAction(context, mailbox, position: position),
onLongPressMailboxAction: (mailbox) => _openMailboxMenuAction(context, mailbox),
listPopupMenuItemAction: _listPopupMenuItemAction(context, mailboxCurrent)
);
});
}
);
});
}
MailboxActions mailboxActionForSpam() {
List<FocusedMenuItem> _listPopupMenuItemAction(BuildContext context, PresentationMailbox mailbox) {
final mailboxActionsSupported = mailbox.isSubscribed?.value == true
? _listActionForMailboxSubscribed(mailbox)
: _listActionForMailboxUnsubscribed();
final listContextMenuItemAction = mailboxActionsSupported
.map((action) => ContextMenuItemMailboxAction(action, action.getContextMenuItemState(mailbox)))
.toList();
return listContextMenuItemAction
.map((action) => _mailboxFocusedMenuItem(context, action, mailbox))
.toList();
}
FocusedMenuItem _mailboxFocusedMenuItem(
BuildContext context,
ContextMenuItemMailboxAction contextMenuItem,
PresentationMailbox mailbox
) {
return FocusedMenuItem(
backgroundColor: Colors.white,
onPressed: () => controller.handleMailboxAction(
context,
contextMenuItem.action,
mailbox,
isFocusedMenu: true
),
title: Expanded(
child: AbsorbPointer(
absorbing: !contextMenuItem.isActivated,
child: Opacity(
opacity: contextMenuItem.isActivated ? 1.0 : 0.3,
child: Row(children: [
SvgPicture.asset(
contextMenuItem.action.getContextMenuIcon(controller.imagePaths),
width: 24,
height: 24,
fit: BoxFit.fill,
color: contextMenuItem.action.getColorContextMenuIcon()
),
const SizedBox(width: 12),
Expanded(child: Text(
contextMenuItem.action.getTitleContextMenu(context),
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 16,
color: contextMenuItem.action.getColorContextMenuTitle()
)
)),
]),
),
),
)
);
}
MailboxActions _mailboxActionForSpam() {
return controller.dashboardController.enableSpamReport
? MailboxActions.disableSpamReport
: MailboxActions.enableSpamReport;
}
List<MailboxActions> _listActionForMailboxUnsubscribed() {
return [
MailboxActions.markAsRead
];
}
List<MailboxActions> _listActionForMailboxSubscribed(PresentationMailbox mailbox) {
return [
if (BuildUtils.isWeb)
MailboxActions.openInNewTab,
if (mailbox.isSpam)
_mailboxActionForSpam(),
MailboxActions.markAsRead,
MailboxActions.move,
MailboxActions.rename,
MailboxActions.delete,
MailboxActions.disableMailbox
];
}
void _openMailboxMenuAction(
BuildContext context,
PresentationMailbox mailbox,
{RelativeRect? position}
) {
final mailboxActionsSupported = [
MailboxActions.openInNewTab,
if (mailbox.isPersonal && mailbox.isSpam)
mailboxActionForSpam(),
MailboxActions.markAsRead,
MailboxActions.move,
MailboxActions.rename,
MailboxActions.delete
];
final mailboxActionsSupported = mailbox.isSubscribed?.value == true
? _listActionForMailboxSubscribed(mailbox)
: _listActionForMailboxUnsubscribed();
final listContextMenuItemAction = mailboxActionsSupported
.map((action) => ContextMenuItemMailboxAction(action, action.getContextMenuItemState(mailbox)))
.toList();
if (controller.responsiveUtils.isScreenWithShortestSide(context)) {
if (controller.responsiveUtils.isScreenWithShortestSide(context) || position == null) {
controller.openContextMenuAction(
context,
_bottomSheetMailboxActionTiles(
@@ -283,7 +367,8 @@ class SearchMailboxView extends GetWidget<SearchMailboxController>
absorbing: !contextMenuItem.isActivated,
child: Opacity(
opacity: contextMenuItem.isActivated ? 1.0 : 0.3,
child: popupItem(contextMenuItem.action.getContextMenuIcon(controller.imagePaths),
child: popupItem(
contextMenuItem.action.getContextMenuIcon(controller.imagePaths),
contextMenuItem.action.getTitleContextMenu(context),
colorIcon: contextMenuItem.action.getColorContextMenuIcon(),
iconSize: 24,
@@ -7,9 +7,9 @@ class SearchMailboxUtils {
return const EdgeInsets.symmetric(vertical: 8, horizontal: 16);
} else {
if (responsiveUtils.isScreenWithShortestSide(context)) {
return const EdgeInsets.symmetric(horizontal: 16, vertical: 8);
return const EdgeInsets.symmetric(horizontal: 12, vertical: 8);
} else {
return const EdgeInsets.symmetric(horizontal: 32, vertical: 8);
return const EdgeInsets.symmetric(horizontal: 28, vertical: 8);
}
}
}
@@ -19,9 +19,9 @@ class SearchMailboxUtils {
return const EdgeInsets.only(left: 5, right: 2);
} else {
if (responsiveUtils.isScreenWithShortestSide(context)) {
return const EdgeInsets.only(left: 10, right: 16);
return const EdgeInsets.only(left: 6, right: 14);
} else {
return const EdgeInsets.only(left: 10, right: 16);
return const EdgeInsets.only(left: 8, right: 16);
}
}
}
@@ -53,4 +53,12 @@ class SearchMailboxUtils {
}
}
}
static EdgeInsets getPaddingItemListView(BuildContext context, ResponsiveUtils responsiveUtils) {
if (responsiveUtils.isWebDesktop(context)) {
return const EdgeInsets.all(8);
} else {
return const EdgeInsets.symmetric(horizontal: 8, vertical: 10);
}
}
}
@@ -2,23 +2,29 @@ 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:core/presentation/utils/style_utils.dart';
import 'package:core/presentation/views/responsive/responsive_widget.dart';
import 'package:core/utils/build_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:focused_menu_custom/focused_menu.dart';
import 'package:focused_menu_custom/modals.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/mailbox/domain/extensions/presentation_mailbox_extension.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/utils/mailbox_method_action_define.dart';
import 'package:tmail_ui_user/features/search/mailbox/presentation/utils/search_mailbox_utils.dart';
class MailboxSearchedItemBuilder extends StatefulWidget {
final PresentationMailbox _presentationMailbox;
final ImagePaths _imagePaths;
final ResponsiveUtils _responsiveUtils;
final double? maxWidth;
final OnClickOpenMailboxAction? onClickOpenMailboxAction;
final OnClickOpenMenuMailboxAction? onClickOpenMenuMailboxAction;
final OnDragEmailToMailboxAccepted? onDragEmailToMailboxAccepted;
final OnLongPressMailboxAction? onLongPressMailboxAction;
final List<FocusedMenuItem>? listPopupMenuItemAction;
const MailboxSearchedItemBuilder(
this._imagePaths,
@@ -26,10 +32,12 @@ class MailboxSearchedItemBuilder extends StatefulWidget {
this._presentationMailbox,
{
Key? key,
this.maxWidth,
this.onClickOpenMailboxAction,
this.onClickOpenMenuMailboxAction,
this.onDragEmailToMailboxAccepted,
this.onLongPressMailboxAction
this.onLongPressMailboxAction,
this.listPopupMenuItemAction
}
) : super(key: key);
@@ -66,7 +74,7 @@ class _MailboxSearchedItemBuilderState extends State<MailboxSearchedItemBuilder>
borderRadius: BorderRadius.circular(8),
color: getBackgroundColorItem(context)
),
padding: const EdgeInsets.all(8),
padding: SearchMailboxUtils.getPaddingItemListView(context, widget._responsiveUtils),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -88,17 +96,83 @@ class _MailboxSearchedItemBuilderState extends State<MailboxSearchedItemBuilder>
)
);
} else {
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
onTap: _onTapMailboxAction,
onLongPress: _onLongPressMailboxAction,
leading: _buildMailboxIcon(),
title: _buildTitleItem(),
subtitle: _buildSubtitleItem()
return ResponsiveWidget(
responsiveUtils: widget._responsiveUtils,
mobile: _buildMailboxItemMobile(),
tablet: _buildMailboxItemTablet()
);
}
}
Widget _buildMailboxItemTablet() {
return FocusedMenuHolder(
onPressed: () {},
menuWidth: widget.maxWidth,
blurBackgroundColor: AppColor.colorInputBorderCreateMailbox,
blurSize: 0.0,
duration: const Duration(milliseconds: 100),
menuOffset: 8.0,
animateMenuItems: true,
menuItems: widget.listPopupMenuItemAction ?? [],
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: _onTapMailboxAction,
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: Padding(
padding: SearchMailboxUtils.getPaddingItemListView(context, widget._responsiveUtils),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildMailboxIcon(),
Expanded(child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTitleItem(),
_buildSubtitleItem()
]
)
))
]
),
)
),
),
);
}
Widget _buildMailboxItemMobile() {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: _onTapMailboxAction,
onLongPress: _onLongPressMailboxAction,
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: Padding(
padding: SearchMailboxUtils.getPaddingItemListView(context, widget._responsiveUtils),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildMailboxIcon(),
Expanded(child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTitleItem(),
_buildSubtitleItem()
]
)
))
]
),
)
),
);
}
void _onTapMailboxAction() {
if (widget._presentationMailbox.isSubscribed?.value == true) {
widget.onClickOpenMailboxAction?.call(widget._presentationMailbox);
+11 -1
View File
@@ -21,11 +21,13 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_da
if (dart.library.html) 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart' deferred as mailbox_dashboard;
import 'package:tmail_ui_user/features/manage_account/presentation/manage_account_dashboard_bindings.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/manage_account_dashboard_view.dart' deferred as manage_account_dashboard;
import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_bindings.dart';
import 'package:tmail_ui_user/features/session/presentation/session_page_bindings.dart';
import 'package:tmail_ui_user/features/session/presentation/session_view.dart' deferred as session;
import 'package:tmail_ui_user/features/unknown_route_page/unknown_route_page_view.dart';
import 'package:tmail_ui_user/main/pages/deferred_widget.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_view.dart' deferred as search_mailbox_view;
class AppPages {
static final pages = [
@@ -96,7 +98,15 @@ class AppPages {
page: () => DeferredWidget(
rules_filter_creator.loadLibrary,
() => rules_filter_creator.RuleFilterCreatorView()),
binding: RulesFilterCreatorBindings())
binding: RulesFilterCreatorBindings()),
GetPage(
name: AppRoutes.searchMailbox,
opaque: false,
page: () => DeferredWidget(
search_mailbox_view.loadLibrary,
() => search_mailbox_view.SearchMailboxView()
),
binding: SearchMailboxBindings()),
]
];
+1
View File
@@ -13,4 +13,5 @@ abstract class AppRoutes {
static const rulesFilterCreator = '/rules_filter_creator';
static const emailsForwardCreator = '/emails_forward_creator';
static const unknownRoutePage = '/notfound';
static const searchMailbox = '/search_mailbox';
}
+7
View File
@@ -731,6 +731,13 @@ packages:
url: "https://github.com/ifnyas/focus_detector.git"
source: git
version: "2.0.1"
focused_menu_custom:
dependency: "direct main"
description:
name: focused_menu_custom
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
forward:
dependency: "direct main"
description:
+2
View File
@@ -214,6 +214,8 @@ dependencies:
fading_edge_scrollview: 3.0.0
focused_menu_custom: 1.2.0
dev_dependencies:
flutter_test:
sdk: flutter