Add board service and repository, update routes and assets
- Introduced BoardService and BoardRepository for managing board-related data. - Updated app routes to reflect the new board screen structure. - Added new board-related assets to the asset manager. - Refactored BoardScreen navigation to utilize the new provider structure.
This commit is contained in:
@@ -1,30 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tm_app/core/extensions/board_data_extension.dart';
|
||||
import 'package:tm_app/core/theme/colors.dart';
|
||||
import 'package:tm_app/core/utils/size_config.dart';
|
||||
import 'package:tm_app/data/services/model/board_response/board_response.dart';
|
||||
import 'package:tm_app/views/shared/desktop_navigation_widget.dart';
|
||||
|
||||
import '../../core/constants/assets.dart';
|
||||
|
||||
class TaskItem {
|
||||
final String id;
|
||||
final String description;
|
||||
final DateTime deadline;
|
||||
final String assignedTo;
|
||||
final bool isReady;
|
||||
TaskStatus status;
|
||||
|
||||
TaskItem({
|
||||
required this.id,
|
||||
required this.description,
|
||||
required this.deadline,
|
||||
required this.assignedTo,
|
||||
required this.isReady,
|
||||
required this.status,
|
||||
});
|
||||
}
|
||||
|
||||
enum TaskStatus { toDo, inProgress, done }
|
||||
import '../../../core/constants/assets.dart';
|
||||
import '../../../core/enums/task_status.dart';
|
||||
import '../../../core/utils/app_toast.dart';
|
||||
import '../../../view_models/board_view_model.dart';
|
||||
import '../strings/board_strings.dart';
|
||||
|
||||
class BoardScreen extends StatefulWidget {
|
||||
const BoardScreen({super.key});
|
||||
@@ -34,175 +21,165 @@ class BoardScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _BoardScreenState extends State<BoardScreen> {
|
||||
List<TaskItem> tasks = [
|
||||
TaskItem(
|
||||
id: '1',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
||||
deadline: DateTime(2025, 6, 15),
|
||||
assignedTo: 'Pratyush Tewari',
|
||||
isReady: false,
|
||||
status: TaskStatus.toDo,
|
||||
),
|
||||
TaskItem(
|
||||
id: '2',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
||||
deadline: DateTime(2025, 6, 15),
|
||||
assignedTo: 'Pratyush Tewari',
|
||||
isReady: true,
|
||||
status: TaskStatus.toDo,
|
||||
),
|
||||
TaskItem(
|
||||
id: '3',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
||||
deadline: DateTime(2025, 6, 15),
|
||||
assignedTo: 'Pratyush Tewari',
|
||||
isReady: false,
|
||||
status: TaskStatus.inProgress,
|
||||
),
|
||||
TaskItem(
|
||||
id: '4',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
||||
deadline: DateTime(2025, 6, 15),
|
||||
assignedTo: 'Pratyush Tewari',
|
||||
isReady: true,
|
||||
status: TaskStatus.inProgress,
|
||||
),
|
||||
TaskItem(
|
||||
id: '5',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua',
|
||||
deadline: DateTime(2025, 6, 15),
|
||||
assignedTo: 'Pratyush Tewari',
|
||||
isReady: false,
|
||||
status: TaskStatus.done,
|
||||
),
|
||||
];
|
||||
late BoardViewModel _boardViewModel;
|
||||
|
||||
void _updateTaskStatus(TaskItem task, TaskStatus newStatus) {
|
||||
if (task.status == newStatus) {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_boardViewModel = context.read<BoardViewModel>();
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
_boardViewModel.getBoard();
|
||||
});
|
||||
}
|
||||
|
||||
void _updateTaskStatus(BoardData task, TaskStatus newStatus) {
|
||||
if (task.statusEnum == newStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
final oldStatus = task.status;
|
||||
setState(() {
|
||||
task.status = newStatus;
|
||||
});
|
||||
final oldStatus = task.statusEnum;
|
||||
|
||||
// update task status in ViewModel
|
||||
_boardViewModel.updateTaskStatus(task.id!, newStatus.value);
|
||||
|
||||
_showStatusChangeMessage(context, task, oldStatus, newStatus);
|
||||
}
|
||||
|
||||
void _showStatusChangeMessage(
|
||||
BuildContext context,
|
||||
TaskItem task,
|
||||
BoardData task,
|
||||
TaskStatus oldStatus,
|
||||
TaskStatus newStatus,
|
||||
) {
|
||||
final statusNames = {
|
||||
TaskStatus.toDo: 'To Do',
|
||||
TaskStatus.inProgress: 'In Progress',
|
||||
TaskStatus.done: 'Done',
|
||||
TaskStatus.toDo: BoardStrings.toDo,
|
||||
TaskStatus.inProgress: BoardStrings.inProgress,
|
||||
TaskStatus.done: BoardStrings.done,
|
||||
};
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Task "${task.description}" moved from ${statusNames[oldStatus]} to ${statusNames[newStatus]}',
|
||||
style: const TextStyle(fontFamily: 'Peyda'),
|
||||
),
|
||||
duration: const Duration(seconds: 2),
|
||||
backgroundColor: AppColors.successColor,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
AppToast.success(
|
||||
context,
|
||||
BoardStrings.taskMovedMessage(
|
||||
statusNames[oldStatus]!,
|
||||
statusNames[newStatus]!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
Consumer<BoardViewModel>(
|
||||
builder: (context, viewModel, child) {
|
||||
if (viewModel.isLoading) {
|
||||
return const Expanded(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColors.secondary50,
|
||||
),
|
||||
child: Row(
|
||||
),
|
||||
);
|
||||
}
|
||||
if (viewModel.errorMessage != null) {
|
||||
return Expanded(
|
||||
child: Center(child: Text(viewModel.errorMessage!)),
|
||||
);
|
||||
}
|
||||
if (viewModel.hasData == false) {
|
||||
return const Expanded(
|
||||
child: Center(child: Text(BoardStrings.emptyListText)),
|
||||
);
|
||||
}
|
||||
|
||||
return Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: SizedBox(
|
||||
width: 740,
|
||||
height: 735,
|
||||
child: Column(
|
||||
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(height: 32),
|
||||
Text(
|
||||
BoardStrings.boardTitle,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0.sp(),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.titleColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// In Progress
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: _BoardColumn(
|
||||
title: 'In progress',
|
||||
color: AppColors.orange20,
|
||||
textColor: AppColors.orange,
|
||||
tasks: _getTasksByStatus(TaskStatus.inProgress),
|
||||
status: TaskStatus.inProgress,
|
||||
onTaskDropped: _updateTaskStatus,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// Done
|
||||
Expanded(
|
||||
child: _BoardColumn(
|
||||
title: 'Done',
|
||||
color: AppColors.secondary20,
|
||||
textColor: AppColors.secondary70,
|
||||
tasks: _getTasksByStatus(TaskStatus.done),
|
||||
status: TaskStatus.done,
|
||||
onTaskDropped: _updateTaskStatus,
|
||||
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: BoardStrings.toDo,
|
||||
color: AppColors.primary20,
|
||||
textColor: const Color(0xFF1976D2),
|
||||
tasks: viewModel.getTasksByStatus(
|
||||
TaskStatus.toDo,
|
||||
),
|
||||
status: TaskStatus.toDo,
|
||||
onTaskDropped: _updateTaskStatus,
|
||||
isValidMove: viewModel.isValidMove,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// In Progress
|
||||
Expanded(
|
||||
child: _BoardColumn(
|
||||
title: BoardStrings.inProgress,
|
||||
color: AppColors.orange20,
|
||||
textColor: AppColors.orange,
|
||||
tasks: viewModel.getTasksByStatus(
|
||||
TaskStatus.inProgress,
|
||||
),
|
||||
status: TaskStatus.inProgress,
|
||||
onTaskDropped: _updateTaskStatus,
|
||||
isValidMove: viewModel.isValidMove,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
// Done
|
||||
Expanded(
|
||||
child: _BoardColumn(
|
||||
title: BoardStrings.done,
|
||||
color: AppColors.secondary20,
|
||||
textColor: AppColors.secondary70,
|
||||
tasks: viewModel.getTasksByStatus(
|
||||
TaskStatus.done,
|
||||
),
|
||||
status: TaskStatus.done,
|
||||
onTaskDropped: _updateTaskStatus,
|
||||
isValidMove: viewModel.isValidMove,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -214,9 +191,10 @@ class _BoardColumn extends StatelessWidget {
|
||||
final String title;
|
||||
final Color color;
|
||||
final Color textColor;
|
||||
final List<TaskItem> tasks;
|
||||
final List<BoardData> tasks;
|
||||
final TaskStatus status;
|
||||
final Function(TaskItem, TaskStatus) onTaskDropped;
|
||||
final Function(BoardData, TaskStatus) onTaskDropped;
|
||||
final bool Function(TaskStatus, TaskStatus) isValidMove;
|
||||
|
||||
const _BoardColumn({
|
||||
required this.title,
|
||||
@@ -225,13 +203,14 @@ class _BoardColumn extends StatelessWidget {
|
||||
required this.tasks,
|
||||
required this.status,
|
||||
required this.onTaskDropped,
|
||||
required this.isValidMove,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DragTarget<TaskItem>(
|
||||
return DragTarget<BoardData>(
|
||||
onWillAcceptWithDetails: (details) {
|
||||
return details.data.status != status;
|
||||
return isValidMove(details.data.statusEnum, status);
|
||||
},
|
||||
onAcceptWithDetails: (details) {
|
||||
onTaskDropped(details.data, status);
|
||||
@@ -288,7 +267,7 @@ class _BoardColumn extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _TaskCard extends StatelessWidget {
|
||||
final TaskItem task;
|
||||
final BoardData task;
|
||||
|
||||
const _TaskCard({required this.task});
|
||||
|
||||
@@ -298,7 +277,7 @@ class _TaskCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Draggable<TaskItem>(
|
||||
return Draggable<BoardData>(
|
||||
data: task,
|
||||
feedback: Material(
|
||||
elevation: 8,
|
||||
@@ -348,9 +327,9 @@ class _TaskCard extends StatelessWidget {
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.0.w()),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
task.status == TaskStatus.toDo
|
||||
task.statusEnum == TaskStatus.toDo
|
||||
? AppColors.primary20
|
||||
: task.status == TaskStatus.inProgress
|
||||
: task.statusEnum == TaskStatus.inProgress
|
||||
? AppColors.orange10
|
||||
: AppColors.secondary10,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
@@ -359,7 +338,7 @@ class _TaskCard extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Deadline :',
|
||||
'${BoardStrings.deadlineLabel} :',
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -367,7 +346,7 @@ class _TaskCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_formatDate(task.deadline),
|
||||
_formatDate(task.deadline ?? DateTime.now()),
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -379,7 +358,7 @@ class _TaskCard extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
task.description,
|
||||
task.description!,
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey70),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@@ -389,7 +368,7 @@ class _TaskCard extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||
decoration: BoxDecoration(
|
||||
color: task.isReady ? AppColors.primary20 : AppColors.red0,
|
||||
color: task.isReady ?? false ? AppColors.primary20 : AppColors.red0,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
@@ -399,16 +378,18 @@ class _TaskCard extends StatelessWidget {
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: task.isReady ? AppColors.textBlue : AppColors.error,
|
||||
color: task.isReady! ? AppColors.textBlue : AppColors.error,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
task.isReady ? 'Ready' : 'Incomplete',
|
||||
task.isReady!
|
||||
? BoardStrings.readyLabel
|
||||
: BoardStrings.incompleteLabel,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: task.isReady ? AppColors.textBlue : AppColors.red20,
|
||||
color: task.isReady! ? AppColors.textBlue : AppColors.red20,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
@@ -421,7 +402,7 @@ class _TaskCard extends StatelessWidget {
|
||||
SvgPicture.asset(AssetsManager.profile),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
task.assignedTo,
|
||||
task.assignedTo!,
|
||||
style: TextStyle(fontSize: 12, color: AppColors.grey60),
|
||||
),
|
||||
],
|
||||
@@ -0,0 +1,14 @@
|
||||
class BoardStrings {
|
||||
BoardStrings._();
|
||||
|
||||
static const String boardTitle = 'My Board';
|
||||
static const String toDo = 'To Do';
|
||||
static const String inProgress = 'In Progress';
|
||||
static const String done = 'Done';
|
||||
static const String emptyListText = 'No tasks found';
|
||||
static const String deadlineLabel = 'Deadline';
|
||||
static const String readyLabel = 'Ready';
|
||||
static const String incompleteLabel = 'Incomplete';
|
||||
static String taskMovedMessage(String oldStatus, String newStatus) =>
|
||||
'Task moved from $oldStatus to $newStatus';
|
||||
}
|
||||
Reference in New Issue
Block a user