make NixNotificationManager using flutter_local_notification
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
Dan Ballard 2022-02-18 15:50:34 -08:00
parent 6859780873
commit 7509c20a62
6 changed files with 147 additions and 70 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -165,10 +165,10 @@ class CwtchNotifier {
var notification = data["notification"];
if (notification == "SimpleEvent") {
notificationManager.notify(notificationSimple ?? "New Message");
notificationManager.notify(notificationSimple ?? "New Message", "", 0);
} else if (notification == "ContactInfo") {
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier);
notificationManager.notify((notificationConversationInfo ?? "New Message from %1").replaceFirst("%1", (contact?.nickname ?? senderHandle.toString())));
notificationManager.notify((notificationConversationInfo ?? "New Message from %1").replaceFirst("%1", (contact?.nickname ?? senderHandle.toString())), data["ProfileOnion"], identifier);
}
profileCN.getProfile(data["ProfileOnion"])?.newMessage(
@ -242,10 +242,10 @@ class CwtchNotifier {
profileCN.getProfile(data["ProfileOnion"])?.newMessage(identifier, idx, timestampSent, senderHandle, senderImage, isAuto, data["Data"], contenthash, selectedProfile, selectedConversation);
if (notification == "SimpleEvent") {
notificationManager.notify(notificationSimple ?? "New Message");
notificationManager.notify(notificationSimple ?? "New Message", "", 0);
} else if (notification == "ContactInfo") {
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier);
notificationManager.notify((notificationConversationInfo ?? "New Message from %1").replaceFirst("%1", (contact?.nickname ?? senderHandle.toString())));
notificationManager.notify((notificationConversationInfo ?? "New Message from %1").replaceFirst("%1", (contact?.nickname ?? senderHandle.toString())), data["ProfileOnion"], identifier);
}
appState.notifyProfileUnread();
}

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:cwtch/config.dart';
import 'package:cwtch/notification_manager.dart';
import 'package:cwtch/themes/cwtch.dart';
import 'package:cwtch/views/doublecolview.dart';
import 'package:cwtch/views/messageview.dart';
import 'package:flutter/foundation.dart';
import 'package:cwtch/cwtch/ffi.dart';
@ -17,8 +18,11 @@ import 'cwtch/cwtch.dart';
import 'cwtch/cwtchNotifier.dart';
import 'licenses.dart';
import 'models/appstate.dart';
import 'models/contactlist.dart';
import 'models/profile.dart';
import 'models/profilelist.dart';
import 'models/servers.dart';
import 'views/contactsview.dart';
import 'views/profilemgrview.dart';
import 'views/splashView.dart';
import 'dart:io' show Platform, exit, sleep;
@ -78,10 +82,10 @@ class FlwtchState extends State<Flwtch> with WindowListener {
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState, globalServersList);
cwtch = CwtchGomobile(cwtchNotifier);
} else if (Platform.isLinux) {
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(), globalAppState, globalServersList);
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(_notificationSelectConvo), globalAppState, globalServersList);
cwtch = CwtchFfi(cwtchNotifier);
} else {
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(), globalAppState, globalServersList);
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(_notificationSelectConvo), globalAppState, globalServersList);
cwtch = CwtchFfi(cwtchNotifier);
}
print("initState: invoking cwtch.Start()");
@ -182,36 +186,43 @@ class FlwtchState extends State<Flwtch> with WindowListener {
// coder beware: args["RemotePeer"] is actually a handle, and could be eg a groupID
Future<void> _externalNotificationClicked(MethodCall call) async {
var args = jsonDecode(call.arguments);
var profile = profs.getProfile(args["ProfileOnion"])!;
var convo = profile.contactList.getContact(args["Handle"])!;
_notificationSelectConvo(args["ProfileOnion"], args["Handle"]);
}
Future<void> _notificationSelectConvo(String profileOnion, int convoId) async {
var profile = profs.getProfile(profileOnion)!;
var convo = profile.contactList.getContact(convoId)!;
if (profileOnion.isEmpty) {
return;
}
Provider.of<AppState>(navKey.currentContext!, listen: false).initialScrollIndex = convo.unreadMessages;
convo.unreadMessages = 0;
// single pane mode pushes; double pane mode reads AppState.selectedProfile/Conversation
var isLandscape = Provider.of<AppState>(navKey.currentContext!, listen: false).isLandscape(navKey.currentContext!);
if (Provider.of<Settings>(navKey.currentContext!, listen: false).uiColumns(isLandscape).length == 1) {
// Clear nav path back to root
while (navKey.currentState!.canPop()) {
print("messageview already open; popping before pushing replacement");
navKey.currentState!.pop();
}
navKey.currentState?.push(
Provider.of<AppState>(navKey.currentContext!, listen: false).selectedConversation = null;
Provider.of<AppState>(navKey.currentContext!, listen: false).selectedProfile = profileOnion;
Provider.of<AppState>(navKey.currentContext!, listen: false).selectedConversation = convoId;
Navigator.of(navKey.currentContext!).push(
MaterialPageRoute<void>(
builder: (BuildContext builderContext) {
settings: RouteSettings(name: "conversations"),
builder: (BuildContext buildcontext) {
return OrientationBuilder(builder: (orientationBuilderContext, orientation) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: profile),
ChangeNotifierProvider.value(value: convo),
],
builder: (context, child) => MessageView(),
);
providers: [ChangeNotifierProvider<ProfileInfoState>.value(value: profile), ChangeNotifierProvider<ContactListState>.value(value: profile.contactList)],
builder: (innercontext, widget) {
var appState = Provider.of<AppState>(navKey.currentContext!);
var settings = Provider.of<Settings>(navKey.currentContext!);
return settings.uiColumns(appState.isLandscape(innercontext)).length > 1 ? DoubleColumnView() : MessageView();
});
});
},
),
);
} else {
//dual pane
Provider.of<AppState>(navKey.currentContext!, listen: false).selectedProfile = args["ProfileOnion"];
Provider.of<AppState>(navKey.currentContext!, listen: false).selectedConversation = args["Handle"];
}
}
// using windowManager flutter plugin until proper lifecycle management lands in desktop

