feat: 增强日历功能并集成 AgentScope 代理服务
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import 'package:social_app/core/api/i_api_client.dart';
|
||||
|
||||
class InboxApi {
|
||||
final IApiClient _client;
|
||||
static const _prefix = '/api/v1/inbox/messages';
|
||||
|
||||
InboxApi(this._client);
|
||||
|
||||
Future<List<InboxMessageResponse>> getMessages({bool? isRead}) async {
|
||||
final queryParams = isRead != null ? '?is_read=$isRead' : '';
|
||||
final response = await _client.get('$_prefix$queryParams');
|
||||
final List<dynamic> data = response.data;
|
||||
return data.map((json) => InboxMessageResponse.fromJson(json)).toList();
|
||||
}
|
||||
|
||||
Future<InboxMessageResponse> markAsRead(String messageId) async {
|
||||
final response = await _client.patch('$_prefix/$messageId/read');
|
||||
return InboxMessageResponse.fromJson(response.data);
|
||||
}
|
||||
}
|
||||
|
||||
class InboxMessageResponse {
|
||||
final String id;
|
||||
final String recipientId;
|
||||
final String? senderId;
|
||||
final InboxMessageType messageType;
|
||||
final String? scheduleItemId;
|
||||
final String? friendshipId;
|
||||
final String? content;
|
||||
final bool isRead;
|
||||
final InboxMessageStatus status;
|
||||
final DateTime createdAt;
|
||||
|
||||
InboxMessageResponse({
|
||||
required this.id,
|
||||
required this.recipientId,
|
||||
this.senderId,
|
||||
required this.messageType,
|
||||
this.scheduleItemId,
|
||||
this.friendshipId,
|
||||
this.content,
|
||||
required this.isRead,
|
||||
required this.status,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory InboxMessageResponse.fromJson(Map<String, dynamic> json) {
|
||||
return InboxMessageResponse(
|
||||
id: json['id'] as String,
|
||||
recipientId: json['recipient_id'] as String,
|
||||
senderId: json['sender_id'] as String?,
|
||||
messageType: InboxMessageType.fromJson(json['message_type'] as String),
|
||||
scheduleItemId: json['schedule_item_id'] as String?,
|
||||
friendshipId: json['friendship_id'] as String?,
|
||||
content: json['content'] as String?,
|
||||
isRead: json['is_read'] as bool,
|
||||
status: InboxMessageStatus.fromJson(json['status'] as String),
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
InboxMessageResponse copyWith({bool? isRead}) {
|
||||
return InboxMessageResponse(
|
||||
id: id,
|
||||
recipientId: recipientId,
|
||||
senderId: senderId,
|
||||
messageType: messageType,
|
||||
scheduleItemId: scheduleItemId,
|
||||
friendshipId: friendshipId,
|
||||
content: content,
|
||||
isRead: isRead ?? this.isRead,
|
||||
status: status,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum InboxMessageType {
|
||||
friendRequest('friend_request'),
|
||||
calendar('calendar'),
|
||||
system('system'),
|
||||
group('group');
|
||||
|
||||
final String value;
|
||||
const InboxMessageType(this.value);
|
||||
|
||||
static InboxMessageType fromJson(String json) {
|
||||
return InboxMessageType.values.firstWhere(
|
||||
(e) => e.value == json,
|
||||
orElse: () => InboxMessageType.system,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum InboxMessageStatus {
|
||||
pending('pending'),
|
||||
accepted('accepted'),
|
||||
rejected('rejected'),
|
||||
dismissed('dismissed');
|
||||
|
||||
final String value;
|
||||
const InboxMessageStatus(this.value);
|
||||
|
||||
static InboxMessageStatus fromJson(String json) {
|
||||
return InboxMessageStatus.values.firstWhere(
|
||||
(e) => e.value == json,
|
||||
orElse: () => InboxMessageStatus.pending,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,214 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart' hide BackButton;
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../../core/di/injection.dart';
|
||||
import '../../../../core/theme/design_tokens.dart';
|
||||
import '../../../../shared/widgets/page_header.dart';
|
||||
import '../../../../shared/widgets/toast/toast.dart';
|
||||
import '../../../../shared/widgets/toast/toast_type.dart';
|
||||
import '../../../friends/data/friends_api.dart';
|
||||
import '../../data/inbox_api.dart';
|
||||
|
||||
class MessageInviteListScreen extends StatelessWidget {
|
||||
class MessageWithFriend {
|
||||
final InboxMessageResponse message;
|
||||
final FriendRequestResponse? friendRequest;
|
||||
|
||||
const MessageWithFriend({required this.message, this.friendRequest});
|
||||
}
|
||||
|
||||
class MessageInviteListScreen extends StatefulWidget {
|
||||
const MessageInviteListScreen({super.key});
|
||||
|
||||
@override
|
||||
State<MessageInviteListScreen> createState() =>
|
||||
_MessageInviteListScreenState();
|
||||
}
|
||||
|
||||
class _MessageInviteListScreenState extends State<MessageInviteListScreen> {
|
||||
late final InboxApi _inboxApi;
|
||||
late final FriendsApi _friendsApi;
|
||||
|
||||
List<MessageWithFriend> _unreadMessages = [];
|
||||
List<MessageWithFriend> _readMessages = [];
|
||||
bool _isLoading = false;
|
||||
int _activeTabIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_inboxApi = sl<InboxApi>();
|
||||
_friendsApi = sl<FriendsApi>();
|
||||
_loadMessages();
|
||||
}
|
||||
|
||||
Future<void> _loadMessages() async {
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = true);
|
||||
}
|
||||
try {
|
||||
final unreadRaw = await _inboxApi.getMessages(isRead: false);
|
||||
final readRaw = await _inboxApi.getMessages(isRead: true);
|
||||
|
||||
final unread = await _enrichWithFriendDetails(unreadRaw);
|
||||
final read = await _enrichWithFriendDetails(readRaw);
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_unreadMessages = unread;
|
||||
_readMessages = read;
|
||||
_isLoading = false;
|
||||
});
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => _isLoading = false);
|
||||
Toast.show(context, '消息加载失败,请稍后重试', type: ToastType.error);
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<MessageWithFriend>> _enrichWithFriendDetails(
|
||||
List<InboxMessageResponse> messages,
|
||||
) async {
|
||||
final futures = messages.map(_fetchFriendRequest);
|
||||
final results = await Future.wait(futures);
|
||||
|
||||
final enriched = <MessageWithFriend>[];
|
||||
for (int i = 0; i < messages.length; i++) {
|
||||
final message = messages[i];
|
||||
final friendRequest = results[i];
|
||||
|
||||
enriched.add(
|
||||
MessageWithFriend(message: message, friendRequest: friendRequest),
|
||||
);
|
||||
}
|
||||
return enriched;
|
||||
}
|
||||
|
||||
Future<FriendRequestResponse?> _fetchFriendRequest(
|
||||
InboxMessageResponse message,
|
||||
) async {
|
||||
if (message.messageType != InboxMessageType.friendRequest ||
|
||||
message.friendshipId == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return await _friendsApi.getRequestById(message.friendshipId!);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleMessageTap(MessageWithFriend item) async {
|
||||
final message = item.message;
|
||||
switch (message.messageType) {
|
||||
case InboxMessageType.calendar:
|
||||
context.push('/messages/invites/${message.id}');
|
||||
return;
|
||||
case InboxMessageType.friendRequest:
|
||||
if (item.friendRequest == null) {
|
||||
Toast.show(context, '发送者信息加载失败,请下拉重试', type: ToastType.error);
|
||||
return;
|
||||
}
|
||||
if (message.isRead) {
|
||||
_showFriendRequestReadOnlySheet(item);
|
||||
} else {
|
||||
_showFriendRequestActionSheet(item);
|
||||
}
|
||||
return;
|
||||
case InboxMessageType.system:
|
||||
case InboxMessageType.group:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _showFriendRequestReadOnlySheet(MessageWithFriend item) {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (ctx) {
|
||||
return _FriendRequestSheet(
|
||||
message: item.message,
|
||||
friendRequest: item.friendRequest!,
|
||||
isReadOnly: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showFriendRequestActionSheet(MessageWithFriend item) {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
builder: (ctx) {
|
||||
return _FriendRequestSheet(
|
||||
message: item.message,
|
||||
friendRequest: item.friendRequest!,
|
||||
isReadOnly: false,
|
||||
onAccept: () async {
|
||||
Navigator.pop(ctx);
|
||||
await _processFriendRequest(item, accept: true);
|
||||
},
|
||||
onDecline: () async {
|
||||
Navigator.pop(ctx);
|
||||
await _processFriendRequest(item, accept: false);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _processFriendRequest(
|
||||
MessageWithFriend item, {
|
||||
required bool accept,
|
||||
}) async {
|
||||
final message = item.message;
|
||||
final friendshipId = message.friendshipId;
|
||||
if (friendshipId == null) {
|
||||
Toast.show(context, '好友请求数据缺失', type: ToastType.error);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (accept) {
|
||||
await _friendsApi.acceptRequest(friendshipId);
|
||||
if (mounted) {
|
||||
Toast.show(context, '已接受好友请求', type: ToastType.success);
|
||||
}
|
||||
} else {
|
||||
await _friendsApi.declineRequest(friendshipId);
|
||||
if (mounted) {
|
||||
Toast.show(context, '已拒绝好友请求', type: ToastType.success);
|
||||
}
|
||||
}
|
||||
await _inboxApi.markAsRead(message.id);
|
||||
await _loadMessages();
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
Toast.show(context, '处理失败,请稍后重试', type: ToastType.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.messageBg,
|
||||
backgroundColor: AppColors.background,
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
PageHeader(leading: BackButton(onPressed: () => context.pop())),
|
||||
_buildHeader(context),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 20, right: 20, bottom: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildRemindTag(),
|
||||
const SizedBox(height: 12),
|
||||
_buildInviteCard(context),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: _isLoading
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColors.blue500,
|
||||
),
|
||||
)
|
||||
: _activeTabIndex == 0
|
||||
? _buildMessageList(_unreadMessages, isUnread: true)
|
||||
: _buildMessageList(_readMessages, isUnread: false),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -35,102 +216,400 @@ class MessageInviteListScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRemindTag() {
|
||||
return Container(
|
||||
height: 24,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.messageTagBg,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'消息提醒',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.blue600,
|
||||
),
|
||||
),
|
||||
Widget _buildHeader(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 20, 8),
|
||||
child: Row(
|
||||
children: [
|
||||
BackButton(onPressed: () => context.pop()),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: _buildTabs()),
|
||||
const SizedBox(width: 56),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInviteCard(BuildContext context) {
|
||||
Widget _buildTabs() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.slate100,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: _buildTab(0, '未读', Icons.mark_email_unread_outlined)),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(child: _buildTab(1, '已读', Icons.mark_email_read_outlined)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab(int index, String label, IconData icon) {
|
||||
final isSelected = _activeTabIndex == index;
|
||||
return GestureDetector(
|
||||
onTap: () => context.push('/messages/invites/1'),
|
||||
onTap: () {
|
||||
if (_activeTabIndex != index) {
|
||||
setState(() => _activeTabIndex = index);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(14),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.messageCardBg,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColors.messageCardBorder),
|
||||
color: isSelected ? AppColors.white : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.messageCalendarBg,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.calendar_today_outlined,
|
||||
size: 20,
|
||||
color: AppColors.blue500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'事件:产品评审会 2026-02-12 14:00',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.slate700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'邀请人:李文浩',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: AppColors.slate500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'点击查看详情并处理邀请',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: AppColors.slate400,
|
||||
),
|
||||
),
|
||||
],
|
||||
Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: isSelected ? AppColors.slate900 : AppColors.slate500,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
|
||||
color: isSelected ? AppColors.slate900 : AppColors.slate500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
size: 16,
|
||||
color: AppColors.messageArrowColor,
|
||||
),
|
||||
if (index == 0 && _unreadMessages.isNotEmpty) ...[
|
||||
const SizedBox(width: 6),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.red500,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
_unreadMessages.length > 99
|
||||
? '99+'
|
||||
: _unreadMessages.length.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMessageList(
|
||||
List<MessageWithFriend> messages, {
|
||||
required bool isUnread,
|
||||
}) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: _loadMessages,
|
||||
color: AppColors.blue500,
|
||||
child: ListView.builder(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
itemCount: messages.isEmpty ? 1 : messages.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (messages.isEmpty) {
|
||||
return SizedBox(
|
||||
height: MediaQuery.sizeOf(context).height * 0.6,
|
||||
child: _buildEmptyState(isUnread: isUnread),
|
||||
);
|
||||
}
|
||||
final item = messages[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: _MessageCard(
|
||||
item: item,
|
||||
onTap: () => _handleMessageTap(item),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState({required bool isUnread}) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.slate100,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
isUnread ? Icons.notifications_none : Icons.inbox_outlined,
|
||||
size: 36,
|
||||
color: AppColors.slate400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
isUnread ? '暂无未读消息' : '暂无已读消息',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.slate500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
isUnread ? '有新消息时会在这里显示' : '处理过的消息会显示在这里',
|
||||
style: const TextStyle(fontSize: 13, color: AppColors.slate400),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MessageCard extends StatelessWidget {
|
||||
final MessageWithFriend item;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _MessageCard({required this.item, required this.onTap});
|
||||
|
||||
InboxMessageResponse get message => item.message;
|
||||
FriendRequestResponse? get friendRequest => item.friendRequest;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: message.isRead
|
||||
? AppColors.borderSecondary
|
||||
: AppColors.blue100,
|
||||
width: message.isRead ? 1 : 1.5,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.emerald500.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.person_add_outlined,
|
||||
size: 22,
|
||||
color: AppColors.emerald500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_title(),
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.slate900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
_content(),
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: AppColors.slate500,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _title() {
|
||||
if (message.messageType == InboxMessageType.friendRequest) {
|
||||
if (friendRequest == null) {
|
||||
return '好友请求信息加载失败';
|
||||
}
|
||||
return '${friendRequest!.sender.username} 请求添加您为好友';
|
||||
}
|
||||
if (message.messageType == InboxMessageType.calendar) {
|
||||
try {
|
||||
final data =
|
||||
jsonDecode(message.content ?? '{}') as Map<String, dynamic>;
|
||||
return data['title'] as String? ?? '日历邀请';
|
||||
} catch (_) {
|
||||
return '日历邀请';
|
||||
}
|
||||
}
|
||||
return '系统消息';
|
||||
}
|
||||
|
||||
String _content() => message.content ?? '点击查看详情';
|
||||
}
|
||||
|
||||
class _FriendRequestSheet extends StatelessWidget {
|
||||
final InboxMessageResponse message;
|
||||
final FriendRequestResponse friendRequest;
|
||||
final bool isReadOnly;
|
||||
final VoidCallback? onAccept;
|
||||
final VoidCallback? onDecline;
|
||||
|
||||
const _FriendRequestSheet({
|
||||
required this.message,
|
||||
required this.friendRequest,
|
||||
required this.isReadOnly,
|
||||
this.onAccept,
|
||||
this.onDecline,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final status = friendRequest.status;
|
||||
final statusText = status == 'accepted'
|
||||
? '已接受'
|
||||
: status == 'rejected'
|
||||
? '已拒绝'
|
||||
: '已处理';
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(24, 20, 24, 0),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.slate300,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Container(
|
||||
width: 72,
|
||||
height: 72,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.emerald500.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.person_add_outlined,
|
||||
size: 32,
|
||||
color: AppColors.emerald500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'${friendRequest.sender.username} 请求添加您为好友',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.slate900,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if ((message.content ?? '').isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'备注: ${message.content}',
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.slate500),
|
||||
),
|
||||
],
|
||||
if (isReadOnly) ...[
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.slate100,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
statusText,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.slate600,
|
||||
),
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
const SizedBox(height: 28),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: onDecline,
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
side: const BorderSide(color: AppColors.slate300),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'拒绝',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.slate600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: onAccept,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.blue500,
|
||||
elevation: 0,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'接受',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
SizedBox(height: MediaQuery.of(context).padding.bottom + 12),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user