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 : }
|