✨ Notification links
This commit is contained in:
		
							
								
								
									
										79
									
								
								lib/models/notification.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								lib/models/notification.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
class Notification {
 | 
			
		||||
  int id;
 | 
			
		||||
  DateTime createdAt;
 | 
			
		||||
  DateTime updatedAt;
 | 
			
		||||
  DateTime? deletedAt;
 | 
			
		||||
  String subject;
 | 
			
		||||
  String content;
 | 
			
		||||
  List<Link>? 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<String, dynamic> 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<Link>.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<String, dynamic> toJson() => {
 | 
			
		||||
        "id": id,
 | 
			
		||||
        "created_at": createdAt.toIso8601String(),
 | 
			
		||||
        "updated_at": updatedAt.toIso8601String(),
 | 
			
		||||
        "deleted_at": deletedAt,
 | 
			
		||||
        "subject": subject,
 | 
			
		||||
        "content": content,
 | 
			
		||||
        "links": links != null
 | 
			
		||||
            ? List<dynamic>.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<String, dynamic> json) => Link(
 | 
			
		||||
        label: json["label"],
 | 
			
		||||
        url: json["url"],
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() => {
 | 
			
		||||
        "label": label,
 | 
			
		||||
        "url": url,
 | 
			
		||||
      };
 | 
			
		||||
}
 | 
			
		||||
@@ -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<NotificationScreen> {
 | 
			
		||||
  final notificationEndpoint =
 | 
			
		||||
      Uri.parse('https://id.solsynth.dev/api/notifications?skip=0&take=25');
 | 
			
		||||
 | 
			
		||||
  List<dynamic> notifications = List.empty();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -26,23 +24,21 @@ class _NotificationScreenState extends State<NotificationScreen> {
 | 
			
		||||
 | 
			
		||||
  Future<void> 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<void> 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<NotificationScreen> {
 | 
			
		||||
                ? 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<NotificationScreen> {
 | 
			
		||||
                    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);
 | 
			
		||||
                        }),
 | 
			
		||||
                      );
 | 
			
		||||
                    },
 | 
			
		||||
                  ),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										94
									
								
								lib/widgets/notification.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								lib/widgets/notification.dart
									
									
									
									
									
										Normal file
									
								
							@@ -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<void>(
 | 
			
		||||
      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<void> 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,
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user