Merge pull request 'check and fix in ui' (#26) from all_fixes into main

Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/26
This commit is contained in:
a.ghabeli
2025-08-10 16:05:37 +03:30
35 changed files with 1066 additions and 416 deletions
+9
View File
@@ -0,0 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.33203 1.33594V3.33594" stroke="#777777" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.668 1.33594V3.33594" stroke="#777777" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.33203 6.0625H13.6654" stroke="#777777" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 5.66927V11.3359C14 13.3359 13 14.6693 10.6667 14.6693H5.33333C3 14.6693 2 13.3359 2 11.3359V5.66927C2 3.66927 3 2.33594 5.33333 2.33594H10.6667C13 2.33594 14 3.66927 14 5.66927Z" stroke="#777777" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.99764 9.13021H8.00363" stroke="#777777" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.52889 9.13021H5.53488" stroke="#777777" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.52889 11.1302H5.53488" stroke="#777777" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 9.33594H28" stroke="#888888" stroke-width="1.5" stroke-linecap="round"/>
<path d="M4 16H28" stroke="#888888" stroke-width="1.5" stroke-linecap="round"/>
<path d="M4 22.6641H28" stroke="#888888" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 353 B

+5
View File
@@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 5.36719V8.14219" stroke="#0164FF" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M10.0171 1.66406C6.9504 1.66406 4.46707 4.1474 4.46707 7.21406V8.96406C4.46707 9.53073 4.23374 10.3807 3.94207 10.8641L2.88374 12.6307C2.23374 13.7224 2.68374 14.9391 3.88374 15.3391C7.86707 16.6641 12.1754 16.6641 16.1587 15.3391C17.2837 14.9641 17.7671 13.6474 17.1587 12.6307L16.1004 10.8641C15.8087 10.3807 15.5754 9.5224 15.5754 8.96406V7.21406C15.5671 4.16406 13.0671 1.66406 10.0171 1.66406Z" stroke="#0164FF" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M12.7766 15.6797C12.7766 17.2047 11.5266 18.4547 10.0016 18.4547C9.24323 18.4547 8.54323 18.138 8.04323 17.638C7.54323 17.138 7.22656 16.438 7.22656 15.6797" stroke="#0164FF" stroke-width="1.5" stroke-miterlimit="10"/>
</svg>

After

Width:  |  Height:  |  Size: 938 B

+2
View File
@@ -6,6 +6,7 @@ import 'package:tm_app/data/repositories/tender_detail_repository.dart';
import 'package:tm_app/data/services/home_service.dart'; import 'package:tm_app/data/services/home_service.dart';
import 'package:tm_app/data/services/tender_detail_service.dart'; import 'package:tm_app/data/services/tender_detail_service.dart';
import 'package:tm_app/view_models/tender_detail_view_model.dart'; import 'package:tm_app/view_models/tender_detail_view_model.dart';
import 'package:tm_app/view_models/tenders_view_model.dart';
import '../../data/repositories/auth_repository.dart'; import '../../data/repositories/auth_repository.dart';
import '../../data/services/auth_service.dart'; import '../../data/services/auth_service.dart';
@@ -59,6 +60,7 @@ List<SingleChildWidget> get viewModels {
(context) => (context) =>
TenderDetailViewModel(tenderDetailRepository: context.read()), TenderDetailViewModel(tenderDetailRepository: context.read()),
), ),
ChangeNotifierProvider(create: (context) => TendersViewModel()),
]; ];
} }
+8
View File
@@ -3,6 +3,11 @@ class AssetsManager {
//bottom navigation //bottom navigation
static const homeActive = 'assets/icons/home_active.svg'; static const homeActive = 'assets/icons/home_active.svg';
static const home = 'assets/icons/home.svg';
static const tenders = 'assets/icons/task_square.svg';
static const tendersActive = 'assets/icons/task-square_active.svg';
static const profile = 'assets/icons/profile-circle.svg';
static const profileActive = 'assets/icons/profile-circle_active.svg';
// login page // login page
static const logo = 'assets/pngs/logo.png'; static const logo = 'assets/pngs/logo.png';
@@ -30,6 +35,9 @@ class AssetsManager {
static const shield = 'assets/icons/shield.svg'; static const shield = 'assets/icons/shield.svg';
static const edit = 'assets/icons/edit.svg'; static const edit = 'assets/icons/edit.svg';
static const arrowRight = 'assets/icons/arrow-right.svg'; static const arrowRight = 'assets/icons/arrow-right.svg';
static const notification = 'assets/icons/notification.svg';
static const calendar = 'assets/icons/calendar.svg';
static const menu = 'assets/icons/menu.svg';
//your tenders page //your tenders page
static const tickCircle = 'assets/icons/tick-circle.svg'; static const tickCircle = 'assets/icons/tick-circle.svg';
+1
View File
@@ -32,6 +32,7 @@ class AppStrings {
static const String tenderValue = 'Tender Value'; static const String tenderValue = 'Tender Value';
static const String thunderStatus = 'Thunder status'; static const String thunderStatus = 'Thunder status';
static const String yourTenders = 'Your tenders'; static const String yourTenders = 'Your tenders';
static const String welcome = 'Welcome';
//Tenders //Tenders
static const String tendersTitle = 'Tenders'; static const String tendersTitle = 'Tenders';
+3 -3
View File
@@ -4,10 +4,10 @@ import 'package:tm_app/views/detail/pages/detail_screen.dart';
import 'package:tm_app/views/home/pages/home_screen.dart'; import 'package:tm_app/views/home/pages/home_screen.dart';
import 'package:tm_app/views/login/pages/login_screen.dart'; import 'package:tm_app/views/login/pages/login_screen.dart';
import '../../views/profile/profile_screen.dart'; import '../../views/profile/pages/profile_screen.dart';
import '../../views/tenders/tenders_screen.dart'; import '../../views/tenders/pages/tenders_screen.dart';
import '../../views/your_tenders/your_tenders_screen.dart'; import '../../views/your_tenders/your_tenders_screen.dart';
import 'app_shell_screen.dart'; import 'app_shell/app_shell_screen.dart';
part 'app_routes.g.dart'; part 'app_routes.g.dart';
@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:tm_app/core/routes/app_shell/desktop_shell_page.dart';
import 'package:tm_app/core/routes/app_shell/mobile_shell_page.dart';
import 'package:tm_app/core/routes/app_shell/tablet_shell_page.dart';
import 'package:tm_app/views/shared/responsive_builder.dart';
class AppShellScreen extends StatelessWidget {
const AppShellScreen({required this.navigationShell, super.key});
final StatefulNavigationShell navigationShell;
void _gotoBranch(int index) {
navigationShell.goBranch(
index,
initialLocation: index == navigationShell.currentIndex,
);
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
mobile: MobileShellPage(
navigationShell: navigationShell,
onTap: _gotoBranch,
),
tablet: TabletShellPage(
navigationShell: navigationShell,
onTap: _gotoBranch,
),
desktop: DesktopShellPage(
navigationShell: navigationShell,
onTap: _gotoBranch,
),
);
}
}
@@ -0,0 +1,109 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
class DesktopShellPage extends StatelessWidget {
const DesktopShellPage({
required this.navigationShell,
required this.onTap,
super.key,
});
final StatefulNavigationShell navigationShell;
final ValueChanged<int> onTap;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [_navigationBar(context), Expanded(child: navigationShell)],
),
);
}
Container _navigationBar(BuildContext context) {
return Container(
height: 64,
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: AppColors.grey30)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(width: 40),
Image.asset(AssetsManager.logo, width: 59, height: 28),
Spacer(),
_bottomNavigationItem(
context: context,
text: 'Home',
isActive: navigationShell.currentIndex == 0,
onTap: () => onTap(0),
iconPath: 'assets/icons/home.svg',
activeIconPath: AssetsManager.homeActive,
),
SizedBox(width: 24),
_bottomNavigationItem(
context: context,
text: 'Tenders',
isActive: navigationShell.currentIndex == 1,
onTap: () => onTap(1),
iconPath: 'assets/icons/task_square.svg',
activeIconPath: 'assets/icons/task-square_active.svg',
),
SizedBox(width: 24),
_bottomNavigationItem(
context: context,
text: 'Profile',
isActive: navigationShell.currentIndex == 2,
onTap: () => onTap(2),
iconPath: 'assets/icons/profile-circle.svg',
activeIconPath: 'assets/icons/profile-circle_active.svg',
),
Spacer(),
SizedBox(width: 59),
],
),
);
}
Widget _bottomNavigationItem({
required BuildContext context,
required String text,
required bool isActive,
required VoidCallback onTap,
required String iconPath,
required String activeIconPath,
}) {
return InkWell(
onTap: onTap,
child: SizedBox(
width: 200.0,
height: 64,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SvgPicture.asset(isActive ? activeIconPath : iconPath),
SizedBox(width: 8),
Text(
text,
style: TextStyle(
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
color:
isActive
? const Color(0xFF0164FF)
: const Color(0xFF777777),
),
),
],
),
),
);
}
}
@@ -1,27 +1,22 @@
// Shell screen with bottom navigation
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
import '../constants/assets.dart'; class MobileShellPage extends StatelessWidget {
import '../theme/colors.dart'; const MobileShellPage({
import '../utils/size_config.dart'; required this.navigationShell,
required this.onTap,
class AppShellScreen extends StatelessWidget { super.key,
const AppShellScreen({required this.navigationShell, super.key}); });
final StatefulNavigationShell navigationShell; final StatefulNavigationShell navigationShell;
final ValueChanged<int> onTap;
void _gotoBranch(int index) {
navigationShell.goBranch(
index,
initialLocation: index == navigationShell.currentIndex,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SizeConfig.init(context);
return Scaffold( return Scaffold(
body: navigationShell, body: navigationShell,
bottomNavigationBar: Container( bottomNavigationBar: Container(
@@ -37,7 +32,7 @@ class AppShellScreen extends StatelessWidget {
context: context, context: context,
text: 'Home', text: 'Home',
isActive: navigationShell.currentIndex == 0, isActive: navigationShell.currentIndex == 0,
onTap: () => _gotoBranch(0), onTap: () => onTap(0),
iconPath: 'assets/icons/home.svg', iconPath: 'assets/icons/home.svg',
activeIconPath: AssetsManager.homeActive, activeIconPath: AssetsManager.homeActive,
), ),
@@ -45,7 +40,7 @@ class AppShellScreen extends StatelessWidget {
context: context, context: context,
text: 'Tenders', text: 'Tenders',
isActive: navigationShell.currentIndex == 1, isActive: navigationShell.currentIndex == 1,
onTap: () => _gotoBranch(1), onTap: () => onTap(1),
iconPath: 'assets/icons/task_square.svg', iconPath: 'assets/icons/task_square.svg',
activeIconPath: 'assets/icons/task-square_active.svg', activeIconPath: 'assets/icons/task-square_active.svg',
), ),
@@ -53,7 +48,7 @@ class AppShellScreen extends StatelessWidget {
context: context, context: context,
text: 'Profile', text: 'Profile',
isActive: navigationShell.currentIndex == 2, isActive: navigationShell.currentIndex == 2,
onTap: () => _gotoBranch(2), onTap: () => onTap(2),
iconPath: 'assets/icons/profile-circle.svg', iconPath: 'assets/icons/profile-circle.svg',
activeIconPath: 'assets/icons/profile-circle_active.svg', activeIconPath: 'assets/icons/profile-circle_active.svg',
), ),
@@ -0,0 +1,126 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
class TabletShellPage extends StatelessWidget {
TabletShellPage({
required this.navigationShell,
required this.onTap,
super.key,
});
final StatefulNavigationShell navigationShell;
final ValueChanged<int> onTap;
final GlobalKey<ScaffoldState> _key = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
key: _key,
body: Stack(
children: [
navigationShell,
Container(
width: double.infinity,
height: 64.0.h(),
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: AppColors.grey30, width: 1),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Image.asset(
AssetsManager.logo,
width: 59.0.w(),
height: 28.0.h(),
),
InkWell(
onTap: () => _key.currentState!.openDrawer(),
child: SvgPicture.asset(AssetsManager.menu),
),
],
),
),
],
),
drawer: Container(
width: 200.0.w(),
height: double.infinity,
color: AppColors.backgroundColor,
child: Column(
children: [
SizedBox(height: 48.0.h()),
_drawerItem(
context: context,
text: 'Home',
isActive: navigationShell.currentIndex == 0,
onTap: () => onTap(0),
iconPath: AssetsManager.home,
activeIconPath: AssetsManager.homeActive,
),
_drawerItem(
context: context,
text: 'Tenders',
isActive: navigationShell.currentIndex == 1,
onTap: () => onTap(1),
iconPath: AssetsManager.tenders,
activeIconPath: AssetsManager.tendersActive,
),
_drawerItem(
context: context,
text: 'Profile',
isActive: navigationShell.currentIndex == 2,
onTap: () => onTap(2),
iconPath: AssetsManager.profile,
activeIconPath: AssetsManager.profileActive,
),
],
),
),
);
}
Widget _drawerItem({
required BuildContext context,
required String text,
required bool isActive,
required VoidCallback onTap,
required String iconPath,
required String activeIconPath,
}) {
return InkWell(
onTap: onTap,
child: SizedBox(
width: 200.0.w(),
height: 64.0.h(),
child: Padding(
padding: EdgeInsetsDirectional.only(start: 24.0.w()),
child: Row(
children: [
SvgPicture.asset(isActive ? activeIconPath : iconPath),
SizedBox(width: 8.0.w()),
Text(
text,
style: TextStyle(
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
color:
isActive
? const Color(0xFF0164FF)
: const Color(0xFF777777),
),
),
],
),
),
),
);
}
}
+2 -1
View File
@@ -3,7 +3,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'colors.dart'; import 'colors.dart';
class ThemeProvider extends ChangeNotifier { class ThemeProvider with ChangeNotifier {
static const String _themeKey = 'theme_mode'; static const String _themeKey = 'theme_mode';
static BuildContext? _context; static BuildContext? _context;
@@ -40,6 +40,7 @@ class ThemeProvider extends ChangeNotifier {
// Notify listeners after loading // Notify listeners after loading
notifyListeners(); notifyListeners();
_rebuildAllChildren();
} }
Future<void> toggleTheme() async { Future<void> toggleTheme() async {
+1 -1
View File
@@ -10,7 +10,7 @@ class AuthService {
// شبیه‌سازی API Call // شبیه‌سازی API Call
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 2));
if (email == 'test@example.com' && password == 'password123') { if (email == 'a' && password == 'a') {
return {'token': 'xyz123', 'name': 'John Doe'}; return {'token': 'xyz123', 'name': 'John Doe'};
} else { } else {
throw Exception('Invalid credentials'); throw Exception('Invalid credentials');
+1
View File
@@ -43,6 +43,7 @@ class _MyAppState extends State<MyApp> {
return MaterialApp.router( return MaterialApp.router(
title: 'Flutter Demo', title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
scrollBehavior: MaterialScrollBehavior().copyWith( scrollBehavior: MaterialScrollBehavior().copyWith(
dragDevices: { dragDevices: {
PointerDeviceKind.mouse, PointerDeviceKind.mouse,
+85
View File
@@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:tm_app/views/tenders/models/tender_model.dart';
class TendersViewModel with ChangeNotifier {
List<Tender> likedTenders = [];
List<Tender> dislikedTenders = [];
int currentPage = 1;
List<Tender> rejectedTenders = [];
List<Tender> submittedTenders = [];
void onDislike(Tender tender) {
if (likedTenders.contains(tender)) {
likedTenders.remove(tender);
if (submittedTenders.contains(tender)) {
submittedTenders.remove(tender);
}
}
if (!dislikedTenders.contains(tender)) {
dislikedTenders.add(tender);
if (submittedTenders.contains(tender)) {
submittedTenders.remove(tender);
}
} else {
dislikedTenders.remove(tender);
}
notifyListeners();
}
void onLike(Tender tender) {
if (dislikedTenders.contains(tender)) {
dislikedTenders.remove(tender);
if (rejectedTenders.contains(tender)) {
rejectedTenders.remove(tender);
}
}
if (!likedTenders.contains(tender)) {
likedTenders.add(tender);
if (rejectedTenders.contains(tender)) {
rejectedTenders.remove(tender);
}
} else {
likedTenders.remove(tender);
}
notifyListeners();
}
void onReject(Tender tender) {
if (rejectedTenders.contains(tender)) {
rejectedTenders.remove(tender);
} else if (submittedTenders.contains(tender)) {
submittedTenders.remove(tender);
rejectedTenders.add(tender);
if (likedTenders.contains(tender)) {
likedTenders.remove(tender);
}
} else {
rejectedTenders.add(tender);
if (likedTenders.contains(tender)) {
likedTenders.remove(tender);
}
}
notifyListeners();
}
void onSubmit(Tender tender) {
if (submittedTenders.contains(tender)) {
submittedTenders.remove(tender);
} else if (rejectedTenders.contains(tender)) {
rejectedTenders.remove(tender);
submittedTenders.add(tender);
if (dislikedTenders.contains(tender)) {
dislikedTenders.remove(tender);
}
} else {
submittedTenders.add(tender);
if (dislikedTenders.contains(tender)) {
dislikedTenders.remove(tender);
}
}
notifyListeners();
}
}
+3 -3
View File
@@ -12,9 +12,9 @@ class TenderDetailScreen extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
SizeConfig.init(context); SizeConfig.init(context);
return ResponsiveBuilder( return ResponsiveBuilder(
mobile: TenderDetailMobilePage(tenderId: 'JNDFKMDV-100JF',), mobile: TenderDetailMobilePage(tenderId: 'JNDFKMDV-100JF'),
tablet: TenderDetailTabletPage(tenderId: 'JNDFKMDV-100JF',), tablet: TenderDetailTabletPage(tenderId: 'JNDFKMDV-100JF'),
desktop: TenderDetailDesktopPage(tenderId: 'JNDFKMDV-100JF',), desktop: TenderDetailDesktopPage(tenderId: 'JNDFKMDV-100JF'),
); );
} }
} }
+3
View File
@@ -6,6 +6,7 @@ import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/data/services/model/home/home_response/home_response_model.dart'; import 'package:tm_app/data/services/model/home/home_response/home_response_model.dart';
import 'package:tm_app/view_models/home_view_model.dart'; import 'package:tm_app/view_models/home_view_model.dart';
import 'package:tm_app/views/home/widgets/notification_card.dart';
import '../widgets.dart'; import '../widgets.dart';
@@ -32,6 +33,8 @@ class DesktopHomePage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
SizedBox(height: 55), SizedBox(height: 55),
SizedBox(width: 780, child: NotificationCard()),
SizedBox(height: 40.0),
_progressBarsRow(homeViewModel.homeResponse!), _progressBarsRow(homeViewModel.homeResponse!),
SizedBox(height: 32.0.h()), SizedBox(height: 32.0.h()),
_firstTenderCardsRow(context, homeViewModel.homeResponse!), _firstTenderCardsRow(context, homeViewModel.homeResponse!),
+3
View File
@@ -7,6 +7,7 @@ import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/data/services/model/home/home_response/home_response_model.dart'; import 'package:tm_app/data/services/model/home/home_response/home_response_model.dart';
import 'package:tm_app/view_models/home_view_model.dart'; import 'package:tm_app/view_models/home_view_model.dart';
import 'package:tm_app/views/home/widgets/notification_card.dart';
import 'package:tm_app/views/shared/tender_app_bar.dart'; import 'package:tm_app/views/shared/tender_app_bar.dart';
import '../widgets.dart'; import '../widgets.dart';
@@ -63,6 +64,8 @@ class MobileHomePage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 18.0.h()), SizedBox(height: 18.0.h()),
NotificationCard(),
SizedBox(height: 32.0.h()),
_progressBarsRow(homeViewModel.homeResponse!), _progressBarsRow(homeViewModel.homeResponse!),
SizedBox(height: 32.0.h()), SizedBox(height: 32.0.h()),
_firstTenderCardsRow(context, homeViewModel.homeResponse!), _firstTenderCardsRow(context, homeViewModel.homeResponse!),
+3
View File
@@ -6,6 +6,7 @@ import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/data/services/model/home/home_response/home_response_model.dart'; import 'package:tm_app/data/services/model/home/home_response/home_response_model.dart';
import 'package:tm_app/view_models/home_view_model.dart'; import 'package:tm_app/view_models/home_view_model.dart';
import 'package:tm_app/views/home/widgets/notification_card.dart';
import '../widgets.dart'; import '../widgets.dart';
@@ -38,6 +39,8 @@ class TabletHomePage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
NotificationCard(),
SizedBox(height: 40.0.h()),
_progressBarsRow(homeViewModel.homeResponse!), _progressBarsRow(homeViewModel.homeResponse!),
SizedBox(height: 32.0.h()), SizedBox(height: 32.0.h()),
_firstTenderCardsRow(context, homeViewModel.homeResponse!), _firstTenderCardsRow(context, homeViewModel.homeResponse!),
@@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/constants/strings.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
class NotificationCard extends StatelessWidget {
const NotificationCard({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 24.0.w()),
padding: EdgeInsets.symmetric(horizontal: 16.0.w(), vertical: 16.0.h()),
decoration: BoxDecoration(
color: AppColors.mainBlue.withValues(alpha: 0.03),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: AppColors.mainBlue.withValues(alpha: 0.15)),
),
child: Column(
children: [
Row(
children: [
SvgPicture.asset(AssetsManager.notification),
SizedBox(width: 8.0.w()),
Text(
AppStrings.welcome,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.grey80,
),
),
],
),
SizedBox(height: 8.0.h()),
Text(
'Youve been invited Lorem ipsum dolor sit amet consectetur. Volutpat velit tincidunt amet diam. Placerat congue ut Sed facilisis',
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
SizedBox(height: 8.0.h()),
Padding(
padding: EdgeInsetsDirectional.only(start: 28.0),
child: Row(
children: [
SvgPicture.asset(AssetsManager.calendar),
SizedBox(width: 4.0.w()),
Text(
'5 Min',
style: TextStyle(
fontSize: 14.0.sp(),
color: AppColors.grey60,
),
),
],
),
),
],
),
);
}
}
@@ -14,8 +14,6 @@ class LoginMobilePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SizeConfig.init(context);
return Scaffold( return Scaffold(
backgroundColor: AppColors.backgroundColor, backgroundColor: AppColors.backgroundColor,
body: Center( body: Center(
@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import '../../../core/theme/colors.dart';
import '../../../core/utils/size_config.dart';
import '../widgets/theme_toggle.dart';
import '../widgets/title_description.dart';
class DesktopProfilePage extends StatelessWidget {
const DesktopProfilePage({super.key});
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: AppColors.backgroundColor,
body: Center(
child: SizedBox(
width: 740,
height: 507,
child: Column(
children: [
SizedBox(height: 36.0.h()),
TitleDescription(
title: 'Company Name',
description: 'Telecom Solutions GmbH',
),
TitleDescription(
title: 'National ID',
description: '12345678901',
),
TitleDescription(
title: 'Registration No.',
description: 'DE-55667788',
),
TitleDescription(
title: 'Business Type',
description: 'Telecommunications',
),
TitleDescription(title: 'Founded', description: '2010'),
SizedBox(height: 24.0.h()),
// Theme Toggle
Container(
padding: EdgeInsets.symmetric(
horizontal: 16.0.w(),
vertical: 12.0.h(),
),
decoration: BoxDecoration(
color: AppColors.surfaceColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColors.borderColor),
),
child: const ThemeToggle(),
),
],
),
),
),
),
);
}
}
@@ -1,13 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tm_app/core/constants/strings.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/shared/tender_app_bar.dart'; import 'package:tm_app/views/shared/tender_app_bar.dart';
import '../../core/theme/colors.dart'; import '../../../core/constants/strings.dart';
import '../../widgets/theme_toggle.dart'; import '../../../core/theme/colors.dart';
import '../../../core/utils/size_config.dart';
import '../widgets/theme_toggle.dart';
import '../widgets/title_description.dart';
class ProfileScreen extends StatelessWidget { class MobileProfilePage extends StatelessWidget {
const ProfileScreen({super.key}); const MobileProfilePage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -61,57 +62,3 @@ class ProfileScreen extends StatelessWidget {
); );
} }
} }
class TitleDescription extends StatelessWidget {
const TitleDescription({
required this.title,
required this.description,
super.key,
});
final String title;
final String description;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: 24.0.h()),
child: SizedBox(
width: double.infinity,
height: 43.0.h(),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
Text(
description,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey80,
),
),
],
),
Container(
width: double.infinity,
height: 1.0.h(),
color: AppColors.dividerColor,
),
],
),
),
);
}
}
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:tm_app/views/shared/responsive_builder.dart';
import '../pages/d_profile_page.dart';
import '../pages/m_profile_page.dart';
import '../pages/t_profile_page.dart';
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
mobile: MobileProfilePage(),
tablet: TabletProfilePage(),
desktop: DesktopProfilePage(),
);
}
}
@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import '../../../core/theme/colors.dart';
import '../../../core/utils/size_config.dart';
import '../widgets/theme_toggle.dart';
import '../widgets/title_description.dart';
class TabletProfilePage extends StatelessWidget {
const TabletProfilePage({super.key});
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: AppColors.backgroundColor,
body: Padding(
padding: EdgeInsets.symmetric(
horizontal: 24.0.w(),
vertical: 16.0.h(),
),
child: Column(
children: [
SizedBox(height: 128.0.h()),
TitleDescription(
title: 'Company Name',
description: 'Telecom Solutions GmbH',
),
TitleDescription(
title: 'National ID',
description: '12345678901',
),
TitleDescription(
title: 'Registration No.',
description: 'DE-55667788',
),
TitleDescription(
title: 'Business Type',
description: 'Telecommunications',
),
TitleDescription(title: 'Founded', description: '2010'),
SizedBox(height: 24.0.h()),
// Theme Toggle
Container(
padding: EdgeInsets.symmetric(
horizontal: 16.0.w(),
vertical: 12.0.h(),
),
decoration: BoxDecoration(
color: AppColors.surfaceColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColors.borderColor),
),
child: const ThemeToggle(),
),
],
),
),
),
);
}
}
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../core/theme/colors.dart'; import '../../../core/theme/colors.dart';
import '../core/theme/theme_provider.dart'; import '../../../core/theme/theme_provider.dart';
class ThemeToggle extends StatelessWidget { class ThemeToggle extends StatelessWidget {
const ThemeToggle({super.key}); const ThemeToggle({super.key});
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
class TitleDescription extends StatelessWidget {
const TitleDescription({
required this.title,
required this.description,
super.key,
});
final String title;
final String description;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(bottom: 24.0.h()),
child: SizedBox(
width: double.infinity,
height: 43.0.h(),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
Text(
description,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey80,
),
),
],
),
Container(
width: double.infinity,
height: 1.0.h(),
color: AppColors.dividerColor,
),
],
),
),
);
}
}
+86 -55
View File
@@ -1,72 +1,103 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:tm_app/core/constants/assets.dart'; import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
PreferredSize tenderMobileAppBar({required String title}) { // PreferredSize tenderMobileAppBar({required String title}) {
// return PreferredSize(
// preferredSize: Size.fromHeight(kToolbarHeight.h()),
// child: Container(
// color: AppColors.backgroundColor,
// padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
// height: 56.0.h(),
// width: double.infinity,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// title,
// style: TextStyle(
// fontSize: 20,
// fontWeight: FontWeight.bold,
// color: AppColors.grey70,
// ),
// ),
// Image.asset(
// 'assets/icons/tenderLogo.png',
// width: 59.0.w(),
// height: 28.0.h(),
// fit: BoxFit.cover,
// ),
// ],
// ),
// ),
// );
// }
PreferredSizeWidget tenderMobileAppBar({required String title}) {
return PreferredSize( return PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight.h()), preferredSize: const Size.fromHeight(56),
child: Container( child: AppBar(
color: AppColors.backgroundColor, backgroundColor: AppColors.backgroundColor,
padding: EdgeInsets.symmetric(horizontal: 24.0.w()), elevation: 0,
height: 56.0.h(), titleSpacing: 0,
width: double.infinity, automaticallyImplyLeading: false,
child: Row( actions: [
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: AppColors.grey70,
),
),
Image.asset( Image.asset(
'assets/icons/tenderLogo.png', AssetsManager.tenderLogo,
width: 59.0.w(), width: 59.0.w(),
height: 28.0.h(), height: 28.0.h(),
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
SizedBox(width: 24.0.w()),
], ],
title: Padding(
padding: EdgeInsetsDirectional.only(start: 24.0.w()),
child: Text(
title,
style: TextStyle(
color: AppColors.grey70,
fontWeight: FontWeight.w600,
fontSize: 20.0.sp(),
),
),
), ),
), ),
); );
} }
PreferredSize appBar({required String title, required BuildContext context}) { // PreferredSize appBar({required String title, required BuildContext context}) {
return PreferredSize( // return PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight.h()), // preferredSize: Size.fromHeight(kToolbarHeight.h()),
child: Container( // child: Container(
color: AppColors.backgroundColor, // color: AppColors.backgroundColor,
padding: EdgeInsets.symmetric(horizontal: 24.0.w()), // padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
height: 56.0.h(), // height: 56.0.h(),
width: double.infinity, // width: double.infinity,
child: Row( // child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ // children: [
GestureDetector( // GestureDetector(
onTap: () { // onTap: () {
Navigator.pop(context); // Navigator.pop(context);
}, // },
child: SvgPicture.asset( // child: SvgPicture.asset(
AssetsManager.arrowLeft, // AssetsManager.arrowLeft,
width: 24.0.w(), // width: 24.0.w(),
height: 24.0.h(), // height: 24.0.h(),
), // ),
), // ),
SizedBox(width: 2.0.w()), // SizedBox(width: 2.0.w()),
Text( // Text(
title, // title,
style: TextStyle( // style: TextStyle(
fontSize: 20, // fontSize: 20,
fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
color: AppColors.grey70, // color: AppColors.grey70,
), // ),
), // ),
], // ],
), // ),
), // ),
); // );
} // }
@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
class DesktopTendersPage extends StatelessWidget {
const DesktopTendersPage({super.key});
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: AppColors.backgroundColor,
body: Center(
child: SingleChildScrollView(
child: SizedBox(
width: 740,
height: 800,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 24.0.h()),
child: MainTendersSlider(isDesktop: true),
),
),
),
),
),
);
}
}
@@ -1,13 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tm_app/core/constants/strings.dart'; import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/shared/tender_app_bar.dart'; import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
import '../../core/theme/colors.dart'; import '../../../core/constants/strings.dart';
import '../../core/utils/size_config.dart'; import '../../../core/theme/colors.dart';
import 'widgets/main_tenders_slider.dart'; import '../../shared/tender_app_bar.dart';
class TendersScreen extends StatelessWidget { class MobileTendersPage extends StatelessWidget {
const TendersScreen({super.key}); const MobileTendersPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
class TabletTendersPage extends StatelessWidget {
const TabletTendersPage({super.key});
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: AppColors.backgroundColor,
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 24.0.h()),
child: MainTendersSlider(),
),
),
),
);
}
}
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:tm_app/views/shared/responsive_builder.dart';
import 'd_tenders_page.dart';
import 'm_tenders_page.dart';
import 't_tenders_page.dart';
class TendersScreen extends StatelessWidget {
const TendersScreen({super.key});
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
mobile: MobileTendersPage(),
tablet: TabletTendersPage(),
desktop: DesktopTendersPage(),
);
}
}
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:tm_app/core/routes/app_routes.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
@@ -9,7 +8,8 @@ import '../models/tender_model.dart';
import 'tender_card.dart'; import 'tender_card.dart';
class MainTendersSlider extends StatefulWidget { class MainTendersSlider extends StatefulWidget {
const MainTendersSlider({super.key}); const MainTendersSlider({super.key, this.isDesktop = false});
final bool isDesktop;
@override @override
State<MainTendersSlider> createState() => _MainTendersSliderState(); State<MainTendersSlider> createState() => _MainTendersSliderState();
@@ -18,37 +18,7 @@ class MainTendersSlider extends StatefulWidget {
class _MainTendersSliderState extends State<MainTendersSlider> { class _MainTendersSliderState extends State<MainTendersSlider> {
final PageController pageController = PageController(initialPage: 0); final PageController pageController = PageController(initialPage: 0);
List<Tender> likedTenders = [];
List<Tender> dislikedTenders = [];
int currentPage = 1; int currentPage = 1;
List<Tender> rejectedTenders = [];
List<Tender> submittedTenders = [];
void _onDislike(Tender tender) {
if (likedTenders.contains(tender)) {
likedTenders.remove(tender);
}
if (!dislikedTenders.contains(tender)) {
dislikedTenders.add(tender);
} else {
dislikedTenders.remove(tender);
}
setState(() {});
}
void _onLike(Tender tender) {
if (dislikedTenders.contains(tender)) {
dislikedTenders.remove(tender);
}
if (!likedTenders.contains(tender)) {
likedTenders.add(tender);
} else {
likedTenders.remove(tender);
}
setState(() {});
}
void _goToPreviousPage() { void _goToPreviousPage() {
pageController.previousPage( pageController.previousPage(
@@ -76,25 +46,6 @@ class _MainTendersSliderState extends State<MainTendersSlider> {
}); });
} }
void _onReject(Tender tender) {
if (rejectedTenders.contains(tender)) {
rejectedTenders.remove(tender);
} else {
rejectedTenders.add(tender);
}
setState(() {});
}
void _onSubmit(Tender tender) {
if (submittedTenders.contains(tender)) {
submittedTenders.remove(tender);
} else {
submittedTenders.add(tender);
}
setState(() {});
}
@override @override
void dispose() { void dispose() {
pageController.dispose(); pageController.dispose();
@@ -119,25 +70,7 @@ class _MainTendersSliderState extends State<MainTendersSlider> {
}, },
itemBuilder: (context, index) { itemBuilder: (context, index) {
final tender = sampleTenders[index]; final tender = sampleTenders[index];
return TenderCard( return TenderCard(tender: tender, isDesktop: widget.isDesktop);
date: tender.date,
deadline: tender.deadline,
title: tender.title,
description: tender.description,
tenderId: tender.tenderId,
location: tender.location,
country: tender.country,
matchPercentage: tender.matchPercentage,
isLiked: likedTenders.contains(tender),
isDisliked: dislikedTenders.contains(tender),
onDislike: () => _onDislike(tender),
onLike: () => _onLike(tender),
onApply: () => TenderDetailRouteData().push(context),
onReject: () => _onReject(tender),
onSubmit: () => _onSubmit(tender),
rejected: rejectedTenders.contains(tender),
submitted: submittedTenders.contains(tender),
);
}, },
), ),
), ),
@@ -1,44 +1,38 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/view_models/tenders_view_model.dart';
import 'package:tm_app/views/tenders/models/tender_model.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
import '../../../core/theme/colors.dart'; import '../../../core/theme/colors.dart';
import '../../../core/utils/size_config.dart'; import '../../../core/utils/size_config.dart';
class TenderActionButtons extends StatelessWidget { class TenderActionButtons extends StatelessWidget {
final VoidCallback? onLike; final Tender tender;
final VoidCallback? onDislike; final bool isDesktop;
final VoidCallback? onClose;
final VoidCallback? onReject;
final VoidCallback? onSubmit;
final bool isLiked;
final bool isDisliked;
final bool rejected;
final bool submitted;
const TenderActionButtons({ const TenderActionButtons({
required this.tender,
this.isDesktop = false,
super.key, super.key,
this.onLike,
this.onDislike,
this.onClose,
this.onReject,
this.onSubmit,
this.isLiked = false,
this.isDisliked = false,
this.rejected = false,
this.submitted = false,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final viewModel = context.watch<TendersViewModel>();
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
SizedBox(width: 16.0.w()), SizedBox(width: 16.0.w()),
// Dislike button // Dislike button
GestureDetector( Tooltip(
onTap: onDislike, message: 'Dislike',
child: InkWell(
onTap: () {
viewModel.onDislike(tender);
},
child: Column( child: Column(
children: [ children: [
SvgPicture.asset( SvgPicture.asset(
@@ -47,61 +41,37 @@ class TenderActionButtons extends StatelessWidget {
fit: BoxFit.cover, fit: BoxFit.cover,
AssetsManager.dislike, AssetsManager.dislike,
colorFilter: ColorFilter.mode( colorFilter: ColorFilter.mode(
isDisliked ? AppColors.errorColor : AppColors.grey50, viewModel.dislikedTenders.contains(tender)
? AppColors.errorColor
: AppColors.grey50,
BlendMode.srcATop, BlendMode.srcATop,
), ),
), ),
SizedBox(height: 8.0.h()), SizedBox(height: 8.0.h()),
if (!isDesktop)
Text( Text(
'Like', 'Like',
style: TextStyle( style: TextStyle(
fontSize: 12.0.sp(), fontSize: 12.0.sp(),
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: isDisliked ? AppColors.errorColor : AppColors.grey50, color:
viewModel.dislikedTenders.contains(tender)
? AppColors.errorColor
: AppColors.grey50,
), ),
), ),
], ],
), ),
), ),
),
SizedBox(width: 32.0.w()), SizedBox(width: 32.0.w()),
//reject button //reject button
GestureDetector( Tooltip(
onTap: onReject, message: 'Reject',
child: Column( child: InkWell(
children: [ onTap: () {
Container( viewModel.onReject(tender);
width: 32.0.w(), },
height: 32.0.w(),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: rejected ? AppColors.errorColor : AppColors.grey60,
width: 1.5.w(),
),
),
alignment: Alignment.center,
child: Icon(
Icons.close,
color: rejected ? AppColors.errorColor : AppColors.grey60,
size: 16,
),
),
SizedBox(height: 8.0.h()),
Text(
'Reject',
style: TextStyle(
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
color: rejected ? AppColors.errorColor : AppColors.grey60,
),
),
],
),
),
SizedBox(width: 32.0.w()),
//submit button
GestureDetector(
onTap: onSubmit,
child: Column( child: Column(
children: [ children: [
Container( Container(
@@ -111,7 +81,59 @@ class TenderActionButtons extends StatelessWidget {
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: color:
submitted ? AppColors.successColor : AppColors.grey60, viewModel.rejectedTenders.contains(tender)
? AppColors.errorColor
: AppColors.grey60,
width: 1.5.w(),
),
),
alignment: Alignment.center,
child: Icon(
Icons.close,
color:
viewModel.rejectedTenders.contains(tender)
? AppColors.errorColor
: AppColors.grey60,
size: 16,
),
),
SizedBox(height: 8.0.h()),
if (!isDesktop)
Text(
'Reject',
style: TextStyle(
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
color:
viewModel.rejectedTenders.contains(tender)
? AppColors.errorColor
: AppColors.grey60,
),
),
],
),
),
),
SizedBox(width: 32.0.w()),
//submit button
Tooltip(
message: 'Submit',
child: InkWell(
onTap: () {
viewModel.onSubmit(tender);
},
child: Column(
children: [
Container(
width: 32.0.w(),
height: 32.0.w(),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color:
viewModel.submittedTenders.contains(tender)
? AppColors.successColor
: AppColors.grey60,
width: 1.5.w(), width: 1.5.w(),
), ),
), ),
@@ -119,27 +141,38 @@ class TenderActionButtons extends StatelessWidget {
child: SvgPicture.asset( child: SvgPicture.asset(
AssetsManager.tick, AssetsManager.tick,
colorFilter: ColorFilter.mode( colorFilter: ColorFilter.mode(
submitted ? AppColors.successColor : AppColors.grey60, viewModel.submittedTenders.contains(tender)
? AppColors.successColor
: AppColors.grey60,
BlendMode.srcATop, BlendMode.srcATop,
), ),
), ),
), ),
SizedBox(height: 8.0.h()), SizedBox(height: 8.0.h()),
if (!isDesktop)
Text( Text(
'Submit', 'Submit',
style: TextStyle( style: TextStyle(
fontSize: 12.0.sp(), fontSize: 12.0.sp(),
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: submitted ? AppColors.successColor : AppColors.grey60, color:
viewModel.submittedTenders.contains(tender)
? AppColors.successColor
: AppColors.grey60,
), ),
), ),
], ],
), ),
), ),
),
SizedBox(width: 32.0.w()), SizedBox(width: 32.0.w()),
// Like button // Like button
GestureDetector( Tooltip(
onTap: onLike, message: 'Like',
child: InkWell(
onTap: () {
viewModel.onLike(tender);
},
child: Column( child: Column(
children: [ children: [
SvgPicture.asset( SvgPicture.asset(
@@ -148,22 +181,29 @@ class TenderActionButtons extends StatelessWidget {
fit: BoxFit.cover, fit: BoxFit.cover,
AssetsManager.like, AssetsManager.like,
colorFilter: ColorFilter.mode( colorFilter: ColorFilter.mode(
isLiked ? AppColors.successColor : AppColors.grey50, viewModel.likedTenders.contains(tender)
? AppColors.successColor
: AppColors.grey50,
BlendMode.srcATop, BlendMode.srcATop,
), ),
), ),
SizedBox(height: 8.0.h()), SizedBox(height: 8.0.h()),
if (!isDesktop)
Text( Text(
'Like', 'Like',
style: TextStyle( style: TextStyle(
fontSize: 12.0.sp(), fontSize: 12.0.sp(),
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: isLiked ? AppColors.successColor : AppColors.grey50, color:
viewModel.likedTenders.contains(tender)
? AppColors.successColor
: AppColors.grey50,
), ),
), ),
], ],
), ),
), ),
),
], ],
); );
} }
+27 -59
View File
@@ -1,52 +1,18 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:tm_app/core/constants/strings.dart'; import 'package:tm_app/core/constants/strings.dart';
import 'package:tm_app/core/routes/app_routes.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
import '../../../core/theme/colors.dart'; import '../../../core/theme/colors.dart';
import '../../../core/utils/size_config.dart'; import '../../../core/utils/size_config.dart';
import '../models/tender_model.dart';
import 'tender_action_buttons.dart'; import 'tender_action_buttons.dart';
class TenderCard extends StatelessWidget { class TenderCard extends StatelessWidget {
final String date; final Tender tender;
final String deadline; final bool isDesktop;
final String title; const TenderCard({required this.tender, super.key, this.isDesktop = false});
final String description;
final String tenderId;
final String location;
final String country;
final double matchPercentage;
final VoidCallback? onApply;
final VoidCallback? onLike;
final VoidCallback? onDislike;
final VoidCallback? onClose;
final bool isLiked;
final bool isDisliked;
final VoidCallback? onReject;
final VoidCallback? onSubmit;
final bool rejected;
final bool submitted;
const TenderCard({
required this.date,
required this.deadline,
required this.title,
required this.description,
required this.tenderId,
required this.location,
required this.country,
required this.matchPercentage,
this.onApply,
this.onLike,
this.onDislike,
this.onClose,
this.isLiked = false,
this.isDisliked = false,
this.onReject,
this.onSubmit,
this.rejected = false,
this.submitted = false,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -71,14 +37,24 @@ class TenderCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Date // Date
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text( Text(
date, tender.date,
style: TextStyle( style: TextStyle(
color: AppColors.grey60, color: AppColors.grey60,
fontSize: 12.0.sp(), fontSize: 12.0.sp(),
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
), ),
if (isDesktop)
TenderActionButtons(
tender: tender,
isDesktop: isDesktop,
),
],
),
SizedBox(height: 12.0.h()), SizedBox(height: 12.0.h()),
// Deadline badge // Deadline badge
@@ -102,7 +78,7 @@ class TenderCard extends StatelessWidget {
), ),
), ),
Text( Text(
deadline, tender.deadline,
style: TextStyle( style: TextStyle(
color: AppColors.grey80, color: AppColors.grey80,
fontSize: 14.0.sp(), fontSize: 14.0.sp(),
@@ -116,7 +92,7 @@ class TenderCard extends StatelessWidget {
// Title // Title
Text( Text(
title, tender.title,
style: TextStyle( style: TextStyle(
color: AppColors.grey80, color: AppColors.grey80,
fontSize: 16.0.sp(), fontSize: 16.0.sp(),
@@ -127,7 +103,7 @@ class TenderCard extends StatelessWidget {
// Description // Description
Text( Text(
description, tender.description,
style: TextStyle( style: TextStyle(
color: AppColors.grey70, color: AppColors.grey70,
fontSize: 16.0.sp(), fontSize: 16.0.sp(),
@@ -139,7 +115,7 @@ class TenderCard extends StatelessWidget {
// Tender ID // Tender ID
Text( Text(
tenderId, tender.tenderId,
style: TextStyle( style: TextStyle(
color: AppColors.grey, color: AppColors.grey,
fontSize: 14.0.sp(), fontSize: 14.0.sp(),
@@ -168,7 +144,7 @@ class TenderCard extends StatelessWidget {
), ),
), ),
Text( Text(
'${matchPercentage.toInt()}%', '${tender.matchPercentage.toInt()}%',
style: TextStyle( style: TextStyle(
color: AppColors.secondaryTextColor, color: AppColors.secondaryTextColor,
fontSize: 16.0.sp(), fontSize: 16.0.sp(),
@@ -189,7 +165,7 @@ class TenderCard extends StatelessWidget {
), ),
child: FractionallySizedBox( child: FractionallySizedBox(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
widthFactor: matchPercentage / 100, widthFactor: tender.matchPercentage / 100,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.secondary50, color: AppColors.secondary50,
@@ -210,7 +186,7 @@ class TenderCard extends StatelessWidget {
SvgPicture.asset(AssetsManager.location), SvgPicture.asset(AssetsManager.location),
SizedBox(width: 4.0.w()), SizedBox(width: 4.0.w()),
Text( Text(
location, tender.location,
style: TextStyle( style: TextStyle(
color: AppColors.grey80, color: AppColors.grey80,
fontSize: 14.0.sp(), fontSize: 14.0.sp(),
@@ -230,7 +206,9 @@ class TenderCard extends StatelessWidget {
// See More button // See More button
GestureDetector( GestureDetector(
onTap: onApply, onTap: () {
TenderDetailRouteData().push(context);
},
child: Container( child: Container(
width: 108.0.w(), width: 108.0.w(),
height: 40.0.h(), height: 40.0.h(),
@@ -258,17 +236,7 @@ class TenderCard extends StatelessWidget {
), ),
), ),
SizedBox(height: 72.0.h()), SizedBox(height: 72.0.h()),
TenderActionButtons( if (!isDesktop) TenderActionButtons(tender: tender),
isDisliked: isDisliked,
isLiked: isLiked,
onDislike: onDislike,
onLike: onLike,
onClose: onClose,
onReject: onReject,
onSubmit: onSubmit,
rejected: rejected,
submitted: submitted,
),
], ],
); );
} }