Initial rudimentary notifications viewer and back end

This commit is contained in:
Hank Grabowski 2022-11-19 00:00:17 -05:00
parent 6af1c4f214
commit f4dee56650
7 changed files with 191 additions and 8 deletions

View file

@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
import '../models/user_notification.dart';
import '../utils/dateutils.dart';
class NotificationControl extends StatelessWidget {
final UserNotification notification;
const NotificationControl({
super.key,
required this.notification,
});
@override
Widget build(BuildContext context) {
return ListTile(
tileColor: notification.seen ? null : Colors.black12,
leading: Text(notification.fromName),
title: HtmlWidget(notification.content),
subtitle:
Text(ElapsedDateUtils.epochSecondsToString(notification.timestamp)),
trailing: notification.seen
? null
: IconButton(onPressed: () {}, icon: Icon(Icons.close_rounded)),
);
}
}

View file

@ -8,6 +8,8 @@ import 'models/TimelineIdentifiers.dart';
import 'models/credentials.dart';
import 'models/exec_error.dart';
import 'models/timeline_entry.dart';
import 'models/user_notification.dart';
import 'serializers/friendica/notification_friendica_extension.dart';
import 'serializers/mastodon/timeline_entry_mastodon_extensions.dart';
class FriendicaClient {
@ -27,6 +29,23 @@ class FriendicaClient {
_authHeader = "Basic $encodedAuthString";
}
FutureResult<List<UserNotification>, ExecError> getNotifications() async {
final url = 'https://$serverName/api/friendica/notifications';
final request = Uri.parse(url);
_logger.finest(() => 'Getting new notifications');
return (await _getApiListRequest(request).andThenSuccessAsync(
(notificationsJson) async => notificationsJson
.map((json) => NotificationFriendicaExtension.fromJson(json))
.toList()))
.mapError((error) {
if (error is ExecError) {
return error;
}
return ExecError(type: ErrorType.localError, message: error.toString());
});
}
FutureResult<List<TimelineEntry>, ExecError> getUserTimeline(
{String userId = '', int page = 1, int count = 10}) async {
_logger.finest(() => 'Getting user timeline for $userId');

View file

@ -9,6 +9,7 @@ import 'routes.dart';
import 'services/auth_service.dart';
import 'services/connections_manager.dart';
import 'services/entry_manager_service.dart';
import 'services/notifications_manager.dart';
import 'services/secrets_service.dart';
import 'services/timeline_manager.dart';
import 'utils/app_scrolling_behavior.dart';
@ -31,6 +32,8 @@ void main() async {
getIt.registerSingleton<SecretsService>(secretsService);
getIt.registerSingleton<AuthService>(authService);
getIt.registerSingleton<TimelineManager>(timelineManager);
getIt.registerLazySingleton<NotificationsManager>(
() => NotificationsManager());
await secretsService.initialize().andThenSuccessAsync((credentials) async {
if (credentials.isEmpty) {
return;
@ -71,6 +74,9 @@ class App extends StatelessWidget {
ChangeNotifierProvider<TimelineManager>(
create: (_) => getIt<TimelineManager>(),
),
ChangeNotifierProvider<NotificationsManager>(
create: (_) => getIt<NotificationsManager>(),
),
],
child: MaterialApp.router(
theme: ThemeData(

View file

@ -0,0 +1,32 @@
enum NotificationType {
favorite,
follow,
follow_request,
mention,
reshare,
status
}
class UserNotification {
final String id;
final String type;
final String fromName;
final String fromUrl;
final int timestamp;
final String iid;
final bool seen;
final String content;
final String link;
UserNotification({
required this.id,
required this.type,
required this.fromName,
required this.fromUrl,
required this.timestamp,
required this.iid,
required this.seen,
required this.content,
required this.link,
});
}

View file

@ -1,24 +1,32 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../controls/app_bottom_nav_bar.dart';
import '../controls/notifications_control.dart';
import '../services/notifications_manager.dart';
class NotificationsScreen extends StatelessWidget {
const NotificationsScreen({super.key});
@override
Widget build(BuildContext context) {
final manager = context.watch<NotificationsManager>();
final notifications = manager.notifications;
if (notifications.isEmpty) {
manager.updateNotifications();
}
return Scaffold(
appBar: AppBar(
title: Text('Notifications'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Notifications'),
],
),
),
body: ListView.separated(
itemBuilder: (context, index) {
return NotificationControl(notification: notifications[index]);
},
separatorBuilder: (context, index) {
return Divider();
},
itemCount: notifications.length),
bottomNavigationBar: AppBottomNavBar(
currentButton: NavBarButtons.notifications,
),

View file

@ -0,0 +1,16 @@
import '../../models/user_notification.dart';
extension NotificationFriendicaExtension on UserNotification {
static UserNotification fromJson(Map<String, dynamic> json) =>
UserNotification(
id: json['id'].toString(),
type: json['type'].toString(),
fromName: json['name'],
fromUrl: json['url'],
timestamp: int.tryParse(json['timestamp'] ?? '') ?? 0,
iid: json['iid'].toString(),
seen: json['seen'] ?? false,
content: json['msg_html'],
link: json['link'],
);
}

View file

@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
import '../globals.dart';
import '../models/exec_error.dart';
import '../models/user_notification.dart';
import 'auth_service.dart';
class NotificationsManager extends ChangeNotifier {
static final _logger = Logger('NotificationManager');
final _notifications = <String, UserNotification>{};
List<UserNotification> get notifications {
final result = List<UserNotification>.from(_notifications.values);
result.sort((n1, n2) {
if (n1.seen == n2.seen) {
return n2.timestamp.compareTo(n1.timestamp);
}
if (n1.seen && !n2.seen) {
return 1;
}
return -1;
});
return result;
}
FutureResult<List<UserNotification>, ExecError> updateNotifications() async {
final auth = getIt<AuthService>();
final clientResult = auth.currentClient;
if (clientResult.isFailure) {
_logger.severe('Error getting Friendica client: ${clientResult.error}');
return clientResult.errorCast();
}
final client = clientResult.value;
final result = await client.getNotifications();
if (result.isFailure) {
return result.errorCast();
}
for (final n in result.value) {
_notifications[n.id] = n;
}
notifyListeners();
return Result.ok(notifications);
}
FutureResult<UserNotification, ExecError> markSeen(String id) async {
final auth = getIt<AuthService>();
final clientResult = auth.currentClient;
if (clientResult.isFailure) {
_logger.severe('Error getting Friendica client: ${clientResult.error}');
return clientResult.errorCast();
}
final client = clientResult.value;
final result = await client.getNotifications();
if (result.isFailure) {
return result.errorCast();
}
for (final n in result.value) {
_notifications[n.id] = n;
}
notifyListeners();
return Result.ok(notifications.first);
}
}