check and fix in ui

This commit is contained in:
amirrezaghabeli
2025-08-10 16:05:03 +03:30
parent 55d7d96baf
commit 9ea9480818
35 changed files with 1066 additions and 416 deletions
+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/tender_detail_service.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/services/auth_service.dart';
@@ -59,6 +60,7 @@ List<SingleChildWidget> get viewModels {
(context) =>
TenderDetailViewModel(tenderDetailRepository: context.read()),
),
ChangeNotifierProvider(create: (context) => TendersViewModel()),
];
}
+8
View File
@@ -3,6 +3,11 @@ class AssetsManager {
//bottom navigation
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
static const logo = 'assets/pngs/logo.png';
@@ -30,6 +35,9 @@ class AssetsManager {
static const shield = 'assets/icons/shield.svg';
static const edit = 'assets/icons/edit.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
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 thunderStatus = 'Thunder status';
static const String yourTenders = 'Your tenders';
static const String welcome = 'Welcome';
//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/login/pages/login_screen.dart';
import '../../views/profile/profile_screen.dart';
import '../../views/tenders/tenders_screen.dart';
import '../../views/profile/pages/profile_screen.dart';
import '../../views/tenders/pages/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';
@@ -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_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';
import '../constants/assets.dart';
import '../theme/colors.dart';
import '../utils/size_config.dart';
class AppShellScreen extends StatelessWidget {
const AppShellScreen({required this.navigationShell, super.key});
class MobileShellPage extends StatelessWidget {
const MobileShellPage({
required this.navigationShell,
required this.onTap,
super.key,
});
final StatefulNavigationShell navigationShell;
void _gotoBranch(int index) {
navigationShell.goBranch(
index,
initialLocation: index == navigationShell.currentIndex,
);
}
final ValueChanged<int> onTap;
@override
Widget build(BuildContext context) {
SizeConfig.init(context);
return Scaffold(
body: navigationShell,
bottomNavigationBar: Container(
@@ -37,7 +32,7 @@ class AppShellScreen extends StatelessWidget {
context: context,
text: 'Home',
isActive: navigationShell.currentIndex == 0,
onTap: () => _gotoBranch(0),
onTap: () => onTap(0),
iconPath: 'assets/icons/home.svg',
activeIconPath: AssetsManager.homeActive,
),
@@ -45,7 +40,7 @@ class AppShellScreen extends StatelessWidget {
context: context,
text: 'Tenders',
isActive: navigationShell.currentIndex == 1,
onTap: () => _gotoBranch(1),
onTap: () => onTap(1),
iconPath: 'assets/icons/task_square.svg',
activeIconPath: 'assets/icons/task-square_active.svg',
),
@@ -53,7 +48,7 @@ class AppShellScreen extends StatelessWidget {
context: context,
text: 'Profile',
isActive: navigationShell.currentIndex == 2,
onTap: () => _gotoBranch(2),
onTap: () => onTap(2),
iconPath: 'assets/icons/profile-circle.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';
class ThemeProvider extends ChangeNotifier {
class ThemeProvider with ChangeNotifier {
static const String _themeKey = 'theme_mode';
static BuildContext? _context;
@@ -40,6 +40,7 @@ class ThemeProvider extends ChangeNotifier {
// Notify listeners after loading
notifyListeners();
_rebuildAllChildren();
}
Future<void> toggleTheme() async {
+1 -1
View File
@@ -10,7 +10,7 @@ class AuthService {
// شبیه‌سازی API Call
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'};
} else {
throw Exception('Invalid credentials');
+1
View File
@@ -43,6 +43,7 @@ class _MyAppState extends State<MyApp> {
return MaterialApp.router(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
scrollBehavior: MaterialScrollBehavior().copyWith(
dragDevices: {
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) {
SizeConfig.init(context);
return ResponsiveBuilder(
mobile: TenderDetailMobilePage(tenderId: 'JNDFKMDV-100JF',),
tablet: TenderDetailTabletPage(tenderId: 'JNDFKMDV-100JF',),
desktop: TenderDetailDesktopPage(tenderId: 'JNDFKMDV-100JF',),
mobile: TenderDetailMobilePage(tenderId: 'JNDFKMDV-100JF'),
tablet: TenderDetailTabletPage(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/data/services/model/home/home_response/home_response_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';
@@ -32,6 +33,8 @@ class DesktopHomePage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 55),
SizedBox(width: 780, child: NotificationCard()),
SizedBox(height: 40.0),
_progressBarsRow(homeViewModel.homeResponse!),
SizedBox(height: 32.0.h()),
_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/data/services/model/home/home_response/home_response_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 '../widgets.dart';
@@ -63,6 +64,8 @@ class MobileHomePage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 18.0.h()),
NotificationCard(),
SizedBox(height: 32.0.h()),
_progressBarsRow(homeViewModel.homeResponse!),
SizedBox(height: 32.0.h()),
_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/data/services/model/home/home_response/home_response_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';
@@ -38,6 +39,8 @@ class TabletHomePage extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
NotificationCard(),
SizedBox(height: 40.0.h()),
_progressBarsRow(homeViewModel.homeResponse!),
SizedBox(height: 32.0.h()),
_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
Widget build(BuildContext context) {
SizeConfig.init(context);
return Scaffold(
backgroundColor: AppColors.backgroundColor,
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: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 '../../core/theme/colors.dart';
import '../../widgets/theme_toggle.dart';
import '../../../core/constants/strings.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 {
const ProfileScreen({super.key});
class MobileProfilePage extends StatelessWidget {
const MobileProfilePage({super.key});
@override
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:provider/provider.dart';
import '../core/theme/colors.dart';
import '../core/theme/theme_provider.dart';
import '../../../core/theme/colors.dart';
import '../../../core/theme/theme_provider.dart';
class ThemeToggle extends StatelessWidget {
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,
),
],
),
),
);
}
}
+91 -60
View File
@@ -1,72 +1,103 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.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';
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(
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,
),
preferredSize: const Size.fromHeight(56),
child: AppBar(
backgroundColor: AppColors.backgroundColor,
elevation: 0,
titleSpacing: 0,
automaticallyImplyLeading: false,
actions: [
Image.asset(
AssetsManager.tenderLogo,
width: 59.0.w(),
height: 28.0.h(),
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(),
),
Image.asset(
'assets/icons/tenderLogo.png',
width: 59.0.w(),
height: 28.0.h(),
fit: BoxFit.cover,
),
],
),
),
),
);
}
PreferredSize appBar({required String title, required BuildContext context}) {
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: [
GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: SvgPicture.asset(
AssetsManager.arrowLeft,
width: 24.0.w(),
height: 24.0.h(),
),
),
SizedBox(width: 2.0.w()),
Text(
title,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: AppColors.grey70,
),
),
],
),
),
);
}
// PreferredSize appBar({required String title, required BuildContext context}) {
// 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: [
// GestureDetector(
// onTap: () {
// Navigator.pop(context);
// },
// child: SvgPicture.asset(
// AssetsManager.arrowLeft,
// width: 24.0.w(),
// height: 24.0.h(),
// ),
// ),
// SizedBox(width: 2.0.w()),
// Text(
// title,
// style: TextStyle(
// fontSize: 20,
// fontWeight: FontWeight.bold,
// 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:tm_app/core/constants/strings.dart';
import 'package:tm_app/views/shared/tender_app_bar.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
import '../../core/theme/colors.dart';
import '../../core/utils/size_config.dart';
import 'widgets/main_tenders_slider.dart';
import '../../../core/constants/strings.dart';
import '../../../core/theme/colors.dart';
import '../../shared/tender_app_bar.dart';
class TendersScreen extends StatelessWidget {
const TendersScreen({super.key});
class MobileTendersPage extends StatelessWidget {
const MobileTendersPage({super.key});
@override
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_svg/svg.dart';
import 'package:tm_app/core/routes/app_routes.dart';
import 'package:tm_app/core/theme/colors.dart';
import '../../../core/constants/assets.dart';
@@ -9,7 +8,8 @@ import '../models/tender_model.dart';
import 'tender_card.dart';
class MainTendersSlider extends StatefulWidget {
const MainTendersSlider({super.key});
const MainTendersSlider({super.key, this.isDesktop = false});
final bool isDesktop;
@override
State<MainTendersSlider> createState() => _MainTendersSliderState();
@@ -18,37 +18,7 @@ class MainTendersSlider extends StatefulWidget {
class _MainTendersSliderState extends State<MainTendersSlider> {
final PageController pageController = PageController(initialPage: 0);
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 (!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() {
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
void dispose() {
pageController.dispose();
@@ -119,25 +70,7 @@ class _MainTendersSliderState extends State<MainTendersSlider> {
},
itemBuilder: (context, index) {
final tender = sampleTenders[index];
return TenderCard(
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),
);
return TenderCard(tender: tender, isDesktop: widget.isDesktop);
},
),
),
@@ -1,167 +1,207 @@
import 'package:flutter/material.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/theme/colors.dart';
import '../../../core/utils/size_config.dart';
class TenderActionButtons extends StatelessWidget {
final VoidCallback? onLike;
final VoidCallback? onDislike;
final VoidCallback? onClose;
final VoidCallback? onReject;
final VoidCallback? onSubmit;
final bool isLiked;
final bool isDisliked;
final bool rejected;
final bool submitted;
final Tender tender;
final bool isDesktop;
const TenderActionButtons({
required this.tender,
this.isDesktop = false,
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
Widget build(BuildContext context) {
final viewModel = context.watch<TendersViewModel>();
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(width: 16.0.w()),
// Dislike button
GestureDetector(
onTap: onDislike,
child: Column(
children: [
SvgPicture.asset(
width: 32.0.w(),
height: 32.0.w(),
fit: BoxFit.cover,
AssetsManager.dislike,
colorFilter: ColorFilter.mode(
isDisliked ? AppColors.errorColor : AppColors.grey50,
BlendMode.srcATop,
Tooltip(
message: 'Dislike',
child: InkWell(
onTap: () {
viewModel.onDislike(tender);
},
child: Column(
children: [
SvgPicture.asset(
width: 32.0.w(),
height: 32.0.w(),
fit: BoxFit.cover,
AssetsManager.dislike,
colorFilter: ColorFilter.mode(
viewModel.dislikedTenders.contains(tender)
? AppColors.errorColor
: AppColors.grey50,
BlendMode.srcATop,
),
),
),
SizedBox(height: 8.0.h()),
Text(
'Like',
style: TextStyle(
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
color: isDisliked ? AppColors.errorColor : AppColors.grey50,
),
),
],
SizedBox(height: 8.0.h()),
if (!isDesktop)
Text(
'Like',
style: TextStyle(
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
color:
viewModel.dislikedTenders.contains(tender)
? AppColors.errorColor
: AppColors.grey50,
),
),
],
),
),
),
SizedBox(width: 32.0.w()),
//reject button
GestureDetector(
onTap: onReject,
child: Column(
children: [
Container(
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(),
Tooltip(
message: 'Reject',
child: InkWell(
onTap: () {
viewModel.onReject(tender);
},
child: Column(
children: [
Container(
width: 32.0.w(),
height: 32.0.w(),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color:
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,
),
),
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(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
GestureDetector(
onTap: onSubmit,
child: Column(
children: [
Container(
width: 32.0.w(),
height: 32.0.w(),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color:
submitted ? AppColors.successColor : AppColors.grey60,
width: 1.5.w(),
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(),
),
),
alignment: Alignment.center,
child: SvgPicture.asset(
AssetsManager.tick,
colorFilter: ColorFilter.mode(
viewModel.submittedTenders.contains(tender)
? AppColors.successColor
: AppColors.grey60,
BlendMode.srcATop,
),
),
),
alignment: Alignment.center,
child: SvgPicture.asset(
AssetsManager.tick,
colorFilter: ColorFilter.mode(
submitted ? AppColors.successColor : AppColors.grey60,
BlendMode.srcATop,
SizedBox(height: 8.0.h()),
if (!isDesktop)
Text(
'Submit',
style: TextStyle(
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
color:
viewModel.submittedTenders.contains(tender)
? AppColors.successColor
: AppColors.grey60,
),
),
),
),
SizedBox(height: 8.0.h()),
Text(
'Submit',
style: TextStyle(
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
color: submitted ? AppColors.successColor : AppColors.grey60,
),
),
],
],
),
),
),
SizedBox(width: 32.0.w()),
// Like button
GestureDetector(
onTap: onLike,
child: Column(
children: [
SvgPicture.asset(
width: 32.0.w(),
height: 32.0.w(),
fit: BoxFit.cover,
AssetsManager.like,
colorFilter: ColorFilter.mode(
isLiked ? AppColors.successColor : AppColors.grey50,
BlendMode.srcATop,
Tooltip(
message: 'Like',
child: InkWell(
onTap: () {
viewModel.onLike(tender);
},
child: Column(
children: [
SvgPicture.asset(
width: 32.0.w(),
height: 32.0.w(),
fit: BoxFit.cover,
AssetsManager.like,
colorFilter: ColorFilter.mode(
viewModel.likedTenders.contains(tender)
? AppColors.successColor
: AppColors.grey50,
BlendMode.srcATop,
),
),
),
SizedBox(height: 8.0.h()),
Text(
'Like',
style: TextStyle(
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
color: isLiked ? AppColors.successColor : AppColors.grey50,
),
),
],
SizedBox(height: 8.0.h()),
if (!isDesktop)
Text(
'Like',
style: TextStyle(
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
color:
viewModel.likedTenders.contains(tender)
? AppColors.successColor
: AppColors.grey50,
),
),
],
),
),
),
],
+33 -65
View File
@@ -1,52 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.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/theme/colors.dart';
import '../../../core/utils/size_config.dart';
import '../models/tender_model.dart';
import 'tender_action_buttons.dart';
class TenderCard extends StatelessWidget {
final String date;
final String deadline;
final String title;
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,
});
final Tender tender;
final bool isDesktop;
const TenderCard({required this.tender, super.key, this.isDesktop = false});
@override
Widget build(BuildContext context) {
@@ -71,13 +37,23 @@ class TenderCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Date
Text(
date,
style: TextStyle(
color: AppColors.grey60,
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
tender.date,
style: TextStyle(
color: AppColors.grey60,
fontSize: 12.0.sp(),
fontWeight: FontWeight.w400,
),
),
if (isDesktop)
TenderActionButtons(
tender: tender,
isDesktop: isDesktop,
),
],
),
SizedBox(height: 12.0.h()),
@@ -102,7 +78,7 @@ class TenderCard extends StatelessWidget {
),
),
Text(
deadline,
tender.deadline,
style: TextStyle(
color: AppColors.grey80,
fontSize: 14.0.sp(),
@@ -116,7 +92,7 @@ class TenderCard extends StatelessWidget {
// Title
Text(
title,
tender.title,
style: TextStyle(
color: AppColors.grey80,
fontSize: 16.0.sp(),
@@ -127,7 +103,7 @@ class TenderCard extends StatelessWidget {
// Description
Text(
description,
tender.description,
style: TextStyle(
color: AppColors.grey70,
fontSize: 16.0.sp(),
@@ -139,7 +115,7 @@ class TenderCard extends StatelessWidget {
// Tender ID
Text(
tenderId,
tender.tenderId,
style: TextStyle(
color: AppColors.grey,
fontSize: 14.0.sp(),
@@ -168,7 +144,7 @@ class TenderCard extends StatelessWidget {
),
),
Text(
'${matchPercentage.toInt()}%',
'${tender.matchPercentage.toInt()}%',
style: TextStyle(
color: AppColors.secondaryTextColor,
fontSize: 16.0.sp(),
@@ -189,7 +165,7 @@ class TenderCard extends StatelessWidget {
),
child: FractionallySizedBox(
alignment: Alignment.centerLeft,
widthFactor: matchPercentage / 100,
widthFactor: tender.matchPercentage / 100,
child: Container(
decoration: BoxDecoration(
color: AppColors.secondary50,
@@ -210,7 +186,7 @@ class TenderCard extends StatelessWidget {
SvgPicture.asset(AssetsManager.location),
SizedBox(width: 4.0.w()),
Text(
location,
tender.location,
style: TextStyle(
color: AppColors.grey80,
fontSize: 14.0.sp(),
@@ -230,7 +206,9 @@ class TenderCard extends StatelessWidget {
// See More button
GestureDetector(
onTap: onApply,
onTap: () {
TenderDetailRouteData().push(context);
},
child: Container(
width: 108.0.w(),
height: 40.0.h(),
@@ -258,17 +236,7 @@ class TenderCard extends StatelessWidget {
),
),
SizedBox(height: 72.0.h()),
TenderActionButtons(
isDisliked: isDisliked,
isLiked: isLiked,
onDislike: onDislike,
onLike: onLike,
onClose: onClose,
onReject: onReject,
onSubmit: onSubmit,
rejected: rejected,
submitted: submitted,
),
if (!isDesktop) TenderActionButtons(tender: tender),
],
);
}