feat: Add support for reported pre-signed URLs in comments and daily progress report

This commit is contained in:
Vaibhav Surve 2025-06-12 22:02:19 +05:30
parent 5cf0202cc1
commit 916cfa3af4
3 changed files with 113 additions and 12 deletions

View File

@ -60,6 +60,7 @@ class _CommentTaskBottomSheetState extends State<CommentTaskBottomSheet>
controller.basicValidator.getController('task_id')?.text = controller.basicValidator.getController('task_id')?.text =
data['taskId'] ?? ''; data['taskId'] ?? '';
controller.basicValidator.getController('comment')?.clear(); controller.basicValidator.getController('comment')?.clear();
controller.basicValidator.getController('preSignedUrls')?.clear();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) { if (_scrollController.hasClients) {
_scrollController.jumpTo(_scrollController.position.maxScrollExtent); _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
@ -178,6 +179,90 @@ class _CommentTaskBottomSheetState extends State<CommentTaskBottomSheet>
icon: Icons.done_all_outlined, icon: Icons.done_all_outlined,
), ),
buildTeamMembers(), buildTeamMembers(),
if ((widget.taskData['reportedPreSignedUrls']
as List<dynamic>?)
?.isNotEmpty ==
true) ...[
MySpacing.height(8),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 0.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.image_outlined,
size: 18, color: Colors.grey[700]),
MySpacing.width(8),
MyText.titleSmall(
"Reported Images",
fontWeight: 600,
),
],
),
),
MySpacing.height(8),
Builder(
builder: (context) {
final allImageUrls = List<String>.from(
widget.taskData['reportedPreSignedUrls'] ?? [],
);
if (allImageUrls.isEmpty) return const SizedBox();
return Padding(
padding: const EdgeInsets.symmetric(
horizontal:
16.0), // Same horizontal padding
child: SizedBox(
height: 70,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: allImageUrls.length,
separatorBuilder: (_, __) =>
const SizedBox(width: 12),
itemBuilder: (context, index) {
final url = allImageUrls[index];
return GestureDetector(
onTap: () {
showDialog(
context: context,
barrierColor: Colors.black54,
builder: (_) => ImageViewerDialog(
imageSources: allImageUrls,
initialIndex: index,
),
);
},
child: ClipRRect(
borderRadius:
BorderRadius.circular(12),
child: Image.network(
url,
width: 70,
height: 70,
fit: BoxFit.cover,
errorBuilder:
(context, error, stackTrace) =>
Container(
width: 70,
height: 70,
color: Colors.grey.shade200,
child: Icon(Icons.broken_image,
color: Colors.grey[600]),
),
),
),
);
},
),
),
);
},
),
MySpacing.height(16),
],
Row( Row(
children: [ children: [
Icon(Icons.comment_outlined, Icon(Icons.comment_outlined,
@ -456,17 +541,9 @@ class _CommentTaskBottomSheetState extends State<CommentTaskBottomSheet>
comment['commentedBy'] ?? 'Unknown'; comment['commentedBy'] ?? 'Unknown';
final relativeTime = final relativeTime =
timeAgo(comment['date'] ?? ''); timeAgo(comment['date'] ?? '');
// Dummy image URLs (simulate as if coming from backend) // Dummy image URLs (simulate as if coming from backend)
final imageUrls = [ final imageUrls = List<String>.from(
'https://picsum.photos/seed/${index}a/100', comment['preSignedUrls'] ?? []);
'https://picsum.photos/seed/${index}b/100',
'https://picsum.photos/seed/${index}a/100',
'https://picsum.photos/seed/${index}b/100',
'https://picsum.photos/seed/${index}a/100',
'https://picsum.photos/seed/${index}b/100',
];
return Container( return Container(
margin: const EdgeInsets.symmetric( margin: const EdgeInsets.symmetric(
vertical: 8), // Spacing between items vertical: 8), // Spacing between items

View File

@ -9,10 +9,11 @@ class TaskModel {
final AssignedBy assignedBy; final AssignedBy assignedBy;
final List<TeamMember> teamMembers; final List<TeamMember> teamMembers;
final List<Comment> comments; final List<Comment> comments;
final List<String> reportedPreSignedUrls;
TaskModel({ TaskModel({
required this.assignmentDate, required this.assignmentDate,
this.reportedDate, this.reportedDate,
required this.id, required this.id,
required this.workItem, required this.workItem,
required this.workItemId, required this.workItemId,
@ -21,13 +22,14 @@ class TaskModel {
required this.assignedBy, required this.assignedBy,
required this.teamMembers, required this.teamMembers,
required this.comments, required this.comments,
required this.reportedPreSignedUrls,
}); });
factory TaskModel.fromJson(Map<String, dynamic> json) { factory TaskModel.fromJson(Map<String, dynamic> json) {
return TaskModel( return TaskModel(
assignmentDate: DateTime.parse(json['assignmentDate']), assignmentDate: DateTime.parse(json['assignmentDate']),
reportedDate: json['reportedDate'] != null reportedDate: json['reportedDate'] != null
? DateTime.tryParse(json['reportedDate']) ? DateTime.tryParse(json['reportedDate'])
: null, : null,
id: json['id'], id: json['id'],
workItem: workItem:
@ -41,6 +43,10 @@ class TaskModel {
.toList(), .toList(),
comments: comments:
(json['comments'] as List).map((e) => Comment.fromJson(e)).toList(), (json['comments'] as List).map((e) => Comment.fromJson(e)).toList(),
reportedPreSignedUrls: (json['reportedPreSignedUrls'] as List<dynamic>?)
?.map((e) => e.toString())
.toList() ??
[],
); );
} }
} }
@ -51,6 +57,7 @@ class WorkItem {
final WorkArea? workArea; final WorkArea? workArea;
final int? plannedWork; final int? plannedWork;
final int? completedWork; final int? completedWork;
final List<String> preSignedUrls;
WorkItem({ WorkItem({
this.id, this.id,
@ -58,6 +65,7 @@ class WorkItem {
this.workArea, this.workArea,
this.plannedWork, this.plannedWork,
this.completedWork, this.completedWork,
this.preSignedUrls = const [],
}); });
factory WorkItem.fromJson(Map<String, dynamic> json) { factory WorkItem.fromJson(Map<String, dynamic> json) {
@ -70,6 +78,10 @@ class WorkItem {
json['workArea'] != null ? WorkArea.fromJson(json['workArea']) : null, json['workArea'] != null ? WorkArea.fromJson(json['workArea']) : null,
plannedWork: json['plannedWork'], plannedWork: json['plannedWork'],
completedWork: json['completedWork'], completedWork: json['completedWork'],
preSignedUrls: (json['preSignedUrls'] as List<dynamic>?)
?.map((e) => e.toString())
.toList() ??
[],
); );
} }
} }
@ -167,11 +179,13 @@ class Comment {
final String comment; final String comment;
final TeamMember commentedBy; final TeamMember commentedBy;
final DateTime timestamp; final DateTime timestamp;
final List<String> preSignedUrls;
Comment({ Comment({
required this.comment, required this.comment,
required this.commentedBy, required this.commentedBy,
required this.timestamp, required this.timestamp,
required this.preSignedUrls,
}); });
factory Comment.fromJson(Map<String, dynamic> json) { factory Comment.fromJson(Map<String, dynamic> json) {
@ -181,6 +195,10 @@ class Comment {
? TeamMember.fromJson(json['employee']) ? TeamMember.fromJson(json['employee'])
: TeamMember(id: '', firstName: '', lastName: null), : TeamMember(id: '', firstName: '', lastName: null),
timestamp: DateTime.parse(json['commentDate'] ?? ''), timestamp: DateTime.parse(json['commentDate'] ?? ''),
preSignedUrls: (json['preSignedUrls'] as List<dynamic>?)
?.map((e) => e.toString())
.toList() ??
[],
); );
} }
} }

View File

@ -607,8 +607,12 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
'text': comment.comment, 'text': comment.comment,
'date': isoDate, 'date': isoDate,
'commentedBy': commenterName, 'commentedBy': commenterName,
'preSignedUrls':
comment.preSignedUrls,
}; };
}).toList(); }).toList();
final taskLevelPreSignedUrls =
task.reportedPreSignedUrls;
final taskData = { final taskData = {
'activity': activityName, 'activity': activityName,
@ -622,6 +626,8 @@ class _DailyProgressReportScreenState extends State<DailyProgressReportScreen>
'teamSize': task.teamMembers.length, 'teamSize': task.teamMembers.length,
'teamMembers': teamMembers, 'teamMembers': teamMembers,
'taskComments': taskComments, 'taskComments': taskComments,
'reportedPreSignedUrls':
taskLevelPreSignedUrls,
}; };
showModalBottomSheet( showModalBottomSheet(