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" |
||||
|
||||
#import <Foundation/Foundation.h> |
||||
#include "ChatPage.h" |
||||
|
||||
#import <AppKit/NSImage.h> |
||||
#import <Foundation/Foundation.h> |
||||
#import <UserNotifications/UserNotifications.h> |
||||
|
||||
#include <QtMac> |
||||
#include <QImage> |
||||
#include <QtMac> |
||||
|
||||
@interface UNNotificationAttachment (UNNotificationAttachmentAdditions) |
||||
+ (UNNotificationAttachment *) createFromImageData:(NSData*)imgData identifier:(NSString *)imageFileIdentifier options:(NSDictionary*)attachmentOptions; |
||||
+ (UNNotificationAttachment*)createFromImageData:(NSData*)imgData |
||||
identifier:(NSString*)imageFileIdentifier |
||||
options: |
||||
(NSDictionary*)attachmentOptions; |
||||
@end |
||||
|
||||
@implementation UNNotificationAttachment (UNNotificationAttachmentAdditions) |
||||
+ (UNNotificationAttachment *) createFromImageData:(NSData*)imgData identifier:(NSString *)imageFileIdentifier options:(NSDictionary*)attachmentOptions { |
||||
NSFileManager *fileManager = [NSFileManager defaultManager]; |
||||
NSString *tmpSubFolderName = [[NSProcessInfo processInfo] globallyUniqueString]; |
||||
NSURL *tmpSubFolderURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:tmpSubFolderName] isDirectory:true]; |
||||
NSError *error = nil; |
||||
[fileManager createDirectoryAtURL:tmpSubFolderURL withIntermediateDirectories:true attributes:nil error:&error]; |
||||
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; |
||||
|
||||
+ (UNNotificationAttachment*)createFromImageData:(NSData*)imgData |
||||
identifier:(NSString*)imageFileIdentifier |
||||
options: |
||||
(NSDictionary*)attachmentOptions |
||||
{ |
||||
NSFileManager* fileManager = [NSFileManager defaultManager]; |
||||
NSString* tmpSubFolderName = |
||||
[[NSProcessInfo processInfo] globallyUniqueString]; |
||||
NSURL* tmpSubFolderURL = [NSURL |
||||
fileURLWithPath:[NSTemporaryDirectory() |
||||
stringByAppendingPathComponent:tmpSubFolderName] |
||||
isDirectory:true]; |
||||
NSError* error = nil; |
||||
[fileManager createDirectoryAtURL:tmpSubFolderURL |
||||
withIntermediateDirectories:true |
||||
attributes:nil |
||||
error:&error]; |
||||
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 |
||||
|
||||
NotificationsManager::NotificationsManager(QObject *parent): QObject(parent) |
||||
NotificationsManager::NotificationsManager(QObject* parent) |
||||
: QObject(parent) |
||||
{ |
||||
|
||||
} |
||||
|
||||
void |
||||
NotificationsManager::objCxxPostNotification(const QString &room_name, |
||||
const QString &room_id, |
||||
const QString &event_id, |
||||
const QString &subtitle, |
||||
const QString &informativeText, |
||||
const QString &bodyImagePath) |
||||
void NotificationsManager::objCxxPostNotification( |
||||
const QString& room_name, |
||||
const QString& room_id, |
||||
const QString& event_id, |
||||
const QString& subtitle, |
||||
const QString& informativeText, |
||||
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; |
||||
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 |
||||
completionHandler:^(BOOL granted, NSError * _Nullable error) { |
||||
if (!granted) { |
||||
NSLog(@"No notification access"); |
||||
if (error) { |
||||
NSLog(@"%@",[error localizedDescription]); |
||||
completionHandler:^(BOOL granted, |
||||
NSError* _Nullable error) { |
||||
if (!granted) { |
||||
NSLog(@"No notification access"); |
||||
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]; |
||||
|
||||
content.title = room_name.toNSString(); |
||||
content.subtitle = subtitle.toNSString(); |
||||
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:identifier |
||||
content:content |
||||
trigger:nil]; |
||||
|
||||
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) { |
||||
if (error != nil) { |
||||
NSLog(@"Unable to Add Notification Request"); |
||||
[content autorelease]; |
||||
} |
||||
}]; |
||||
|
||||
[content autorelease]; |
||||
} |
||||
|
||||
//unused |
||||
void |
||||
NotificationsManager::actionInvoked(uint, QString) |
||||
void NotificationsManager::attachToMacNotifCenter() |
||||
{ |
||||
} |
||||
UNUserNotificationCenter* center = |
||||
[UNUserNotificationCenter currentNotificationCenter]; |
||||
|
||||
void |
||||
NotificationsManager::notificationReplied(uint, QString) |
||||
{ |
||||
} |
||||
std::unique_ptr<NotificationManagerProxy> proxy = std::make_unique<NotificationManagerProxy>(); |
||||
|
||||
void |
||||
NotificationsManager::notificationClosed(uint, uint) |
||||
{ |
||||
connect(proxy.get(), &NotificationManagerProxy::notificationReplied, ChatPage::instance(), &ChatPage::sendNotificationReply); |
||||
|
||||
MacNotificationDelegate* notifDelegate = [[MacNotificationDelegate alloc] initWithProxy:std::move(proxy)]; |
||||
|
||||
center.delegate = notifDelegate; |
||||
} |
||||
|
||||
void |
||||
NotificationsManager::removeNotification(const QString &, const QString &) |
||||
{} |
||||
// unused |
||||
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