feat: integrate invite API and improve notification handling
- Add invite code display and binding functionality via API - Fix notification unread count sync on auth state change - Improve notification mark read with server state validation - Add auth state listener to trigger notification refresh - Add YaoCoinConverter for coin-to-yao type conversion - Remove YaoLegend from divination screens (UI cleanup) - Abbreviate relation labels in yao detail view - Add re-register notice to account delete screen - Update 'coins' terminology to 'points' in localization - Fix backend points consumption to only run in CHAT mode - Add HttpxAuthNoiseFilter to suppress auth endpoint logging - Fix notification static_schema import path - Add test coverage for notification bloc error handling - Update AGENTS.md page header rules and image handling - Delete deprecated run-dev.sh script
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../features/notifications/data/models/notification_item.dart';
|
||||
import '../../theme/design_tokens.dart';
|
||||
|
||||
class NotificationDetailBottomSheet extends StatefulWidget {
|
||||
const NotificationDetailBottomSheet({
|
||||
super.key,
|
||||
required this.item,
|
||||
required this.onMarkRead,
|
||||
});
|
||||
|
||||
final NotificationItem item;
|
||||
final Future<void> Function() onMarkRead;
|
||||
|
||||
@override
|
||||
State<NotificationDetailBottomSheet> createState() =>
|
||||
_NotificationDetailBottomSheetState();
|
||||
}
|
||||
|
||||
class _NotificationDetailBottomSheetState
|
||||
extends State<NotificationDetailBottomSheet> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.onMarkRead();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(AppRadius.lg),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: AppSpacing.sm),
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.outlineVariant,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.item.title,
|
||||
style: textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colors.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: Icon(Icons.close, color: colors.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg),
|
||||
child: Text(
|
||||
_formatTime(widget.item.createdAt),
|
||||
style: textTheme.labelSmall?.copyWith(color: colors.outline),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg),
|
||||
child: Text(
|
||||
widget.item.body,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: colors.onSurfaceVariant,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatTime(DateTime dt) {
|
||||
final now = DateTime.now();
|
||||
final diff = now.difference(dt);
|
||||
if (diff.inMinutes < 1) return '刚刚';
|
||||
if (diff.inHours < 1) return '${diff.inMinutes}分钟前';
|
||||
if (diff.inDays < 1) return '${diff.inHours}小时前';
|
||||
if (diff.inDays < 30) return '${diff.inDays}天前';
|
||||
return '${dt.month}/${dt.day}';
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showNotificationDetailBottomSheet({
|
||||
required BuildContext context,
|
||||
required NotificationItem item,
|
||||
required Future<void> Function() onMarkRead,
|
||||
}) {
|
||||
return showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) =>
|
||||
NotificationDetailBottomSheet(item: item, onMarkRead: onMarkRead),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user