@@ -6,6 +6,7 @@ import '../services/api/tf_api_client.dart';
66import '../services/auth_state.dart' ;
77import '../services/notification_service.dart' ;
88import '../utils/talker.dart' ;
9+ import '../widgets/markdown_renderer.dart' ;
910
1011class AnnouncementScreen extends StatefulWidget {
1112 const AnnouncementScreen ({super .key});
@@ -143,6 +144,68 @@ class _AnnouncementScreenState extends State<AnnouncementScreen> {
143144 }
144145 }
145146
147+ Future <void > _showEditDialog (Announcement a) async {
148+ final l10n = AppLocalizations .of (context)! ;
149+ final controller = TextEditingController (text: a.content);
150+ final ok = await showDialog <bool >(
151+ context: context,
152+ builder: (ctx) => AlertDialog (
153+ title: Text (l10n.announcementEdit),
154+ content: TextField (
155+ controller: controller,
156+ maxLines: 5 ,
157+ decoration: InputDecoration (
158+ hintText: l10n.announcementEditHint,
159+ border: const OutlineInputBorder (),
160+ ),
161+ ),
162+ actions: [
163+ TextButton (
164+ onPressed: () => Navigator .pop (ctx, false ),
165+ child: Text (l10n.cancel),
166+ ),
167+ FilledButton (
168+ onPressed: () => Navigator .pop (ctx, true ),
169+ child: Text (l10n.confirm),
170+ ),
171+ ],
172+ ),
173+ );
174+ if (ok != true ) return ;
175+
176+ final content = controller.text.trim ();
177+ if (mounted) {
178+ if (content.isEmpty) {
179+ ScaffoldMessenger .of (
180+ context,
181+ ).showSnackBar (SnackBar (content: Text (l10n.announcementEditEmpty)));
182+ return ;
183+ }
184+ final uid = AuthState .instance.uid;
185+ final password = AuthState .instance.password;
186+ if (uid == null || password == null ) return ;
187+ final success = await TfApiClient .instance.editAnnouncement (
188+ uid,
189+ password,
190+ a.timeStamp,
191+ content,
192+ );
193+ if (mounted) {
194+ final l10n = AppLocalizations .of (context)! ;
195+ ScaffoldMessenger .of (context).showSnackBar (
196+ SnackBar (
197+ content: Text (
198+ success
199+ ? l10n.announcementEditSuccess
200+ : l10n.announcementEditFailed,
201+ ),
202+ ),
203+ );
204+ if (success) _load ();
205+ }
206+ }
207+ }
208+
146209 Future <void > _confirmDelete (Announcement a) async {
147210 final l10n = AppLocalizations .of (context)! ;
148211 final ok = await showDialog <bool >(
@@ -289,6 +352,7 @@ class _AnnouncementScreenState extends State<AnnouncementScreen> {
289352 itemBuilder: (context, index) => _AnnouncementCard (
290353 announcement: list[index],
291354 isAdmin: _isAdmin,
355+ onEdit: () => _showEditDialog (list[index]),
292356 onDelete: () => _confirmDelete (list[index]),
293357 ),
294358 ),
@@ -387,11 +451,13 @@ class _AnnouncementNotificationSheet extends StatelessWidget {
387451class _AnnouncementCard extends StatelessWidget {
388452 final Announcement announcement;
389453 final bool isAdmin;
454+ final VoidCallback onEdit;
390455 final VoidCallback onDelete;
391456
392457 const _AnnouncementCard ({
393458 required this .announcement,
394459 required this .isAdmin,
460+ required this .onEdit,
395461 required this .onDelete,
396462 });
397463
@@ -404,42 +470,55 @@ class _AnnouncementCard extends StatelessWidget {
404470
405471 return Card (
406472 margin: const EdgeInsets .symmetric (vertical: 6 ),
407- child: InkWell (
408- borderRadius: BorderRadius .circular (12 ),
409- onLongPress: isAdmin ? onDelete : null ,
410- child: Padding (
411- padding: const EdgeInsets .all (20 ),
412- child: Column (
413- crossAxisAlignment: CrossAxisAlignment .start,
414- children: [
415- Row (
416- children: [
417- Icon (
418- Icons .campaign_outlined,
419- size: 24 ,
473+ child: Padding (
474+ padding: const EdgeInsets .all (20 ),
475+ child: Column (
476+ crossAxisAlignment: CrossAxisAlignment .start,
477+ children: [
478+ Row (
479+ children: [
480+ Icon (
481+ Icons .campaign_outlined,
482+ size: 24 ,
483+ color: theme.colorScheme.primary,
484+ ),
485+ const SizedBox (width: 10 ),
486+ Text (
487+ senderLabel,
488+ style: theme.textTheme.titleSmall? .copyWith (
420489 color: theme.colorScheme.primary,
490+ fontWeight: FontWeight .w600,
421491 ),
422- const SizedBox (width: 10 ),
423- Text (
424- senderLabel,
425- style: theme.textTheme.titleSmall? .copyWith (
426- color: theme.colorScheme.primary,
427- fontWeight: FontWeight .w600,
428- ),
492+ ),
493+ const Spacer (),
494+ if (isAdmin) ...[
495+ IconButton (
496+ icon: const Icon (Icons .edit_outlined),
497+ tooltip: '编辑公告' ,
498+ iconSize: 18 ,
499+ onPressed: onEdit,
429500 ),
430- const Spacer (),
431- Text (
432- timeLabel,
433- style: theme.textTheme.bodyMedium? .copyWith (
434- color: theme.colorScheme.onSurfaceVariant,
501+ IconButton (
502+ icon: Icon (
503+ Icons .delete_outline,
504+ color: theme.colorScheme.error,
435505 ),
506+ tooltip: '删除公告' ,
507+ iconSize: 18 ,
508+ onPressed: onDelete,
436509 ),
437510 ],
438- ),
439- const SizedBox (height: 12 ),
440- Text (announcement.content, style: theme.textTheme.bodyLarge),
441- ],
442- ),
511+ Text (
512+ timeLabel,
513+ style: theme.textTheme.bodyMedium? .copyWith (
514+ color: theme.colorScheme.onSurfaceVariant,
515+ ),
516+ ),
517+ ],
518+ ),
519+ const SizedBox (height: 12 ),
520+ MarkdownRenderer (data: announcement.content),
521+ ],
443522 ),
444523 ),
445524 );
0 commit comments