mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-19 08:03:50 +02:00
Merge MatrixKit develop with commit hash: b85b736313bec0592bd1cabc68035d97f5331137
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MatrixSDK/MatrixSDK.h>
|
||||
|
||||
@class MXKUTI;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern NSString * const kMXKAttachmentErrorDomain;
|
||||
|
||||
/**
|
||||
List attachment types
|
||||
*/
|
||||
typedef enum : NSUInteger {
|
||||
MXKAttachmentTypeUndefined,
|
||||
MXKAttachmentTypeImage,
|
||||
MXKAttachmentTypeAudio,
|
||||
MXKAttachmentTypeVoiceMessage,
|
||||
MXKAttachmentTypeVideo,
|
||||
MXKAttachmentTypeLocation,
|
||||
MXKAttachmentTypeFile,
|
||||
MXKAttachmentTypeSticker
|
||||
|
||||
} MXKAttachmentType;
|
||||
|
||||
/**
|
||||
`MXKAttachment` represents a room attachment.
|
||||
*/
|
||||
@interface MXKAttachment : NSObject
|
||||
|
||||
/**
|
||||
The media manager instance used to download the attachment data.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXMediaManager *mediaManager;
|
||||
|
||||
/**
|
||||
The attachment type.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXKAttachmentType type;
|
||||
|
||||
/**
|
||||
The attachment information retrieved from the event content during the initialisation.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSString *eventId;
|
||||
@property (nonatomic, readonly, nullable) NSString *eventRoomId;
|
||||
@property (nonatomic, readonly) MXEventSentState eventSentState;
|
||||
@property (nonatomic, readonly, nullable) NSString *contentURL;
|
||||
@property (nonatomic, readonly, nullable) NSDictionary *contentInfo;
|
||||
|
||||
/**
|
||||
The URL of a 'standard size' thumbnail.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSString *mxcThumbnailURI;
|
||||
@property (nonatomic, readonly, nullable) NSString *thumbnailMimeType;
|
||||
|
||||
/**
|
||||
The download identifier of the attachment content (related to contentURL).
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSString *downloadId;
|
||||
/**
|
||||
The download identifier of the attachment thumbnail.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSString *thumbnailDownloadId;
|
||||
|
||||
/**
|
||||
The attached video thumbnail information.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSDictionary *thumbnailInfo;
|
||||
|
||||
/**
|
||||
The original file name retrieved from the event body (if any).
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSString *originalFileName;
|
||||
|
||||
/**
|
||||
The thumbnail orientation (relevant in case of image).
|
||||
*/
|
||||
@property (nonatomic, readonly) UIImageOrientation thumbnailOrientation;
|
||||
|
||||
/**
|
||||
The cache file path of the attachment.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSString *cacheFilePath;
|
||||
|
||||
/**
|
||||
The cache file path of the attachment thumbnail (may be nil).
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSString *thumbnailCachePath;
|
||||
|
||||
/**
|
||||
The preview of the attachment (nil by default).
|
||||
*/
|
||||
@property (nonatomic, nullable) UIImage *previewImage;
|
||||
|
||||
/**
|
||||
True if the attachment is encrypted
|
||||
The encryption status of the thumbnail is not covered by this
|
||||
property: it is possible for the thumbnail to be encrypted
|
||||
whether this peoperty is true or false.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL isEncrypted;
|
||||
|
||||
/**
|
||||
The UTI of this attachment.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) MXKUTI *uti;
|
||||
|
||||
/**
|
||||
Create a `MXKAttachment` instance for the passed event.
|
||||
The created instance copies the current data of the event (content, event id, sent state...).
|
||||
It will ignore any future changes of these data.
|
||||
|
||||
@param event a matrix event.
|
||||
@param mediaManager the media manager instance used to download the attachment data.
|
||||
@return `MXKAttachment` instance.
|
||||
*/
|
||||
- (nullable instancetype)initWithEvent:(MXEvent*)event andMediaManager:(MXMediaManager*)mediaManager;
|
||||
|
||||
- (void)destroy;
|
||||
|
||||
/**
|
||||
Gets the thumbnail for this attachment if it is in the memory or disk cache,
|
||||
otherwise return nil
|
||||
*/
|
||||
- (nullable UIImage *)getCachedThumbnail;
|
||||
|
||||
/**
|
||||
For image attachments, gets a UIImage for the full-res image
|
||||
*/
|
||||
- (void)getImage:(void (^_Nullable)(MXKAttachment *_Nullable, UIImage *_Nullable))onSuccess failure:(void (^_Nullable)(MXKAttachment *_Nullable, NSError * _Nullable error))onFailure;
|
||||
|
||||
/**
|
||||
Decrypt the attachment data into memory and provide it as an NSData
|
||||
*/
|
||||
- (void)getAttachmentData:(void (^_Nullable)(NSData *_Nullable))onSuccess failure:(void (^_Nullable)(NSError * _Nullable error))onFailure;
|
||||
|
||||
/**
|
||||
Decrypts the attachment to a newly created temporary file.
|
||||
If the isEncrypted property is YES, this method (or getImage) should be used to
|
||||
obtain the full decrypted attachment. The behaviour of this method is undefined
|
||||
if isEncrypted is NO.
|
||||
It is the caller's responsibility to delete the temporary file once it is no longer
|
||||
needed.
|
||||
*/
|
||||
- (void)decryptToTempFile:(void (^_Nullable)(NSString *_Nullable))onSuccess failure:(void (^_Nullable)(NSError * _Nullable error))onFailure;
|
||||
|
||||
|
||||
/** Deletes all previously created temporary files */
|
||||
+ (void)clearCache;
|
||||
|
||||
/**
|
||||
Gets the thumbnails for this attachment, downloading it or loading it from disk cache
|
||||
if necessary
|
||||
*/
|
||||
- (void)getThumbnail:(void (^_Nullable)(MXKAttachment *_Nullable, UIImage *_Nullable))onSuccess failure:(void (^_Nullable)(MXKAttachment *_Nullable, NSError * _Nullable error))onFailure;
|
||||
|
||||
/**
|
||||
Download the attachment data if it is not already cached.
|
||||
|
||||
@param onAttachmentReady block called when attachment is available at 'cacheFilePath'.
|
||||
@param onFailure the block called on failure.
|
||||
*/
|
||||
- (void)prepare:(void (^_Nullable)(void))onAttachmentReady failure:(void (^_Nullable)(NSError * _Nullable error))onFailure;
|
||||
|
||||
/**
|
||||
Save the attachment in user's photo library. This operation is available only for images and video.
|
||||
|
||||
@param onSuccess the block called on success.
|
||||
@param onFailure the block called on failure.
|
||||
*/
|
||||
- (void)save:(void (^_Nullable)(void))onSuccess failure:(void (^_Nullable)(NSError * _Nullable error))onFailure;
|
||||
|
||||
/**
|
||||
Copy the attachment data in general pasteboard.
|
||||
|
||||
@param onSuccess the block called on success.
|
||||
@param onFailure the block called on failure.
|
||||
*/
|
||||
- (void)copy:(void (^_Nullable)(void))onSuccess failure:(void (^_Nullable)(NSError * _Nullable error))onFailure;
|
||||
|
||||
/**
|
||||
Prepare the attachment data to share it. The original name of the attachment (if any) is used
|
||||
to name the prepared file.
|
||||
|
||||
The developer must call 'onShareEnd' when share operation is ended in order to release potential
|
||||
resources allocated here.
|
||||
|
||||
@param onReadyToShare the block called when attachment is ready to share at the provided file URL.
|
||||
@param onFailure the block called on failure.
|
||||
*/
|
||||
- (void)prepareShare:(void (^_Nullable)(NSURL * _Nullable fileURL))onReadyToShare failure:(void (^_Nullable)(NSError * _Nullable error))onFailure;
|
||||
- (void)onShareEnded;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,718 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKAttachment.h"
|
||||
#import "MXKSwiftHeader.h"
|
||||
|
||||
@import MatrixSDK;
|
||||
@import MobileCoreServices;
|
||||
|
||||
#import "MXKTools.h"
|
||||
|
||||
// The size of thumbnail we request from the server
|
||||
// Note that this is smaller than the ones we upload: when sending, one size
|
||||
// must fit all, including the web which will want relatively high res thumbnails.
|
||||
// We, however, are a mobile client and so would prefer smaller thumbnails, which
|
||||
// we can have if they're being generated by the media repo.
|
||||
static const int kThumbnailWidth = 320;
|
||||
static const int kThumbnailHeight = 240;
|
||||
|
||||
NSString *const kMXKAttachmentErrorDomain = @"kMXKAttachmentErrorDomain";
|
||||
NSString *const kMXKAttachmentFileNameBase = @"attatchment";
|
||||
|
||||
@interface MXKAttachment ()
|
||||
{
|
||||
/**
|
||||
The information on the encrypted content.
|
||||
*/
|
||||
MXEncryptedContentFile *contentFile;
|
||||
|
||||
/**
|
||||
The information on the encrypted thumbnail.
|
||||
*/
|
||||
MXEncryptedContentFile *thumbnailFile;
|
||||
|
||||
/**
|
||||
Observe Attachment download
|
||||
*/
|
||||
id onAttachmentDownloadObs;
|
||||
|
||||
/**
|
||||
The local path used to store the attachment with its original name
|
||||
*/
|
||||
NSString *documentCopyPath;
|
||||
|
||||
/**
|
||||
The attachment mimetype.
|
||||
*/
|
||||
NSString *mimetype;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKAttachment
|
||||
|
||||
- (instancetype)initWithEvent:(MXEvent*)event andMediaManager:(MXMediaManager*)mediaManager
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
_mediaManager = mediaManager;
|
||||
|
||||
// Make a copy as the data can be read at anytime later
|
||||
_eventId = event.eventId;
|
||||
_eventRoomId = event.roomId;
|
||||
_eventSentState = event.sentState;
|
||||
|
||||
NSDictionary *eventContent = event.content;
|
||||
|
||||
// Set default thumbnail orientation
|
||||
_thumbnailOrientation = UIImageOrientationUp;
|
||||
|
||||
if (event.eventType == MXEventTypeSticker)
|
||||
{
|
||||
_type = MXKAttachmentTypeSticker;
|
||||
MXJSONModelSetDictionary(_thumbnailInfo, eventContent[@"info"][@"thumbnail_info"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note: mxEvent.eventType is supposed to be MXEventTypeRoomMessage here.
|
||||
NSString *msgtype = eventContent[@"msgtype"];
|
||||
if ([msgtype isEqualToString:kMXMessageTypeImage])
|
||||
{
|
||||
_type = MXKAttachmentTypeImage;
|
||||
}
|
||||
else if (event.isVoiceMessage)
|
||||
{
|
||||
_type = MXKAttachmentTypeVoiceMessage;
|
||||
}
|
||||
else if ([msgtype isEqualToString:kMXMessageTypeAudio])
|
||||
{
|
||||
_type = MXKAttachmentTypeAudio;
|
||||
}
|
||||
else if ([msgtype isEqualToString:kMXMessageTypeVideo])
|
||||
{
|
||||
_type = MXKAttachmentTypeVideo;
|
||||
MXJSONModelSetDictionary(_thumbnailInfo, eventContent[@"info"][@"thumbnail_info"]);
|
||||
}
|
||||
else if ([msgtype isEqualToString:kMXMessageTypeLocation])
|
||||
{
|
||||
// Not supported yet
|
||||
// _type = MXKAttachmentTypeLocation;
|
||||
return nil;
|
||||
}
|
||||
else if ([msgtype isEqualToString:kMXMessageTypeFile])
|
||||
{
|
||||
_type = MXKAttachmentTypeFile;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
MXJSONModelSetString(_originalFileName, eventContent[@"body"]);
|
||||
MXJSONModelSetDictionary(_contentInfo, eventContent[@"info"]);
|
||||
MXJSONModelSetMXJSONModel(contentFile, MXEncryptedContentFile, eventContent[@"file"]);
|
||||
|
||||
// Retrieve the content url by taking into account the potential encryption.
|
||||
if (contentFile)
|
||||
{
|
||||
_isEncrypted = YES;
|
||||
_contentURL = contentFile.url;
|
||||
|
||||
MXJSONModelSetMXJSONModel(thumbnailFile, MXEncryptedContentFile, _contentInfo[@"thumbnail_file"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_isEncrypted = NO;
|
||||
MXJSONModelSetString(_contentURL, eventContent[@"url"]);
|
||||
}
|
||||
|
||||
mimetype = nil;
|
||||
if (_contentInfo)
|
||||
{
|
||||
MXJSONModelSetString(mimetype, _contentInfo[@"mimetype"]);
|
||||
}
|
||||
|
||||
_cacheFilePath = [MXMediaManager cachePathForMatrixContentURI:_contentURL andType:mimetype inFolder:_eventRoomId];
|
||||
_downloadId = [MXMediaManager downloadIdForMatrixContentURI:_contentURL inFolder:_eventRoomId];
|
||||
|
||||
// Deduce the thumbnail information from the retrieved data.
|
||||
_mxcThumbnailURI = [self getThumbnailURI];
|
||||
_thumbnailMimeType = [self getThumbnailMimeType];
|
||||
_thumbnailCachePath = [self getThumbnailCachePath];
|
||||
_thumbnailDownloadId = [self getThumbnailDownloadId];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self destroy];
|
||||
}
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
if (onAttachmentDownloadObs)
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:onAttachmentDownloadObs];
|
||||
onAttachmentDownloadObs = nil;
|
||||
}
|
||||
|
||||
// Remove the temporary file created to prepare attachment sharing
|
||||
if (documentCopyPath)
|
||||
{
|
||||
[[NSFileManager defaultManager] removeItemAtPath:documentCopyPath error:nil];
|
||||
documentCopyPath = nil;
|
||||
}
|
||||
|
||||
_previewImage = nil;
|
||||
}
|
||||
|
||||
- (NSString *)getThumbnailURI
|
||||
{
|
||||
if (thumbnailFile)
|
||||
{
|
||||
// there's an encrypted thumbnail: we return the mxc url
|
||||
return thumbnailFile.url;
|
||||
}
|
||||
|
||||
// Look for a clear thumbnail url
|
||||
return _contentInfo[@"thumbnail_url"];
|
||||
}
|
||||
|
||||
- (NSString *)getThumbnailMimeType
|
||||
{
|
||||
return _thumbnailInfo[@"mimetype"];
|
||||
}
|
||||
|
||||
- (NSString*)getThumbnailCachePath
|
||||
{
|
||||
if (_mxcThumbnailURI)
|
||||
{
|
||||
return [MXMediaManager cachePathForMatrixContentURI:_mxcThumbnailURI andType:_thumbnailMimeType inFolder:_eventRoomId];
|
||||
}
|
||||
// In case of an unencrypted image, consider the thumbnail URI deduced from the content URL, except if
|
||||
// the attachment is currently uploading.
|
||||
// Note: When the uploading is in progress, the upload id is stored in the content url (nasty trick).
|
||||
else if (_type == MXKAttachmentTypeImage && !_isEncrypted && _contentURL && ![_contentURL hasPrefix:kMXMediaUploadIdPrefix])
|
||||
{
|
||||
return [MXMediaManager thumbnailCachePathForMatrixContentURI:_contentURL
|
||||
andType:@"image/jpeg"
|
||||
inFolder:_eventRoomId
|
||||
toFitViewSize:CGSizeMake(kThumbnailWidth, kThumbnailHeight)
|
||||
withMethod:MXThumbnailingMethodScale];
|
||||
|
||||
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)getThumbnailDownloadId
|
||||
{
|
||||
if (_mxcThumbnailURI)
|
||||
{
|
||||
return [MXMediaManager downloadIdForMatrixContentURI:_mxcThumbnailURI inFolder:_eventRoomId];
|
||||
}
|
||||
// In case of an unencrypted image, consider the thumbnail URI deduced from the content URL, except if
|
||||
// the attachment is currently uploading.
|
||||
// Note: When the uploading is in progress, the upload id is stored in the content url (nasty trick).
|
||||
else if (_type == MXKAttachmentTypeImage && !_isEncrypted && _contentURL && ![_contentURL hasPrefix:kMXMediaUploadIdPrefix])
|
||||
{
|
||||
return [MXMediaManager thumbnailDownloadIdForMatrixContentURI:_contentURL
|
||||
inFolder:_eventRoomId
|
||||
toFitViewSize:CGSizeMake(kThumbnailWidth, kThumbnailHeight)
|
||||
withMethod:MXThumbnailingMethodScale];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (UIImage *)getCachedThumbnail
|
||||
{
|
||||
if (_thumbnailCachePath)
|
||||
{
|
||||
UIImage *thumb = [MXMediaManager getFromMemoryCacheWithFilePath:_thumbnailCachePath];
|
||||
if (thumb) return thumb;
|
||||
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:_thumbnailCachePath])
|
||||
{
|
||||
return [MXMediaManager loadThroughCacheWithFilePath:_thumbnailCachePath];
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)getThumbnail:(void (^)(MXKAttachment *, UIImage *))onSuccess failure:(void (^)(MXKAttachment *, NSError *error))onFailure
|
||||
{
|
||||
// Check whether a thumbnail is defined.
|
||||
if (!_thumbnailCachePath)
|
||||
{
|
||||
// there is no thumbnail: if we're an image, return the full size image. Otherwise, nothing we can do.
|
||||
if (_type == MXKAttachmentTypeImage)
|
||||
{
|
||||
[self getImage:onSuccess failure:onFailure];
|
||||
}
|
||||
else if (onFailure)
|
||||
{
|
||||
onFailure(self, nil);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the current memory cache.
|
||||
UIImage *thumb = [MXMediaManager getFromMemoryCacheWithFilePath:_thumbnailCachePath];
|
||||
if (thumb)
|
||||
{
|
||||
onSuccess(self, thumb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (thumbnailFile)
|
||||
{
|
||||
MXWeakify(self);
|
||||
|
||||
void (^decryptAndCache)(void) = ^{
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
NSInputStream *instream = [[NSInputStream alloc] initWithFileAtPath:self.thumbnailCachePath];
|
||||
NSOutputStream *outstream = [[NSOutputStream alloc] initToMemory];
|
||||
[MXEncryptedAttachments decryptAttachment:self->thumbnailFile inputStream:instream outputStream:outstream success:^{
|
||||
UIImage *img = [UIImage imageWithData:[outstream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]];
|
||||
// Save this image to in-memory cache.
|
||||
[MXMediaManager cacheImage:img withCachePath:self.thumbnailCachePath];
|
||||
onSuccess(self, img);
|
||||
} failure:^(NSError *err) {
|
||||
if (err) {
|
||||
MXLogDebug(@"Error decrypting attachment! %@", err.userInfo);
|
||||
if (onFailure) onFailure(self, err);
|
||||
return;
|
||||
}
|
||||
}];
|
||||
};
|
||||
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:_thumbnailCachePath])
|
||||
{
|
||||
decryptAndCache();
|
||||
}
|
||||
else
|
||||
{
|
||||
[_mediaManager downloadEncryptedMediaFromMatrixContentFile:thumbnailFile
|
||||
mimeType:_thumbnailMimeType
|
||||
inFolder:_eventRoomId
|
||||
success:^(NSString *outputFilePath) {
|
||||
decryptAndCache();
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
if (onFailure) onFailure(self, error);
|
||||
}];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:_thumbnailCachePath])
|
||||
{
|
||||
onSuccess(self, [MXMediaManager loadThroughCacheWithFilePath:_thumbnailCachePath]);
|
||||
}
|
||||
else if (_mxcThumbnailURI)
|
||||
{
|
||||
[_mediaManager downloadMediaFromMatrixContentURI:_mxcThumbnailURI
|
||||
withType:_thumbnailMimeType
|
||||
inFolder:_eventRoomId
|
||||
success:^(NSString *outputFilePath) {
|
||||
// Here outputFilePath = thumbnailCachePath
|
||||
onSuccess(self, [MXMediaManager loadThroughCacheWithFilePath:outputFilePath]);
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
if (onFailure) onFailure(self, error);
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Here _thumbnailCachePath is defined, so a thumbnail is available.
|
||||
// Because _mxcThumbnailURI is null, this means we have to consider the content uri (see getThumbnailCachePath).
|
||||
[_mediaManager downloadThumbnailFromMatrixContentURI:_contentURL
|
||||
withType:@"image/jpeg"
|
||||
inFolder:_eventRoomId
|
||||
toFitViewSize:CGSizeMake(kThumbnailWidth, kThumbnailHeight)
|
||||
withMethod:MXThumbnailingMethodScale
|
||||
success:^(NSString *outputFilePath) {
|
||||
// Here outputFilePath = thumbnailCachePath
|
||||
onSuccess(self, [MXMediaManager loadThroughCacheWithFilePath:outputFilePath]);
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
if (onFailure) onFailure(self, error);
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)getImage:(void (^)(MXKAttachment *, UIImage *))onSuccess failure:(void (^)(MXKAttachment *, NSError *error))onFailure
|
||||
{
|
||||
[self getAttachmentData:^(NSData *data) {
|
||||
|
||||
UIImage *img = [UIImage imageWithData:data];
|
||||
|
||||
if (img)
|
||||
{
|
||||
if (onSuccess)
|
||||
{
|
||||
onSuccess(self, img);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (onFailure)
|
||||
{
|
||||
NSError *error = [NSError errorWithDomain:kMXKAttachmentErrorDomain code:0 userInfo:@{@"err": @"error_get_image_from_data"}];
|
||||
onFailure(self, error);
|
||||
}
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
if (onFailure) onFailure(self, error);
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)getAttachmentData:(void (^)(NSData *))onSuccess failure:(void (^)(NSError *error))onFailure
|
||||
{
|
||||
MXWeakify(self);
|
||||
[self prepare:^{
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
if (self.isEncrypted)
|
||||
{
|
||||
// decrypt the encrypted file
|
||||
NSInputStream *instream = [[NSInputStream alloc] initWithFileAtPath:self.cacheFilePath];
|
||||
NSOutputStream *outstream = [[NSOutputStream alloc] initToMemory];
|
||||
[MXEncryptedAttachments decryptAttachment:self->contentFile inputStream:instream outputStream:outstream success:^{
|
||||
onSuccess([outstream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]);
|
||||
} failure:^(NSError *err) {
|
||||
if (err)
|
||||
{
|
||||
MXLogDebug(@"Error decrypting attachment! %@", err.userInfo);
|
||||
return;
|
||||
}
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
onSuccess([NSData dataWithContentsOfFile:self.cacheFilePath]);
|
||||
}
|
||||
} failure:onFailure];
|
||||
}
|
||||
|
||||
- (void)decryptToTempFile:(void (^)(NSString *))onSuccess failure:(void (^)(NSError *error))onFailure
|
||||
{
|
||||
MXWeakify(self);
|
||||
[self prepare:^{
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
NSString *tempPath = [self getTempFile];
|
||||
if (!tempPath)
|
||||
{
|
||||
if (onFailure) onFailure([NSError errorWithDomain:kMXKAttachmentErrorDomain code:0 userInfo:@{@"err": @"error_creating_temp_file"}]);
|
||||
return;
|
||||
}
|
||||
|
||||
NSInputStream *inStream = [NSInputStream inputStreamWithFileAtPath:self.cacheFilePath];
|
||||
NSOutputStream *outStream = [NSOutputStream outputStreamToFileAtPath:tempPath append:NO];
|
||||
|
||||
[MXEncryptedAttachments decryptAttachment:self->contentFile inputStream:inStream outputStream:outStream success:^{
|
||||
onSuccess(tempPath);
|
||||
} failure:^(NSError *err) {
|
||||
if (err) {
|
||||
if (onFailure) onFailure(err);
|
||||
return;
|
||||
}
|
||||
}];
|
||||
} failure:onFailure];
|
||||
}
|
||||
|
||||
- (NSString *)getTempFile
|
||||
{
|
||||
// create a file with an appropriate extension because iOS detects based on file extension
|
||||
// all over the place
|
||||
NSString *ext = [MXTools fileExtensionFromContentType:mimetype];
|
||||
NSString *filenameTemplate = [NSString stringWithFormat:@"%@.XXXXXX%@", kMXKAttachmentFileNameBase, ext];
|
||||
NSString *template = [NSTemporaryDirectory() stringByAppendingPathComponent:filenameTemplate];
|
||||
|
||||
const char *templateCstr = [template fileSystemRepresentation];
|
||||
char *tempPathCstr = (char *)malloc(strlen(templateCstr) + 1);
|
||||
strcpy(tempPathCstr, templateCstr);
|
||||
|
||||
int fd = mkstemps(tempPathCstr, (int)ext.length);
|
||||
if (!fd)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
NSString *tempPath = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempPathCstr
|
||||
length:strlen(tempPathCstr)];
|
||||
free(tempPathCstr);
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
+ (void)clearCache
|
||||
{
|
||||
NSString *temporaryDirectoryPath = NSTemporaryDirectory();
|
||||
NSDirectoryEnumerator<NSString *> *enumerator = [NSFileManager.defaultManager enumeratorAtPath:temporaryDirectoryPath];
|
||||
|
||||
NSString *filePath;
|
||||
while (filePath = [enumerator nextObject]) {
|
||||
if(![filePath containsString:kMXKAttachmentFileNameBase]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
BOOL result = [NSFileManager.defaultManager removeItemAtPath:[temporaryDirectoryPath stringByAppendingPathComponent:filePath] error:&error];
|
||||
if (!result && error) {
|
||||
MXLogError(@"[MXKAttachment] Failed deleting temporary file with error: %@", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)prepare:(void (^)(void))onAttachmentReady failure:(void (^)(NSError *error))onFailure
|
||||
{
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:_cacheFilePath])
|
||||
{
|
||||
// Done
|
||||
if (onAttachmentReady)
|
||||
{
|
||||
onAttachmentReady();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Trigger download if it is not already in progress
|
||||
MXMediaLoader* loader = [MXMediaManager existingDownloaderWithIdentifier:_downloadId];
|
||||
if (!loader)
|
||||
{
|
||||
if (_isEncrypted)
|
||||
{
|
||||
loader = [_mediaManager downloadEncryptedMediaFromMatrixContentFile:contentFile
|
||||
mimeType:mimetype
|
||||
inFolder:_eventRoomId];
|
||||
}
|
||||
else
|
||||
{
|
||||
loader = [_mediaManager downloadMediaFromMatrixContentURI:_contentURL
|
||||
withType:mimetype
|
||||
inFolder:_eventRoomId];
|
||||
}
|
||||
}
|
||||
|
||||
if (loader)
|
||||
{
|
||||
MXWeakify(self);
|
||||
|
||||
// Add observers
|
||||
onAttachmentDownloadObs = [[NSNotificationCenter defaultCenter] addObserverForName:kMXMediaLoaderStateDidChangeNotification object:loader queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
||||
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
MXMediaLoader *loader = (MXMediaLoader*)notif.object;
|
||||
switch (loader.state) {
|
||||
case MXMediaLoaderStateDownloadCompleted:
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self->onAttachmentDownloadObs];
|
||||
self->onAttachmentDownloadObs = nil;
|
||||
if (onAttachmentReady)
|
||||
{
|
||||
onAttachmentReady ();
|
||||
}
|
||||
break;
|
||||
case MXMediaLoaderStateDownloadFailed:
|
||||
case MXMediaLoaderStateCancelled:
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self->onAttachmentDownloadObs];
|
||||
self->onAttachmentDownloadObs = nil;
|
||||
if (onFailure)
|
||||
{
|
||||
onFailure (loader.error);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}];
|
||||
}
|
||||
else if (onFailure)
|
||||
{
|
||||
onFailure (nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)save:(void (^)(void))onSuccess failure:(void (^)(NSError *error))onFailure
|
||||
{
|
||||
if (_type == MXKAttachmentTypeImage || _type == MXKAttachmentTypeVideo)
|
||||
{
|
||||
MXWeakify(self);
|
||||
if (self.isEncrypted) {
|
||||
[self decryptToTempFile:^(NSString *path) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
NSURL* url = [NSURL fileURLWithPath:path];
|
||||
|
||||
[MXMediaManager saveMediaToPhotosLibrary:url
|
||||
isImage:(self.type == MXKAttachmentTypeImage)
|
||||
success:^(NSURL *assetURL){
|
||||
if (onSuccess)
|
||||
{
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
failure:onFailure];
|
||||
} failure:onFailure];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self prepare:^{
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
NSURL* url = [NSURL fileURLWithPath:self.cacheFilePath];
|
||||
|
||||
[MXMediaManager saveMediaToPhotosLibrary:url
|
||||
isImage:(self.type == MXKAttachmentTypeImage)
|
||||
success:^(NSURL *assetURL){
|
||||
if (onSuccess)
|
||||
{
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
failure:onFailure];
|
||||
} failure:onFailure];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not supported
|
||||
if (onFailure)
|
||||
{
|
||||
onFailure(nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)copy:(void (^)(void))onSuccess failure:(void (^)(NSError *error))onFailure
|
||||
{
|
||||
MXWeakify(self);
|
||||
[self prepare:^{
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
if (self.type == MXKAttachmentTypeImage)
|
||||
{
|
||||
[self getImage:^(MXKAttachment *attachment, UIImage *img) {
|
||||
MXKPasteboardManager.shared.pasteboard.image = img;
|
||||
if (onSuccess)
|
||||
{
|
||||
onSuccess();
|
||||
}
|
||||
} failure:^(MXKAttachment *attachment, NSError *error) {
|
||||
if (onFailure) onFailure(error);
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXWeakify(self);
|
||||
[self getAttachmentData:^(NSData *data) {
|
||||
if (data)
|
||||
{
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
NSString* UTI = (__bridge_transfer NSString *) UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[self.cacheFilePath pathExtension] , NULL);
|
||||
|
||||
if (UTI)
|
||||
{
|
||||
[MXKPasteboardManager.shared.pasteboard setData:data forPasteboardType:UTI];
|
||||
if (onSuccess)
|
||||
{
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
}
|
||||
} failure:onFailure];
|
||||
}
|
||||
|
||||
// Unexpected error
|
||||
if (onFailure)
|
||||
{
|
||||
onFailure(nil);
|
||||
}
|
||||
|
||||
} failure:onFailure];
|
||||
}
|
||||
|
||||
- (MXKUTI *)uti
|
||||
{
|
||||
return [[MXKUTI alloc] initWithMimeType:mimetype];
|
||||
}
|
||||
|
||||
- (void)prepareShare:(void (^)(NSURL *fileURL))onReadyToShare failure:(void (^)(NSError *error))onFailure
|
||||
{
|
||||
MXWeakify(self);
|
||||
void (^haveFile)(NSString *) = ^(NSString *path) {
|
||||
// Prepare the file URL by considering the original file name (if any)
|
||||
NSURL *fileUrl;
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
// Check whether the original name retrieved from event body has extension
|
||||
if (self.originalFileName && [self.originalFileName pathExtension].length)
|
||||
{
|
||||
// Copy the cached file to restore its original name
|
||||
// Note: We used previously symbolic link (instead of copy) but UIDocumentInteractionController failed to open Office documents (.docx, .pptx...).
|
||||
self->documentCopyPath = [[MXMediaManager getCachePath] stringByAppendingPathComponent:self.originalFileName];
|
||||
|
||||
[[NSFileManager defaultManager] removeItemAtPath:self->documentCopyPath error:nil];
|
||||
if ([[NSFileManager defaultManager] copyItemAtPath:path toPath:self->documentCopyPath error:nil])
|
||||
{
|
||||
fileUrl = [NSURL fileURLWithPath:self->documentCopyPath];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileUrl)
|
||||
{
|
||||
// Use the cached file by default
|
||||
fileUrl = [NSURL fileURLWithPath:path];
|
||||
self->documentCopyPath = path;
|
||||
}
|
||||
|
||||
onReadyToShare (fileUrl);
|
||||
};
|
||||
|
||||
if (self.isEncrypted)
|
||||
{
|
||||
[self decryptToTempFile:^(NSString *path) {
|
||||
haveFile(path);
|
||||
} failure:onFailure];
|
||||
}
|
||||
else
|
||||
{
|
||||
// First download data if it is not already done
|
||||
[self prepare:^{
|
||||
haveFile(self.cacheFilePath);
|
||||
} failure:onFailure];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onShareEnded
|
||||
{
|
||||
// Remove the temporary file created to prepare attachment sharing
|
||||
if (documentCopyPath)
|
||||
{
|
||||
[[NSFileManager defaultManager] removeItemAtPath:documentCopyPath error:nil];
|
||||
documentCopyPath = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MatrixSDK/MatrixSDK.h>
|
||||
|
||||
/**
|
||||
`MXKQueuedEvent` represents an event waiting to be processed.
|
||||
*/
|
||||
@interface MXKQueuedEvent : NSObject
|
||||
|
||||
/**
|
||||
The event.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXEvent *event;
|
||||
|
||||
/**
|
||||
The state of the room when the event has been received.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXRoomState *state;
|
||||
|
||||
/**
|
||||
The direction of reception. Is it a live event or an event from the history?
|
||||
*/
|
||||
@property (nonatomic, readonly) MXTimelineDirection direction;
|
||||
|
||||
/**
|
||||
Tells whether the event is queued during server sync or not.
|
||||
*/
|
||||
@property (nonatomic) BOOL serverSyncEvent;
|
||||
|
||||
/**
|
||||
Date of the `event`. If event has a valid `originServerTs`, it's converted to a date object, otherwise current date.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSDate *eventDate;
|
||||
|
||||
- (instancetype)initWithEvent:(MXEvent*)event andRoomState:(MXRoomState*)state direction:(MXTimelineDirection)direction;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKQueuedEvent.h"
|
||||
|
||||
@implementation MXKQueuedEvent
|
||||
|
||||
- (instancetype)initWithEvent:(MXEvent *)event andRoomState:(MXRoomState *)state direction:(MXTimelineDirection)direction
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
_event = event;
|
||||
_state = state;
|
||||
_direction = direction;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDate *)eventDate
|
||||
{
|
||||
if (_event.originServerTs != kMXUndefinedTimestamp)
|
||||
{
|
||||
return [NSDate dateWithTimeIntervalSince1970:(double)_event.originServerTs/1000];
|
||||
}
|
||||
|
||||
return [NSDate date];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKCellData.h"
|
||||
#import "MXKRoomBubbleCellDataStoring.h"
|
||||
|
||||
#import "MXKRoomBubbleComponent.h"
|
||||
|
||||
#define MXKROOMBUBBLECELLDATA_TEXTVIEW_DEFAULT_VERTICAL_INSET 8
|
||||
|
||||
/**
|
||||
`MXKRoomBubbleCellData` instances compose data for `MXKRoomBubbleTableViewCell` cells.
|
||||
|
||||
This is the basic implementation which considers only one component (event) by bubble.
|
||||
`MXKRoomBubbleCellDataWithAppendingMode` extends this class to merge consecutive messages from the same sender into one bubble.
|
||||
*/
|
||||
@interface MXKRoomBubbleCellData : MXKCellData <MXKRoomBubbleCellDataStoring>
|
||||
{
|
||||
@protected
|
||||
/**
|
||||
The data source owner of this instance.
|
||||
*/
|
||||
__weak MXKRoomDataSource *roomDataSource;
|
||||
|
||||
/**
|
||||
Array of bubble components. Each bubble is supposed to have at least one component.
|
||||
*/
|
||||
NSMutableArray *bubbleComponents;
|
||||
|
||||
/**
|
||||
The body of the message with sets of attributes, or kind of content description in case of attachment (e.g. "image attachment")
|
||||
*/
|
||||
NSAttributedString *attributedTextMessage;
|
||||
|
||||
/**
|
||||
The optional text pattern to be highlighted in the body of the message.
|
||||
*/
|
||||
NSString *highlightedPattern;
|
||||
UIColor *highlightedPatternColor;
|
||||
UIFont *highlightedPatternFont;
|
||||
}
|
||||
|
||||
/**
|
||||
The matrix session.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXSession *mxSession;
|
||||
|
||||
/**
|
||||
Returns bubble components list (`MXKRoomBubbleComponent` instances).
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<MXKRoomBubbleComponent*> *bubbleComponents;
|
||||
|
||||
/**
|
||||
Read receipts per event.
|
||||
*/
|
||||
@property(nonatomic) NSMutableDictionary<NSString* /* eventId */,
|
||||
NSArray<MXReceiptData *> *> *readReceipts;
|
||||
|
||||
/**
|
||||
Aggregated reactions per event.
|
||||
*/
|
||||
@property(nonatomic) NSMutableDictionary<NSString* /* eventId */, MXAggregatedReactions*> *reactions;
|
||||
|
||||
/**
|
||||
Whether there is a link to preview in the components.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL hasLink;
|
||||
|
||||
/**
|
||||
Event formatter
|
||||
*/
|
||||
@property (nonatomic) MXKEventFormatter *eventFormatter;
|
||||
|
||||
/**
|
||||
The max width of the text view used to display the text message (relevant only for text message or attached file).
|
||||
*/
|
||||
@property (nonatomic) CGFloat maxTextViewWidth;
|
||||
|
||||
/**
|
||||
The bubble content size depends on its type:
|
||||
- Text: returns suitable content size of a text view to display the whole text message (respecting maxTextViewWidth).
|
||||
- Attached image or video: returns suitable content size for an image view in order to display
|
||||
attachment thumbnail or icon.
|
||||
- Attached file: returns suitable content size of a text view to display the file name (no icon is used presently).
|
||||
*/
|
||||
@property (nonatomic) CGSize contentSize;
|
||||
|
||||
/**
|
||||
Set of flags indicating fixes that need to be applied at display time.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXKRoomBubbleComponentDisplayFix displayFix;
|
||||
|
||||
/**
|
||||
Attachment upload
|
||||
*/
|
||||
@property (nonatomic) NSString *uploadId;
|
||||
@property (nonatomic) CGFloat uploadProgress;
|
||||
|
||||
/**
|
||||
Indicate a bubble component needs to show encryption badge.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL containsBubbleComponentWithEncryptionBadge;
|
||||
|
||||
/**
|
||||
Indicate that the current text message layout is no longer valid and should be recomputed
|
||||
before presentation in a bubble cell. This could be due to the content changing, or the
|
||||
available space for the cell has been updated.
|
||||
|
||||
This will clear the current `attributedTextMessage` allowing it to be
|
||||
rebuilt on demand when requested.
|
||||
*/
|
||||
- (void)invalidateTextLayout;
|
||||
|
||||
/**
|
||||
Check and refresh the position of each component.
|
||||
*/
|
||||
- (void)prepareBubbleComponentsPosition;
|
||||
|
||||
/**
|
||||
Return the raw height of the provided text by removing any vertical margin/inset.
|
||||
|
||||
@param attributedText the attributed text to measure
|
||||
@return the computed height
|
||||
*/
|
||||
- (CGFloat)rawTextHeight:(NSAttributedString*)attributedText;
|
||||
|
||||
/**
|
||||
Return the content size of a text view initialized with the provided attributed text.
|
||||
CAUTION: This method runs only on main thread.
|
||||
|
||||
@param attributedText the attributed text to measure
|
||||
@param removeVerticalInset tell whether the computation should remove vertical inset in text container.
|
||||
@return the computed size content
|
||||
*/
|
||||
- (CGSize)textContentSize:(NSAttributedString*)attributedText removeVerticalInset:(BOOL)removeVerticalInset;
|
||||
|
||||
/**
|
||||
Get bubble component index from event id.
|
||||
|
||||
@param eventId Event id of bubble component.
|
||||
@return Index of bubble component associated to event id or NSNotFound
|
||||
*/
|
||||
- (NSInteger)bubbleComponentIndexForEventId:(NSString *)eventId;
|
||||
|
||||
/**
|
||||
Get the first visible component.
|
||||
|
||||
@return First visible component or nil.
|
||||
*/
|
||||
- (MXKRoomBubbleComponent*)getFirstBubbleComponentWithDisplay;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,923 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#define MXKROOMBUBBLECELLDATA_MAX_ATTACHMENTVIEW_WIDTH 192
|
||||
|
||||
#define MXKROOMBUBBLECELLDATA_DEFAULT_MAX_TEXTVIEW_WIDTH 200
|
||||
|
||||
@import MatrixSDK;
|
||||
|
||||
#import "MXKRoomBubbleCellData.h"
|
||||
|
||||
#import "MXKTools.h"
|
||||
|
||||
@implementation MXKRoomBubbleCellData
|
||||
@synthesize senderId, targetId, roomId, senderDisplayName, senderAvatarUrl, senderAvatarPlaceholder, targetDisplayName, targetAvatarUrl, targetAvatarPlaceholder, isEncryptedRoom, isPaginationFirstBubble, shouldHideSenderInformation, date, isIncoming, isAttachmentWithThumbnail, isAttachmentWithIcon, attachment, senderFlair;
|
||||
@synthesize textMessage, attributedTextMessage;
|
||||
@synthesize shouldHideSenderName, isTyping, showBubbleDateTime, showBubbleReceipts, useCustomDateTimeLabel, useCustomReceipts, useCustomUnsentButton, hasNoDisplay;
|
||||
@synthesize tag;
|
||||
@synthesize collapsable, collapsed, collapsedAttributedTextMessage, prevCollapsableCellData, nextCollapsableCellData, collapseState;
|
||||
|
||||
#pragma mark - MXKRoomBubbleCellDataStoring
|
||||
|
||||
- (instancetype)initWithEvent:(MXEvent *)event andRoomState:(MXRoomState *)roomState andRoomDataSource:(MXKRoomDataSource *)roomDataSource2
|
||||
{
|
||||
self = [self init];
|
||||
if (self)
|
||||
{
|
||||
roomDataSource = roomDataSource2;
|
||||
|
||||
// Initialize read receipts
|
||||
self.readReceipts = [NSMutableDictionary dictionary];
|
||||
|
||||
// Create the bubble component based on matrix event
|
||||
MXKRoomBubbleComponent *firstComponent = [[MXKRoomBubbleComponent alloc] initWithEvent:event roomState:roomState eventFormatter:roomDataSource.eventFormatter session:roomDataSource.mxSession];
|
||||
if (firstComponent)
|
||||
{
|
||||
bubbleComponents = [NSMutableArray array];
|
||||
[bubbleComponents addObject:firstComponent];
|
||||
|
||||
senderId = event.sender;
|
||||
targetId = [event.type isEqualToString:kMXEventTypeStringRoomMember] ? event.stateKey : nil;
|
||||
roomId = roomDataSource.roomId;
|
||||
senderDisplayName = [roomDataSource.eventFormatter senderDisplayNameForEvent:event withRoomState:roomState];
|
||||
senderAvatarUrl = [roomDataSource.eventFormatter senderAvatarUrlForEvent:event withRoomState:roomState];
|
||||
senderAvatarPlaceholder = nil;
|
||||
targetDisplayName = [roomDataSource.eventFormatter targetDisplayNameForEvent:event withRoomState:roomState];
|
||||
targetAvatarUrl = [roomDataSource.eventFormatter targetAvatarUrlForEvent:event withRoomState:roomState];
|
||||
targetAvatarPlaceholder = nil;
|
||||
isEncryptedRoom = roomState.isEncrypted;
|
||||
isIncoming = ([event.sender isEqualToString:roomDataSource.mxSession.myUser.userId] == NO);
|
||||
|
||||
// Check attachment if any
|
||||
if ([roomDataSource.eventFormatter isSupportedAttachment:event])
|
||||
{
|
||||
// Note: event.eventType is equal here to MXEventTypeRoomMessage or MXEventTypeSticker
|
||||
attachment = [[MXKAttachment alloc] initWithEvent:event andMediaManager:roomDataSource.mxSession.mediaManager];
|
||||
if (attachment && attachment.type == MXKAttachmentTypeImage)
|
||||
{
|
||||
// Check the current thumbnail orientation. Rotate the current content size (if need)
|
||||
if (attachment.thumbnailOrientation == UIImageOrientationLeft || attachment.thumbnailOrientation == UIImageOrientationRight)
|
||||
{
|
||||
_contentSize = CGSizeMake(_contentSize.height, _contentSize.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report the attributed string (This will initialize _contentSize attribute)
|
||||
self.attributedTextMessage = firstComponent.attributedTextMessage;
|
||||
|
||||
// Initialize rendering attributes
|
||||
_maxTextViewWidth = MXKROOMBUBBLECELLDATA_DEFAULT_MAX_TEXTVIEW_WIDTH;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ignore this event
|
||||
self = nil;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
// Reset any observer on publicised groups by user.
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdatePublicisedGroupsForUsersNotification object:self.mxSession];
|
||||
|
||||
roomDataSource = nil;
|
||||
bubbleComponents = nil;
|
||||
}
|
||||
|
||||
- (NSUInteger)updateEvent:(NSString *)eventId withEvent:(MXEvent *)event
|
||||
{
|
||||
NSUInteger count = 0;
|
||||
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
// Retrieve the component storing the event and update it
|
||||
for (NSUInteger index = 0; index < bubbleComponents.count; index++)
|
||||
{
|
||||
MXKRoomBubbleComponent *roomBubbleComponent = [bubbleComponents objectAtIndex:index];
|
||||
if ([roomBubbleComponent.event.eventId isEqualToString:eventId])
|
||||
{
|
||||
[roomBubbleComponent updateWithEvent:event roomState:roomDataSource.roomState session:self.mxSession];
|
||||
if (!roomBubbleComponent.textMessage.length)
|
||||
{
|
||||
[bubbleComponents removeObjectAtIndex:index];
|
||||
}
|
||||
// Indicate that the text message layout should be recomputed.
|
||||
[self invalidateTextLayout];
|
||||
|
||||
// Handle here attachment update.
|
||||
// For example: the case of update of attachment event happens when an echo is replaced by its true event
|
||||
// received back by the events stream.
|
||||
if (attachment)
|
||||
{
|
||||
// Check the current content url, to update it with the actual one
|
||||
// Retrieve content url/info
|
||||
NSString *eventContentURL = event.content[@"url"];
|
||||
if (event.content[@"file"][@"url"])
|
||||
{
|
||||
eventContentURL = event.content[@"file"][@"url"];
|
||||
}
|
||||
|
||||
if (!eventContentURL.length)
|
||||
{
|
||||
// The attachment has been redacted.
|
||||
attachment = nil;
|
||||
_contentSize = CGSizeZero;
|
||||
}
|
||||
else if (![attachment.eventId isEqualToString:event.eventId] || ![attachment.contentURL isEqualToString:eventContentURL])
|
||||
{
|
||||
MXKAttachment *updatedAttachment = [[MXKAttachment alloc] initWithEvent:event andMediaManager:roomDataSource.mxSession.mediaManager];
|
||||
|
||||
// Sanity check on attachment type
|
||||
if (updatedAttachment && attachment.type == updatedAttachment.type)
|
||||
{
|
||||
// Re-use the current image as preview to prevent the cell from flashing
|
||||
updatedAttachment.previewImage = [attachment getCachedThumbnail];
|
||||
if (!updatedAttachment.previewImage && attachment.type == MXKAttachmentTypeImage)
|
||||
{
|
||||
updatedAttachment.previewImage = [MXMediaManager loadPictureFromFilePath:attachment.cacheFilePath];
|
||||
}
|
||||
|
||||
// Clean the cache by removing the useless data
|
||||
if (![updatedAttachment.cacheFilePath isEqualToString:attachment.cacheFilePath])
|
||||
{
|
||||
[[NSFileManager defaultManager] removeItemAtPath:attachment.cacheFilePath error:nil];
|
||||
}
|
||||
if (![updatedAttachment.thumbnailCachePath isEqualToString:attachment.thumbnailCachePath])
|
||||
{
|
||||
[[NSFileManager defaultManager] removeItemAtPath:attachment.thumbnailCachePath error:nil];
|
||||
}
|
||||
|
||||
// Update the current attachment description
|
||||
attachment = updatedAttachment;
|
||||
|
||||
if (attachment.type == MXKAttachmentTypeImage)
|
||||
{
|
||||
// Reset content size
|
||||
_contentSize = CGSizeZero;
|
||||
|
||||
// Check the current thumbnail orientation. Rotate the current content size (if need)
|
||||
if (attachment.thumbnailOrientation == UIImageOrientationLeft || attachment.thumbnailOrientation == UIImageOrientationRight)
|
||||
{
|
||||
_contentSize = CGSizeMake(_contentSize.height, _contentSize.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXKRoomBubbleCellData] updateEvent: Warning: Does not support change of attachment type");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ([roomDataSource.eventFormatter isSupportedAttachment:event])
|
||||
{
|
||||
// The event is updated to an event with attachement
|
||||
attachment = [[MXKAttachment alloc] initWithEvent:event andMediaManager:roomDataSource.mxSession.mediaManager];
|
||||
if (attachment && attachment.type == MXKAttachmentTypeImage)
|
||||
{
|
||||
// Check the current thumbnail orientation. Rotate the current content size (if need)
|
||||
if (attachment.thumbnailOrientation == UIImageOrientationLeft || attachment.thumbnailOrientation == UIImageOrientationRight)
|
||||
{
|
||||
_contentSize = CGSizeMake(_contentSize.height, _contentSize.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
count = bubbleComponents.count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSUInteger)removeEvent:(NSString *)eventId
|
||||
{
|
||||
NSUInteger count = 0;
|
||||
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
for (MXKRoomBubbleComponent *roomBubbleComponent in bubbleComponents)
|
||||
{
|
||||
if ([roomBubbleComponent.event.eventId isEqualToString:eventId])
|
||||
{
|
||||
[bubbleComponents removeObject:roomBubbleComponent];
|
||||
|
||||
// Indicate that the text message layout should be recomputed.
|
||||
[self invalidateTextLayout];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
count = bubbleComponents.count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSUInteger)removeEventsFromEvent:(NSString*)eventId removedEvents:(NSArray<MXEvent*>**)removedEvents;
|
||||
{
|
||||
NSMutableArray *cuttedEvents = [NSMutableArray array];
|
||||
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
NSInteger componentIndex = [self bubbleComponentIndexForEventId:eventId];
|
||||
|
||||
if (NSNotFound != componentIndex)
|
||||
{
|
||||
NSArray *newBubbleComponents = [bubbleComponents subarrayWithRange:NSMakeRange(0, componentIndex)];
|
||||
|
||||
for (NSUInteger i = componentIndex; i < bubbleComponents.count; i++)
|
||||
{
|
||||
MXKRoomBubbleComponent *roomBubbleComponent = bubbleComponents[i];
|
||||
[cuttedEvents addObject:roomBubbleComponent.event];
|
||||
}
|
||||
|
||||
bubbleComponents = [NSMutableArray arrayWithArray:newBubbleComponents];
|
||||
|
||||
// Indicate that the text message layout should be recomputed.
|
||||
[self invalidateTextLayout];
|
||||
}
|
||||
}
|
||||
|
||||
*removedEvents = cuttedEvents;
|
||||
return bubbleComponents.count;
|
||||
}
|
||||
|
||||
- (BOOL)hasSameSenderAsBubbleCellData:(id<MXKRoomBubbleCellDataStoring>)bubbleCellData
|
||||
{
|
||||
// Sanity check: accept only object of MXKRoomBubbleCellData classes or sub-classes
|
||||
NSParameterAssert([bubbleCellData isKindOfClass:[MXKRoomBubbleCellData class]]);
|
||||
|
||||
// NOTE: Same sender means here same id, same display name and same avatar
|
||||
|
||||
// Check first user id
|
||||
if ([senderId isEqualToString:bubbleCellData.senderId] == NO)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
// Check sender name
|
||||
if ((senderDisplayName.length || bubbleCellData.senderDisplayName.length) && ([senderDisplayName isEqualToString:bubbleCellData.senderDisplayName] == NO))
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
// Check avatar url
|
||||
if ((senderAvatarUrl.length || bubbleCellData.senderAvatarUrl.length) && ([senderAvatarUrl isEqualToString:bubbleCellData.senderAvatarUrl] == NO))
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (MXKRoomBubbleComponent*) getFirstBubbleComponent
|
||||
{
|
||||
MXKRoomBubbleComponent* first = nil;
|
||||
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
if (bubbleComponents.count)
|
||||
{
|
||||
first = [bubbleComponents firstObject];
|
||||
}
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
- (MXKRoomBubbleComponent*) getFirstBubbleComponentWithDisplay
|
||||
{
|
||||
// Look for the first component which is actually displayed (some event are ignored in room history display).
|
||||
MXKRoomBubbleComponent* first = nil;
|
||||
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
for (NSInteger index = 0; index < bubbleComponents.count; index++)
|
||||
{
|
||||
MXKRoomBubbleComponent *component = bubbleComponents[index];
|
||||
if (component.attributedTextMessage)
|
||||
{
|
||||
first = component;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return first;
|
||||
}
|
||||
|
||||
- (NSAttributedString*)attributedTextMessageWithHighlightedEvent:(NSString*)eventId tintColor:(UIColor*)tintColor
|
||||
{
|
||||
NSAttributedString *customAttributedTextMsg;
|
||||
|
||||
// By default only one component is supported, consider here the first component
|
||||
MXKRoomBubbleComponent *firstComponent = [self getFirstBubbleComponent];
|
||||
|
||||
if (firstComponent)
|
||||
{
|
||||
customAttributedTextMsg = firstComponent.attributedTextMessage;
|
||||
|
||||
// Sanity check
|
||||
if (customAttributedTextMsg && [firstComponent.event.eventId isEqualToString:eventId])
|
||||
{
|
||||
NSMutableAttributedString *customComponentString = [[NSMutableAttributedString alloc] initWithAttributedString:customAttributedTextMsg];
|
||||
UIColor *color = tintColor ? tintColor : [UIColor lightGrayColor];
|
||||
[customComponentString addAttribute:NSBackgroundColorAttributeName value:color range:NSMakeRange(0, customComponentString.length)];
|
||||
customAttributedTextMsg = customComponentString;
|
||||
}
|
||||
}
|
||||
|
||||
return customAttributedTextMsg;
|
||||
}
|
||||
|
||||
- (void)highlightPatternInTextMessage:(NSString*)pattern withForegroundColor:(UIColor*)patternColor andFont:(UIFont*)patternFont
|
||||
{
|
||||
highlightedPattern = pattern;
|
||||
highlightedPatternColor = patternColor;
|
||||
highlightedPatternFont = patternFont;
|
||||
|
||||
// Indicate that the text message layout should be recomputed.
|
||||
[self invalidateTextLayout];
|
||||
}
|
||||
|
||||
- (void)setShouldHideSenderInformation:(BOOL)inShouldHideSenderInformation
|
||||
{
|
||||
shouldHideSenderInformation = inShouldHideSenderInformation;
|
||||
|
||||
if (!shouldHideSenderInformation)
|
||||
{
|
||||
// Refresh the flair
|
||||
[self refreshSenderFlair];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)refreshSenderFlair
|
||||
{
|
||||
// Reset by default any observer on publicised groups by user.
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdatePublicisedGroupsForUsersNotification object:self.mxSession];
|
||||
|
||||
// Check first whether the room enabled the flair for some groups
|
||||
NSArray<NSString *> *roomRelatedGroups = roomDataSource.roomState.relatedGroups;
|
||||
if (roomRelatedGroups.count && senderId)
|
||||
{
|
||||
NSArray<NSString *> *senderPublicisedGroups;
|
||||
|
||||
senderPublicisedGroups = [self.mxSession publicisedGroupsForUser:senderId];
|
||||
|
||||
if (senderPublicisedGroups.count)
|
||||
{
|
||||
// Cross the 2 arrays to keep only the common group ids
|
||||
NSMutableArray *flair = [NSMutableArray arrayWithCapacity:roomRelatedGroups.count];
|
||||
|
||||
for (NSString *groupId in roomRelatedGroups)
|
||||
{
|
||||
if ([senderPublicisedGroups indexOfObject:groupId] != NSNotFound)
|
||||
{
|
||||
MXGroup *group = [roomDataSource groupWithGroupId:groupId];
|
||||
[flair addObject:group];
|
||||
}
|
||||
}
|
||||
|
||||
if (flair.count)
|
||||
{
|
||||
self.senderFlair = flair;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.senderFlair = nil;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self.senderFlair = nil;
|
||||
}
|
||||
|
||||
// Observe any change on publicised groups for the message sender
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionUpdatePublicisedGroupsForUsers:) name:kMXSessionDidUpdatePublicisedGroupsForUsersNotification object:self.mxSession];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)invalidateTextLayout
|
||||
{
|
||||
self.attributedTextMessage = nil;
|
||||
}
|
||||
|
||||
- (void)prepareBubbleComponentsPosition
|
||||
{
|
||||
// Consider here only the first component if any
|
||||
MXKRoomBubbleComponent *firstComponent = [self getFirstBubbleComponent];
|
||||
|
||||
if (firstComponent)
|
||||
{
|
||||
CGFloat positionY = (attachment == nil || attachment.type == MXKAttachmentTypeFile || attachment.type == MXKAttachmentTypeAudio || attachment.type == MXKAttachmentTypeVoiceMessage) ? MXKROOMBUBBLECELLDATA_TEXTVIEW_DEFAULT_VERTICAL_INSET : 0;
|
||||
firstComponent.position = CGPointMake(0, positionY);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)bubbleComponentIndexForEventId:(NSString *)eventId
|
||||
{
|
||||
return [self.bubbleComponents indexOfObjectPassingTest:^BOOL(MXKRoomBubbleComponent * _Nonnull bubbleComponent, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
if ([bubbleComponent.event.eventId isEqualToString:eventId])
|
||||
{
|
||||
*stop = YES;
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Text measuring
|
||||
|
||||
// Return the raw height of the provided text by removing any margin
|
||||
- (CGFloat)rawTextHeight: (NSAttributedString*)attributedText
|
||||
{
|
||||
__block CGSize textSize;
|
||||
if ([NSThread currentThread] != [NSThread mainThread])
|
||||
{
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
textSize = [self textContentSize:attributedText removeVerticalInset:YES];
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
textSize = [self textContentSize:attributedText removeVerticalInset:YES];
|
||||
}
|
||||
|
||||
return textSize.height;
|
||||
}
|
||||
|
||||
- (CGSize)textContentSize:(NSAttributedString*)attributedText removeVerticalInset:(BOOL)removeVerticalInset
|
||||
{
|
||||
static UITextView* measurementTextView = nil;
|
||||
static UITextView* measurementTextViewWithoutInset = nil;
|
||||
|
||||
if (attributedText.length)
|
||||
{
|
||||
if (!measurementTextView)
|
||||
{
|
||||
measurementTextView = [[UITextView alloc] init];
|
||||
|
||||
measurementTextViewWithoutInset = [[UITextView alloc] init];
|
||||
// Remove the container inset: this operation impacts only the vertical margin.
|
||||
// Note: consider textContainer.lineFragmentPadding to remove horizontal margin
|
||||
measurementTextViewWithoutInset.textContainerInset = UIEdgeInsetsZero;
|
||||
}
|
||||
|
||||
// Select the right text view for measurement
|
||||
UITextView *selectedTextView = (removeVerticalInset ? measurementTextViewWithoutInset : measurementTextView);
|
||||
|
||||
selectedTextView.frame = CGRectMake(0, 0, _maxTextViewWidth, MAXFLOAT);
|
||||
selectedTextView.attributedText = attributedText;
|
||||
|
||||
CGSize size = [selectedTextView sizeThatFits:selectedTextView.frame.size];
|
||||
|
||||
// Manage the case where a string attribute has a single paragraph with a left indent
|
||||
// In this case, [UITextView sizeThatFits] ignores the indent and return the width
|
||||
// of the text only.
|
||||
// So, add this indent afterwards
|
||||
NSRange textRange = NSMakeRange(0, attributedText.length);
|
||||
NSRange longestEffectiveRange;
|
||||
NSParagraphStyle *paragraphStyle = [attributedText attribute:NSParagraphStyleAttributeName atIndex:0 longestEffectiveRange:&longestEffectiveRange inRange:textRange];
|
||||
|
||||
if (NSEqualRanges(textRange, longestEffectiveRange))
|
||||
{
|
||||
size.width = size.width + paragraphStyle.headIndent;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
return CGSizeZero;
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (MXSession*)mxSession
|
||||
{
|
||||
return roomDataSource.mxSession;
|
||||
}
|
||||
|
||||
- (NSArray*)bubbleComponents
|
||||
{
|
||||
NSArray* copy;
|
||||
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
copy = [bubbleComponents copy];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
- (NSString*)textMessage
|
||||
{
|
||||
return self.attributedTextMessage.string;
|
||||
}
|
||||
|
||||
- (void)setAttributedTextMessage:(NSAttributedString *)inAttributedTextMessage
|
||||
{
|
||||
attributedTextMessage = inAttributedTextMessage;
|
||||
|
||||
if (attributedTextMessage.length && highlightedPattern)
|
||||
{
|
||||
[self highlightPattern];
|
||||
}
|
||||
|
||||
// Reset content size
|
||||
_contentSize = CGSizeZero;
|
||||
}
|
||||
|
||||
- (NSAttributedString*)attributedTextMessage
|
||||
{
|
||||
if (self.hasAttributedTextMessage && !attributedTextMessage.length)
|
||||
{
|
||||
// By default only one component is supported, consider here the first component
|
||||
MXKRoomBubbleComponent *firstComponent = [self getFirstBubbleComponent];
|
||||
|
||||
if (firstComponent)
|
||||
{
|
||||
attributedTextMessage = firstComponent.attributedTextMessage;
|
||||
|
||||
if (attributedTextMessage.length && highlightedPattern)
|
||||
{
|
||||
[self highlightPattern];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attributedTextMessage;
|
||||
}
|
||||
|
||||
- (BOOL)hasAttributedTextMessage
|
||||
{
|
||||
// Determine if the event formatter will return at least one string for the events in this cell.
|
||||
// No string means that the event formatter has been configured so that it did not accept all events
|
||||
// of the cell.
|
||||
BOOL hasAttributedTextMessage = NO;
|
||||
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
for (MXKRoomBubbleComponent *roomBubbleComponent in bubbleComponents)
|
||||
{
|
||||
if (roomBubbleComponent.attributedTextMessage)
|
||||
{
|
||||
hasAttributedTextMessage = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasAttributedTextMessage;
|
||||
}
|
||||
|
||||
- (BOOL)hasLink
|
||||
{
|
||||
@synchronized (bubbleComponents) {
|
||||
for (MXKRoomBubbleComponent *component in bubbleComponents)
|
||||
{
|
||||
if (component.link)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (MXKRoomBubbleComponentDisplayFix)displayFix
|
||||
{
|
||||
MXKRoomBubbleComponentDisplayFix displayFix = MXKRoomBubbleComponentDisplayFixNone;
|
||||
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
for (MXKRoomBubbleComponent *component in self.bubbleComponents)
|
||||
{
|
||||
displayFix |= component.displayFix;
|
||||
}
|
||||
}
|
||||
return displayFix;
|
||||
}
|
||||
|
||||
- (BOOL)shouldHideSenderName
|
||||
{
|
||||
BOOL res = NO;
|
||||
|
||||
MXKRoomBubbleComponent *firstDisplayedComponent = [self getFirstBubbleComponentWithDisplay];
|
||||
NSString *senderDisplayName = self.senderDisplayName;
|
||||
|
||||
if (firstDisplayedComponent)
|
||||
{
|
||||
res = (firstDisplayedComponent.event.isEmote || (firstDisplayedComponent.event.isState && senderDisplayName && [firstDisplayedComponent.textMessage hasPrefix:senderDisplayName]));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
- (NSArray*)events
|
||||
{
|
||||
NSMutableArray* eventsArray;
|
||||
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
eventsArray = [NSMutableArray arrayWithCapacity:bubbleComponents.count];
|
||||
for (MXKRoomBubbleComponent *roomBubbleComponent in bubbleComponents)
|
||||
{
|
||||
if (roomBubbleComponent.event)
|
||||
{
|
||||
[eventsArray addObject:roomBubbleComponent.event];
|
||||
}
|
||||
}
|
||||
}
|
||||
return eventsArray;
|
||||
}
|
||||
|
||||
- (NSDate*)date
|
||||
{
|
||||
MXKRoomBubbleComponent *firstDisplayedComponent = [self getFirstBubbleComponentWithDisplay];
|
||||
|
||||
if (firstDisplayedComponent)
|
||||
{
|
||||
return firstDisplayedComponent.date;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)hasNoDisplay
|
||||
{
|
||||
BOOL noDisplay = YES;
|
||||
|
||||
// Check whether at least one component has a string description.
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
if (self.collapsed)
|
||||
{
|
||||
// Collapsed cells have no display except their cell header
|
||||
noDisplay = !self.collapsedAttributedTextMessage;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (MXKRoomBubbleComponent *roomBubbleComponent in bubbleComponents)
|
||||
{
|
||||
if (roomBubbleComponent.attributedTextMessage)
|
||||
{
|
||||
noDisplay = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (noDisplay && !attachment);
|
||||
}
|
||||
|
||||
- (BOOL)isAttachmentWithThumbnail
|
||||
{
|
||||
return (attachment && (attachment.type == MXKAttachmentTypeImage || attachment.type == MXKAttachmentTypeVideo || attachment.type == MXKAttachmentTypeSticker));
|
||||
}
|
||||
|
||||
- (BOOL)isAttachmentWithIcon
|
||||
{
|
||||
// Not supported yet (TODO for audio, file).
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)setMaxTextViewWidth:(CGFloat)inMaxTextViewWidth
|
||||
{
|
||||
// Check change
|
||||
if (inMaxTextViewWidth != _maxTextViewWidth)
|
||||
{
|
||||
_maxTextViewWidth = inMaxTextViewWidth;
|
||||
// Reset content size
|
||||
_contentSize = CGSizeZero;
|
||||
}
|
||||
}
|
||||
|
||||
- (CGSize)contentSize
|
||||
{
|
||||
if (CGSizeEqualToSize(_contentSize, CGSizeZero))
|
||||
{
|
||||
if (attachment == nil)
|
||||
{
|
||||
// Here the bubble is a text message
|
||||
if ([NSThread currentThread] != [NSThread mainThread])
|
||||
{
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
self->_contentSize = [self textContentSize:self.attributedTextMessage removeVerticalInset:NO];
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_contentSize = [self textContentSize:self.attributedTextMessage removeVerticalInset:NO];
|
||||
}
|
||||
}
|
||||
else if (self.isAttachmentWithThumbnail)
|
||||
{
|
||||
CGFloat width, height;
|
||||
|
||||
// Set default content size
|
||||
width = height = MXKROOMBUBBLECELLDATA_MAX_ATTACHMENTVIEW_WIDTH;
|
||||
|
||||
if (attachment.thumbnailInfo || attachment.contentInfo)
|
||||
{
|
||||
if (attachment.thumbnailInfo && attachment.thumbnailInfo[@"w"] && attachment.thumbnailInfo[@"h"])
|
||||
{
|
||||
width = [attachment.thumbnailInfo[@"w"] integerValue];
|
||||
height = [attachment.thumbnailInfo[@"h"] integerValue];
|
||||
}
|
||||
else if (attachment.contentInfo[@"w"] && attachment.contentInfo[@"h"])
|
||||
{
|
||||
width = [attachment.contentInfo[@"w"] integerValue];
|
||||
height = [attachment.contentInfo[@"h"] integerValue];
|
||||
}
|
||||
|
||||
if (width > MXKROOMBUBBLECELLDATA_MAX_ATTACHMENTVIEW_WIDTH || height > MXKROOMBUBBLECELLDATA_MAX_ATTACHMENTVIEW_WIDTH)
|
||||
{
|
||||
if (width > height)
|
||||
{
|
||||
height = (height * MXKROOMBUBBLECELLDATA_MAX_ATTACHMENTVIEW_WIDTH) / width;
|
||||
height = floorf(height / 2) * 2;
|
||||
width = MXKROOMBUBBLECELLDATA_MAX_ATTACHMENTVIEW_WIDTH;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = (width * MXKROOMBUBBLECELLDATA_MAX_ATTACHMENTVIEW_WIDTH) / height;
|
||||
width = floorf(width / 2) * 2;
|
||||
height = MXKROOMBUBBLECELLDATA_MAX_ATTACHMENTVIEW_WIDTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check here thumbnail orientation
|
||||
if (attachment.thumbnailOrientation == UIImageOrientationLeft || attachment.thumbnailOrientation == UIImageOrientationRight)
|
||||
{
|
||||
_contentSize = CGSizeMake(height, width);
|
||||
}
|
||||
else
|
||||
{
|
||||
_contentSize = CGSizeMake(width, height);
|
||||
}
|
||||
}
|
||||
else if (attachment.type == MXKAttachmentTypeFile || attachment.type == MXKAttachmentTypeAudio)
|
||||
{
|
||||
// Presently we displayed only the file name for attached file (no icon yet)
|
||||
// Return suitable content size of a text view to display the file name (available in text message).
|
||||
if ([NSThread currentThread] != [NSThread mainThread])
|
||||
{
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
self->_contentSize = [self textContentSize:self.attributedTextMessage removeVerticalInset:NO];
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_contentSize = [self textContentSize:self.attributedTextMessage removeVerticalInset:NO];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_contentSize = CGSizeMake(40, 40);
|
||||
}
|
||||
}
|
||||
return _contentSize;
|
||||
}
|
||||
|
||||
- (MXKEventFormatter *)eventFormatter
|
||||
{
|
||||
MXKRoomBubbleComponent *firstComponent = [bubbleComponents firstObject];
|
||||
|
||||
// Retrieve event formatter from the first component
|
||||
if (firstComponent)
|
||||
{
|
||||
return firstComponent.eventFormatter;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)showAntivirusScanStatus
|
||||
{
|
||||
MXKRoomBubbleComponent *firstBubbleComponent = self.bubbleComponents.firstObject;
|
||||
|
||||
if (self.attachment == nil || firstBubbleComponent == nil)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
MXEventScan *eventScan = firstBubbleComponent.eventScan;
|
||||
|
||||
return eventScan != nil && eventScan.antivirusScanStatus != MXAntivirusScanStatusTrusted;
|
||||
}
|
||||
|
||||
- (BOOL)containsBubbleComponentWithEncryptionBadge
|
||||
{
|
||||
BOOL containsBubbleComponentWithEncryptionBadge = NO;
|
||||
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
for (MXKRoomBubbleComponent *component in bubbleComponents)
|
||||
{
|
||||
if (component.showEncryptionBadge)
|
||||
{
|
||||
containsBubbleComponentWithEncryptionBadge = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return containsBubbleComponentWithEncryptionBadge;
|
||||
}
|
||||
|
||||
#pragma mark - Bubble collapsing
|
||||
|
||||
- (BOOL)collapseWith:(id<MXKRoomBubbleCellDataStoring>)cellData
|
||||
{
|
||||
// NO by default
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Internals
|
||||
|
||||
- (void)highlightPattern
|
||||
{
|
||||
NSMutableAttributedString *customAttributedTextMsg = nil;
|
||||
|
||||
NSString *currentTextMessage = self.textMessage;
|
||||
NSRange range = [currentTextMessage rangeOfString:highlightedPattern options:NSCaseInsensitiveSearch];
|
||||
|
||||
if (range.location != NSNotFound)
|
||||
{
|
||||
customAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedTextMessage];
|
||||
|
||||
while (range.location != NSNotFound)
|
||||
{
|
||||
if (highlightedPatternColor)
|
||||
{
|
||||
// Update text color
|
||||
[customAttributedTextMsg addAttribute:NSForegroundColorAttributeName value:highlightedPatternColor range:range];
|
||||
}
|
||||
|
||||
if (highlightedPatternFont)
|
||||
{
|
||||
// Update text font
|
||||
[customAttributedTextMsg addAttribute:NSFontAttributeName value:highlightedPatternFont range:range];
|
||||
}
|
||||
|
||||
// Look for the next pattern occurrence
|
||||
range.location += range.length;
|
||||
if (range.location < currentTextMessage.length)
|
||||
{
|
||||
range.length = currentTextMessage.length - range.location;
|
||||
range = [currentTextMessage rangeOfString:highlightedPattern options:NSCaseInsensitiveSearch range:range];
|
||||
}
|
||||
else
|
||||
{
|
||||
range.location = NSNotFound;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (customAttributedTextMsg)
|
||||
{
|
||||
// Update resulting message body
|
||||
attributedTextMessage = customAttributedTextMsg;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMXSessionUpdatePublicisedGroupsForUsers:(NSNotification *)notif
|
||||
{
|
||||
// Retrieved the list of the concerned users
|
||||
NSArray<NSString*> *userIds = notif.userInfo[kMXSessionNotificationUserIdsArrayKey];
|
||||
if (userIds.count && self.senderId)
|
||||
{
|
||||
// Check whether the current sender is concerned.
|
||||
if ([userIds indexOfObject:self.senderId] != NSNotFound)
|
||||
{
|
||||
[self refreshSenderFlair];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MatrixSDK/MatrixSDK.h>
|
||||
|
||||
#import "MXKRoomDataSource.h"
|
||||
|
||||
#import "MXKAttachment.h"
|
||||
|
||||
#import "MXEvent+MatrixKit.h"
|
||||
|
||||
@class MXKRoomDataSource;
|
||||
/**
|
||||
`MXKRoomBubbleCellDataStoring` defines a protocol a class must conform in order to store MXKRoomBubble cell data
|
||||
managed by `MXKRoomDataSource`.
|
||||
*/
|
||||
@protocol MXKRoomBubbleCellDataStoring <NSObject>
|
||||
|
||||
#pragma mark - Data displayed by a room bubble cell
|
||||
|
||||
/**
|
||||
The sender Id
|
||||
*/
|
||||
@property (nonatomic) NSString *senderId;
|
||||
|
||||
/**
|
||||
The target Id (may be nil)
|
||||
|
||||
@discussion "target" refers to the room member who is the target of this event (if any), e.g.
|
||||
the invitee, the person being banned, etc.
|
||||
*/
|
||||
@property (nonatomic) NSString *targetId;
|
||||
|
||||
/**
|
||||
The room id
|
||||
*/
|
||||
@property (nonatomic) NSString *roomId;
|
||||
|
||||
/**
|
||||
The sender display name composed when event occured
|
||||
*/
|
||||
@property (nonatomic) NSString *senderDisplayName;
|
||||
|
||||
/**
|
||||
The sender avatar url retrieved when event occured
|
||||
*/
|
||||
@property (nonatomic) NSString *senderAvatarUrl;
|
||||
|
||||
/**
|
||||
The sender avatar placeholder (may be nil) - Used when url is nil, or during avatar download.
|
||||
*/
|
||||
@property (nonatomic) UIImage *senderAvatarPlaceholder;
|
||||
|
||||
/**
|
||||
The target display name composed when event occured (may be nil)
|
||||
|
||||
@discussion "target" refers to the room member who is the target of this event (if any), e.g.
|
||||
the invitee, the person being banned, etc.
|
||||
*/
|
||||
@property (nonatomic) NSString *targetDisplayName;
|
||||
|
||||
/**
|
||||
The target avatar url retrieved when event occured (may be nil)
|
||||
|
||||
@discussion "target" refers to the room member who is the target of this event (if any), e.g.
|
||||
the invitee, the person being banned, etc.
|
||||
*/
|
||||
@property (nonatomic) NSString *targetAvatarUrl;
|
||||
|
||||
/**
|
||||
The target avatar placeholder (may be nil) - Used when url is nil, or during avatar download.
|
||||
|
||||
@discussion "target" refers to the room member who is the target of this event (if any), e.g.
|
||||
the invitee, the person being banned, etc.
|
||||
*/
|
||||
@property (nonatomic) UIImage *targetAvatarPlaceholder;
|
||||
|
||||
/**
|
||||
The current sender flair (list of the publicised groups in the sender profile which matches the room flair settings)
|
||||
*/
|
||||
@property (nonatomic) NSArray<MXGroup*> *senderFlair;
|
||||
|
||||
/**
|
||||
Tell whether the room is encrypted.
|
||||
*/
|
||||
@property (nonatomic) BOOL isEncryptedRoom;
|
||||
|
||||
/**
|
||||
Tell whether a new pagination starts with this bubble.
|
||||
*/
|
||||
@property (nonatomic) BOOL isPaginationFirstBubble;
|
||||
|
||||
/**
|
||||
Tell whether the sender information is relevant for this bubble
|
||||
(For example this information should be hidden in case of 2 consecutive bubbles from the same sender).
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldHideSenderInformation;
|
||||
|
||||
/**
|
||||
Tell whether this bubble has nothing to display (neither a message nor an attachment).
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL hasNoDisplay;
|
||||
|
||||
/**
|
||||
The list of events (`MXEvent` instances) handled by this bubble.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<MXEvent*> *events;
|
||||
|
||||
/**
|
||||
The bubble attachment (if any).
|
||||
*/
|
||||
@property (nonatomic) MXKAttachment *attachment;
|
||||
|
||||
/**
|
||||
The bubble date
|
||||
*/
|
||||
@property (nonatomic) NSDate *date;
|
||||
|
||||
/**
|
||||
YES when the bubble is composed by incoming event(s).
|
||||
*/
|
||||
@property (nonatomic) BOOL isIncoming;
|
||||
|
||||
/**
|
||||
YES when the bubble correspond to an attachment displayed with a thumbnail (see image, video).
|
||||
*/
|
||||
@property (nonatomic) BOOL isAttachmentWithThumbnail;
|
||||
|
||||
/**
|
||||
YES when the bubble correspond to an attachment displayed with an icon (audio, file...).
|
||||
*/
|
||||
@property (nonatomic) BOOL isAttachmentWithIcon;
|
||||
|
||||
/**
|
||||
Flag that indicates that self.attributedTextMessage will be not nil.
|
||||
This avoids the computation of self.attributedTextMessage that can take time.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL hasAttributedTextMessage;
|
||||
|
||||
/**
|
||||
The body of the message with sets of attributes, or kind of content description in case of attachment (e.g. "image attachment")
|
||||
*/
|
||||
@property (nonatomic) NSAttributedString *attributedTextMessage;
|
||||
|
||||
/**
|
||||
The raw text message (without attributes)
|
||||
*/
|
||||
@property (nonatomic) NSString *textMessage;
|
||||
|
||||
/**
|
||||
Tell whether the sender's name is relevant or not for this bubble.
|
||||
Return YES if the first component of the bubble message corresponds to an emote, or a state event in which
|
||||
the sender's name appears at the beginning of the message text (for example membership events).
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldHideSenderName;
|
||||
|
||||
/**
|
||||
YES if the sender is currently typing in the current room
|
||||
*/
|
||||
@property (nonatomic) BOOL isTyping;
|
||||
|
||||
/**
|
||||
Show the date time label in rendered bubble cell. NO by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL showBubbleDateTime;
|
||||
|
||||
/**
|
||||
A Boolean value that determines whether the date time labels are customized (By default date time display is handled by MatrixKit). NO by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL useCustomDateTimeLabel;
|
||||
|
||||
/**
|
||||
Show the receipts in rendered bubble cell. YES by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL showBubbleReceipts;
|
||||
|
||||
/**
|
||||
A Boolean value that determines whether the read receipts are customized (By default read receipts display is handled by MatrixKit). NO by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL useCustomReceipts;
|
||||
|
||||
/**
|
||||
A Boolean value that determines whether the unsent button is customized (By default an 'Unsent' button is displayed by MatrixKit in front of unsent events). NO by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL useCustomUnsentButton;
|
||||
|
||||
/**
|
||||
An integer that you can use to identify cell data in your application.
|
||||
The default value is 0. You can set the value of this tag and use that value to identify the cell data later.
|
||||
*/
|
||||
@property (nonatomic) NSInteger tag;
|
||||
|
||||
/**
|
||||
Indicate if antivirus scan status should be shown.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL showAntivirusScanStatus;
|
||||
|
||||
#pragma mark - Public methods
|
||||
/**
|
||||
Create a new `MXKRoomBubbleCellDataStoring` object for a new bubble cell.
|
||||
|
||||
@param event the event to be displayed in the cell.
|
||||
@param roomState the room state when the event occured.
|
||||
@param roomDataSource the `MXKRoomDataSource` object that will use this instance.
|
||||
@return the newly created instance.
|
||||
*/
|
||||
- (instancetype)initWithEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState andRoomDataSource:(MXKRoomDataSource*)roomDataSource;
|
||||
|
||||
/**
|
||||
Update the event because its sent state changed or it is has been redacted.
|
||||
|
||||
@param eventId the id of the event to change.
|
||||
@param event the new event data
|
||||
@return the number of events hosting by the object after the update.
|
||||
*/
|
||||
- (NSUInteger)updateEvent:(NSString*)eventId withEvent:(MXEvent*)event;
|
||||
|
||||
/**
|
||||
Remove the event from the `MXKRoomBubbleCellDataStoring` object.
|
||||
|
||||
@param eventId the id of the event to remove.
|
||||
@return the number of events still hosting by the object after the removal
|
||||
*/
|
||||
- (NSUInteger)removeEvent:(NSString*)eventId;
|
||||
|
||||
/**
|
||||
Remove the passed event and all events after it.
|
||||
|
||||
@param eventId the id of the event where to start removing.
|
||||
@param removedEvents removedEvents will contain the list of removed events.
|
||||
@return the number of events still hosting by the object after the removal.
|
||||
*/
|
||||
- (NSUInteger)removeEventsFromEvent:(NSString*)eventId removedEvents:(NSArray<MXEvent*>**)removedEvents;
|
||||
|
||||
/**
|
||||
Check if the receiver has the same sender as another bubble.
|
||||
|
||||
@param bubbleCellData an object conforms to `MXKRoomBubbleCellDataStoring` protocol.
|
||||
@return YES if the receiver has the same sender as the provided bubble
|
||||
*/
|
||||
- (BOOL)hasSameSenderAsBubbleCellData:(id<MXKRoomBubbleCellDataStoring>)bubbleCellData;
|
||||
|
||||
/**
|
||||
Highlight text message of an event in the resulting message body.
|
||||
|
||||
@param eventId the id of the event to highlight.
|
||||
@param tintColor optional tint color
|
||||
@return The body of the message by highlighting the content related to the provided event id
|
||||
*/
|
||||
- (NSAttributedString*)attributedTextMessageWithHighlightedEvent:(NSString*)eventId tintColor:(UIColor*)tintColor;
|
||||
|
||||
/**
|
||||
Highlight all the occurrences of a pattern in the resulting message body 'attributedTextMessage'.
|
||||
|
||||
@param pattern the text pattern to highlight.
|
||||
@param patternColor optional text color (the pattern text color is unchanged if nil).
|
||||
@param patternFont optional text font (the pattern font is unchanged if nil).
|
||||
*/
|
||||
- (void)highlightPatternInTextMessage:(NSString*)pattern withForegroundColor:(UIColor*)patternColor andFont:(UIFont*)patternFont;
|
||||
|
||||
/**
|
||||
Refresh the sender flair information
|
||||
*/
|
||||
- (void)refreshSenderFlair;
|
||||
|
||||
/**
|
||||
Indicate that the current text message layout is no longer valid and should be recomputed
|
||||
before presentation in a bubble cell. This could be due to the content changing, or the
|
||||
available space for the cell has been updated.
|
||||
*/
|
||||
- (void)invalidateTextLayout;
|
||||
|
||||
#pragma mark - Bubble collapsing
|
||||
|
||||
/**
|
||||
A Boolean value that indicates if the cell is collapsable.
|
||||
*/
|
||||
@property (nonatomic) BOOL collapsable;
|
||||
|
||||
/**
|
||||
A Boolean value that indicates if the cell and its series is collapsed.
|
||||
*/
|
||||
@property (nonatomic) BOOL collapsed;
|
||||
|
||||
/**
|
||||
The attributed string to display when the collapsable cells series is collapsed.
|
||||
It is not nil only for the start cell of the cells series.
|
||||
*/
|
||||
@property (nonatomic) NSAttributedString *collapsedAttributedTextMessage;
|
||||
|
||||
/**
|
||||
Bidirectional linked list of cells that can be collapsed together.
|
||||
If prevCollapsableCellData is nil, this cell data instance is the data of the start
|
||||
cell of the collapsable cells series.
|
||||
*/
|
||||
@property (nonatomic) id<MXKRoomBubbleCellDataStoring> prevCollapsableCellData;
|
||||
@property (nonatomic) id<MXKRoomBubbleCellDataStoring> nextCollapsableCellData;
|
||||
|
||||
/**
|
||||
The room state to use for computing or updating the data to display for the series when it is
|
||||
collapsed.
|
||||
It is not nil only for the start cell of the cells series.
|
||||
*/
|
||||
@property (nonatomic) MXRoomState *collapseState;
|
||||
|
||||
/**
|
||||
Check whether the two cells can be collapsable together.
|
||||
|
||||
@return YES if YES.
|
||||
*/
|
||||
- (BOOL)collapseWith:(id<MXKRoomBubbleCellDataStoring>)cellData;
|
||||
|
||||
@optional
|
||||
/**
|
||||
Attempt to add a new event to the bubble.
|
||||
|
||||
@param event the event to be displayed in the cell.
|
||||
@param roomState the room state when the event occured.
|
||||
@return YES if the model accepts that the event can concatenated to events already in the bubble.
|
||||
*/
|
||||
- (BOOL)addEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState;
|
||||
|
||||
/**
|
||||
The receiver appends to its content the provided bubble cell data, if both have the same sender.
|
||||
|
||||
@param bubbleCellData an object conforms to `MXKRoomBubbleCellDataStoring` protocol.
|
||||
@return YES if the provided cell data has been merged into receiver.
|
||||
*/
|
||||
- (BOOL)mergeWithBubbleCellData:(id<MXKRoomBubbleCellDataStoring>)bubbleCellData;
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKRoomBubbleCellData.h"
|
||||
|
||||
/**
|
||||
`MXKRoomBubbleCellDataWithAppendingMode` class inherits from `MXKRoomBubbleCellData`, it merges
|
||||
consecutive events from the same sender into one bubble.
|
||||
Each concatenated event is represented by a bubble component.
|
||||
*/
|
||||
@interface MXKRoomBubbleCellDataWithAppendingMode : MXKRoomBubbleCellData
|
||||
{
|
||||
@protected
|
||||
/**
|
||||
YES if position of each component must be refreshed
|
||||
*/
|
||||
BOOL shouldUpdateComponentsPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
The string appended to the current message before adding a new component text.
|
||||
*/
|
||||
+ (NSAttributedString *)messageSeparator;
|
||||
|
||||
/**
|
||||
The maximum number of components in each bubble. Default is 10.
|
||||
We limit the number of components to reduce the computation time required during bubble handling.
|
||||
Indeed some process like [prepareBubbleComponentsPosition] is time consuming.
|
||||
*/
|
||||
@property (nonatomic) NSUInteger maxComponentCount;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKRoomBubbleCellDataWithAppendingMode.h"
|
||||
|
||||
static NSAttributedString *messageSeparator = nil;
|
||||
|
||||
@implementation MXKRoomBubbleCellDataWithAppendingMode
|
||||
|
||||
#pragma mark - MXKRoomBubbleCellDataStoring
|
||||
|
||||
- (instancetype)initWithEvent:(MXEvent *)event andRoomState:(MXRoomState *)roomState andRoomDataSource:(MXKRoomDataSource *)roomDataSource2
|
||||
{
|
||||
self = [super initWithEvent:event andRoomState:roomState andRoomDataSource:roomDataSource2];
|
||||
if (self)
|
||||
{
|
||||
// Set default settings
|
||||
self.maxComponentCount = 10;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)addEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState
|
||||
{
|
||||
// We group together text messages from the same user (attachments are not merged).
|
||||
if ([event.sender isEqualToString:self.senderId] && (self.attachment == nil) && (self.bubbleComponents.count < self.maxComponentCount))
|
||||
{
|
||||
// Attachments (image, video, sticker ...) cannot be added here
|
||||
if ([roomDataSource.eventFormatter isSupportedAttachment:event])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Check sender information
|
||||
NSString *eventSenderName = [roomDataSource.eventFormatter senderDisplayNameForEvent:event withRoomState:roomState];
|
||||
NSString *eventSenderAvatar = [roomDataSource.eventFormatter senderAvatarUrlForEvent:event withRoomState:roomState];
|
||||
if ((self.senderDisplayName || eventSenderName) &&
|
||||
([self.senderDisplayName isEqualToString:eventSenderName] == NO))
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
if ((self.senderAvatarUrl || eventSenderAvatar) &&
|
||||
([self.senderAvatarUrl isEqualToString:eventSenderAvatar] == NO))
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Take into account here the rendered bubbles pagination
|
||||
if (roomDataSource.bubblesPagination == MXKRoomDataSourceBubblesPaginationPerDay)
|
||||
{
|
||||
// Event must be sent the same day than the existing bubble.
|
||||
NSString *bubbleDateString = [roomDataSource.eventFormatter dateStringFromDate:self.date withTime:NO];
|
||||
NSString *eventDateString = [roomDataSource.eventFormatter dateStringFromEvent:event withTime:NO];
|
||||
if (bubbleDateString && eventDateString && ![bubbleDateString isEqualToString:eventDateString])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Create new message component
|
||||
MXKRoomBubbleComponent *addedComponent = [[MXKRoomBubbleComponent alloc] initWithEvent:event roomState:roomState eventFormatter:roomDataSource.eventFormatter session:self.mxSession];
|
||||
if (addedComponent)
|
||||
{
|
||||
[self addComponent:addedComponent];
|
||||
}
|
||||
// else the event is ignored, we consider it as handled
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)mergeWithBubbleCellData:(id<MXKRoomBubbleCellDataStoring>)bubbleCellData
|
||||
{
|
||||
if ([self hasSameSenderAsBubbleCellData:bubbleCellData])
|
||||
{
|
||||
MXKRoomBubbleCellData *cellData = (MXKRoomBubbleCellData*)bubbleCellData;
|
||||
// Only text messages are merged (Attachments are not merged).
|
||||
if ((self.attachment == nil) && (cellData.attachment == nil))
|
||||
{
|
||||
// Take into account here the rendered bubbles pagination
|
||||
if (roomDataSource.bubblesPagination == MXKRoomDataSourceBubblesPaginationPerDay)
|
||||
{
|
||||
// bubble components must be sent the same day than self.
|
||||
NSString *selfDateString = [roomDataSource.eventFormatter dateStringFromDate:self.date withTime:NO];
|
||||
NSString *bubbleDateString = [roomDataSource.eventFormatter dateStringFromDate:bubbleCellData.date withTime:NO];
|
||||
if (![bubbleDateString isEqualToString:selfDateString])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Add all components of the provided message
|
||||
for (MXKRoomBubbleComponent* component in cellData.bubbleComponents)
|
||||
{
|
||||
[self addComponent:component];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSAttributedString*)attributedTextMessageWithHighlightedEvent:(NSString*)eventId tintColor:(UIColor*)tintColor
|
||||
{
|
||||
// Create attributed string
|
||||
NSMutableAttributedString *customAttributedTextMsg;
|
||||
NSAttributedString *componentString;
|
||||
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
for (MXKRoomBubbleComponent* component in bubbleComponents)
|
||||
{
|
||||
componentString = component.attributedTextMessage;
|
||||
|
||||
if (componentString)
|
||||
{
|
||||
if ([component.event.eventId isEqualToString:eventId])
|
||||
{
|
||||
NSMutableAttributedString *customComponentString = [[NSMutableAttributedString alloc] initWithAttributedString:componentString];
|
||||
UIColor *color = tintColor ? tintColor : [UIColor lightGrayColor];
|
||||
[customComponentString addAttribute:NSBackgroundColorAttributeName value:color range:NSMakeRange(0, customComponentString.length)];
|
||||
componentString = customComponentString;
|
||||
}
|
||||
|
||||
if (!customAttributedTextMsg)
|
||||
{
|
||||
customAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:componentString];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Append attributed text
|
||||
[customAttributedTextMsg appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
|
||||
[customAttributedTextMsg appendAttributedString:componentString];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return customAttributedTextMsg;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)prepareBubbleComponentsPosition
|
||||
{
|
||||
// Set position of the first component
|
||||
[super prepareBubbleComponentsPosition];
|
||||
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
// Check whether the position of other components need to be refreshed
|
||||
if (!self.attachment && shouldUpdateComponentsPosition && bubbleComponents.count > 1)
|
||||
{
|
||||
// Init attributed string with the first text component not nil.
|
||||
MXKRoomBubbleComponent *component = bubbleComponents.firstObject;
|
||||
CGFloat positionY = component.position.y;
|
||||
NSMutableAttributedString *attributedString;
|
||||
NSUInteger index = 0;
|
||||
|
||||
for (; index < bubbleComponents.count; index++)
|
||||
{
|
||||
component = [bubbleComponents objectAtIndex:index];
|
||||
|
||||
component.position = CGPointMake(0, positionY);
|
||||
|
||||
if (component.attributedTextMessage)
|
||||
{
|
||||
attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:component.attributedTextMessage];
|
||||
[attributedString appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (index++; index < bubbleComponents.count; index++)
|
||||
{
|
||||
// Append the next text component
|
||||
component = [bubbleComponents objectAtIndex:index];
|
||||
|
||||
if (component.attributedTextMessage)
|
||||
{
|
||||
[attributedString appendAttributedString:component.attributedTextMessage];
|
||||
|
||||
// Compute the height of the resulting string
|
||||
CGFloat cumulatedHeight = [self rawTextHeight:attributedString];
|
||||
|
||||
// Deduce the position of the beginning of this component
|
||||
CGFloat positionY = MXKROOMBUBBLECELLDATA_TEXTVIEW_DEFAULT_VERTICAL_INSET + (cumulatedHeight - [self rawTextHeight:component.attributedTextMessage]);
|
||||
|
||||
component.position = CGPointMake(0, positionY);
|
||||
|
||||
[attributedString appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Apply the current vertical position on this empty component.
|
||||
component.position = CGPointMake(0, positionY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shouldUpdateComponentsPosition = NO;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSString*)textMessage
|
||||
{
|
||||
NSString *rawText = nil;
|
||||
|
||||
if (self.attributedTextMessage)
|
||||
{
|
||||
// Append all components text message
|
||||
NSMutableString *currentTextMsg;
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
for (MXKRoomBubbleComponent* component in bubbleComponents)
|
||||
{
|
||||
if (component.textMessage == nil)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!currentTextMsg)
|
||||
{
|
||||
currentTextMsg = [NSMutableString stringWithString:component.textMessage];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Append text message
|
||||
[currentTextMsg appendString:@"\n"];
|
||||
[currentTextMsg appendString:component.textMessage];
|
||||
}
|
||||
}
|
||||
}
|
||||
rawText = currentTextMsg;
|
||||
}
|
||||
|
||||
return rawText;
|
||||
}
|
||||
|
||||
- (void)setAttributedTextMessage:(NSAttributedString *)inAttributedTextMessage
|
||||
{
|
||||
super.attributedTextMessage = inAttributedTextMessage;
|
||||
|
||||
// Position of each components should be computed again
|
||||
shouldUpdateComponentsPosition = YES;
|
||||
}
|
||||
|
||||
- (NSAttributedString*)attributedTextMessage
|
||||
{
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
if (self.hasAttributedTextMessage && !attributedTextMessage.length)
|
||||
{
|
||||
// Create attributed string
|
||||
NSMutableAttributedString *currentAttributedTextMsg;
|
||||
|
||||
for (MXKRoomBubbleComponent* component in bubbleComponents)
|
||||
{
|
||||
if (component.attributedTextMessage)
|
||||
{
|
||||
if (!currentAttributedTextMsg)
|
||||
{
|
||||
currentAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:component.attributedTextMessage];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Append attributed text
|
||||
[currentAttributedTextMsg appendAttributedString:[MXKRoomBubbleCellDataWithAppendingMode messageSeparator]];
|
||||
[currentAttributedTextMsg appendAttributedString:component.attributedTextMessage];
|
||||
}
|
||||
}
|
||||
}
|
||||
self.attributedTextMessage = currentAttributedTextMsg;
|
||||
}
|
||||
}
|
||||
|
||||
return attributedTextMessage;
|
||||
}
|
||||
|
||||
- (void)setMaxTextViewWidth:(CGFloat)inMaxTextViewWidth
|
||||
{
|
||||
CGFloat previousMaxWidth = self.maxTextViewWidth;
|
||||
|
||||
[super setMaxTextViewWidth:inMaxTextViewWidth];
|
||||
|
||||
// Check change
|
||||
if (previousMaxWidth != self.maxTextViewWidth)
|
||||
{
|
||||
// Position of each components should be computed again
|
||||
shouldUpdateComponentsPosition = YES;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (NSAttributedString *)messageSeparator
|
||||
{
|
||||
@synchronized(self)
|
||||
{
|
||||
if(messageSeparator == nil)
|
||||
{
|
||||
messageSeparator = [[NSAttributedString alloc] initWithString:@"\n\n" attributes:@{NSForegroundColorAttributeName : [UIColor blackColor],
|
||||
NSFontAttributeName: [UIFont systemFontOfSize:4]}];
|
||||
}
|
||||
}
|
||||
return messageSeparator;
|
||||
}
|
||||
|
||||
#pragma mark - Privates
|
||||
|
||||
- (void)addComponent:(MXKRoomBubbleComponent*)addedComponent
|
||||
{
|
||||
@synchronized(bubbleComponents)
|
||||
{
|
||||
// Check date of existing components to insert this new one
|
||||
NSUInteger index = bubbleComponents.count;
|
||||
|
||||
// Component without date is added at the end by default
|
||||
if (addedComponent.date)
|
||||
{
|
||||
while (index)
|
||||
{
|
||||
MXKRoomBubbleComponent *msgComponent = [bubbleComponents objectAtIndex:(--index)];
|
||||
if (msgComponent.date && [msgComponent.date compare:addedComponent.date] != NSOrderedDescending)
|
||||
{
|
||||
// New component will be inserted here
|
||||
index ++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert new component
|
||||
[bubbleComponents insertObject:addedComponent atIndex:index];
|
||||
|
||||
// Indicate that the data's text message layout should be recomputed.
|
||||
[self invalidateTextLayout];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKRoomBubbleCellDataWithAppendingMode.h"
|
||||
|
||||
/**
|
||||
`MXKRoomBubbleCellDataWithIncomingAppendingMode` class inherits from `MXKRoomBubbleCellDataWithAppendingMode`,
|
||||
only the incoming message cells are merged.
|
||||
*/
|
||||
@interface MXKRoomBubbleCellDataWithIncomingAppendingMode : MXKRoomBubbleCellDataWithAppendingMode
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKRoomBubbleCellDataWithIncomingAppendingMode.h"
|
||||
|
||||
@implementation MXKRoomBubbleCellDataWithIncomingAppendingMode
|
||||
|
||||
#pragma mark - MXKRoomBubbleCellDataStoring
|
||||
|
||||
- (BOOL)addEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState
|
||||
{
|
||||
// Do not merge outgoing events
|
||||
if ([event.sender isEqualToString:roomDataSource.mxSession.myUser.userId])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [super addEvent:event andRoomState:roomState];
|
||||
}
|
||||
|
||||
- (BOOL)mergeWithBubbleCellData:(id<MXKRoomBubbleCellDataStoring>)bubbleCellData
|
||||
{
|
||||
// Do not merge outgoing events
|
||||
if ([bubbleCellData.senderId isEqualToString:roomDataSource.mxSession.myUser.userId])
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [super mergeWithBubbleCellData:bubbleCellData];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <MatrixSDK/MatrixSDK.h>
|
||||
|
||||
#import "MXKEventFormatter.h"
|
||||
#import "MXKURLPreviewDataProtocol.h"
|
||||
|
||||
/**
|
||||
Flags to indicate if a fix is required at the display time.
|
||||
*/
|
||||
typedef enum : NSUInteger {
|
||||
|
||||
/**
|
||||
No fix required.
|
||||
*/
|
||||
MXKRoomBubbleComponentDisplayFixNone = 0,
|
||||
|
||||
/**
|
||||
Borders for HTML blockquotes need to be fixed.
|
||||
*/
|
||||
MXKRoomBubbleComponentDisplayFixHtmlBlockquote = 0x1
|
||||
|
||||
} MXKRoomBubbleComponentDisplayFix;
|
||||
|
||||
/**
|
||||
`MXKRoomBubbleComponent` class compose data related to one `MXEvent` instance.
|
||||
*/
|
||||
@interface MXKRoomBubbleComponent : NSObject
|
||||
|
||||
/**
|
||||
The body of the message, or kind of content description in case of attachment (e.g. "image attachment").
|
||||
*/
|
||||
@property (nonatomic) NSString *textMessage;
|
||||
|
||||
/**
|
||||
The `textMessage` with sets of attributes.
|
||||
*/
|
||||
@property (nonatomic) NSAttributedString *attributedTextMessage;
|
||||
|
||||
/**
|
||||
The event date
|
||||
*/
|
||||
@property (nonatomic) NSDate *date;
|
||||
|
||||
/**
|
||||
Event formatter
|
||||
*/
|
||||
@property (nonatomic) MXKEventFormatter *eventFormatter;
|
||||
|
||||
/**
|
||||
The event on which the component is based (used in case of redaction)
|
||||
*/
|
||||
@property (nonatomic, readonly) MXEvent *event;
|
||||
|
||||
// The following properties are defined to store information on component.
|
||||
// They must be handled by the object which creates the MXKRoomBubbleComponent instance.
|
||||
//@property (nonatomic) CGFloat height;
|
||||
@property (nonatomic) CGPoint position;
|
||||
|
||||
/**
|
||||
Set of flags indicating fixes that need to be applied at display time.
|
||||
*/
|
||||
@property (nonatomic) MXKRoomBubbleComponentDisplayFix displayFix;
|
||||
|
||||
/**
|
||||
The first link detected in the event's content, otherwise nil.
|
||||
*/
|
||||
@property (nonatomic) NSURL *link;
|
||||
|
||||
/**
|
||||
Any data necessary to show a URL preview.
|
||||
Note: MatrixKit is unable to display this data by itself.
|
||||
*/
|
||||
@property (nonatomic) id <MXKURLPreviewDataProtocol> urlPreviewData;
|
||||
|
||||
/**
|
||||
Whether a URL preview should be displayed for this cell.
|
||||
Note: MatrixKit is unable to display URL previews by itself.
|
||||
*/
|
||||
@property (nonatomic) BOOL showURLPreview;
|
||||
|
||||
/**
|
||||
Event antivirus scan. Present only if antivirus is enabled and event contains media.
|
||||
*/
|
||||
@property (nonatomic) MXEventScan *eventScan;
|
||||
|
||||
/**
|
||||
Indicate if an encryption badge should be shown.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL showEncryptionBadge;
|
||||
|
||||
/**
|
||||
Create a new `MXKRoomBubbleComponent` object based on a `MXEvent` instance.
|
||||
|
||||
@param event the event used to compose the bubble component.
|
||||
@param roomState the room state when the event occured.
|
||||
@param eventFormatter object used to format event into displayable string.
|
||||
@param session the related matrix session.
|
||||
@return the newly created instance.
|
||||
*/
|
||||
- (instancetype)initWithEvent:(MXEvent*)event roomState:(MXRoomState*)roomState eventFormatter:(MXKEventFormatter*)eventFormatter session:(MXSession*)session;
|
||||
|
||||
/**
|
||||
Update the event because its sent state changed or it is has been redacted.
|
||||
|
||||
@param event the new event data.
|
||||
@param roomState the up-to-date state of the room.
|
||||
@param session the related matrix session.
|
||||
*/
|
||||
- (void)updateWithEvent:(MXEvent*)event roomState:(MXRoomState*)roomState session:(MXSession*)session;
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKRoomBubbleComponent.h"
|
||||
|
||||
#import "MXEvent+MatrixKit.h"
|
||||
#import "MXKSwiftHeader.h"
|
||||
|
||||
@implementation MXKRoomBubbleComponent
|
||||
|
||||
- (instancetype)initWithEvent:(MXEvent*)event roomState:(MXRoomState*)roomState eventFormatter:(MXKEventFormatter*)eventFormatter session:(MXSession*)session;
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
// Build text component related to this event
|
||||
_eventFormatter = eventFormatter;
|
||||
MXKEventFormatterError error;
|
||||
|
||||
NSAttributedString *eventString = [_eventFormatter attributedStringFromEvent:event withRoomState:roomState error:&error];
|
||||
|
||||
// Store the potential error
|
||||
event.mxkEventFormatterError = error;
|
||||
|
||||
_textMessage = nil;
|
||||
_attributedTextMessage = eventString;
|
||||
|
||||
// Set date time
|
||||
if (event.originServerTs != kMXUndefinedTimestamp)
|
||||
{
|
||||
_date = [NSDate dateWithTimeIntervalSince1970:(double)event.originServerTs/1000];
|
||||
}
|
||||
else
|
||||
{
|
||||
_date = nil;
|
||||
}
|
||||
|
||||
// Keep ref on event (used to handle the read marker, or a potential event redaction).
|
||||
_event = event;
|
||||
|
||||
_displayFix = MXKRoomBubbleComponentDisplayFixNone;
|
||||
if ([event.content[@"format"] isEqualToString:kMXRoomMessageFormatHTML])
|
||||
{
|
||||
if ([((NSString*)event.content[@"formatted_body"]) containsString:@"<blockquote"])
|
||||
{
|
||||
_displayFix |= MXKRoomBubbleComponentDisplayFixHtmlBlockquote;
|
||||
}
|
||||
}
|
||||
|
||||
_showEncryptionBadge = [self shouldShowWarningBadgeForEvent:event roomState:(MXRoomState*)roomState session:session];
|
||||
|
||||
[self updateLinkWithRoomState:roomState];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateWithEvent:(MXEvent*)event roomState:(MXRoomState*)roomState session:(MXSession*)session
|
||||
{
|
||||
// Report the new event
|
||||
_event = event;
|
||||
|
||||
if (_event.isRedactedEvent)
|
||||
{
|
||||
// Do not use the live room state for redacted events as they occurred in the past
|
||||
// Note: as we don't have valid room state in this case, userId will be used as display name
|
||||
roomState = nil;
|
||||
}
|
||||
// Other calls to updateWithEvent are made to update the state of an event (ex: MXKEventStateSending to MXKEventStateDefault).
|
||||
// They occur in live so we can use the room up-to-date state without making huge errors
|
||||
|
||||
_textMessage = nil;
|
||||
|
||||
MXKEventFormatterError error;
|
||||
_attributedTextMessage = [_eventFormatter attributedStringFromEvent:event withRoomState:roomState error:&error];
|
||||
|
||||
_showEncryptionBadge = [self shouldShowWarningBadgeForEvent:event roomState:roomState session:session];
|
||||
|
||||
[self updateLinkWithRoomState:roomState];
|
||||
}
|
||||
|
||||
- (NSString *)textMessage
|
||||
{
|
||||
if (!_textMessage)
|
||||
{
|
||||
_textMessage = _attributedTextMessage.string;
|
||||
}
|
||||
return _textMessage;
|
||||
}
|
||||
|
||||
- (void)updateLinkWithRoomState:(MXRoomState*)roomState
|
||||
{
|
||||
// Ensure link detection has been enabled
|
||||
if (!MXKAppSettings.standardAppSettings.enableBubbleComponentLinkDetection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only detect links in unencrypted rooms, for un-redacted message events that are text, notice or emote.
|
||||
// Specifically check the room's encryption state rather than the event's as outgoing events are always unencrypted initially.
|
||||
if (roomState.isEncrypted || self.event.eventType != MXEventTypeRoomMessage || [self.event isRedactedEvent])
|
||||
{
|
||||
self.link = nil; // Ensure there's no link for a redacted event
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *messageType = self.event.content[@"msgtype"];
|
||||
|
||||
if (!messageType || !([messageType isEqualToString:kMXMessageTypeText] || [messageType isEqualToString:kMXMessageTypeNotice] || [messageType isEqualToString:kMXMessageTypeEmote]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect links in the attributed string which gets updated when the message is edited.
|
||||
// Restrict detection to the unquoted string so links are only found in the sender's message.
|
||||
NSString *body = [self.attributedTextMessage mxk_unquotedString];
|
||||
NSURL *url = [body mxk_firstURLDetected];
|
||||
|
||||
if (!url)
|
||||
{
|
||||
self.link = nil;
|
||||
return;
|
||||
}
|
||||
|
||||
self.link = url;
|
||||
}
|
||||
|
||||
- (BOOL)shouldShowWarningBadgeForEvent:(MXEvent*)event roomState:(MXRoomState*)roomState session:(MXSession*)session
|
||||
{
|
||||
// Warning badges are unnecessary in unencrypted rooms
|
||||
if (!roomState.isEncrypted)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Not all events are encrypted (e.g. state/reactions/redactions) and we only have encrypted cell subclasses for messages and attachments.
|
||||
if (event.eventType != MXEventTypeRoomMessage && !event.isMediaAttachment)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Always show a warning badge if there was a decryption error.
|
||||
if (event.decryptionError)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Unencrypted message events should show a warning unless they're pending local echoes
|
||||
if (!event.isEncrypted)
|
||||
{
|
||||
if (event.isLocalEvent
|
||||
|| event.contentHasBeenEdited) // Local echo for an edit is clear but uses a true event id, the one of the edited event
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
// The encryption is in a good state.
|
||||
// Only show a warning badge if there are trust issues.
|
||||
if (event.sender)
|
||||
{
|
||||
MXUserTrustLevel *userTrustLevel = [session.crypto trustLevelForUser:event.sender];
|
||||
MXDeviceInfo *deviceInfo = [session.crypto eventDeviceInfo:event];
|
||||
|
||||
if (userTrustLevel.isVerified && !deviceInfo.trustLevel.isVerified)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Everything was fine
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIImage.h>
|
||||
|
||||
#import <MatrixSDK/MXEnumConstants.h>
|
||||
|
||||
@class MXSession;
|
||||
|
||||
/**
|
||||
`MXKRoomCreationInputs` objects lists all the fields considered for a new room creation.
|
||||
*/
|
||||
@interface MXKRoomCreationInputs : NSObject
|
||||
|
||||
/**
|
||||
The selected matrix session in which the new room should be created.
|
||||
*/
|
||||
@property (nonatomic) MXSession* mxSession;
|
||||
|
||||
/**
|
||||
The room name.
|
||||
*/
|
||||
@property (nonatomic) NSString* roomName;
|
||||
|
||||
/**
|
||||
The room alias.
|
||||
*/
|
||||
@property (nonatomic) NSString* roomAlias;
|
||||
|
||||
/**
|
||||
The room topic.
|
||||
*/
|
||||
@property (nonatomic) NSString* roomTopic;
|
||||
|
||||
/**
|
||||
The room picture.
|
||||
*/
|
||||
@property (nonatomic) UIImage *roomPicture;
|
||||
|
||||
/**
|
||||
The room visibility (kMXRoomVisibilityPrivate by default).
|
||||
*/
|
||||
@property (nonatomic) MXRoomDirectoryVisibility roomVisibility;
|
||||
|
||||
/**
|
||||
The room participants (nil by default).
|
||||
*/
|
||||
@property (nonatomic) NSArray *roomParticipants;
|
||||
|
||||
/**
|
||||
Add a participant.
|
||||
|
||||
@param participantId The matrix user id of the participant.
|
||||
*/
|
||||
- (void)addParticipant:(NSString *)participantId;
|
||||
|
||||
/**
|
||||
Remove a participant.
|
||||
|
||||
@param participantId The matrix user id of the participant.
|
||||
*/
|
||||
- (void)removeParticipant:(NSString *)participantId;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKRoomCreationInputs.h"
|
||||
|
||||
#import <MatrixSDK/MXSession.h>
|
||||
|
||||
@interface MXKRoomCreationInputs ()
|
||||
{
|
||||
NSMutableArray *participants;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation MXKRoomCreationInputs
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
_roomVisibility = kMXRoomDirectoryVisibilityPrivate;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setRoomParticipants:(NSArray *)roomParticipants
|
||||
{
|
||||
participants = [NSMutableArray arrayWithArray:roomParticipants];
|
||||
}
|
||||
|
||||
- (NSArray*)roomParticipants
|
||||
{
|
||||
return participants;
|
||||
}
|
||||
|
||||
- (void)addParticipant:(NSString *)participantId
|
||||
{
|
||||
if (participantId.length)
|
||||
{
|
||||
if (!participants)
|
||||
{
|
||||
participants = [NSMutableArray array];
|
||||
}
|
||||
[participants addObject:participantId];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeParticipant:(NSString *)participantId
|
||||
{
|
||||
if (participantId.length)
|
||||
{
|
||||
[participants removeObject:participantId];
|
||||
|
||||
if (!participants.count)
|
||||
{
|
||||
participants = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,779 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "MXKDataSource.h"
|
||||
#import "MXKRoomBubbleCellDataStoring.h"
|
||||
#import "MXKEventFormatter.h"
|
||||
|
||||
@class MXKQueuedEvent;
|
||||
|
||||
/**
|
||||
Define the threshold which triggers a bubbles count flush.
|
||||
*/
|
||||
#define MXKROOMDATASOURCE_CACHED_BUBBLES_COUNT_THRESHOLD 30
|
||||
|
||||
/**
|
||||
Define the number of messages to preload around the initial event.
|
||||
*/
|
||||
#define MXKROOMDATASOURCE_PAGINATION_LIMIT_AROUND_INITIAL_EVENT 30
|
||||
|
||||
/**
|
||||
List the supported pagination of the rendered room bubble cells
|
||||
*/
|
||||
typedef enum : NSUInteger
|
||||
{
|
||||
/**
|
||||
No pagination
|
||||
*/
|
||||
MXKRoomDataSourceBubblesPaginationNone,
|
||||
/**
|
||||
The rendered room bubble cells are paginated per day
|
||||
*/
|
||||
MXKRoomDataSourceBubblesPaginationPerDay
|
||||
|
||||
} MXKRoomDataSourceBubblesPagination;
|
||||
|
||||
|
||||
#pragma mark - Cells identifiers
|
||||
|
||||
/**
|
||||
String identifying the object used to store and prepare room bubble data.
|
||||
*/
|
||||
extern NSString *const kMXKRoomBubbleCellDataIdentifier;
|
||||
|
||||
|
||||
#pragma mark - Notifications
|
||||
|
||||
/**
|
||||
Posted when a server sync starts or ends (depend on 'serverSyncEventCount').
|
||||
The notification object is the `MXKRoomDataSource` instance.
|
||||
*/
|
||||
extern NSString *const kMXKRoomDataSourceSyncStatusChanged;
|
||||
|
||||
/**
|
||||
Posted when the data source has failed to paginate around an event.
|
||||
The notification object is the `MXKRoomDataSource` instance. The `userInfo` dictionary contains the following key:
|
||||
- kMXKRoomDataTimelineErrorErrorKey: The NSError.
|
||||
*/
|
||||
extern NSString *const kMXKRoomDataSourceTimelineError;
|
||||
|
||||
/**
|
||||
Notifications `userInfo` keys
|
||||
*/
|
||||
extern NSString *const kMXKRoomDataSourceTimelineErrorErrorKey;
|
||||
|
||||
#pragma mark - MXKRoomDataSource
|
||||
@protocol MXKRoomBubbleCellDataStoring;
|
||||
@class MXKRoomBubbleCellData;
|
||||
|
||||
/**
|
||||
The data source for `MXKRoomViewController`.
|
||||
*/
|
||||
@interface MXKRoomDataSource : MXKDataSource <UITableViewDataSource>
|
||||
{
|
||||
@protected
|
||||
|
||||
/**
|
||||
The data for the cells served by `MXKRoomDataSource`.
|
||||
*/
|
||||
NSMutableArray<id<MXKRoomBubbleCellDataStoring>> *bubbles;
|
||||
|
||||
/**
|
||||
The queue of events that need to be processed in order to compute their display.
|
||||
*/
|
||||
NSMutableArray<MXKQueuedEvent*> *eventsToProcess;
|
||||
|
||||
/**
|
||||
The dictionary of the related groups that the current user did not join.
|
||||
*/
|
||||
NSMutableDictionary<NSString*, MXGroup*> *externalRelatedGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
The id of the room managed by the data source.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *roomId;
|
||||
|
||||
/**
|
||||
The id of the secondary room managed by the data source. Events with specified types from the secondary room will be provided from the data source.
|
||||
@see `secondaryRoomEventTypes`.
|
||||
Can be nil.
|
||||
*/
|
||||
@property (nonatomic, copy) NSString *secondaryRoomId;
|
||||
|
||||
/**
|
||||
Types of events to include from the secondary room. Default is all call events.
|
||||
*/
|
||||
@property (nonatomic, copy) NSArray<MXEventTypeString> *secondaryRoomEventTypes;
|
||||
|
||||
/**
|
||||
The room the data comes from.
|
||||
The object is defined when the MXSession has data for the room
|
||||
*/
|
||||
@property (nonatomic, readonly) MXRoom *room;
|
||||
|
||||
/**
|
||||
The preloaded room.state.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXRoomState *roomState;
|
||||
|
||||
/**
|
||||
The timeline being managed. It can be the live timeline of the room
|
||||
or a timeline from a past event, initialEventId.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXEventTimeline *timeline;
|
||||
|
||||
/**
|
||||
Flag indicating if the data source manages, or will manage, a live timeline.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL isLive;
|
||||
|
||||
/**
|
||||
Flag indicating if the data source is used to peek into a room, ie it gets data from
|
||||
a room the user has not joined yet.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL isPeeking;
|
||||
|
||||
/**
|
||||
The list of the attachments with thumbnail in the current available bubbles (MXKAttachment instances).
|
||||
Note: the stickers are excluded from the returned list.
|
||||
Note2: the attachments for which the antivirus scan status is not available are excluded too.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray *attachmentsWithThumbnail;
|
||||
|
||||
/**
|
||||
The events are processed asynchronously. This property counts the number of queued events
|
||||
during server sync for which the process is pending.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSInteger serverSyncEventCount;
|
||||
|
||||
/**
|
||||
The current text message partially typed in text input (use nil to reset it).
|
||||
*/
|
||||
@property (nonatomic) NSString *partialTextMessage;
|
||||
|
||||
#pragma mark - Configuration
|
||||
/**
|
||||
The text formatter applied on the events.
|
||||
By default, the events are filtered according to the value stored in the shared application settings (see [MXKAppSettings standardAppSettings].eventsFilterForMessages).
|
||||
The events whose the type doesn't belong to the this list are not displayed.
|
||||
`MXKRoomBubbleCellDataStoring` instances can use it to format text.
|
||||
*/
|
||||
@property (nonatomic) MXKEventFormatter *eventFormatter;
|
||||
|
||||
/**
|
||||
Show the date time label in rendered room bubble cells. NO by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL showBubblesDateTime;
|
||||
|
||||
/**
|
||||
A Boolean value that determines whether the date time labels are customized (By default date time display is handled by MatrixKit). NO by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL useCustomDateTimeLabel;
|
||||
|
||||
/**
|
||||
Show the read marker (if any) in the rendered room bubble cells. YES by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL showReadMarker;
|
||||
|
||||
/**
|
||||
Show the receipts in rendered bubble cell. YES by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL showBubbleReceipts;
|
||||
|
||||
/**
|
||||
A Boolean value that determines whether the read receipts are customized (By default read receipts display is handled by MatrixKit). NO by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL useCustomReceipts;
|
||||
|
||||
/**
|
||||
Show the reactions in rendered bubble cell. NO by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL showReactions;
|
||||
|
||||
/**
|
||||
Show only reactions with single Emoji. NO by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL showOnlySingleEmojiReactions;
|
||||
|
||||
/**
|
||||
A Boolean value that determines whether the unsent button is customized (By default an 'Unsent' button is displayed by MatrixKit in front of unsent events). NO by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL useCustomUnsentButton;
|
||||
|
||||
/**
|
||||
Show the typing notifications of other room members in the chat history (NO by default).
|
||||
*/
|
||||
@property (nonatomic) BOOL showTypingNotifications;
|
||||
|
||||
/**
|
||||
The pagination applied on the rendered room bubble cells (MXKRoomDataSourceBubblesPaginationNone by default).
|
||||
*/
|
||||
@property (nonatomic) MXKRoomDataSourceBubblesPagination bubblesPagination;
|
||||
|
||||
/**
|
||||
Max nbr of cached bubbles when there is no delegate.
|
||||
The default value is 30.
|
||||
*/
|
||||
@property (nonatomic) unsigned long maxBackgroundCachedBubblesCount;
|
||||
|
||||
/**
|
||||
The number of messages to preload around the initial event.
|
||||
The default value is 30.
|
||||
*/
|
||||
@property (nonatomic) NSUInteger paginationLimitAroundInitialEvent;
|
||||
|
||||
/**
|
||||
Tell whether only the message events with an url key in their content must be handled. NO by default.
|
||||
Note: The stickers are not retained by this filter.
|
||||
*/
|
||||
@property (nonatomic) BOOL filterMessagesWithURL;
|
||||
|
||||
#pragma mark - Life cycle
|
||||
|
||||
/**
|
||||
Asynchronously create a data source to serve data corresponding to the passed room.
|
||||
|
||||
This method preloads room data, like the room state, to make it available once
|
||||
the room data source is created.
|
||||
|
||||
@param roomId the id of the room to get data from.
|
||||
@param mxSession the Matrix session to get data from.
|
||||
@param onComplete a block providing the newly created instance.
|
||||
*/
|
||||
+ (void)loadRoomDataSourceWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)mxSession onComplete:(void (^)(id roomDataSource))onComplete;
|
||||
|
||||
/**
|
||||
Asynchronously create adata source to serve data corresponding to an event in the
|
||||
past of a room.
|
||||
|
||||
This method preloads room data, like the room state, to make it available once
|
||||
the room data source is created.
|
||||
|
||||
@param roomId the id of the room to get data from.
|
||||
@param initialEventId the id of the event where to start the timeline.
|
||||
@param mxSession the Matrix session to get data from.
|
||||
@param onComplete a block providing the newly created instance.
|
||||
*/
|
||||
+ (void)loadRoomDataSourceWithRoomId:(NSString*)roomId initialEventId:(NSString*)initialEventId andMatrixSession:(MXSession*)mxSession onComplete:(void (^)(id roomDataSource))onComplete;
|
||||
|
||||
/**
|
||||
Asynchronously create a data source to peek into a room.
|
||||
|
||||
The data source will close the `peekingRoom` instance on [self destroy].
|
||||
|
||||
This method preloads room data, like the room state, to make it available once
|
||||
the room data source is created.
|
||||
|
||||
@param peekingRoom the room to peek.
|
||||
@param initialEventId the id of the event where to start the timeline. nil means the live
|
||||
timeline.
|
||||
@param onComplete a block providing the newly created instance.
|
||||
*/
|
||||
+ (void)loadRoomDataSourceWithPeekingRoom:(MXPeekingRoom*)peekingRoom andInitialEventId:(NSString*)initialEventId onComplete:(void (^)(id roomDataSource))onComplete;
|
||||
|
||||
#pragma mark - Constructors (Should not be called directly)
|
||||
|
||||
/**
|
||||
Initialise the data source to serve data corresponding to the passed room.
|
||||
|
||||
@param roomId the id of the room to get data from.
|
||||
@param mxSession the Matrix session to get data from.
|
||||
@return the newly created instance.
|
||||
*/
|
||||
- (instancetype)initWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Initialise the data source to serve data corresponding to an event in the
|
||||
past of a room.
|
||||
|
||||
@param roomId the id of the room to get data from.
|
||||
@param initialEventId the id of the event where to start the timeline.
|
||||
@param mxSession the Matrix session to get data from.
|
||||
@return the newly created instance.
|
||||
*/
|
||||
- (instancetype)initWithRoomId:(NSString*)roomId initialEventId:(NSString*)initialEventId andMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Initialise the data source to peek into a room.
|
||||
|
||||
The data source will close the `peekingRoom` instance on [self destroy].
|
||||
|
||||
@param peekingRoom the room to peek.
|
||||
@param initialEventId the id of the event where to start the timeline. nil means the live
|
||||
timeline.
|
||||
@return the newly created instance.
|
||||
*/
|
||||
- (instancetype)initWithPeekingRoom:(MXPeekingRoom*)peekingRoom andInitialEventId:(NSString*)initialEventId;
|
||||
|
||||
/**
|
||||
Mark all messages as read in the room.
|
||||
*/
|
||||
- (void)markAllAsRead;
|
||||
|
||||
/**
|
||||
Reduce memory usage by releasing room data if the number of bubbles is over the provided limit 'maxBubbleNb'.
|
||||
|
||||
This operation is ignored if some local echoes are pending or if unread messages counter is not nil.
|
||||
|
||||
@param maxBubbleNb The room bubble data are released only if the number of bubbles is over this limit.
|
||||
*/
|
||||
- (void)limitMemoryUsage:(NSInteger)maxBubbleNb;
|
||||
|
||||
/**
|
||||
Force data reload.
|
||||
*/
|
||||
- (void)reload;
|
||||
|
||||
/**
|
||||
Called when room property changed. Designed to be used by subclasses.
|
||||
*/
|
||||
- (void)roomDidSet;
|
||||
|
||||
#pragma mark - Public methods
|
||||
/**
|
||||
Get the data for the cell at the given index.
|
||||
|
||||
@param index the index of the cell in the array
|
||||
@return the cell data
|
||||
*/
|
||||
- (id<MXKRoomBubbleCellDataStoring>)cellDataAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
Get the data for the cell which contains the event with the provided event id.
|
||||
|
||||
@param eventId the event identifier
|
||||
@return the cell data
|
||||
*/
|
||||
- (id<MXKRoomBubbleCellDataStoring>)cellDataOfEventWithEventId:(NSString*)eventId;
|
||||
|
||||
/**
|
||||
Get the index of the cell which contains the event with the provided event id.
|
||||
|
||||
@param eventId the event identifier
|
||||
@return the index of the concerned cell (NSNotFound if none).
|
||||
*/
|
||||
- (NSInteger)indexOfCellDataWithEventId:(NSString *)eventId;
|
||||
|
||||
/**
|
||||
Get height of the cell at the given index.
|
||||
|
||||
@param index the index of the cell in the array.
|
||||
@param maxWidth the maximum available width.
|
||||
@return the cell height (0 if no data is available for this cell, or if the delegate is undefined).
|
||||
*/
|
||||
- (CGFloat)cellHeightAtIndex:(NSInteger)index withMaximumWidth:(CGFloat)maxWidth;
|
||||
|
||||
|
||||
/**
|
||||
Force bubbles cell data message recalculation.
|
||||
*/
|
||||
- (void)invalidateBubblesCellDataCache;
|
||||
|
||||
#pragma mark - Pagination
|
||||
/**
|
||||
Load more messages.
|
||||
This method fails (with nil error) if the data source is not ready (see `MXKDataSourceStateReady`).
|
||||
|
||||
@param numItems the number of items to get.
|
||||
@param direction backwards or forwards.
|
||||
@param onlyFromStore if YES, return available events from the store, do not make a pagination request to the homeserver.
|
||||
@param success a block called when the operation succeeds. This block returns the number of added cells.
|
||||
(Note this count may be 0 if paginated messages have been concatenated to the current first cell).
|
||||
@param failure a block called when the operation fails.
|
||||
*/
|
||||
- (void)paginate:(NSUInteger)numItems direction:(MXTimelineDirection)direction onlyFromStore:(BOOL)onlyFromStore success:(void (^)(NSUInteger addedCellNumber))success failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Load enough messages to fill the rect.
|
||||
This method fails (with nil error) if the data source is not ready (see `MXKDataSourceStateReady`),
|
||||
or if the delegate is undefined (this delegate is required to compute the actual size of the cells).
|
||||
|
||||
@param rect the rect to fill.
|
||||
@param direction backwards or forwards.
|
||||
@param minRequestMessagesCount if messages are not available in the store, a request to the homeserver
|
||||
is required. minRequestMessagesCount indicates the minimum messages count to retrieve from the hs.
|
||||
@param success a block called when the operation succeeds.
|
||||
@param failure a block called when the operation fails.
|
||||
*/
|
||||
- (void)paginateToFillRect:(CGRect)rect direction:(MXTimelineDirection)direction withMinRequestMessagesCount:(NSUInteger)minRequestMessagesCount success:(void (^)(void))success failure:(void (^)(NSError *error))failure;
|
||||
|
||||
|
||||
#pragma mark - Sending
|
||||
/**
|
||||
Send a text message to the room.
|
||||
|
||||
While sending, a fake event will be echoed in the messages list.
|
||||
Once complete, this local echo will be replaced by the event saved by the homeserver.
|
||||
|
||||
@param text the text to send.
|
||||
@param success A block object called when the operation succeeds. It returns
|
||||
the event id of the event generated on the homeserver
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)sendTextMessage:(NSString*)text
|
||||
success:(void (^)(NSString *eventId))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Send a reply to an event with text message to the room.
|
||||
|
||||
While sending, a fake event will be echoed in the messages list.
|
||||
Once complete, this local echo will be replaced by the event saved by the homeserver.
|
||||
|
||||
@param eventIdToReply the id of event to reply.
|
||||
@param text the text to send.
|
||||
@param success A block object called when the operation succeeds. It returns
|
||||
the event id of the event generated on the homeserver
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)sendReplyToEventWithId:(NSString*)eventIdToReply
|
||||
withTextMessage:(NSString *)text
|
||||
success:(void (^)(NSString *))success
|
||||
failure:(void (^)(NSError *))failure;
|
||||
|
||||
/**
|
||||
Indicates if replying to the provided event is supported.
|
||||
Only event of type 'MXEventTypeRoomMessage' are supported for the moment, and for certain msgtype.
|
||||
|
||||
@param eventId The id of the event.
|
||||
@return YES if it is possible to reply to this event.
|
||||
*/
|
||||
- (BOOL)canReplyToEventWithId:(NSString*)eventId;
|
||||
|
||||
/**
|
||||
Send an image to the room.
|
||||
|
||||
While sending, a fake event will be echoed in the messages list.
|
||||
Once complete, this local echo will be replaced by the event saved by the homeserver.
|
||||
|
||||
@param image the UIImage containing the image to send.
|
||||
@param success A block object called when the operation succeeds. It returns
|
||||
the event id of the event generated on the homeserver
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)sendImage:(UIImage*)image
|
||||
success:(void (^)(NSString *eventId))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Send an image to the room.
|
||||
|
||||
While sending, a fake event will be echoed in the messages list.
|
||||
Once complete, this local echo will be replaced by the event saved by the homeserver.
|
||||
|
||||
@param imageData the full-sized image data of the image to send.
|
||||
@param mimetype the mime type of the image
|
||||
@param success A block object called when the operation succeeds. It returns
|
||||
the event id of the event generated on the homeserver
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)sendImage:(NSData*)imageData mimeType:(NSString*)mimetype success:(void (^)(NSString *))success failure:(void (^)(NSError *))failure;
|
||||
|
||||
/**
|
||||
Send a video to the room.
|
||||
|
||||
While sending, a fake event will be echoed in the messages list.
|
||||
Once complete, this local echo will be replaced by the event saved by the homeserver.
|
||||
|
||||
@param videoLocalURL the local filesystem path of the video to send.
|
||||
@param videoThumbnail the UIImage hosting a video thumbnail.
|
||||
@param success A block object called when the operation succeeds. It returns
|
||||
the event id of the event generated on the homeserver
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)sendVideo:(NSURL*)videoLocalURL
|
||||
withThumbnail:(UIImage*)videoThumbnail
|
||||
success:(void (^)(NSString *eventId))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Send a video to the room.
|
||||
|
||||
While sending, a fake event will be echoed in the messages list.
|
||||
Once complete, this local echo will be replaced by the event saved by the homeserver.
|
||||
|
||||
@param videoAsset the AVAsset that represents the video to send.
|
||||
@param videoThumbnail the UIImage hosting a video thumbnail.
|
||||
@param success A block object called when the operation succeeds. It returns
|
||||
the event id of the event generated on the homeserver
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)sendVideoAsset:(AVAsset*)videoAsset
|
||||
withThumbnail:(UIImage*)videoThumbnail
|
||||
success:(void (^)(NSString *eventId))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Send an audio file to the room.
|
||||
|
||||
While sending, a fake event will be echoed in the messages list.
|
||||
Once complete, this local echo will be replaced by the event saved by the homeserver.
|
||||
|
||||
@param audioFileLocalURL the local filesystem path of the audio file to send.
|
||||
@param mimeType the mime type of the file.
|
||||
@param success A block object called when the operation succeeds. It returns
|
||||
the event id of the event generated on the homeserver
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)sendAudioFile:(NSURL *)audioFileLocalURL
|
||||
mimeType:mimeType
|
||||
success:(void (^)(NSString *))success
|
||||
failure:(void (^)(NSError *))failure;
|
||||
|
||||
/**
|
||||
Send a voice message to the room.
|
||||
|
||||
While sending, a fake event will be echoed in the messages list.
|
||||
Once complete, this local echo will be replaced by the event saved by the homeserver.
|
||||
|
||||
@param audioFileLocalURL the local filesystem path of the audio file to send.
|
||||
@param mimeType (optional) the mime type of the file. Defaults to `audio/ogg`
|
||||
@param duration the length of the voice message in milliseconds
|
||||
@param samples an array of floating point values normalized to [0, 1], boxed within NSNumbers
|
||||
@param success A block object called when the operation succeeds. It returns
|
||||
the event id of the event generated on the homeserver
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)sendVoiceMessage:(NSURL *)audioFileLocalURL
|
||||
mimeType:mimeType
|
||||
duration:(NSUInteger)duration
|
||||
samples:(NSArray<NSNumber *> *)samples
|
||||
success:(void (^)(NSString *))success
|
||||
failure:(void (^)(NSError *))failure;
|
||||
|
||||
/**
|
||||
Send a file to the room.
|
||||
|
||||
While sending, a fake event will be echoed in the messages list.
|
||||
Once complete, this local echo will be replaced by the event saved by the homeserver.
|
||||
|
||||
@param fileLocalURL the local filesystem path of the file to send.
|
||||
@param mimeType the mime type of the file.
|
||||
@param success A block object called when the operation succeeds. It returns
|
||||
the event id of the event generated on the homeserver
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)sendFile:(NSURL*)fileLocalURL
|
||||
mimeType:(NSString*)mimeType
|
||||
success:(void (^)(NSString *eventId))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Send a room message to a room.
|
||||
|
||||
While sending, a fake event will be echoed in the messages list.
|
||||
Once complete, this local echo will be replaced by the event saved by the homeserver.
|
||||
|
||||
@param content the message content that will be sent to the server as a JSON object.
|
||||
@param success A block object called when the operation succeeds. It returns
|
||||
the event id of the event generated on the homeserver
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)sendMessageWithContent:(NSDictionary*)content
|
||||
success:(void (^)(NSString *eventId))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Send a generic non state event to a room.
|
||||
|
||||
While sending, a fake event will be echoed in the messages list.
|
||||
Once complete, this local echo will be replaced by the event saved by the homeserver.
|
||||
|
||||
@param eventTypeString the type of the event. @see MXEventType.
|
||||
@param content the content that will be sent to the server as a JSON object.
|
||||
@param success A block object called when the operation succeeds. It returns
|
||||
the event id of the event generated on the homeserver
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)sendEventOfType:(MXEventTypeString)eventTypeString
|
||||
content:(NSDictionary<NSString*, id>*)content
|
||||
success:(void (^)(NSString *eventId))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Resend a room message event.
|
||||
|
||||
The echo message corresponding to the event will be removed and a new echo message
|
||||
will be added at the end of the room history.
|
||||
|
||||
@param eventId of the event to resend.
|
||||
@param success A block object called when the operation succeeds. It returns
|
||||
the event id of the event generated on the homeserver
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)resendEventWithEventId:(NSString*)eventId
|
||||
success:(void (^)(NSString *eventId))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
|
||||
#pragma mark - Events management
|
||||
/**
|
||||
Get an event loaded in this room datasource.
|
||||
|
||||
@param eventId of the event to retrieve.
|
||||
@return the MXEvent object or nil if not found.
|
||||
*/
|
||||
- (MXEvent *)eventWithEventId:(NSString *)eventId;
|
||||
|
||||
/**
|
||||
Remove an event from the events loaded by room datasource.
|
||||
|
||||
@param eventId of the event to remove.
|
||||
*/
|
||||
- (void)removeEventWithEventId:(NSString *)eventId;
|
||||
|
||||
/**
|
||||
This method is called for each read receipt event received in forward mode.
|
||||
|
||||
By default, it tells the delegate that some cell data/views have been changed.
|
||||
You may override this method to handle the receipt event according to the application needs.
|
||||
|
||||
You should not call this method directly.
|
||||
You may override it in inherited 'MXKRoomDataSource' class.
|
||||
|
||||
@param receiptEvent an event with 'm.receipt' type.
|
||||
@param roomState the room state right before the event
|
||||
*/
|
||||
- (void)didReceiveReceiptEvent:(MXEvent *)receiptEvent roomState:(MXRoomState *)roomState;
|
||||
|
||||
/**
|
||||
Update read receipts for an event in a bubble cell data.
|
||||
|
||||
@param cellData The cell data to update.
|
||||
@param readReceipts The new read receipts.
|
||||
@param eventId The id of the event.
|
||||
*/
|
||||
- (void)updateCellData:(MXKRoomBubbleCellData*)cellData withReadReceipts:(NSArray<MXReceiptData*>*)readReceipts forEventId:(NSString*)eventId;
|
||||
|
||||
/**
|
||||
Overridable method to customise the way how unsent messages are managed.
|
||||
By default, they are added to the end of the timeline.
|
||||
*/
|
||||
- (void)handleUnsentMessages;
|
||||
|
||||
#pragma mark - Asynchronous events processing
|
||||
/**
|
||||
The dispatch queue to process room messages.
|
||||
|
||||
This processing can consume time. Handling it on a separated thread avoids to block the main thread.
|
||||
All MXKRoomDataSource instances share the same dispatch queue.
|
||||
*/
|
||||
+ (dispatch_queue_t)processingQueue;
|
||||
|
||||
#pragma mark - Bubble collapsing
|
||||
|
||||
/**
|
||||
Collapse or expand a series of collapsable bubbles.
|
||||
|
||||
@param bubbleData the first bubble of the series.
|
||||
@param collapsed YES to collapse. NO to expand.
|
||||
*/
|
||||
- (void)collapseRoomBubble:(id<MXKRoomBubbleCellDataStoring>)bubbleData collapsed:(BOOL)collapsed;
|
||||
|
||||
#pragma mark - Groups
|
||||
|
||||
/**
|
||||
Get a MXGroup instance for a group.
|
||||
This method is used by the bubble to retrieve a related groups of the room.
|
||||
|
||||
@param groupId The identifier to the group.
|
||||
@return the MXGroup instance.
|
||||
*/
|
||||
- (MXGroup *)groupWithGroupId:(NSString*)groupId;
|
||||
|
||||
#pragma mark - Reactions
|
||||
|
||||
/**
|
||||
Indicates if it's possible to react on the event.
|
||||
|
||||
@param eventId The id of the event.
|
||||
@return True to indicates reaction possibility for this event.
|
||||
*/
|
||||
- (BOOL)canReactToEventWithId:(NSString*)eventId;
|
||||
|
||||
/**
|
||||
Send a reaction to an event.
|
||||
|
||||
@param reaction Reaction to add.
|
||||
@param eventId The id of the event.
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)addReaction:(NSString *)reaction
|
||||
forEventId:(NSString *)eventId
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Unreact a reaction to an event.
|
||||
|
||||
@param reaction Reaction to unreact.
|
||||
@param eventId The id of the event.
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)removeReaction:(NSString *)reaction
|
||||
forEventId:(NSString *)eventId
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
#pragma mark - Editions
|
||||
|
||||
/**
|
||||
Indicates if it's possible to edit the event content.
|
||||
|
||||
@param eventId The id of the event.
|
||||
@return True to indicates edition possibility for this event.
|
||||
*/
|
||||
- (BOOL)canEditEventWithId:(NSString*)eventId;
|
||||
|
||||
/**
|
||||
Replace a text in an event.
|
||||
|
||||
@param eventId The eventId of event to replace.
|
||||
@param text The new message text.
|
||||
@param success A block object called when the operation succeeds. It returns
|
||||
the event id of the event generated on the homeserver.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)replaceTextMessageForEventWithId:(NSString *)eventId
|
||||
withTextMessage:(NSString *)text
|
||||
success:(void (^)(NSString *eventId))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
|
||||
/**
|
||||
Update reactions for an event in a bubble cell data.
|
||||
|
||||
@param cellData The cell data to update.
|
||||
@param eventId The id of the event.
|
||||
*/
|
||||
- (void)updateCellDataReactions:(id<MXKRoomBubbleCellDataStoring>)cellData forEventId:(NSString*)eventId;
|
||||
|
||||
/**
|
||||
Retrieve editable text message from an event.
|
||||
|
||||
@param event An event.
|
||||
@return Event text editable by user.
|
||||
*/
|
||||
- (NSString*)editableTextMessageForEvent:(MXEvent*)event;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MXKRoomDataSource.h"
|
||||
|
||||
/**
|
||||
`MXKRoomDataSourceManagerReleasePolicy` defines how a `MXKRoomDataSource` instance must be released
|
||||
when [MXKRoomDataSourceManager closeRoomDataSourceWithRoomId:] is called.
|
||||
|
||||
Once released, the in-memory data (messages that are outgoing, failed sending, ...) of room data source
|
||||
is lost.
|
||||
*/
|
||||
typedef enum : NSUInteger {
|
||||
|
||||
/**
|
||||
Created `MXKRoomDataSource` instances are never released when they are closed.
|
||||
*/
|
||||
MXKRoomDataSourceManagerReleasePolicyNeverRelease,
|
||||
|
||||
/**
|
||||
Created `MXKRoomDataSource` instances are released when they are closed.
|
||||
*/
|
||||
MXKRoomDataSourceManagerReleasePolicyReleaseOnClose,
|
||||
|
||||
} MXKRoomDataSourceManagerReleasePolicy;
|
||||
|
||||
|
||||
/**
|
||||
`MXKRoomDataSourceManager` manages a pool of `MXKRoomDataSource` instances for a given Matrix session.
|
||||
|
||||
It makes the `MXKRoomDataSource` instances reusable so that their data (messages that are outgoing, failed sending, ...)
|
||||
is not lost when the view controller that displays them is gone.
|
||||
*/
|
||||
@interface MXKRoomDataSourceManager : NSObject
|
||||
|
||||
/**
|
||||
Retrieve the MXKRoomDataSources manager for a particular Matrix session.
|
||||
|
||||
@param mxSession the Matrix session,
|
||||
@return the MXKRoomDataSources manager to use for this session.
|
||||
*/
|
||||
+ (MXKRoomDataSourceManager*)sharedManagerForMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Remove the MXKRoomDataSources manager for a particular Matrix session.
|
||||
|
||||
@param mxSession the Matrix session.
|
||||
*/
|
||||
+ (void)removeSharedManagerForMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Register the MXKRoomDataSource-inherited class that will be used to instantiate all room data source.
|
||||
By default MXKRoomDataSource class is considered.
|
||||
|
||||
CAUTION: All existing room data source instances are reset in case of class change.
|
||||
|
||||
@param roomDataSourceClass a MXKRoomDataSource-inherited class.
|
||||
*/
|
||||
+ (void)registerRoomDataSourceClass:(Class)roomDataSourceClass;
|
||||
|
||||
/**
|
||||
Force close all the current room data source instances.
|
||||
*/
|
||||
- (void)reset;
|
||||
|
||||
/**
|
||||
Get a room data source corresponding to a room id.
|
||||
|
||||
If a room data source already exists for this room, its reference will be returned. Else,
|
||||
if requested, the method will instantiate it.
|
||||
|
||||
@param roomId the room id of the room.
|
||||
@param create if YES, the MXKRoomDataSourceManager will create the room data source if it does not exist yet.
|
||||
@param onComplete blocked with the room data source (instance of MXKRoomDataSource-inherited class).
|
||||
*/
|
||||
- (void)roomDataSourceForRoom:(NSString*)roomId create:(BOOL)create onComplete:(void (^)(MXKRoomDataSource *roomDataSource))onComplete;
|
||||
|
||||
/**
|
||||
Make a room data source be managed by the manager.
|
||||
|
||||
Use this method to add a MXKRoomDataSource-inherited instance that cannot be automatically created by
|
||||
[MXKRoomDataSourceManager roomDataSourceForRoom: create:].
|
||||
|
||||
@param roomDataSource the MXKRoomDataSource-inherited object to the manager scope.
|
||||
*/
|
||||
- (void)addRoomDataSource:(MXKRoomDataSource*)roomDataSource;
|
||||
|
||||
/**
|
||||
Close the roomDataSource.
|
||||
|
||||
The roomDataSource instance will be actually destroyed according to the current release policy.
|
||||
|
||||
@param roomId the room if of the data source to release.
|
||||
@param forceRelease if yes the room data source instance will be destroyed whatever the policy is.
|
||||
*/
|
||||
- (void)closeRoomDataSourceWithRoomId:(NSString*)roomId forceClose:(BOOL)forceRelease;
|
||||
|
||||
/**
|
||||
The release policy to apply when `MXKRoomDataSource` instances are closed.
|
||||
Default is MXKRoomDataSourceManagerReleasePolicyNeverRelease.
|
||||
*/
|
||||
@property (nonatomic) MXKRoomDataSourceManagerReleasePolicy releasePolicy;
|
||||
|
||||
/**
|
||||
Tells whether a server sync is in progress in the matrix session.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL isServerSyncInProgress;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKRoomDataSourceManager.h"
|
||||
|
||||
@interface MXKRoomDataSourceManager()
|
||||
{
|
||||
MXSession *mxSession;
|
||||
|
||||
/**
|
||||
The list of running roomDataSources.
|
||||
Each key is a room ID. Each value, the MXKRoomDataSource instance.
|
||||
*/
|
||||
NSMutableDictionary *roomDataSources;
|
||||
|
||||
/**
|
||||
Observe UIApplicationDidReceiveMemoryWarningNotification to dispose of any resources that can be recreated.
|
||||
*/
|
||||
id UIApplicationDidReceiveMemoryWarningNotificationObserver;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static NSMutableDictionary *_roomDataSourceManagers = nil;
|
||||
static Class _roomDataSourceClass;
|
||||
|
||||
@implementation MXKRoomDataSourceManager
|
||||
|
||||
+ (MXKRoomDataSourceManager *)sharedManagerForMatrixSession:(MXSession *)mxSession
|
||||
{
|
||||
// Manage a pool of managers: one per Matrix session
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_roomDataSourceManagers = [NSMutableDictionary dictionary];
|
||||
});
|
||||
|
||||
MXKRoomDataSourceManager *roomDataSourceManager;
|
||||
|
||||
// Compute an id for this mxSession object: its pointer address as a string
|
||||
NSString *mxSessionId = [NSString stringWithFormat:@"%p", mxSession];
|
||||
|
||||
@synchronized(_roomDataSourceManagers)
|
||||
{
|
||||
if (_roomDataSourceClass == nil)
|
||||
{
|
||||
// Set default class
|
||||
_roomDataSourceClass = MXKRoomDataSource.class;
|
||||
}
|
||||
// If not available yet, create the `MXKRoomDataSourceManager` for this Matrix session
|
||||
roomDataSourceManager = _roomDataSourceManagers[mxSessionId];
|
||||
if (!roomDataSourceManager)
|
||||
{
|
||||
roomDataSourceManager = [[MXKRoomDataSourceManager alloc]initWithMatrixSession:mxSession];
|
||||
_roomDataSourceManagers[mxSessionId] = roomDataSourceManager;
|
||||
}
|
||||
}
|
||||
|
||||
return roomDataSourceManager;
|
||||
}
|
||||
|
||||
+ (void)removeSharedManagerForMatrixSession:(MXSession*)mxSession
|
||||
{
|
||||
// Compute the id for this mxSession object: its pointer address as a string
|
||||
NSString *mxSessionId = [NSString stringWithFormat:@"%p", mxSession];
|
||||
|
||||
@synchronized(_roomDataSourceManagers)
|
||||
{
|
||||
MXKRoomDataSourceManager *roomDataSourceManager = [_roomDataSourceManagers objectForKey:mxSessionId];
|
||||
if (roomDataSourceManager)
|
||||
{
|
||||
[roomDataSourceManager destroy];
|
||||
[_roomDataSourceManagers removeObjectForKey:mxSessionId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)registerRoomDataSourceClass:(Class)roomDataSourceClass
|
||||
{
|
||||
// Sanity check: accept only MXKRoomDataSource classes or sub-classes
|
||||
NSParameterAssert([roomDataSourceClass isSubclassOfClass:MXKRoomDataSource.class]);
|
||||
|
||||
@synchronized(_roomDataSourceManagers)
|
||||
{
|
||||
if (roomDataSourceClass !=_roomDataSourceClass)
|
||||
{
|
||||
_roomDataSourceClass = roomDataSourceClass;
|
||||
|
||||
NSArray *mxSessionIds = _roomDataSourceManagers.allKeys;
|
||||
for (NSString *mxSessionId in mxSessionIds)
|
||||
{
|
||||
MXKRoomDataSourceManager *roomDataSourceManager = [_roomDataSourceManagers objectForKey:mxSessionId];
|
||||
if (roomDataSourceManager)
|
||||
{
|
||||
[roomDataSourceManager destroy];
|
||||
[_roomDataSourceManagers removeObjectForKey:mxSessionId];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithMatrixSession:(MXSession *)matrixSession
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
mxSession = matrixSession;
|
||||
roomDataSources = [NSMutableDictionary dictionary];
|
||||
_releasePolicy = MXKRoomDataSourceManagerReleasePolicyNeverRelease;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionDidLeaveRoom:) name:kMXSessionDidLeaveRoomNotification object:nil];
|
||||
|
||||
// Observe UIApplicationDidReceiveMemoryWarningNotification
|
||||
UIApplicationDidReceiveMemoryWarningNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
||||
|
||||
MXLogDebug(@"[MXKRoomDataSourceManager] %@: Received memory warning.", self);
|
||||
|
||||
// Reload all data sources (except the current used ones) to reduce memory usage.
|
||||
for (MXKRoomDataSource *roomDataSource in self->roomDataSources.allValues)
|
||||
{
|
||||
if (!roomDataSource.delegate)
|
||||
{
|
||||
[roomDataSource reload];
|
||||
}
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidLeaveRoomNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
[self reset];
|
||||
|
||||
if (UIApplicationDidReceiveMemoryWarningNotificationObserver)
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:UIApplicationDidReceiveMemoryWarningNotificationObserver];
|
||||
UIApplicationDidReceiveMemoryWarningNotificationObserver = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark
|
||||
|
||||
- (BOOL)isServerSyncInProgress
|
||||
{
|
||||
// Check first the matrix session state
|
||||
if (mxSession.state == MXSessionStateSyncInProgress)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Check all data sources (events process is asynchronous, server sync may not be complete in data source).
|
||||
for (MXKRoomDataSource *roomDataSource in roomDataSources.allValues)
|
||||
{
|
||||
if (roomDataSource.serverSyncEventCount)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark
|
||||
|
||||
- (void)reset
|
||||
{
|
||||
NSArray *roomIds = roomDataSources.allKeys;
|
||||
for (NSString *roomId in roomIds)
|
||||
{
|
||||
[self closeRoomDataSourceWithRoomId:roomId forceClose:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)roomDataSourceForRoom:(NSString *)roomId create:(BOOL)create onComplete:(void (^)(MXKRoomDataSource *roomDataSource))onComplete
|
||||
{
|
||||
NSParameterAssert(roomId);
|
||||
|
||||
// If not available yet, create the room data source
|
||||
MXKRoomDataSource *roomDataSource = roomDataSources[roomId];
|
||||
|
||||
if (!roomDataSource && create && roomId)
|
||||
{
|
||||
[_roomDataSourceClass loadRoomDataSourceWithRoomId:roomId andMatrixSession:mxSession onComplete:^(id roomDataSource) {
|
||||
[self addRoomDataSource:roomDataSource];
|
||||
onComplete(roomDataSource);
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
onComplete(roomDataSource);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addRoomDataSource:(MXKRoomDataSource *)roomDataSource
|
||||
{
|
||||
roomDataSources[roomDataSource.roomId] = roomDataSource;
|
||||
}
|
||||
|
||||
- (void)closeRoomDataSourceWithRoomId:(NSString*)roomId forceClose:(BOOL)forceRelease;
|
||||
{
|
||||
// Check first whether this roomDataSource is well handled by this manager
|
||||
if (!roomId || !roomDataSources[roomId])
|
||||
{
|
||||
MXLogDebug(@"[MXKRoomDataSourceManager] Failed to close an unknown room id: %@", roomId);
|
||||
return;
|
||||
}
|
||||
|
||||
MXKRoomDataSource *roomDataSource = roomDataSources[roomId];
|
||||
|
||||
// According to the policy, it is interesting to keep the room data source in life: it can keep managing echo messages
|
||||
// in background for instance
|
||||
MXKRoomDataSourceManagerReleasePolicy releasePolicy = _releasePolicy;
|
||||
if (forceRelease)
|
||||
{
|
||||
// Act as ReleaseOnClose policy
|
||||
releasePolicy = MXKRoomDataSourceManagerReleasePolicyReleaseOnClose;
|
||||
}
|
||||
|
||||
switch (releasePolicy)
|
||||
{
|
||||
case MXKRoomDataSourceManagerReleasePolicyReleaseOnClose:
|
||||
|
||||
// Destroy and forget the instance
|
||||
[roomDataSource destroy];
|
||||
[roomDataSources removeObjectForKey:roomDataSource.roomId];
|
||||
break;
|
||||
|
||||
case MXKRoomDataSourceManagerReleasePolicyNeverRelease:
|
||||
|
||||
// The close here consists in no more sending actions to the current view controller, the room data source delegate
|
||||
roomDataSource.delegate = nil;
|
||||
|
||||
// Keep the instance for life (reduce memory usage by flushing room data if the number of bubbles is over 30).
|
||||
[roomDataSource limitMemoryUsage:roomDataSource.maxBackgroundCachedBubblesCount];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMXSessionDidLeaveRoom:(NSNotification *)notif
|
||||
{
|
||||
if (mxSession == notif.object)
|
||||
{
|
||||
// The room is no more available, remove it from the manager
|
||||
[self closeRoomDataSourceWithRoomId:notif.userInfo[kMXSessionNotificationRoomIdKey] forceClose:YES];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MatrixSDK/MXSendReplyEventStringLocalizerProtocol.h>
|
||||
|
||||
/**
|
||||
A `MXKSendReplyEventStringLocalizer` instance represents string localizations used when send reply event to a message in a room.
|
||||
*/
|
||||
@interface MXKSendReplyEventStringLocalizer : NSObject<MXSendReplyEventStringLocalizerProtocol>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#import "MXKSendReplyEventStringLocalizer.h"
|
||||
#import "MXKSwiftHeader.h"
|
||||
|
||||
@implementation MXKSendReplyEventStringLocalizer
|
||||
|
||||
- (NSString *)senderSentAnImage
|
||||
{
|
||||
return [MatrixKitL10n messageReplyToSenderSentAnImage];
|
||||
}
|
||||
|
||||
- (NSString *)senderSentAVideo
|
||||
{
|
||||
return [MatrixKitL10n messageReplyToSenderSentAVideo];
|
||||
}
|
||||
|
||||
- (NSString *)senderSentAnAudioFile
|
||||
{
|
||||
return [MatrixKitL10n messageReplyToSenderSentAnAudioFile];
|
||||
}
|
||||
|
||||
- (NSString *)senderSentAVoiceMessage
|
||||
{
|
||||
return [MatrixKitL10n messageReplyToSenderSentAVoiceMessage];
|
||||
}
|
||||
|
||||
- (NSString *)senderSentAFile
|
||||
{
|
||||
return [MatrixKitL10n messageReplyToSenderSentAFile];
|
||||
}
|
||||
|
||||
- (NSString *)messageToReplyToPrefix
|
||||
{
|
||||
return [MatrixKitL10n messageReplyToMessageToReplyToPrefix];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
@import Foundation;
|
||||
|
||||
/**
|
||||
Slash commands used to perform actions from a room.
|
||||
*/
|
||||
|
||||
FOUNDATION_EXPORT NSString *const kMXKSlashCmdChangeDisplayName;
|
||||
FOUNDATION_EXPORT NSString *const kMXKSlashCmdEmote;
|
||||
FOUNDATION_EXPORT NSString *const kMXKSlashCmdJoinRoom;
|
||||
FOUNDATION_EXPORT NSString *const kMXKSlashCmdPartRoom;
|
||||
FOUNDATION_EXPORT NSString *const kMXKSlashCmdInviteUser;
|
||||
FOUNDATION_EXPORT NSString *const kMXKSlashCmdKickUser;
|
||||
FOUNDATION_EXPORT NSString *const kMXKSlashCmdBanUser;
|
||||
FOUNDATION_EXPORT NSString *const kMXKSlashCmdUnbanUser;
|
||||
FOUNDATION_EXPORT NSString *const kMXKSlashCmdSetUserPowerLevel;
|
||||
FOUNDATION_EXPORT NSString *const kMXKSlashCmdResetUserPowerLevel;
|
||||
FOUNDATION_EXPORT NSString *const kMXKSlashCmdChangeRoomTopic;
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#import "MXKSlashCommands.h"
|
||||
|
||||
NSString *const kMXKSlashCmdChangeDisplayName = @"/nick";
|
||||
NSString *const kMXKSlashCmdEmote = @"/me";
|
||||
NSString *const kMXKSlashCmdJoinRoom = @"/join";
|
||||
NSString *const kMXKSlashCmdPartRoom = @"/part";
|
||||
NSString *const kMXKSlashCmdInviteUser = @"/invite";
|
||||
NSString *const kMXKSlashCmdKickUser = @"/kick";
|
||||
NSString *const kMXKSlashCmdBanUser = @"/ban";
|
||||
NSString *const kMXKSlashCmdUnbanUser = @"/unban";
|
||||
NSString *const kMXKSlashCmdSetUserPowerLevel = @"/op";
|
||||
NSString *const kMXKSlashCmdResetUserPowerLevel = @"/deop";
|
||||
NSString *const kMXKSlashCmdChangeRoomTopic = @"/topic";
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Copyright 2020 The Matrix.org Foundation C.I.C
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
@protocol MXKURLPreviewDataProtocol <NSObject>
|
||||
|
||||
/// The URL that's represented by the preview data.
|
||||
@property (readonly, nonnull) NSURL *url;
|
||||
|
||||
/// The ID of the event that created this preview.
|
||||
@property (readonly, nonnull) NSString *eventID;
|
||||
|
||||
/// The ID of the room that this preview is from.
|
||||
@property (readonly, nonnull) NSString *roomID;
|
||||
|
||||
/// The OpenGraph site name for the URL.
|
||||
@property (readonly, nullable) NSString *siteName;
|
||||
|
||||
/// The OpenGraph title for the URL.
|
||||
@property (readonly, nullable) NSString *title;
|
||||
|
||||
/// The OpenGraph description for the URL.
|
||||
@property (readonly, nullable) NSString *text;
|
||||
|
||||
/// The OpenGraph image for the URL.
|
||||
@property (readwrite, nullable) UIImage *image;
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user