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)
114 0 : : profileOnion = json['profileOnion'],
115 0 : convoId = json['convoId'];
116 :
117 0 : Map<String, dynamic> toJson() => {
118 0 : 'profileOnion': profileOnion,
119 0 : 'convoId': convoId,
120 : };
121 : }
122 :
123 : // FlutterLocalNotificationsPlugin based NotificationManager that handles MacOS <s>and Linux</s>
124 : // TODO: Upgrade from 9.6 to 12.x but there are breaking changes (including for mac)
125 : // TODO: Windows support is being worked on, check back and migrate to that too when it lands
126 : class NixNotificationManager implements NotificationsManager {
127 : late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
128 : late Future<void> Function(String, int) notificationSelectConvo;
129 : late String linuxAssetsPath;
130 :
131 : // Cwtch can install in non flutter supported ways on linux, this code detects where the assets are on Linux
132 0 : Future<String> detectLinuxAssetsPath() async {
133 0 : var devStat = FileStat.stat("assets");
134 0 : var localStat = FileStat.stat("data/flutter_assets");
135 0 : var homeStat = FileStat.stat((Platform.environment["HOME"] ?? "") + "/.local/share/cwtch/data/flutter_assets");
136 0 : var rootStat = FileStat.stat("/usr/share/cwtch/data/flutter_assets");
137 :
138 0 : if ((await devStat).type == FileSystemEntityType.directory) {
139 0 : return Directory.current.path; //appPath;
140 0 : } else if ((await localStat).type == FileSystemEntityType.directory) {
141 0 : return path.join(Directory.current.path, "data/flutter_assets/");
142 0 : } else if ((await homeStat).type == FileSystemEntityType.directory) {
143 0 : return (Platform.environment["HOME"] ?? "") + "/.local/share/cwtch/data/flutter_assets/";
144 0 : } else if ((await rootStat).type == FileSystemEntityType.directory) {
145 : return "/usr/share/cwtch/data/flutter_assets/";
146 : }
147 : return "";
148 : }
149 :
150 0 : NixNotificationManager(Future<void> Function(String, int) notificationSelectConvo) {
151 0 : this.notificationSelectConvo = notificationSelectConvo;
152 0 : flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
153 :
154 0 : scheduleMicrotask(() async {
155 0 : if (Platform.isLinux) {
156 0 : linuxAssetsPath = await detectLinuxAssetsPath();
157 : } else {
158 0 : linuxAssetsPath = "";
159 : }
160 :
161 0 : var linuxIcon = FilePathLinuxIcon(path.join(linuxAssetsPath, 'assets/knott.png'));
162 :
163 0 : final LinuxInitializationSettings initializationSettingsLinux = LinuxInitializationSettings(defaultActionName: 'Open notification', defaultIcon: linuxIcon, defaultSuppressSound: true);
164 :
165 : final InitializationSettings initializationSettings =
166 0 : InitializationSettings(android: null, iOS: null, macOS: DarwinInitializationSettings(defaultPresentSound: false), linux: initializationSettingsLinux);
167 :
168 0 : flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<MacOSFlutterLocalNotificationsPlugin>()?.requestPermissions(
169 : alert: true,
170 : badge: false,
171 : sound: false,
172 : );
173 :
174 0 : await flutterLocalNotificationsPlugin.initialize(
175 : initializationSettings,
176 : );
177 : });
178 : }
179 :
180 0 : Future<void> notify(String message, String profile, int conversationId) async {
181 0 : if (!globalAppState.focus) {
182 : // Warning: Only use title field on Linux, body field will render links as clickable
183 0 : await flutterLocalNotificationsPlugin.show(
184 : 0,
185 : message,
186 : '',
187 0 : NotificationDetails(
188 0 : linux: LinuxNotificationDetails(suppressSound: true, category: LinuxNotificationCategory.imReceived, icon: FilePathLinuxIcon(path.join(linuxAssetsPath, 'assets/knott.png')))),
189 0 : payload: jsonEncode(NotificationPayload(profile, conversationId)));
190 : }
191 : }
192 :
193 : // Notification click response function, triggers ui jump to conversation
194 0 : void selectNotification(String? payloadJson) async {
195 : if (payloadJson != null) {
196 0 : Map<String, dynamic> payloadMap = jsonDecode(payloadJson);
197 0 : var payload = NotificationPayload.fromJson(payloadMap);
198 0 : notificationSelectConvo(payload.profileOnion, payload.convoId);
199 : }
200 : }
201 : }
202 :
203 0 : NotificationsManager newDesktopNotificationsManager(Future<void> Function(String profileOnion, int convoId) notificationSelectConvo) {
204 : // We don't want notifications in Dev Mode
205 : if (EnvironmentConfig.TEST_MODE) {
206 0 : return NullNotificationsManager();
207 : }
208 :
209 0 : if (Platform.isLinux && !Platform.isAndroid) {
210 : try {
211 0 : return NixNotificationManager(notificationSelectConvo);
212 : } catch (e) {
213 0 : EnvironmentConfig.debugLog("Failed to create LinuxNotificationManager. Switching off notifications.");
214 : }
215 0 : } else if (Platform.isMacOS) {
216 : try {
217 0 : return NixNotificationManager(notificationSelectConvo);
218 : } catch (e) {
219 0 : EnvironmentConfig.debugLog("Failed to create NixNotificationManager. Switching off notifications.");
220 : }
221 0 : } else if (Platform.isWindows) {
222 : try {
223 0 : return WindowsNotificationManager(notificationSelectConvo);
224 : } catch (e) {
225 0 : EnvironmentConfig.debugLog("Failed to create Windows desktoasts notification manager");
226 : }
227 : }
228 :
229 0 : return NullNotificationsManager();
230 : }
|