feat(apps): 更新消息邀请详情和认证流程路由
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:formz/formz.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import '../../../../core/di/injection.dart';
|
import '../../../../core/di/injection.dart';
|
||||||
|
import '../../../../core/router/app_routes.dart';
|
||||||
import '../../../../core/theme/design_tokens.dart';
|
import '../../../../core/theme/design_tokens.dart';
|
||||||
import '../../../../shared/widgets/app_button.dart';
|
import '../../../../shared/widgets/app_button.dart';
|
||||||
import '../../../../shared/widgets/banner/app_banner.dart';
|
import '../../../../shared/widgets/banner/app_banner.dart';
|
||||||
@@ -59,7 +60,7 @@ class _LoginViewState extends State<LoginView> {
|
|||||||
final response = await cubit.submit();
|
final response = await cubit.submit();
|
||||||
if (response != null && mounted) {
|
if (response != null && mounted) {
|
||||||
context.read<AuthBloc>().add(AuthLoggedIn(user: response.user));
|
context.read<AuthBloc>().add(AuthLoggedIn(user: response.user));
|
||||||
context.go('/home');
|
context.go(AppRoutes.homeMain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:formz/formz.dart';
|
import 'package:formz/formz.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../../../../core/router/app_routes.dart';
|
||||||
import '../../../../core/theme/design_tokens.dart';
|
import '../../../../core/theme/design_tokens.dart';
|
||||||
import '../../../../shared/widgets/app_button.dart';
|
import '../../../../shared/widgets/app_button.dart';
|
||||||
import '../../../../shared/widgets/banner/app_banner.dart';
|
import '../../../../shared/widgets/banner/app_banner.dart';
|
||||||
@@ -93,7 +94,7 @@ class _RegisterVerificationViewState extends State<RegisterVerificationView> {
|
|||||||
final response = await cubit.submitStep2();
|
final response = await cubit.submitStep2();
|
||||||
if (response != null && mounted) {
|
if (response != null && mounted) {
|
||||||
context.read<AuthBloc>().add(AuthLoggedIn(user: response.user));
|
context.read<AuthBloc>().add(AuthLoggedIn(user: response.user));
|
||||||
context.go('/home');
|
context.go(AppRoutes.homeMain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
import 'package:flutter/material.dart' hide BackButton;
|
import 'package:flutter/material.dart' hide BackButton;
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../../../../core/di/injection.dart';
|
||||||
import '../../../../core/theme/design_tokens.dart';
|
import '../../../../core/theme/design_tokens.dart';
|
||||||
|
import '../../../../shared/widgets/app_loading_indicator.dart';
|
||||||
import '../../../../shared/widgets/page_header.dart';
|
import '../../../../shared/widgets/page_header.dart';
|
||||||
|
import '../../../../shared/widgets/toast/toast.dart';
|
||||||
|
import '../../../../shared/widgets/toast/toast_type.dart';
|
||||||
|
import '../../../calendar/data/calendar_api.dart';
|
||||||
|
import '../../../users/data/users_api.dart';
|
||||||
|
import '../../data/inbox_api.dart';
|
||||||
|
|
||||||
class MessageInviteDetailScreen extends StatefulWidget {
|
class MessageInviteDetailScreen extends StatefulWidget {
|
||||||
const MessageInviteDetailScreen({super.key});
|
final String inviteId;
|
||||||
|
|
||||||
|
const MessageInviteDetailScreen({super.key, required this.inviteId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MessageInviteDetailScreen> createState() =>
|
State<MessageInviteDetailScreen> createState() =>
|
||||||
@@ -12,16 +22,155 @@ class MessageInviteDetailScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MessageInviteDetailScreenState extends State<MessageInviteDetailScreen> {
|
class _MessageInviteDetailScreenState extends State<MessageInviteDetailScreen> {
|
||||||
final _reasonController = TextEditingController();
|
late final InboxApi _inboxApi;
|
||||||
|
late final CalendarApi _calendarApi;
|
||||||
|
late final UsersApi _usersApi;
|
||||||
|
|
||||||
|
InboxMessageResponse? _message;
|
||||||
|
String? _calendarTitle;
|
||||||
|
String? _senderName;
|
||||||
|
bool _loading = true;
|
||||||
|
bool _submitting = false;
|
||||||
|
String? _error;
|
||||||
|
|
||||||
|
bool get _isPending => _message?.status == InboxMessageStatus.pending;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void initState() {
|
||||||
_reasonController.dispose();
|
super.initState();
|
||||||
super.dispose();
|
_inboxApi = sl<InboxApi>();
|
||||||
|
_calendarApi = sl<CalendarApi>();
|
||||||
|
_usersApi = sl<UsersApi>();
|
||||||
|
_loadDetail();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadDetail() async {
|
||||||
|
setState(() {
|
||||||
|
_loading = true;
|
||||||
|
_error = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final results = await Future.wait([
|
||||||
|
_inboxApi.getMessages(isRead: false),
|
||||||
|
_inboxApi.getMessages(isRead: true),
|
||||||
|
]);
|
||||||
|
final messages = [...results[0], ...results[1]];
|
||||||
|
InboxMessageResponse? message;
|
||||||
|
for (final item in messages) {
|
||||||
|
if (item.id == widget.inviteId) {
|
||||||
|
message = item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (message == null) {
|
||||||
|
throw StateError('邀请不存在或已失效');
|
||||||
|
}
|
||||||
|
|
||||||
|
String? calendarTitle;
|
||||||
|
if (message.scheduleItemId != null) {
|
||||||
|
try {
|
||||||
|
final event = await _calendarApi.getById(message.scheduleItemId!);
|
||||||
|
calendarTitle = event.title;
|
||||||
|
} catch (_) {
|
||||||
|
calendarTitle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String? senderName;
|
||||||
|
if (message.senderId != null) {
|
||||||
|
try {
|
||||||
|
final sender = await _usersApi.getById(message.senderId!);
|
||||||
|
senderName = sender.username;
|
||||||
|
} catch (_) {
|
||||||
|
senderName = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_message = message;
|
||||||
|
_calendarTitle = calendarTitle;
|
||||||
|
_senderName = senderName;
|
||||||
|
_loading = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_error = e.toString().replaceFirst('Bad state: ', '');
|
||||||
|
_loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _acceptInvite() async {
|
||||||
|
final message = _message;
|
||||||
|
final itemId = message?.scheduleItemId;
|
||||||
|
if (message == null || itemId == null || _submitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => _submitting = true);
|
||||||
|
try {
|
||||||
|
await _calendarApi.acceptSubscription(itemId);
|
||||||
|
await _inboxApi.markAsRead(message.id);
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Toast.show(context, '已接受邀请', type: ToastType.success);
|
||||||
|
await _loadDetail();
|
||||||
|
} catch (_) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Toast.show(context, '操作失败,请稍后重试', type: ToastType.error);
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _submitting = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _rejectInvite() async {
|
||||||
|
final message = _message;
|
||||||
|
final itemId = message?.scheduleItemId;
|
||||||
|
if (message == null || itemId == null || _submitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => _submitting = true);
|
||||||
|
try {
|
||||||
|
await _calendarApi.rejectSubscription(itemId);
|
||||||
|
await _inboxApi.markAsRead(message.id);
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Toast.show(context, '已拒绝邀请', type: ToastType.success);
|
||||||
|
await _loadDetail();
|
||||||
|
} catch (_) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Toast.show(context, '操作失败,请稍后重试', type: ToastType.error);
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _submitting = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (_loading) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: SafeArea(child: Center(child: AppLoadingIndicator(size: 22))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.messageBg,
|
backgroundColor: AppColors.messageBg,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
@@ -40,8 +189,16 @@ class _MessageInviteDetailScreenState extends State<MessageInviteDetailScreen> {
|
|||||||
_buildCalendarTip(),
|
_buildCalendarTip(),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
_buildActionRow(),
|
_buildActionRow(),
|
||||||
const SizedBox(height: 14),
|
if (_error != null) ...[
|
||||||
_buildRejectReasonCard(),
|
const SizedBox(height: 14),
|
||||||
|
Text(
|
||||||
|
_error!,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: AppColors.feedbackErrorText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -53,6 +210,21 @@ class _MessageInviteDetailScreenState extends State<MessageInviteDetailScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSummaryCard() {
|
Widget _buildSummaryCard() {
|
||||||
|
final message = _message;
|
||||||
|
final statusText = message == null
|
||||||
|
? '未知'
|
||||||
|
: switch (message.status) {
|
||||||
|
InboxMessageStatus.pending => '待处理',
|
||||||
|
InboxMessageStatus.accepted => '已接受',
|
||||||
|
InboxMessageStatus.rejected => '已拒绝',
|
||||||
|
InboxMessageStatus.dismissed => '已处理',
|
||||||
|
};
|
||||||
|
|
||||||
|
final createdAt = message?.createdAt;
|
||||||
|
final createdAtText = createdAt == null
|
||||||
|
? '未知'
|
||||||
|
: '${createdAt.year}-${createdAt.month.toString().padLeft(2, '0')}-${createdAt.day.toString().padLeft(2, '0')} ${createdAt.hour.toString().padLeft(2, '0')}:${createdAt.minute.toString().padLeft(2, '0')}';
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
@@ -63,8 +235,8 @@ class _MessageInviteDetailScreenState extends State<MessageInviteDetailScreen> {
|
|||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: const [
|
children: [
|
||||||
Text(
|
const Text(
|
||||||
'日历邀请详情',
|
'日历邀请详情',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@@ -72,46 +244,46 @@ class _MessageInviteDetailScreenState extends State<MessageInviteDetailScreen> {
|
|||||||
color: AppColors.slate900,
|
color: AppColors.slate900,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
'事件:产品评审会',
|
'事件:${_calendarTitle ?? '未命名日程'}',
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: AppColors.slate700,
|
color: AppColors.slate700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
'邀请人:李文浩(产品经理)',
|
'邀请人:${_senderName ?? '未知用户'}',
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
color: AppColors.slate500,
|
color: AppColors.slate500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
'时间:2026-02-12 14:00 - 15:30',
|
'消息时间:$createdAtText',
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
color: AppColors.slate500,
|
color: AppColors.slate500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
'地点:3F-A 会议室 / 腾讯会议 231-889-100',
|
'状态:$statusText',
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
color: AppColors.slate500,
|
color: AppColors.slate500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
'备注:请提前准备本周版本风险与排期结论。',
|
'邀请ID:${widget.inviteId}',
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
color: AppColors.slate500,
|
color: AppColors.slate500,
|
||||||
@@ -131,13 +303,13 @@ class _MessageInviteDetailScreenState extends State<MessageInviteDetailScreen> {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: AppColors.messageTipBorder),
|
border: Border.all(color: AppColors.messageTipBorder),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: const Row(
|
||||||
children: const [
|
children: [
|
||||||
Icon(Icons.info_outline, size: 14, color: AppColors.slate500),
|
Icon(Icons.info_outline, size: 14, color: AppColors.slate500),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'更多附件与相关链接,请在订阅后在日历中查看',
|
'同意后将加入该日历事件,拒绝后该邀请会被标记为已处理',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
@@ -151,22 +323,43 @@ class _MessageInviteDetailScreenState extends State<MessageInviteDetailScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildActionRow() {
|
Widget _buildActionRow() {
|
||||||
|
if (!_isPending) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.messageCardBg,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: AppColors.messageCardBorder),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'该邀请已处理,无需重复操作',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.slate600,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 46,
|
height: 46,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: _handleReject,
|
onTap: _submitting ? null : _rejectInvite,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.messageCardBg,
|
color: AppColors.messageCardBg,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: AppColors.messageRejectBorder),
|
border: Border.all(color: AppColors.messageRejectBorder),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: const Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: const [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'×',
|
'×',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -192,16 +385,16 @@ class _MessageInviteDetailScreenState extends State<MessageInviteDetailScreen> {
|
|||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: _handleAccept,
|
onTap: _submitting ? null : _acceptInvite,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.messageTagBg,
|
color: AppColors.messageTagBg,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: AppColors.messageAcceptBorder),
|
border: Border.all(color: AppColors.messageAcceptBorder),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: const Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: const [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'√',
|
'√',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -228,66 +421,4 @@ class _MessageInviteDetailScreenState extends State<MessageInviteDetailScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRejectReasonCard() {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.messageCardBg,
|
|
||||||
borderRadius: BorderRadius.circular(14),
|
|
||||||
border: Border.all(color: AppColors.messageReasonBorder),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'拒绝补充信息(可选)',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: AppColors.slate600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Container(
|
|
||||||
height: 92,
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.messageBtnWrap,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
border: Border.all(color: AppColors.messageInputBorder),
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
controller: _reasonController,
|
|
||||||
maxLines: null,
|
|
||||||
expands: true,
|
|
||||||
style: const TextStyle(fontSize: 12, color: AppColors.slate700),
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
border: InputBorder.none,
|
|
||||||
hintText: '可填写拒绝原因,例如:该时间段已有客户会议,无法参加。',
|
|
||||||
hintStyle: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
color: AppColors.messagePlaceholder,
|
|
||||||
),
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleReject() {
|
|
||||||
final reason = _reasonController.text;
|
|
||||||
debugPrint('Reject with reason: $reason');
|
|
||||||
context.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleAccept() {
|
|
||||||
debugPrint('Accept invitation');
|
|
||||||
context.pop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,14 @@ import 'package:flutter/material.dart' hide BackButton;
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import '../../../../core/di/injection.dart';
|
import '../../../../core/di/injection.dart';
|
||||||
|
import '../../../../core/router/app_routes.dart';
|
||||||
import '../../../../core/theme/design_tokens.dart';
|
import '../../../../core/theme/design_tokens.dart';
|
||||||
import '../../../../shared/widgets/app_loading_indicator.dart';
|
import '../../../../shared/widgets/app_loading_indicator.dart';
|
||||||
import '../../../../shared/widgets/app_pull_refresh_feedback.dart';
|
import '../../../../shared/widgets/app_pull_refresh_feedback.dart';
|
||||||
import '../../../../shared/widgets/page_header.dart';
|
import '../../../../shared/widgets/page_header.dart';
|
||||||
import '../../../../shared/widgets/toast/toast.dart';
|
import '../../../../shared/widgets/toast/toast.dart';
|
||||||
import '../../../../shared/widgets/toast/toast_type.dart';
|
import '../../../../shared/widgets/toast/toast_type.dart';
|
||||||
import '../../../calendar/data/calendar_api.dart';
|
|
||||||
import '../../../calendar/data/models/schedule_item_model.dart';
|
|
||||||
import '../../../friends/data/friends_api.dart';
|
import '../../../friends/data/friends_api.dart';
|
||||||
import '../../../users/data/models/user_response.dart';
|
|
||||||
import '../../../users/data/users_api.dart';
|
|
||||||
import '../../data/inbox_api.dart';
|
import '../../data/inbox_api.dart';
|
||||||
import '../../ui/widgets/message_action_sheet.dart';
|
import '../../ui/widgets/message_action_sheet.dart';
|
||||||
|
|
||||||
@@ -34,8 +31,6 @@ class MessageInviteListScreen extends StatefulWidget {
|
|||||||
class _MessageInviteListScreenState extends State<MessageInviteListScreen> {
|
class _MessageInviteListScreenState extends State<MessageInviteListScreen> {
|
||||||
late final InboxApi _inboxApi;
|
late final InboxApi _inboxApi;
|
||||||
late final FriendsApi _friendsApi;
|
late final FriendsApi _friendsApi;
|
||||||
late final CalendarApi _calendarApi;
|
|
||||||
late final UsersApi _usersApi;
|
|
||||||
|
|
||||||
List<MessageWithFriend> _unreadMessages = [];
|
List<MessageWithFriend> _unreadMessages = [];
|
||||||
List<MessageWithFriend> _readMessages = [];
|
List<MessageWithFriend> _readMessages = [];
|
||||||
@@ -48,8 +43,6 @@ class _MessageInviteListScreenState extends State<MessageInviteListScreen> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
_inboxApi = sl<InboxApi>();
|
_inboxApi = sl<InboxApi>();
|
||||||
_friendsApi = sl<FriendsApi>();
|
_friendsApi = sl<FriendsApi>();
|
||||||
_calendarApi = sl<CalendarApi>();
|
|
||||||
_usersApi = sl<UsersApi>();
|
|
||||||
_loadMessages();
|
_loadMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,14 +137,12 @@ class _MessageInviteListScreenState extends State<MessageInviteListScreen> {
|
|||||||
|
|
||||||
final type = content['type'] as String?;
|
final type = content['type'] as String?;
|
||||||
if (type == 'invite') {
|
if (type == 'invite') {
|
||||||
if (message.status.value == 'pending') {
|
context.push(AppRoutes.messageInviteDetail(message.id));
|
||||||
await _showCalendarInviteSheet(message);
|
|
||||||
} else {
|
|
||||||
await _showCalendarInviteReadOnlySheet(message);
|
|
||||||
}
|
|
||||||
} else if (type == 'update') {
|
} else if (type == 'update') {
|
||||||
if (message.scheduleItemId != null) {
|
if (message.scheduleItemId != null) {
|
||||||
context.push('/calendar/events/${message.scheduleItemId}');
|
context.push(
|
||||||
|
AppRoutes.calendarEventDetail(message.scheduleItemId!),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -172,103 +163,6 @@ class _MessageInviteListScreenState extends State<MessageInviteListScreen> {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<(String calendarTitle, String senderName)?> _getCalendarInviteInfo(
|
|
||||||
InboxMessageResponse message,
|
|
||||||
) async {
|
|
||||||
if (message.scheduleItemId == null || message.senderId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final result = await Future.wait([
|
|
||||||
_calendarApi.getById(message.scheduleItemId!),
|
|
||||||
_usersApi.getById(message.senderId!),
|
|
||||||
]);
|
|
||||||
final calendar = result[0] as ScheduleItemModel;
|
|
||||||
final sender = result[1] as UserResponse;
|
|
||||||
return (calendar.title, sender.username);
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showCalendarInviteSheet(InboxMessageResponse message) async {
|
|
||||||
final itemId = message.scheduleItemId;
|
|
||||||
if (itemId == null) return;
|
|
||||||
|
|
||||||
final info = await _getCalendarInviteInfo(message);
|
|
||||||
final title = info != null ? '${info.$2} 邀请你加入日历' : '日历邀请';
|
|
||||||
final description = info?.$1;
|
|
||||||
|
|
||||||
if (!mounted) return;
|
|
||||||
|
|
||||||
showModalBottomSheet<void>(
|
|
||||||
context: context,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
builder: (ctx) => MessageActionSheet(
|
|
||||||
title: title,
|
|
||||||
description: description,
|
|
||||||
icon: Icons.calendar_today,
|
|
||||||
iconColor: AppColors.blue500,
|
|
||||||
onAccept: () async {
|
|
||||||
try {
|
|
||||||
await _calendarApi.acceptSubscription(itemId);
|
|
||||||
await _inboxApi.markAsRead(message.id);
|
|
||||||
if (mounted) {
|
|
||||||
Toast.show(context, '已接受', type: ToastType.success);
|
|
||||||
_loadMessages();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (mounted) {
|
|
||||||
Toast.show(context, '操作失败', type: ToastType.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDecline: () async {
|
|
||||||
try {
|
|
||||||
await _calendarApi.rejectSubscription(itemId);
|
|
||||||
await _inboxApi.markAsRead(message.id);
|
|
||||||
if (mounted) {
|
|
||||||
Toast.show(context, '已拒绝', type: ToastType.success);
|
|
||||||
_loadMessages();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (mounted) {
|
|
||||||
Toast.show(context, '操作失败', type: ToastType.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showCalendarInviteReadOnlySheet(
|
|
||||||
InboxMessageResponse message,
|
|
||||||
) async {
|
|
||||||
final itemId = message.scheduleItemId;
|
|
||||||
if (itemId == null) return;
|
|
||||||
|
|
||||||
final info = await _getCalendarInviteInfo(message);
|
|
||||||
final title = info != null ? '${info.$2} 邀请你加入日历' : '日历邀请';
|
|
||||||
final description = info?.$1;
|
|
||||||
|
|
||||||
final statusText = message.status.value == 'accepted' ? '已接受' : '已拒绝';
|
|
||||||
|
|
||||||
if (!mounted) return;
|
|
||||||
|
|
||||||
showModalBottomSheet<void>(
|
|
||||||
context: context,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
builder: (ctx) => MessageActionSheet(
|
|
||||||
title: title,
|
|
||||||
description: description,
|
|
||||||
statusText: statusText,
|
|
||||||
isReadOnly: true,
|
|
||||||
icon: Icons.calendar_today,
|
|
||||||
iconColor: AppColors.blue500,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _showFriendRequestSheet(
|
void _showFriendRequestSheet(
|
||||||
MessageWithFriend item, {
|
MessageWithFriend item, {
|
||||||
bool isReadOnly = false,
|
bool isReadOnly = false,
|
||||||
|
|||||||
Reference in New Issue
Block a user