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..bafdd20
--- /dev/null
+++ b/lib/views/detail/detail_screen.dart
@@ -0,0 +1,356 @@
+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'),
+ infoItem(
+ 'Deadline',
+ 'For approval: 2025-05-21\nFor submission: 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 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