mirror of
https://github.com/Sunnyyoung/WeChatTweak-macOS.git
synced 2025-05-22 22:36:07 +08:00
Add recalled message tag UI enhancement
This commit is contained in:
parent
ec6823a564
commit
fdd4e8ddfe
1
Podfile
1
Podfile
|
@ -5,4 +5,5 @@ target 'WeChatTweak' do
|
|||
pod 'JRSwizzle'
|
||||
pod 'GCDWebServer'
|
||||
pod 'YYModel'
|
||||
pod 'MMKV'
|
||||
end
|
||||
|
|
|
@ -3,24 +3,28 @@ PODS:
|
|||
- GCDWebServer/Core (= 3.5.3)
|
||||
- GCDWebServer/Core (3.5.3)
|
||||
- JRSwizzle (1.0)
|
||||
- MMKV (1.0.22)
|
||||
- YYModel (1.0.4)
|
||||
|
||||
DEPENDENCIES:
|
||||
- GCDWebServer
|
||||
- JRSwizzle
|
||||
- MMKV
|
||||
- YYModel
|
||||
|
||||
SPEC REPOS:
|
||||
https://github.com/cocoapods/specs.git:
|
||||
- GCDWebServer
|
||||
- JRSwizzle
|
||||
- MMKV
|
||||
- YYModel
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
GCDWebServer: c0ab22c73e1b84f358d1e2f74bf6afd1c60829f2
|
||||
JRSwizzle: dd5ead5d913a0f29e7f558200165849f006bb1e3
|
||||
MMKV: 99b05c376c0eb412b468ba1d3e6813502edeaa24
|
||||
YYModel: 2a7fdd96aaa4b86a824e26d0c517de8928c04b30
|
||||
|
||||
PODFILE CHECKSUM: af44d62b300e2c55cb63386ec4be3227b93c7761
|
||||
PODFILE CHECKSUM: fb1b1e412c5f88813595570bd6f951a741cda575
|
||||
|
||||
COCOAPODS: 1.7.5
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
7D9049FA1F82B708004E6370 /* NSString+WeChatTweak.h in Headers */ = {isa = PBXBuildFile; fileRef = 7D9049F71F82B6FB004E6370 /* NSString+WeChatTweak.h */; };
|
||||
7DB8AFBE206211D900E683AE /* WTConfigManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DB8AFBC206211D900E683AE /* WTConfigManager.h */; };
|
||||
7DB8AFBF206211D900E683AE /* WTConfigManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DB8AFBD206211D900E683AE /* WTConfigManager.m */; };
|
||||
7DBA4467231812AB004CE2DB /* RecallCacheManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DBA4465231812AB004CE2DB /* RecallCacheManager.h */; };
|
||||
7DBA4468231812AB004CE2DB /* RecallCacheManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DBA4466231812AB004CE2DB /* RecallCacheManager.m */; };
|
||||
7DF8422C1F40583F00D42D79 /* WeChatTweak.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DF8422A1F40583F00D42D79 /* WeChatTweak.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
7DF842341F4058AB00D42D79 /* WeChatTweak.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DF842331F4058AB00D42D79 /* WeChatTweak.m */; };
|
||||
7DF842521F4058C600D42D79 /* TweakPreferencesController.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DF8424F1F4058C600D42D79 /* TweakPreferencesController.h */; };
|
||||
|
@ -45,6 +47,8 @@
|
|||
7D9049F81F82B6FB004E6370 /* NSString+WeChatTweak.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+WeChatTweak.m"; sourceTree = "<group>"; };
|
||||
7DB8AFBC206211D900E683AE /* WTConfigManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WTConfigManager.h; sourceTree = "<group>"; };
|
||||
7DB8AFBD206211D900E683AE /* WTConfigManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WTConfigManager.m; sourceTree = "<group>"; };
|
||||
7DBA4465231812AB004CE2DB /* RecallCacheManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RecallCacheManager.h; sourceTree = "<group>"; };
|
||||
7DBA4466231812AB004CE2DB /* RecallCacheManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RecallCacheManager.m; sourceTree = "<group>"; };
|
||||
7DF842271F40583F00D42D79 /* WeChatTweak.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WeChatTweak.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7DF8422A1F40583F00D42D79 /* WeChatTweak.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WeChatTweak.h; sourceTree = "<group>"; };
|
||||
7DF8422B1F40583F00D42D79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -86,6 +90,8 @@
|
|||
7D14E5A31F6447DB00D75132 /* AlfredManager.m */,
|
||||
7DB8AFBC206211D900E683AE /* WTConfigManager.h */,
|
||||
7DB8AFBD206211D900E683AE /* WTConfigManager.m */,
|
||||
7DBA4465231812AB004CE2DB /* RecallCacheManager.h */,
|
||||
7DBA4466231812AB004CE2DB /* RecallCacheManager.m */,
|
||||
);
|
||||
path = Manager;
|
||||
sourceTree = "<group>";
|
||||
|
@ -187,6 +193,7 @@
|
|||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7DBA4467231812AB004CE2DB /* RecallCacheManager.h in Headers */,
|
||||
7D9049FA1F82B708004E6370 /* NSString+WeChatTweak.h in Headers */,
|
||||
7DF8422C1F40583F00D42D79 /* WeChatTweak.h in Headers */,
|
||||
7DF8425B1F4058DD00D42D79 /* NSBundle+WeChatTweak.h in Headers */,
|
||||
|
@ -241,6 +248,7 @@
|
|||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
English,
|
||||
en,
|
||||
Base,
|
||||
"zh-Hans",
|
||||
|
@ -316,6 +324,7 @@
|
|||
7D14E5A51F6447DB00D75132 /* AlfredManager.m in Sources */,
|
||||
7DF842531F4058C600D42D79 /* TweakPreferencesController.m in Sources */,
|
||||
7D9049F91F82B6FB004E6370 /* NSString+WeChatTweak.m in Sources */,
|
||||
7DBA4468231812AB004CE2DB /* RecallCacheManager.m in Sources */,
|
||||
7DF842341F4058AB00D42D79 /* WeChatTweak.m in Sources */,
|
||||
7DB8AFBF206211D900E683AE /* WTConfigManager.m in Sources */,
|
||||
7D9049F51F82A41A004E6370 /* fishhook.c in Sources */,
|
||||
|
|
23
WeChatTweak/Manager/RecallCacheManager.h
Normal file
23
WeChatTweak/Manager/RecallCacheManager.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// RecallCacheManager.h
|
||||
// WeChatTweak
|
||||
//
|
||||
// Created by Sunny Young on 2019/8/29.
|
||||
// Copyright © 2019 Sunnyyoung. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MMKV/MMKV.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RecallCacheManager : NSObject
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
+ (void)insertRevokedMessageID:(long long)messageID;
|
||||
+ (BOOL)containsRevokedMessageID:(long long)messageID;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
44
WeChatTweak/Manager/RecallCacheManager.m
Normal file
44
WeChatTweak/Manager/RecallCacheManager.m
Normal file
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// RecallCacheManager.m
|
||||
// WeChatTweak
|
||||
//
|
||||
// Created by Sunny Young on 2019/8/29.
|
||||
// Copyright © 2019 Sunnyyoung. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RecallCacheManager.h"
|
||||
|
||||
@interface RecallCacheManager()
|
||||
|
||||
@property (nonatomic, strong) MMKV *kv;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RecallCacheManager
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
[MMKV setLogLevel:MMKVLogNone];
|
||||
_kv = [MMKV mmkvWithID:@"Recall.cache"];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static RecallCacheManager *shared;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
shared = [[RecallCacheManager alloc] init];
|
||||
});
|
||||
return shared;
|
||||
}
|
||||
|
||||
+ (void)insertRevokedMessageID:(long long)messageID {
|
||||
[RecallCacheManager.sharedInstance.kv setBool:YES forKey:@(messageID).stringValue];
|
||||
}
|
||||
|
||||
+ (BOOL)containsRevokedMessageID:(long long)messageID {
|
||||
return [RecallCacheManager.sharedInstance.kv containsKey:@(messageID).stringValue];
|
||||
}
|
||||
|
||||
@end
|
|
@ -64,6 +64,7 @@ typedef NS_ENUM(unsigned int, MessageDataType) {
|
|||
|
||||
- (instancetype)initWithMsgType:(long long)arg1;
|
||||
- (BOOL)isSendFromSelf;
|
||||
- (id)getChatNameForCurMsg;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -111,7 +112,8 @@ typedef NS_ENUM(unsigned int, MessageDataType) {
|
|||
- (id)GetMsgData:(id)arg1 svrId:(unsigned long long)arg2;
|
||||
- (void)DelMsg:(id)arg1 msgList:(id)arg2 isDelAll:(BOOL)arg3 isManual:(BOOL)arg4;
|
||||
- (void)AddLocalMsg:(id)arg1 msgData:(id)arg2;
|
||||
- (void)notifyAddMsgOnMainThread:(id)arg1 msgData:(id)arg2;
|
||||
- (void)notifyDelMsgOnMainThread:(id)arg1 msgData:(id)arg2;
|
||||
- (void)notifyAddRevokePromptMsgOnMainThread:(id)arg1 msgData:(id)arg2;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#import "TweakPreferencesController.h"
|
||||
#import "AlfredManager.h"
|
||||
#import "WTConfigManager.h"
|
||||
#import "RecallCacheManager.h"
|
||||
|
||||
// Global Function
|
||||
static NSString *(*original_NSHomeDirectory)(void);
|
||||
|
@ -63,8 +64,12 @@ static void __attribute__((constructor)) tweak(void) {
|
|||
[objc_getClass("CUtility") jr_swizzleClassMethod:NSSelectorFromString(@"FFSvrChatInfoMsgWithImgZZ") withClassMethod:@selector(tweak_HasWechatInstance) error:nil];
|
||||
[objc_getClass("NSRunningApplication") jr_swizzleClassMethod:NSSelectorFromString(@"runningApplicationsWithBundleIdentifier:") withClassMethod:@selector(tweak_runningApplicationsWithBundleIdentifier:) error:nil];
|
||||
[objc_getClass("MASPreferencesWindowController") jr_swizzleMethod:NSSelectorFromString(@"initWithViewControllers:") withMethod:@selector(tweak_initWithViewControllers:) error:nil];
|
||||
|
||||
[objc_getClass("MMMessageCellView") jr_swizzleMethod:NSSelectorFromString(@"contextMenu") withMethod:@selector(tweak_contextMenu) error:nil];
|
||||
|
||||
[objc_getClass("MMMessageCellView") jr_swizzleMethod:NSSelectorFromString(@"initWithFrame:") withMethod:@selector(tweak_initWithFrame:) error:nil];
|
||||
[objc_getClass("MMMessageCellView") jr_swizzleMethod:NSSelectorFromString(@"populateWithMessage:") withMethod:@selector(tweak_populateWithMessage:) error:nil];
|
||||
[objc_getClass("MMMessageCellView") jr_swizzleMethod:NSSelectorFromString(@"layout") withMethod:@selector(tweak_layout) error:nil];
|
||||
|
||||
objc_property_attribute_t type = { "T", "@\"NSString\"" }; // NSString
|
||||
objc_property_attribute_t atom = { "N", "" }; // nonatomic
|
||||
objc_property_attribute_t ownership = { "&", "" }; // C = copy & = strong
|
||||
|
@ -76,6 +81,59 @@ static void __attribute__((constructor)) tweak(void) {
|
|||
class_addMethod(objc_getClass("WCContactData"), @selector(modelPropertyWhitelist), method_getImplementation(class_getClassMethod(objc_getClass("WCContactData"), @selector(modelPropertyWhitelist))), "v@:");
|
||||
}
|
||||
|
||||
- (instancetype)tweak_initWithFrame:(NSRect)arg1 {
|
||||
MMMessageCellView *view = (MMMessageCellView *)[self tweak_initWithFrame:arg1];
|
||||
NSTextField *revokeTextField = [[NSTextField alloc] init];
|
||||
revokeTextField.hidden = YES;
|
||||
revokeTextField.editable = NO;
|
||||
revokeTextField.selectable = NO;
|
||||
revokeTextField.bordered = NO;
|
||||
revokeTextField.drawsBackground = NO;
|
||||
revokeTextField.usesSingleLineMode = YES;
|
||||
revokeTextField.tag = 9527;
|
||||
revokeTextField.stringValue = @"[已撤回]";
|
||||
revokeTextField.font = [NSFont systemFontOfSize:10];
|
||||
revokeTextField.textColor = [NSColor lightGrayColor];
|
||||
[view addSubview:revokeTextField];
|
||||
return view;
|
||||
}
|
||||
|
||||
- (void)tweak_populateWithMessage:(MMMessageTableItem *)tableItem {
|
||||
[self tweak_populateWithMessage:tableItem];
|
||||
BOOL style = [RecallCacheManager containsRevokedMessageID:tableItem.message.mesSvrID] && tableItem.message.messageType != MessageDataTypePrompt;
|
||||
[((MMMessageCellView *)self).subviews enumerateObjectsUsingBlock:^(__kindof NSView * _Nonnull view, NSUInteger index, BOOL * _Nonnull stop) {
|
||||
if (view.tag != 9527) {
|
||||
return ;
|
||||
}
|
||||
*stop = YES;
|
||||
view.hidden = !style;
|
||||
}];
|
||||
((MMMessageCellView *)self).layer.backgroundColor = style ? [NSColor.yellowColor colorWithAlphaComponent:0.3].CGColor : ((MMMessageCellView *)self).layer.backgroundColor;
|
||||
}
|
||||
|
||||
- (void)tweak_layout {
|
||||
[self tweak_layout];
|
||||
__block NSTextField *label = nil;
|
||||
[((MMMessageCellView *)self).subviews enumerateObjectsUsingBlock:^(__kindof NSView * _Nonnull view, NSUInteger index, BOOL * _Nonnull stop) {
|
||||
if (view.tag != 9527) {
|
||||
return ;
|
||||
}
|
||||
*stop = YES;
|
||||
label = view;
|
||||
}];
|
||||
if (label == nil) {
|
||||
return;
|
||||
}
|
||||
label.frame = ({
|
||||
NSView *avatarView = ((MMMessageCellView *)self).avatarImgView;
|
||||
CGFloat x = CGRectGetMidX(avatarView.frame) - CGRectGetWidth(label.frame) / 2.0;
|
||||
CGFloat y = CGRectGetMinY(avatarView.frame) - CGRectGetHeight(label.frame);
|
||||
NSRect fuck = [label.stringValue boundingRectWithSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX) options:kNilOptions attributes:nil];
|
||||
NSRect frame = NSMakeRect(x, y, CGRectGetWidth(fuck), CGRectGetHeight(fuck));
|
||||
frame;
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - No Revoke Message
|
||||
|
||||
- (void)tweak_onRevokeMsg:(MessageData *)message {
|
||||
|
@ -83,54 +141,10 @@ static void __attribute__((constructor)) tweak(void) {
|
|||
NSString *session = [message.msgContent tweak_subStringFrom:@"<session>" to:@"</session>"];
|
||||
NSUInteger newMessageID = [message.msgContent tweak_subStringFrom:@"<newmsgid>" to:@"</newmsgid>"].longLongValue;
|
||||
NSString *replaceMessage = [message.msgContent tweak_subStringFrom:@"<replacemsg><![CDATA[" to:@"]]></replacemsg>"];
|
||||
|
||||
// Prepare message data
|
||||
MessageData *localMessageData = [((MessageService *)self) GetMsgData:session svrId:newMessageID];
|
||||
MessageData *promptMessageData = ({
|
||||
MessageData *data = [[objc_getClass("MessageData") alloc] initWithMsgType:10000];
|
||||
data.msgStatus = 4;
|
||||
data.toUsrName = localMessageData.toUsrName;
|
||||
data.fromUsrName = localMessageData.fromUsrName;
|
||||
data.mesSvrID = localMessageData.mesSvrID;
|
||||
data.mesLocalID = localMessageData.mesLocalID;
|
||||
data.msgCreateTime = localMessageData.msgCreateTime;
|
||||
if ([localMessageData isSendFromSelf]) {
|
||||
data.msgContent = replaceMessage;
|
||||
} else {
|
||||
NSString *fromUserName = [replaceMessage componentsSeparatedByString:@" "].firstObject;
|
||||
NSString *userRevoke = [NSString stringWithFormat:@"%@ %@ ", fromUserName, [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Recalled"]];
|
||||
NSString *tips = [NSString stringWithFormat:[NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.InterceptedARecalledMessage"], userRevoke];
|
||||
NSMutableString *msgContent = [NSMutableString stringWithString:tips];
|
||||
switch (localMessageData.messageType) {
|
||||
case MessageDataTypeText: {
|
||||
if (localMessageData.msgContent.length) {
|
||||
if ([session rangeOfString:@"@chatroom"].location == NSNotFound) {
|
||||
[msgContent appendFormat:@"\"%@\"", localMessageData.msgContent];
|
||||
} else {
|
||||
[msgContent appendFormat:@"\"%@\"", [localMessageData.msgContent componentsSeparatedByString:@":\n"].lastObject];
|
||||
}
|
||||
} else {
|
||||
[msgContent appendString:[NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.AMessage"]];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MessageDataTypeImage:
|
||||
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Image"]]; break;
|
||||
case MessageDataTypeVoice:
|
||||
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Voice"]]; break;
|
||||
case MessageDataTypeVideo:
|
||||
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Video"]]; break;
|
||||
case MessageDataTypeSticker:
|
||||
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Sticker"]]; break;
|
||||
case MessageDataTypeAppUrl:
|
||||
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Link"]]; break;
|
||||
default:
|
||||
[msgContent appendString:[NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.AMessage"]]; break;
|
||||
}
|
||||
data.msgContent = msgContent;
|
||||
}
|
||||
data;
|
||||
});
|
||||
|
||||
// Get message data
|
||||
MessageData *messageData = [((MessageService *)self) GetMsgData:session svrId:newMessageID];
|
||||
[RecallCacheManager insertRevokedMessageID:messageData.mesSvrID];
|
||||
|
||||
// Prepare notification information
|
||||
MMServiceCenter *serviceCenter = [objc_getClass("MMServiceCenter") defaultCenter];
|
||||
|
@ -149,21 +163,31 @@ static void __attribute__((constructor)) tweak(void) {
|
|||
userNotification.informativeText = [NSString stringWithFormat:@"%@: %@", groupName, replaceMessage];
|
||||
}
|
||||
|
||||
// Delete message if it is revoke from myself
|
||||
if ([localMessageData isSendFromSelf]) {
|
||||
[((MessageService *)self) DelMsg:session msgList:@[localMessageData] isDelAll:NO isManual:YES];
|
||||
if ([messageData isSendFromSelf]) {
|
||||
MessageData *promptMessageData = ({
|
||||
MessageData *data = [[objc_getClass("MessageData") alloc] initWithMsgType:MessageDataTypePrompt];
|
||||
data.msgStatus = 4;
|
||||
data.toUsrName = messageData.toUsrName;
|
||||
data.fromUsrName = messageData.fromUsrName;
|
||||
data.mesSvrID = messageData.mesSvrID;
|
||||
data.mesLocalID = messageData.mesLocalID;
|
||||
data.msgCreateTime = messageData.msgCreateTime;
|
||||
data.msgContent = replaceMessage;
|
||||
data;
|
||||
});
|
||||
// Delete message if it is revoke from myself
|
||||
[((MessageService *)self) DelMsg:session msgList:@[messageData] isDelAll:NO isManual:YES];
|
||||
[((MessageService *)self) AddLocalMsg:session msgData:promptMessageData];
|
||||
} else {
|
||||
if (localMessageData.messageType == MessageDataTypeText) {
|
||||
[((MessageService *)self) DelMsg:session msgList:@[localMessageData] isDelAll:NO isManual:YES];
|
||||
}
|
||||
[((MessageService *)self) AddLocalMsg:session msgData:promptMessageData];
|
||||
// Invoke message reloading
|
||||
[((MessageService *)self) notifyDelMsgOnMainThread:messageData.getChatNameForCurMsg msgData:messageData];
|
||||
[((MessageService *)self) notifyAddRevokePromptMsgOnMainThread:messageData.getChatNameForCurMsg msgData:messageData];
|
||||
}
|
||||
|
||||
|
||||
// Dispatch notification
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// Deliver notification
|
||||
if (![localMessageData isSendFromSelf]) {
|
||||
if (![messageData isSendFromSelf]) {
|
||||
RevokeNotificationType notificationType = [[NSUserDefaults standardUserDefaults] integerForKey:WeChatTweakPreferenceRevokeNotificationTypeKey];
|
||||
if (notificationType == RevokeNotificationTypeReceiveAll || (notificationType == RevokeNotificationTypeFollow && isChatStatusNotifyOpen)) {
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
|
||||
|
|
Loading…
Reference in New Issue
Block a user