diff --git a/assets/svgs/arrow_left.svg b/assets/svgs/arrow_left.svg new file mode 100644 index 0000000..f5f0821 --- /dev/null +++ b/assets/svgs/arrow_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svgs/export.svg b/assets/svgs/export.svg new file mode 100644 index 0000000..6c89dab --- /dev/null +++ b/assets/svgs/export.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..e549ee2 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/lib/core/constants/assets.dart b/lib/core/constants/assets.dart index 100370b..f02a024 100644 --- a/lib/core/constants/assets.dart +++ b/lib/core/constants/assets.dart @@ -17,4 +17,6 @@ class AssetsManager { static const location = 'assets/icons/location.svg'; static const arrowCircleLeft = 'assets/icons/arrow-circle-left.svg'; static const arrowCircleRight = 'assets/icons/arrow-circle-right.svg'; + static const arrowLeft = 'assets/svgs/arrow_left.svg'; + static const export = 'assets/svgs/export.svg'; } diff --git a/lib/core/constants/colors.dart b/lib/core/constants/colors.dart index 78d7a52..ccab1f8 100644 --- a/lib/core/constants/colors.dart +++ b/lib/core/constants/colors.dart @@ -20,15 +20,21 @@ class AppColors { static const Color successColor = Color(0xFF28A745); static const Color errorColor = Color(0xFFDC3545); static const Color warningColor = Color(0xFFFFC107); - + static const Color jellyBean = Color(0xFF34BCCB); static const Color grey = Color(0xFF555555); static const Color grey50 = Color(0xFF9E9E9E); static const Color grey60 = Color(0xFF777777); static const Color grey70 = Color(0xFF444444); static const Color grey80 = Color(0xFF222222); static const Color grey10 = Color(0xFFF4F4F4); + static const Color grey20 = Color(0xFFE3E3E3); static const Color grey0 = Color(0xFFFCFCFC); static const Color backgroundColor = Color(0xFFFFFFFF); static const Color borderColor = Color(0xFFE1E1E1); static const Color primary2 = Color(0xFFE5EFFF); + static const Color paleOrange = Color(0xFFfff7ee); + static const Color red10 = Color(0xFFC02525); + static const Color lightBlue = Color(0xFFe8ecfc); + static const Color cyanAqua = Color(0xFFd6f4f8); + static const Color cyanTeal = Color(0xFF24A6B4); } diff --git a/lib/core/routes/app_routes.dart b/lib/core/routes/app_routes.dart index 0c82310..b43a3aa 100644 --- a/lib/core/routes/app_routes.dart +++ b/lib/core/routes/app_routes.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; - +import 'package:tm_app/views/detail/detail_screen.dart'; import '../../views/home/home_screen.dart'; -import '../../views/login/login_screen.dart'; import '../../views/profile/profile_screen.dart'; import '../../views/tenders/tenders_screen.dart'; import 'app_shell_screen.dart'; @@ -31,7 +30,7 @@ class LoginScreenRoute extends GoRouteData with _$LoginScreenRoute { @override Widget build(BuildContext context, GoRouterState state) { - return const LoginScreen(); + return const TenderDetailScreen(); } } diff --git a/lib/views/detail/detail_screen.dart b/lib/views/detail/detail_screen.dart new file mode 100644 index 0000000..33002d4 --- /dev/null +++ b/lib/views/detail/detail_screen.dart @@ -0,0 +1,432 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tm_app/core/constants/assets.dart'; +import 'package:tm_app/core/constants/colors.dart'; +import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/views/login/widgets/login_button.dart'; + +class TenderDetailScreen extends StatelessWidget { + const TenderDetailScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: PreferredSize( + preferredSize: const Size.fromHeight(60), + child: AppBar( + backgroundColor: Colors.white, + elevation: 0, + titleSpacing: 0, + leading: IconButton( + icon: SvgPicture.asset( + AssetsManager.arrowLeft, + height: 24.0.w(), + width: 24.0.w(), + ), + onPressed: () => Navigator.pop(context), + ), + + title: const Text( + 'Tender Detail', + style: TextStyle(color: Colors.black, fontWeight: FontWeight.w600), + ), + ), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + border: Border.all(width: 0.5, color: AppColors.grey50), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '2025-05-21', + style: TextStyle( + color: AppColors.grey60, + fontWeight: FontWeight.w400, + fontSize: 12.0.sp(), + ), + ), + StatusTag( + text: 'Open', + textColor: AppColors.cyanTeal, + backgroundColor: AppColors.cyanAqua, + ), + ], + ), + SizedBox(height: 12.0.h()), + Text( + 'Operation, support and further development of open e-platform Operation, support and further development of open e-platform', + style: TextStyle( + color: AppColors.grey80, + fontWeight: FontWeight.w600, + fontSize: 18.0.sp(), + ), + ), + SizedBox(height: 10.0.h()), + + Divider(), + SizedBox(height: 5.0.h()), + + infoItem('ID', 'JNDFKMDV-100-JF'), + deadlineItem( + title: 'Deadline', + approvalText: 'For approval', + approvalDate: '2025-05-21', + submissionText: 'For submission', + submissionDate: '2025-05-21', + ), + infoItem('Client', 'Procurement Notice Procurement Notice'), + infoItem('Delivery Locations', 'Norrbotten County'), + infoItem('Reference Number', 'KLF 2025/120'), + Padding( + padding: const EdgeInsets.symmetric(vertical: 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 45.0.h(), + child: Text( + 'Reference Number', + style: TextStyle( + color: AppColors.grey80, + fontWeight: FontWeight.w600, + fontSize: 16.0.sp(), + ), + ), + ), + Row( + children: [ + SvgPicture.asset( + AssetsManager.location, + height: 20.0.h(), + width: 20.0.w(), + ), + SizedBox(width: 4.0.w()), + Text( + 'UK', + style: TextStyle( + color: AppColors.grey70, + fontSize: 14.0.sp(), + fontWeight: FontWeight.w400, + ), + ), + SizedBox(width: 4.0.w()), + Image.asset( + AssetsManager.seFlag, + height: 20.0.h(), + width: 20.0.w(), + ), + ], + ), + SizedBox(height: 8.0.h()), + ], + ), + ), + SizedBox(height: 16.0.h()), + Text( + 'Locations', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16.0.sp(), + ), + ), + SizedBox(height: 8.0.h()), + const Text( + 'Luleå Municipality is procuring on behalf of Norrbotten’s e-board (all municipalities in Norrbotten).\n\n' + 'The procurement includes operation, support, and further development of the existing open e-platform.\n\n' + 'No additional information, amendments, or answers to questions will be provided after June 16.\n\n' + '0 additional information, amendments, or answers to questions will be provided after June 16.\n\n' + '0 additional information, amendments, or answers to questions will be provided after June 16 website: www.tenders.com\n\n' + 'Lorem ipsum is amet\n\n' + 'Luleå Municipality is procuring on behalf of Norrbotten’s e-board (all municipalities in Norrbotten).\n\n' + 'The procurement includes operation, support, and further development of the existing open e-platform.\n\n' + 'No additional information, amendments, or answers to questions will be provided after June 16.\n\n' + '0 additional information, amendments, or answers to questions will be provided after June 16.\n\n' + '0 additional information, amendments, or answers to questions will be provided after June 16.', + style: TextStyle(height: 1.5), + ), + SizedBox(height: 16.0.h()), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SvgPicture.asset(AssetsManager.export), + SizedBox(height: 6.0.h()), + Text( + 'PDF , Document', + style: TextStyle( + color: Colors.blue, + decoration: TextDecoration.underline, + ), + ), + ], + ), + ], + ), + ), + SizedBox(height: 24.0.h()), + ResumeMatchCard(), + SizedBox(height: 24.0.h()), + Column( + children: [ + BaseButton( + isEnabled: true, + text: 'Submit', + backgroundColor: AppColors.lightBlue, + textColor: AppColors.mainBlue, + onPressed: () {}, + ), + SizedBox(height: 16.0.h()), + BaseButton( + isEnabled: true, + text: 'reject', + borderColor: AppColors.red10, + textColor: AppColors.red10, + backgroundColor: Colors.white, + borderWidth: 1, + onPressed: () {}, + ), + SizedBox(height: 16.0.h()), + ], + ), + ], + ), + ), + ), + ); + } + + Widget deadlineItem({ + required String title, + required String approvalText, + required String approvalDate, + required String submissionText, + required String submissionDate, + }) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 45.0.h(), + child: Text( + title, + style: TextStyle( + color: AppColors.grey80, + fontWeight: FontWeight.w600, + fontSize: 16.0.sp(), + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + approvalText, + style: TextStyle( + color: AppColors.grey70, + fontWeight: FontWeight.w400, + fontSize: 14.0.sp(), + ), + ), + Text( + approvalDate, + style: TextStyle( + color: AppColors.grey70, + fontWeight: FontWeight.w400, + fontSize: 14.0.sp(), + ), + ), + ], + ), + SizedBox(height: 4.0.h()), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + submissionText, + style: TextStyle( + color: AppColors.grey70, + fontWeight: FontWeight.w400, + fontSize: 14.0.sp(), + ), + ), + Text( + submissionDate, + style: TextStyle( + color: AppColors.grey70, + fontWeight: FontWeight.w400, + fontSize: 14.0.sp(), + ), + ), + ], + ), + SizedBox(height: 8.0.h()), + Divider(), + ], + ), + ); + } + + Widget infoItem(String title, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 45.0.h(), + child: Text( + title, + style: TextStyle( + color: AppColors.grey80, + fontWeight: FontWeight.w600, + fontSize: 16.0.sp(), + ), + ), + ), + Text( + value, + style: TextStyle( + color: AppColors.grey70, + fontWeight: FontWeight.w400, + fontSize: 14.0.sp(), + ), + ), + SizedBox(height: 8.0.h()), + Divider(), + ], + ), + ); + } +} + +class ResumeMatchCard extends StatelessWidget { + const ResumeMatchCard({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.paleOrange, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Match with your profile', + style: TextStyle( + fontSize: 12.0.sp(), + color: Colors.black87, + fontWeight: FontWeight.w400, + ), + ), + Text( + '75%', + style: TextStyle( + fontSize: 12.0.sp(), + fontWeight: FontWeight.w600, + color: AppColors.jellyBean, + ), + ), + ], + ), + SizedBox(height: 6.0.h()), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: 0.75, + backgroundColor: AppColors.grey20, + valueColor: AlwaysStoppedAnimation(AppColors.jellyBean), + minHeight: 6, + ), + ), + SizedBox(height: 16.0.h()), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.warning_rounded, color: Colors.red), + SizedBox(width: 8.0.w()), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Incomplete Resume Information', + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16.0.sp(), + color: Colors.black87, + ), + ), + SizedBox(height: 4.0.h()), + Text( + 'No experience in e-platform development', + style: TextStyle( + fontSize: 14.0.sp(), + color: Colors.black54, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ); + } +} + +class StatusTag extends StatelessWidget { + final String text; + final Color textColor; + final Color backgroundColor; + + const StatusTag({ + required this.text, + required this.textColor, + required this.backgroundColor, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 12.0.w(), vertical: 6.0.h()), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + text, + style: TextStyle( + color: textColor, + fontWeight: FontWeight.w500, + fontSize: 14.0.sp(), + ), + ), + ); + } +} diff --git a/lib/views/login/login_screen.dart b/lib/views/login/login_screen.dart index 3631fc6..c9c7831 100644 --- a/lib/views/login/login_screen.dart +++ b/lib/views/login/login_screen.dart @@ -83,7 +83,7 @@ class _LoginScreenState extends State { ), SizedBox(height: 32.0.h()), - LoginButton( + BaseButton( isEnabled: _isLoginEnabled, onPressed: _isLoginEnabled ? () {} : null, ), diff --git a/lib/views/login/widgets/login_button.dart b/lib/views/login/widgets/login_button.dart index c8da53d..52d64d1 100644 --- a/lib/views/login/widgets/login_button.dart +++ b/lib/views/login/widgets/login_button.dart @@ -1,17 +1,30 @@ import 'package:flutter/material.dart'; - import '../../../core/constants/colors.dart'; import '../../../core/constants/strings.dart'; import '../../../core/utils/size_config.dart'; -class LoginButton extends StatelessWidget { +class BaseButton extends StatelessWidget { final bool isEnabled; final VoidCallback? onPressed; + final String? text; + final Color? textColor; + final Color? backgroundColor; + final Color? borderColor; + final double? borderWidth; + final double? borderRadius; + final double? elevation; - const LoginButton({ - super.key, + const BaseButton({ required this.isEnabled, required this.onPressed, + this.text, + this.textColor, + this.backgroundColor, + this.borderColor, + this.borderWidth, + this.borderRadius, + this.elevation, + super.key, }); @override @@ -20,19 +33,29 @@ class LoginButton extends StatelessWidget { width: double.infinity, height: 55.0.h(), child: ElevatedButton( - onPressed: onPressed, + onPressed: isEnabled ? onPressed : null, style: ElevatedButton.styleFrom( - backgroundColor: isEnabled ? AppColors.mainBlue : Colors.grey[300], - foregroundColor: Colors.white, + backgroundColor: backgroundColor ?? + (isEnabled ? AppColors.mainBlue : Colors.grey[300]), + foregroundColor: textColor ?? Colors.white, + elevation: elevation ?? 0, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(28.0.w()), + borderRadius: BorderRadius.circular(borderRadius ?? 28.0.w()), + side: BorderSide( + color: borderColor ?? Colors.transparent, + width: borderWidth ?? 0, + ), ), ), child: Text( - AppStrings.loginButton, - style: TextStyle(fontSize: 16.0.sp(), fontWeight: FontWeight.bold), + text ?? AppStrings.loginButton, + style: TextStyle( + fontSize: 16.0.sp(), + fontWeight: FontWeight.bold, + color: textColor ?? Colors.white, + ), ), ), ); } -} +} \ No newline at end of file