import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; import 'package:result_monad/result_monad.dart'; import '../globals.dart'; import '../models/exec_error.dart'; import '../models/user_notification.dart'; import '../routes.dart'; import '../services/connections_manager.dart'; import '../services/notifications_manager.dart'; import '../services/timeline_manager.dart'; import '../utils/active_profile_selector.dart'; import '../utils/dateutils.dart'; import '../utils/snackbar_builder.dart'; import 'html_text_viewer_control.dart'; import 'image_control.dart'; class NotificationControl extends StatelessWidget { static final _logger = Logger('$NotificationControl'); final UserNotification notification; bool _processingTap = false; NotificationControl({ super.key, required this.notification, }); Future _goToStatus(BuildContext context) async { final manager = getIt>().activeEntry.value; final existingPostData = manager.getPostTreeEntryBy(notification.iid); if (existingPostData.isSuccess) { context .push('/post/view/${existingPostData.value.id}/${notification.iid}'); return; } final loadedPost = await manager.refreshStatusChain(notification.iid); if (loadedPost.isSuccess) { if (context.mounted) { context.push('/post/view/${loadedPost.value.id}/${notification.iid}'); } return; } if (context.mounted) { buildSnackbar( context, 'Error getting data for notification: ${loadedPost.error}'); } } @override Widget build(BuildContext context) { const iconSize = 50.0; final manager = context .watch>() .activeEntry .fold( onSuccess: (manager) => manager, onError: (error) { _logger.severe('Error getting notification manager: $error'); return null; }, ); final fromIcon = getIt>() .activeEntry .andThen((manager) => manager.getById(notification.fromId)) .fold( onSuccess: (connection) => ImageControl( imageUrl: connection.avatarUrl.toString(), iconOverride: const Icon(Icons.person), width: iconSize, height: iconSize, onTap: () async { context.pushNamed(ScreenPaths.userProfile, pathParameters: {'id': notification.fromId}); }, ), onError: (error) => GestureDetector( onTap: () async { context.pushNamed(ScreenPaths.userProfile, pathParameters: {'id': notification.fromId}); }, child: const SizedBox( width: iconSize, height: iconSize, child: Icon(Icons.person), ), ), ); Function()? onTapCallFunction; switch (notification.type) { case NotificationType.follow: onTapCallFunction = () async { await context.pushNamed(ScreenPaths.userProfile, pathParameters: {'id': notification.fromId}); }; break; case NotificationType.follow_request: onTapCallFunction = () async { await context.push('/connect/${notification.fromId}'); }; break; case NotificationType.unknown: buildSnackbar(context, 'Unknown message type, nothing to do'); break; case NotificationType.favourite: case NotificationType.mention: case NotificationType.reshare: case NotificationType.reblog: case NotificationType.status: onTapCallFunction = () async { await _goToStatus(context); }; break; case NotificationType.direct_message: onTapCallFunction = () async => await context.pushNamed( ScreenPaths.thread, queryParameters: {'uri': notification.iid}, ); break; } final onTap = onTapCallFunction == null ? null : () async { if (_processingTap) { return true; } _tapProcessingStarted(); await onTapCallFunction!(); _tapProcessingStop(); return true; }; return ListTile( tileColor: notification.dismissed ? null : Colors.black12, leading: fromIcon, title: GestureDetector( onTap: onTap, child: HtmlTextViewerControl( content: notification.content, onTapUrl: (_) async => onTap!(), ), ), subtitle: notification.type == NotificationType.follow_request ? null : GestureDetector( onTap: onTap, child: Text( ElapsedDateUtils.elapsedTimeStringFromEpochSeconds( notification.timestamp), ), ), trailing: notification.dismissed || notification.type == NotificationType.direct_message || notification.type == NotificationType.follow_request ? null : IconButton( onPressed: manager == null ? null : () async { await manager.markSeen(notification).withError((error) { buildSnackbar( context, 'Error marking notification: $error'); logError(error, _logger); }); }, icon: const Icon(Icons.close_rounded)), ); } void _tapProcessingStarted() { _processingTap = true; Future.delayed(const Duration(seconds: 10), () => _processingTap = false); } void _tapProcessingStop() { _processingTap = false; } }