tender detail changes
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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 ';
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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<String> items;
|
||||
const DetailDropDown({required this.title, required this.items, super.key});
|
||||
|
||||
@override
|
||||
State<DetailDropDown> createState() => _DetailDropDownState();
|
||||
}
|
||||
|
||||
class _DetailDropDownState extends State<DetailDropDown>
|
||||
with SingleTickerProviderStateMixin {
|
||||
bool isOpen = true;
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _rotationAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
_rotationAnimation = Tween<double>(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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ?? '',
|
||||
|
||||
Reference in New Issue
Block a user