diff --git a/lib/models/notification.dart b/lib/models/notification.dart
new file mode 100644
index 0000000..e8cd55f
--- /dev/null
+++ b/lib/models/notification.dart
@@ -0,0 +1,79 @@
+class Notification {
+ int id;
+ DateTime createdAt;
+ DateTime updatedAt;
+ DateTime? deletedAt;
+ String subject;
+ String content;
+ List? links;
+ bool isImportant;
+ DateTime? readAt;
+ int senderId;
+ int recipientId;
+
+ Notification({
+ required this.id,
+ required this.createdAt,
+ required this.updatedAt,
+ this.deletedAt,
+ required this.subject,
+ required this.content,
+ this.links,
+ required this.isImportant,
+ this.readAt,
+ required this.senderId,
+ required this.recipientId,
+ });
+
+ factory Notification.fromJson(Map json) => Notification(
+ id: json["id"],
+ createdAt: DateTime.parse(json["created_at"]),
+ updatedAt: DateTime.parse(json["updated_at"]),
+ deletedAt: json["deleted_at"],
+ subject: json["subject"],
+ content: json["content"],
+ links: json["links"] != null
+ ? List.from(json["links"].map((x) => Link.fromJson(x)))
+ : List.empty(),
+ isImportant: json["is_important"],
+ readAt: json["read_at"],
+ senderId: json["sender_id"],
+ recipientId: json["recipient_id"],
+ );
+
+ Map toJson() => {
+ "id": id,
+ "created_at": createdAt.toIso8601String(),
+ "updated_at": updatedAt.toIso8601String(),
+ "deleted_at": deletedAt,
+ "subject": subject,
+ "content": content,
+ "links": links != null
+ ? List.from(links!.map((x) => x.toJson()))
+ : List.empty(),
+ "is_important": isImportant,
+ "read_at": readAt,
+ "sender_id": senderId,
+ "recipient_id": recipientId,
+ };
+}
+
+class Link {
+ String label;
+ String url;
+
+ Link({
+ required this.label,
+ required this.url,
+ });
+
+ factory Link.fromJson(Map json) => Link(
+ label: json["label"],
+ url: json["url"],
+ );
+
+ Map toJson() => {
+ "label": label,
+ "url": url,
+ };
+}
diff --git a/lib/screens/notifications.dart b/lib/screens/notifications.dart
index 5742b00..c107465 100644
--- a/lib/screens/notifications.dart
+++ b/lib/screens/notifications.dart
@@ -1,9 +1,10 @@
import 'dart:convert';
-import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
import 'package:solaragent/auth.dart';
+import 'package:solaragent/models/notification.dart' as model;
+import 'package:solaragent/models/pagination.dart';
+import 'package:solaragent/widgets/notification.dart';
class NotificationScreen extends StatefulWidget {
const NotificationScreen({super.key});
@@ -13,9 +14,6 @@ class NotificationScreen extends StatefulWidget {
}
class _NotificationScreenState extends State {
- final notificationEndpoint =
- Uri.parse('https://id.solsynth.dev/api/notifications?skip=0&take=25');
-
List notifications = List.empty();
@override
@@ -26,23 +24,21 @@ class _NotificationScreenState extends State {
Future pullNotifications() async {
if (await authClient.isAuthorized()) {
- var res = await authClient.client!.get(notificationEndpoint);
+ var uri =
+ Uri.parse('https://id.solsynth.dev/api/notifications?skip=0&take=25');
+ var res = await authClient.client!.get(uri);
if (res.statusCode == 200) {
+ final result =
+ PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
setState(() {
- notifications = jsonDecode(utf8.decode(res.bodyBytes))["data"];
+ notifications =
+ result.data?.map((x) => model.Notification.fromJson(x)).toList() ??
+ List.empty();
});
}
}
}
- Future markAsRead(element) async {
- if (authClient.client != null) {
- var id = element['id'];
- var uri = Uri.parse('https://id.solsynth.dev/api/notifications/$id/read');
- await authClient.client!.put(uri);
- }
- }
-
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -67,7 +63,7 @@ class _NotificationScreenState extends State {
? SliverToBoxAdapter(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
- color: Colors.grey[300],
+ color: Colors.grey[50],
child: const ListTile(
leading: Icon(Icons.check),
title: Text('You\'re done!'),
@@ -81,42 +77,12 @@ class _NotificationScreenState extends State {
itemCount: notifications.length,
itemBuilder: (BuildContext context, int index) {
var element = notifications[index];
- return Dismissible(
- key: Key('notification-$index'),
- onDismissed: (direction) {
- var subject = element["subject"];
- markAsRead(element).then((value) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: RichText(
- text: TextSpan(children: [
- TextSpan(
- text: subject,
- style: const TextStyle(
- fontWeight: FontWeight.bold),
- ),
- const TextSpan(
- text: " is marked as read",
- )
- ]),
- ),
- ),
- );
- });
- setState(() {
- notifications.removeAt(index);
- });
- },
- background: Container(
- color: Colors.green,
- ),
- child: Container(
- padding: const EdgeInsets.symmetric(horizontal: 10),
- child: ListTile(
- title: Text(element["subject"]),
- subtitle: Text(element["content"]),
- ),
- ),
+ return NotificationItem(
+ index: index,
+ item: element,
+ onDismiss: () => setState(() {
+ notifications.removeAt(index);
+ }),
);
},
),
diff --git a/lib/widgets/notification.dart b/lib/widgets/notification.dart
new file mode 100644
index 0000000..70a9362
--- /dev/null
+++ b/lib/widgets/notification.dart
@@ -0,0 +1,94 @@
+import 'package:flutter/material.dart';
+import 'package:solaragent/models/notification.dart' as model;
+import 'package:solaragent/auth.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+class NotificationItem extends StatelessWidget {
+ final int index;
+ final model.Notification item;
+ final void Function()? onDismiss;
+
+ const NotificationItem(
+ {super.key, required this.index, required this.item, this.onDismiss});
+
+ bool hasLinks() => item.links != null && item.links!.isNotEmpty;
+
+ void showLinks(BuildContext context) {
+ if (!hasLinks()) return;
+
+ showModalBottomSheet(
+ context: context,
+ builder: (BuildContext context) {
+ return ListView.builder(
+ padding: const EdgeInsets.all(8),
+ itemCount: item.links!.length,
+ itemBuilder: (BuildContext context, int index) {
+ var element = item.links![index];
+ return InkWell(
+ borderRadius: const BorderRadius.all(
+ Radius.circular(64),
+ ),
+ onTap: () async {
+ await launchUrl(Uri.parse(element.url));
+ },
+ child: ListTile(title: Text(element.label)),
+ );
+ });
+ },
+ );
+ }
+
+ Future markAsRead(element) async {
+ if (authClient.client != null) {
+ var id = element['id'];
+ var uri = Uri.parse('https://id.solsynth.dev/api/notifications/$id/read');
+ await authClient.client!.put(uri);
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Dismissible(
+ key: Key('notification-$index'),
+ onDismissed: (direction) {
+ markAsRead(item).then((value) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: RichText(
+ text: TextSpan(children: [
+ TextSpan(
+ text: item.subject,
+ style: const TextStyle(fontWeight: FontWeight.bold),
+ ),
+ const TextSpan(
+ text: " is marked as read",
+ )
+ ]),
+ ),
+ ),
+ );
+ });
+ if (onDismiss != null) {
+ onDismiss!();
+ }
+ },
+ background: Container(
+ color: Colors.grey,
+ ),
+ child: Container(
+ padding: const EdgeInsets.only(left: 10),
+ child: ListTile(
+ title: Text(item.subject),
+ subtitle: Text(item.subject),
+ trailing: hasLinks()
+ ? TextButton(
+ onPressed: () => showLinks(context),
+ style: TextButton.styleFrom(shape: const CircleBorder()),
+ child: const Icon(Icons.more_vert),
+ )
+ : null,
+ ),
+ ),
+ );
+ }
+}