mirror of https://github.com/Nheko-Reborn/nheko
Add ability to respond to notifications on macOS See merge request nheko-reborn/nheko!21pull/1221/head
commit
a6f53699f5
@ -0,0 +1,20 @@ |
|||||||
|
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||||
|
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include "notifications/Manager.h" |
||||||
|
#include "notifications/NotificationManagerProxy.h" |
||||||
|
#include <mtx/responses/notifications.hpp> |
||||||
|
|
||||||
|
#import <Foundation/Foundation.h> |
||||||
|
#import <UserNotifications/UserNotifications.h> |
||||||
|
|
||||||
|
@interface MacNotificationDelegate : NSObject <UNUserNotificationCenterDelegate> { |
||||||
|
std::unique_ptr<NotificationManagerProxy> mProxy; |
||||||
|
} |
||||||
|
|
||||||
|
- (id)initWithProxy:(std::unique_ptr<NotificationManagerProxy>&&)proxy; |
||||||
|
@end |
@ -0,0 +1,47 @@ |
|||||||
|
// SPDX-FileCopyrightText: 2021 Nheko Contributors |
||||||
|
// SPDX-FileCopyrightText: 2022 Nheko Contributors |
||||||
|
// |
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||||
|
|
||||||
|
#import "notifications/MacNotificationDelegate.h" |
||||||
|
|
||||||
|
#include <QString.h> |
||||||
|
|
||||||
|
#include "ChatPage.h" |
||||||
|
|
||||||
|
@implementation MacNotificationDelegate |
||||||
|
|
||||||
|
- (id)initWithProxy: (std::unique_ptr<NotificationManagerProxy>&&)proxy |
||||||
|
{ |
||||||
|
if(self = [super init]) { |
||||||
|
mProxy = std::move(proxy); |
||||||
|
} |
||||||
|
|
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
- (void)userNotificationCenter:(UNUserNotificationCenter*)center |
||||||
|
didReceiveNotificationResponse:(UNNotificationResponse*)response |
||||||
|
withCompletionHandler:(void (^)())completionHandler |
||||||
|
{ |
||||||
|
if ([response.actionIdentifier isEqualToString:@"ReplyAction"]) { |
||||||
|
if ([response respondsToSelector:@selector(userText)]) { |
||||||
|
UNTextInputNotificationResponse* textResponse = (UNTextInputNotificationResponse*)response; |
||||||
|
NSString* textValue = [textResponse userText]; |
||||||
|
NSString* eventId = [[[textResponse notification] request] identifier]; |
||||||
|
NSString* roomId = [[[[textResponse notification] request] content] threadIdentifier]; |
||||||
|
mProxy->notificationReplied(QString::fromNSString(roomId), QString::fromNSString(eventId), QString::fromNSString(textValue)); |
||||||
|
} |
||||||
|
} |
||||||
|
completionHandler(); |
||||||
|
} |
||||||
|
|
||||||
|
- (void)userNotificationCenter:(UNUserNotificationCenter*)center |
||||||
|
willPresentNotification:(UNNotification*)notification |
||||||
|
withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler |
||||||
|
{ |
||||||
|
|
||||||
|
completionHandler(UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound); |
||||||
|
} |
||||||
|
|
||||||
|
@end |
@ -1,112 +1,187 @@ |
|||||||
|
// SPDX-FileCopyrightText: 2021 Nheko Contributors |
||||||
|
// SPDX-FileCopyrightText: 2022 Nheko Contributors |
||||||
|
// |
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||||
|
|
||||||
|
#include "notifications/NotificationManagerProxy.h" |
||||||
|
#include "notifications/MacNotificationDelegate.h" |
||||||
#include "notifications/Manager.h" |
#include "notifications/Manager.h" |
||||||
|
|
||||||
#import <Foundation/Foundation.h> |
#include "ChatPage.h" |
||||||
|
|
||||||
#import <AppKit/NSImage.h> |
#import <AppKit/NSImage.h> |
||||||
|
#import <Foundation/Foundation.h> |
||||||
#import <UserNotifications/UserNotifications.h> |
#import <UserNotifications/UserNotifications.h> |
||||||
|
|
||||||
#include <QtMac> |
|
||||||
#include <QImage> |
#include <QImage> |
||||||
|
#include <QtMac> |
||||||
|
|
||||||
@interface UNNotificationAttachment (UNNotificationAttachmentAdditions) |
@interface UNNotificationAttachment (UNNotificationAttachmentAdditions) |
||||||
+ (UNNotificationAttachment *) createFromImageData:(NSData*)imgData identifier:(NSString *)imageFileIdentifier options:(NSDictionary*)attachmentOptions; |
+ (UNNotificationAttachment*)createFromImageData:(NSData*)imgData |
||||||
|
identifier:(NSString*)imageFileIdentifier |
||||||
|
options: |
||||||
|
(NSDictionary*)attachmentOptions; |
||||||
@end |
@end |
||||||
|
|
||||||
@implementation UNNotificationAttachment (UNNotificationAttachmentAdditions) |
@implementation UNNotificationAttachment (UNNotificationAttachmentAdditions) |
||||||
+ (UNNotificationAttachment *) createFromImageData:(NSData*)imgData identifier:(NSString *)imageFileIdentifier options:(NSDictionary*)attachmentOptions { |
+ (UNNotificationAttachment*)createFromImageData:(NSData*)imgData |
||||||
NSFileManager *fileManager = [NSFileManager defaultManager]; |
identifier:(NSString*)imageFileIdentifier |
||||||
NSString *tmpSubFolderName = [[NSProcessInfo processInfo] globallyUniqueString]; |
options: |
||||||
NSURL *tmpSubFolderURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:tmpSubFolderName] isDirectory:true]; |
(NSDictionary*)attachmentOptions |
||||||
NSError *error = nil; |
{ |
||||||
[fileManager createDirectoryAtURL:tmpSubFolderURL withIntermediateDirectories:true attributes:nil error:&error]; |
NSFileManager* fileManager = [NSFileManager defaultManager]; |
||||||
if(error) { |
NSString* tmpSubFolderName = |
||||||
NSLog(@"%@",[error localizedDescription]); |
[[NSProcessInfo processInfo] globallyUniqueString]; |
||||||
return nil; |
NSURL* tmpSubFolderURL = [NSURL |
||||||
} |
fileURLWithPath:[NSTemporaryDirectory() |
||||||
NSURL *fileURL = [tmpSubFolderURL URLByAppendingPathComponent:imageFileIdentifier]; |
stringByAppendingPathComponent:tmpSubFolderName] |
||||||
[imgData writeToURL:fileURL atomically:true]; |
isDirectory:true]; |
||||||
UNNotificationAttachment *imageAttachment = [UNNotificationAttachment attachmentWithIdentifier:@"" URL:fileURL options:attachmentOptions error:&error]; |
NSError* error = nil; |
||||||
if(error) { |
[fileManager createDirectoryAtURL:tmpSubFolderURL |
||||||
NSLog(@"%@",[error localizedDescription]); |
withIntermediateDirectories:true |
||||||
return nil; |
attributes:nil |
||||||
} |
error:&error]; |
||||||
return imageAttachment; |
if (error) { |
||||||
|
NSLog(@"%@", [error localizedDescription]); |
||||||
|
return nil; |
||||||
} |
} |
||||||
|
NSURL* fileURL = |
||||||
|
[tmpSubFolderURL URLByAppendingPathComponent:imageFileIdentifier]; |
||||||
|
[imgData writeToURL:fileURL atomically:true]; |
||||||
|
UNNotificationAttachment* imageAttachment = |
||||||
|
[UNNotificationAttachment attachmentWithIdentifier:@"" |
||||||
|
URL:fileURL |
||||||
|
options:attachmentOptions |
||||||
|
error:&error]; |
||||||
|
if (error) { |
||||||
|
NSLog(@"%@", [error localizedDescription]); |
||||||
|
return nil; |
||||||
|
} |
||||||
|
return imageAttachment; |
||||||
|
} |
||||||
@end |
@end |
||||||
|
|
||||||
NotificationsManager::NotificationsManager(QObject *parent): QObject(parent) |
NotificationsManager::NotificationsManager(QObject* parent) |
||||||
|
: QObject(parent) |
||||||
{ |
{ |
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
void |
void NotificationsManager::objCxxPostNotification( |
||||||
NotificationsManager::objCxxPostNotification(const QString &room_name, |
const QString& room_name, |
||||||
const QString &room_id, |
const QString& room_id, |
||||||
const QString &event_id, |
const QString& event_id, |
||||||
const QString &subtitle, |
const QString& subtitle, |
||||||
const QString &informativeText, |
const QString& informativeText, |
||||||
const QString &bodyImagePath) |
const QString& bodyImagePath, |
||||||
|
const QString& respondStr, |
||||||
|
const QString& sendStr, |
||||||
|
const QString& placeholder) |
||||||
{ |
{ |
||||||
|
// Request permissions for alerts (the generic type of notification), sound playback, |
||||||
|
// and badges (which allows the Nheko app icon to show the little red bubble with unread count). |
||||||
|
// NOTE: Possible macOS bug... the 'Play sound for notification checkbox' doesn't appear in |
||||||
|
// the Notifications and Focus settings unless UNAuthorizationOptionBadges is also |
||||||
|
// specified |
||||||
UNAuthorizationOptions options = UNAuthorizationOptionAlert + UNAuthorizationOptionSound + UNAuthorizationOptionBadge; |
UNAuthorizationOptions options = UNAuthorizationOptionAlert + UNAuthorizationOptionSound + UNAuthorizationOptionBadge; |
||||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; |
UNUserNotificationCenter* center = |
||||||
|
[UNUserNotificationCenter currentNotificationCenter]; |
||||||
|
|
||||||
|
// TODO: Move this somewhere that isn't dependent on receiving a notification |
||||||
|
// to actually request notification access. |
||||||
[center requestAuthorizationWithOptions:options |
[center requestAuthorizationWithOptions:options |
||||||
completionHandler:^(BOOL granted, NSError * _Nullable error) { |
completionHandler:^(BOOL granted, |
||||||
if (!granted) { |
NSError* _Nullable error) { |
||||||
NSLog(@"No notification access"); |
if (!granted) { |
||||||
if (error) { |
NSLog(@"No notification access"); |
||||||
NSLog(@"%@",[error localizedDescription]); |
if (error) { |
||||||
|
NSLog(@"%@", [error localizedDescription]); |
||||||
|
} |
||||||
|
} |
||||||
|
}]; |
||||||
|
|
||||||
|
UNTextInputNotificationAction* replyAction = [UNTextInputNotificationAction actionWithIdentifier:@"ReplyAction" |
||||||
|
title:respondStr.toNSString() |
||||||
|
options:UNNotificationActionOptionNone |
||||||
|
textInputButtonTitle:sendStr.toNSString() |
||||||
|
textInputPlaceholder:placeholder.toNSString()]; |
||||||
|
|
||||||
|
UNNotificationCategory* category = [UNNotificationCategory categoryWithIdentifier:@"ReplyCategory" |
||||||
|
actions:@[ replyAction ] |
||||||
|
intentIdentifiers:@[] |
||||||
|
options:UNNotificationCategoryOptionNone]; |
||||||
|
|
||||||
|
NSString* title = room_name.toNSString(); |
||||||
|
NSString* sub = subtitle.toNSString(); |
||||||
|
NSString* body = informativeText.toNSString(); |
||||||
|
NSString* threadIdentifier = room_id.toNSString(); |
||||||
|
NSString* identifier = event_id.toNSString(); |
||||||
|
NSString* imgUrl = bodyImagePath.toNSString(); |
||||||
|
|
||||||
|
NSSet* categories = [NSSet setWithObject:category]; |
||||||
|
[center setNotificationCategories:categories]; |
||||||
|
[center getNotificationSettingsWithCompletionHandler:^( |
||||||
|
UNNotificationSettings* _Nonnull settings) { |
||||||
|
if (settings.authorizationStatus == UNAuthorizationStatusAuthorized) { |
||||||
|
UNMutableNotificationContent* content = |
||||||
|
[[UNMutableNotificationContent alloc] init]; |
||||||
|
|
||||||
|
content.title = title; |
||||||
|
content.subtitle = sub; |
||||||
|
content.body = body; |
||||||
|
content.sound = [UNNotificationSound defaultSound]; |
||||||
|
content.threadIdentifier = threadIdentifier; |
||||||
|
content.categoryIdentifier = @"ReplyCategory"; |
||||||
|
|
||||||
|
if ([imgUrl length] != 0) { |
||||||
|
NSURL* imageURL = [NSURL fileURLWithPath:imgUrl]; |
||||||
|
NSData* img = [NSData dataWithContentsOfURL:imageURL]; |
||||||
|
NSArray* attachments = [NSMutableArray array]; |
||||||
|
UNNotificationAttachment* attachment = [UNNotificationAttachment |
||||||
|
createFromImageData:img |
||||||
|
identifier:@"attachment_image.jpeg" |
||||||
|
options:nil]; |
||||||
|
if (attachment) { |
||||||
|
attachments = [NSMutableArray arrayWithObjects:attachment, nil]; |
||||||
|
content.attachments = attachments; |
||||||
|
} |
||||||
} |
} |
||||||
} |
|
||||||
}]; |
|
||||||
|
|
||||||
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; |
UNNotificationRequest* notificationRequest = |
||||||
|
[UNNotificationRequest requestWithIdentifier:identifier |
||||||
content.title = room_name.toNSString(); |
content:content |
||||||
content.subtitle = subtitle.toNSString(); |
trigger:nil]; |
||||||
content.body = informativeText.toNSString(); |
|
||||||
content.sound = [UNNotificationSound defaultSound]; |
|
||||||
content.threadIdentifier = room_id.toNSString(); |
|
||||||
|
|
||||||
if (!bodyImagePath.isEmpty()) { |
|
||||||
NSURL *imageURL = [NSURL fileURLWithPath:bodyImagePath.toNSString()]; |
|
||||||
NSData *img = [NSData dataWithContentsOfURL:imageURL]; |
|
||||||
NSArray *attachments = [NSMutableArray array]; |
|
||||||
UNNotificationAttachment *attachment = [UNNotificationAttachment createFromImageData:img identifier:@"attachment_image.jpeg" options:nil]; |
|
||||||
if (attachment) { |
|
||||||
attachments = [NSMutableArray arrayWithObjects: attachment, nil]; |
|
||||||
content.attachments = attachments; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
UNNotificationRequest *notificationRequest = [UNNotificationRequest requestWithIdentifier:event_id.toNSString() content:content trigger:nil]; |
[center addNotificationRequest:notificationRequest |
||||||
|
withCompletionHandler:^(NSError* _Nullable error) { |
||||||
|
if (error != nil) { |
||||||
|
NSLog(@"Unable to Add Notification Request: %@", [error localizedDescription]); |
||||||
|
} |
||||||
|
}]; |
||||||
|
|
||||||
[center addNotificationRequest:notificationRequest withCompletionHandler:^(NSError * _Nullable error) { |
[content autorelease]; |
||||||
if (error != nil) { |
|
||||||
NSLog(@"Unable to Add Notification Request"); |
|
||||||
} |
} |
||||||
}]; |
}]; |
||||||
|
|
||||||
[content autorelease]; |
|
||||||
} |
} |
||||||
|
|
||||||
//unused |
void NotificationsManager::attachToMacNotifCenter() |
||||||
void |
|
||||||
NotificationsManager::actionInvoked(uint, QString) |
|
||||||
{ |
{ |
||||||
} |
UNUserNotificationCenter* center = |
||||||
|
[UNUserNotificationCenter currentNotificationCenter]; |
||||||
|
|
||||||
void |
std::unique_ptr<NotificationManagerProxy> proxy = std::make_unique<NotificationManagerProxy>(); |
||||||
NotificationsManager::notificationReplied(uint, QString) |
|
||||||
{ |
|
||||||
} |
|
||||||
|
|
||||||
void |
connect(proxy.get(), &NotificationManagerProxy::notificationReplied, ChatPage::instance(), &ChatPage::sendNotificationReply); |
||||||
NotificationsManager::notificationClosed(uint, uint) |
|
||||||
{ |
MacNotificationDelegate* notifDelegate = [[MacNotificationDelegate alloc] initWithProxy:std::move(proxy)]; |
||||||
|
|
||||||
|
center.delegate = notifDelegate; |
||||||
} |
} |
||||||
|
|
||||||
void |
// unused |
||||||
NotificationsManager::removeNotification(const QString &, const QString &) |
void NotificationsManager::actionInvoked(uint, QString) { } |
||||||
{} |
|
||||||
|
void NotificationsManager::notificationReplied(uint, QString) { } |
||||||
|
|
||||||
|
void NotificationsManager::notificationClosed(uint, uint) { } |
||||||
|
|
||||||
|
void NotificationsManager::removeNotification(const QString&, const QString&) { } |
||||||
|
@ -0,0 +1,22 @@ |
|||||||
|
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||||
|
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <QObject> |
||||||
|
#include <QString> |
||||||
|
|
||||||
|
class NotificationManagerProxy final : public QObject |
||||||
|
{ |
||||||
|
Q_OBJECT |
||||||
|
public: |
||||||
|
NotificationManagerProxy(QObject *parent = nullptr) |
||||||
|
: QObject(parent) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
signals: |
||||||
|
void notificationReplied(const QString &room, const QString &event, const QString &reply); |
||||||
|
}; |
Loading…
Reference in new issue