import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.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/auth/profile.dart'; import '../models/exec_error.dart'; import '../models/user_notification.dart'; import '../riverpod_controllers/entry_tree_item_services.dart'; import '../riverpod_controllers/notification_services.dart'; import '../routes.dart'; import '../services/auth_service.dart'; import '../services/connections_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 ConsumerWidget { static final _logger = Logger('$NotificationControl'); final UserNotification notification; bool _processingTap = false; NotificationControl({ super.key, required this.notification, }); Future _goToStatus( BuildContext context, WidgetRef ref, Profile profile) async { final existingPostData = ref.read(postTreeEntryByIdProvider(profile, notification.iid)); if (existingPostData.isSuccess) { context .push('/post/view/${existingPostData.value.id}/${notification.iid}'); return; } final loadedPost = await ref .read(timelineUpdaterProvider(profile).notifier) .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, WidgetRef ref) { final profile = context.watch().currentProfile; const iconSize = 50.0; 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, ref, profile); }; 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: () async { await ref .read(notificationsManagerProvider(profile).notifier) .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; } }