LCOV - code coverage report
Current view: top level - lib - notification_manager.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 68 0.0 %
Date: 2026-07-15 19:28:30 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : import 'dart:convert';
       3             : import 'dart:io';
       4             : 
       5             : import 'package:cwtch/main.dart';
       6             : import 'package:win_toast/win_toast.dart';
       7             : import 'package:flutter_local_notifications/flutter_local_notifications.dart';
       8             : 
       9             : import 'package:path/path.dart' as path;
      10             : 
      11             : import 'config.dart';
      12             : 
      13             : // NotificationsManager provides a wrapper around platform specific notifications logic.
      14             : abstract class NotificationsManager {
      15             :   Future<void> notify(String message, String profile, int conversationId);
      16             : }
      17             : 
      18             : // NullNotificationsManager ignores all notification requests
      19             : class NullNotificationsManager implements NotificationsManager {
      20           0 :   @override
      21             :   Future<void> notify(String message, String profile, int conversationId) async {}
      22             : }
      23             : 
      24             : // Windows Notification Manager uses https://pub.dev/packages/desktoasts to implement
      25             : // windows notifications
      26             : class WindowsNotificationManager implements NotificationsManager {
      27             :   bool active = false;
      28             :   bool initialized = false;
      29             :   late Future<void> Function(String, int) notificationSelectConvo;
      30             : 
      31           0 :   WindowsNotificationManager(Future<void> Function(String, int) notificationSelectConvo) {
      32           0 :     this.notificationSelectConvo = notificationSelectConvo;
      33           0 :     scheduleMicrotask(() async {
      34           0 :       initialized = await WinToast.instance().initialize(appName: 'cwtch', productName: 'Cwtch', companyName: 'Open Privacy Research Society');
      35             :       /*
      36             :        * WinToast 0.3.0 code for when we can compile it
      37             :        *
      38             :       var init = await WinToast.instance().initialize(
      39             :         aumId: 'OpenPrivacyResearchSociety.Cwtch',
      40             :         displayName: 'Cwtch',
      41             :         iconPath: '', // TODO NEED ICON
      42             :         clsid: 'cwtch',
      43             :       );
      44             :       WinToast.instance().setActivatedCallback((event) {
      45             :         if (event.argument != "close") {
      46             :           try {
      47             :             Map<String, dynamic> payloadMap = jsonDecode(event.argument);
      48             :             var payload = NotificationPayload.fromJson(payloadMap);
      49             :             notificationSelectConvo(payload.profileOnion, payload.convoId);
      50             :           } catch (e) {
      51             :             /* it failed, is ok, may have been 'close'? */
      52             :           }
      53             :         }
      54             :       });*/
      55           0 :       initialized = true;
      56             :     });
      57             :   }
      58             : 
      59           0 :   Future<void> notify(String message, String profile, int conversationId) async {
      60           0 :     if (initialized && !globalAppState.focus) {
      61           0 :       if (!active) {
      62           0 :         active = true;
      63           0 :         WinToast.instance().clear();
      64           0 :         final toast = await WinToast.instance().showToast(type: ToastType.text01, title: message);
      65           0 :         toast?.eventStream.listen((event) {
      66           0 :           if (event is ActivatedEvent) {
      67           0 :             WinToast.instance().bringWindowToFront();
      68             :           }
      69           0 :           active = false;
      70             :         });
      71             :       }
      72             : 
      73             :       /*
      74             :        * WinToast 0.3.0 code for when we can compile it
      75             :        *  
      76             :         WinToast.instance().clear();
      77             :         await WinToast.instance().showToast(
      78             :             toast: Toast(duration: ToastDuration.short, children: [
      79             :           ToastChildAudio(source: ToastAudioSource.im),
      80             :           ToastChildVisual(
      81             :               binding: ToastVisualBinding(children: [
      82             :             ToastVisualBindingChildText(
      83             :               text: message,
      84             :               id: 1,
      85             :             ),
      86             :           ])),
      87             :           ToastChildActions(children: [
      88             :             ToastAction(
      89             :               content: "Open",
      90             :               arguments: jsonEncode(NotificationPayload(profile, conversationId)),
      91             :             ),
      92             :             ToastAction(
      93             :               content: "Close",
      94             :               arguments: "close",
      95             :             ),
      96             :           ]),
      97             :         ]));
      98             :         active = false;
      99             :       }*/
     100             :     }
     101             :   }
     102             : }
     103             : 
     104             : class NotificationPayload {
     105             :   late String profileOnion;
     106             :   late int convoId;
     107             : 
     108           0 :   NotificationPayload(String po, int cid) {
     109           0 :     profileOnion = po;
     110           0 :     convoId = cid;
     111             :   }
     112             : 
     113           0 :   NotificationPayload.fromJson(Map<String, dynamic> json) : profileOnion = json['profileOnion'], convoId = json['convoId'];
     114             : 
     115           0 :   Map<String, dynamic> toJson() => {'profileOnion': profileOnion, 'convoId': convoId};
     116             : }
     117             : 
     118             : // FlutterLocalNotificationsPlugin based NotificationManager that handles MacOS <s>and Linux</s>
     119             : // TODO: Upgrade from 9.6 to 12.x but there are breaking changes (including for mac)
     120             : // TODO: Windows support is being worked on, check back and migrate to that too when it lands
     121             : class NixNotificationManager implements NotificationsManager {
     122             :   late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
     123             :   late Future<void> Function(String, int) notificationSelectConvo;
     124             :   late String linuxAssetsPath;
     125             : 
     126             :   // Cwtch can install in non flutter supported ways on linux, this code detects where the assets are on Linux
     127           0 :   Future<String> detectLinuxAssetsPath() async {
     128           0 :     var devStat = FileStat.stat("assets");
     129           0 :     var localStat = FileStat.stat("data/flutter_assets");
     130           0 :     var homeStat = FileStat.stat((Platform.environment["HOME"] ?? "") + "/.local/share/cwtch/data/flutter_assets");
     131           0 :     var rootStat = FileStat.stat("/usr/share/cwtch/data/flutter_assets");
     132             : 
     133           0 :     if ((await devStat).type == FileSystemEntityType.directory) {
     134           0 :       return Directory.current.path; //appPath;
     135           0 :     } else if ((await localStat).type == FileSystemEntityType.directory) {
     136           0 :       return path.join(Directory.current.path, "data/flutter_assets/");
     137           0 :     } else if ((await homeStat).type == FileSystemEntityType.directory) {
     138           0 :       return (Platform.environment["HOME"] ?? "") + "/.local/share/cwtch/data/flutter_assets/";
     139           0 :     } else if ((await rootStat).type == FileSystemEntityType.directory) {
     140             :       return "/usr/share/cwtch/data/flutter_assets/";
     141             :     }
     142             :     return "";
     143             :   }
     144             : 
     145           0 :   NixNotificationManager(Future<void> Function(String, int) notificationSelectConvo) {
     146           0 :     this.notificationSelectConvo = notificationSelectConvo;
     147           0 :     flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
     148             : 
     149           0 :     scheduleMicrotask(() async {
     150           0 :       if (Platform.isLinux) {
     151           0 :         linuxAssetsPath = await detectLinuxAssetsPath();
     152             :       } else {
     153           0 :         linuxAssetsPath = "";
     154             :       }
     155             : 
     156           0 :       var linuxIcon = FilePathLinuxIcon(path.join(linuxAssetsPath, 'assets/knott.png'));
     157             : 
     158           0 :       final LinuxInitializationSettings initializationSettingsLinux = LinuxInitializationSettings(defaultActionName: 'Open notification', defaultIcon: linuxIcon, defaultSuppressSound: true);
     159             : 
     160           0 :       final InitializationSettings initializationSettings = InitializationSettings(
     161             :         android: null,
     162             :         iOS: null,
     163           0 :         macOS: DarwinInitializationSettings(defaultPresentSound: false),
     164             :         linux: initializationSettingsLinux,
     165             :       );
     166             : 
     167           0 :       flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<MacOSFlutterLocalNotificationsPlugin>()?.requestPermissions(alert: true, badge: false, sound: false);
     168             : 
     169           0 :       await flutterLocalNotificationsPlugin.initialize(settings: initializationSettings);
     170             :     });
     171             :   }
     172             : 
     173           0 :   Future<void> notify(String message, String profile, int conversationId) async {
     174           0 :     if (!globalAppState.focus) {
     175             :       // Warning: Only use title field on Linux, body field will render links as clickable
     176           0 :       await flutterLocalNotificationsPlugin.show(
     177             :         id: 0,
     178             :         title: message,
     179             :         body: '',
     180           0 :         notificationDetails: NotificationDetails(
     181           0 :           linux: LinuxNotificationDetails(suppressSound: true, category: LinuxNotificationCategory.imReceived, icon: FilePathLinuxIcon(path.join(linuxAssetsPath, 'assets/knott.png'))),
     182             :         ),
     183           0 :         payload: jsonEncode(NotificationPayload(profile, conversationId)),
     184             :       );
     185             :     }
     186             :   }
     187             : 
     188             :   // Notification click response function, triggers ui jump to conversation
     189           0 :   void selectNotification(String? payloadJson) async {
     190             :     if (payloadJson != null) {
     191           0 :       Map<String, dynamic> payloadMap = jsonDecode(payloadJson);
     192           0 :       var payload = NotificationPayload.fromJson(payloadMap);
     193           0 :       notificationSelectConvo(payload.profileOnion, payload.convoId);
     194             :     }
     195             :   }
     196             : }
     197             : 
     198           0 : NotificationsManager newDesktopNotificationsManager(Future<void> Function(String profileOnion, int convoId) notificationSelectConvo) {
     199             :   // We don't want notifications in Dev Mode
     200             :   if (EnvironmentConfig.TEST_MODE) {
     201           0 :     return NullNotificationsManager();
     202             :   }
     203             : 
     204           0 :   if (Platform.isLinux && !Platform.isAndroid) {
     205             :     try {
     206           0 :       return NixNotificationManager(notificationSelectConvo);
     207             :     } catch (e) {
     208           0 :       EnvironmentConfig.debugLog("Failed to create LinuxNotificationManager. Switching off notifications.");
     209             :     }
     210           0 :   } else if (Platform.isMacOS) {
     211             :     try {
     212           0 :       return NixNotificationManager(notificationSelectConvo);
     213             :     } catch (e) {
     214           0 :       EnvironmentConfig.debugLog("Failed to create NixNotificationManager. Switching off notifications.");
     215             :     }
     216           0 :   } else if (Platform.isWindows) {
     217             :     try {
     218           0 :       return WindowsNotificationManager(notificationSelectConvo);
     219             :     } catch (e) {
     220           0 :       EnvironmentConfig.debugLog("Failed to create Windows desktoasts notification manager");
     221             :     }
     222             :   }
     223             : 
     224           0 :   return NullNotificationsManager();
     225             : }

Generated by: LCOV version 1.14