relatica/lib/controls/notifications_control.dart

199 wiersze
6.1 KiB
Dart

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:result_monad/result_monad.dart';
import '../models/auth/profile.dart';
import '../models/exec_error.dart';
import '../models/user_notification.dart';
import '../riverpod_controllers/account_services.dart';
import '../riverpod_controllers/connection_manager_services.dart';
import '../riverpod_controllers/entry_tree_item_services.dart';
import '../riverpod_controllers/notification_services.dart';
import '../routes.dart';
import '../utils/dateutils.dart';
import '../utils/snackbar_builder.dart';
import 'html_text_viewer_control.dart';
import 'image_control.dart';
final _logger = Logger('$NotificationControl');
class NotificationControl extends ConsumerStatefulWidget {
final UserNotification notification;
const NotificationControl({
super.key,
required this.notification,
});
@override
ConsumerState<NotificationControl> createState() =>
_NotificationControlState();
}
class _NotificationControlState extends ConsumerState<NotificationControl> {
bool _processingTap = false;
UserNotification get notification => widget.notification;
Future<void> _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;
}
buildSnackbar(context, 'Fetching status to load');
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) {
final profile = ref.watch(activeProfileProvider);
const iconSize = 50.0;
final fromIcon =
ref.watch(connectionByIdProvider(profile, 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 = _processingTap || 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() {
setState(() {
_processingTap = true;
});
Future.delayed(const Duration(seconds: 20), () {
if (mounted) {
_tapProcessingStop();
}
});
}
void _tapProcessingStop() {
setState(() {
_processingTap = false;
});
}
}