diff --git a/analysis_options.yaml b/analysis_options.yaml index a4ccf49..650dc2d 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -62,6 +62,7 @@ linter: prefer_iterable_whereType: true prefer_null_aware_operators: true prefer_spread_collections: true + prefer_const_constructors: true recursive_getters: true type_init_formals: true unnecessary_constructor_name: true diff --git a/assets/icons/arrow-down.svg b/assets/icons/arrow-down.svg new file mode 100644 index 0000000..64a3e86 --- /dev/null +++ b/assets/icons/arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/arrow-up.svg b/assets/icons/arrow-up.svg new file mode 100644 index 0000000..f080b70 --- /dev/null +++ b/assets/icons/arrow-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/clipboard-text.svg b/assets/icons/clipboard-text.svg new file mode 100644 index 0000000..7ee1569 --- /dev/null +++ b/assets/icons/clipboard-text.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/danger-fill.svg b/assets/icons/danger-fill.svg new file mode 100644 index 0000000..15c578f --- /dev/null +++ b/assets/icons/danger-fill.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/upload_file.svg b/assets/icons/upload_file.svg new file mode 100644 index 0000000..a414ea0 --- /dev/null +++ b/assets/icons/upload_file.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lib/core/config/dependencies.dart b/lib/core/config/dependencies.dart index 5c3bdfb..ec4f903 100644 --- a/lib/core/config/dependencies.dart +++ b/lib/core/config/dependencies.dart @@ -11,6 +11,7 @@ import 'package:tm_app/data/services/home_service.dart'; import 'package:tm_app/data/services/liked_tenders_service.dart'; import 'package:tm_app/data/services/tender_detail_service.dart'; import 'package:tm_app/data/services/your_tenders_service.dart'; +import 'package:tm_app/view_models/final_completion_of_documents_view_model.dart'; import 'package:tm_app/view_models/liked_tenders_view_model.dart'; import 'package:tm_app/view_models/tender_detail_view_model.dart'; import 'package:tm_app/view_models/tenders_view_model.dart'; @@ -142,6 +143,9 @@ List get viewModels { authViewModel: context.read(), ), ), + ChangeNotifierProvider( + create: (context) => FinalCompletionOfDocumentsViewModel(), + ), ]; } diff --git a/lib/core/constants/assets.dart b/lib/core/constants/assets.dart index 723fbe1..e0088c3 100644 --- a/lib/core/constants/assets.dart +++ b/lib/core/constants/assets.dart @@ -49,6 +49,7 @@ class AssetsManager { static const arrowRight = 'assets/icons/arrow-right.svg'; static const notification = 'assets/icons/notification.svg'; static const danger = 'assets/icons/danger.svg'; + static const calendar = 'assets/icons/calendar.svg'; static const menu = 'assets/icons/menu.svg'; static const thumbLike = 'assets/icons/thumb_like.svg'; @@ -67,5 +68,13 @@ class AssetsManager { //Liked tenders static const trash = 'assets/icons/trash.svg'; static const arrowDownSmall = 'assets/icons/arrow_down_small.svg'; + static const filter = 'assets/icons/filter.svg'; + + //final completion of documents + static const clipboardText = 'assets/icons/clipboard-text.svg'; + static const dangerFill = 'assets/icons/danger-fill.svg'; + static const arrowDown = 'assets/icons/arrow-down.svg'; + static const arrowUp = 'assets/icons/arrow-up.svg'; + static const uploadFile = 'assets/icons/upload_file.svg'; } diff --git a/lib/core/routes/app_routes.dart b/lib/core/routes/app_routes.dart index 401d615..85bc7ce 100644 --- a/lib/core/routes/app_routes.dart +++ b/lib/core/routes/app_routes.dart @@ -5,6 +5,7 @@ import 'package:tm_app/views/home/pages/home_screen.dart'; import 'package:tm_app/views/liked_tenders/pages/liked_tenders_screen.dart'; import 'package:tm_app/views/login/pages/login_screen.dart'; +import '../../views/final_completion_of_documents/pages/final_completion_of_documents_screen.dart'; import '../../views/forgot_password/pages/forgot_password_screen.dart'; import '../../views/notification/pages/notification_screen.dart'; import '../../views/profile/pages/profile_screen.dart'; @@ -168,7 +169,20 @@ class YourTendersRouteData extends GoRouteData with _$YourTendersRouteData { @override Widget build(BuildContext context, GoRouterState state) { - return YourTendersScreen(); + return const YourTendersScreen(); + } +} + +@TypedGoRoute( + path: '/final_completion_of_documents', +) +class FinalCompletionOfDocumentsRouteData extends GoRouteData + with _$FinalCompletionOfDocumentsRouteData { + const FinalCompletionOfDocumentsRouteData(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return const FinalCompletionOfDocumentsScreen(); } } @@ -178,7 +192,7 @@ class LikedTendersRouteData extends GoRouteData with _$LikedTendersRouteData { @override Widget build(BuildContext context, GoRouterState state) { - return LikedTendersScreen(); + return const LikedTendersScreen(); } } @@ -200,6 +214,6 @@ class ForgotPasswordRouteData extends GoRouteData @override Widget build(BuildContext context, GoRouterState state) { - return ForgotPasswordScreen(); + return const ForgotPasswordScreen(); } } diff --git a/lib/core/routes/app_routes.g.dart b/lib/core/routes/app_routes.g.dart index 225597b..126fd4b 100644 --- a/lib/core/routes/app_routes.g.dart +++ b/lib/core/routes/app_routes.g.dart @@ -10,6 +10,7 @@ List get $appRoutes => [ $loginScreenRoute, $appShellRouteData, $yourTendersRouteData, + $finalCompletionOfDocumentsRouteData, $likedTendersRouteData, $tenderDetailRouteData, $forgotPasswordRouteData, @@ -201,6 +202,34 @@ mixin _$YourTendersRouteData on GoRouteData { void replace(BuildContext context) => context.replace(location); } +RouteBase get $finalCompletionOfDocumentsRouteData => GoRouteData.$route( + path: '/final_completion_of_documents', + + factory: _$FinalCompletionOfDocumentsRouteData._fromState, +); + +mixin _$FinalCompletionOfDocumentsRouteData on GoRouteData { + static FinalCompletionOfDocumentsRouteData _fromState(GoRouterState state) => + const FinalCompletionOfDocumentsRouteData(); + + @override + String get location => + GoRouteData.$location('/final_completion_of_documents'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + RouteBase get $likedTendersRouteData => GoRouteData.$route( path: '/like-tenders', diff --git a/lib/core/theme/colors.dart b/lib/core/theme/colors.dart index 0d87b8e..4af3f2a 100644 --- a/lib/core/theme/colors.dart +++ b/lib/core/theme/colors.dart @@ -159,4 +159,10 @@ class AppColors { static const Color lightBlue = Color(0xFFe8ecfc); static const Color cyanAqua = Color(0xFFd6f4f8); static const Color cyanTeal = Color(0xFF24A6B4); + static const Color yellow1 = Color(0xFFFFF7EE); + static const Color yellow2 = Color(0xFFFFE4C9); + static const Color yellow3 = Color(0xFFF8EAD6); + static const Color yellow4 = Color(0xFFC07A25); + static const Color secondaryborder = Color(0xFFCDCDCD); + static const Color secondaryborder2 = Color(0xFFDADADA); } diff --git a/lib/view_models/final_completion_of_documents_view_model.dart b/lib/view_models/final_completion_of_documents_view_model.dart new file mode 100644 index 0000000..1b7608f --- /dev/null +++ b/lib/view_models/final_completion_of_documents_view_model.dart @@ -0,0 +1,16 @@ +import 'dart:io'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; + +class FinalCompletionOfDocumentsViewModel extends ChangeNotifier { + final FilePicker _filePicker = FilePicker.platform; + File? _selectedFile; + File? get selectedFile => _selectedFile; + + Future pickFile() async { + final result = await _filePicker.pickFiles(); + _selectedFile = File(result?.files.single.path ?? ''); + notifyListeners(); + } +} diff --git a/lib/views/final_completion_of_documents/pages/final_completion_of_documents_screen.dart b/lib/views/final_completion_of_documents/pages/final_completion_of_documents_screen.dart new file mode 100644 index 0000000..8256cd2 --- /dev/null +++ b/lib/views/final_completion_of_documents/pages/final_completion_of_documents_screen.dart @@ -0,0 +1,330 @@ +import 'package:dotted_border/dotted_border.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; +import 'package:tm_app/core/constants/assets.dart'; +import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/view_models/final_completion_of_documents_view_model.dart'; +import 'package:tm_app/views/final_completion_of_documents/strings/final_completion_of_documents_strings.dart'; +import 'package:tm_app/views/final_completion_of_documents/widgets/comment_box.dart'; +import 'package:tm_app/views/login/widgets/widgets.dart'; +import 'package:tm_app/views/shared/desktop_navigation_widget.dart'; + +import '../../shared/main_drop_down.dart'; + +class FinalCompletionOfDocumentsScreen extends StatelessWidget { + const FinalCompletionOfDocumentsScreen({super.key}); + + @override + Widget build(BuildContext context) { + SizeConfig.init(context); + return Scaffold( + body: Consumer( + builder: + (context, viewModel, child) => Column( + children: [ + const DesktopNavigationWidget(currentIndex: 1), + const SizedBox(height: 64.0), + Expanded( + child: SizedBox( + width: 740, + child: SingleChildScrollView( + child: Column( + children: [ + Row( + children: [ + InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () => context.pop(), + child: SvgPicture.asset( + AssetsManager.arrowLeft, + ), + ), + const SizedBox(width: 8.0), + Text( + FinalCompletionOfDocumentsStrings + .completionOfDocuments, + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.bold, + color: AppColors.grey70, + ), + ), + ], + ), + const SizedBox(height: 32.0), + const CommentBox(), + const SizedBox(height: 32.0), + FormCard(viewModel: viewModel), + const SizedBox(height: 40.0), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + BaseButton( + isEnabled: true, + onPressed: () {}, + width: 176, + backgroundColor: AppColors.primary10, + textColor: AppColors.mainBlue, + text: FinalCompletionOfDocumentsStrings.back, + ), + const SizedBox(width: 16.0), + BaseButton( + isEnabled: true, + onPressed: () {}, + width: 176, + text: FinalCompletionOfDocumentsStrings.next, + ), + ], + ), + const SizedBox(height: 35.0), + ], + ), + ), + ), + ), + ], + ), + ), + ); + } +} + +class FormCard extends StatelessWidget { + const FormCard({required this.viewModel, super.key}); + final FinalCompletionOfDocumentsViewModel viewModel; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: Border.all(color: AppColors.borderColor), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + height: 64.0, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: Border.all(color: AppColors.borderColor), + ), + alignment: AlignmentDirectional.centerStart, + child: Container( + width: 118, + height: 32, + decoration: BoxDecoration( + color: AppColors.yellow3, + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + child: const Text( + '50% Completed', + style: TextStyle(color: AppColors.yellow4), + ), + ), + ), + const SizedBox(height: 48.0), + Text( + FinalCompletionOfDocumentsStrings.numberOfServersListedTitle, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w400, + color: AppColors.grey60, + ), + ), + const SizedBox(height: 12.0), + Text( + '12 servers across 2 data centers', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w400, + color: AppColors.grey80, + ), + ), + const SizedBox(height: 24), + Divider(color: AppColors.borderColor), + const SizedBox(height: 24), + Text( + FinalCompletionOfDocumentsStrings + .technicalTeamOrganizationChartUploadedTitle, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w400, + color: AppColors.grey60, + ), + ), + const SizedBox(height: 12.0), + Text( + 'PDF submitted with full team roles and structure', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w400, + color: AppColors.grey80, + ), + ), + const SizedBox(height: 24), + Divider(color: AppColors.borderColor), + const SizedBox(height: 48), + _titleDropDownRow( + text: + FinalCompletionOfDocumentsStrings + .describeInfrastructureCapabilitiestitle, + ), + const SizedBox(height: 16), + _uploadFileCard(viewModel: viewModel), + const SizedBox(height: 40), + _titleDropDownRow( + text: + FinalCompletionOfDocumentsStrings + .uploadTechnicalTeamOrgChartTitle, + ), + const SizedBox(height: 16), + _describeYourTeamTextField(), + const SizedBox(height: 48), + _titleDropDownRow( + text: + FinalCompletionOfDocumentsStrings + .noCertificationInISO27001Title, + ), + const SizedBox(height: 16), + _uploadFileCard(viewModel: viewModel), + const SizedBox(height: 48), + _titleDropDownRow( + text: + FinalCompletionOfDocumentsStrings + .uploadCompletionCertificateTitle, + ), + const SizedBox(height: 16), + _uploadFileCard(viewModel: viewModel), + ], + ), + ); + } + + Widget _describeYourTeamTextField() { + return TextFormField( + maxLines: 5, + decoration: InputDecoration( + filled: true, + fillColor: AppColors.grey0, + hint: Text( + FinalCompletionOfDocumentsStrings.describeYourTeamHint, + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.w400, + color: AppColors.grey60, + ), + ), + border: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.secondaryborder2), + borderRadius: BorderRadius.circular(12), + ), + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.secondaryborder2), + borderRadius: BorderRadius.circular(12), + ), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.secondaryborder2), + borderRadius: BorderRadius.circular(12), + ), + ), + ); + } + + Widget _uploadFileCard({ + required FinalCompletionOfDocumentsViewModel viewModel, + }) { + return InkWell( + onTap: () => viewModel.pickFile(), + child: Container( + width: double.infinity, + height: 140, + decoration: BoxDecoration(color: AppColors.grey0), + child: DottedBorder( + options: const RoundedRectDottedBorderOptions( + radius: Radius.circular(12), + dashPattern: [4, 4], + strokeWidth: 1, + color: AppColors.secondaryborder, + padding: EdgeInsets.all(16), + ), + + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset(AssetsManager.uploadFile), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + FinalCompletionOfDocumentsStrings.chooseFile, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.mainBlue, + ), + ), + const SizedBox(width: 4), + Text( + FinalCompletionOfDocumentsStrings.toUpload, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.grey60, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + FinalCompletionOfDocumentsStrings.selectZipOrEtcTitle, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: AppColors.grey60, + ), + ), + ], + ), + ), + ), + ); + } + + Widget _titleDropDownRow({required String text}) { + return Row( + children: [ + SvgPicture.asset(AssetsManager.dangerFill), + const SizedBox(width: 8), + Text( + text, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: AppColors.grey60, + ), + ), + const Spacer(), + MainDropDown( + text: FinalCompletionOfDocumentsStrings.statusTitle, + items: const [ + FinalCompletionOfDocumentsStrings.statusWaitingForOthers, + FinalCompletionOfDocumentsStrings.statusToDo, + FinalCompletionOfDocumentsStrings.statusDone, + ], + onSelect: (String item) {}, + ), + ], + ); + } +} diff --git a/lib/views/final_completion_of_documents/strings/final_completion_of_documents_strings.dart b/lib/views/final_completion_of_documents/strings/final_completion_of_documents_strings.dart new file mode 100644 index 0000000..8e9d150 --- /dev/null +++ b/lib/views/final_completion_of_documents/strings/final_completion_of_documents_strings.dart @@ -0,0 +1,29 @@ +class FinalCompletionOfDocumentsStrings { + FinalCompletionOfDocumentsStrings._(); + static const String completionOfDocuments = 'Completion of documents'; + static const String commentNoteTitle = 'Comment / note'; + static const String replyToComment = 'reply to comment'; + static const String deadlineForCompletionOfDocuments = + 'Deadline for Completion of documents:'; + static const String numberOfServersListedTitle = 'Number of servers listed'; + static const String technicalTeamOrganizationChartUploadedTitle = + 'Technical team organization chart uploaded'; + static const String describeInfrastructureCapabilitiestitle = + 'Describe infrastructure capabilities'; + static const String statusTitle = 'Status'; + static const String statusWaitingForOthers = 'Waiting for others'; + static const String statusToDo = 'To do'; + static const String statusDone = 'Done'; + static const String chooseFile = 'Choose file'; + static const String toUpload = 'To upload'; + static const String selectZipOrEtcTitle = 'Select zip,image,pdf or ms.word'; + static const String uploadTechnicalTeamOrgChartTitle = + 'Upload technical team org chart'; + static const String describeYourTeamHint = 'Describe your team'; + static const String noCertificationInISO27001Title = + 'No certification in ISO 27001'; + static const String uploadCompletionCertificateTitle = + 'Upload completion certificate'; + static const String back = 'Back'; + static const String next = 'Next'; +} diff --git a/lib/views/final_completion_of_documents/widgets/comment_box.dart b/lib/views/final_completion_of_documents/widgets/comment_box.dart new file mode 100644 index 0000000..8c4ef54 --- /dev/null +++ b/lib/views/final_completion_of_documents/widgets/comment_box.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:tm_app/core/constants/assets.dart'; +import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/views/final_completion_of_documents/strings/final_completion_of_documents_strings.dart'; + +class CommentBox extends StatelessWidget { + const CommentBox({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + height: 148, + decoration: BoxDecoration( + color: AppColors.yellow1, + borderRadius: BorderRadius.circular(4), + border: Border.all(color: AppColors.yellow2), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsetsDirectional.fromSTEB(40, 24.0, 0.0, 0), + + child: Row( + children: [ + SvgPicture.asset(AssetsManager.clipboardText), + const SizedBox(width: 16.0), + Text( + FinalCompletionOfDocumentsStrings.commentNoteTitle, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w500, + color: AppColors.grey80, + ), + ), + ], + ), + ), + const Spacer(), + const Padding( + padding: EdgeInsetsDirectional.only(start: 65, bottom: 10), + child: SizedBox( + width: 256, + height: 78, + child: Text( + 'Lorem ipsum is a dummy or placeholder text commonly used in graphic design, publishing, and web development.', + maxLines: 3, + style: TextStyle( + fontSize: 14.0, + overflow: TextOverflow.ellipsis, + fontWeight: FontWeight.w400, + color: AppColors.grey, + ), + ), + ), + ), + ], + ), + Column( + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(end: 14, top: 15), + child: Row( + children: [ + Text( + FinalCompletionOfDocumentsStrings + .deadlineForCompletionOfDocuments, + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.w400, + color: AppColors.grey80, + ), + ), + const SizedBox(width: 4), + Container( + width: 87, + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(80), + color: AppColors.mainBlue, + ), + alignment: Alignment.center, + child: Text( + '2025-06-15', + style: TextStyle( + color: AppColors.white, + fontSize: 14.0, + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 51), + TextButton( + onPressed: () {}, + child: const Text( + FinalCompletionOfDocumentsStrings.replyToComment, + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.w500, + color: AppColors.mainBlue, + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/views/shared/main_drop_down.dart b/lib/views/shared/main_drop_down.dart new file mode 100644 index 0000000..cee7447 --- /dev/null +++ b/lib/views/shared/main_drop_down.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/size_config.dart'; + +import '../../core/constants/assets.dart'; + +class MainDropDown extends StatefulWidget { + const MainDropDown({ + required this.onSelect, + required this.items, + required this.text, + super.key, + this.backgroundColor, + this.active = true, + }); + final ValueChanged onSelect; + final List items; + final Color? backgroundColor; + final String text; + final bool active; + + @override + State createState() => _MainDropDownState(); +} + +class _MainDropDownState extends State { + final LayerLink _layerLink = LayerLink(); + OverlayEntry? _overlayEntry; + String? selectedItemText; + + void _showPopupMenu() { + if (_overlayEntry == null) { + OverlayState? overlayState = Overlay.of(context); + _overlayEntry = _createOverlayEntry(); + overlayState.insert(_overlayEntry!); + setState(() {}); + } else { + _hidePopupMenu(); + } + } + + void _hidePopupMenu() { + setState(() { + _overlayEntry?.remove(); + _overlayEntry = null; + }); + } + + @override + Widget build(BuildContext context) { + return _body(); + } + + Widget _body() { + return CompositedTransformTarget( + link: _layerLink, + child: GestureDetector( + onTap: () { + if (widget.active) { + _showPopupMenu(); + } else { + return; + } + }, + child: Container( + width: 168.0.w(), + height: 40.0.h(), + decoration: BoxDecoration( + color: widget.backgroundColor ?? Colors.transparent, + borderRadius: BorderRadius.circular(12.0), + border: Border.all(color: AppColors.secondaryborder), + ), + child: Row( + children: [ + SizedBox(width: 10.0.w()), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 120.0.w(), + child: Text( + selectedItemText ?? widget.text, + style: TextStyle( + color: AppColors.grey80, + fontSize: 16.0.sp(), + overflow: TextOverflow.ellipsis, + ), + ), + ), + ], + ), + const Spacer(), + + SvgPicture.asset( + _overlayEntry == null + ? AssetsManager.arrowDown + : AssetsManager.arrowUp, + ), + SizedBox(width: 12.0.w()), + ], + ), + ), + ), + ); + } + + OverlayEntry _createOverlayEntry() { + return OverlayEntry( + builder: + (context) => Positioned( + width: 168.0.w(), + child: CompositedTransformFollower( + link: _layerLink, + showWhenUnlinked: false, + offset: Offset(0, 48.0.h()), // Offset below the TextField + child: _notificationsMessages(), + ), + ), + ); + } + + Widget _notificationsMessages() { + return Material( + color: Colors.transparent, + child: Container( + padding: EdgeInsets.symmetric(vertical: 4.0.h()), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(12.0), + border: Border.all(color: AppColors.secondaryborder), + ), + child: Column( + children: [ + ...widget.items.map((String choice) { + return GestureDetector( + onTap: () { + setState(() { + selectedItemText = choice; + widget.onSelect(choice); + }); + _hidePopupMenu(); + }, + child: Column( + children: [ + SizedBox( + width: double.maxFinite, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 16.0.w(), + vertical: 8.0.h(), + ), + child: Text( + choice, + style: TextStyle( + fontSize: 16.0.sp(), + fontWeight: FontWeight.w400, + color: AppColors.grey70, + ), + ), + ), + ), + ], + ), + ); + }), + ], + ), + ), + ); + } + + @override + void dispose() { + _overlayEntry?.remove(); + _overlayEntry = null; + super.dispose(); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index ef4ff3d..23e4a3c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,6 +29,8 @@ dependencies: toastification: ^3.0.3 pinput: ^5.0.2 path_provider: ^2.1.5 + dotted_border: ^3.1.0 + file_picker: ^10.3.3 dev_dependencies: