tender detail changes

This commit is contained in:
amirrezaghabeli
2025-10-04 10:37:49 +03:30
parent fd8c969690
commit 56620d4c1b
6 changed files with 348 additions and 64 deletions
+3
View File
@@ -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 youd like to join, Please Reply by ';
static const String lastDayToPrepareProposal =
'Last day to prepare proposal is ';
}
+22 -26
View File
@@ -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 (15 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 ?? '',