diff --git a/lib/core/theme/colors.dart b/lib/core/theme/colors.dart index 948a7df..f642da5 100644 --- a/lib/core/theme/colors.dart +++ b/lib/core/theme/colors.dart @@ -145,6 +145,9 @@ class AppColors { static Color get blue0 => _isDarkMode ? const Color(0xFF4C6367) : const Color(0xFFD6F4F8); + static Color get blue10 => + _isDarkMode ? const Color(0xFF45C4D2) : const Color(0xFF24A6B4); + static Color get error => _isDarkMode ? const Color(0xFFFF3232) : const Color(0xFFC02525); diff --git a/lib/views/detail/strings/tender_details_strings.dart b/lib/views/detail/strings/tender_details_strings.dart index e4c5a6e..9d152fd 100644 --- a/lib/views/detail/strings/tender_details_strings.dart +++ b/lib/views/detail/strings/tender_details_strings.dart @@ -34,4 +34,7 @@ class TenderDetailsStrings { static const String tenderNoData = 'No tender details available'; static const String meetingTime = 'Meeting Time'; static const String apply = 'Apply'; + static const String pleaseReplyBy = 'If you’d like to join, Please Reply by '; + static const String lastDayToPrepareProposal = + 'Last day to prepare proposal is '; } diff --git a/lib/views/detail/widgets/deadline_item.dart b/lib/views/detail/widgets/deadline_item.dart index 6845a54..e0d1f73 100644 --- a/lib/views/detail/widgets/deadline_item.dart +++ b/lib/views/detail/widgets/deadline_item.dart @@ -25,17 +25,15 @@ class DeadlineItem extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - height: 45.0.h(), - child: Text( - detail.title ?? '', - style: TextStyle( - color: AppColors.grey80, - fontWeight: FontWeight.w600, - fontSize: 16.0.sp(), - ), + Text( + TenderDetailsStrings.tenderDeadlineLabel, + style: TextStyle( + color: AppColors.grey80, + fontWeight: FontWeight.w600, + fontSize: 16.0.sp(), ), ), + SizedBox(height: 12.0.h()), IntrinsicHeight( child: Row( children: [ @@ -44,10 +42,10 @@ class DeadlineItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - TenderDetailsStrings.tenderApprovalText, + TenderDetailsStrings.pleaseReplyBy, style: TextStyle( color: AppColors.grey70, - fontWeight: FontWeight.w400, + fontWeight: FontWeight.w500, fontSize: 14.0.sp(), ), ), @@ -73,10 +71,10 @@ class DeadlineItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - TenderDetailsStrings.tenderSubmissionText, + TenderDetailsStrings.lastDayToPrepareProposal, style: TextStyle( color: AppColors.grey70, - fontWeight: FontWeight.w400, + fontWeight: FontWeight.w500, fontSize: 14.0.sp(), ), ), @@ -105,25 +103,23 @@ class DeadlineItem extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - height: 45.0.h(), - child: Text( - detail.title ?? '', - style: TextStyle( - color: AppColors.grey80, - fontWeight: FontWeight.w600, - fontSize: 16.0.sp(), - ), + Text( + TenderDetailsStrings.tenderDeadlineLabel, + style: TextStyle( + color: AppColors.grey80, + fontWeight: FontWeight.w600, + fontSize: 16.0.sp(), ), ), + SizedBox(height: 12.0.h()), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - TenderDetailsStrings.tenderApprovalText, + TenderDetailsStrings.pleaseReplyBy, style: TextStyle( color: AppColors.grey70, - fontWeight: FontWeight.w400, + fontWeight: FontWeight.w500, fontSize: 14.0.sp(), ), ), @@ -142,10 +138,10 @@ class DeadlineItem extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - TenderDetailsStrings.tenderSubmissionText, + TenderDetailsStrings.lastDayToPrepareProposal, style: TextStyle( color: AppColors.grey70, - fontWeight: FontWeight.w400, + fontWeight: FontWeight.w500, fontSize: 14.0.sp(), ), ), diff --git a/lib/views/detail/widgets/detail_drop_down.dart b/lib/views/detail/widgets/detail_drop_down.dart new file mode 100644 index 0000000..6901977 --- /dev/null +++ b/lib/views/detail/widgets/detail_drop_down.dart @@ -0,0 +1,112 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:tm_app/core/utils/size_config.dart'; + +import '../../../core/theme/colors.dart'; + +class DetailDropDown extends StatefulWidget { + final String title; + final List items; + const DetailDropDown({required this.title, required this.items, super.key}); + + @override + State createState() => _DetailDropDownState(); +} + +class _DetailDropDownState extends State + with SingleTickerProviderStateMixin { + bool isOpen = true; + late AnimationController _animationController; + late Animation _rotationAnimation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _rotationAnimation = Tween(begin: 0.0, end: 0.5).animate( + CurvedAnimation(parent: _animationController, curve: Curves.elasticIn), + ); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + InkWell( + onTap: () { + setState(() { + isOpen = !isOpen; + }); + if (isOpen) { + _animationController.forward(); + } else { + _animationController.reverse(); + } + }, + child: Container( + width: 24.0.w(), + height: 24.0.h(), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: AppColors.grey60), + ), + alignment: Alignment.center, + child: AnimatedBuilder( + animation: _rotationAnimation, + builder: (context, child) { + return Icon( + isOpen + ? CupertinoIcons.chevron_up + : CupertinoIcons.chevron_down, + color: AppColors.grey60, + size: 12, + ); + }, + ), + ), + ), + SizedBox(width: 8.0.w()), + Text( + widget.title, + style: TextStyle( + color: AppColors.grey80, + fontSize: 16.0.sp(), + fontWeight: FontWeight.w600, + ), + ), + ], + ), + SizedBox(height: 12.0.h()), + AnimatedSize( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: + isOpen + ? Column( + children: + widget.items + .map( + (e) => Padding( + padding: EdgeInsets.only(left: 12.0.w()), + child: Text(e), + ), + ) + .toList(), + ) + : const SizedBox.shrink(), + ), + ], + ); + } +} diff --git a/lib/views/detail/widgets/tender_detail_header.dart b/lib/views/detail/widgets/tender_detail_header.dart index 1fae266..7956dab 100644 --- a/lib/views/detail/widgets/tender_detail_header.dart +++ b/lib/views/detail/widgets/tender_detail_header.dart @@ -1,12 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:tm_app/core/theme/colors.dart'; -import 'package:tm_app/core/utils/date_utils.dart'; +import 'package:tm_app/core/utils/price_extension.dart'; import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; -import 'package:tm_app/views/detail/widgets/status_tag.dart'; import 'package:tm_app/views/detail/widgets/tender_detail_info_section.dart'; -import 'package:tm_app/views/detail/widgets/tender_document_section.dart'; -import 'package:tm_app/views/detail/widgets/tender_location_section.dart'; + +import '../../../core/constants/assets.dart'; +import '../../shared/flag.dart'; +import '../strings/tender_details_strings.dart'; +import 'detail_drop_down.dart'; class TenderDetailHeader extends StatelessWidget { final bool isScreenBig; @@ -21,50 +24,225 @@ class TenderDetailHeader extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: const EdgeInsets.all(16), + padding: EdgeInsets.symmetric(horizontal: 16.0.w(), vertical: 16.0.h()), decoration: BoxDecoration( color: AppColors.backgroundColor, - borderRadius: BorderRadius.circular(5), + borderRadius: BorderRadius.circular(12), border: Border.all(width: 0.5, color: AppColors.grey50), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildDateAndStatus(), - SizedBox(height: 12.0.h()), - _buildTitle(), - SizedBox(height: 10.0.h()), - Divider(color: AppColors.grey20), - SizedBox(height: 5.0.h()), - TenderDetailInfoSection(isScreenBig: isScreenBig, detail: detail), - TenderLocationSection(detail: detail), + _titleText(), + SizedBox(height: 4.0.h()), + _nameText(), + SizedBox(height: 4.0.h()), + _idText(), SizedBox(height: 16.0.h()), - TenderDocumentSection(detail: detail), + _locationBudgetRow(), + SizedBox(height: 24.0.h()), + _projectId(), + SizedBox(height: 12.0.h()), + _winningChanceAndDificulty(), + SizedBox(height: 24.0.h()), + _keyRisksDropDown(), + SizedBox(height: 24.0.h()), + Divider(color: AppColors.grey20), + TenderDetailInfoSection(isScreenBig: isScreenBig, detail: detail), + SizedBox(height: 24.0.h()), + _overViewTitle(), + SizedBox(height: 12.0.h()), + _descriptionText(), + SizedBox(height: 24.0.h()), + _serviceDropDown(), + SizedBox(height: 24.0.h()), + _requirementsDropDown(), + SizedBox(height: 24.0.h()), + _backgroundDropDown(), ], ), ); } - Widget _buildDateAndStatus() => Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - timeConvertor(detail.publicationDate!), + Widget _serviceDropDown() => + const DetailDropDown(title: 'Servive/Product', items: []); + + Widget _backgroundDropDown() => + const DetailDropDown(title: 'Background', items: []); + + Widget _requirementsDropDown() { + return const DetailDropDown( + title: 'Requirements (Summary)', + items: [ + 'Market consultation for selecting and procuring a Customer Data Platform (CDP) to unify student data from multiple sources and improve personalization and marketing.', + ], + ); + } + + Widget _descriptionText() { + return Text( + detail.description ?? '', + style: TextStyle( + color: AppColors.grey70, + fontSize: 16.0.sp(), + fontWeight: FontWeight.w400, + ), + ); + } + + Widget _overViewTitle() { + return Text( + 'Overview', + style: TextStyle( + color: AppColors.grey80, + fontSize: 16.0.sp(), + fontWeight: FontWeight.w600, + ), + ); + } + + Widget _keyRisksDropDown() { + return const DetailDropDown( + title: 'Key risks & dealbreakers', + items: [ + 'ISO 27001:2022 likely mandatory for future tender.', + 'Lack of past performance — public sector values references.', + 'Implementation & ongoing support required (1–5 person team expectation).', + ], + ); + } + + Widget _winningChanceAndDificulty() { + return Row( + children: [ + Container( + height: 24.0.h(), + padding: EdgeInsets.symmetric( + horizontal: 16.0.w(), + vertical: 4.0.h(), + ), + decoration: BoxDecoration( + color: AppColors.blue0, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + 'Winning chance: <5%', + style: TextStyle( + color: AppColors.blue10, + fontSize: 12.0.sp(), + fontWeight: FontWeight.w500, + ), + ), + ), + SizedBox(width: 12.0.w()), + Container( + height: 24.0.h(), + padding: EdgeInsets.symmetric( + horizontal: 16.0.w(), + vertical: 4.0.h(), + ), + decoration: BoxDecoration( + color: AppColors.blue0, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + 'Difficulty 2/5', + style: TextStyle( + color: AppColors.blue10, + fontSize: 12.0.sp(), + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ); + } + + Widget _projectId() { + return Container( + height: 24.0.h(), + padding: EdgeInsets.symmetric(horizontal: 16.0.w(), vertical: 4.0.h()), + decoration: BoxDecoration( + color: AppColors.blue0, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + 'Project ID 6243372025NLD', style: TextStyle( - color: AppColors.grey60, - fontWeight: FontWeight.w400, + color: AppColors.blue10, fontSize: 12.0.sp(), + fontWeight: FontWeight.w500, ), ), - StatusTag( - text: detail.procurementTypeCode ?? '', - textColor: AppColors.cyanTeal, - backgroundColor: AppColors.cyanAqua, - ), - ], - ); + ); + } - Widget _buildTitle() => Text( + Widget _locationBudgetRow() { + return Row( + children: [ + SvgPicture.asset( + AssetsManager.location, + height: 20.0.h(), + width: 20.0.w(), + ), + SizedBox(width: 4.0.w()), + Text( + detail.countryCode ?? '', + style: TextStyle( + color: AppColors.grey70, + fontSize: 14.0.sp(), + fontWeight: FontWeight.w400, + ), + ), + SizedBox(width: 8.0.w()), + detail.countryCode != null + ? Flag(countryCode: detail.countryCode!) + : const Flag(countryCode: ''), + const Spacer(), + Container( + height: 32.0.h(), + padding: EdgeInsets.symmetric(horizontal: 16.0.w()), + decoration: BoxDecoration( + color: AppColors.grey30, + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + child: Text( + '${detail.estimatedValue?.formattedPrice} ${detail.currency ?? ''}', + style: TextStyle( + color: AppColors.grey60, + fontSize: 16.0.sp(), + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ); + } + + Widget _idText() { + return Text( + '${TenderDetailsStrings.tenderIdLabel}: ${detail.tenderId}', + style: TextStyle( + color: AppColors.grey80, + fontSize: 14.0.sp(), + fontWeight: FontWeight.w400, + ), + ); + } + + Widget _nameText() { + return Text( + detail.buyerOrganization?.name ?? '', + style: TextStyle( + color: AppColors.grey80, + fontSize: 14.0.sp(), + fontWeight: FontWeight.w400, + ), + ); + } + + Widget _titleText() => Text( detail.title ?? '', style: TextStyle( color: AppColors.grey80, diff --git a/lib/views/detail/widgets/tender_detail_info_section.dart b/lib/views/detail/widgets/tender_detail_info_section.dart index b1f9e70..56ec426 100644 --- a/lib/views/detail/widgets/tender_detail_info_section.dart +++ b/lib/views/detail/widgets/tender_detail_info_section.dart @@ -20,10 +20,6 @@ class TenderDetailInfoSection extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - InfoItem( - title: TenderDetailsStrings.tenderIdLabel, - value: detail.tenderId ?? '', - ), DeadlineItem(detail: detail, isScreenBig: isScreenBig), InfoItem( title: TenderDetailsStrings.tenderClientLabel, @@ -34,10 +30,6 @@ class TenderDetailInfoSection extends StatelessWidget { value: '${detail.estimatedValue?.formattedPrice} ${detail.currency ?? ''}', ), - InfoItem( - title: TenderDetailsStrings.duration, - value: '${detail.duration} ${detail.durationUnit ?? ''}', - ), InfoItem( title: TenderDetailsStrings.tenderReferenceNumberLabel, value: detail.noticePublicationId ?? '',