Add board navigation and assets
- Introduced new board-related assets in AssetsManager. - Updated NetworkManager to increase connection and receive timeouts to 20 seconds. - Added BoardRouteData for navigation to the BoardScreen. - Updated app routes to include the new board route. - Modified DesktopNavigationWidget to include a navigation item for the board. - Added a new string constant for the board label in TendersStrings.
This commit is contained in:
@@ -0,0 +1,468 @@
|
||||
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/shared/desktop_navigation_widget.dart';
|
||||
|
||||
// مدل Task برای نگهداری اطلاعات هر کارت
|
||||
class TaskItem {
|
||||
final String id;
|
||||
final String title;
|
||||
final String description;
|
||||
final DateTime deadline;
|
||||
final String assignedTo;
|
||||
final bool isReady;
|
||||
TaskStatus status;
|
||||
|
||||
TaskItem({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.deadline,
|
||||
required this.assignedTo,
|
||||
required this.isReady,
|
||||
required this.status,
|
||||
});
|
||||
}
|
||||
|
||||
enum TaskStatus { toDo, inProgress, done }
|
||||
|
||||
class BoardScreen extends StatefulWidget {
|
||||
const BoardScreen({super.key});
|
||||
|
||||
@override
|
||||
State<BoardScreen> createState() => _BoardScreenState();
|
||||
}
|
||||
|
||||
class _BoardScreenState extends State<BoardScreen> {
|
||||
// لیست تسکهای نمونه
|
||||
List<TaskItem> tasks = [
|
||||
TaskItem(
|
||||
id: '1',
|
||||
title: 'موضوع مناقصه',
|
||||
description: 'لورم سد می تورتور اوی پوروس سنکتوس. موربی لائورت تورتور...',
|
||||
deadline: DateTime(2025, 6, 15),
|
||||
assignedTo: 'Pratyush Tewari',
|
||||
isReady: false,
|
||||
status: TaskStatus.toDo,
|
||||
),
|
||||
TaskItem(
|
||||
id: '2',
|
||||
title: 'موضوع مناقصه',
|
||||
description: 'لورم سد موربی لائورت تورتور...',
|
||||
deadline: DateTime(2025, 6, 15),
|
||||
assignedTo: 'Pratyush Tewari',
|
||||
isReady: true,
|
||||
status: TaskStatus.toDo,
|
||||
),
|
||||
TaskItem(
|
||||
id: '3',
|
||||
title: 'موضوع مناقصه',
|
||||
description:
|
||||
'لورم ایپسوم. سد می تورتور اوی پوروس سنکتوس. موربی لائورت تورتور...',
|
||||
deadline: DateTime(2025, 6, 15),
|
||||
assignedTo: 'Pratyush Tewari',
|
||||
isReady: false,
|
||||
status: TaskStatus.inProgress,
|
||||
),
|
||||
TaskItem(
|
||||
id: '4',
|
||||
title: 'موضوع مناقصه',
|
||||
description: 'لورم ایپسوم امت...',
|
||||
deadline: DateTime(2025, 6, 15),
|
||||
assignedTo: 'Pratyush Tewari',
|
||||
isReady: true,
|
||||
status: TaskStatus.inProgress,
|
||||
),
|
||||
TaskItem(
|
||||
id: '5',
|
||||
title: 'موضوع مناقصه',
|
||||
description: 'لورم سد می تورتور اوی پوروس سنکتوس. موربی لائورت تورتور...',
|
||||
deadline: DateTime(2025, 6, 15),
|
||||
assignedTo: 'Pratyush Tewari',
|
||||
isReady: false,
|
||||
status: TaskStatus.done,
|
||||
),
|
||||
];
|
||||
|
||||
// متد برای تغییر وضعیت task
|
||||
void _updateTaskStatus(TaskItem task, TaskStatus newStatus) {
|
||||
// اگر وضعیت تغییر نکرده، نیازی به آپدیت نیست
|
||||
if (task.status == newStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
final oldStatus = task.status;
|
||||
setState(() {
|
||||
task.status = newStatus;
|
||||
});
|
||||
|
||||
// نمایش پیام تایید
|
||||
_showStatusChangeMessage(context, task, oldStatus, newStatus);
|
||||
}
|
||||
|
||||
// نمایش پیام تغییر وضعیت
|
||||
void _showStatusChangeMessage(
|
||||
BuildContext context,
|
||||
TaskItem task,
|
||||
TaskStatus oldStatus,
|
||||
TaskStatus newStatus,
|
||||
) {
|
||||
final statusNames = {
|
||||
TaskStatus.toDo: 'To Do',
|
||||
TaskStatus.inProgress: 'In Progress',
|
||||
TaskStatus.done: 'Done',
|
||||
};
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'کارت "${task.title}" از ${statusNames[oldStatus]} به ${statusNames[newStatus]} منتقل شد',
|
||||
style: const TextStyle(fontFamily: 'Peyda'),
|
||||
),
|
||||
duration: const Duration(seconds: 2),
|
||||
backgroundColor: AppColors.successColor,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// گرفتن تسکهای هر دسته
|
||||
List<TaskItem> _getTasksByStatus(TaskStatus status) {
|
||||
return tasks.where((task) => task.status == status).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SizeConfig.init(context);
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
|
||||
body: Column(
|
||||
children: [
|
||||
const DesktopNavigationWidget(
|
||||
currentIndex: 2, // Tenders index
|
||||
),
|
||||
SizedBox(
|
||||
width: 740,
|
||||
height: 735,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 64),
|
||||
Text(
|
||||
'My board',
|
||||
style: TextStyle(
|
||||
fontSize: 20.0.sp(),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.titleColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: AppColors.primary20,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// ستون To Do
|
||||
Expanded(
|
||||
child: _BoardColumn(
|
||||
title: 'To do',
|
||||
color: AppColors.primary20,
|
||||
textColor: const Color(0xFF1976D2),
|
||||
tasks: _getTasksByStatus(TaskStatus.toDo),
|
||||
status: TaskStatus.toDo,
|
||||
onTaskDropped: _updateTaskStatus,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// ستون In Progress
|
||||
Expanded(
|
||||
child: _BoardColumn(
|
||||
title: 'In progress',
|
||||
color: const Color(0xFFFFF3E0),
|
||||
textColor: const Color(0xFFF57C00),
|
||||
tasks: _getTasksByStatus(TaskStatus.inProgress),
|
||||
status: TaskStatus.inProgress,
|
||||
onTaskDropped: _updateTaskStatus,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// ستون Done
|
||||
Expanded(
|
||||
child: _BoardColumn(
|
||||
title: 'Done',
|
||||
color: const Color(0xFFE8F5E9),
|
||||
textColor: const Color(0xFF388E3C),
|
||||
tasks: _getTasksByStatus(TaskStatus.done),
|
||||
status: TaskStatus.done,
|
||||
onTaskDropped: _updateTaskStatus,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ویجت ستون بورد
|
||||
class _BoardColumn extends StatelessWidget {
|
||||
final String title;
|
||||
final Color color;
|
||||
final Color textColor;
|
||||
final List<TaskItem> tasks;
|
||||
final TaskStatus status;
|
||||
final Function(TaskItem, TaskStatus) onTaskDropped;
|
||||
|
||||
const _BoardColumn({
|
||||
required this.title,
|
||||
required this.color,
|
||||
required this.textColor,
|
||||
required this.tasks,
|
||||
required this.status,
|
||||
required this.onTaskDropped,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DragTarget<TaskItem>(
|
||||
onWillAcceptWithDetails: (details) {
|
||||
// فقط اگر task از ستون دیگری باشد، قبول کن
|
||||
return details.data.status != status;
|
||||
},
|
||||
onAcceptWithDetails: (details) {
|
||||
onTaskDropped(details.data, status);
|
||||
},
|
||||
builder: (context, candidateData, rejectedData) {
|
||||
final isHovering = candidateData.isNotEmpty;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isHovering ? color.withOpacity(0.3) : AppColors.primary0,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isHovering ? textColor : AppColors.borderBlue,
|
||||
width: isHovering ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// هدر ستون
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 34,
|
||||
margin: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
// لیست تسکها
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.all(12),
|
||||
itemCount: tasks.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _TaskCard(task: tasks[index]);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ویجت کارت Task
|
||||
class _TaskCard extends StatelessWidget {
|
||||
final TaskItem task;
|
||||
|
||||
const _TaskCard({required this.task});
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Draggable<TaskItem>(
|
||||
data: task,
|
||||
feedback: Material(
|
||||
elevation: 8,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
width: 280,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary0,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.borderBlue, width: 2),
|
||||
),
|
||||
child: _buildCardContent(context),
|
||||
),
|
||||
),
|
||||
childWhenDragging: Opacity(opacity: 0.3, child: _buildCard(context)),
|
||||
child: _buildCard(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCard(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary0,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.grey30),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: _buildCardContent(context),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCardContent(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Deadline
|
||||
Container(
|
||||
height: 24.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.0.w()),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary20,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Deadline :',
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.textBlue,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_formatDate(task.deadline),
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey80,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Row(
|
||||
// children: [
|
||||
// Text(
|
||||
// 'Deadline:',
|
||||
// style: TextStyle(
|
||||
// fontSize: 12,
|
||||
// color: AppColors.grey60,
|
||||
// fontWeight: FontWeight.w500,
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 8),
|
||||
// Text(
|
||||
// _formatDate(task.deadline),
|
||||
// style: TextStyle(
|
||||
// fontSize: 12,
|
||||
// color: AppColors.primaryTextColor,
|
||||
// fontWeight: FontWeight.w600,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
const SizedBox(height: 12),
|
||||
// عنوان
|
||||
Text(
|
||||
task.title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.primaryTextColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// توضیحات
|
||||
Text(
|
||||
task.description,
|
||||
style: TextStyle(fontSize: 12, color: AppColors.grey60, height: 1.5),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// وضعیت و اطلاعات کاربر
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// وضعیت
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: task.isReady ? Colors.blue : Colors.red,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
task.isReady ? 'Ready' : 'Incomplete',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: task.isReady ? Colors.blue : Colors.red,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// آیکون کاربر و نام
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.account_circle, size: 16, color: AppColors.grey60),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
task.assignedTo,
|
||||
style: TextStyle(fontSize: 11, color: AppColors.grey60),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -86,18 +86,21 @@ class DesktopNavigationWidget extends StatelessWidget {
|
||||
const SizedBox(width: 24),
|
||||
_navigationItem(
|
||||
context: context,
|
||||
text: TendersStrings.contracts,
|
||||
text: TendersStrings.board,
|
||||
isActive: currentIndex == 2,
|
||||
onTap: () {
|
||||
if (currentIndex == 2) {
|
||||
return;
|
||||
} else {
|
||||
// Router.neglect(context, () => TendersRouteData().go(context));
|
||||
Router.neglect(
|
||||
context,
|
||||
() => const BoardRouteData().go(context),
|
||||
);
|
||||
// context.read<TendersViewModel>().getTenders();
|
||||
}
|
||||
},
|
||||
iconPath: AssetsManager.contracts,
|
||||
activeIconPath: AssetsManager.contracts,
|
||||
iconPath: AssetsManager.board,
|
||||
activeIconPath: AssetsManager.boardActive,
|
||||
),
|
||||
const SizedBox(width: 24),
|
||||
_navigationItem(
|
||||
|
||||
@@ -26,4 +26,5 @@ class TendersStrings {
|
||||
static const String mostOfTheTime = 'Most of the time';
|
||||
static const String minimumTime = 'Minimum time';
|
||||
static const String tenderBudgetLabel = 'Budget :';
|
||||
static const String board = 'Board';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user