View File

@ -1,38 +1,24 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:cwtch/main.dart';
import 'package:win_toast/win_toast.dart';
import 'package:desktop_notifications/desktop_notifications.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:path/path.dart' as path;
import 'config.dart';
// NotificationsManager provides a wrapper around platform specific notifications logic.
abstract class NotificationsManager {
Future<void> notify(String message);
Future<void> notify(String message, String profile, int conversationId);
}
// NullNotificationsManager ignores all notification requests
class NullNotificationsManager implements NotificationsManager {
@override
Future<void> notify(String message) async {}
}
// LinuxNotificationsManager uses the desktop_notifications package to implement
// the standard dbus-powered linux desktop notifications.
class LinuxNotificationsManager implements NotificationsManager {
int previous_id = 0;
late NotificationsClient client;
LinuxNotificationsManager(NotificationsClient client) {
this.client = client;
}
Future<void> notify(String message) async {
var iconPath = Uri.file(path.join(path.current, "cwtch.png"));
client.notify(message, appName: "cwtch", appIcon: iconPath.toString(), replacesId: this.previous_id).then((Notification value) => previous_id = value.id);
}
Future<void> notify(String message, String profile, int conversationId) async {}
}
// Windows Notification Manager uses https://pub.dev/packages/desktoasts to implement
@ -47,7 +33,7 @@ class WindowsNotificationManager implements NotificationsManager {
});
}
Future<void> notify(String message) async {
Future<void> notify(String message, String profile, int conversationId) async {
if (initialized && !globalAppState.focus) {
if (!active) {
active = true;
@ -64,16 +50,73 @@ class WindowsNotificationManager implements NotificationsManager {
}
}
NotificationsManager newDesktopNotificationsManager() {
if (Platform.isLinux) {
class NotificationPayload {
late String profileOnion;
late int convoId;
NotificationPayload(String po, int cid) {
profileOnion = po;
convoId = cid;
}
NotificationPayload.fromJson(Map<String, dynamic> json)
: profileOnion = json['profileOnion'],
convoId = json['convoId'];
Map<String, dynamic> toJson() => {
'profileOnion': profileOnion,
'convoId': convoId,
};
}
// FlutterLocalNotificationsPlugin based NotificationManager that handles Linux and MacOS
// Todo: it can also handle Android, do we want to migrate away from our manual solution?
class NixNotificationManager implements NotificationsManager {
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
late Future<void> Function(String, int) notificationSelectConvo;
NixNotificationManager(Future<void> Function(String, int) notificationSelectConvo) {
this.notificationSelectConvo = notificationSelectConvo;
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
final MacOSInitializationSettings initializationSettingsMacOS = MacOSInitializationSettings();
final LinuxInitializationSettings initializationSettingsLinux =
LinuxInitializationSettings(
defaultActionName: 'Open notification',
defaultIcon: AssetsLinuxIcon('assets/knott.png'),
);
final InitializationSettings initializationSettings = InitializationSettings(android: null, iOS: null, macOS: initializationSettingsMacOS, linux: initializationSettingsLinux);
flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<MacOSFlutterLocalNotificationsPlugin>()?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
scheduleMicrotask(() async {
await flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: selectNotification);
});
}
void selectNotification(String? payloadJson) async {
if (payloadJson != null) {
Map<String, dynamic> payloadMap = jsonDecode(payloadJson);
var payload = NotificationPayload.fromJson(payloadMap);
notificationSelectConvo(payload.profileOnion, payload.convoId);
}
}
Future<void> notify(String message, String profile, int conversationId) async {
await flutterLocalNotificationsPlugin.show(0, 'Cwtch', message, null, payload: jsonEncode(NotificationPayload(profile, conversationId)));
}
}
NotificationsManager newDesktopNotificationsManager(Future<void> Function(String profileOnion, int convoId) notificationSelectConvo) {
if (Platform.isLinux || Platform.isMacOS) {
try {
// Test that we can actually access DBUS. Otherwise return a null
// notifications manager...
NotificationsClient client = NotificationsClient();
client.getCapabilities();
return LinuxNotificationsManager(client);
return NixNotificationManager(notificationSelectConvo);
} catch (e) {
EnvironmentConfig.debugLog("Attempted to access DBUS for notifications but failed. Switching off notifications.");
EnvironmentConfig.debugLog("Failed to create NixNotificationManager. Switching off notifications.");
}
} else if (Platform.isWindows) {
try {
@ -82,5 +125,6 @@ NotificationsManager newDesktopNotificationsManager() {
EnvironmentConfig.debugLog("Failed to create Windows desktoasts notification manager");
}
}
return NullNotificationsManager();
}

View File

@ -189,14 +189,14 @@ packages:
name: dbus
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.6"
version: "0.6.8"
desktop_notifications:
dependency: "direct main"
description:
name: desktop_notifications
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.0"
version: "0.6.0"
fake_async:
dependency: transitive
description:
@ -256,6 +256,27 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0-rc.9"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
url: "https://pub.dartlang.org"
source: hosted
version: "9.3.2"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.1+1"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.0"
flutter_localizations:
dependency: "direct main"
description: flutter
@ -519,13 +540,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
petitparser:
dependency: transitive
description:
@ -671,6 +685,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.3"
timezone:
dependency: transitive
description:
name: timezone
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.0"
timing:
dependency: transitive
description:

View File

@ -34,7 +34,6 @@ dependencies:
cupertino_icons: ^1.0.0
ffi: ^1.0.0
path_provider: ^2.0.0
desktop_notifications: 0.5.0
crypto: 3.0.1
glob: any
@ -42,8 +41,10 @@ dependencies:
file_picker: ^4.3.2
file_picker_desktop: ^1.1.0
url_launcher: ^6.0.18
win_toast: ^0.0.2
window_manager: ^0.1.4
desktop_notifications: 0.6.0
win_toast: ^0.0.2
flutter_local_notifications: 9.3.2
dev_dependencies:
msix: ^2.1.3