feat: deploy infrastructure + disk/drive, calendar, presentation, workspaces, onboarding, demo user
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../models/collaboration.dart';
|
||||
import '../core/theme/app_theme.dart';
|
||||
|
||||
class CommentThread extends StatelessWidget {
|
||||
final List<Comment> comments;
|
||||
final String currentUserId;
|
||||
final Future<bool> Function(String body, {String? parentId}) onAddComment;
|
||||
|
||||
const CommentThread({
|
||||
super.key,
|
||||
required this.comments,
|
||||
required this.currentUserId,
|
||||
required this.onAddComment,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final topLevel = comments.where((c) => c.parentId == null).toList();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
...topLevel.map((comment) => _CommentCard(
|
||||
comment: comment,
|
||||
currentUserId: currentUserId,
|
||||
onReply: (parentId, body) => onAddComment(body, parentId: parentId),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CommentCard extends StatelessWidget {
|
||||
final Comment comment;
|
||||
final String currentUserId;
|
||||
final Future<bool> Function(String parentId, String body) onReply;
|
||||
|
||||
const _CommentCard({
|
||||
required this.comment,
|
||||
required this.currentUserId,
|
||||
required this.onReply,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: VoIdeaColors.surface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 14,
|
||||
backgroundColor: VoIdeaColors.primary.withOpacity(0.1),
|
||||
child: Text(
|
||||
comment.userName.isNotEmpty
|
||||
? comment.userName[0].toUpperCase()
|
||||
: '?',
|
||||
style: const TextStyle(
|
||||
color: VoIdeaColors.primary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
comment.userName,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
DateFormat('d MMM').format(comment.createdAt),
|
||||
style: TextStyle(
|
||||
color: VoIdeaColors.muted,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(comment.body),
|
||||
if (comment.userId != currentUserId)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: GestureDetector(
|
||||
onTap: () => _showReplyDialog(context),
|
||||
child: Text(
|
||||
'Ответить',
|
||||
style: TextStyle(
|
||||
color: VoIdeaColors.primary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (comment.replies.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 24, top: 8),
|
||||
child: Column(
|
||||
children: comment.replies.map((reply) => _CommentCard(
|
||||
comment: reply,
|
||||
currentUserId: currentUserId,
|
||||
onReply: onReply,
|
||||
)).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showReplyDialog(BuildContext context) {
|
||||
final controller = TextEditingController();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Ответить'),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
maxLines: 3,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Напишите ответ...',
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: const Text('Отмена'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (controller.text.isNotEmpty) {
|
||||
onReply(comment.id, controller.text);
|
||||
Navigator.pop(ctx);
|
||||
}
|
||||
},
|
||||
child: const Text('Отправить'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user