mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-23 01:52:44 +02:00
Merge MatrixKit develop with commit hash: b85b736313bec0592bd1cabc68035d97f5331137
This commit is contained in:
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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 <MatrixSDK/MatrixSDK.h>
|
||||
|
||||
@class MXKAccount;
|
||||
|
||||
/**
|
||||
Posted when account user information (display name, picture, presence) has been updated.
|
||||
The notification object is the matrix user id of the account.
|
||||
*/
|
||||
extern NSString *const kMXKAccountUserInfoDidChangeNotification;
|
||||
|
||||
/**
|
||||
Posted when the activity of the Apple Push Notification Service has been changed.
|
||||
The notification object is the matrix user id of the account.
|
||||
*/
|
||||
extern NSString *const kMXKAccountAPNSActivityDidChangeNotification;
|
||||
|
||||
/**
|
||||
Posted when the activity of the Push notification based on PushKit has been changed.
|
||||
The notification object is the matrix user id of the account.
|
||||
*/
|
||||
extern NSString *const kMXKAccountPushKitActivityDidChangeNotification;
|
||||
|
||||
/**
|
||||
MXKAccount error domain
|
||||
*/
|
||||
extern NSString *const kMXKAccountErrorDomain;
|
||||
|
||||
/**
|
||||
Block called when a certificate change is observed during authentication challenge from a server.
|
||||
|
||||
@param mxAccount the account concerned by this certificate change.
|
||||
@param certificate the server certificate to evaluate.
|
||||
@return YES to accept/trust this certificate, NO to cancel/ignore it.
|
||||
*/
|
||||
typedef BOOL (^MXKAccountOnCertificateChange)(MXKAccount *mxAccount, NSData *certificate);
|
||||
|
||||
/**
|
||||
`MXKAccount` object contains the credentials of a logged matrix user. It is used to handle matrix
|
||||
session and presence for this user.
|
||||
*/
|
||||
@interface MXKAccount : NSObject <NSCoding>
|
||||
|
||||
/**
|
||||
The account's credentials: homeserver, access token, user id.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXCredentials *mxCredentials;
|
||||
|
||||
/**
|
||||
The identity server URL.
|
||||
*/
|
||||
@property (nonatomic) NSString *identityServerURL;
|
||||
|
||||
/**
|
||||
The antivirus server URL, if any (nil by default).
|
||||
Set a non-null url to configure the antivirus scanner use.
|
||||
*/
|
||||
@property (nonatomic) NSString *antivirusServerURL;
|
||||
|
||||
/**
|
||||
The Push Gateway URL used to send event notifications to (nil by default).
|
||||
This URL should be over HTTPS and never over HTTP.
|
||||
*/
|
||||
@property (nonatomic) NSString *pushGatewayURL;
|
||||
|
||||
/**
|
||||
The matrix REST client used to make matrix API requests.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXRestClient *mxRestClient;
|
||||
|
||||
/**
|
||||
The matrix session opened with the account (nil by default).
|
||||
*/
|
||||
@property (nonatomic, readonly) MXSession *mxSession;
|
||||
|
||||
/**
|
||||
The account user's display name (nil by default, available if matrix session `mxSession` is opened).
|
||||
The notification `kMXKAccountUserInfoDidChangeNotification` is posted in case of change of this property.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *userDisplayName;
|
||||
|
||||
/**
|
||||
The account user's avatar url (nil by default, available if matrix session `mxSession` is opened).
|
||||
The notification `kMXKAccountUserInfoDidChangeNotification` is posted in case of change of this property.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *userAvatarUrl;
|
||||
|
||||
/**
|
||||
The account display name based on user id and user displayname (if any).
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *fullDisplayName;
|
||||
|
||||
/**
|
||||
The 3PIDs linked to this account.
|
||||
[self load3PIDs] must be called to update the property.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<MXThirdPartyIdentifier *> *threePIDs;
|
||||
|
||||
/**
|
||||
The email addresses linked to this account.
|
||||
This is a subset of self.threePIDs.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<NSString *> *linkedEmails;
|
||||
|
||||
/**
|
||||
The phone numbers linked to this account.
|
||||
This is a subset of self.threePIDs.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<NSString *> *linkedPhoneNumbers;
|
||||
|
||||
/**
|
||||
The account user's device.
|
||||
[self loadDeviceInformation] must be called to update the property.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXDevice *device;
|
||||
|
||||
/**
|
||||
The account user's presence (`MXPresenceUnknown` by default, available if matrix session `mxSession` is opened).
|
||||
The notification `kMXKAccountUserInfoDidChangeNotification` is posted in case of change of this property.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXPresence userPresence;
|
||||
|
||||
/**
|
||||
The account user's tint color: a unique color fixed by the user id. This tint color may be used to highlight
|
||||
rooms which belong to this account's user.
|
||||
*/
|
||||
@property (nonatomic, readonly) UIColor *userTintColor;
|
||||
|
||||
/**
|
||||
The Apple Push Notification Service activity for this account. YES when APNS is turned on (locally available and synced with server).
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL pushNotificationServiceIsActive;
|
||||
|
||||
/**
|
||||
Transient information storage.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSMutableDictionary<NSString *, id<NSCoding>> *others;
|
||||
|
||||
/**
|
||||
Enable Push notification based on Apple Push Notification Service (APNS).
|
||||
|
||||
This method creates or removes a pusher on the homeserver.
|
||||
|
||||
@param enable YES to enable it.
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)enablePushNotifications:(BOOL)enable
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *))failure;
|
||||
|
||||
/**
|
||||
Flag to indicate that an APNS pusher has been set on the homeserver for this device.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL hasPusherForPushNotifications;
|
||||
|
||||
/**
|
||||
The Push notification activity (based on PushKit) for this account.
|
||||
YES when Push is turned on (locally available and enabled homeserver side).
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL isPushKitNotificationActive;
|
||||
|
||||
/**
|
||||
Enable Push notification based on PushKit.
|
||||
|
||||
This method creates or removes a pusher on the homeserver.
|
||||
|
||||
@param enable YES to enable it.
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)enablePushKitNotifications:(BOOL)enable
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *))failure;
|
||||
|
||||
/**
|
||||
Flag to indicate that a PushKit pusher has been set on the homeserver for this device.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL hasPusherForPushKitNotifications;
|
||||
|
||||
|
||||
/**
|
||||
Enable In-App notifications based on Remote notifications rules.
|
||||
NO by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL enableInAppNotifications;
|
||||
|
||||
/**
|
||||
Disable the account without logging out (NO by default).
|
||||
|
||||
A matrix session is automatically opened for the account when this property is toggled from YES to NO.
|
||||
The session is closed when this property is set to YES.
|
||||
*/
|
||||
@property (nonatomic,getter=isDisabled) BOOL disabled;
|
||||
|
||||
/**
|
||||
Manage the online presence event.
|
||||
|
||||
The presence event must not be sent if the application is launched by a push notification.
|
||||
*/
|
||||
@property (nonatomic) BOOL hideUserPresence;
|
||||
|
||||
/**
|
||||
Flag indicating if the end user has been warned about encryption and its limitations.
|
||||
*/
|
||||
@property (nonatomic,getter=isWarnedAboutEncryption) BOOL warnedAboutEncryption;
|
||||
|
||||
/**
|
||||
Register the MXKAccountOnCertificateChange block that will be used to handle certificate change during account use.
|
||||
This block is nil by default, any new certificate is ignored/untrusted (this will abort the connection to the server).
|
||||
|
||||
@param onCertificateChangeBlock the block that will be used to handle certificate change.
|
||||
*/
|
||||
+ (void)registerOnCertificateChangeBlock:(MXKAccountOnCertificateChange)onCertificateChangeBlock;
|
||||
|
||||
/**
|
||||
Get the color code related to a specific presence.
|
||||
|
||||
@param presence a user presence
|
||||
@return color defined for the provided presence (nil if no color is defined).
|
||||
*/
|
||||
+ (UIColor*)presenceColor:(MXPresence)presence;
|
||||
|
||||
/**
|
||||
Init `MXKAccount` instance with credentials. No matrix session is opened by default.
|
||||
|
||||
@param credentials user's credentials
|
||||
*/
|
||||
- (instancetype)initWithCredentials:(MXCredentials*)credentials;
|
||||
|
||||
/**
|
||||
Create a matrix session based on the provided store.
|
||||
When store data is ready, the live stream is automatically launched by synchronising the session with the server.
|
||||
|
||||
In case of failure during server sync, the method is reiterated until the data is up-to-date with the server.
|
||||
This loop is stopped if you call [MXCAccount closeSession:], it is suspended if you call [MXCAccount pauseInBackgroundTask].
|
||||
|
||||
@param store the store to use for the session.
|
||||
*/
|
||||
-(void)openSessionWithStore:(id<MXStore>)store;
|
||||
|
||||
/**
|
||||
Close the matrix session.
|
||||
|
||||
@param clearStore set YES to delete all store data.
|
||||
*/
|
||||
- (void)closeSession:(BOOL)clearStore;
|
||||
|
||||
/**
|
||||
Invalidate the access token, close the matrix session and delete all store data.
|
||||
|
||||
@note This method is equivalent to `logoutSendingServerRequest:completion:` with `sendLogoutServerRequest` parameter to YES
|
||||
|
||||
@param completion the block to execute at the end of the operation (independently if it succeeded or not).
|
||||
*/
|
||||
- (void)logout:(void (^)(void))completion;
|
||||
|
||||
/**
|
||||
Invalidate the access token, close the matrix session and delete all store data.
|
||||
|
||||
@param sendLogoutServerRequest indicate to send logout request to homeserver.
|
||||
@param completion the block to execute at the end of the operation (independently if it succeeded or not).
|
||||
*/
|
||||
- (void)logoutSendingServerRequest:(BOOL)sendLogoutServerRequest
|
||||
completion:(void (^)(void))completion;
|
||||
|
||||
|
||||
#pragma mark - Soft logout
|
||||
|
||||
/**
|
||||
Flag to indicate if the account has been logged out by the homeserver admin.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL isSoftLogout;
|
||||
|
||||
/**
|
||||
Soft logout the account.
|
||||
|
||||
The matix session is stopped but the data is kept.
|
||||
*/
|
||||
- (void)softLogout;
|
||||
|
||||
/**
|
||||
Hydrate the account using the credentials provided.
|
||||
|
||||
@param credentials the new credentials.
|
||||
*/
|
||||
- (void)hydrateWithCredentials:(MXCredentials*)credentials;
|
||||
|
||||
/**
|
||||
Pause the current matrix session.
|
||||
|
||||
@warning: This matrix session is paused without using background task if no background mode handler
|
||||
is set in the MXSDKOptions sharedInstance (see `backgroundModeHandler`).
|
||||
*/
|
||||
- (void)pauseInBackgroundTask;
|
||||
|
||||
/**
|
||||
Perform a background sync by keeping the user offline.
|
||||
|
||||
@warning: This operation failed when no background mode handler is set in the
|
||||
MXSDKOptions sharedInstance (see `backgroundModeHandler`).
|
||||
|
||||
@param timeout the timeout in milliseconds.
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)backgroundSync:(unsigned int)timeout success:(void (^)(void))success failure:(void (^)(NSError *))failure;
|
||||
|
||||
/**
|
||||
Resume the current matrix session.
|
||||
*/
|
||||
- (void)resume;
|
||||
|
||||
/**
|
||||
Close the potential matrix session and open a new one if the account is not disabled.
|
||||
|
||||
@param clearCache set YES to delete all store data.
|
||||
*/
|
||||
- (void)reload:(BOOL)clearCache;
|
||||
|
||||
/**
|
||||
Set the display name of the account user.
|
||||
|
||||
@param displayname the new display name.
|
||||
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)setUserDisplayName:(NSString*)displayname success:(void (^)(void))success failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Set the avatar url of the account user.
|
||||
|
||||
@param avatarUrl the new avatar url.
|
||||
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)setUserAvatarUrl:(NSString*)avatarUrl success:(void (^)(void))success failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Update the account password.
|
||||
|
||||
@param oldPassword the old password.
|
||||
@param newPassword the new password.
|
||||
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)changePassword:(NSString*)oldPassword with:(NSString*)newPassword success:(void (^)(void))success failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Load the 3PIDs linked to this account.
|
||||
This method must be called to refresh self.threePIDs and self.linkedEmails.
|
||||
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)load3PIDs:(void (^)(void))success failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Load the current device information for this account.
|
||||
This method must be called to refresh self.device.
|
||||
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)loadDeviceInformation:(void (^)(void))success failure:(void (^)(NSError *error))failure;
|
||||
|
||||
#pragma mark - Push notification listeners
|
||||
/**
|
||||
Register a listener to push notifications for the account's session.
|
||||
|
||||
The listener will be called when a push rule matches a live event.
|
||||
Note: only one listener is supported. Potential existing listener is removed.
|
||||
|
||||
You may use `[MXCAccount updateNotificationListenerForRoomId:]` to disable/enable all notifications from a specific room.
|
||||
|
||||
@param onNotification the block that will be called once a live event matches a push rule.
|
||||
*/
|
||||
- (void)listenToNotifications:(MXOnNotification)onNotification;
|
||||
|
||||
/**
|
||||
Unregister the listener.
|
||||
*/
|
||||
- (void)removeNotificationListener;
|
||||
|
||||
/**
|
||||
Update the listener to ignore or restore notifications from a specific room.
|
||||
|
||||
@param roomID the id of the concerned room.
|
||||
@param isIgnored YES to disable notifications from the specified room. NO to restore them.
|
||||
*/
|
||||
- (void)updateNotificationListenerForRoomId:(NSString*)roomID ignore:(BOOL)isIgnored;
|
||||
|
||||
#pragma mark - Crypto
|
||||
/**
|
||||
Delete the device id.
|
||||
|
||||
Call this method when the current device id cannot be used anymore.
|
||||
*/
|
||||
- (void)resetDeviceId;
|
||||
|
||||
#pragma mark - Sync filter
|
||||
/**
|
||||
Check if the homeserver supports room members lazy loading.
|
||||
@param completion the check result.
|
||||
*/
|
||||
- (void)supportLazyLoadOfRoomMembers:(void (^)(BOOL supportLazyLoadOfRoomMembers))completion;
|
||||
|
||||
/**
|
||||
Call this method at an appropriate time to attempt dehydrating to a new backup device
|
||||
*/
|
||||
- (void)attemptDeviceDehydrationWithKeyData:(NSData *)keyData
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2018 New Vector 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 <Foundation/Foundation.h>
|
||||
|
||||
#import "MXKAccount.h"
|
||||
|
||||
/**
|
||||
Posted when the user logged in with a matrix account.
|
||||
The notification object is the new added account.
|
||||
*/
|
||||
extern NSString *const kMXKAccountManagerDidAddAccountNotification;
|
||||
|
||||
/**
|
||||
Posted when an existing account is logged out.
|
||||
The notification object is the removed account.
|
||||
*/
|
||||
extern NSString *const kMXKAccountManagerDidRemoveAccountNotification;
|
||||
|
||||
/**
|
||||
Posted when an existing account is soft logged out.
|
||||
The notification object is the account.
|
||||
*/
|
||||
extern NSString *const kMXKAccountManagerDidSoftlogoutAccountNotification;
|
||||
|
||||
/**
|
||||
Used to identify the type of data when requesting MXKeyProvider
|
||||
*/
|
||||
extern NSString *const MXKAccountManagerDataType;
|
||||
|
||||
/**
|
||||
`MXKAccountManager` manages a pool of `MXKAccount` instances.
|
||||
*/
|
||||
@interface MXKAccountManager : NSObject
|
||||
|
||||
/**
|
||||
The class of store used to open matrix session for the accounts. This class must be conformed to MXStore protocol.
|
||||
By default this class is MXFileStore.
|
||||
*/
|
||||
@property (nonatomic) Class storeClass;
|
||||
|
||||
/**
|
||||
List of all available accounts (enabled and disabled).
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<MXKAccount *> *accounts;
|
||||
|
||||
/**
|
||||
List of active accounts (only enabled accounts)
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<MXKAccount *> *activeAccounts;
|
||||
|
||||
/**
|
||||
The device token used for Apple Push Notification Service registration.
|
||||
*/
|
||||
@property (nonatomic, copy) NSData *apnsDeviceToken;
|
||||
|
||||
/**
|
||||
The APNS status: YES when app is registered for remote notif, and device token is known.
|
||||
*/
|
||||
@property (nonatomic) BOOL isAPNSAvailable;
|
||||
|
||||
/**
|
||||
The device token used for Push notifications registration (PushKit support).
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSData *pushDeviceToken;
|
||||
|
||||
/**
|
||||
The current options of the Push notifications based on PushKit.
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSDictionary *pushOptions;
|
||||
|
||||
/**
|
||||
Set the push token and the potential push options.
|
||||
For example, for clients that want to go & fetch the body of the event themselves anyway,
|
||||
the key-value `format: event_id_only` may be used in `pushOptions` dictionary to tell the
|
||||
HTTP pusher to send just the event_id of the event it's notifying about, the room id and
|
||||
the notification counts.
|
||||
|
||||
@param pushDeviceToken the push token.
|
||||
@param pushOptions dictionary of the push options (may be nil).
|
||||
*/
|
||||
- (void)setPushDeviceToken:(NSData *)pushDeviceToken withPushOptions:(NSDictionary *)pushOptions;
|
||||
|
||||
/**
|
||||
The PushKit status: YES when app is registered for push notif, and push token is known.
|
||||
*/
|
||||
@property (nonatomic) BOOL isPushAvailable;
|
||||
|
||||
@property (nonatomic, readonly) MXDehydrationService *dehydrationService;
|
||||
|
||||
/**
|
||||
Retrieve the MXKAccounts manager.
|
||||
|
||||
@return the MXKAccounts manager.
|
||||
*/
|
||||
+ (MXKAccountManager *)sharedManager;
|
||||
|
||||
/**
|
||||
Check for each enabled account if a matrix session is already opened.
|
||||
Open a matrix session for each enabled account which doesn't have a session.
|
||||
The developper must set 'storeClass' before the first call of this method
|
||||
if the default class is not suitable.
|
||||
*/
|
||||
- (void)prepareSessionForActiveAccounts;
|
||||
|
||||
/**
|
||||
Save a snapshot of the current accounts.
|
||||
*/
|
||||
- (void)saveAccounts;
|
||||
|
||||
/**
|
||||
Add an account and save the new account list. Optionally a matrix session may be opened for the provided account.
|
||||
|
||||
@param account a matrix account.
|
||||
@param openSession YES to open a matrix session (this value is ignored if the account is disabled).
|
||||
*/
|
||||
- (void)addAccount:(MXKAccount *)account andOpenSession:(BOOL)openSession;
|
||||
|
||||
/**
|
||||
Remove the provided account and save the new account list. This method is used in case of logout.
|
||||
|
||||
@note equivalent to `removeAccount:sendLogoutRequest:completion:` method with `sendLogoutRequest` parameter to YES
|
||||
|
||||
@param account a matrix account.
|
||||
@param completion the block to execute at the end of the operation.
|
||||
*/
|
||||
- (void)removeAccount:(MXKAccount *)account completion:(void (^)(void))completion;
|
||||
|
||||
|
||||
/**
|
||||
Remove the provided account and save the new account list. This method is used in case of logout or account deactivation.
|
||||
|
||||
@param account a matrix account.
|
||||
@param sendLogoutRequest Indicate whether send logout request to homeserver.
|
||||
@param completion the block to execute at the end of the operation.
|
||||
*/
|
||||
- (void)removeAccount:(MXKAccount*)account
|
||||
sendLogoutRequest:(BOOL)sendLogoutRequest
|
||||
completion:(void (^)(void))completion;
|
||||
|
||||
/**
|
||||
Log out and remove all the existing accounts
|
||||
|
||||
@param completion the block to execute at the end of the operation.
|
||||
*/
|
||||
- (void)logoutWithCompletion:(void (^)(void))completion;
|
||||
|
||||
/**
|
||||
Soft logout an account.
|
||||
|
||||
@param account a matrix account.
|
||||
*/
|
||||
- (void)softLogout:(MXKAccount*)account;
|
||||
|
||||
/**
|
||||
Hydrate an existing account by using the credentials provided.
|
||||
|
||||
This updates account credentials and restarts the account session
|
||||
|
||||
If the credentials belong to a different user from the account already stored,
|
||||
the old account will be cleared automatically.
|
||||
|
||||
@param account a matrix account.
|
||||
@param credentials the new credentials.
|
||||
*/
|
||||
- (void)hydrateAccount:(MXKAccount*)account withCredentials:(MXCredentials*)credentials;
|
||||
|
||||
/**
|
||||
Retrieve the account for a user id.
|
||||
|
||||
@param userId the user id.
|
||||
@return the user's account (nil if no account exist).
|
||||
*/
|
||||
- (MXKAccount *)accountForUserId:(NSString *)userId;
|
||||
|
||||
/**
|
||||
Retrieve an account that knows the room with the passed id or alias.
|
||||
|
||||
Note: The method is not accurate as it returns the first account that matches.
|
||||
|
||||
@param roomIdOrAlias the room id or alias.
|
||||
@return the user's account. Nil if no account matches.
|
||||
*/
|
||||
- (MXKAccount *)accountKnowingRoomWithRoomIdOrAlias:(NSString *)roomIdOrAlias;
|
||||
|
||||
/**
|
||||
Retrieve an account that knows the user with the passed id.
|
||||
|
||||
Note: The method is not accurate as it returns the first account that matches.
|
||||
|
||||
@param userId the user id.
|
||||
@return the user's account. Nil if no account matches.
|
||||
*/
|
||||
- (MXKAccount *)accountKnowingUserWithUserId:(NSString *)userId;
|
||||
|
||||
/**
|
||||
Force the account manager to reload existing accounts from the local storage.
|
||||
The account manager is supposed to handle itself the list of the accounts.
|
||||
Call this method only when an account has been changed from an other application from the same group.
|
||||
*/
|
||||
- (void)forceReloadAccounts;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,726 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector 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 "MXKAccountManager.h"
|
||||
#import "MXKAppSettings.h"
|
||||
|
||||
#import "MXKTools.h"
|
||||
|
||||
static NSString *const kMXKAccountsKeyOld = @"accounts";
|
||||
static NSString *const kMXKAccountsKey = @"accountsV2";
|
||||
|
||||
NSString *const kMXKAccountManagerDidAddAccountNotification = @"kMXKAccountManagerDidAddAccountNotification";
|
||||
NSString *const kMXKAccountManagerDidRemoveAccountNotification = @"kMXKAccountManagerDidRemoveAccountNotification";
|
||||
NSString *const kMXKAccountManagerDidSoftlogoutAccountNotification = @"kMXKAccountManagerDidSoftlogoutAccountNotification";
|
||||
NSString *const MXKAccountManagerDataType = @"org.matrix.kit.MXKAccountManagerDataType";
|
||||
|
||||
@interface MXKAccountManager()
|
||||
{
|
||||
/**
|
||||
The list of all accounts (enabled and disabled). Each value is a `MXKAccount` instance.
|
||||
*/
|
||||
NSMutableArray<MXKAccount *> *mxAccounts;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKAccountManager
|
||||
|
||||
+ (MXKAccountManager *)sharedManager
|
||||
{
|
||||
static MXKAccountManager *sharedAccountManager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedAccountManager = [[super allocWithZone:NULL] init];
|
||||
});
|
||||
|
||||
return sharedAccountManager;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
_storeClass = [MXFileStore class];
|
||||
_dehydrationService = [MXDehydrationService new];
|
||||
|
||||
// Migrate old account file to new format
|
||||
[self migrateAccounts];
|
||||
|
||||
// Load existing accounts from local storage
|
||||
[self loadAccounts];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
mxAccounts = nil;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)prepareSessionForActiveAccounts
|
||||
{
|
||||
for (MXKAccount *account in mxAccounts)
|
||||
{
|
||||
// Check whether the account is enabled. Open a new matrix session if none.
|
||||
if (!account.isDisabled && !account.isSoftLogout && !account.mxSession)
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager] openSession for %@ account", account.mxCredentials.userId);
|
||||
|
||||
id<MXStore> store = [[_storeClass alloc] init];
|
||||
[account openSessionWithStore:store];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)saveAccounts
|
||||
{
|
||||
NSDate *startDate = [NSDate date];
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager] saveAccounts...");
|
||||
|
||||
NSMutableData *data = [NSMutableData data];
|
||||
NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
|
||||
|
||||
[encoder encodeObject:mxAccounts forKey:@"mxAccounts"];
|
||||
|
||||
[encoder finishEncoding];
|
||||
|
||||
[data setData:[self encryptData:data]];
|
||||
|
||||
BOOL result = [data writeToFile:[self accountFile] atomically:YES];
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager] saveAccounts. Done (result: %@) in %.0fms", @(result), [[NSDate date] timeIntervalSinceDate:startDate] * 1000);
|
||||
}
|
||||
|
||||
- (void)addAccount:(MXKAccount *)account andOpenSession:(BOOL)openSession
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager] login (%@)", account.mxCredentials.userId);
|
||||
|
||||
[mxAccounts addObject:account];
|
||||
[self saveAccounts];
|
||||
|
||||
// Check conditions to open a matrix session
|
||||
if (openSession && !account.disabled)
|
||||
{
|
||||
// Open a new matrix session by default
|
||||
MXLogDebug(@"[MXKAccountManager] openSession for %@ account (device %@)", account.mxCredentials.userId, account.mxCredentials.deviceId);
|
||||
id<MXStore> store = [[_storeClass alloc] init];
|
||||
[account openSessionWithStore:store];
|
||||
}
|
||||
|
||||
// Post notification
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountManagerDidAddAccountNotification object:account userInfo:nil];
|
||||
}
|
||||
|
||||
- (void)removeAccount:(MXKAccount*)theAccount completion:(void (^)(void))completion
|
||||
{
|
||||
[self removeAccount:theAccount sendLogoutRequest:YES completion:completion];
|
||||
}
|
||||
|
||||
- (void)removeAccount:(MXKAccount*)theAccount
|
||||
sendLogoutRequest:(BOOL)sendLogoutRequest
|
||||
completion:(void (^)(void))completion
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager] logout (%@), send logout request to homeserver: %d", theAccount.mxCredentials.userId, sendLogoutRequest);
|
||||
|
||||
// Close session and clear associated store.
|
||||
[theAccount logoutSendingServerRequest:sendLogoutRequest completion:^{
|
||||
|
||||
// Retrieve the corresponding account in the internal array
|
||||
MXKAccount* removedAccount = nil;
|
||||
|
||||
for (MXKAccount *account in self->mxAccounts)
|
||||
{
|
||||
if ([account.mxCredentials.userId isEqualToString:theAccount.mxCredentials.userId])
|
||||
{
|
||||
removedAccount = account;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (removedAccount)
|
||||
{
|
||||
[self->mxAccounts removeObject:removedAccount];
|
||||
|
||||
[self saveAccounts];
|
||||
|
||||
// Post notification
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountManagerDidRemoveAccountNotification object:removedAccount userInfo:nil];
|
||||
}
|
||||
|
||||
if (completion)
|
||||
{
|
||||
completion();
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
- (void)logoutWithCompletion:(void (^)(void))completion
|
||||
{
|
||||
// Logout one by one the existing accounts
|
||||
if (mxAccounts.count)
|
||||
{
|
||||
[self removeAccount:mxAccounts.lastObject completion:^{
|
||||
|
||||
// loop: logout the next existing account (if any)
|
||||
[self logoutWithCompletion:completion];
|
||||
|
||||
}];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
NSUserDefaults *sharedUserDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults;
|
||||
|
||||
// Remove APNS device token
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"apnsDeviceToken"];
|
||||
|
||||
// Remove Push device token
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushDeviceToken"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
|
||||
|
||||
// Be sure that no account survive in local storage
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kMXKAccountsKey];
|
||||
[sharedUserDefaults removeObjectForKey:kMXKAccountsKey];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:[self accountFile] error:nil];
|
||||
|
||||
if (completion)
|
||||
{
|
||||
completion();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)softLogout:(MXKAccount*)account
|
||||
{
|
||||
[account softLogout];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountManagerDidSoftlogoutAccountNotification
|
||||
object:account
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
- (void)hydrateAccount:(MXKAccount*)account withCredentials:(MXCredentials*)credentials
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager] hydrateAccount: %@", account.mxCredentials.userId);
|
||||
|
||||
if ([account.mxCredentials.userId isEqualToString:credentials.userId])
|
||||
{
|
||||
// Restart the account
|
||||
[account hydrateWithCredentials:credentials];
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager] hydrateAccount: Open session");
|
||||
|
||||
id<MXStore> store = [[_storeClass alloc] init];
|
||||
[account openSessionWithStore:store];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountManagerDidAddAccountNotification
|
||||
object:account
|
||||
userInfo:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager] hydrateAccount: Credentials given for another account: %@", credentials.userId);
|
||||
|
||||
// Logout the old account and create a new one with the new credentials
|
||||
[self removeAccount:account sendLogoutRequest:YES completion:nil];
|
||||
|
||||
MXKAccount *newAccount = [[MXKAccount alloc] initWithCredentials:credentials];
|
||||
[self addAccount:newAccount andOpenSession:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (MXKAccount *)accountForUserId:(NSString *)userId
|
||||
{
|
||||
for (MXKAccount *account in mxAccounts)
|
||||
{
|
||||
if ([account.mxCredentials.userId isEqualToString:userId])
|
||||
{
|
||||
return account;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (MXKAccount *)accountKnowingRoomWithRoomIdOrAlias:(NSString *)roomIdOrAlias
|
||||
{
|
||||
MXKAccount *theAccount = nil;
|
||||
|
||||
NSArray *activeAccounts = self.activeAccounts;
|
||||
|
||||
for (MXKAccount *account in activeAccounts)
|
||||
{
|
||||
if ([roomIdOrAlias hasPrefix:@"#"])
|
||||
{
|
||||
if ([account.mxSession roomWithAlias:roomIdOrAlias])
|
||||
{
|
||||
theAccount = account;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([account.mxSession roomWithRoomId:roomIdOrAlias])
|
||||
{
|
||||
theAccount = account;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return theAccount;
|
||||
}
|
||||
|
||||
- (MXKAccount *)accountKnowingUserWithUserId:(NSString *)userId
|
||||
{
|
||||
MXKAccount *theAccount = nil;
|
||||
|
||||
NSArray *activeAccounts = self.activeAccounts;
|
||||
|
||||
for (MXKAccount *account in activeAccounts)
|
||||
{
|
||||
if ([account.mxSession userWithUserId:userId])
|
||||
{
|
||||
theAccount = account;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return theAccount;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)setStoreClass:(Class)storeClass
|
||||
{
|
||||
// Sanity check
|
||||
NSAssert([storeClass conformsToProtocol:@protocol(MXStore)], @"MXKAccountManager only manages store class that conforms to MXStore protocol");
|
||||
|
||||
_storeClass = storeClass;
|
||||
}
|
||||
|
||||
- (NSArray<MXKAccount *> *)accounts
|
||||
{
|
||||
return [mxAccounts copy];
|
||||
}
|
||||
|
||||
- (NSArray<MXKAccount *> *)activeAccounts
|
||||
{
|
||||
NSMutableArray *activeAccounts = [NSMutableArray arrayWithCapacity:mxAccounts.count];
|
||||
for (MXKAccount *account in mxAccounts)
|
||||
{
|
||||
if (!account.disabled && !account.isSoftLogout)
|
||||
{
|
||||
[activeAccounts addObject:account];
|
||||
}
|
||||
}
|
||||
return activeAccounts;
|
||||
}
|
||||
|
||||
- (NSData *)apnsDeviceToken
|
||||
{
|
||||
NSData *token = [[NSUserDefaults standardUserDefaults] objectForKey:@"apnsDeviceToken"];
|
||||
if (!token.length)
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"apnsDeviceToken"];
|
||||
token = nil;
|
||||
}
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager][Push] apnsDeviceToken: %@", [MXKTools logForPushToken:token]);
|
||||
return token;
|
||||
}
|
||||
|
||||
- (void)setApnsDeviceToken:(NSData *)apnsDeviceToken
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: %@", [MXKTools logForPushToken:apnsDeviceToken]);
|
||||
|
||||
NSData *oldToken = self.apnsDeviceToken;
|
||||
if (!apnsDeviceToken.length)
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: reset APNS device token");
|
||||
|
||||
if (oldToken)
|
||||
{
|
||||
// turn off the Apns flag for all accounts if any
|
||||
for (MXKAccount *account in mxAccounts)
|
||||
{
|
||||
[account enablePushNotifications:NO success:nil failure:nil];
|
||||
}
|
||||
}
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"apnsDeviceToken"];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSArray *activeAccounts = self.activeAccounts;
|
||||
|
||||
if (!oldToken)
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: set APNS device token");
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setObject:apnsDeviceToken forKey:@"apnsDeviceToken"];
|
||||
|
||||
// turn on the Apns flag for all accounts, when the Apns registration succeeds for the first time
|
||||
for (MXKAccount *account in activeAccounts)
|
||||
{
|
||||
[account enablePushNotifications:YES success:nil failure:nil];
|
||||
}
|
||||
}
|
||||
else if (![oldToken isEqualToData:apnsDeviceToken])
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: update APNS device token");
|
||||
|
||||
NSMutableArray<MXKAccount*> *accountsWithAPNSPusher = [NSMutableArray new];
|
||||
|
||||
// Delete the pushers related to the old token
|
||||
for (MXKAccount *account in activeAccounts)
|
||||
{
|
||||
if (account.hasPusherForPushNotifications)
|
||||
{
|
||||
[accountsWithAPNSPusher addObject:account];
|
||||
}
|
||||
|
||||
[account enablePushNotifications:NO success:nil failure:nil];
|
||||
}
|
||||
|
||||
// Update the token
|
||||
[[NSUserDefaults standardUserDefaults] setObject:apnsDeviceToken forKey:@"apnsDeviceToken"];
|
||||
|
||||
// Refresh pushers with the new token.
|
||||
for (MXKAccount *account in activeAccounts)
|
||||
{
|
||||
if ([accountsWithAPNSPusher containsObject:account])
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: Resync APNS for %@ account", account.mxCredentials.userId);
|
||||
[account enablePushNotifications:YES success:nil failure:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: hasPusherForPushNotifications = NO for %@ account. Do not enable Push", account.mxCredentials.userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: Same token. Nothing to do.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isAPNSAvailable
|
||||
{
|
||||
// [UIApplication isRegisteredForRemoteNotifications] tells whether your app can receive
|
||||
// remote notifications or not. Receiving remote notifications does not guarantee it will
|
||||
// display them to the user as they may have notifications set to deliver quietly.
|
||||
|
||||
BOOL isRemoteNotificationsAllowed = NO;
|
||||
|
||||
UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)];
|
||||
if (sharedApplication)
|
||||
{
|
||||
isRemoteNotificationsAllowed = [sharedApplication isRegisteredForRemoteNotifications];
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager][Push] isAPNSAvailable: The user %@ remote notification", (isRemoteNotificationsAllowed ? @"allowed" : @"denied"));
|
||||
}
|
||||
|
||||
BOOL isAPNSAvailable = (isRemoteNotificationsAllowed && self.apnsDeviceToken);
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager][Push] isAPNSAvailable: %@", @(isAPNSAvailable));
|
||||
|
||||
return isAPNSAvailable;
|
||||
}
|
||||
|
||||
- (NSData *)pushDeviceToken
|
||||
{
|
||||
NSData *token = [[NSUserDefaults standardUserDefaults] objectForKey:@"pushDeviceToken"];
|
||||
if (!token.length)
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushDeviceToken"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
|
||||
token = nil;
|
||||
}
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager][Push] pushDeviceToken: %@", [MXKTools logForPushToken:token]);
|
||||
return token;
|
||||
}
|
||||
|
||||
- (NSDictionary *)pushOptions
|
||||
{
|
||||
NSDictionary *pushOptions = [[NSUserDefaults standardUserDefaults] objectForKey:@"pushOptions"];
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager][Push] pushOptions: %@", pushOptions);
|
||||
return pushOptions;
|
||||
}
|
||||
|
||||
- (void)setPushDeviceToken:(NSData *)pushDeviceToken withPushOptions:(NSDictionary *)pushOptions
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: %@ withPushOptions: %@", [MXKTools logForPushToken:pushDeviceToken], pushOptions);
|
||||
|
||||
NSData *oldToken = self.pushDeviceToken;
|
||||
if (!pushDeviceToken.length)
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Reset Push device token");
|
||||
|
||||
if (oldToken)
|
||||
{
|
||||
// turn off the Push flag for all accounts if any
|
||||
for (MXKAccount *account in mxAccounts)
|
||||
{
|
||||
[account enablePushKitNotifications:NO success:^{
|
||||
// make sure pusher really removed before losing token.
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushDeviceToken"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
|
||||
} failure:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSArray *activeAccounts = self.activeAccounts;
|
||||
|
||||
if (!oldToken)
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Set Push device token");
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setObject:pushDeviceToken forKey:@"pushDeviceToken"];
|
||||
if (pushOptions)
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:pushOptions forKey:@"pushOptions"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
|
||||
}
|
||||
|
||||
// turn on the Push flag for all accounts
|
||||
for (MXKAccount *account in activeAccounts)
|
||||
{
|
||||
[account enablePushKitNotifications:YES success:nil failure:nil];
|
||||
}
|
||||
}
|
||||
else if (![oldToken isEqualToData:pushDeviceToken])
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Update Push device token");
|
||||
|
||||
NSMutableArray<MXKAccount*> *accountsWithPushKitPusher = [NSMutableArray new];
|
||||
|
||||
// Delete the pushers related to the old token
|
||||
for (MXKAccount *account in activeAccounts)
|
||||
{
|
||||
if (account.hasPusherForPushKitNotifications)
|
||||
{
|
||||
[accountsWithPushKitPusher addObject:account];
|
||||
}
|
||||
|
||||
[account enablePushKitNotifications:NO success:nil failure:nil];
|
||||
}
|
||||
|
||||
// Update the token
|
||||
[[NSUserDefaults standardUserDefaults] setObject:pushDeviceToken forKey:@"pushDeviceToken"];
|
||||
if (pushOptions)
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:pushOptions forKey:@"pushOptions"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
|
||||
}
|
||||
|
||||
// Refresh pushers with the new token.
|
||||
for (MXKAccount *account in activeAccounts)
|
||||
{
|
||||
if ([accountsWithPushKitPusher containsObject:account])
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Resync Push for %@ account", account.mxCredentials.userId);
|
||||
[account enablePushKitNotifications:YES success:nil failure:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: hasPusherForPushKitNotifications = NO for %@ account. Do not enable Push", account.mxCredentials.userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Same token. Nothing to do.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isPushAvailable
|
||||
{
|
||||
// [UIApplication isRegisteredForRemoteNotifications] tells whether your app can receive
|
||||
// remote notifications or not. Receiving remote notifications does not guarantee it will
|
||||
// display them to the user as they may have notifications set to deliver quietly.
|
||||
|
||||
BOOL isRemoteNotificationsAllowed = NO;
|
||||
|
||||
UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)];
|
||||
if (sharedApplication)
|
||||
{
|
||||
isRemoteNotificationsAllowed = [sharedApplication isRegisteredForRemoteNotifications];
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager][Push] isPushAvailable: The user %@ remote notification", (isRemoteNotificationsAllowed ? @"allowed" : @"denied"));
|
||||
}
|
||||
|
||||
BOOL isPushAvailable = (isRemoteNotificationsAllowed && self.pushDeviceToken);
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager][Push] isPushAvailable: %@", @(isPushAvailable));
|
||||
return isPushAvailable;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
// Return the path of the file containing stored MXAccounts array
|
||||
- (NSString*)accountFile
|
||||
{
|
||||
NSString *matrixKitCacheFolder = [MXKAppSettings cacheFolder];
|
||||
return [matrixKitCacheFolder stringByAppendingPathComponent:kMXKAccountsKey];
|
||||
}
|
||||
|
||||
- (void)loadAccounts
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager] loadAccounts");
|
||||
|
||||
NSString *accountFile = [self accountFile];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:accountFile])
|
||||
{
|
||||
NSDate *startDate = [NSDate date];
|
||||
|
||||
NSError *error = nil;
|
||||
NSData* filecontent = [NSData dataWithContentsOfFile:accountFile options:(NSDataReadingMappedAlways | NSDataReadingUncached) error:&error];
|
||||
|
||||
if (!error)
|
||||
{
|
||||
// Decrypt data if encryption method is provided
|
||||
NSData *unciphered = [self decryptData:filecontent];
|
||||
NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:unciphered];
|
||||
mxAccounts = [decoder decodeObjectForKey:@"mxAccounts"];
|
||||
|
||||
if (!mxAccounts && [[MXKeyProvider sharedInstance] isEncryptionAvailableForDataOfType:MXKAccountManagerDataType])
|
||||
{
|
||||
// This happens if the V2 file has not been encrypted -> read file content then save encrypted accounts
|
||||
MXLogDebug(@"[MXKAccountManager] loadAccounts. Failed to read decrypted data: reading file data without encryption.");
|
||||
decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:filecontent];
|
||||
mxAccounts = [decoder decodeObjectForKey:@"mxAccounts"];
|
||||
|
||||
if (mxAccounts)
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager] loadAccounts. saving encrypted accounts");
|
||||
[self saveAccounts];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager] loadAccounts. %tu accounts loaded in %.0fms", mxAccounts.count, [[NSDate date] timeIntervalSinceDate:startDate] * 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Migration of accountData from sharedUserDefaults to a file
|
||||
NSUserDefaults *sharedDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults;
|
||||
|
||||
NSData *accountData = [sharedDefaults objectForKey:kMXKAccountsKey];
|
||||
if (!accountData)
|
||||
{
|
||||
// Migration of accountData from [NSUserDefaults standardUserDefaults], the first location storage
|
||||
accountData = [[NSUserDefaults standardUserDefaults] objectForKey:kMXKAccountsKey];
|
||||
}
|
||||
|
||||
if (accountData)
|
||||
{
|
||||
mxAccounts = [NSMutableArray arrayWithArray:[NSKeyedUnarchiver unarchiveObjectWithData:accountData]];
|
||||
[self saveAccounts];
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager] loadAccounts: performed data migration");
|
||||
|
||||
// Now that data has been migrated, erase old location of accountData
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kMXKAccountsKey];
|
||||
|
||||
[sharedDefaults removeObjectForKey:kMXKAccountsKey];
|
||||
}
|
||||
}
|
||||
|
||||
if (!mxAccounts)
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager] loadAccounts. No accounts");
|
||||
mxAccounts = [NSMutableArray array];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)forceReloadAccounts
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager] Force reload existing accounts from local storage");
|
||||
[self loadAccounts];
|
||||
}
|
||||
|
||||
- (NSData*)encryptData:(NSData*)data
|
||||
{
|
||||
// Exceptions are not caught as the key is always needed if the KeyProviderDelegate
|
||||
// is provided.
|
||||
MXKeyData *keyData = [[MXKeyProvider sharedInstance] requestKeyForDataOfType:MXKAccountManagerDataType isMandatory:YES expectedKeyType:kAes];
|
||||
if (keyData && [keyData isKindOfClass:[MXAesKeyData class]])
|
||||
{
|
||||
MXAesKeyData *aesKey = (MXAesKeyData *) keyData;
|
||||
NSData *cipher = [MXAes encrypt:data aesKey:aesKey.key iv:aesKey.iv error:nil];
|
||||
return cipher;
|
||||
}
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager] encryptData: no key method provided for encryption.");
|
||||
return data;
|
||||
}
|
||||
|
||||
- (NSData*)decryptData:(NSData*)data
|
||||
{
|
||||
// Exceptions are not cached as the key is always needed if the KeyProviderDelegate
|
||||
// is provided.
|
||||
MXKeyData *keyData = [[MXKeyProvider sharedInstance] requestKeyForDataOfType:MXKAccountManagerDataType isMandatory:YES expectedKeyType:kAes];
|
||||
if (keyData && [keyData isKindOfClass:[MXAesKeyData class]])
|
||||
{
|
||||
MXAesKeyData *aesKey = (MXAesKeyData *) keyData;
|
||||
NSData *decrypt = [MXAes decrypt:data aesKey:aesKey.key iv:aesKey.iv error:nil];
|
||||
return decrypt;
|
||||
}
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager] decryptData: no key method provided for decryption.");
|
||||
return data;
|
||||
}
|
||||
|
||||
- (void)migrateAccounts
|
||||
{
|
||||
NSString *pathOld = [[MXKAppSettings cacheFolder] stringByAppendingPathComponent:kMXKAccountsKeyOld];
|
||||
NSString *pathNew = [[MXKAppSettings cacheFolder] stringByAppendingPathComponent:kMXKAccountsKey];
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
if ([fileManager fileExistsAtPath:pathOld])
|
||||
{
|
||||
if (![fileManager fileExistsAtPath:pathNew])
|
||||
{
|
||||
MXLogDebug(@"[MXKAccountManager] migrateAccounts: reading account");
|
||||
mxAccounts = [NSKeyedUnarchiver unarchiveObjectWithFile:pathOld];
|
||||
MXLogDebug(@"[MXKAccountManager] migrateAccounts: writing to accountV2");
|
||||
[self saveAccounts];
|
||||
}
|
||||
|
||||
MXLogDebug(@"[MXKAccountManager] migrateAccounts: removing account");
|
||||
[fileManager removeItemAtPath:pathOld error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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 <AddressBook/AddressBook.h>
|
||||
|
||||
#import "MXKCellData.h"
|
||||
|
||||
#import "MXKEmail.h"
|
||||
#import "MXKPhoneNumber.h"
|
||||
|
||||
/**
|
||||
Posted when the contact thumbnail is updated.
|
||||
The notification object is a contact Id.
|
||||
*/
|
||||
extern NSString *const kMXKContactThumbnailUpdateNotification;
|
||||
|
||||
extern NSString *const kMXKContactLocalContactPrefixId;
|
||||
extern NSString *const kMXKContactMatrixContactPrefixId;
|
||||
extern NSString *const kMXKContactDefaultContactPrefixId;
|
||||
|
||||
@interface MXKContact : MXKCellData <NSCoding>
|
||||
|
||||
/**
|
||||
The unique identifier
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString * contactID;
|
||||
|
||||
/**
|
||||
The display name
|
||||
*/
|
||||
@property (nonatomic, readwrite) NSString *displayName;
|
||||
|
||||
/**
|
||||
The sorting display name built by trimming the symbols [_!~`@#$%^&*-+();:={}[],.<>?\/"'] from the display name.
|
||||
*/
|
||||
@property (nonatomic) NSString* sortingDisplayName;
|
||||
|
||||
/**
|
||||
The contact thumbnail. Default size: 256 X 256 pixels
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) UIImage *thumbnail;
|
||||
|
||||
/**
|
||||
YES if the contact does not exist in the contacts book
|
||||
the contact has been created from a MXUser or MXRoomThirdPartyInvite
|
||||
*/
|
||||
@property (nonatomic) BOOL isMatrixContact;
|
||||
|
||||
/**
|
||||
YES if the contact is coming from MXRoomThirdPartyInvite event (NO by default).
|
||||
*/
|
||||
@property (nonatomic) BOOL isThirdPartyInvite;
|
||||
|
||||
/**
|
||||
The array of MXKPhoneNumber
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray *phoneNumbers;
|
||||
|
||||
/**
|
||||
The array of MXKEmail
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray *emailAddresses;
|
||||
|
||||
/**
|
||||
The array of matrix identifiers
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray* matrixIdentifiers;
|
||||
|
||||
/**
|
||||
The matrix avatar url used (if any) to build the current thumbnail, nil by default.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString* matrixAvatarURL;
|
||||
|
||||
/**
|
||||
Reset the current thumbnail if it is retrieved from a matrix url. May be used in case of the matrix avatar url change.
|
||||
A new thumbnail will be automatically restored from the contact data.
|
||||
*/
|
||||
- (void)resetMatrixThumbnail;
|
||||
|
||||
/**
|
||||
The contact ID from native phonebook record
|
||||
*/
|
||||
+ (NSString*)contactID:(ABRecordRef)record;
|
||||
|
||||
/**
|
||||
Create a local contact from a device contact
|
||||
|
||||
@param record device contact id
|
||||
@return MXKContact instance
|
||||
*/
|
||||
- (id)initLocalContactWithABRecord:(ABRecordRef)record;
|
||||
|
||||
/**
|
||||
Create a matrix contact with the dedicated info
|
||||
|
||||
@param displayName the contact display name
|
||||
@param matrixID the contact matrix id
|
||||
@return MXKContact instance
|
||||
*/
|
||||
- (id)initMatrixContactWithDisplayName:(NSString*)displayName andMatrixID:(NSString*)matrixID;
|
||||
|
||||
/**
|
||||
Create a matrix contact with the dedicated info
|
||||
|
||||
@param displayName the contact display name
|
||||
@param matrixID the contact matrix id
|
||||
@param matrixAvatarURL the matrix avatar url
|
||||
@return MXKContact instance
|
||||
*/
|
||||
- (id)initMatrixContactWithDisplayName:(NSString*)displayName matrixID:(NSString*)matrixID andMatrixAvatarURL:(NSString*)matrixAvatarURL;
|
||||
|
||||
/**
|
||||
Create a contact with the dedicated info
|
||||
|
||||
@param displayName the contact display name
|
||||
@param emails an array of emails
|
||||
@param phones an array of phone numbers
|
||||
@param thumbnail the contact thumbnail
|
||||
@return MXKContact instance
|
||||
*/
|
||||
- (id)initContactWithDisplayName:(NSString*)displayName
|
||||
emails:(NSArray<MXKEmail*> *)emails
|
||||
phoneNumbers:(NSArray<MXKPhoneNumber*> *)phones
|
||||
andThumbnail:(UIImage *)thumbnail;
|
||||
|
||||
/**
|
||||
The contact thumbnail with a prefered size.
|
||||
|
||||
If the thumbnail is already loaded, this method returns this one by ignoring prefered size.
|
||||
The prefered size is used only if a server request is required.
|
||||
|
||||
@return thumbnail with a prefered size
|
||||
*/
|
||||
- (UIImage*)thumbnailWithPreferedSize:(CGSize)size;
|
||||
|
||||
/**
|
||||
Tell whether a component of the contact's displayName, or one of his matrix id/email has the provided prefix.
|
||||
|
||||
@param prefix a non empty string.
|
||||
@return YES when at least one matrix id, email or a component of the display name has this prefix.
|
||||
*/
|
||||
- (BOOL)hasPrefix:(NSString*)prefix;
|
||||
|
||||
/**
|
||||
Check if the patterns can match with this contact
|
||||
*/
|
||||
- (BOOL)matchedWithPatterns:(NSArray*)patterns;
|
||||
|
||||
/**
|
||||
The default ISO 3166-1 country code used to internationalize the contact phone numbers.
|
||||
*/
|
||||
@property (nonatomic) NSString *defaultCountryCode;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,659 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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 "MXKContact.h"
|
||||
|
||||
#import "MXKEmail.h"
|
||||
#import "MXKPhoneNumber.h"
|
||||
|
||||
NSString *const kMXKContactThumbnailUpdateNotification = @"kMXKContactThumbnailUpdateNotification";
|
||||
|
||||
NSString *const kMXKContactLocalContactPrefixId = @"Local_";
|
||||
NSString *const kMXKContactMatrixContactPrefixId = @"Matrix_";
|
||||
NSString *const kMXKContactDefaultContactPrefixId = @"Default_";
|
||||
|
||||
@interface MXKContact()
|
||||
{
|
||||
UIImage* contactThumbnail;
|
||||
UIImage* matrixThumbnail;
|
||||
|
||||
// The matrix id of the contact (used when the contact is not defined in the contacts book)
|
||||
MXKContactField *matrixIdField;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation MXKContact
|
||||
@synthesize isMatrixContact, isThirdPartyInvite;
|
||||
|
||||
+ (NSString*)contactID:(ABRecordRef)record
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@%d", kMXKContactLocalContactPrefixId, ABRecordGetRecordID(record)];
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
matrixIdField = nil;
|
||||
isMatrixContact = NO;
|
||||
_matrixAvatarURL = nil;
|
||||
|
||||
isThirdPartyInvite = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initLocalContactWithABRecord:(ABRecordRef)record
|
||||
{
|
||||
self = [self init];
|
||||
if (self)
|
||||
{
|
||||
// compute a contact ID
|
||||
_contactID = [MXKContact contactID:record];
|
||||
|
||||
// use the contact book display name
|
||||
_displayName = (__bridge NSString*) ABRecordCopyCompositeName(record);
|
||||
|
||||
// avoid nil display name
|
||||
// the display name is used to sort contacts
|
||||
if (!_displayName)
|
||||
{
|
||||
_displayName = @"";
|
||||
}
|
||||
|
||||
// extract the phone numbers and their related label
|
||||
ABMultiValueRef multi = ABRecordCopyValue(record, kABPersonPhoneProperty);
|
||||
CFIndex nCount = ABMultiValueGetCount(multi);
|
||||
NSMutableArray* pns = [[NSMutableArray alloc] initWithCapacity:nCount];
|
||||
|
||||
for (int i = 0; i < nCount; i++)
|
||||
{
|
||||
CFTypeRef phoneRef = ABMultiValueCopyValueAtIndex(multi, i);
|
||||
NSString *phoneVal = (__bridge NSString*)phoneRef;
|
||||
|
||||
// sanity check
|
||||
if (0 != [phoneVal length])
|
||||
{
|
||||
CFStringRef lblRef = ABMultiValueCopyLabelAtIndex(multi, i);
|
||||
CFStringRef localizedLblRef = nil;
|
||||
NSString *lbl = @"";
|
||||
|
||||
if (lblRef != nil)
|
||||
{
|
||||
localizedLblRef = ABAddressBookCopyLocalizedLabel(lblRef);
|
||||
if (localizedLblRef)
|
||||
{
|
||||
lbl = (__bridge NSString*)localizedLblRef;
|
||||
}
|
||||
else
|
||||
{
|
||||
lbl = (__bridge NSString*)lblRef;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
localizedLblRef = ABAddressBookCopyLocalizedLabel(kABOtherLabel);
|
||||
if (localizedLblRef)
|
||||
{
|
||||
lbl = (__bridge NSString*)localizedLblRef;
|
||||
}
|
||||
}
|
||||
|
||||
[pns addObject:[[MXKPhoneNumber alloc] initWithTextNumber:phoneVal type:lbl contactID:_contactID matrixID:nil]];
|
||||
|
||||
if (lblRef)
|
||||
{
|
||||
CFRelease(lblRef);
|
||||
}
|
||||
if (localizedLblRef)
|
||||
{
|
||||
CFRelease(localizedLblRef);
|
||||
}
|
||||
}
|
||||
|
||||
// release meory
|
||||
if (phoneRef)
|
||||
{
|
||||
CFRelease(phoneRef);
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease(multi);
|
||||
_phoneNumbers = pns;
|
||||
|
||||
// extract the emails
|
||||
multi = ABRecordCopyValue(record, kABPersonEmailProperty);
|
||||
nCount = ABMultiValueGetCount(multi);
|
||||
|
||||
NSMutableArray *emails = [[NSMutableArray alloc] initWithCapacity:nCount];
|
||||
|
||||
for (int i = 0; i < nCount; i++)
|
||||
{
|
||||
CFTypeRef emailValRef = ABMultiValueCopyValueAtIndex(multi, i);
|
||||
NSString *emailVal = (__bridge NSString*)emailValRef;
|
||||
|
||||
// sanity check
|
||||
if ((nil != emailVal) && (0 != [emailVal length]))
|
||||
{
|
||||
CFStringRef lblRef = ABMultiValueCopyLabelAtIndex(multi, i);
|
||||
CFStringRef localizedLblRef = nil;
|
||||
NSString *lbl = @"";
|
||||
|
||||
if (lblRef != nil)
|
||||
{
|
||||
localizedLblRef = ABAddressBookCopyLocalizedLabel(lblRef);
|
||||
|
||||
if (localizedLblRef)
|
||||
{
|
||||
lbl = (__bridge NSString*)localizedLblRef;
|
||||
}
|
||||
else
|
||||
{
|
||||
lbl = (__bridge NSString*)lblRef;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
localizedLblRef = ABAddressBookCopyLocalizedLabel(kABOtherLabel);
|
||||
if (localizedLblRef)
|
||||
{
|
||||
lbl = (__bridge NSString*)localizedLblRef;
|
||||
}
|
||||
}
|
||||
|
||||
[emails addObject: [[MXKEmail alloc] initWithEmailAddress:emailVal type:lbl contactID:_contactID matrixID:nil]];
|
||||
|
||||
if (lblRef)
|
||||
{
|
||||
CFRelease(lblRef);
|
||||
}
|
||||
|
||||
if (localizedLblRef)
|
||||
{
|
||||
CFRelease(localizedLblRef);
|
||||
}
|
||||
}
|
||||
|
||||
if (emailValRef)
|
||||
{
|
||||
CFRelease(emailValRef);
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease(multi);
|
||||
|
||||
_emailAddresses = emails;
|
||||
|
||||
// thumbnail/picture
|
||||
// check whether the contact has a picture
|
||||
if (ABPersonHasImageData(record))
|
||||
{
|
||||
CFDataRef dataRef;
|
||||
|
||||
dataRef = ABPersonCopyImageDataWithFormat(record, kABPersonImageFormatThumbnail);
|
||||
if (dataRef)
|
||||
{
|
||||
contactThumbnail = [UIImage imageWithData:(__bridge NSData*)dataRef];
|
||||
CFRelease(dataRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initMatrixContactWithDisplayName:(NSString*)displayName andMatrixID:(NSString*)matrixID
|
||||
{
|
||||
self = [self init];
|
||||
if (self)
|
||||
{
|
||||
_contactID = [NSString stringWithFormat:@"%@%@", kMXKContactMatrixContactPrefixId, [[NSUUID UUID] UUIDString]];
|
||||
|
||||
// Sanity check
|
||||
if (matrixID.length)
|
||||
{
|
||||
// used when the contact is not defined in the contacts book
|
||||
matrixIdField = [[MXKContactField alloc] initWithContactID:_contactID matrixID:matrixID];
|
||||
isMatrixContact = YES;
|
||||
}
|
||||
|
||||
// _displayName must not be nil
|
||||
// it is used to sort the contacts
|
||||
if (displayName)
|
||||
{
|
||||
_displayName = displayName;
|
||||
}
|
||||
else
|
||||
{
|
||||
_displayName = @"";
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initMatrixContactWithDisplayName:(NSString*)displayName matrixID:(NSString*)matrixID andMatrixAvatarURL:(NSString*)matrixAvatarURL
|
||||
{
|
||||
self = [self initMatrixContactWithDisplayName:displayName andMatrixID:matrixID];
|
||||
if (self)
|
||||
{
|
||||
matrixIdField.matrixAvatarURL = matrixAvatarURL;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initContactWithDisplayName:(NSString*)displayName
|
||||
emails:(NSArray<MXKEmail*> *)emails
|
||||
phoneNumbers:(NSArray<MXKPhoneNumber*> *)phones
|
||||
andThumbnail:(UIImage *)thumbnail
|
||||
{
|
||||
self = [self init];
|
||||
if (self)
|
||||
{
|
||||
_contactID = [NSString stringWithFormat:@"%@%@", kMXKContactDefaultContactPrefixId, [[NSUUID UUID] UUIDString]];
|
||||
|
||||
// _displayName must not be nil
|
||||
// it is used to sort the contacts
|
||||
if (displayName)
|
||||
{
|
||||
_displayName = displayName;
|
||||
}
|
||||
else
|
||||
{
|
||||
_displayName = @"";
|
||||
}
|
||||
|
||||
_emailAddresses = emails;
|
||||
_phoneNumbers = phones;
|
||||
|
||||
contactThumbnail = thumbnail;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSString*)sortingDisplayName
|
||||
{
|
||||
if (!_sortingDisplayName)
|
||||
{
|
||||
// Sanity check - display name should not be nil here
|
||||
if (self.displayName)
|
||||
{
|
||||
NSCharacterSet *specialCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"_!~`@#$%^&*-+();:={}[],.<>?\\/\"\'"];
|
||||
|
||||
_sortingDisplayName = [self.displayName stringByTrimmingCharactersInSet:specialCharacterSet];
|
||||
}
|
||||
else
|
||||
{
|
||||
return @"";
|
||||
}
|
||||
}
|
||||
|
||||
return _sortingDisplayName;
|
||||
}
|
||||
|
||||
- (BOOL)hasPrefix:(NSString*)prefix
|
||||
{
|
||||
prefix = [prefix lowercaseString];
|
||||
|
||||
// Check first display name
|
||||
if (_displayName.length)
|
||||
{
|
||||
NSString *lowercaseString = [_displayName lowercaseString];
|
||||
if ([lowercaseString hasPrefix:prefix])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSArray *components = [lowercaseString componentsSeparatedByString:@" "];
|
||||
for (NSString *component in components)
|
||||
{
|
||||
NSString *theComponent = [component stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
if ([theComponent hasPrefix:prefix])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check matrix identifiers
|
||||
NSArray *identifiers = self.matrixIdentifiers;
|
||||
NSString *idPrefix = prefix;
|
||||
if (![prefix hasPrefix:@"@"])
|
||||
{
|
||||
idPrefix = [NSString stringWithFormat:@"@%@", prefix];
|
||||
}
|
||||
|
||||
for (NSString* mxId in identifiers)
|
||||
{
|
||||
if ([[mxId lowercaseString] hasPrefix:idPrefix])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Check email
|
||||
for (MXKEmail* email in _emailAddresses)
|
||||
{
|
||||
if ([email.emailAddress hasPrefix:prefix])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Check phones
|
||||
for (MXKPhoneNumber* phone in _phoneNumbers)
|
||||
{
|
||||
if ([phone hasPrefix:prefix])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)matchedWithPatterns:(NSArray*)patterns
|
||||
{
|
||||
BOOL matched = NO;
|
||||
|
||||
if (patterns.count > 0)
|
||||
{
|
||||
matched = YES;
|
||||
|
||||
// test first display name
|
||||
for (NSString* pattern in patterns)
|
||||
{
|
||||
if ([_displayName rangeOfString:pattern options:NSCaseInsensitiveSearch].location == NSNotFound)
|
||||
{
|
||||
matched = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NSArray *identifiers = self.matrixIdentifiers;
|
||||
if (!matched && identifiers.count > 0)
|
||||
{
|
||||
for (NSString* mxId in identifiers)
|
||||
{
|
||||
// Consider only the first part of the matrix id (ignore homeserver name)
|
||||
NSRange range = [mxId rangeOfString:@":"];
|
||||
if (range.location != NSNotFound)
|
||||
{
|
||||
NSString *mxIdName = [mxId substringToIndex:range.location];
|
||||
for (NSString* pattern in patterns)
|
||||
{
|
||||
if ([mxIdName rangeOfString:pattern options:NSCaseInsensitiveSearch].location != NSNotFound)
|
||||
{
|
||||
matched = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched && _phoneNumbers.count > 0)
|
||||
{
|
||||
for (MXKPhoneNumber* phonenumber in _phoneNumbers)
|
||||
{
|
||||
if ([phonenumber matchedWithPatterns:patterns])
|
||||
{
|
||||
matched = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched && _emailAddresses.count > 0)
|
||||
{
|
||||
for (MXKEmail* email in _emailAddresses)
|
||||
{
|
||||
if ([email matchedWithPatterns:patterns])
|
||||
{
|
||||
matched = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if there is no pattern to search, it should always matched
|
||||
matched = YES;
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
- (void)setDefaultCountryCode:(NSString *)defaultCountryCode
|
||||
{
|
||||
for (MXKPhoneNumber* phonenumber in _phoneNumbers)
|
||||
{
|
||||
phonenumber.defaultCountryCode = defaultCountryCode;
|
||||
}
|
||||
|
||||
_defaultCountryCode = defaultCountryCode;
|
||||
}
|
||||
|
||||
#pragma mark - getter/setter
|
||||
|
||||
- (NSArray*)matrixIdentifiers
|
||||
{
|
||||
NSMutableArray* identifiers = [[NSMutableArray alloc] init];
|
||||
|
||||
if (matrixIdField)
|
||||
{
|
||||
[identifiers addObject:matrixIdField.matrixID];
|
||||
}
|
||||
|
||||
for (MXKEmail* email in _emailAddresses)
|
||||
{
|
||||
if (email.matrixID && ([identifiers indexOfObject:email.matrixID] == NSNotFound))
|
||||
{
|
||||
[identifiers addObject:email.matrixID];
|
||||
}
|
||||
}
|
||||
|
||||
for (MXKPhoneNumber* pn in _phoneNumbers)
|
||||
{
|
||||
if (pn.matrixID && ([identifiers indexOfObject:pn.matrixID] == NSNotFound))
|
||||
{
|
||||
[identifiers addObject:pn.matrixID];
|
||||
}
|
||||
}
|
||||
|
||||
return identifiers;
|
||||
}
|
||||
|
||||
- (void)setDisplayName:(NSString *)displayName
|
||||
{
|
||||
// a display name must not be emptied
|
||||
// it is used to sort the contacts
|
||||
if (displayName.length == 0)
|
||||
{
|
||||
_displayName = _contactID;
|
||||
}
|
||||
else
|
||||
{
|
||||
_displayName = displayName;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resetMatrixThumbnail
|
||||
{
|
||||
matrixThumbnail = nil;
|
||||
_matrixAvatarURL = nil;
|
||||
|
||||
// Reset the avatar in the contact fields too.
|
||||
[matrixIdField resetMatrixAvatar];
|
||||
|
||||
for (MXKEmail* email in _emailAddresses)
|
||||
{
|
||||
[email resetMatrixAvatar];
|
||||
}
|
||||
}
|
||||
|
||||
- (UIImage*)thumbnailWithPreferedSize:(CGSize)size
|
||||
{
|
||||
// Consider first the local thumbnail if any.
|
||||
if (contactThumbnail)
|
||||
{
|
||||
return contactThumbnail;
|
||||
}
|
||||
|
||||
// Check whether a matrix thumbnail is already found.
|
||||
if (matrixThumbnail)
|
||||
{
|
||||
return matrixThumbnail;
|
||||
}
|
||||
|
||||
// Look for a thumbnail from the matrix identifiers
|
||||
MXKContactField* firstField = matrixIdField;
|
||||
if (firstField)
|
||||
{
|
||||
if (firstField.avatarImage)
|
||||
{
|
||||
matrixThumbnail = firstField.avatarImage;
|
||||
_matrixAvatarURL = firstField.matrixAvatarURL;
|
||||
return matrixThumbnail;
|
||||
}
|
||||
}
|
||||
|
||||
// try to replace the thumbnail by the matrix one
|
||||
if (_emailAddresses.count > 0)
|
||||
{
|
||||
// list the linked email
|
||||
// search if one email field has a dedicated thumbnail
|
||||
for (MXKEmail* email in _emailAddresses)
|
||||
{
|
||||
if (email.avatarImage)
|
||||
{
|
||||
matrixThumbnail = email.avatarImage;
|
||||
_matrixAvatarURL = email.matrixAvatarURL;
|
||||
return matrixThumbnail;
|
||||
}
|
||||
else if (!firstField && email.matrixID)
|
||||
{
|
||||
firstField = email;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_phoneNumbers.count > 0)
|
||||
{
|
||||
// list the linked phones
|
||||
// search if one phone field has a dedicated thumbnail
|
||||
for (MXKPhoneNumber* phoneNb in _phoneNumbers)
|
||||
{
|
||||
if (phoneNb.avatarImage)
|
||||
{
|
||||
matrixThumbnail = phoneNb.avatarImage;
|
||||
_matrixAvatarURL = phoneNb.matrixAvatarURL;
|
||||
return matrixThumbnail;
|
||||
}
|
||||
else if (!firstField && phoneNb.matrixID)
|
||||
{
|
||||
firstField = phoneNb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if no thumbnail has been found
|
||||
// try to load the first field one
|
||||
if (firstField)
|
||||
{
|
||||
// should be retrieved by the cell info
|
||||
[firstField loadAvatarWithSize:size];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (UIImage*)thumbnail
|
||||
{
|
||||
return [self thumbnailWithPreferedSize:CGSizeMake(256, 256)];
|
||||
}
|
||||
|
||||
#pragma mark NSCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
_contactID = [coder decodeObjectForKey:@"contactID"];
|
||||
_displayName = [coder decodeObjectForKey:@"displayName"];
|
||||
|
||||
matrixIdField = [coder decodeObjectForKey:@"matrixIdField"];
|
||||
|
||||
_phoneNumbers = [coder decodeObjectForKey:@"phoneNumbers"];
|
||||
_emailAddresses = [coder decodeObjectForKey:@"emailAddresses"];
|
||||
|
||||
NSData *data = [coder decodeObjectForKey:@"contactThumbnail"];
|
||||
if (!data)
|
||||
{
|
||||
// Check the legacy storage.
|
||||
data = [coder decodeObjectForKey:@"contactBookThumbnail"];
|
||||
}
|
||||
|
||||
if (data)
|
||||
{
|
||||
contactThumbnail = [UIImage imageWithData:data];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder
|
||||
{
|
||||
|
||||
[coder encodeObject:_contactID forKey:@"contactID"];
|
||||
[coder encodeObject:_displayName forKey:@"displayName"];
|
||||
|
||||
if (matrixIdField)
|
||||
{
|
||||
[coder encodeObject:matrixIdField forKey:@"matrixIdField"];
|
||||
}
|
||||
|
||||
if (_phoneNumbers.count)
|
||||
{
|
||||
[coder encodeObject:_phoneNumbers forKey:@"phoneNumbers"];
|
||||
}
|
||||
|
||||
if (_emailAddresses.count)
|
||||
{
|
||||
[coder encodeObject:_emailAddresses forKey:@"emailAddresses"];
|
||||
}
|
||||
|
||||
if (contactThumbnail)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
NSData *data = UIImageJPEGRepresentation(contactThumbnail, 0.8);
|
||||
[coder encodeObject:data forKey:@"contactThumbnail"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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>
|
||||
|
||||
@interface MXKContactField : NSObject<NSCoding>
|
||||
|
||||
/**
|
||||
The identifier of the contact to whom the data belongs to.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *contactID;
|
||||
/**
|
||||
The linked matrix identifier if any
|
||||
*/
|
||||
@property (nonatomic, readwrite) NSString *matrixID;
|
||||
/**
|
||||
The matrix avatar url (Matrix Content URI), nil by default.
|
||||
*/
|
||||
@property (nonatomic) NSString* matrixAvatarURL;
|
||||
/**
|
||||
The current avatar downloaded by using the avatar url if any
|
||||
*/
|
||||
@property (nonatomic, readonly) UIImage *avatarImage;
|
||||
|
||||
- (id)initWithContactID:(NSString*)contactID matrixID:(NSString*)matrixID;
|
||||
|
||||
- (void)loadAvatarWithSize:(CGSize)avatarSize;
|
||||
|
||||
/**
|
||||
Reset the current avatar. May be used in case of the matrix avatar url change.
|
||||
A new avatar will be automatically restored from the matrix data.
|
||||
*/
|
||||
- (void)resetMatrixAvatar;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
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 "MXKContactField.h"
|
||||
|
||||
@import MatrixSDK.MXMediaManager;
|
||||
|
||||
#import "MXKContactManager.h"
|
||||
|
||||
@interface MXKContactField()
|
||||
{
|
||||
// Tell whether we already check the contact avatar definition.
|
||||
BOOL shouldCheckAvatarURL;
|
||||
// The media manager of the session used to retrieve the contect avatar url
|
||||
// This manager is used to download this avatar if need
|
||||
MXMediaManager *mediaManager;
|
||||
// The current download id
|
||||
NSString *downloadId;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation MXKContactField
|
||||
|
||||
- (void)initFields
|
||||
{
|
||||
// init members
|
||||
_contactID = nil;
|
||||
_matrixID = nil;
|
||||
|
||||
[self resetMatrixAvatar];
|
||||
}
|
||||
|
||||
- (id)initWithContactID:(NSString*)contactID matrixID:(NSString*)matrixID
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self initFields];
|
||||
_contactID = contactID;
|
||||
_matrixID = matrixID;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)resetMatrixAvatar
|
||||
{
|
||||
_avatarImage = nil;
|
||||
_matrixAvatarURL = nil;
|
||||
shouldCheckAvatarURL = YES;
|
||||
mediaManager = nil;
|
||||
downloadId = nil;
|
||||
}
|
||||
|
||||
- (void)loadAvatarWithSize:(CGSize)avatarSize
|
||||
{
|
||||
// Check whether the avatar image is already set
|
||||
if (_avatarImage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (_matrixID)
|
||||
{
|
||||
if (shouldCheckAvatarURL)
|
||||
{
|
||||
// Consider here all sessions reported into contact manager
|
||||
NSArray* mxSessions = [MXKContactManager sharedManager].mxSessions;
|
||||
|
||||
if (mxSessions.count)
|
||||
{
|
||||
// Check whether a matrix user is already known
|
||||
MXUser* user;
|
||||
MXSession *mxSession;
|
||||
|
||||
for (mxSession in mxSessions)
|
||||
{
|
||||
user = [mxSession userWithUserId:_matrixID];
|
||||
if (user)
|
||||
{
|
||||
_matrixAvatarURL = user.avatarUrl;
|
||||
if (_matrixAvatarURL)
|
||||
{
|
||||
shouldCheckAvatarURL = NO;
|
||||
mediaManager = mxSession.mediaManager;
|
||||
[self downloadAvatarImage:avatarSize];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger a server request if this url has not been found.
|
||||
if (shouldCheckAvatarURL)
|
||||
{
|
||||
MXWeakify(self);
|
||||
[mxSession.matrixRestClient avatarUrlForUser:_matrixID
|
||||
success:^(NSString *mxAvatarUrl) {
|
||||
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
self.matrixAvatarURL = mxAvatarUrl;
|
||||
self->shouldCheckAvatarURL = NO;
|
||||
self->mediaManager = mxSession.mediaManager;
|
||||
[self downloadAvatarImage:avatarSize];
|
||||
|
||||
} failure:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_matrixAvatarURL)
|
||||
{
|
||||
[self downloadAvatarImage:avatarSize];
|
||||
}
|
||||
// Do nothing if the avatar url has been checked, and it is null.
|
||||
}
|
||||
}
|
||||
|
||||
- (void)downloadAvatarImage:(CGSize)avatarSize
|
||||
{
|
||||
// the avatar image is already done
|
||||
if (_avatarImage)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_matrixAvatarURL)
|
||||
{
|
||||
NSString *cacheFilePath = [MXMediaManager thumbnailCachePathForMatrixContentURI:_matrixAvatarURL
|
||||
andType:nil
|
||||
inFolder:kMXMediaManagerAvatarThumbnailFolder
|
||||
toFitViewSize:avatarSize
|
||||
withMethod:MXThumbnailingMethodCrop];
|
||||
_avatarImage = [MXMediaManager loadPictureFromFilePath:cacheFilePath];
|
||||
|
||||
// the image is already in the cache
|
||||
if (_avatarImage)
|
||||
{
|
||||
MXWeakify(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKContactThumbnailUpdateNotification object:self.contactID userInfo:nil];
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
NSString *downloadId = [MXMediaManager thumbnailDownloadIdForMatrixContentURI:_matrixAvatarURL inFolder:kMXMediaManagerAvatarThumbnailFolder toFitViewSize:avatarSize withMethod:MXThumbnailingMethodCrop];
|
||||
MXMediaLoader* loader = [MXMediaManager existingDownloaderWithIdentifier:downloadId];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXMediaLoaderStateDidChangeNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onMediaDownloadEnd:) name:kMXMediaLoaderStateDidChangeNotification object:loader];
|
||||
if (!loader && mediaManager)
|
||||
{
|
||||
[mediaManager downloadThumbnailFromMatrixContentURI:_matrixAvatarURL
|
||||
withType:nil
|
||||
inFolder:kMXMediaManagerAvatarThumbnailFolder
|
||||
toFitViewSize:avatarSize
|
||||
withMethod:MXThumbnailingMethodCrop
|
||||
success:nil
|
||||
failure:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onMediaDownloadEnd:(NSNotification *)notif
|
||||
{
|
||||
MXMediaLoader *loader = (MXMediaLoader*)notif.object;
|
||||
if ([loader.downloadId isEqualToString:downloadId])
|
||||
{
|
||||
// update the image
|
||||
switch (loader.state) {
|
||||
case MXMediaLoaderStateDownloadCompleted:
|
||||
{
|
||||
UIImage *image = [MXMediaManager loadPictureFromFilePath:loader.downloadOutputFilePath];
|
||||
if (image)
|
||||
{
|
||||
_avatarImage = image;
|
||||
|
||||
MXWeakify(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKContactThumbnailUpdateNotification object:self.contactID userInfo:nil];
|
||||
});
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXMediaLoaderStateDidChangeNotification object:nil];
|
||||
downloadId = nil;
|
||||
break;
|
||||
}
|
||||
case MXMediaLoaderStateDownloadFailed:
|
||||
case MXMediaLoaderStateCancelled:
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXMediaLoaderStateDidChangeNotification object:nil];
|
||||
downloadId = nil;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark NSCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
if (self)
|
||||
{
|
||||
[self initFields];
|
||||
_contactID = [coder decodeObjectForKey:@"contactID"];
|
||||
_matrixID = [coder decodeObjectForKey:@"matrixID"];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder
|
||||
{
|
||||
[coder encodeObject:_contactID forKey:@"contactID"];
|
||||
[coder encodeObject:_matrixID forKey:@"matrixID"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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 <Foundation/Foundation.h>
|
||||
#import <Contacts/Contacts.h>
|
||||
|
||||
#import <MatrixSDK/MatrixSDK.h>
|
||||
|
||||
#import "MXKSectionedContacts.h"
|
||||
#import "MXKContact.h"
|
||||
|
||||
/**
|
||||
Posted when the matrix contact list is loaded or updated.
|
||||
The notification object is:
|
||||
- a contact Id when a matrix contact has been added/updated/removed.
|
||||
or
|
||||
- nil when all matrix contacts are concerned.
|
||||
*/
|
||||
extern NSString * _Nonnull const kMXKContactManagerDidUpdateMatrixContactsNotification;
|
||||
|
||||
/**
|
||||
Posted when the local contact list is loaded and updated.
|
||||
The notification object is:
|
||||
- a contact Id when a local contact has been added/updated/removed.
|
||||
or
|
||||
- nil when all local contacts are concerned.
|
||||
*/
|
||||
extern NSString * _Nonnull const kMXKContactManagerDidUpdateLocalContactsNotification;
|
||||
|
||||
/**
|
||||
Posted when local contact matrix ids is updated.
|
||||
The notification object is:
|
||||
- a contact Id when a local contact has been added/updated/removed.
|
||||
or
|
||||
- nil when all local contacts are concerned.
|
||||
*/
|
||||
extern NSString * _Nonnull const kMXKContactManagerDidUpdateLocalContactMatrixIDsNotification;
|
||||
|
||||
/**
|
||||
Posted when the presence of a matrix user linked at least to one contact has changed.
|
||||
The notification object is the matrix Id. The `userInfo` dictionary contains an `MXPresenceString` object under the `kMXKContactManagerMatrixPresenceKey` key, representing the matrix user presence.
|
||||
*/
|
||||
extern NSString * _Nonnull const kMXKContactManagerMatrixUserPresenceChangeNotification;
|
||||
extern NSString * _Nonnull const kMXKContactManagerMatrixPresenceKey;
|
||||
|
||||
/**
|
||||
Posted when all phonenumbers of local contacts have been internationalized.
|
||||
The notification object is nil.
|
||||
*/
|
||||
extern NSString * _Nonnull const kMXKContactManagerDidInternationalizeNotification;
|
||||
|
||||
/**
|
||||
Used to identify the type of data when requesting MXKeyProvider
|
||||
*/
|
||||
extern NSString * _Nonnull const MXKContactManagerDataType;
|
||||
|
||||
/**
|
||||
Define the contact creation for the room members
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, MXKContactManagerMXRoomSource) {
|
||||
MXKContactManagerMXRoomSourceNone = 0, // the MXMember does not create any new contact.
|
||||
MXKContactManagerMXRoomSourceDirectChats = 1, // the direct chat users have their own contact even if they are not defined in the device contacts book
|
||||
MXKContactManagerMXRoomSourceAll = 2, // all the room members have their own contact even if they are not defined in the device contacts book
|
||||
};
|
||||
|
||||
/**
|
||||
This manager handles 2 kinds of contact list:
|
||||
- The local contacts retrieved from the device phonebook.
|
||||
- The matrix contacts retrieved from the matrix one-to-one rooms.
|
||||
|
||||
Note: The local contacts handling depends on the 'syncLocalContacts' and 'phonebookCountryCode' properties
|
||||
of the shared application settings object '[MXKAppSettings standardAppSettings]'.
|
||||
*/
|
||||
@interface MXKContactManager : NSObject
|
||||
|
||||
/**
|
||||
The shared instance of contact manager.
|
||||
*/
|
||||
+ (MXKContactManager* _Nonnull)sharedManager;
|
||||
|
||||
/**
|
||||
Block called (if any) to discover the Matrix users bound to a set of third-party identifiers (email addresses, phone numbers).
|
||||
If this property is unset, the contact manager will consider the potential identity server URL (see the `identityServer` property)
|
||||
to build its own Restclient and trigger `lookup3PIDs` requests.
|
||||
|
||||
@param threepids the list of 3rd party ids: [[<(MX3PIDMedium)media1>, <(NSString*)address1>], [<(MX3PIDMedium)media2>, <(NSString*)address2>], ...].
|
||||
@param success a block object called when the operation succeeds. It provides the array of the discovered users:
|
||||
[[<(MX3PIDMedium)media>, <(NSString*)address>, <(NSString*)userId>], ...].
|
||||
@param failure a block object called when the operation fails.
|
||||
*/
|
||||
typedef void(^MXKContactManagerDiscoverUsersBoundTo3PIDs)(NSArray<NSArray<NSString *> *> * _Nonnull threepids,
|
||||
void (^ _Nonnull success)(NSArray<NSArray<NSString *> *> *_Nonnull),
|
||||
void (^ _Nonnull failure)(NSError *_Nonnull));
|
||||
@property (nonatomic, nullable) MXKContactManagerDiscoverUsersBoundTo3PIDs discoverUsersBoundTo3PIDsBlock;
|
||||
|
||||
/**
|
||||
Define if the room member must have their dedicated contact even if they are not define in the device contacts book.
|
||||
The default value is MXKContactManagerMXRoomSourceDirectChats;
|
||||
*/
|
||||
@property (nonatomic) MXKContactManagerMXRoomSource contactManagerMXRoomSource;
|
||||
|
||||
/**
|
||||
Associated matrix sessions (empty by default).
|
||||
*/
|
||||
@property (nonatomic, readonly, nonnull) NSArray *mxSessions;
|
||||
|
||||
/**
|
||||
The current list of the contacts extracted from matrix data. Depends on 'contactManagerMXRoomSource'.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSArray *matrixContacts;
|
||||
|
||||
/**
|
||||
The current list of the local contacts (nil by default until the contacts are loaded).
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSArray *localContacts;
|
||||
|
||||
/**
|
||||
The current list of the local contacts who have contact methods which can be used to invite them or to discover matrix users.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSArray *localContactsWithMethods;
|
||||
|
||||
/**
|
||||
The contacts list obtained by splitting each local contact by contact method.
|
||||
This list is alphabetically sorted.
|
||||
Each contact has one and only one contact method.
|
||||
*/
|
||||
//- (void)localContactsSplitByContactMethod:(void (^)(NSArray<MXKContact*> *localContactsSplitByContactMethod))onComplete;
|
||||
|
||||
@property (nonatomic, readonly, nullable) NSArray *localContactsSplitByContactMethod;
|
||||
|
||||
/**
|
||||
The current list of the contacts for whom a direct chat exists.
|
||||
*/
|
||||
@property (nonatomic, readonly, nonnull) NSArray *directMatrixContacts;
|
||||
|
||||
/// Flag to allow local contacts access or not. Default value is YES.
|
||||
@property (nonatomic, assign) BOOL allowLocalContactsAccess;
|
||||
|
||||
/**
|
||||
Add/remove matrix session. The matrix contact list is automatically updated (see kMXKContactManagerDidUpdateMatrixContactsNotification event).
|
||||
*/
|
||||
- (void)addMatrixSession:(MXSession* _Nonnull)mxSession;
|
||||
- (void)removeMatrixSession:(MXSession* _Nonnull)mxSession;
|
||||
|
||||
/**
|
||||
Takes into account the state of the identity service's terms, local contacts access authorization along with
|
||||
whether the user has left the app for the Settings app to update the contacts access, and enables/disables
|
||||
the `syncLocalContacts` property of `MXKAppSettings` when necessary.
|
||||
@param mxSession The session who's identity service shall be used.
|
||||
*/
|
||||
- (void)validateSyncLocalContactsStateForSession:(MXSession *)mxSession;
|
||||
|
||||
/**
|
||||
Load and/or refresh the local contacts. Observe kMXKContactManagerDidUpdateLocalContactsNotification to know when local contacts are available.
|
||||
*/
|
||||
- (void)refreshLocalContacts;
|
||||
|
||||
/**
|
||||
Delete contacts info
|
||||
*/
|
||||
- (void)reset;
|
||||
|
||||
/**
|
||||
Get contact by its identifier.
|
||||
|
||||
@param contactID the contact identifier.
|
||||
@return the contact defined with the provided id.
|
||||
*/
|
||||
- (MXKContact* _Nullable)contactWithContactID:(NSString* _Nonnull)contactID;
|
||||
|
||||
/**
|
||||
Refresh matrix IDs for a specific local contact. See kMXKContactManagerDidUpdateLocalContactMatrixIDsNotification
|
||||
posted when update is done.
|
||||
|
||||
@param contact the local contact to refresh.
|
||||
*/
|
||||
- (void)updateMatrixIDsForLocalContact:(MXKContact* _Nonnull)contact;
|
||||
|
||||
/**
|
||||
Refresh matrix IDs for all local contacts. See kMXKContactManagerDidUpdateLocalContactMatrixIDsNotification
|
||||
posted when update for all local contacts is done.
|
||||
*/
|
||||
- (void)updateMatrixIDsForAllLocalContacts;
|
||||
|
||||
/**
|
||||
The contacts list obtained by splitting each local contact by contact method.
|
||||
This list is alphabetically sorted.
|
||||
Each contact has one and only one contact method.
|
||||
*/
|
||||
//- (void)localContactsSplitByContactMethod:(void (^)(NSArray<MXKContact*> *localContactsSplitByContactMethod))onComplete;
|
||||
|
||||
/**
|
||||
Sort a contacts array in sectioned arrays to be displayable in a UITableview
|
||||
*/
|
||||
- (MXKSectionedContacts* _Nullable)getSectionedContacts:(NSArray* _Nonnull)contactList;
|
||||
|
||||
/**
|
||||
Sort alphabetically an array of contacts.
|
||||
|
||||
@param contactsArray the array of contacts to sort.
|
||||
*/
|
||||
- (void)sortAlphabeticallyContacts:(NSMutableArray<MXKContact*> * _Nonnull)contactsArray;
|
||||
|
||||
/**
|
||||
Sort an array of contacts by last active, with "active now" first.
|
||||
...and then alphabetically.
|
||||
|
||||
@param contactsArray the array of contacts to sort.
|
||||
*/
|
||||
- (void)sortContactsByLastActiveInformation:(NSMutableArray<MXKContact*> * _Nonnull)contactsArray;
|
||||
|
||||
/**
|
||||
Refresh the international phonenumber of the local contacts (See kMXKContactManagerDidInternationalizeNotification).
|
||||
|
||||
@param countryCode the country code.
|
||||
*/
|
||||
- (void)internationalizePhoneNumbers:(NSString* _Nonnull)countryCode;
|
||||
|
||||
/**
|
||||
Request user permission for syncing local contacts.
|
||||
|
||||
@param viewController the view controller to attach the dialog to the user.
|
||||
@param handler the block called with the result of requesting access
|
||||
*/
|
||||
+ (void)requestUserConfirmationForLocalContactsSyncInViewController:(UIViewController* _Nonnull)viewController
|
||||
completionHandler:(void (^_Nonnull)(BOOL granted))handler;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
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 <UIKit/UIKit.h>
|
||||
#import "MXKContactField.h"
|
||||
|
||||
@interface MXKEmail : MXKContactField
|
||||
|
||||
// email info (the address is stored in lowercase)
|
||||
@property (nonatomic, readonly) NSString *type;
|
||||
@property (nonatomic, readonly) NSString *emailAddress;
|
||||
|
||||
- (id)initWithEmailAddress:(NSString*)anEmailAddress type:(NSString*)aType contactID:(NSString*)aContactID matrixID:(NSString*)matrixID;
|
||||
|
||||
- (BOOL)matchedWithPatterns:(NSArray*)patterns;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
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 "MXKEmail.h"
|
||||
|
||||
@implementation MXKEmail
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_emailAddress = nil;
|
||||
_type = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithEmailAddress:(NSString*)anEmailAddress type:(NSString*)aType contactID:(NSString*)aContactID matrixID:(NSString*)matrixID
|
||||
{
|
||||
self = [super initWithContactID:aContactID matrixID:matrixID];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_emailAddress = [anEmailAddress lowercaseString];
|
||||
_type = aType;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)matchedWithPatterns:(NSArray*)patterns
|
||||
{
|
||||
// no number -> cannot match
|
||||
if (_emailAddress.length == 0)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
if (patterns.count > 0)
|
||||
{
|
||||
for(NSString *pattern in patterns)
|
||||
{
|
||||
if ([_emailAddress rangeOfString:pattern options:NSCaseInsensitiveSearch].location == NSNotFound)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
#pragma mark NSCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_type = [coder decodeObjectForKey:@"type"];
|
||||
_emailAddress = [[coder decodeObjectForKey:@"emailAddress"] lowercaseString];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder
|
||||
{
|
||||
[super encodeWithCoder:coder];
|
||||
|
||||
[coder encodeObject:_type forKey:@"type"];
|
||||
[coder encodeObject:_emailAddress forKey:@"emailAddress"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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 "MXKContactField.h"
|
||||
|
||||
@class NBPhoneNumber;
|
||||
|
||||
@interface MXKPhoneNumber : MXKContactField
|
||||
|
||||
/**
|
||||
The phone number information
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *type;
|
||||
@property (nonatomic, readonly) NSString *textNumber;
|
||||
@property (nonatomic, readonly) NSString *cleanedPhonenumber;
|
||||
|
||||
/**
|
||||
When the number is considered to be a possible number. We expose here
|
||||
the corresponding NBPhoneNumber instance. Use the NBPhoneNumberUtil interface
|
||||
to format this phone number, or check whether the number is actually a
|
||||
valid number.
|
||||
*/
|
||||
@property (nonatomic, readonly) NBPhoneNumber* nbPhoneNumber;
|
||||
|
||||
/**
|
||||
The default ISO 3166-1 country code used to parse the text number,
|
||||
and create the nbPhoneNumber instance.
|
||||
*/
|
||||
@property (nonatomic) NSString *defaultCountryCode;
|
||||
|
||||
/**
|
||||
The Mobile Station International Subscriber Directory Number.
|
||||
Available when the nbPhoneNumber is not nil.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *msisdn;
|
||||
|
||||
/**
|
||||
Create a new MXKPhoneNumber instance
|
||||
|
||||
@param textNumber the phone number
|
||||
@param type the phone number type
|
||||
@param contactID The identifier of the contact to whom the data belongs to.
|
||||
@param matrixID The linked matrix identifier if any.
|
||||
*/
|
||||
- (id)initWithTextNumber:(NSString*)textNumber type:(NSString*)type contactID:(NSString*)contactID matrixID:(NSString*)matrixID;
|
||||
|
||||
/**
|
||||
Return YES when all the provided patterns are found in the phone number or its msisdn.
|
||||
|
||||
@param patterns an array of patterns (The potential "+" (or "00") prefix is ignored during the msisdn handling).
|
||||
*/
|
||||
- (BOOL)matchedWithPatterns:(NSArray*)patterns;
|
||||
|
||||
/**
|
||||
Tell whether the phone number or its msisdn has the provided prefix.
|
||||
|
||||
@param prefix a non empty string (The potential "+" (or "00") prefix is ignored during the msisdn handling).
|
||||
@return YES when the phone number or its msisdn has the provided prefix.
|
||||
*/
|
||||
- (BOOL)hasPrefix:(NSString*)prefix;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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 "MXKPhoneNumber.h"
|
||||
|
||||
@import libPhoneNumber_iOS;
|
||||
|
||||
@implementation MXKPhoneNumber
|
||||
|
||||
@synthesize msisdn;
|
||||
|
||||
- (id)initWithTextNumber:(NSString*)textNumber type:(NSString*)type contactID:(NSString*)contactID matrixID:(NSString*)matrixID
|
||||
{
|
||||
self = [super initWithContactID:contactID matrixID:matrixID];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_type = type ? type : @"";
|
||||
_textNumber = textNumber ? textNumber : @"" ;
|
||||
_cleanedPhonenumber = [MXKPhoneNumber cleanPhonenumber:_textNumber];
|
||||
_defaultCountryCode = nil;
|
||||
msisdn = nil;
|
||||
|
||||
_nbPhoneNumber = [[NBPhoneNumberUtil sharedInstance] parse:_cleanedPhonenumber defaultRegion:nil error:nil];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// remove the unuseful characters in a phonenumber
|
||||
+ (NSString*)cleanPhonenumber:(NSString*)phoneNumber
|
||||
{
|
||||
// sanity check
|
||||
if (nil == phoneNumber)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
// empty string
|
||||
if (0 == [phoneNumber length])
|
||||
{
|
||||
return @"";
|
||||
}
|
||||
|
||||
static NSCharacterSet *invertedPhoneCharSet = nil;
|
||||
|
||||
if (!invertedPhoneCharSet)
|
||||
{
|
||||
invertedPhoneCharSet = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789+*#,()"] invertedSet];
|
||||
}
|
||||
|
||||
return [[phoneNumber componentsSeparatedByCharactersInSet:invertedPhoneCharSet] componentsJoinedByString:@""];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)matchedWithPatterns:(NSArray*)patterns
|
||||
{
|
||||
// no number -> cannot match
|
||||
if (_textNumber.length == 0)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (patterns.count > 0)
|
||||
{
|
||||
for (NSString *pattern in patterns)
|
||||
{
|
||||
if ([_textNumber rangeOfString:pattern].location == NSNotFound)
|
||||
{
|
||||
NSString *cleanPattern = [[pattern componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] componentsJoinedByString:@""];
|
||||
|
||||
if ([_cleanedPhonenumber rangeOfString:cleanPattern].location == NSNotFound)
|
||||
{
|
||||
NSString *msisdnPattern;
|
||||
|
||||
if ([cleanPattern hasPrefix:@"+"])
|
||||
{
|
||||
msisdnPattern = [cleanPattern substringFromIndex:1];
|
||||
}
|
||||
else if ([cleanPattern hasPrefix:@"00"])
|
||||
{
|
||||
msisdnPattern = [cleanPattern substringFromIndex:2];
|
||||
}
|
||||
else
|
||||
{
|
||||
msisdnPattern = cleanPattern;
|
||||
}
|
||||
|
||||
// Check the msisdn
|
||||
if (!self.msisdn || !msisdnPattern.length || [self.msisdn rangeOfString:msisdnPattern].location == NSNotFound)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)hasPrefix:(NSString*)prefix
|
||||
{
|
||||
// no number -> cannot match
|
||||
if (_textNumber.length == 0)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([_textNumber hasPrefix:prefix])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Remove whitespace before checking the cleaned phone number
|
||||
prefix = [[prefix componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] componentsJoinedByString:@""];
|
||||
|
||||
if ([_cleanedPhonenumber hasPrefix:prefix])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (self.msisdn)
|
||||
{
|
||||
if ([prefix hasPrefix:@"+"])
|
||||
{
|
||||
prefix = [prefix substringFromIndex:1];
|
||||
}
|
||||
else if ([prefix hasPrefix:@"00"])
|
||||
{
|
||||
prefix = [prefix substringFromIndex:2];
|
||||
}
|
||||
|
||||
return [self.msisdn hasPrefix:prefix];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)setDefaultCountryCode:(NSString *)defaultCountryCode
|
||||
{
|
||||
if (![defaultCountryCode isEqualToString:_defaultCountryCode])
|
||||
{
|
||||
_nbPhoneNumber = [[NBPhoneNumberUtil sharedInstance] parse:_cleanedPhonenumber defaultRegion:defaultCountryCode error:nil];
|
||||
|
||||
_defaultCountryCode = defaultCountryCode;
|
||||
msisdn = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString*)msisdn
|
||||
{
|
||||
if (!msisdn && _nbPhoneNumber)
|
||||
{
|
||||
NSString *e164 = [[NBPhoneNumberUtil sharedInstance] format:_nbPhoneNumber numberFormat:NBEPhoneNumberFormatE164 error:nil];
|
||||
if ([e164 hasPrefix:@"+"])
|
||||
{
|
||||
msisdn = [e164 substringFromIndex:1];
|
||||
}
|
||||
else if ([e164 hasPrefix:@"00"])
|
||||
{
|
||||
msisdn = [e164 substringFromIndex:2];
|
||||
}
|
||||
}
|
||||
return msisdn;
|
||||
}
|
||||
|
||||
#pragma mark NSCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_type = [coder decodeObjectForKey:@"type"];
|
||||
_textNumber = [coder decodeObjectForKey:@"textNumber"];
|
||||
_cleanedPhonenumber = [coder decodeObjectForKey:@"cleanedPhonenumber"];
|
||||
_defaultCountryCode = [coder decodeObjectForKey:@"countryCode"];
|
||||
|
||||
_nbPhoneNumber = [[NBPhoneNumberUtil sharedInstance] parse:_cleanedPhonenumber defaultRegion:_defaultCountryCode error:nil];
|
||||
msisdn = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder
|
||||
{
|
||||
[super encodeWithCoder:coder];
|
||||
|
||||
[coder encodeObject:_type forKey:@"type"];
|
||||
[coder encodeObject:_textNumber forKey:@"textNumber"];
|
||||
[coder encodeObject:_cleanedPhonenumber forKey:@"cleanedPhonenumber"];
|
||||
[coder encodeObject:_defaultCountryCode forKey:@"countryCode"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 "MXKContact.h"
|
||||
|
||||
@interface MXKSectionedContacts : NSObject {
|
||||
int contactsCount;
|
||||
NSArray<NSString*> *sectionTitles;
|
||||
NSArray<NSArray<MXKContact*>*> *sectionedContacts;
|
||||
}
|
||||
|
||||
@property (nonatomic, readonly) int contactsCount;
|
||||
@property (nonatomic, readonly) NSArray<NSString*> *sectionTitles;
|
||||
@property (nonatomic, readonly) NSArray<NSArray<MXKContact*>*> *sectionedContacts;
|
||||
|
||||
- (instancetype)initWithContacts:(NSArray<NSArray<MXKContact*>*> *)inSectionedContacts andTitles:(NSArray<NSString*> *)titles andCount:(int)count;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
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 "MXKSectionedContacts.h"
|
||||
|
||||
@implementation MXKSectionedContacts
|
||||
|
||||
@synthesize contactsCount, sectionTitles, sectionedContacts;
|
||||
|
||||
-(id)initWithContacts:(NSArray<NSArray<MXKContact*> *> *)inSectionedContacts andTitles:(NSArray<NSString *> *)titles andCount:(int)count {
|
||||
if (self = [super init]) {
|
||||
contactsCount = count;
|
||||
sectionedContacts = inSectionedContacts;
|
||||
sectionTitles = titles;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations 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 "MXKGroupCellDataStoring.h"
|
||||
|
||||
/**
|
||||
`MXKGroupCellData` modelised the data for a `MXKGroupTableViewCell` cell.
|
||||
*/
|
||||
@interface MXKGroupCellData : MXKCellData <MXKGroupCellDataStoring>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations 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 "MXKGroupCellData.h"
|
||||
|
||||
#import "MXKSessionGroupsDataSource.h"
|
||||
|
||||
@implementation MXKGroupCellData
|
||||
@synthesize group, groupsDataSource, groupDisplayname, sortingDisplayname;
|
||||
|
||||
- (instancetype)initWithGroup:(MXGroup*)theGroup andGroupsDataSource:(MXKSessionGroupsDataSource*)theGroupsDataSource
|
||||
{
|
||||
self = [self init];
|
||||
if (self)
|
||||
{
|
||||
groupsDataSource = theGroupsDataSource;
|
||||
[self updateWithGroup:theGroup];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateWithGroup:(MXGroup*)theGroup
|
||||
{
|
||||
group = theGroup;
|
||||
|
||||
groupDisplayname = sortingDisplayname = group.profile.name;
|
||||
|
||||
if (!groupDisplayname.length)
|
||||
{
|
||||
groupDisplayname = group.groupId;
|
||||
// Ignore the prefix '+' of the group id during sorting.
|
||||
sortingDisplayname = [groupDisplayname substringFromIndex:1];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations 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 "MXKCellData.h"
|
||||
|
||||
@class MXKSessionGroupsDataSource;
|
||||
|
||||
/**
|
||||
`MXKGroupCellDataStoring` defines a protocol a class must conform in order to store group cell data
|
||||
managed by `MXKSessionGroupsDataSource`.
|
||||
*/
|
||||
@protocol MXKGroupCellDataStoring <NSObject>
|
||||
|
||||
@property (nonatomic, weak, readonly) MXKSessionGroupsDataSource *groupsDataSource;
|
||||
|
||||
@property (nonatomic, readonly) MXGroup *group;
|
||||
|
||||
@property (nonatomic, readonly) NSString *groupDisplayname;
|
||||
@property (nonatomic, readonly) NSString *sortingDisplayname;
|
||||
|
||||
#pragma mark - Public methods
|
||||
/**
|
||||
Create a new `MXKCellData` object for a new group cell.
|
||||
|
||||
@param group the `MXGroup` object that has data about the group.
|
||||
@param groupsDataSource the `MXKSessionGroupsDataSource` object that will use this instance.
|
||||
@return the newly created instance.
|
||||
*/
|
||||
- (instancetype)initWithGroup:(MXGroup*)group andGroupsDataSource:(MXKSessionGroupsDataSource*)groupsDataSource;
|
||||
|
||||
/**
|
||||
The `MXKSessionGroupsDataSource` object calls this method when the group data has been updated.
|
||||
|
||||
@param group the updated group.
|
||||
*/
|
||||
- (void)updateWithGroup:(MXGroup*)group;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations 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 "MXKDataSource.h"
|
||||
#import "MXKGroupCellData.h"
|
||||
|
||||
/**
|
||||
Identifier to use for cells that display a group.
|
||||
*/
|
||||
extern NSString *const kMXKGroupCellIdentifier;
|
||||
|
||||
/**
|
||||
'MXKSessionGroupsDataSource' is a base class to handle the groups of a matrix session.
|
||||
A 'MXKSessionGroupsDataSource' instance provides the data source for `MXKGroupListViewController`.
|
||||
|
||||
A section is created to handle the invitations to a group, the first one if any.
|
||||
*/
|
||||
@interface MXKSessionGroupsDataSource : MXKDataSource <UITableViewDataSource>
|
||||
{
|
||||
@protected
|
||||
|
||||
/**
|
||||
The current list of the group invitations (sorted in the alphabetic order).
|
||||
This list takes into account potential filter defined by`patternsList`.
|
||||
*/
|
||||
NSMutableArray<MXKGroupCellData*> *groupsInviteCellDataArray;
|
||||
|
||||
/**
|
||||
The current displayed list of the joined groups (sorted in the alphabetic order).
|
||||
This list takes into account potential filter defined by`patternsList`.
|
||||
*/
|
||||
NSMutableArray<MXKGroupCellData*> *groupsCellDataArray;
|
||||
}
|
||||
|
||||
@property (nonatomic) NSInteger groupInvitesSection;
|
||||
@property (nonatomic) NSInteger joinedGroupsSection;
|
||||
|
||||
#pragma mark - Life cycle
|
||||
|
||||
/**
|
||||
Refresh all the groups summary.
|
||||
The group data are not synced with the server, use this method to refresh them according to your needs.
|
||||
|
||||
@param completion the block to execute when a request has been done for each group (whatever the result of the requests).
|
||||
You may specify nil for this parameter.
|
||||
*/
|
||||
- (void)refreshGroupsSummary:(void (^)(void))completion;
|
||||
|
||||
/**
|
||||
Filter the current groups list according to the provided patterns.
|
||||
When patterns are not empty, the search result is stored in `filteredGroupsCellDataArray`,
|
||||
this array provides then data for the cells served by `MXKSessionGroupsDataSource`.
|
||||
|
||||
@param patternsList the list of patterns (`NSString` instances) to match with. Set nil to cancel search.
|
||||
*/
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList;
|
||||
|
||||
/**
|
||||
Get the data for the cell at the given index path.
|
||||
|
||||
@param indexPath the index of the cell in the table
|
||||
@return the cell data
|
||||
*/
|
||||
- (id<MXKGroupCellDataStoring>)cellDataAtIndex:(NSIndexPath*)indexPath;
|
||||
|
||||
/**
|
||||
Get the index path of the cell related to the provided groupId.
|
||||
|
||||
@param groupId the group identifier.
|
||||
@return indexPath the index of the cell (nil if not found).
|
||||
*/
|
||||
- (NSIndexPath*)cellIndexPathWithGroupId:(NSString*)groupId;
|
||||
|
||||
/**
|
||||
Leave the group displayed at the provided path.
|
||||
|
||||
@param indexPath the index of the group cell in the table
|
||||
*/
|
||||
- (void)leaveGroupAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,611 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector 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 "MXKSessionGroupsDataSource.h"
|
||||
|
||||
#import "NSBundle+MatrixKit.h"
|
||||
|
||||
#import "MXKConstants.h"
|
||||
|
||||
#import "MXKSwiftHeader.h"
|
||||
|
||||
#pragma mark - Constant definitions
|
||||
NSString *const kMXKGroupCellIdentifier = @"kMXKGroupCellIdentifier";
|
||||
|
||||
|
||||
@interface MXKSessionGroupsDataSource ()
|
||||
{
|
||||
/**
|
||||
Internal array used to regulate change notifications.
|
||||
Cell data changes are stored instantly in this array.
|
||||
We wait at least for 500 ms between two notifications of the delegate.
|
||||
*/
|
||||
NSMutableArray *internalCellDataArray;
|
||||
|
||||
/*
|
||||
Timer to not notify the delegate on every changes.
|
||||
*/
|
||||
NSTimer *timer;
|
||||
|
||||
/*
|
||||
Tells whether some changes must be notified.
|
||||
*/
|
||||
BOOL isDataChangePending;
|
||||
|
||||
/**
|
||||
Store the current search patterns list.
|
||||
*/
|
||||
NSArray* searchPatternsList;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKSessionGroupsDataSource
|
||||
|
||||
- (instancetype)initWithMatrixSession:(MXSession *)matrixSession
|
||||
{
|
||||
self = [super initWithMatrixSession:matrixSession];
|
||||
if (self)
|
||||
{
|
||||
internalCellDataArray = [NSMutableArray array];
|
||||
groupsCellDataArray = [NSMutableArray array];
|
||||
groupsInviteCellDataArray = [NSMutableArray array];
|
||||
|
||||
isDataChangePending = NO;
|
||||
|
||||
// Set default data and view classes
|
||||
[self registerCellDataClass:MXKGroupCellData.class forCellIdentifier:kMXKGroupCellIdentifier];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
groupsCellDataArray = nil;
|
||||
groupsInviteCellDataArray = nil;
|
||||
internalCellDataArray = nil;
|
||||
|
||||
searchPatternsList = nil;
|
||||
|
||||
[timer invalidate];
|
||||
timer = nil;
|
||||
|
||||
[super destroy];
|
||||
}
|
||||
|
||||
- (void)didMXSessionStateChange
|
||||
{
|
||||
if (MXSessionStateRunning <= self.mxSession.state)
|
||||
{
|
||||
// Check whether some data have been already load
|
||||
if (0 == internalCellDataArray.count)
|
||||
{
|
||||
[self loadData];
|
||||
}
|
||||
else if (self.mxSession.state == MXSessionStateRunning)
|
||||
{
|
||||
// Refresh the group data
|
||||
[self refreshGroupsSummary:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)refreshGroupsSummary:(void (^)(void))completion
|
||||
{
|
||||
MXLogDebug(@"[MXKSessionGroupsDataSource] refreshGroupsSummary");
|
||||
|
||||
__block NSUInteger count = internalCellDataArray.count;
|
||||
|
||||
if (count)
|
||||
{
|
||||
for (id<MXKGroupCellDataStoring> groupData in internalCellDataArray)
|
||||
{
|
||||
// Force the matrix session to refresh the group summary.
|
||||
[self.mxSession updateGroupSummary:groupData.group success:^{
|
||||
|
||||
if (completion && !(--count))
|
||||
{
|
||||
// All the requests have been done.
|
||||
completion ();
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
MXLogDebug(@"[MXKSessionGroupsDataSource] refreshGroupsSummary: group summary update failed %@", groupData.group.groupId);
|
||||
|
||||
if (completion && !(--count))
|
||||
{
|
||||
// All the requests have been done.
|
||||
completion ();
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
}
|
||||
else if (completion)
|
||||
{
|
||||
completion();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList
|
||||
{
|
||||
if (patternsList.count)
|
||||
{
|
||||
searchPatternsList = patternsList;
|
||||
}
|
||||
else
|
||||
{
|
||||
searchPatternsList = nil;
|
||||
}
|
||||
|
||||
[self onCellDataChange];
|
||||
}
|
||||
|
||||
- (id<MXKGroupCellDataStoring>)cellDataAtIndex:(NSIndexPath*)indexPath
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData;
|
||||
|
||||
if (indexPath.section == _groupInvitesSection)
|
||||
{
|
||||
if (indexPath.row < groupsInviteCellDataArray.count)
|
||||
{
|
||||
groupData = groupsInviteCellDataArray[indexPath.row];
|
||||
}
|
||||
}
|
||||
else if (indexPath.section == _joinedGroupsSection)
|
||||
{
|
||||
if (indexPath.row < groupsCellDataArray.count)
|
||||
{
|
||||
groupData = groupsCellDataArray[indexPath.row];
|
||||
}
|
||||
}
|
||||
|
||||
return groupData;
|
||||
}
|
||||
|
||||
- (NSIndexPath*)cellIndexPathWithGroupId:(NSString*)groupId
|
||||
{
|
||||
// Look for the cell
|
||||
if (_groupInvitesSection != -1)
|
||||
{
|
||||
for (NSInteger index = 0; index < groupsInviteCellDataArray.count; index ++)
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData = groupsInviteCellDataArray[index];
|
||||
if ([groupId isEqualToString:groupData.group.groupId])
|
||||
{
|
||||
// Got it
|
||||
return [NSIndexPath indexPathForRow:index inSection:_groupInvitesSection];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_joinedGroupsSection != -1)
|
||||
{
|
||||
for (NSInteger index = 0; index < groupsCellDataArray.count; index ++)
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData = groupsCellDataArray[index];
|
||||
if ([groupId isEqualToString:groupData.group.groupId])
|
||||
{
|
||||
// Got it
|
||||
return [NSIndexPath indexPathForRow:index inSection:_joinedGroupsSection];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - Groups processing
|
||||
|
||||
- (void)loadData
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionNewGroupInviteNotification object:self.mxSession];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidJoinGroupNotification object:self.mxSession];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidLeaveGroupNotification object:self.mxSession];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidUpdateGroupSummaryNotification object:self.mxSession];
|
||||
|
||||
// Reset the table
|
||||
[internalCellDataArray removeAllObjects];
|
||||
|
||||
// Retrieve the MXKCellData class to manage the data
|
||||
Class class = [self cellDataClassForCellIdentifier:kMXKGroupCellIdentifier];
|
||||
NSAssert([class conformsToProtocol:@protocol(MXKGroupCellDataStoring)], @"MXKSessionGroupsDataSource only manages MXKCellData that conforms to MXKGroupCellDataStoring protocol");
|
||||
|
||||
// Listen to MXSession groups changes
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onNewGroupInvite:) name:kMXSessionNewGroupInviteNotification object:self.mxSession];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didJoinGroup:) name:kMXSessionDidJoinGroupNotification object:self.mxSession];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didLeaveGroup:) name:kMXSessionDidLeaveGroupNotification object:self.mxSession];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateGroup:) name:kMXSessionDidUpdateGroupSummaryNotification object:self.mxSession];
|
||||
|
||||
NSDate *startDate = [NSDate date];
|
||||
|
||||
NSArray *groups = self.mxSession.groups;
|
||||
for (MXGroup *group in groups)
|
||||
{
|
||||
id<MXKGroupCellDataStoring> cellData = [[class alloc] initWithGroup:group andGroupsDataSource:self];
|
||||
if (cellData)
|
||||
{
|
||||
[internalCellDataArray addObject:cellData];
|
||||
|
||||
// Force the matrix session to refresh the group summary.
|
||||
[self.mxSession updateGroupSummary:group success:nil failure:^(NSError *error) {
|
||||
MXLogDebug(@"[MXKSessionGroupsDataSource] loadData: group summary update failed %@", group.groupId);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
MXLogDebug(@"[MXKSessionGroupsDataSource] Loaded %tu groups in %.3fms", groups.count, [[NSDate date] timeIntervalSinceDate:startDate] * 1000);
|
||||
|
||||
[self sortCellData];
|
||||
[self onCellDataChange];
|
||||
}
|
||||
|
||||
- (void)didUpdateGroup:(NSNotification *)notif
|
||||
{
|
||||
MXGroup *group = notif.userInfo[kMXSessionNotificationGroupKey];
|
||||
if (group)
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData = [self cellDataWithGroupId:group.groupId];
|
||||
if (groupData)
|
||||
{
|
||||
[groupData updateWithGroup:group];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXKSessionGroupsDataSource] didUpdateGroup: Cannot find the changed group for %@ (%@). It is probably not managed by this group data source", group.groupId, group);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[self sortCellData];
|
||||
[self onCellDataChange];
|
||||
}
|
||||
|
||||
- (void)onNewGroupInvite:(NSNotification *)notif
|
||||
{
|
||||
MXGroup *group = notif.userInfo[kMXSessionNotificationGroupKey];
|
||||
if (group)
|
||||
{
|
||||
// Add the group if there is not yet a cell for it
|
||||
id<MXKGroupCellDataStoring> groupData = [self cellDataWithGroupId:group.groupId];
|
||||
if (nil == groupData)
|
||||
{
|
||||
MXLogDebug(@"MXKSessionGroupsDataSource] Add new group invite: %@", group.groupId);
|
||||
|
||||
// Retrieve the MXKCellData class to manage the data
|
||||
Class class = [self cellDataClassForCellIdentifier:kMXKGroupCellIdentifier];
|
||||
|
||||
id<MXKGroupCellDataStoring> cellData = [[class alloc] initWithGroup:group andGroupsDataSource:self];
|
||||
if (cellData)
|
||||
{
|
||||
[internalCellDataArray addObject:cellData];
|
||||
|
||||
[self sortCellData];
|
||||
[self onCellDataChange];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didJoinGroup:(NSNotification *)notif
|
||||
{
|
||||
MXGroup *group = notif.userInfo[kMXSessionNotificationGroupKey];
|
||||
if (group)
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData = [self cellDataWithGroupId:group.groupId];
|
||||
if (groupData)
|
||||
{
|
||||
MXLogDebug(@"MXKSessionGroupsDataSource] Update joined room: %@", group.groupId);
|
||||
[groupData updateWithGroup:group];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"MXKSessionGroupsDataSource] Add new joined invite: %@", group.groupId);
|
||||
|
||||
// Retrieve the MXKCellData class to manage the data
|
||||
Class class = [self cellDataClassForCellIdentifier:kMXKGroupCellIdentifier];
|
||||
|
||||
id<MXKGroupCellDataStoring> cellData = [[class alloc] initWithGroup:group andGroupsDataSource:self];
|
||||
if (cellData)
|
||||
{
|
||||
[internalCellDataArray addObject:cellData];
|
||||
}
|
||||
}
|
||||
|
||||
[self sortCellData];
|
||||
[self onCellDataChange];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didLeaveGroup:(NSNotification *)notif
|
||||
{
|
||||
NSString *groupId = notif.userInfo[kMXSessionNotificationGroupIdKey];
|
||||
if (groupId)
|
||||
{
|
||||
[self removeGroup:groupId];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeGroup:(NSString*)groupId
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData = [self cellDataWithGroupId:groupId];
|
||||
if (groupData)
|
||||
{
|
||||
MXLogDebug(@"MXKSessionGroupsDataSource] Remove left group: %@", groupId);
|
||||
|
||||
[internalCellDataArray removeObject:groupData];
|
||||
|
||||
[self sortCellData];
|
||||
[self onCellDataChange];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onCellDataChange
|
||||
{
|
||||
isDataChangePending = NO;
|
||||
|
||||
// Check no notification was done recently.
|
||||
// Note: do not wait in case of search
|
||||
if (timer == nil || searchPatternsList)
|
||||
{
|
||||
[timer invalidate];
|
||||
timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(checkPendingUpdate:) userInfo:nil repeats:NO];
|
||||
|
||||
// Prepare cell data array, and notify the delegate.
|
||||
[self prepareCellDataAndNotifyChanges];
|
||||
}
|
||||
else
|
||||
{
|
||||
isDataChangePending = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)checkPendingUpdate:(id)sender
|
||||
{
|
||||
[timer invalidate];
|
||||
timer = nil;
|
||||
|
||||
if (isDataChangePending)
|
||||
{
|
||||
[self onCellDataChange];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sortCellData
|
||||
{
|
||||
// Order alphabetically the groups
|
||||
[internalCellDataArray sortUsingComparator:^NSComparisonResult(id<MXKGroupCellDataStoring> cellData1, id<MXKGroupCellDataStoring> cellData2)
|
||||
{
|
||||
if (cellData1.sortingDisplayname.length && cellData2.sortingDisplayname.length)
|
||||
{
|
||||
return [cellData1.sortingDisplayname compare:cellData2.sortingDisplayname options:NSCaseInsensitiveSearch];
|
||||
}
|
||||
else if (cellData1.sortingDisplayname.length)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
else if (cellData2.sortingDisplayname.length)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
return NSOrderedSame;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)prepareCellDataAndNotifyChanges
|
||||
{
|
||||
// Prepare the cell data arrays by considering the potential filter.
|
||||
[groupsInviteCellDataArray removeAllObjects];
|
||||
[groupsCellDataArray removeAllObjects];
|
||||
for (id<MXKGroupCellDataStoring> groupData in internalCellDataArray)
|
||||
{
|
||||
BOOL isKept = !searchPatternsList;
|
||||
|
||||
for (NSString* pattern in searchPatternsList)
|
||||
{
|
||||
if (groupData.groupDisplayname && [groupData.groupDisplayname rangeOfString:pattern options:NSCaseInsensitiveSearch].location != NSNotFound)
|
||||
{
|
||||
isKept = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isKept)
|
||||
{
|
||||
if (groupData.group.membership == MXMembershipInvite)
|
||||
{
|
||||
[groupsInviteCellDataArray addObject:groupData];
|
||||
}
|
||||
else
|
||||
{
|
||||
[groupsCellDataArray addObject:groupData];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update here data source state
|
||||
if (state != MXKDataSourceStateReady)
|
||||
{
|
||||
state = MXKDataSourceStateReady;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didStateChange:)])
|
||||
{
|
||||
[self.delegate dataSource:self didStateChange:state];
|
||||
}
|
||||
}
|
||||
|
||||
// And inform the delegate about the update
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
|
||||
// Find the cell data that stores information about the given group id
|
||||
- (id<MXKGroupCellDataStoring>)cellDataWithGroupId:(NSString*)groupId
|
||||
{
|
||||
id<MXKGroupCellDataStoring> theGroupData;
|
||||
for (id<MXKGroupCellDataStoring> groupData in internalCellDataArray)
|
||||
{
|
||||
if ([groupData.group.groupId isEqualToString:groupId])
|
||||
{
|
||||
theGroupData = groupData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return theGroupData;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
NSInteger count = 0;
|
||||
_groupInvitesSection = _joinedGroupsSection = -1;
|
||||
|
||||
// Check whether all data sources are ready before rendering groups.
|
||||
if (self.state == MXKDataSourceStateReady)
|
||||
{
|
||||
if (groupsInviteCellDataArray.count)
|
||||
{
|
||||
_groupInvitesSection = count++;
|
||||
}
|
||||
if (groupsCellDataArray.count)
|
||||
{
|
||||
_joinedGroupsSection = count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
if (section == _groupInvitesSection)
|
||||
{
|
||||
return groupsInviteCellDataArray.count;
|
||||
}
|
||||
else if (section == _joinedGroupsSection)
|
||||
{
|
||||
return groupsCellDataArray.count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
NSString* sectionTitle = nil;
|
||||
|
||||
if (section == _groupInvitesSection)
|
||||
{
|
||||
sectionTitle = [MatrixKitL10n groupInviteSection];
|
||||
}
|
||||
else if (section == _joinedGroupsSection)
|
||||
{
|
||||
sectionTitle = [MatrixKitL10n groupSection];
|
||||
}
|
||||
|
||||
return sectionTitle;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
id<MXKGroupCellDataStoring> groupData;
|
||||
|
||||
if (indexPath.section == _groupInvitesSection)
|
||||
{
|
||||
if (indexPath.row < groupsInviteCellDataArray.count)
|
||||
{
|
||||
groupData = groupsInviteCellDataArray[indexPath.row];
|
||||
}
|
||||
}
|
||||
else if (indexPath.section == _joinedGroupsSection)
|
||||
{
|
||||
if (indexPath.row < groupsCellDataArray.count)
|
||||
{
|
||||
groupData = groupsCellDataArray[indexPath.row];
|
||||
}
|
||||
}
|
||||
|
||||
if (groupData)
|
||||
{
|
||||
NSString *cellIdentifier = [self.delegate cellReuseIdentifierForCellData:groupData];
|
||||
if (cellIdentifier)
|
||||
{
|
||||
UITableViewCell<MXKCellRendering> *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
|
||||
|
||||
// Make sure we listen to user actions on the cell
|
||||
cell.delegate = self;
|
||||
|
||||
// Make the bubble display the data
|
||||
[cell render:groupData];
|
||||
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a fake cell to prevent app from crashing.
|
||||
return [[UITableViewCell alloc] init];
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
// Return NO if you do not want the specified item to be editable.
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (editingStyle == UITableViewCellEditingStyleDelete)
|
||||
{
|
||||
[self leaveGroupAtIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)leaveGroupAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
id<MXKGroupCellDataStoring> cellData = [self cellDataAtIndex:indexPath];
|
||||
|
||||
if (cellData.group)
|
||||
{
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
[self.mxSession leaveGroup:cellData.group.groupId success:^{
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
// Refresh the table content
|
||||
typeof(self) self = weakSelf;
|
||||
[self removeGroup:cellData.group.groupId];
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
MXLogDebug(@"[MXKSessionGroupsDataSource] Failed to leave group (%@)", cellData.group.groupId);
|
||||
|
||||
// Notify MatrixKit user
|
||||
NSString *myUserId = self.mxSession.myUser.userId;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
|
||||
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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>
|
||||
|
||||
typedef enum : NSUInteger {
|
||||
MXK3PIDAuthStateUnknown,
|
||||
MXK3PIDAuthStateTokenRequested,
|
||||
MXK3PIDAuthStateTokenReceived,
|
||||
MXK3PIDAuthStateTokenSubmitted,
|
||||
MXK3PIDAuthStateAuthenticated
|
||||
} MXK3PIDAuthState;
|
||||
|
||||
|
||||
@interface MXK3PID : NSObject
|
||||
|
||||
/**
|
||||
The type of the third party media.
|
||||
*/
|
||||
@property (nonatomic, readonly) MX3PIDMedium medium;
|
||||
|
||||
/**
|
||||
The third party media (email address, msisdn,...).
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *address;
|
||||
|
||||
/**
|
||||
The current client secret key used during third party validation.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *clientSecret;
|
||||
|
||||
/**
|
||||
The current session identifier during third party validation.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *sid;
|
||||
|
||||
/**
|
||||
The id of the user on Matrix.
|
||||
nil if unknown or not yet resolved.
|
||||
*/
|
||||
@property (nonatomic) NSString *userId;
|
||||
|
||||
@property (nonatomic, readonly) MXK3PIDAuthState validationState;
|
||||
|
||||
/**
|
||||
Initialise the instance with a 3PID.
|
||||
|
||||
@param medium the medium.
|
||||
@param address the id of the contact on this medium.
|
||||
@return the new instance.
|
||||
*/
|
||||
- (instancetype)initWithMedium:(NSString*)medium andAddress:(NSString*)address;
|
||||
|
||||
/**
|
||||
Cancel the current request, and reset parameters
|
||||
*/
|
||||
- (void)cancelCurrentRequest;
|
||||
|
||||
/**
|
||||
Start the validation process
|
||||
The identity server will send a validation token by email or sms.
|
||||
|
||||
In case of email, the end user must click on the link in the received email
|
||||
to validate their email address in order to be able to call add3PIDToUser successfully.
|
||||
|
||||
In case of phone number, the end user must send back the sms token
|
||||
in order to be able to call add3PIDToUser successfully.
|
||||
|
||||
@param restClient used to make matrix API requests during validation process.
|
||||
@param isDuringRegistration tell whether this request occurs during a registration flow.
|
||||
@param nextLink the link the validation page will automatically open. Can be nil.
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)requestValidationTokenWithMatrixRestClient:(MXRestClient*)restClient
|
||||
isDuringRegistration:(BOOL)isDuringRegistration
|
||||
nextLink:(NSString*)nextLink
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Submit the received validation token.
|
||||
|
||||
@param token the validation token.
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)submitValidationToken:(NSString *)token
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Link a 3rd party id to the user.
|
||||
|
||||
@param bind whether the homeserver should also bind this third party identifier
|
||||
to the account's Matrix ID with the identity server.
|
||||
@param success A block object called when the operation succeeds. It provides the raw
|
||||
server response.
|
||||
@param failure A block object called when the operation fails.
|
||||
*/
|
||||
- (void)add3PIDToUser:(BOOL)bind
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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 "MXK3PID.h"
|
||||
|
||||
@import libPhoneNumber_iOS;
|
||||
|
||||
@interface MXK3PID ()
|
||||
{
|
||||
MXRestClient *mxRestClient;
|
||||
MXHTTPOperation *currentRequest;
|
||||
}
|
||||
@property (nonatomic) NSString *clientSecret;
|
||||
@property (nonatomic) NSUInteger sendAttempt;
|
||||
@property (nonatomic) NSString *sid;
|
||||
@property (nonatomic) MXIdentityService *identityService;
|
||||
@property (nonatomic) NSString *submitUrl;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXK3PID
|
||||
|
||||
- (instancetype)initWithMedium:(NSString *)medium andAddress:(NSString *)address
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
_medium = [medium copy];
|
||||
_address = [address copy];
|
||||
self.clientSecret = [MXTools generateSecret];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)cancelCurrentRequest
|
||||
{
|
||||
_validationState = MXK3PIDAuthStateUnknown;
|
||||
|
||||
[currentRequest cancel];
|
||||
currentRequest = nil;
|
||||
mxRestClient = nil;
|
||||
self.identityService = nil;
|
||||
|
||||
self.sendAttempt = 1;
|
||||
self.sid = nil;
|
||||
// Removed potential linked userId
|
||||
self.userId = nil;
|
||||
}
|
||||
|
||||
- (void)requestValidationTokenWithMatrixRestClient:(MXRestClient*)restClient
|
||||
isDuringRegistration:(BOOL)isDuringRegistration
|
||||
nextLink:(NSString*)nextLink
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *error))failure
|
||||
{
|
||||
// Sanity Check
|
||||
if (_validationState != MXK3PIDAuthStateTokenRequested && restClient)
|
||||
{
|
||||
// Reset if the current state is different than "Unknown"
|
||||
if (_validationState != MXK3PIDAuthStateUnknown)
|
||||
{
|
||||
[self cancelCurrentRequest];
|
||||
}
|
||||
|
||||
NSString *identityServer = restClient.identityServer;
|
||||
if (identityServer)
|
||||
{
|
||||
// Use same identity server as REST client for validation token submission
|
||||
self.identityService = [[MXIdentityService alloc] initWithIdentityServer:identityServer accessToken:nil andHomeserverRestClient:restClient];
|
||||
}
|
||||
|
||||
if ([self.medium isEqualToString:kMX3PIDMediumEmail])
|
||||
{
|
||||
_validationState = MXK3PIDAuthStateTokenRequested;
|
||||
mxRestClient = restClient;
|
||||
|
||||
currentRequest = [mxRestClient requestTokenForEmail:self.address isDuringRegistration:isDuringRegistration clientSecret:self.clientSecret sendAttempt:self.sendAttempt nextLink:nextLink success:^(NSString *sid) {
|
||||
|
||||
self->_validationState = MXK3PIDAuthStateTokenReceived;
|
||||
self->currentRequest = nil;
|
||||
self.sid = sid;
|
||||
|
||||
if (success)
|
||||
{
|
||||
success();
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
// Return in unknown state
|
||||
self->_validationState = MXK3PIDAuthStateUnknown;
|
||||
self->currentRequest = nil;
|
||||
// Increment attempt counter
|
||||
self.sendAttempt++;
|
||||
|
||||
if (failure)
|
||||
{
|
||||
failure (error);
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
else if ([self.medium isEqualToString:kMX3PIDMediumMSISDN])
|
||||
{
|
||||
_validationState = MXK3PIDAuthStateTokenRequested;
|
||||
mxRestClient = restClient;
|
||||
|
||||
NSString *phoneNumber = [NSString stringWithFormat:@"+%@", self.address];
|
||||
|
||||
currentRequest = [mxRestClient requestTokenForPhoneNumber:phoneNumber isDuringRegistration:isDuringRegistration countryCode:nil clientSecret:self.clientSecret sendAttempt:self.sendAttempt nextLink:nextLink success:^(NSString *sid, NSString *msisdn, NSString *submitUrl) {
|
||||
|
||||
self->_validationState = MXK3PIDAuthStateTokenReceived;
|
||||
self->currentRequest = nil;
|
||||
self.sid = sid;
|
||||
self.submitUrl = submitUrl;
|
||||
|
||||
if (success)
|
||||
{
|
||||
success();
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
// Return in unknown state
|
||||
self->_validationState = MXK3PIDAuthStateUnknown;
|
||||
self->currentRequest = nil;
|
||||
// Increment attempt counter
|
||||
self.sendAttempt++;
|
||||
|
||||
if (failure)
|
||||
{
|
||||
failure (error);
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXK3PID] requestValidationToken: is not supported for this 3PID: %@ (%@)", self.address, self.medium);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXK3PID] Failed to request validation token for 3PID: %@ (%@), state: %lu", self.address, self.medium, (unsigned long)_validationState);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)submitValidationToken:(NSString *)token
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *error))failure
|
||||
{
|
||||
// Sanity Check
|
||||
if (_validationState == MXK3PIDAuthStateTokenReceived)
|
||||
{
|
||||
if (self.submitUrl)
|
||||
{
|
||||
_validationState = MXK3PIDAuthStateTokenSubmitted;
|
||||
|
||||
currentRequest = [self submitMsisdnTokenOtherUrl:self.submitUrl token:token medium:self.medium clientSecret:self.clientSecret sid:self.sid success:^{
|
||||
|
||||
self->_validationState = MXK3PIDAuthStateAuthenticated;
|
||||
self->currentRequest = nil;
|
||||
|
||||
if (success)
|
||||
{
|
||||
success();
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
// Return in previous state
|
||||
self->_validationState = MXK3PIDAuthStateTokenReceived;
|
||||
self->currentRequest = nil;
|
||||
|
||||
if (failure)
|
||||
{
|
||||
failure (error);
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
else if (self.identityService)
|
||||
{
|
||||
_validationState = MXK3PIDAuthStateTokenSubmitted;
|
||||
|
||||
currentRequest = [self.identityService submit3PIDValidationToken:token medium:self.medium clientSecret:self.clientSecret sid:self.sid success:^{
|
||||
|
||||
self->_validationState = MXK3PIDAuthStateAuthenticated;
|
||||
self->currentRequest = nil;
|
||||
|
||||
if (success)
|
||||
{
|
||||
success();
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
// Return in previous state
|
||||
self->_validationState = MXK3PIDAuthStateTokenReceived;
|
||||
self->currentRequest = nil;
|
||||
|
||||
if (failure)
|
||||
{
|
||||
failure (error);
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXK3PID] Failed to submit validation token for 3PID: %@ (%@), identity service is not set", self.address, self.medium);
|
||||
|
||||
if (failure)
|
||||
{
|
||||
failure(nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXK3PID] Failed to submit validation token for 3PID: %@ (%@), state: %lu", self.address, self.medium, (unsigned long)_validationState);
|
||||
|
||||
if (failure)
|
||||
{
|
||||
failure(nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (MXHTTPOperation *)submitMsisdnTokenOtherUrl:(NSString *)url
|
||||
token:(NSString*)token
|
||||
medium:(NSString *)medium
|
||||
clientSecret:(NSString *)clientSecret
|
||||
sid:(NSString *)sid
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *))failure
|
||||
{
|
||||
NSDictionary *parameters = @{
|
||||
@"sid": sid,
|
||||
@"client_secret": clientSecret,
|
||||
@"token": token
|
||||
};
|
||||
|
||||
MXHTTPClient *httpClient = [[MXHTTPClient alloc] initWithBaseURL:nil andOnUnrecognizedCertificateBlock:nil];
|
||||
return [httpClient requestWithMethod:@"POST"
|
||||
path:url
|
||||
parameters:parameters
|
||||
success:^(NSDictionary *JSONResponse) {
|
||||
success();
|
||||
}
|
||||
failure:failure];
|
||||
}
|
||||
|
||||
- (void)add3PIDToUser:(BOOL)bind
|
||||
success:(void (^)(void))success
|
||||
failure:(void (^)(NSError *error))failure
|
||||
{
|
||||
if ([self.medium isEqualToString:kMX3PIDMediumEmail] || [self.medium isEqualToString:kMX3PIDMediumMSISDN])
|
||||
{
|
||||
MXWeakify(self);
|
||||
|
||||
currentRequest = [mxRestClient add3PID:self.sid clientSecret:self.clientSecret bind:bind success:^{
|
||||
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
// Update linked userId in 3PID
|
||||
self.userId = self->mxRestClient.credentials.userId;
|
||||
self->currentRequest = nil;
|
||||
|
||||
if (success)
|
||||
{
|
||||
success();
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
self->currentRequest = nil;
|
||||
|
||||
if (failure)
|
||||
{
|
||||
failure (error);
|
||||
}
|
||||
|
||||
}];
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXK3PID] bindWithUserId: is not supported for this 3PID: %@ (%@)", self.address, self.medium);
|
||||
}
|
||||
|
||||
// Here the validation process failed
|
||||
if (failure)
|
||||
{
|
||||
failure (nil);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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>
|
||||
|
||||
typedef NS_ENUM(NSUInteger, MXKKeyPreSharingStrategy)
|
||||
{
|
||||
MXKKeyPreSharingNone = 0,
|
||||
MXKKeyPreSharingWhenEnteringRoom = 1,
|
||||
MXKKeyPreSharingWhenTyping = 2
|
||||
};
|
||||
|
||||
/**
|
||||
`MXKAppSettings` represents the application settings. Most of them are used to handle matrix session data.
|
||||
|
||||
The shared object `standardAppSettings` provides the default application settings defined in `standardUserDefaults`.
|
||||
Any property change of this shared settings is reported into `standardUserDefaults`.
|
||||
|
||||
Developper may define their own `MXKAppSettings` instances to handle specific setting values without impacting the shared object.
|
||||
*/
|
||||
@interface MXKAppSettings : NSObject
|
||||
|
||||
#pragma mark - /sync filter
|
||||
|
||||
/**
|
||||
Lazy load room members when /syncing with the homeserver.
|
||||
*/
|
||||
@property (nonatomic) BOOL syncWithLazyLoadOfRoomMembers;
|
||||
|
||||
#pragma mark - Room display
|
||||
|
||||
/**
|
||||
Display all received events in room history (Only recognized events are displayed, presently `custom` events are ignored).
|
||||
|
||||
This boolean value is defined in shared settings object with the key: `showAllEventsInRoomHistory`.
|
||||
Return NO if no value is defined.
|
||||
*/
|
||||
@property (nonatomic) BOOL showAllEventsInRoomHistory;
|
||||
|
||||
/**
|
||||
The types of events allowed to be displayed in room history.
|
||||
Its value depends on `showAllEventsInRoomHistory`.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<MXEventTypeString> *eventsFilterForMessages;
|
||||
|
||||
/**
|
||||
All the event types which may be displayed in the room history.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<MXEventTypeString> *allEventTypesForMessages;
|
||||
|
||||
/**
|
||||
An allow list for the types of events allowed to be displayed as the last message.
|
||||
|
||||
When `nil`, there is no list and all events are allowed.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<MXEventTypeString> *lastMessageEventTypesAllowList;
|
||||
|
||||
/**
|
||||
Add event types to `eventsFilterForMessages` and `eventsFilterForMessages`.
|
||||
|
||||
@param eventTypes the event types to add.
|
||||
*/
|
||||
- (void)addSupportedEventTypes:(NSArray<MXEventTypeString> *)eventTypes;
|
||||
|
||||
/**
|
||||
Remove event types from `eventsFilterForMessages` and `eventsFilterForMessages`.
|
||||
|
||||
@param eventTypes the event types to remove.
|
||||
*/
|
||||
- (void)removeSupportedEventTypes:(NSArray<MXEventTypeString> *)eventTypes;
|
||||
|
||||
/**
|
||||
Display redacted events in room history.
|
||||
|
||||
This boolean value is defined in shared settings object with the key: `showRedactionsInRoomHistory`.
|
||||
Return NO if no value is defined.
|
||||
*/
|
||||
@property (nonatomic) BOOL showRedactionsInRoomHistory;
|
||||
|
||||
/**
|
||||
Display unsupported/unexpected events in room history.
|
||||
|
||||
This boolean value is defined in shared settings object with the key: `showUnsupportedEventsInRoomHistory`.
|
||||
Return NO if no value is defined.
|
||||
*/
|
||||
@property (nonatomic) BOOL showUnsupportedEventsInRoomHistory;
|
||||
|
||||
/**
|
||||
Scheme with which to open HTTP links. e.g. if this is set to "googlechrome", any http:// links displayed in a room will be rewritten to use the googlechrome:// scheme.
|
||||
Defaults to "http".
|
||||
*/
|
||||
@property (nonatomic) NSString *httpLinkScheme;
|
||||
|
||||
/**
|
||||
Scheme with which to open HTTPS links. e.g. if this is set to "googlechromes", any https:// links displayed in a room will be rewritten to use the googlechromes:// scheme.
|
||||
Defaults to "https".
|
||||
*/
|
||||
@property (nonatomic) NSString *httpsLinkScheme;
|
||||
|
||||
/**
|
||||
Whether a bubble component should detect the first link in its event's body, storing it in the `link` property.
|
||||
|
||||
This boolean value is defined in shared settings object with the key: `enableBubbleComponentLinkDetection`.
|
||||
Return NO if no value is defined.
|
||||
*/
|
||||
@property (nonatomic) BOOL enableBubbleComponentLinkDetection;
|
||||
|
||||
/**
|
||||
Any hosts that should be ignored when calling `mxk_firstURLDetected` on an `NSString` without passing in any parameters.
|
||||
Customising this value modifies the behaviour of link detection in `MXKRoomBubbleComponent`.
|
||||
|
||||
This boolean value is defined in shared settings object with the key: `firstURLDetectionIgnoredHosts`.
|
||||
The default value of this property only contains the matrix.to host.
|
||||
*/
|
||||
@property (nonatomic) NSArray<NSString *> *firstURLDetectionIgnoredHosts;
|
||||
|
||||
/**
|
||||
Indicate to hide un-decryptable events before joining the room. Default is `NO`.
|
||||
*/
|
||||
@property (nonatomic) BOOL hidePreJoinedUndecryptableEvents;
|
||||
|
||||
/**
|
||||
Indicate to hide un-decryptable events in the room. Default is `NO`.
|
||||
*/
|
||||
@property (nonatomic) BOOL hideUndecryptableEvents;
|
||||
|
||||
/**
|
||||
Indicates the strategy for sharing the outbound session key to other devices of the room
|
||||
*/
|
||||
@property (nonatomic) MXKKeyPreSharingStrategy outboundGroupSessionKeyPreSharingStrategy;
|
||||
|
||||
#pragma mark - Room members
|
||||
|
||||
/**
|
||||
Sort room members by considering their presence.
|
||||
Set NO to sort members in alphabetic order.
|
||||
|
||||
This boolean value is defined in shared settings object with the key: `sortRoomMembersUsingLastSeenTime`.
|
||||
Return YES if no value is defined.
|
||||
*/
|
||||
@property (nonatomic) BOOL sortRoomMembersUsingLastSeenTime;
|
||||
|
||||
/**
|
||||
Show left members in room member list.
|
||||
|
||||
This boolean value is defined in shared settings object with the key: `showLeftMembersInRoomMemberList`.
|
||||
Return NO if no value is defined.
|
||||
*/
|
||||
@property (nonatomic) BOOL showLeftMembersInRoomMemberList;
|
||||
|
||||
/// Flag to allow sharing a message or not. Default value is YES.
|
||||
@property (nonatomic) BOOL messageDetailsAllowSharing;
|
||||
|
||||
/// Flag to allow saving a message or not. Default value is YES.
|
||||
@property (nonatomic) BOOL messageDetailsAllowSaving;
|
||||
|
||||
/// Flag to allow copying a media/file or not. Default value is YES.
|
||||
@property (nonatomic) BOOL messageDetailsAllowCopyingMedia;
|
||||
|
||||
/// Flag to allow pasting a media/file or not. Default value is YES.
|
||||
@property (nonatomic) BOOL messageDetailsAllowPastingMedia;
|
||||
|
||||
#pragma mark - Contacts
|
||||
|
||||
/**
|
||||
Return YES if the user allows the local contacts sync.
|
||||
|
||||
This boolean value is defined in shared settings object with the key: `syncLocalContacts`.
|
||||
Return NO if no value is defined.
|
||||
*/
|
||||
@property (nonatomic) BOOL syncLocalContacts;
|
||||
|
||||
/**
|
||||
Return YES if the user has been already asked for local contacts sync permission.
|
||||
|
||||
This boolean value is defined in shared settings object with the key: `syncLocalContactsPermissionRequested`.
|
||||
Return NO if no value is defined.
|
||||
*/
|
||||
@property (nonatomic) BOOL syncLocalContactsPermissionRequested;
|
||||
|
||||
/**
|
||||
Return YES if after the user has been asked for local contacts sync permission and choose to open
|
||||
the system's Settings app to enable contacts access.
|
||||
|
||||
This boolean value is defined in shared settings object with the key: `syncLocalContactsPermissionOpenedSystemSettings`.
|
||||
Return NO if no value is defined.
|
||||
*/
|
||||
@property (nonatomic) BOOL syncLocalContactsPermissionOpenedSystemSettings;
|
||||
|
||||
/**
|
||||
The current selected country code for the phonebook.
|
||||
|
||||
This value is defined in shared settings object with the key: `phonebookCountryCode`.
|
||||
Return the SIM card information (if any) if no default value is defined.
|
||||
*/
|
||||
@property (nonatomic) NSString* phonebookCountryCode;
|
||||
|
||||
|
||||
#pragma mark - Matrix users
|
||||
|
||||
/**
|
||||
Color associated to online matrix users.
|
||||
|
||||
This color value is defined in shared settings object with the key: `presenceColorForOnlineUser`.
|
||||
The default color is `[UIColor greenColor]`.
|
||||
*/
|
||||
@property (nonatomic) UIColor *presenceColorForOnlineUser;
|
||||
|
||||
/**
|
||||
Color associated to unavailable matrix users.
|
||||
|
||||
This color value is defined in shared settings object with the key: `presenceColorForUnavailableUser`.
|
||||
The default color is `[UIColor yellowColor]`.
|
||||
*/
|
||||
@property (nonatomic) UIColor *presenceColorForUnavailableUser;
|
||||
|
||||
/**
|
||||
Color associated to offline matrix users.
|
||||
|
||||
This color value is defined in shared settings object with the key: `presenceColorForOfflineUser`.
|
||||
The default color is `[UIColor redColor]`.
|
||||
*/
|
||||
@property (nonatomic) UIColor *presenceColorForOfflineUser;
|
||||
|
||||
#pragma mark - Notifications
|
||||
|
||||
/// Flag to allow PushKit pushers or not. Default value is `NO`.
|
||||
@property (nonatomic, assign) BOOL allowPushKitPushers;
|
||||
|
||||
/**
|
||||
A localization key used when registering the default notification payload.
|
||||
This key will be translated and displayed for APNS notifications as the body
|
||||
content, unless it is modified locally by a Notification Service Extension.
|
||||
|
||||
The default value for this setting is "MESSAGE". Changes are *not* persisted.
|
||||
Updating the value after MXKAccount has called `enableAPNSPusher:success:failure:`
|
||||
will have no effect.
|
||||
*/
|
||||
@property (nonatomic) NSString *notificationBodyLocalizationKey;
|
||||
|
||||
#pragma mark - Calls
|
||||
|
||||
/**
|
||||
Return YES if the user enable CallKit support.
|
||||
|
||||
This boolean value is defined in shared settings object with the key: `enableCallKit`.
|
||||
Return YES if no value is defined.
|
||||
*/
|
||||
@property (nonatomic, getter=isCallKitEnabled) BOOL enableCallKit;
|
||||
|
||||
#pragma mark - Shared userDefaults
|
||||
|
||||
/**
|
||||
A userDefaults object that is shared within the application group. The application group identifier
|
||||
is retrieved from MXSDKOptions sharedInstance (see `applicationGroupIdentifier` property).
|
||||
The default group is "group.org.matrix".
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUserDefaults *sharedUserDefaults;
|
||||
|
||||
#pragma mark - Class methods
|
||||
|
||||
/**
|
||||
Return the shared application settings object. These settings are retrieved/stored in the shared defaults object (`[NSUserDefaults standardUserDefaults]`).
|
||||
*/
|
||||
+ (MXKAppSettings *)standardAppSettings;
|
||||
|
||||
/**
|
||||
Return the folder to use for caching MatrixKit data.
|
||||
*/
|
||||
+ (NSString*)cacheFolder;
|
||||
|
||||
/**
|
||||
Restore the default values.
|
||||
*/
|
||||
- (void)reset;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,865 @@
|
||||
/*
|
||||
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 "MXKAppSettings.h"
|
||||
|
||||
#import "MXKTools.h"
|
||||
|
||||
|
||||
// get ISO country name
|
||||
#import <CoreTelephony/CTCarrier.h>
|
||||
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
|
||||
|
||||
static MXKAppSettings *standardAppSettings = nil;
|
||||
|
||||
static NSString *const kMXAppGroupID = @"group.org.matrix";
|
||||
|
||||
@interface MXKAppSettings ()
|
||||
{
|
||||
NSMutableArray <NSString*> *eventsFilterForMessages;
|
||||
NSMutableArray <NSString*> *allEventTypesForMessages;
|
||||
NSMutableArray <NSString*> *lastMessageEventTypesAllowList;
|
||||
}
|
||||
|
||||
@property (nonatomic, readwrite) NSUserDefaults *sharedUserDefaults;
|
||||
@property (nonatomic) NSString *currentApplicationGroup;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKAppSettings
|
||||
@synthesize syncWithLazyLoadOfRoomMembers;
|
||||
@synthesize showAllEventsInRoomHistory, showRedactionsInRoomHistory, showUnsupportedEventsInRoomHistory, httpLinkScheme, httpsLinkScheme;
|
||||
@synthesize enableBubbleComponentLinkDetection, firstURLDetectionIgnoredHosts, showLeftMembersInRoomMemberList, sortRoomMembersUsingLastSeenTime;
|
||||
@synthesize syncLocalContacts, syncLocalContactsPermissionRequested, syncLocalContactsPermissionOpenedSystemSettings, phonebookCountryCode;
|
||||
@synthesize presenceColorForOnlineUser, presenceColorForUnavailableUser, presenceColorForOfflineUser;
|
||||
@synthesize enableCallKit;
|
||||
@synthesize sharedUserDefaults;
|
||||
|
||||
+ (MXKAppSettings *)standardAppSettings
|
||||
{
|
||||
@synchronized(self)
|
||||
{
|
||||
if(standardAppSettings == nil)
|
||||
{
|
||||
standardAppSettings = [[super allocWithZone:NULL] init];
|
||||
}
|
||||
}
|
||||
return standardAppSettings;
|
||||
}
|
||||
|
||||
+ (NSString *)cacheFolder
|
||||
{
|
||||
NSString *cacheFolder;
|
||||
|
||||
// Check for a potential application group id
|
||||
NSString *applicationGroupIdentifier = [MXSDKOptions sharedInstance].applicationGroupIdentifier;
|
||||
if (applicationGroupIdentifier)
|
||||
{
|
||||
NSURL *sharedContainerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:applicationGroupIdentifier];
|
||||
cacheFolder = [sharedContainerURL path];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSArray *cacheDirList = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
||||
cacheFolder = [cacheDirList objectAtIndex:0];
|
||||
}
|
||||
|
||||
// Use a dedicated cache folder for MatrixKit
|
||||
cacheFolder = [cacheFolder stringByAppendingPathComponent:@"MatrixKit"];
|
||||
|
||||
// Make sure the folder exists so that it can be used
|
||||
if (cacheFolder && ![[NSFileManager defaultManager] fileExistsAtPath:cacheFolder])
|
||||
{
|
||||
NSError *error;
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:cacheFolder withIntermediateDirectories:YES attributes:nil error:&error];
|
||||
if (error)
|
||||
{
|
||||
MXLogDebug(@"[MXKAppSettings] cacheFolder: Error: Cannot create MatrixKit folder at %@. Error: %@", cacheFolder, error);
|
||||
}
|
||||
}
|
||||
|
||||
return cacheFolder;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
-(instancetype)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
syncWithLazyLoadOfRoomMembers = YES;
|
||||
|
||||
// Use presence to sort room members by default
|
||||
if (![[NSUserDefaults standardUserDefaults] objectForKey:@"sortRoomMembersUsingLastSeenTime"])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"sortRoomMembersUsingLastSeenTime"];
|
||||
}
|
||||
_hidePreJoinedUndecryptableEvents = NO;
|
||||
_hideUndecryptableEvents = NO;
|
||||
sortRoomMembersUsingLastSeenTime = YES;
|
||||
|
||||
presenceColorForOnlineUser = [UIColor greenColor];
|
||||
presenceColorForUnavailableUser = [UIColor yellowColor];
|
||||
presenceColorForOfflineUser = [UIColor redColor];
|
||||
|
||||
httpLinkScheme = @"http";
|
||||
httpsLinkScheme = @"https";
|
||||
enableBubbleComponentLinkDetection = NO;
|
||||
firstURLDetectionIgnoredHosts = @[[NSURL URLWithString:kMXMatrixDotToUrl].host];
|
||||
|
||||
_allowPushKitPushers = NO;
|
||||
_notificationBodyLocalizationKey = @"MESSAGE";
|
||||
enableCallKit = YES;
|
||||
|
||||
eventsFilterForMessages = @[
|
||||
kMXEventTypeStringRoomCreate,
|
||||
kMXEventTypeStringRoomName,
|
||||
kMXEventTypeStringRoomTopic,
|
||||
kMXEventTypeStringRoomMember,
|
||||
kMXEventTypeStringRoomEncrypted,
|
||||
kMXEventTypeStringRoomEncryption,
|
||||
kMXEventTypeStringRoomHistoryVisibility,
|
||||
kMXEventTypeStringRoomMessage,
|
||||
kMXEventTypeStringRoomThirdPartyInvite,
|
||||
kMXEventTypeStringRoomGuestAccess,
|
||||
kMXEventTypeStringRoomJoinRules,
|
||||
kMXEventTypeStringCallInvite,
|
||||
kMXEventTypeStringCallAnswer,
|
||||
kMXEventTypeStringCallHangup,
|
||||
kMXEventTypeStringCallReject,
|
||||
kMXEventTypeStringCallNegotiate,
|
||||
kMXEventTypeStringSticker,
|
||||
kMXEventTypeStringKeyVerificationCancel,
|
||||
kMXEventTypeStringKeyVerificationDone
|
||||
].mutableCopy;
|
||||
|
||||
|
||||
// List all the event types, except kMXEventTypeStringPresence which are not related to a specific room.
|
||||
allEventTypesForMessages = @[
|
||||
kMXEventTypeStringRoomName,
|
||||
kMXEventTypeStringRoomTopic,
|
||||
kMXEventTypeStringRoomMember,
|
||||
kMXEventTypeStringRoomCreate,
|
||||
kMXEventTypeStringRoomEncrypted,
|
||||
kMXEventTypeStringRoomEncryption,
|
||||
kMXEventTypeStringRoomJoinRules,
|
||||
kMXEventTypeStringRoomPowerLevels,
|
||||
kMXEventTypeStringRoomAliases,
|
||||
kMXEventTypeStringRoomHistoryVisibility,
|
||||
kMXEventTypeStringRoomMessage,
|
||||
kMXEventTypeStringRoomMessageFeedback,
|
||||
kMXEventTypeStringRoomRedaction,
|
||||
kMXEventTypeStringRoomThirdPartyInvite,
|
||||
kMXEventTypeStringRoomRelatedGroups,
|
||||
kMXEventTypeStringReaction,
|
||||
kMXEventTypeStringCallInvite,
|
||||
kMXEventTypeStringCallAnswer,
|
||||
kMXEventTypeStringCallSelectAnswer,
|
||||
kMXEventTypeStringCallHangup,
|
||||
kMXEventTypeStringCallReject,
|
||||
kMXEventTypeStringCallNegotiate,
|
||||
kMXEventTypeStringSticker,
|
||||
kMXEventTypeStringKeyVerificationCancel,
|
||||
kMXEventTypeStringKeyVerificationDone
|
||||
].mutableCopy;
|
||||
|
||||
lastMessageEventTypesAllowList = @[
|
||||
kMXEventTypeStringRoomCreate, // Without any messages, calls or stickers an event is needed to provide a date.
|
||||
kMXEventTypeStringRoomEncrypted, // Show a UTD string rather than the previous message.
|
||||
kMXEventTypeStringRoomMessage,
|
||||
kMXEventTypeStringRoomMember,
|
||||
kMXEventTypeStringCallInvite,
|
||||
kMXEventTypeStringCallAnswer,
|
||||
kMXEventTypeStringCallHangup,
|
||||
kMXEventTypeStringSticker
|
||||
].mutableCopy;
|
||||
|
||||
_messageDetailsAllowSharing = YES;
|
||||
_messageDetailsAllowSaving = YES;
|
||||
_messageDetailsAllowCopyingMedia = YES;
|
||||
_messageDetailsAllowPastingMedia = YES;
|
||||
_outboundGroupSessionKeyPreSharingStrategy = MXKKeyPreSharingWhenTyping;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reset
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
// Flush shared user defaults
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"syncWithLazyLoadOfRoomMembers2"];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"showAllEventsInRoomHistory"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"showRedactionsInRoomHistory"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"showUnsupportedEventsInRoomHistory"];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"sortRoomMembersUsingLastSeenTime"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"showLeftMembersInRoomMemberList"];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"syncLocalContactsPermissionRequested"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"syncLocalContacts"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"phonebookCountryCode"];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"presenceColorForOnlineUser"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"presenceColorForUnavailableUser"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"presenceColorForOfflineUser"];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"httpLinkScheme"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"httpsLinkScheme"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"enableBubbleComponentLinkDetection"];
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"firstURLDetectionIgnoredHosts"];
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"enableCallKit"];
|
||||
}
|
||||
else
|
||||
{
|
||||
syncWithLazyLoadOfRoomMembers = YES;
|
||||
|
||||
showAllEventsInRoomHistory = NO;
|
||||
showRedactionsInRoomHistory = NO;
|
||||
showUnsupportedEventsInRoomHistory = NO;
|
||||
|
||||
sortRoomMembersUsingLastSeenTime = YES;
|
||||
showLeftMembersInRoomMemberList = NO;
|
||||
|
||||
syncLocalContactsPermissionRequested = NO;
|
||||
syncLocalContacts = NO;
|
||||
phonebookCountryCode = nil;
|
||||
|
||||
presenceColorForOnlineUser = [UIColor greenColor];
|
||||
presenceColorForUnavailableUser = [UIColor yellowColor];
|
||||
presenceColorForOfflineUser = [UIColor redColor];
|
||||
|
||||
httpLinkScheme = @"http";
|
||||
httpsLinkScheme = @"https";
|
||||
|
||||
enableCallKit = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUserDefaults *)sharedUserDefaults
|
||||
{
|
||||
if (sharedUserDefaults)
|
||||
{
|
||||
// Check whether the current group id did not change.
|
||||
NSString *applicationGroup = [MXSDKOptions sharedInstance].applicationGroupIdentifier;
|
||||
if (!applicationGroup.length)
|
||||
{
|
||||
applicationGroup = kMXAppGroupID;
|
||||
}
|
||||
|
||||
if (![_currentApplicationGroup isEqualToString:applicationGroup])
|
||||
{
|
||||
// Reset the existing shared object
|
||||
sharedUserDefaults = nil;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sharedUserDefaults)
|
||||
{
|
||||
_currentApplicationGroup = [MXSDKOptions sharedInstance].applicationGroupIdentifier;
|
||||
if (!_currentApplicationGroup.length)
|
||||
{
|
||||
_currentApplicationGroup = kMXAppGroupID;
|
||||
}
|
||||
|
||||
sharedUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:_currentApplicationGroup];
|
||||
}
|
||||
|
||||
return sharedUserDefaults;
|
||||
}
|
||||
|
||||
#pragma mark - Calls
|
||||
|
||||
- (BOOL)syncWithLazyLoadOfRoomMembers
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
id storedValue = [[NSUserDefaults standardUserDefaults] objectForKey:@"syncWithLazyLoadOfRoomMembers2"];
|
||||
if (storedValue)
|
||||
{
|
||||
return [(NSNumber *)storedValue boolValue];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Enabled by default
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return syncWithLazyLoadOfRoomMembers;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSyncWithLazyLoadOfRoomMembers:(BOOL)syncWithLazyLoadOfRoomMembers
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:syncWithLazyLoadOfRoomMembers forKey:@"syncWithLazyLoadOfRoomMembers2"];
|
||||
}
|
||||
else
|
||||
{
|
||||
syncWithLazyLoadOfRoomMembers = syncWithLazyLoadOfRoomMembers;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Room display
|
||||
|
||||
- (BOOL)showAllEventsInRoomHistory
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
return [[NSUserDefaults standardUserDefaults] boolForKey:@"showAllEventsInRoomHistory"];
|
||||
}
|
||||
else
|
||||
{
|
||||
return showAllEventsInRoomHistory;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShowAllEventsInRoomHistory:(BOOL)boolValue
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:boolValue forKey:@"showAllEventsInRoomHistory"];
|
||||
}
|
||||
else
|
||||
{
|
||||
showAllEventsInRoomHistory = boolValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)eventsFilterForMessages
|
||||
{
|
||||
if (showAllEventsInRoomHistory)
|
||||
{
|
||||
// Consider all the event types
|
||||
return self.allEventTypesForMessages;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Display only a subset of events
|
||||
return eventsFilterForMessages;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)allEventTypesForMessages
|
||||
{
|
||||
return allEventTypesForMessages;
|
||||
}
|
||||
|
||||
- (NSArray<MXEventTypeString> *)lastMessageEventTypesAllowList
|
||||
{
|
||||
return lastMessageEventTypesAllowList;
|
||||
}
|
||||
|
||||
- (void)addSupportedEventTypes:(NSArray<NSString *> *)eventTypes
|
||||
{
|
||||
[eventsFilterForMessages addObjectsFromArray:eventTypes];
|
||||
[allEventTypesForMessages addObjectsFromArray:eventTypes];
|
||||
}
|
||||
|
||||
- (void)removeSupportedEventTypes:(NSArray<NSString *> *)eventTypes
|
||||
{
|
||||
[eventsFilterForMessages removeObjectsInArray:eventTypes];
|
||||
[allEventTypesForMessages removeObjectsInArray:eventTypes];
|
||||
[lastMessageEventTypesAllowList removeObjectsInArray:eventTypes];
|
||||
}
|
||||
|
||||
- (BOOL)showRedactionsInRoomHistory
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
return [[NSUserDefaults standardUserDefaults] boolForKey:@"showRedactionsInRoomHistory"];
|
||||
}
|
||||
else
|
||||
{
|
||||
return showRedactionsInRoomHistory;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShowRedactionsInRoomHistory:(BOOL)boolValue
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:boolValue forKey:@"showRedactionsInRoomHistory"];
|
||||
}
|
||||
else
|
||||
{
|
||||
showRedactionsInRoomHistory = boolValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)showUnsupportedEventsInRoomHistory
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
return [[NSUserDefaults standardUserDefaults] boolForKey:@"showUnsupportedEventsInRoomHistory"];
|
||||
}
|
||||
else
|
||||
{
|
||||
return showUnsupportedEventsInRoomHistory;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShowUnsupportedEventsInRoomHistory:(BOOL)boolValue
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:boolValue forKey:@"showUnsupportedEventsInRoomHistory"];
|
||||
}
|
||||
else
|
||||
{
|
||||
showUnsupportedEventsInRoomHistory = boolValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)httpLinkScheme
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
NSString *ret = [[NSUserDefaults standardUserDefaults] stringForKey:@"httpLinkScheme"];
|
||||
if (ret == nil) {
|
||||
ret = @"http";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
return httpLinkScheme;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHttpLinkScheme:(NSString *)stringValue
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:stringValue forKey:@"httpLinkScheme"];
|
||||
}
|
||||
else
|
||||
{
|
||||
httpLinkScheme = stringValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)httpsLinkScheme
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
NSString *ret = [[NSUserDefaults standardUserDefaults] stringForKey:@"httpsLinkScheme"];
|
||||
if (ret == nil) {
|
||||
ret = @"https";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
return httpsLinkScheme;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHttpsLinkScheme:(NSString *)stringValue
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:stringValue forKey:@"httpsLinkScheme"];
|
||||
}
|
||||
else
|
||||
{
|
||||
httpsLinkScheme = stringValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)enableBubbleComponentLinkDetection
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
return [NSUserDefaults.standardUserDefaults boolForKey:@"enableBubbleComponentLinkDetection"];
|
||||
}
|
||||
else
|
||||
{
|
||||
return enableBubbleComponentLinkDetection;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setEnableBubbleComponentLinkDetection:(BOOL)storeLinksInBubbleComponents
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[NSUserDefaults.standardUserDefaults setBool:storeLinksInBubbleComponents forKey:@"enableBubbleComponentLinkDetection"];
|
||||
}
|
||||
else
|
||||
{
|
||||
enableBubbleComponentLinkDetection = storeLinksInBubbleComponents;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)firstURLDetectionIgnoredHosts
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
return [NSUserDefaults.standardUserDefaults objectForKey:@"firstURLDetectionIgnoredHosts"] ?: @[[NSURL URLWithString:kMXMatrixDotToUrl].host];
|
||||
}
|
||||
else
|
||||
{
|
||||
return firstURLDetectionIgnoredHosts;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFirstURLDetectionIgnoredHosts:(NSArray<NSString *> *)ignoredHosts
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
if (ignoredHosts == nil)
|
||||
{
|
||||
ignoredHosts = @[];
|
||||
}
|
||||
|
||||
[NSUserDefaults.standardUserDefaults setObject:ignoredHosts forKey:@"firstURLDetectionIgnoredHosts"];
|
||||
}
|
||||
else
|
||||
{
|
||||
firstURLDetectionIgnoredHosts = ignoredHosts;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Room members
|
||||
|
||||
- (BOOL)sortRoomMembersUsingLastSeenTime
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
return [[NSUserDefaults standardUserDefaults] boolForKey:@"sortRoomMembersUsingLastSeenTime"];
|
||||
}
|
||||
else
|
||||
{
|
||||
return sortRoomMembersUsingLastSeenTime;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSortRoomMembersUsingLastSeenTime:(BOOL)boolValue
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:boolValue forKey:@"sortRoomMembersUsingLastSeenTime"];
|
||||
}
|
||||
else
|
||||
{
|
||||
sortRoomMembersUsingLastSeenTime = boolValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)showLeftMembersInRoomMemberList
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
return [[NSUserDefaults standardUserDefaults] boolForKey:@"showLeftMembersInRoomMemberList"];
|
||||
}
|
||||
else
|
||||
{
|
||||
return showLeftMembersInRoomMemberList;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setShowLeftMembersInRoomMemberList:(BOOL)boolValue
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:boolValue forKey:@"showLeftMembersInRoomMemberList"];
|
||||
}
|
||||
else
|
||||
{
|
||||
showLeftMembersInRoomMemberList = boolValue;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Contacts
|
||||
|
||||
- (BOOL)syncLocalContacts
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
return [[NSUserDefaults standardUserDefaults] boolForKey:@"syncLocalContacts"];
|
||||
}
|
||||
else
|
||||
{
|
||||
return syncLocalContacts;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSyncLocalContacts:(BOOL)boolValue
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:boolValue forKey:@"syncLocalContacts"];
|
||||
}
|
||||
else
|
||||
{
|
||||
syncLocalContacts = boolValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)syncLocalContactsPermissionRequested
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
return [[NSUserDefaults standardUserDefaults] boolForKey:@"syncLocalContactsPermissionRequested"];
|
||||
}
|
||||
else
|
||||
{
|
||||
return syncLocalContactsPermissionRequested;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSyncLocalContactsPermissionRequested:(BOOL)theSyncLocalContactsPermissionRequested
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:theSyncLocalContactsPermissionRequested forKey:@"syncLocalContactsPermissionRequested"];
|
||||
}
|
||||
else
|
||||
{
|
||||
syncLocalContactsPermissionRequested = theSyncLocalContactsPermissionRequested;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)syncLocalContactsPermissionOpenedSystemSettings
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
return [[NSUserDefaults standardUserDefaults] boolForKey:@"syncLocalContactsPermissionOpenedSystemSettings"];
|
||||
}
|
||||
else
|
||||
{
|
||||
return syncLocalContactsPermissionOpenedSystemSettings;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSyncLocalContactsPermissionOpenedSystemSettings:(BOOL)theSyncLocalContactsPermissionOpenedSystemSettings
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:theSyncLocalContactsPermissionOpenedSystemSettings forKey:@"syncLocalContactsPermissionOpenedSystemSettings"];
|
||||
}
|
||||
else
|
||||
{
|
||||
syncLocalContactsPermissionOpenedSystemSettings = theSyncLocalContactsPermissionOpenedSystemSettings;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString*)phonebookCountryCode
|
||||
{
|
||||
NSString* res = phonebookCountryCode;
|
||||
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
res = [[NSUserDefaults standardUserDefaults] stringForKey:@"phonebookCountryCode"];
|
||||
}
|
||||
|
||||
// does not exist : try to get the SIM card information
|
||||
if (!res)
|
||||
{
|
||||
// get the current MCC
|
||||
CTTelephonyNetworkInfo *netInfo = [[CTTelephonyNetworkInfo alloc] init];
|
||||
CTCarrier *carrier = [netInfo subscriberCellularProvider];
|
||||
|
||||
if (carrier)
|
||||
{
|
||||
res = [[carrier isoCountryCode] uppercaseString];
|
||||
|
||||
if (res)
|
||||
{
|
||||
[self setPhonebookCountryCode:res];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
- (void)setPhonebookCountryCode:(NSString *)stringValue
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:stringValue forKey:@"phonebookCountryCode"];
|
||||
}
|
||||
else
|
||||
{
|
||||
phonebookCountryCode = stringValue;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Matrix users
|
||||
|
||||
- (UIColor*)presenceColorForOnlineUser
|
||||
{
|
||||
UIColor *color = presenceColorForOnlineUser;
|
||||
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
NSNumber *rgbValue = [[NSUserDefaults standardUserDefaults] objectForKey:@"presenceColorForOnlineUser"];
|
||||
if (rgbValue)
|
||||
{
|
||||
color = [MXKTools colorWithRGBValue:[rgbValue unsignedIntegerValue]];
|
||||
}
|
||||
else
|
||||
{
|
||||
color = [UIColor greenColor];
|
||||
}
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
- (void)setPresenceColorForOnlineUser:(UIColor*)color
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
if (color)
|
||||
{
|
||||
NSUInteger rgbValue = [MXKTools rgbValueWithColor:color];
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:rgbValue forKey:@"presenceColorForOnlineUser"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"presenceColorForOnlineUser"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
presenceColorForOnlineUser = color ? color : [UIColor greenColor];
|
||||
}
|
||||
}
|
||||
|
||||
- (UIColor*)presenceColorForUnavailableUser
|
||||
{
|
||||
UIColor *color = presenceColorForUnavailableUser;
|
||||
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
NSNumber *rgbValue = [[NSUserDefaults standardUserDefaults] objectForKey:@"presenceColorForUnavailableUser"];
|
||||
if (rgbValue)
|
||||
{
|
||||
color = [MXKTools colorWithRGBValue:[rgbValue unsignedIntegerValue]];
|
||||
}
|
||||
else
|
||||
{
|
||||
color = [UIColor yellowColor];
|
||||
}
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
- (void)setPresenceColorForUnavailableUser:(UIColor*)color
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
if (color)
|
||||
{
|
||||
NSUInteger rgbValue = [MXKTools rgbValueWithColor:color];
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:rgbValue forKey:@"presenceColorForUnavailableUser"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"presenceColorForUnavailableUser"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
presenceColorForUnavailableUser = color ? color : [UIColor yellowColor];
|
||||
}
|
||||
}
|
||||
|
||||
- (UIColor*)presenceColorForOfflineUser
|
||||
{
|
||||
UIColor *color = presenceColorForOfflineUser;
|
||||
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
NSNumber *rgbValue = [[NSUserDefaults standardUserDefaults] objectForKey:@"presenceColorForOfflineUser"];
|
||||
if (rgbValue)
|
||||
{
|
||||
color = [MXKTools colorWithRGBValue:[rgbValue unsignedIntegerValue]];
|
||||
}
|
||||
else
|
||||
{
|
||||
color = [UIColor redColor];
|
||||
}
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
- (void)setPresenceColorForOfflineUser:(UIColor *)color
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
if (color)
|
||||
{
|
||||
NSUInteger rgbValue = [MXKTools rgbValueWithColor:color];
|
||||
[[NSUserDefaults standardUserDefaults] setInteger:rgbValue forKey:@"presenceColorForOfflineUser"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"presenceColorForOfflineUser"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
presenceColorForOfflineUser = color ? color : [UIColor redColor];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Calls
|
||||
|
||||
- (BOOL)isCallKitEnabled
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
id storedValue = [[NSUserDefaults standardUserDefaults] objectForKey:@"enableCallKit"];
|
||||
if (storedValue)
|
||||
{
|
||||
return [(NSNumber *)storedValue boolValue];
|
||||
}
|
||||
else
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return enableCallKit;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setEnableCallKit:(BOOL)enable
|
||||
{
|
||||
if (self == [MXKAppSettings standardAppSettings])
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool:enable forKey:@"enableCallKit"];
|
||||
}
|
||||
else
|
||||
{
|
||||
enableCallKit = enable;
|
||||
}
|
||||
}
|
||||
|
||||
@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 <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
`MXKCellData` objects contain data that is displayed by objects implementing `MXKCellRendering`.
|
||||
|
||||
The goal of `MXKCellData` is mainly to cache computed data in order to avoid to compute it each time
|
||||
a cell is displayed.
|
||||
*/
|
||||
@interface MXKCellData : NSObject
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
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 "MXKCellData.h"
|
||||
|
||||
@implementation MXKCellData
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
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>
|
||||
#import "MXKCellRendering.h"
|
||||
|
||||
/**
|
||||
List data source states.
|
||||
*/
|
||||
typedef enum : NSUInteger {
|
||||
/**
|
||||
Default value (used when all resources have been disposed).
|
||||
The instance cannot be used anymore.
|
||||
*/
|
||||
MXKDataSourceStateUnknown,
|
||||
|
||||
/**
|
||||
Initialisation is in progress.
|
||||
*/
|
||||
MXKDataSourceStatePreparing,
|
||||
|
||||
/**
|
||||
Something wrong happens during initialisation.
|
||||
*/
|
||||
MXKDataSourceStateFailed,
|
||||
|
||||
/**
|
||||
Data source is ready to be used.
|
||||
*/
|
||||
MXKDataSourceStateReady
|
||||
|
||||
} MXKDataSourceState;
|
||||
|
||||
@protocol MXKDataSourceDelegate;
|
||||
|
||||
/**
|
||||
`MXKDataSource` is the base class for data sources managed by MatrixKit.
|
||||
|
||||
Inherited 'MXKDataSource' instances are used to handle table or collection data.
|
||||
They may conform to UITableViewDataSource or UICollectionViewDataSource protocol to be used as data source delegate
|
||||
for a UITableView or a UICollectionView instance.
|
||||
*/
|
||||
@interface MXKDataSource : NSObject <MXKCellRenderingDelegate>
|
||||
{
|
||||
@protected
|
||||
MXKDataSourceState state;
|
||||
}
|
||||
|
||||
/**
|
||||
The matrix session.
|
||||
*/
|
||||
@property (nonatomic, weak, readonly) MXSession *mxSession;
|
||||
|
||||
/**
|
||||
The data source state
|
||||
*/
|
||||
@property (nonatomic, readonly) MXKDataSourceState state;
|
||||
|
||||
/**
|
||||
The delegate notified when the data has been updated.
|
||||
*/
|
||||
@property (weak, nonatomic) id<MXKDataSourceDelegate> delegate;
|
||||
|
||||
|
||||
#pragma mark - Life cycle
|
||||
/**
|
||||
Base constructor of data source.
|
||||
|
||||
Customization like class registrations must be done before loading data (see '[MXKDataSource registerCellDataClass: forCellIdentifier:]') .
|
||||
That is why 3 steps should be considered during 'MXKDataSource' initialization:
|
||||
1- call [MXKDataSource initWithMatrixSession:] to initialize a new allocated object.
|
||||
2- customize classes and others...
|
||||
3- call [MXKDataSource finalizeInitialization] to finalize the initialization.
|
||||
|
||||
@param mxSession the Matrix session to get data from.
|
||||
@return the newly created instance.
|
||||
*/
|
||||
- (instancetype)initWithMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Finalize the initialization by adding an observer on matrix session state change.
|
||||
*/
|
||||
- (void)finalizeInitialization;
|
||||
|
||||
/**
|
||||
Dispose all resources.
|
||||
*/
|
||||
- (void)destroy;
|
||||
|
||||
/**
|
||||
This method is called when the state of the attached Matrix session has changed.
|
||||
*/
|
||||
- (void)didMXSessionStateChange;
|
||||
|
||||
|
||||
#pragma mark - MXKCellData classes
|
||||
/**
|
||||
Register the MXKCellData class that will be used to process and store data for cells
|
||||
with the designated identifier.
|
||||
|
||||
@param cellDataClass a MXKCellData-inherited class that will handle data for cells.
|
||||
@param identifier the identifier of targeted cell.
|
||||
*/
|
||||
- (void)registerCellDataClass:(Class)cellDataClass forCellIdentifier:(NSString *)identifier;
|
||||
|
||||
/**
|
||||
Return the MXKCellData class that handles data for cells with the designated identifier.
|
||||
|
||||
@param identifier the cell identifier.
|
||||
@return the associated MXKCellData-inherited class.
|
||||
*/
|
||||
- (Class)cellDataClassForCellIdentifier:(NSString *)identifier;
|
||||
|
||||
#pragma mark - Pending HTTP requests
|
||||
|
||||
/**
|
||||
Cancel all registered requests.
|
||||
*/
|
||||
- (void)cancelAllRequests;
|
||||
|
||||
@end
|
||||
|
||||
@protocol MXKDataSourceDelegate <NSObject>
|
||||
|
||||
/**
|
||||
Ask the delegate which MXKCellRendering-compliant class must be used to render this cell data.
|
||||
|
||||
This method is called when MXKDataSource instance is used as the data source delegate of a table or a collection.
|
||||
CAUTION: The table or the collection MUST have registered the returned class with the same identifier than the one returned by [cellReuseIdentifierForCellData:].
|
||||
|
||||
@param cellData the cell data to display.
|
||||
@return a MXKCellRendering-compliant class which inherits UITableViewCell or UICollectionViewCell class (nil if the cellData is not supported).
|
||||
*/
|
||||
- (Class<MXKCellRendering>)cellViewClassForCellData:(MXKCellData*)cellData;
|
||||
|
||||
/**
|
||||
Ask the delegate which identifier must be used to dequeue reusable cell for this cell data.
|
||||
|
||||
This method is called when MXKDataSource instance is used as the data source delegate of a table or a collection.
|
||||
CAUTION: The table or the collection MUST have registered the right class with the returned identifier (see [cellViewClassForCellData:]).
|
||||
|
||||
@param cellData the cell data to display.
|
||||
@return the reuse identifier for the cell (nil if the cellData is not supported).
|
||||
*/
|
||||
- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData;
|
||||
|
||||
/**
|
||||
Tells the delegate that some cell data/views have been changed.
|
||||
|
||||
@param dataSource the involved data source.
|
||||
@param changes contains the index paths of objects that changed.
|
||||
*/
|
||||
- (void)dataSource:(MXKDataSource*)dataSource didCellChange:(id /* @TODO*/)changes;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
Tells the delegate that data source state changed
|
||||
|
||||
@param dataSource the involved data source.
|
||||
@param state the new data source state.
|
||||
*/
|
||||
- (void)dataSource:(MXKDataSource*)dataSource didStateChange:(MXKDataSourceState)state;
|
||||
|
||||
/**
|
||||
Relevant only for data source which support multi-sessions.
|
||||
Tells the delegate that a matrix session has been added.
|
||||
|
||||
@param dataSource the involved data source.
|
||||
@param mxSession the new added session.
|
||||
*/
|
||||
- (void)dataSource:(MXKDataSource*)dataSource didAddMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Relevant only for data source which support multi-sessions.
|
||||
Tells the delegate that a matrix session has been removed.
|
||||
|
||||
@param dataSource the involved data source.
|
||||
@param mxSession the removed session.
|
||||
*/
|
||||
- (void)dataSource:(MXKDataSource*)dataSource didRemoveMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Tells the delegate when a user action is observed inside a cell.
|
||||
|
||||
@see `MXKCellRenderingDelegate` for more details.
|
||||
|
||||
@param dataSource the involved data source.
|
||||
@param actionIdentifier an identifier indicating the action type (tap, long press...) and which part of the cell is concerned.
|
||||
@param cell the cell in which action has been observed.
|
||||
@param userInfo a dict containing additional information. It depends on actionIdentifier. May be nil.
|
||||
*/
|
||||
- (void)dataSource:(MXKDataSource*)dataSource didRecognizeAction:(NSString*)actionIdentifier inCell:(id<MXKCellRendering>)cell userInfo:(NSDictionary*)userInfo;
|
||||
|
||||
/**
|
||||
Asks the delegate if a user action (click on a link) can be done.
|
||||
|
||||
@see `MXKCellRenderingDelegate` for more details.
|
||||
|
||||
@param dataSource the involved data source.
|
||||
@param actionIdentifier an identifier indicating the action type (link click) and which part of the cell is concerned.
|
||||
@param cell the cell in which action has been observed.
|
||||
@param userInfo a dict containing additional information. It depends on actionIdentifier. May be nil.
|
||||
@param defaultValue the value to return by default if the action is not handled.
|
||||
@return a boolean value which depends on actionIdentifier.
|
||||
*/
|
||||
- (BOOL)dataSource:(MXKDataSource*)dataSource shouldDoAction:(NSString *)actionIdentifier inCell:(id<MXKCellRendering>)cell userInfo:(NSDictionary *)userInfo defaultValue:(BOOL)defaultValue;
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
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 "MXKDataSource.h"
|
||||
|
||||
#import "MXKCellData.h"
|
||||
#import "MXKCellRendering.h"
|
||||
|
||||
@interface MXKDataSource ()
|
||||
{
|
||||
/**
|
||||
The mapping between cell identifiers and MXKCellData classes.
|
||||
*/
|
||||
NSMutableDictionary *cellDataMap;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation MXKDataSource
|
||||
@synthesize state;
|
||||
|
||||
#pragma mark - Life cycle
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
state = MXKDataSourceStateUnknown;
|
||||
cellDataMap = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMatrixSession:(MXSession *)matrixSession
|
||||
{
|
||||
self = [self init];
|
||||
if (self)
|
||||
{
|
||||
_mxSession = matrixSession;
|
||||
state = MXKDataSourceStatePreparing;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)finalizeInitialization
|
||||
{
|
||||
// Add an observer on matrix session state change (prevent multiple registrations).
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionStateDidChangeNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionStateChange:) name:kMXSessionStateDidChangeNotification object:nil];
|
||||
|
||||
// Call the registered callback to finalize the initialisation step.
|
||||
[self didMXSessionStateChange];
|
||||
}
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
state = MXKDataSourceStateUnknown;
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(dataSource:didStateChange:)])
|
||||
{
|
||||
[_delegate dataSource:self didStateChange:state];
|
||||
}
|
||||
|
||||
_mxSession = nil;
|
||||
_delegate = nil;
|
||||
|
||||
[self cancelAllRequests];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
cellDataMap = nil;
|
||||
}
|
||||
|
||||
#pragma mark - MXSessionStateDidChangeNotification
|
||||
- (void)didMXSessionStateChange:(NSNotification *)notif
|
||||
{
|
||||
// Check this is our Matrix session that has changed
|
||||
if (notif.object == _mxSession)
|
||||
{
|
||||
[self didMXSessionStateChange];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMXSessionStateChange
|
||||
{
|
||||
// The inherited class is highly invited to override this method for its business logic
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - MXKCellData classes
|
||||
- (void)registerCellDataClass:(Class)cellDataClass forCellIdentifier:(NSString *)identifier
|
||||
{
|
||||
// Sanity check: accept only MXKCellData classes or sub-classes
|
||||
NSParameterAssert([cellDataClass isSubclassOfClass:MXKCellData.class]);
|
||||
|
||||
cellDataMap[identifier] = cellDataClass;
|
||||
}
|
||||
|
||||
- (Class)cellDataClassForCellIdentifier:(NSString *)identifier
|
||||
{
|
||||
return cellDataMap[identifier];
|
||||
}
|
||||
|
||||
#pragma mark - MXKCellRenderingDelegate
|
||||
- (void)cell:(id<MXKCellRendering>)cell didRecognizeAction:(NSString*)actionIdentifier userInfo:(NSDictionary *)userInfo
|
||||
{
|
||||
// The data source simply relays the information to its delegate
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(dataSource:didRecognizeAction:inCell:userInfo:)])
|
||||
{
|
||||
[_delegate dataSource:self didRecognizeAction:actionIdentifier inCell:cell userInfo:userInfo];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)cell:(id<MXKCellRendering>)cell shouldDoAction:(NSString *)actionIdentifier userInfo:(NSDictionary *)userInfo defaultValue:(BOOL)defaultValue
|
||||
{
|
||||
BOOL shouldDoAction = defaultValue;
|
||||
|
||||
// The data source simply relays the question to its delegate
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(dataSource:shouldDoAction:inCell:userInfo:defaultValue:)])
|
||||
{
|
||||
shouldDoAction = [_delegate dataSource:self shouldDoAction:actionIdentifier inCell:cell userInfo:userInfo defaultValue:defaultValue];
|
||||
}
|
||||
|
||||
return shouldDoAction;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Pending HTTP requests
|
||||
/**
|
||||
Cancel all registered requests.
|
||||
*/
|
||||
- (void)cancelAllRequests
|
||||
{
|
||||
// The inherited class is invited to override this method
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
@objcMembers
|
||||
public class MXKPasteboardManager: NSObject {
|
||||
|
||||
public static let shared = MXKPasteboardManager(withPasteboard: .general)
|
||||
|
||||
private init(withPasteboard pasteboard: UIPasteboard) {
|
||||
self.pasteboard = pasteboard
|
||||
super.init()
|
||||
}
|
||||
|
||||
/// Pasteboard to use on copy operations. Defaults to `UIPasteboard.generalPasteboard`.
|
||||
public var pasteboard: UIPasteboard
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations 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 "MXKDirectoryServerCellDataStoring.h"
|
||||
|
||||
/**
|
||||
`MXKRoomMemberCellData` modelised the data for a `MXKRoomMemberTableViewCell` cell.
|
||||
*/
|
||||
@interface MXKDirectoryServerCellData : MXKCellData <MXKDirectoryServerCellDataStoring>
|
||||
|
||||
@end
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 "MXKDirectoryServerCellData.h"
|
||||
|
||||
#import "NSBundle+MatrixKit.h"
|
||||
|
||||
#import "MXKSwiftHeader.h"
|
||||
|
||||
@implementation MXKDirectoryServerCellData;
|
||||
@synthesize desc, icon;
|
||||
@synthesize homeserver, includeAllNetworks;
|
||||
@synthesize thirdPartyProtocolInstance, thirdPartyProtocol;
|
||||
@synthesize mediaManager;
|
||||
|
||||
- (id)initWithHomeserver:(NSString *)theHomeserver includeAllNetworks:(BOOL)theIncludeAllNetworks
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
homeserver = theHomeserver;
|
||||
includeAllNetworks = theIncludeAllNetworks;
|
||||
|
||||
if (theIncludeAllNetworks)
|
||||
{
|
||||
desc = homeserver;
|
||||
icon = nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the Matrix name and logo when looking for Matrix rooms only
|
||||
desc = [MatrixKitL10n matrix];
|
||||
icon = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"network_matrix"];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithProtocolInstance:(MXThirdPartyProtocolInstance *)instance protocol:(MXThirdPartyProtocol *)protocol
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
thirdPartyProtocolInstance = instance;
|
||||
thirdPartyProtocol = protocol;
|
||||
desc = thirdPartyProtocolInstance.desc;
|
||||
icon = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
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 "MXKCellData.h"
|
||||
|
||||
/**
|
||||
`MXKDirectoryServerCellDataStoring` defines a protocol a class must conform in order to
|
||||
store directory cell data managed by `MXKDirectoryServersDataSource`.
|
||||
*/
|
||||
@protocol MXKDirectoryServerCellDataStoring <NSObject>
|
||||
|
||||
#pragma mark - Data displayed by a server cell
|
||||
|
||||
/**
|
||||
The name of the directory server.
|
||||
*/
|
||||
@property (nonatomic) NSString *desc;
|
||||
|
||||
/**
|
||||
The icon of the server.
|
||||
*/
|
||||
@property (nonatomic) UIImage *icon;
|
||||
|
||||
/**
|
||||
The optional media manager used to download the icon of the server.
|
||||
*/
|
||||
@property (nonatomic) MXMediaManager *mediaManager;
|
||||
|
||||
/**
|
||||
In case the cell data represents a homeserver, its description.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *homeserver;
|
||||
@property (nonatomic, readonly) BOOL includeAllNetworks;
|
||||
|
||||
/**
|
||||
In case the cell data represents a third-party protocol instance, its description.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXThirdPartyProtocolInstance *thirdPartyProtocolInstance;
|
||||
@property (nonatomic, readonly) MXThirdPartyProtocol *thirdPartyProtocol;
|
||||
|
||||
/**
|
||||
Define a MXKDirectoryServerCellData that will store a homeserver.
|
||||
|
||||
@param homeserver the homeserver name (ex: "matrix.org).
|
||||
@param includeAllNetworks YES to list all public rooms on the homeserver whatever their protocol.
|
||||
NO to list only matrix rooms.
|
||||
*/
|
||||
- (id)initWithHomeserver:(NSString*)homeserver includeAllNetworks:(BOOL)includeAllNetworks;
|
||||
|
||||
/**
|
||||
Define a MXKDirectoryServerCellData that will store a third-party protocol instance.
|
||||
|
||||
@param instance the instance of the protocol.
|
||||
@param protocol the protocol description.
|
||||
*/
|
||||
- (id)initWithProtocolInstance:(MXThirdPartyProtocolInstance*)instance protocol:(MXThirdPartyProtocol*)protocol;
|
||||
|
||||
@end
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations 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 "MXKDataSource.h"
|
||||
#import "MXKDirectoryServerCellDataStoring.h"
|
||||
|
||||
/**
|
||||
Identifier to use for cells that display a server in the servers list.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const kMXKDirectorServerCellIdentifier;
|
||||
|
||||
/**
|
||||
`DirectoryServersDataSource` is a base class to list servers and third-party protocols
|
||||
instances available on the user homeserver.
|
||||
|
||||
We can then list public rooms from the directory of these servers. This is done
|
||||
with `PublicRoomsDirectoryDataSource`.
|
||||
|
||||
As a `MXKDataSource` child class, the class has a state where values have the following meanings:
|
||||
- MXKDataSourceStatePreparing: the data source is not yet ready or it is fetching data from the homeserver.
|
||||
- MXKDataSourceStateReady: the data source data is ready.
|
||||
- MXKDataSourceStateFailed: the data source failed to fetch data.
|
||||
|
||||
There is no way in Matrix to be notified when there is a change.
|
||||
*/
|
||||
@interface MXKDirectoryServersDataSource : MXKDataSource <UITableViewDataSource>
|
||||
{
|
||||
@protected
|
||||
/**
|
||||
The data for the cells served by `DirectoryServersDataSource`.
|
||||
*/
|
||||
NSMutableArray<id<MXKDirectoryServerCellDataStoring>> *cellDataArray;
|
||||
|
||||
/**
|
||||
The filtered servers: sub-list of `cellDataArray` defined by `searchWithPatterns:`.
|
||||
*/
|
||||
NSMutableArray<id<MXKDirectoryServerCellDataStoring>> *filteredCellDataArray;
|
||||
}
|
||||
|
||||
/**
|
||||
Additional room directory servers the datasource will list.
|
||||
*/
|
||||
@property (nonatomic) NSArray<NSString*> *roomDirectoryServers;
|
||||
|
||||
/**
|
||||
Fetch the data source data.
|
||||
*/
|
||||
- (void)loadData;
|
||||
|
||||
/**
|
||||
Filter the current recents list according to the provided patterns.
|
||||
When patterns are not empty, the search result is stored in `filteredCellDataArray`,
|
||||
this array provides then data for the cells served by `MXKDirectoryServersDataSource`.
|
||||
|
||||
@param patternsList the list of patterns to match with. Set nil to cancel search.
|
||||
*/
|
||||
- (void)searchWithPatterns:(NSArray<NSString*> *)patternsList;
|
||||
|
||||
/**
|
||||
Get the data for the cell at the given index path.
|
||||
|
||||
@param indexPath the index of the cell.
|
||||
@return the cell data.
|
||||
*/
|
||||
- (id<MXKDirectoryServerCellDataStoring>)cellDataAtIndexPath:(NSIndexPath*)indexPath;
|
||||
|
||||
@end
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
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 "MXKDirectoryServersDataSource.h"
|
||||
|
||||
#import "MXKDirectoryServerCellData.h"
|
||||
|
||||
NSString *const kMXKDirectorServerCellIdentifier = @"kMXKDirectorServerCellIdentifier";
|
||||
|
||||
#pragma mark - DirectoryServersDataSource
|
||||
|
||||
@interface MXKDirectoryServersDataSource ()
|
||||
{
|
||||
// The pending request to load third-party protocols.
|
||||
MXHTTPOperation *request;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKDirectoryServersDataSource
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
cellDataArray = [NSMutableArray array];
|
||||
filteredCellDataArray = nil;
|
||||
|
||||
// Set default data w classes
|
||||
[self registerCellDataClass:MXKDirectoryServerCellData.class forCellIdentifier:kMXKDirectorServerCellIdentifier];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
cellDataArray = nil;
|
||||
filteredCellDataArray = nil;
|
||||
}
|
||||
|
||||
- (void)cancelAllRequests
|
||||
{
|
||||
[super cancelAllRequests];
|
||||
|
||||
[request cancel];
|
||||
request = nil;
|
||||
}
|
||||
|
||||
- (void)loadData
|
||||
{
|
||||
// Cancel the previous request
|
||||
if (request)
|
||||
{
|
||||
[request cancel];
|
||||
}
|
||||
|
||||
// Reset all vars
|
||||
[cellDataArray removeAllObjects];
|
||||
|
||||
[self setState:MXKDataSourceStatePreparing];
|
||||
|
||||
Class class = [self cellDataClassForCellIdentifier:kMXKDirectorServerCellIdentifier];
|
||||
|
||||
// Add user's HS
|
||||
NSString *userHomeserver = self.mxSession.matrixRestClient.credentials.homeServerName;
|
||||
id<MXKDirectoryServerCellDataStoring> cellData = [[class alloc] initWithHomeserver:userHomeserver includeAllNetworks:YES];
|
||||
[cellDataArray addObject:cellData];
|
||||
|
||||
// Add user's HS but for Matrix public rooms only
|
||||
cellData = [[class alloc] initWithHomeserver:userHomeserver includeAllNetworks:NO];
|
||||
[cellDataArray addObject:cellData];
|
||||
|
||||
// Add custom directory servers
|
||||
for (NSString *homeserver in _roomDirectoryServers)
|
||||
{
|
||||
if (![homeserver isEqualToString:userHomeserver])
|
||||
{
|
||||
cellData = [[class alloc] initWithHomeserver:homeserver includeAllNetworks:YES];
|
||||
[cellDataArray addObject:cellData];
|
||||
}
|
||||
}
|
||||
|
||||
MXWeakify(self);
|
||||
request = [self.mxSession.matrixRestClient thirdpartyProtocols:^(MXThirdpartyProtocolsResponse *thirdpartyProtocolsResponse) {
|
||||
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
for (NSString *protocolName in thirdpartyProtocolsResponse.protocols)
|
||||
{
|
||||
MXThirdPartyProtocol *protocol = thirdpartyProtocolsResponse.protocols[protocolName];
|
||||
|
||||
for (MXThirdPartyProtocolInstance *instance in protocol.instances)
|
||||
{
|
||||
id<MXKDirectoryServerCellDataStoring> cellData = [[class alloc] initWithProtocolInstance:instance protocol:protocol];
|
||||
cellData.mediaManager = self.mxSession.mediaManager;
|
||||
[self->cellDataArray addObject:cellData];
|
||||
}
|
||||
}
|
||||
|
||||
[self setState:MXKDataSourceStateReady];
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
if (!self->request || self->request.isCancelled)
|
||||
{
|
||||
// Do not take into account error coming from a cancellation
|
||||
return;
|
||||
}
|
||||
|
||||
self->request = nil;
|
||||
|
||||
MXLogDebug(@"[MXKDirectoryServersDataSource] Failed to fecth third-party protocols. The HS may be too old to support third party networks");
|
||||
|
||||
[self setState:MXKDataSourceStateReady];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList
|
||||
{
|
||||
if (patternsList.count)
|
||||
{
|
||||
if (filteredCellDataArray)
|
||||
{
|
||||
[filteredCellDataArray removeAllObjects];
|
||||
}
|
||||
else
|
||||
{
|
||||
filteredCellDataArray = [NSMutableArray arrayWithCapacity:cellDataArray.count];
|
||||
}
|
||||
|
||||
for (id<MXKDirectoryServerCellDataStoring> cellData in cellDataArray)
|
||||
{
|
||||
for (NSString* pattern in patternsList)
|
||||
{
|
||||
if ([cellData.desc rangeOfString:pattern options:NSCaseInsensitiveSearch].location != NSNotFound)
|
||||
{
|
||||
[filteredCellDataArray addObject:cellData];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
filteredCellDataArray = nil;
|
||||
}
|
||||
|
||||
if (self.delegate)
|
||||
{
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Get the data for the cell at the given index path.
|
||||
|
||||
@param indexPath the index of the cell.
|
||||
@return the cell data.
|
||||
*/
|
||||
- (id<MXKDirectoryServerCellDataStoring>)cellDataAtIndexPath:(NSIndexPath*)indexPath;
|
||||
{
|
||||
if (filteredCellDataArray)
|
||||
{
|
||||
return filteredCellDataArray[indexPath.row];
|
||||
}
|
||||
return cellDataArray[indexPath.row];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Private methods
|
||||
|
||||
// Update the MXKDataSource state and the delegate
|
||||
- (void)setState:(MXKDataSourceState)newState
|
||||
{
|
||||
state = newState;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didStateChange:)])
|
||||
{
|
||||
[self.delegate dataSource:self didStateChange:state];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
if (filteredCellDataArray)
|
||||
{
|
||||
return filteredCellDataArray.count;
|
||||
}
|
||||
return cellDataArray.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
id<MXKDirectoryServerCellDataStoring> cellData = [self cellDataAtIndexPath:indexPath];
|
||||
|
||||
if (cellData && self.delegate)
|
||||
{
|
||||
NSString *identifier = [self.delegate cellReuseIdentifierForCellData:cellData];
|
||||
if (identifier)
|
||||
{
|
||||
UITableViewCell<MXKCellRendering> *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
|
||||
|
||||
// Make the cell display the data
|
||||
[cell render:cellData];
|
||||
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a fake cell to prevent app from crashing.
|
||||
return [[UITableViewCell alloc] init];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
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 "MXKRecentsDataSource.h"
|
||||
|
||||
/**
|
||||
'MXKInterleavedRecentsDataSource' class inherits from 'MXKRecentsDataSource'.
|
||||
|
||||
It interleaves the recents in case of multiple sessions to display first the most recent room.
|
||||
*/
|
||||
@interface MXKInterleavedRecentsDataSource : MXKRecentsDataSource
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,439 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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 "MXKInterleavedRecentsDataSource.h"
|
||||
|
||||
#import "MXKInterleavedRecentTableViewCell.h"
|
||||
|
||||
#import "MXKAccountManager.h"
|
||||
|
||||
#import "NSBundle+MatrixKit.h"
|
||||
|
||||
@interface MXKInterleavedRecentsDataSource ()
|
||||
{
|
||||
/**
|
||||
The interleaved recents: cell data served by `MXKInterleavedRecentsDataSource`.
|
||||
*/
|
||||
NSMutableArray *interleavedCellDataArray;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKInterleavedRecentsDataSource
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
interleavedCellDataArray = [NSMutableArray array];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Override MXKDataSource
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
interleavedCellDataArray = nil;
|
||||
|
||||
[super destroy];
|
||||
}
|
||||
|
||||
#pragma mark - Override MXKRecentsDataSource
|
||||
|
||||
- (UIView *)viewForHeaderInSection:(NSInteger)section withFrame:(CGRect)frame
|
||||
{
|
||||
UIView *sectionHeader = nil;
|
||||
|
||||
if (displayedRecentsDataSourceArray.count > 1 && section == 0)
|
||||
{
|
||||
sectionHeader = [[UIView alloc] initWithFrame:frame];
|
||||
sectionHeader.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0];
|
||||
CGFloat btnWidth = frame.size.width / displayedRecentsDataSourceArray.count;
|
||||
UIButton *previousShrinkButton;
|
||||
|
||||
for (NSInteger index = 0; index < displayedRecentsDataSourceArray.count; index++)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:index];
|
||||
NSString* btnTitle = recentsDataSource.mxSession.myUser.userId;
|
||||
|
||||
// Add shrink button
|
||||
UIButton *shrinkButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
CGRect btnFrame = CGRectMake(index * btnWidth, 0, btnWidth, sectionHeader.frame.size.height);
|
||||
shrinkButton.frame = btnFrame;
|
||||
shrinkButton.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[shrinkButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
|
||||
shrinkButton.tag = index;
|
||||
[sectionHeader addSubview:shrinkButton];
|
||||
sectionHeader.userInteractionEnabled = YES;
|
||||
|
||||
// Set shrink button constraints
|
||||
NSLayoutConstraint *leftConstraint;
|
||||
NSLayoutConstraint *widthConstraint;
|
||||
shrinkButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
if (!previousShrinkButton)
|
||||
{
|
||||
leftConstraint = [NSLayoutConstraint constraintWithItem:shrinkButton
|
||||
attribute:NSLayoutAttributeLeading
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:sectionHeader
|
||||
attribute:NSLayoutAttributeLeading
|
||||
multiplier:1
|
||||
constant:0];
|
||||
widthConstraint = [NSLayoutConstraint constraintWithItem:shrinkButton
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:sectionHeader
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:(1.0 /displayedRecentsDataSourceArray.count)
|
||||
constant:0];
|
||||
}
|
||||
else
|
||||
{
|
||||
leftConstraint = [NSLayoutConstraint constraintWithItem:shrinkButton
|
||||
attribute:NSLayoutAttributeLeading
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:previousShrinkButton
|
||||
attribute:NSLayoutAttributeTrailing
|
||||
multiplier:1
|
||||
constant:0];
|
||||
widthConstraint = [NSLayoutConstraint constraintWithItem:shrinkButton
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:previousShrinkButton
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1
|
||||
constant:0];
|
||||
}
|
||||
[NSLayoutConstraint activateConstraints:@[leftConstraint, widthConstraint]];
|
||||
previousShrinkButton = shrinkButton;
|
||||
|
||||
// Add shrink icon
|
||||
UIImage *chevron;
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] != NSNotFound)
|
||||
{
|
||||
chevron = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"disclosure"];
|
||||
}
|
||||
else
|
||||
{
|
||||
chevron = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"shrink"];
|
||||
}
|
||||
UIImageView *chevronView = [[UIImageView alloc] initWithImage:chevron];
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
// Display the tint color of the user
|
||||
MXKAccount *account = [[MXKAccountManager sharedManager] accountForUserId:recentsDataSource.mxSession.myUser.userId];
|
||||
if (account)
|
||||
{
|
||||
chevronView.backgroundColor = account.userTintColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
chevronView.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
chevronView.backgroundColor = [UIColor lightGrayColor];
|
||||
}
|
||||
chevronView.contentMode = UIViewContentModeCenter;
|
||||
frame = chevronView.frame;
|
||||
frame.size.width = frame.size.height = shrinkButton.frame.size.height - 10;
|
||||
frame.origin.x = shrinkButton.frame.size.width - frame.size.width - 8;
|
||||
frame.origin.y = (shrinkButton.frame.size.height - frame.size.height) / 2;
|
||||
chevronView.frame = frame;
|
||||
[shrinkButton addSubview:chevronView];
|
||||
chevronView.autoresizingMask |= (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin);
|
||||
|
||||
// Add label
|
||||
frame = shrinkButton.frame;
|
||||
frame.origin.x = 5;
|
||||
frame.origin.y = 5;
|
||||
frame.size.width = chevronView.frame.origin.x - 10;
|
||||
frame.size.height -= 10;
|
||||
UILabel *headerLabel = [[UILabel alloc] initWithFrame:frame];
|
||||
headerLabel.font = [UIFont boldSystemFontOfSize:16];
|
||||
headerLabel.backgroundColor = [UIColor clearColor];
|
||||
headerLabel.text = btnTitle;
|
||||
[shrinkButton addSubview:headerLabel];
|
||||
headerLabel.autoresizingMask |= (UIViewAutoresizingFlexibleWidth);
|
||||
}
|
||||
}
|
||||
|
||||
return sectionHeader;
|
||||
}
|
||||
|
||||
- (id<MXKRecentCellDataStoring>)cellDataAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
id<MXKRecentCellDataStoring> cellData = nil;
|
||||
|
||||
// Only one section is handled by this data source
|
||||
if (indexPath.section == 0)
|
||||
{
|
||||
// Consider first the case where there is only one data source (no interleaving).
|
||||
if (displayedRecentsDataSourceArray.count == 1)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = displayedRecentsDataSourceArray.firstObject;
|
||||
cellData = [recentsDataSource cellDataAtIndex:indexPath.row];
|
||||
}
|
||||
// Else all the cells have been interleaved.
|
||||
else if (indexPath.row < interleavedCellDataArray.count)
|
||||
{
|
||||
cellData = interleavedCellDataArray[indexPath.row];
|
||||
}
|
||||
}
|
||||
|
||||
return cellData;
|
||||
}
|
||||
|
||||
- (CGFloat)cellHeightAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
CGFloat height = 0;
|
||||
|
||||
// Only one section is handled by this data source
|
||||
if (indexPath.section == 0)
|
||||
{
|
||||
// Consider first the case where there is only one data source (no interleaving).
|
||||
if (displayedRecentsDataSourceArray.count == 1)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = displayedRecentsDataSourceArray.firstObject;
|
||||
height = [recentsDataSource cellHeightAtIndex:indexPath.row];
|
||||
}
|
||||
// Else all the cells have been interleaved.
|
||||
else if (indexPath.row < interleavedCellDataArray.count)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> recentCellData = interleavedCellDataArray[indexPath.row];
|
||||
|
||||
// Select the related recent data source
|
||||
MXKDataSource *dataSource = recentCellData.dataSource;
|
||||
if ([dataSource isKindOfClass:[MXKSessionRecentsDataSource class]])
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = (MXKSessionRecentsDataSource*)dataSource;
|
||||
// Count the index of this cell data in original data source array
|
||||
NSInteger rank = 0;
|
||||
for (NSInteger index = 0; index < indexPath.row; index++)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> cellData = interleavedCellDataArray[index];
|
||||
if (cellData.roomSummary == recentCellData.roomSummary)
|
||||
{
|
||||
rank++;
|
||||
}
|
||||
}
|
||||
|
||||
height = [recentsDataSource cellHeightAtIndex:rank];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
- (NSIndexPath*)cellIndexPathWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)matrixSession
|
||||
{
|
||||
NSIndexPath *indexPath = nil;
|
||||
|
||||
// Consider first the case where there is only one data source (no interleaving).
|
||||
if (displayedRecentsDataSourceArray.count == 1)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = displayedRecentsDataSourceArray.firstObject;
|
||||
if (recentsDataSource.mxSession == matrixSession)
|
||||
{
|
||||
// Look for the cell
|
||||
for (NSInteger index = 0; index < recentsDataSource.numberOfCells; index ++)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> recentCellData = [recentsDataSource cellDataAtIndex:index];
|
||||
if ([roomId isEqualToString:recentCellData.roomIdentifier])
|
||||
{
|
||||
// Got it
|
||||
indexPath = [NSIndexPath indexPathForRow:index inSection:0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Look for the right data source
|
||||
for (MXKSessionRecentsDataSource *recentsDataSource in displayedRecentsDataSourceArray)
|
||||
{
|
||||
if (recentsDataSource.mxSession == matrixSession)
|
||||
{
|
||||
// Check whether the source is not shrinked
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
// Look for the cell
|
||||
for (NSInteger index = 0; index < interleavedCellDataArray.count; index ++)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> recentCellData = interleavedCellDataArray[index];
|
||||
if ([roomId isEqualToString:recentCellData.roomIdentifier])
|
||||
{
|
||||
// Got it
|
||||
indexPath = [NSIndexPath indexPathForRow:index inSection:0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return indexPath;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
// Check whether all data sources are ready before rendering recents
|
||||
if (self.state == MXKDataSourceStateReady)
|
||||
{
|
||||
// Only one section is handled by this data source.
|
||||
return (displayedRecentsDataSourceArray.count ? 1 : 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
// Consider first the case where there is only one data source (no interleaving).
|
||||
if (displayedRecentsDataSourceArray.count == 1)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = displayedRecentsDataSourceArray.firstObject;
|
||||
return recentsDataSource.numberOfCells;
|
||||
}
|
||||
|
||||
return interleavedCellDataArray.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
id<MXKRecentCellDataStoring> roomData = [self cellDataAtIndexPath:indexPath];
|
||||
if (roomData && self.delegate)
|
||||
{
|
||||
NSString *cellIdentifier = [self.delegate cellReuseIdentifierForCellData:roomData];
|
||||
if (cellIdentifier)
|
||||
{
|
||||
UITableViewCell<MXKCellRendering> *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
|
||||
|
||||
// Make sure we listen to user actions on the cell
|
||||
cell.delegate = self;
|
||||
|
||||
// Make the bubble display the data
|
||||
[cell render:roomData];
|
||||
|
||||
// Clear the user flag, if only one recents list is available
|
||||
if (displayedRecentsDataSourceArray.count == 1 && [cell isKindOfClass:[MXKInterleavedRecentTableViewCell class]])
|
||||
{
|
||||
((MXKInterleavedRecentTableViewCell*)cell).userFlag.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a fake cell to prevent app from crashing.
|
||||
return [[UITableViewCell alloc] init];
|
||||
}
|
||||
|
||||
#pragma mark - MXKDataSourceDelegate
|
||||
|
||||
- (void)dataSource:(MXKDataSource*)dataSource didCellChange:(id)changes
|
||||
{
|
||||
// Consider first the case where there is only one data source (no interleaving).
|
||||
if (displayedRecentsDataSourceArray.count == 1)
|
||||
{
|
||||
// Flush interleaved cells array, we will refer directly to the cell data of the unique data source.
|
||||
[interleavedCellDataArray removeAllObjects];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle here the specific case where a second source is just added.
|
||||
// The empty interleaved cells array has to be prefilled with the cell data of the other source (except if this other source is shrinked).
|
||||
if (!interleavedCellDataArray.count && displayedRecentsDataSourceArray.count == 2)
|
||||
{
|
||||
// This is the first interleaving, look for the other source
|
||||
MXKSessionRecentsDataSource *recentsDataSource = displayedRecentsDataSourceArray.firstObject;
|
||||
if (recentsDataSource == dataSource)
|
||||
{
|
||||
recentsDataSource = displayedRecentsDataSourceArray.lastObject;
|
||||
}
|
||||
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
// Report all cell data
|
||||
for (NSInteger index = 0; index < recentsDataSource.numberOfCells; index ++)
|
||||
{
|
||||
[interleavedCellDataArray addObject:[recentsDataSource cellDataAtIndex:index]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update now interleaved cells array, TODO take into account 'changes' parameter
|
||||
MXKSessionRecentsDataSource *updateRecentsDataSource = (MXKSessionRecentsDataSource*)dataSource;
|
||||
NSInteger numberOfUpdatedCells = 0;
|
||||
// Check whether this dataSource is used
|
||||
if ([displayedRecentsDataSourceArray indexOfObject:dataSource] != NSNotFound && [shrinkedRecentsDataSourceArray indexOfObject:dataSource] == NSNotFound)
|
||||
{
|
||||
numberOfUpdatedCells = updateRecentsDataSource.numberOfCells;
|
||||
}
|
||||
|
||||
NSInteger currentCellIndex = 0;
|
||||
NSInteger updatedCellIndex = 0;
|
||||
id<MXKRecentCellDataStoring> updatedCellData = nil;
|
||||
|
||||
if (numberOfUpdatedCells)
|
||||
{
|
||||
updatedCellData = [updateRecentsDataSource cellDataAtIndex:updatedCellIndex++];
|
||||
}
|
||||
|
||||
// Review all cell data items of the current list
|
||||
while (currentCellIndex < interleavedCellDataArray.count)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> currentCellData = interleavedCellDataArray[currentCellIndex];
|
||||
|
||||
// Remove existing cell data of the updated data source
|
||||
if (currentCellData.dataSource == dataSource)
|
||||
{
|
||||
[interleavedCellDataArray removeObjectAtIndex:currentCellIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
while (updatedCellData && (updatedCellData.roomSummary.lastMessage.originServerTs > currentCellData.roomSummary.lastMessage.originServerTs))
|
||||
{
|
||||
[interleavedCellDataArray insertObject:updatedCellData atIndex:currentCellIndex++];
|
||||
updatedCellData = [updateRecentsDataSource cellDataAtIndex:updatedCellIndex++];
|
||||
}
|
||||
|
||||
currentCellIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
while (updatedCellData)
|
||||
{
|
||||
[interleavedCellDataArray addObject:updatedCellData];
|
||||
updatedCellData = [updateRecentsDataSource cellDataAtIndex:updatedCellIndex++];
|
||||
}
|
||||
}
|
||||
|
||||
// Call super to keep update readyRecentsDataSourceArray.
|
||||
[super dataSource:dataSource didCellChange:changes];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
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 "MXKRecentCellDataStoring.h"
|
||||
|
||||
/**
|
||||
`MXKRecentCellData` modelised the data for a `MXKRecentTableViewCell` cell.
|
||||
*/
|
||||
@interface MXKRecentCellData : MXKCellData <MXKRecentCellDataStoring>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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 "MXKRecentCellData.h"
|
||||
|
||||
@import MatrixSDK;
|
||||
|
||||
#import "MXKDataSource.h"
|
||||
#import "MXEvent+MatrixKit.h"
|
||||
|
||||
#import "MXKSwiftHeader.h"
|
||||
|
||||
@implementation MXKRecentCellData
|
||||
@synthesize roomSummary, dataSource, lastEventDate;
|
||||
|
||||
- (instancetype)initWithRoomSummary:(id<MXRoomSummaryProtocol>)theRoomSummary
|
||||
dataSource:(MXKDataSource*)theDataSource;
|
||||
{
|
||||
self = [self init];
|
||||
if (self)
|
||||
{
|
||||
roomSummary = theRoomSummary;
|
||||
dataSource = theDataSource;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
roomSummary = nil;
|
||||
}
|
||||
|
||||
- (MXSession *)mxSession
|
||||
{
|
||||
return dataSource.mxSession;
|
||||
}
|
||||
|
||||
- (NSString*)lastEventDate
|
||||
{
|
||||
return (NSString*)roomSummary.lastMessage.others[@"lastEventDate"];
|
||||
}
|
||||
|
||||
- (BOOL)hasUnread
|
||||
{
|
||||
return (roomSummary.localUnreadEventCount != 0);
|
||||
}
|
||||
|
||||
- (NSString *)roomIdentifier
|
||||
{
|
||||
if (self.isSuggestedRoom)
|
||||
{
|
||||
return self.roomSummary.spaceChildInfo.childRoomId;
|
||||
}
|
||||
return roomSummary.roomId;
|
||||
}
|
||||
|
||||
- (NSString *)roomDisplayname
|
||||
{
|
||||
if (self.isSuggestedRoom)
|
||||
{
|
||||
return self.roomSummary.spaceChildInfo.displayName;
|
||||
}
|
||||
return roomSummary.displayname;
|
||||
}
|
||||
|
||||
- (NSString *)avatarUrl
|
||||
{
|
||||
if (self.isSuggestedRoom)
|
||||
{
|
||||
return self.roomSummary.spaceChildInfo.avatarUrl;
|
||||
}
|
||||
return roomSummary.avatar;
|
||||
}
|
||||
|
||||
- (NSString *)lastEventTextMessage
|
||||
{
|
||||
if (self.isSuggestedRoom)
|
||||
{
|
||||
return roomSummary.spaceChildInfo.topic;
|
||||
}
|
||||
return roomSummary.lastMessage.text;
|
||||
}
|
||||
|
||||
- (NSAttributedString *)lastEventAttributedTextMessage
|
||||
{
|
||||
if (self.isSuggestedRoom)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
return roomSummary.lastMessage.attributedText;
|
||||
}
|
||||
|
||||
- (NSUInteger)notificationCount
|
||||
{
|
||||
return roomSummary.notificationCount;
|
||||
}
|
||||
|
||||
- (NSUInteger)highlightCount
|
||||
{
|
||||
return roomSummary.highlightCount;
|
||||
}
|
||||
|
||||
- (NSString*)notificationCountStringValue
|
||||
{
|
||||
return [NSString stringWithFormat:@"%tu", self.notificationCount];
|
||||
}
|
||||
|
||||
- (NSString*)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ %@: %@ - %@", super.description, self.roomSummary.roomId, self.roomDisplayname, self.lastEventTextMessage];
|
||||
}
|
||||
|
||||
- (BOOL)isSuggestedRoom
|
||||
{
|
||||
// As off now, we only store MXSpaceChildInfo in case of suggested rooms
|
||||
return self.roomSummary.spaceChildInfo != nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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 "MXKCellData.h"
|
||||
|
||||
@class MXKDataSource;
|
||||
@class MXSpaceChildInfo;
|
||||
|
||||
/**
|
||||
`MXKRecentCellDataStoring` defines a protocol a class must conform in order to store recent cell data
|
||||
managed by `MXKSessionRecentsDataSource`.
|
||||
*/
|
||||
@protocol MXKRecentCellDataStoring <NSObject>
|
||||
|
||||
#pragma mark - Data displayed by a room recent cell
|
||||
|
||||
/**
|
||||
The original data source of the recent displayed by the cell.
|
||||
*/
|
||||
@property (nonatomic, weak, readonly) MXKDataSource *dataSource;
|
||||
|
||||
/**
|
||||
The `MXRoomSummaryProtocol` instance of the room for the recent displayed by the cell.
|
||||
*/
|
||||
@property (nonatomic, readonly) id<MXRoomSummaryProtocol> roomSummary;
|
||||
|
||||
@property (nonatomic, readonly) NSString *roomIdentifier;
|
||||
@property (nonatomic, readonly) NSString *roomDisplayname;
|
||||
@property (nonatomic, readonly) NSString *avatarUrl;
|
||||
@property (nonatomic, readonly) NSString *lastEventTextMessage;
|
||||
@property (nonatomic, readonly) NSString *lastEventDate;
|
||||
|
||||
@property (nonatomic, readonly) BOOL hasUnread;
|
||||
@property (nonatomic, readonly) NSUInteger notificationCount;
|
||||
@property (nonatomic, readonly) NSUInteger highlightCount;
|
||||
@property (nonatomic, readonly) NSString *notificationCountStringValue;
|
||||
@property (nonatomic, readonly) BOOL isSuggestedRoom;
|
||||
|
||||
@property (nonatomic, readonly) MXSession *mxSession;
|
||||
|
||||
#pragma mark - Public methods
|
||||
/**
|
||||
Create a new `MXKCellData` object for a new recent cell.
|
||||
|
||||
@param roomSummary the `id<MXRoomSummaryProtocol>` object that has data about the room.
|
||||
@param dataSource the `MXKDataSource` object that will use this instance.
|
||||
@return the newly created instance.
|
||||
*/
|
||||
- (instancetype)initWithRoomSummary:(id<MXRoomSummaryProtocol>)roomSummary
|
||||
dataSource:(MXKDataSource*)dataSource;
|
||||
|
||||
@optional
|
||||
/**
|
||||
The `lastEventTextMessage` with sets of attributes.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSAttributedString *lastEventAttributedTextMessage;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
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 "MXKSessionRecentsDataSource.h"
|
||||
|
||||
/**
|
||||
'MXKRecentsDataSource' is a base class to handle recents from one or multiple matrix sessions.
|
||||
A 'MXKRecentsDataSource' instance provides the recents data source for `MXKRecentListViewController`.
|
||||
|
||||
By default, the recents list of different sessions are handled into separate sections.
|
||||
*/
|
||||
@interface MXKRecentsDataSource : MXKDataSource <UITableViewDataSource, MXKDataSourceDelegate>
|
||||
{
|
||||
@protected
|
||||
/**
|
||||
Array of `MXKSessionRecentsDataSource` instances. Only ready and non empty data source are listed here.
|
||||
(Note: a data source may be considered as empty during searching)
|
||||
*/
|
||||
NSMutableArray *displayedRecentsDataSourceArray;
|
||||
|
||||
/**
|
||||
Array of shrinked sources. Sub-list of displayedRecentsDataSourceArray.
|
||||
*/
|
||||
NSMutableArray *shrinkedRecentsDataSourceArray;
|
||||
}
|
||||
|
||||
/**
|
||||
List of associated matrix sessions.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray* mxSessions;
|
||||
|
||||
/**
|
||||
The number of available recents data sources (This count may be different than mxSession.count because empty data sources are ignored).
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger displayedRecentsDataSourcesCount;
|
||||
|
||||
/**
|
||||
Tell whether there are some unread messages.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL hasUnread;
|
||||
|
||||
/**
|
||||
The current search patterns list.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray* searchPatternsList;
|
||||
|
||||
@property (nonatomic, strong) MXSpace *currentSpace;
|
||||
|
||||
#pragma mark - Configuration
|
||||
|
||||
/**
|
||||
Add recents data from a matrix session.
|
||||
|
||||
@param mxSession the Matrix session to retrieve contextual data.
|
||||
@return the new 'MXKSessionRecentsDataSource' instance created for this Matrix session.
|
||||
*/
|
||||
- (MXKSessionRecentsDataSource *)addMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Remove recents data related to a matrix session.
|
||||
|
||||
@param mxSession the session to remove.
|
||||
*/
|
||||
- (void)removeMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Filter the current recents list according to the provided patterns.
|
||||
|
||||
@param patternsList the list of patterns (`NSString` instances) to match with. Set nil to cancel search.
|
||||
*/
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList;
|
||||
|
||||
/**
|
||||
Get the section header view.
|
||||
|
||||
@param section the section index
|
||||
@param frame the drawing area for the header of the specified section.
|
||||
@return the section header.
|
||||
*/
|
||||
- (UIView *)viewForHeaderInSection:(NSInteger)section withFrame:(CGRect)frame;
|
||||
|
||||
/**
|
||||
Get the data for the cell at the given index path.
|
||||
|
||||
@param indexPath the index of the cell
|
||||
@return the cell data
|
||||
*/
|
||||
- (id<MXKRecentCellDataStoring>)cellDataAtIndexPath:(NSIndexPath*)indexPath;
|
||||
|
||||
/**
|
||||
Get the height of the cell at the given index path.
|
||||
|
||||
@param indexPath the index of the cell
|
||||
@return the cell height
|
||||
*/
|
||||
- (CGFloat)cellHeightAtIndexPath:(NSIndexPath*)indexPath;
|
||||
|
||||
/**
|
||||
Get the index path of the cell related to the provided roomId and session.
|
||||
|
||||
@param roomId the room identifier.
|
||||
@param mxSession the matrix session in which the room should be available.
|
||||
@return indexPath the index of the cell (nil if not found or if the related section is shrinked).
|
||||
*/
|
||||
- (NSIndexPath*)cellIndexPathWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Returns the room at the index path
|
||||
|
||||
@param indexPath the index of the cell
|
||||
@return the MXRoom if it exists
|
||||
*/
|
||||
- (MXRoom*)getRoomAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
Leave the room at the index path
|
||||
|
||||
@param indexPath the index of the cell
|
||||
*/
|
||||
- (void)leaveRoomAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
Action registered on buttons used to shrink/disclose recents sources.
|
||||
*/
|
||||
- (IBAction)onButtonPressed:(id)sender;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,657 @@
|
||||
/*
|
||||
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 "MXKRecentsDataSource.h"
|
||||
|
||||
@import MatrixSDK.MXMediaManager;
|
||||
|
||||
#import "NSBundle+MatrixKit.h"
|
||||
|
||||
#import "MXKConstants.h"
|
||||
|
||||
@interface MXKRecentsDataSource ()
|
||||
{
|
||||
/**
|
||||
Array of `MXSession` instances.
|
||||
*/
|
||||
NSMutableArray *mxSessionArray;
|
||||
|
||||
/**
|
||||
Array of `MXKSessionRecentsDataSource` instances (one by matrix session).
|
||||
*/
|
||||
NSMutableArray *recentsDataSourceArray;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKRecentsDataSource
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
mxSessionArray = [NSMutableArray array];
|
||||
recentsDataSourceArray = [NSMutableArray array];
|
||||
|
||||
displayedRecentsDataSourceArray = [NSMutableArray array];
|
||||
shrinkedRecentsDataSourceArray = [NSMutableArray array];
|
||||
|
||||
// Set default data and view classes
|
||||
[self registerCellDataClass:MXKRecentCellData.class forCellIdentifier:kMXKRecentCellIdentifier];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionInviteRoomUpdate:) name:kMXSessionInvitedRoomsDidChangeNotification object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMatrixSession:(MXSession *)matrixSession
|
||||
{
|
||||
self = [self init];
|
||||
if (self)
|
||||
{
|
||||
[self addMatrixSession:matrixSession];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (MXKSessionRecentsDataSource *)addMatrixSession:(MXSession *)matrixSession
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [[MXKSessionRecentsDataSource alloc] initWithMatrixSession:matrixSession];
|
||||
|
||||
if (recentsDataSource)
|
||||
{
|
||||
// Set the actual data and view classes
|
||||
[recentsDataSource registerCellDataClass:[self cellDataClassForCellIdentifier:kMXKRecentCellIdentifier] forCellIdentifier:kMXKRecentCellIdentifier];
|
||||
|
||||
[mxSessionArray addObject:matrixSession];
|
||||
|
||||
recentsDataSource.delegate = self;
|
||||
[recentsDataSourceArray addObject:recentsDataSource];
|
||||
|
||||
[recentsDataSource finalizeInitialization];
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didAddMatrixSession:)])
|
||||
{
|
||||
[self.delegate dataSource:self didAddMatrixSession:matrixSession];
|
||||
}
|
||||
|
||||
// Check the current state of the data source
|
||||
[self dataSource:recentsDataSource didStateChange:recentsDataSource.state];
|
||||
}
|
||||
|
||||
return recentsDataSource;
|
||||
}
|
||||
|
||||
- (void)removeMatrixSession:(MXSession*)matrixSession
|
||||
{
|
||||
for (NSUInteger index = 0; index < mxSessionArray.count; index++)
|
||||
{
|
||||
MXSession *mxSession = [mxSessionArray objectAtIndex:index];
|
||||
if (mxSession == matrixSession)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [recentsDataSourceArray objectAtIndex:index];
|
||||
[recentsDataSource destroy];
|
||||
|
||||
[displayedRecentsDataSourceArray removeObject:recentsDataSource];
|
||||
|
||||
[recentsDataSourceArray removeObjectAtIndex:index];
|
||||
[mxSessionArray removeObjectAtIndex:index];
|
||||
|
||||
// Loop on 'didCellChange' method to let inherited 'MXKRecentsDataSource' class handle this removed data source.
|
||||
[self dataSource:recentsDataSource didCellChange:nil];
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didRemoveMatrixSession:)])
|
||||
{
|
||||
[self.delegate dataSource:self didRemoveMatrixSession:matrixSession];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCurrentSpace:(MXSpace *)currentSpace
|
||||
{
|
||||
_currentSpace = currentSpace;
|
||||
|
||||
for (MXKSessionRecentsDataSource *recentsDataSource in recentsDataSourceArray) {
|
||||
recentsDataSource.currentSpace = currentSpace;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - MXKDataSource overridden
|
||||
|
||||
- (MXSession*)mxSession
|
||||
{
|
||||
if (mxSessionArray.count > 1)
|
||||
{
|
||||
MXLogDebug(@"[MXKRecentsDataSource] CAUTION: mxSession property is not relevant in case of multi-sessions (%tu)", mxSessionArray.count);
|
||||
}
|
||||
|
||||
// TODO: This property is not well adapted in case of multi-sessions
|
||||
// We consider by default the first added session as the main one...
|
||||
if (mxSessionArray.count)
|
||||
{
|
||||
return [mxSessionArray firstObject];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (MXKDataSourceState)state
|
||||
{
|
||||
// Manage a global state based on the state of each internal data source.
|
||||
|
||||
MXKDataSourceState currentState = MXKDataSourceStateUnknown;
|
||||
MXKSessionRecentsDataSource *dataSource;
|
||||
|
||||
if (recentsDataSourceArray.count)
|
||||
{
|
||||
dataSource = [recentsDataSourceArray firstObject];
|
||||
currentState = dataSource.state;
|
||||
|
||||
// Deduce the current state according to the internal data sources
|
||||
for (NSUInteger index = 1; index < recentsDataSourceArray.count; index++)
|
||||
{
|
||||
dataSource = [recentsDataSourceArray objectAtIndex:index];
|
||||
|
||||
switch (dataSource.state)
|
||||
{
|
||||
case MXKDataSourceStateUnknown:
|
||||
break;
|
||||
case MXKDataSourceStatePreparing:
|
||||
currentState = MXKDataSourceStatePreparing;
|
||||
break;
|
||||
case MXKDataSourceStateFailed:
|
||||
if (currentState == MXKDataSourceStateUnknown)
|
||||
{
|
||||
currentState = MXKDataSourceStateFailed;
|
||||
}
|
||||
break;
|
||||
case MXKDataSourceStateReady:
|
||||
if (currentState == MXKDataSourceStateUnknown || currentState == MXKDataSourceStateFailed)
|
||||
{
|
||||
currentState = MXKDataSourceStateReady;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
for (MXKSessionRecentsDataSource *recentsDataSource in recentsDataSourceArray)
|
||||
{
|
||||
[recentsDataSource destroy];
|
||||
}
|
||||
displayedRecentsDataSourceArray = nil;
|
||||
recentsDataSourceArray = nil;
|
||||
shrinkedRecentsDataSourceArray = nil;
|
||||
mxSessionArray = nil;
|
||||
|
||||
_searchPatternsList = nil;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionInvitedRoomsDidChangeNotification object:nil];
|
||||
|
||||
[super destroy];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSArray*)mxSessions
|
||||
{
|
||||
return [NSArray arrayWithArray:mxSessionArray];
|
||||
}
|
||||
|
||||
- (NSUInteger)displayedRecentsDataSourcesCount
|
||||
{
|
||||
return displayedRecentsDataSourceArray.count;
|
||||
}
|
||||
|
||||
- (BOOL)hasUnread
|
||||
{
|
||||
// Check hasUnread flag in all ready data sources
|
||||
for (MXKSessionRecentsDataSource *recentsDataSource in displayedRecentsDataSourceArray)
|
||||
{
|
||||
if (recentsDataSource.hasUnread)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList
|
||||
{
|
||||
_searchPatternsList = patternsList;
|
||||
|
||||
// CAUTION: Apply here the search pattern to all ready data sources (not only displayed ones).
|
||||
// Some data sources may have been removed from 'displayedRecentsDataSourceArray' during a previous search if no recent was matching.
|
||||
for (MXKSessionRecentsDataSource *recentsDataSource in recentsDataSourceArray)
|
||||
{
|
||||
if (recentsDataSource.state == MXKDataSourceStateReady)
|
||||
{
|
||||
[recentsDataSource searchWithPatterns:patternsList];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (UIView *)viewForHeaderInSection:(NSInteger)section withFrame:(CGRect)frame
|
||||
{
|
||||
UIView *sectionHeader = nil;
|
||||
|
||||
if (displayedRecentsDataSourceArray.count > 1 && section < displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:section];
|
||||
|
||||
NSString* sectionTitle = recentsDataSource.mxSession.myUser.userId;
|
||||
|
||||
sectionHeader = [[UIView alloc] initWithFrame:frame];
|
||||
sectionHeader.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0];
|
||||
|
||||
// Add shrink button
|
||||
UIButton *shrinkButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
frame.origin.x = frame.origin.y = 0;
|
||||
shrinkButton.frame = frame;
|
||||
shrinkButton.backgroundColor = [UIColor clearColor];
|
||||
[shrinkButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
|
||||
shrinkButton.tag = section;
|
||||
[sectionHeader addSubview:shrinkButton];
|
||||
sectionHeader.userInteractionEnabled = YES;
|
||||
|
||||
// Add shrink icon
|
||||
UIImage *chevron;
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] != NSNotFound)
|
||||
{
|
||||
chevron = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"disclosure"];
|
||||
}
|
||||
else
|
||||
{
|
||||
chevron = [NSBundle mxk_imageFromMXKAssetsBundleWithName:@"shrink"];
|
||||
}
|
||||
UIImageView *chevronView = [[UIImageView alloc] initWithImage:chevron];
|
||||
chevronView.contentMode = UIViewContentModeCenter;
|
||||
frame = chevronView.frame;
|
||||
frame.origin.x = sectionHeader.frame.size.width - frame.size.width - 8;
|
||||
frame.origin.y = (sectionHeader.frame.size.height - frame.size.height) / 2;
|
||||
chevronView.frame = frame;
|
||||
[sectionHeader addSubview:chevronView];
|
||||
chevronView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin);
|
||||
|
||||
// Add label
|
||||
frame = sectionHeader.frame;
|
||||
frame.origin.x = 5;
|
||||
frame.origin.y = 5;
|
||||
frame.size.width = chevronView.frame.origin.x - 10;
|
||||
frame.size.height -= 10;
|
||||
UILabel *headerLabel = [[UILabel alloc] initWithFrame:frame];
|
||||
headerLabel.font = [UIFont boldSystemFontOfSize:16];
|
||||
headerLabel.backgroundColor = [UIColor clearColor];
|
||||
headerLabel.text = sectionTitle;
|
||||
[sectionHeader addSubview:headerLabel];
|
||||
}
|
||||
|
||||
return sectionHeader;
|
||||
}
|
||||
|
||||
- (id<MXKRecentCellDataStoring>)cellDataAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.section < displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:indexPath.section];
|
||||
|
||||
return [recentsDataSource cellDataAtIndex:indexPath.row];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGFloat)cellHeightAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.section < displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:indexPath.section];
|
||||
|
||||
return [recentsDataSource cellHeightAtIndex:indexPath.row];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSIndexPath*)cellIndexPathWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)matrixSession
|
||||
{
|
||||
NSIndexPath *indexPath = nil;
|
||||
|
||||
// Look for the right data source
|
||||
for (NSInteger section = 0; section < displayedRecentsDataSourceArray.count; section++)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = displayedRecentsDataSourceArray[section];
|
||||
if (recentsDataSource.mxSession == matrixSession)
|
||||
{
|
||||
// Check whether the source is not shrinked
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
// Look for the cell
|
||||
for (NSInteger index = 0; index < recentsDataSource.numberOfCells; index ++)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> recentCellData = [recentsDataSource cellDataAtIndex:index];
|
||||
if ([roomId isEqualToString:recentCellData.roomIdentifier])
|
||||
{
|
||||
// Got it
|
||||
indexPath = [NSIndexPath indexPathForRow:index inSection:section];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return indexPath;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
// Check whether all data sources are ready before rendering recents
|
||||
if (self.state == MXKDataSourceStateReady)
|
||||
{
|
||||
return displayedRecentsDataSourceArray.count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
if (section < displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:section];
|
||||
|
||||
// Check whether the source is shrinked
|
||||
if ([shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
return recentsDataSource.numberOfCells;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
NSString* sectionTitle = nil;
|
||||
|
||||
if (displayedRecentsDataSourceArray.count > 1 && section < displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:section];
|
||||
|
||||
sectionTitle = recentsDataSource.mxSession.myUser.userId;
|
||||
}
|
||||
|
||||
return sectionTitle;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.section < displayedRecentsDataSourceArray.count && self.delegate)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:indexPath.section];
|
||||
|
||||
id<MXKRecentCellDataStoring> roomData = [recentsDataSource cellDataAtIndex:indexPath.row];
|
||||
|
||||
NSString *cellIdentifier = [self.delegate cellReuseIdentifierForCellData:roomData];
|
||||
if (cellIdentifier)
|
||||
{
|
||||
UITableViewCell<MXKCellRendering> *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
|
||||
|
||||
// Make sure we listen to user actions on the cell
|
||||
cell.delegate = self;
|
||||
|
||||
// Make the bubble display the data
|
||||
[cell render:roomData];
|
||||
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a fake cell to prevent app from crashing.
|
||||
return [[UITableViewCell alloc] init];
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
// Return NO if you do not want the specified item to be editable.
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (editingStyle == UITableViewCellEditingStyleDelete)
|
||||
{
|
||||
[self leaveRoomAtIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - MXKDataSourceDelegate
|
||||
|
||||
- (Class<MXKCellRendering>)cellViewClassForCellData:(MXKCellData*)cellData
|
||||
{
|
||||
// Retrieve the class from the delegate here
|
||||
if (self.delegate)
|
||||
{
|
||||
return [self.delegate cellViewClassForCellData:cellData];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData
|
||||
{
|
||||
// Retrieve the identifier from the delegate here
|
||||
if (self.delegate)
|
||||
{
|
||||
return [self.delegate cellReuseIdentifierForCellData:cellData];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)dataSource:(MXKDataSource*)dataSource didCellChange:(id)changes
|
||||
{
|
||||
// Keep update readyRecentsDataSourceArray by checking number of cells
|
||||
if (dataSource.state == MXKDataSourceStateReady)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = (MXKSessionRecentsDataSource*)dataSource;
|
||||
|
||||
if (recentsDataSource.numberOfCells)
|
||||
{
|
||||
// Check whether the data source must be added
|
||||
if ([displayedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
// Add this data source first
|
||||
[self dataSource:dataSource didStateChange:dataSource.state];
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check whether this data source must be removed
|
||||
if ([displayedRecentsDataSourceArray indexOfObject:recentsDataSource] != NSNotFound)
|
||||
{
|
||||
[displayedRecentsDataSourceArray removeObject:recentsDataSource];
|
||||
|
||||
// Loop on 'didCellChange' method to let inherited 'MXKRecentsDataSource' class handle this removed data source.
|
||||
[self dataSource:recentsDataSource didCellChange:nil];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify delegate
|
||||
[self.delegate dataSource:self didCellChange:changes];
|
||||
}
|
||||
|
||||
- (void)dataSource:(MXKDataSource*)dataSource didStateChange:(MXKDataSourceState)state
|
||||
{
|
||||
// Update list of ready data sources
|
||||
MXKSessionRecentsDataSource *recentsDataSource = (MXKSessionRecentsDataSource*)dataSource;
|
||||
if (dataSource.state == MXKDataSourceStateReady && recentsDataSource.numberOfCells)
|
||||
{
|
||||
if ([displayedRecentsDataSourceArray indexOfObject:recentsDataSource] == NSNotFound)
|
||||
{
|
||||
// Add this new recents data source.
|
||||
if (!displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
[displayedRecentsDataSourceArray addObject:recentsDataSource];
|
||||
}
|
||||
else
|
||||
{
|
||||
// To display multiple accounts in a consistent order, we sort the recents data source by considering the account user id (alphabetic order).
|
||||
NSUInteger index;
|
||||
for (index = 0; index < displayedRecentsDataSourceArray.count; index++)
|
||||
{
|
||||
MXKSessionRecentsDataSource *currentRecentsDataSource = displayedRecentsDataSourceArray[index];
|
||||
if ([currentRecentsDataSource.mxSession.myUser.userId compare:recentsDataSource.mxSession.myUser.userId] == NSOrderedDescending)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert this data source
|
||||
[displayedRecentsDataSourceArray insertObject:recentsDataSource atIndex:index];
|
||||
}
|
||||
|
||||
// Check whether a search session is in progress
|
||||
if (_searchPatternsList)
|
||||
{
|
||||
[recentsDataSource searchWithPatterns:_searchPatternsList];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Loop on 'didCellChange' method to let inherited 'MXKRecentsDataSource' class handle this new added data source.
|
||||
[self dataSource:recentsDataSource didCellChange:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ([displayedRecentsDataSourceArray indexOfObject:recentsDataSource] != NSNotFound)
|
||||
{
|
||||
[displayedRecentsDataSourceArray removeObject:recentsDataSource];
|
||||
|
||||
// Loop on 'didCellChange' method to let inherited 'MXKRecentsDataSource' class handle this removed data source.
|
||||
[self dataSource:recentsDataSource didCellChange:nil];
|
||||
}
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didStateChange:)])
|
||||
{
|
||||
[self.delegate dataSource:self didStateChange:self.state];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Action
|
||||
|
||||
- (IBAction)onButtonPressed:(id)sender
|
||||
{
|
||||
if ([sender isKindOfClass:[UIButton class]])
|
||||
{
|
||||
UIButton *shrinkButton = (UIButton*)sender;
|
||||
|
||||
if (shrinkButton.tag < displayedRecentsDataSourceArray.count)
|
||||
{
|
||||
MXKSessionRecentsDataSource *recentsDataSource = [displayedRecentsDataSourceArray objectAtIndex:shrinkButton.tag];
|
||||
|
||||
NSUInteger index = [shrinkedRecentsDataSourceArray indexOfObject:recentsDataSource];
|
||||
if (index != NSNotFound)
|
||||
{
|
||||
// Disclose the
|
||||
[shrinkedRecentsDataSourceArray removeObjectAtIndex:index];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Shrink the recents from this session
|
||||
[shrinkedRecentsDataSourceArray addObject:recentsDataSource];
|
||||
}
|
||||
|
||||
// Loop on 'didCellChange' method to let inherited 'MXKRecentsDataSource' class handle change on this data source.
|
||||
[self dataSource:recentsDataSource didCellChange:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - room actions
|
||||
- (MXRoom*)getRoomAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
// Leave the selected room
|
||||
id<MXKRecentCellDataStoring> recentCellData = [self cellDataAtIndexPath:indexPath];
|
||||
|
||||
if (recentCellData)
|
||||
{
|
||||
return [self.mxSession roomWithRoomId:recentCellData.roomIdentifier];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)leaveRoomAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
MXRoom* room = [self getRoomAtIndexPath:indexPath];
|
||||
|
||||
if (room)
|
||||
{
|
||||
// cancel pending uploads/downloads
|
||||
// they are useless by now
|
||||
[MXMediaManager cancelDownloadsInCacheFolder:room.roomId];
|
||||
|
||||
// TODO GFO cancel pending uploads related to this room
|
||||
|
||||
[room leave:^{
|
||||
|
||||
// Trigger recents table refresh
|
||||
if (self.delegate)
|
||||
{
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
MXLogDebug(@"[MXKRecentsDataSource] Failed to leave room (%@) failed", room.roomId);
|
||||
|
||||
// Notify MatrixKit user
|
||||
NSString *myUserId = room.mxSession.myUser.userId;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil];
|
||||
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMXSessionInviteRoomUpdate:(NSNotification *)notif
|
||||
{
|
||||
MXSession *mxSession = notif.object;
|
||||
if ([self.mxSessions indexOfObject:mxSession] != NSNotFound)
|
||||
{
|
||||
// do nothing by default
|
||||
// the inherited classes might require to perform a full or a particial refresh.
|
||||
//[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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 "MXKConstants.h"
|
||||
#import "MXKDataSource.h"
|
||||
#import "MXKRecentCellData.h"
|
||||
|
||||
@class MXSpace;
|
||||
|
||||
/**
|
||||
Identifier to use for cells that display a room in the recents list.
|
||||
*/
|
||||
extern NSString *const kMXKRecentCellIdentifier;
|
||||
|
||||
/**
|
||||
The recents data source based on a unique matrix session.
|
||||
*/
|
||||
MXK_DEPRECATED_ATTRIBUTE_WITH_MSG("See MXSession.roomListDataManager")
|
||||
@interface MXKSessionRecentsDataSource : MXKDataSource {
|
||||
|
||||
@protected
|
||||
|
||||
/**
|
||||
The data for the cells served by `MXKSessionRecentsDataSource`.
|
||||
*/
|
||||
NSMutableArray *cellDataArray;
|
||||
|
||||
/**
|
||||
The filtered recents: sub-list of `cellDataArray` defined by `searchWithPatterns:` call.
|
||||
*/
|
||||
NSMutableArray *filteredCellDataArray;
|
||||
}
|
||||
|
||||
/**
|
||||
The current number of cells.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSInteger numberOfCells;
|
||||
|
||||
/**
|
||||
Tell whether there are some unread messages.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL hasUnread;
|
||||
|
||||
@property (nonatomic, strong, nullable) MXSpace *currentSpace;
|
||||
|
||||
|
||||
#pragma mark - Life cycle
|
||||
|
||||
/**
|
||||
Filter the current recents list according to the provided patterns.
|
||||
When patterns are not empty, the search result is stored in `filteredCellDataArray`,
|
||||
this array provides then data for the cells served by `MXKRecentsDataSource`.
|
||||
|
||||
@param patternsList the list of patterns (`NSString` instances) to match with. Set nil to cancel search.
|
||||
*/
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList;
|
||||
|
||||
/**
|
||||
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<MXKRecentCellDataStoring>)cellDataAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
Get height of the cell at the given index.
|
||||
|
||||
@param index the index of the cell in the array
|
||||
@return the cell height
|
||||
*/
|
||||
- (CGFloat)cellHeightAtIndex:(NSInteger)index;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,552 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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 "MXKSessionRecentsDataSource.h"
|
||||
|
||||
@import MatrixSDK;
|
||||
|
||||
#import "MXKRoomDataSourceManager.h"
|
||||
|
||||
#import "MXKSwiftHeader.h"
|
||||
|
||||
#pragma mark - Constant definitions
|
||||
NSString *const kMXKRecentCellIdentifier = @"kMXKRecentCellIdentifier";
|
||||
static NSTimeInterval const roomSummaryChangeThrottlerDelay = .5;
|
||||
|
||||
|
||||
@interface MXKSessionRecentsDataSource ()
|
||||
{
|
||||
MXKRoomDataSourceManager *roomDataSourceManager;
|
||||
|
||||
/**
|
||||
Internal array used to regulate change notifications.
|
||||
Cell data changes are stored instantly in this array.
|
||||
These changes are reported to the delegate only if no server sync is in progress.
|
||||
*/
|
||||
NSMutableArray *internalCellDataArray;
|
||||
|
||||
/**
|
||||
Store the current search patterns list.
|
||||
*/
|
||||
NSArray* searchPatternsList;
|
||||
|
||||
/**
|
||||
Do not react on every summary change
|
||||
*/
|
||||
MXThrottler *roomSummaryChangeThrottler;
|
||||
|
||||
/**
|
||||
Last received suggested rooms per space ID
|
||||
*/
|
||||
NSMutableDictionary<NSString*, NSArray<MXSpaceChildInfo *> *> *lastSuggestedRooms;
|
||||
|
||||
/**
|
||||
Event listener of the current space used to update the UI if an event occurs.
|
||||
*/
|
||||
id spaceEventsListener;
|
||||
|
||||
/**
|
||||
Observer used to reload data when the space service is initialised
|
||||
*/
|
||||
id spaceServiceDidInitialiseObserver;
|
||||
}
|
||||
|
||||
/**
|
||||
Additional suggestedRooms related to the current selected Space
|
||||
*/
|
||||
@property (nonatomic, strong) NSArray<MXSpaceChildInfo *> *suggestedRooms;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKSessionRecentsDataSource
|
||||
|
||||
- (instancetype)initWithMatrixSession:(MXSession *)matrixSession
|
||||
{
|
||||
self = [super initWithMatrixSession:matrixSession];
|
||||
if (self)
|
||||
{
|
||||
roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mxSession];
|
||||
|
||||
internalCellDataArray = [NSMutableArray array];
|
||||
filteredCellDataArray = nil;
|
||||
|
||||
lastSuggestedRooms = [NSMutableDictionary new];
|
||||
|
||||
// Set default data and view classes
|
||||
[self registerCellDataClass:MXKRecentCellData.class forCellIdentifier:kMXKRecentCellIdentifier];
|
||||
|
||||
roomSummaryChangeThrottler = [[MXThrottler alloc] initWithMinimumDelay:roomSummaryChangeThrottlerDelay];
|
||||
|
||||
[[MXKAppSettings standardAppSettings] addObserver:self forKeyPath:@"showAllRoomsInHomeSpace" options:0 context:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXRoomSummaryDidChangeNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKRoomDataSourceSyncStatusChanged object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionNewRoomNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidLeaveRoomNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDirectRoomsDidChangeNotification object:nil];
|
||||
|
||||
if (spaceServiceDidInitialiseObserver) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:spaceServiceDidInitialiseObserver];
|
||||
}
|
||||
|
||||
[roomSummaryChangeThrottler cancelAll];
|
||||
roomSummaryChangeThrottler = nil;
|
||||
|
||||
cellDataArray = nil;
|
||||
internalCellDataArray = nil;
|
||||
filteredCellDataArray = nil;
|
||||
lastSuggestedRooms = nil;
|
||||
|
||||
searchPatternsList = nil;
|
||||
|
||||
[[MXKAppSettings standardAppSettings] removeObserver:self forKeyPath:@"showAllRoomsInHomeSpace" context:nil];
|
||||
|
||||
[super destroy];
|
||||
}
|
||||
|
||||
- (void)didMXSessionStateChange
|
||||
{
|
||||
if (MXSessionStateStoreDataReady <= self.mxSession.state)
|
||||
{
|
||||
// Check whether some data have been already load
|
||||
if (0 == internalCellDataArray.count)
|
||||
{
|
||||
[self loadData];
|
||||
}
|
||||
else if (!roomDataSourceManager.isServerSyncInProgress)
|
||||
{
|
||||
// Sort cell data and notify the delegate
|
||||
[self sortCellDataAndNotifyChanges];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCurrentSpace:(MXSpace *)currentSpace
|
||||
{
|
||||
if (_currentSpace == currentSpace)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentSpace && spaceEventsListener)
|
||||
{
|
||||
[_currentSpace.room removeListener:spaceEventsListener];
|
||||
}
|
||||
|
||||
_currentSpace = currentSpace;
|
||||
|
||||
self.suggestedRooms = _currentSpace ? lastSuggestedRooms[_currentSpace.spaceId] : nil;
|
||||
[self updateSuggestedRooms];
|
||||
|
||||
MXWeakify(self);
|
||||
spaceEventsListener = [self.currentSpace.room listenToEvents:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
[self updateSuggestedRooms];
|
||||
}];
|
||||
}
|
||||
|
||||
-(void)setSuggestedRooms:(NSArray<MXSpaceChildInfo *> *)suggestedRooms
|
||||
{
|
||||
_suggestedRooms = suggestedRooms;
|
||||
[self loadData];
|
||||
}
|
||||
|
||||
-(void)updateSuggestedRooms
|
||||
{
|
||||
if (self.currentSpace)
|
||||
{
|
||||
NSString *currentSpaceId = self.currentSpace.spaceId;
|
||||
MXWeakify(self);
|
||||
[self.mxSession.spaceService getSpaceChildrenForSpaceWithId:currentSpaceId suggestedOnly:YES limit:5 maxDepth:1 paginationToken:nil success:^(MXSpaceChildrenSummary * _Nonnull childrenSummary) {
|
||||
MXLogDebug(@"[MXKSessionRecentsDataSource] getSpaceChildrenForSpaceWithId %@: %ld found", self.currentSpace.spaceId, childrenSummary.childInfos.count);
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
self->lastSuggestedRooms[currentSpaceId] = childrenSummary.childInfos;
|
||||
if ([self.currentSpace.spaceId isEqual:currentSpaceId]) {
|
||||
self.suggestedRooms = childrenSummary.childInfos;
|
||||
}
|
||||
} failure:^(NSError * _Nonnull error) {
|
||||
MXLogError(@"[MXKSessionRecentsDataSource] getSpaceChildrenForSpaceWithId failed with error: %@", error);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSInteger)numberOfCells
|
||||
{
|
||||
if (filteredCellDataArray)
|
||||
{
|
||||
return filteredCellDataArray.count;
|
||||
}
|
||||
return cellDataArray.count;
|
||||
}
|
||||
|
||||
- (BOOL)hasUnread
|
||||
{
|
||||
// Check all current cells
|
||||
// Use numberOfRowsInSection methods so that we take benefit of the filtering
|
||||
for (NSUInteger i = 0; i < self.numberOfCells; i++)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> cellData = [self cellDataAtIndex:i];
|
||||
if (cellData.hasUnread)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList
|
||||
{
|
||||
if (patternsList.count)
|
||||
{
|
||||
searchPatternsList = patternsList;
|
||||
|
||||
if (filteredCellDataArray)
|
||||
{
|
||||
[filteredCellDataArray removeAllObjects];
|
||||
}
|
||||
else
|
||||
{
|
||||
filteredCellDataArray = [NSMutableArray arrayWithCapacity:cellDataArray.count];
|
||||
}
|
||||
|
||||
for (id<MXKRecentCellDataStoring> cellData in cellDataArray)
|
||||
{
|
||||
for (NSString* pattern in patternsList)
|
||||
{
|
||||
if (cellData.roomDisplayname && [cellData.roomDisplayname rangeOfString:pattern options:NSCaseInsensitiveSearch].location != NSNotFound)
|
||||
{
|
||||
[filteredCellDataArray addObject:cellData];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
filteredCellDataArray = nil;
|
||||
searchPatternsList = nil;
|
||||
}
|
||||
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
|
||||
- (id<MXKRecentCellDataStoring>)cellDataAtIndex:(NSInteger)index
|
||||
{
|
||||
if (filteredCellDataArray)
|
||||
{
|
||||
if (index < filteredCellDataArray.count)
|
||||
{
|
||||
return filteredCellDataArray[index];
|
||||
}
|
||||
}
|
||||
else if (index < cellDataArray.count)
|
||||
{
|
||||
return cellDataArray[index];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGFloat)cellHeightAtIndex:(NSInteger)index
|
||||
{
|
||||
if (self.delegate)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> cellData = [self cellDataAtIndex:index];
|
||||
|
||||
Class<MXKCellRendering> class = [self.delegate cellViewClassForCellData:cellData];
|
||||
return [class heightForCellData:cellData withMaximumWidth:0];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#pragma mark - Events processing
|
||||
|
||||
/**
|
||||
Filtering in this method won't have any effect anymore. This class is not maintained.
|
||||
*/
|
||||
- (void)loadData
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXRoomSummaryDidChangeNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXKRoomDataSourceSyncStatusChanged object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionNewRoomNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidLeaveRoomNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDirectRoomsDidChangeNotification object:nil];
|
||||
|
||||
if (!self.mxSession.spaceService.isInitialised && !spaceServiceDidInitialiseObserver) {
|
||||
MXWeakify(self);
|
||||
spaceServiceDidInitialiseObserver = [[NSNotificationCenter defaultCenter] addObserverForName:MXSpaceService.didInitialise object:self.mxSession.spaceService queue:nil usingBlock:^(NSNotification * _Nonnull note) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
[self loadData];
|
||||
}];
|
||||
}
|
||||
|
||||
// Reset the table
|
||||
[internalCellDataArray removeAllObjects];
|
||||
|
||||
// Retrieve the MXKCellData class to manage the data
|
||||
Class class = [self cellDataClassForCellIdentifier:kMXKRecentCellIdentifier];
|
||||
NSAssert([class conformsToProtocol:@protocol(MXKRecentCellDataStoring)], @"MXKSessionRecentsDataSource only manages MXKCellData that conforms to MXKRecentCellDataStoring protocol");
|
||||
|
||||
NSDate *startDate = [NSDate date];
|
||||
|
||||
for (MXRoomSummary *roomSummary in self.mxSession.roomsSummaries)
|
||||
{
|
||||
// Filter out private rooms with conference users
|
||||
if (!roomSummary.isConferenceUserRoom // @TODO Abstract this condition with roomSummary.hiddenFromUser
|
||||
&& !roomSummary.hiddenFromUser)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> cellData = [[class alloc] initWithRoomSummary:roomSummary dataSource:self];
|
||||
if (cellData)
|
||||
{
|
||||
[internalCellDataArray addObject:cellData];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (MXSpaceChildInfo *childInfo in _suggestedRooms)
|
||||
{
|
||||
id<MXRoomSummaryProtocol> summary = [[MXRoomSummary alloc] initWithSpaceChildInfo:childInfo];
|
||||
id<MXKRecentCellDataStoring> cellData = [[class alloc] initWithRoomSummary:summary
|
||||
dataSource:self];
|
||||
if (cellData)
|
||||
{
|
||||
[internalCellDataArray addObject:cellData];
|
||||
}
|
||||
}
|
||||
|
||||
MXLogDebug(@"[MXKSessionRecentsDataSource] Loaded %tu recents in %.3fms", self.mxSession.rooms.count, [[NSDate date] timeIntervalSinceDate:startDate] * 1000);
|
||||
|
||||
// Make sure all rooms have a last message
|
||||
[self.mxSession fixRoomsSummariesLastMessage];
|
||||
|
||||
// Report loaded array except if sync is in progress
|
||||
if (!roomDataSourceManager.isServerSyncInProgress)
|
||||
{
|
||||
[self sortCellDataAndNotifyChanges];
|
||||
}
|
||||
|
||||
// Listen to MXSession rooms count changes
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionHaveNewRoom:) name:kMXSessionNewRoomNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionDidLeaveRoom:) name:kMXSessionDidLeaveRoomNotification object:nil];
|
||||
|
||||
// Listen to the direct rooms list
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didDirectRoomsChange:) name:kMXSessionDirectRoomsDidChangeNotification object:nil];
|
||||
|
||||
// Listen to MXRoomSummary
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didRoomSummaryChanged:) name:kMXRoomSummaryDidChangeNotification object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionStateChange) name:kMXKRoomDataSourceSyncStatusChanged object:nil];
|
||||
}
|
||||
|
||||
- (void)didDirectRoomsChange:(NSNotification *)notif
|
||||
{
|
||||
// Inform the delegate about the update
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
|
||||
- (void)didRoomSummaryChanged:(NSNotification *)notif
|
||||
{
|
||||
[roomSummaryChangeThrottler throttle:^{
|
||||
[self didRoomSummaryChanged2:notif];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)didRoomSummaryChanged2:(NSNotification *)notif
|
||||
{
|
||||
MXRoomSummary *roomSummary = notif.object;
|
||||
if (roomSummary.mxSession == self.mxSession && internalCellDataArray.count)
|
||||
{
|
||||
// Find the index of the related cell data
|
||||
NSInteger index = NSNotFound;
|
||||
for (index = 0; index < internalCellDataArray.count; index++)
|
||||
{
|
||||
id<MXKRecentCellDataStoring> theRoomData = [internalCellDataArray objectAtIndex:index];
|
||||
if (theRoomData.roomSummary == roomSummary)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index < internalCellDataArray.count)
|
||||
{
|
||||
if (roomSummary.hiddenFromUser)
|
||||
{
|
||||
[internalCellDataArray removeObjectAtIndex:index];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new instance to not modify the content of 'cellDataArray' (the copy is not a deep copy).
|
||||
Class class = [self cellDataClassForCellIdentifier:kMXKRecentCellIdentifier];
|
||||
id<MXKRecentCellDataStoring> cellData = [[class alloc] initWithRoomSummary:roomSummary dataSource:self];
|
||||
if (cellData)
|
||||
{
|
||||
[internalCellDataArray replaceObjectAtIndex:index withObject:cellData];
|
||||
}
|
||||
}
|
||||
|
||||
// Report change except if sync is in progress
|
||||
if (!roomDataSourceManager.isServerSyncInProgress)
|
||||
{
|
||||
[self sortCellDataAndNotifyChanges];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXKSessionRecentsDataSource] didRoomLastMessageChanged: Cannot find the changed room summary for %@ (%@). It is probably not managed by this recents data source", roomSummary.roomId, roomSummary);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Inform the delegate that all the room summaries have been updated.
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMXSessionHaveNewRoom:(NSNotification *)notif
|
||||
{
|
||||
MXSession *mxSession = notif.object;
|
||||
if (mxSession == self.mxSession)
|
||||
{
|
||||
NSString *roomId = notif.userInfo[kMXSessionNotificationRoomIdKey];
|
||||
|
||||
// Add the room if there is not yet a cell for it
|
||||
id<MXKRecentCellDataStoring> roomData = [self cellDataWithRoomId:roomId];
|
||||
if (nil == roomData)
|
||||
{
|
||||
MXLogDebug(@"MXKSessionRecentsDataSource] Add newly joined room: %@", roomId);
|
||||
|
||||
// Retrieve the MXKCellData class to manage the data
|
||||
Class class = [self cellDataClassForCellIdentifier:kMXKRecentCellIdentifier];
|
||||
|
||||
MXRoomSummary *roomSummary = [mxSession roomSummaryWithRoomId:roomId];
|
||||
id<MXKRecentCellDataStoring> cellData = [[class alloc] initWithRoomSummary:roomSummary dataSource:self];
|
||||
if (cellData)
|
||||
{
|
||||
[internalCellDataArray addObject:cellData];
|
||||
|
||||
// Report change except if sync is in progress
|
||||
if (!roomDataSourceManager.isServerSyncInProgress)
|
||||
{
|
||||
[self sortCellDataAndNotifyChanges];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMXSessionDidLeaveRoom:(NSNotification *)notif
|
||||
{
|
||||
MXSession *mxSession = notif.object;
|
||||
if (mxSession == self.mxSession)
|
||||
{
|
||||
NSString *roomId = notif.userInfo[kMXSessionNotificationRoomIdKey];
|
||||
id<MXKRecentCellDataStoring> roomData = [self cellDataWithRoomId:roomId];
|
||||
|
||||
if (roomData)
|
||||
{
|
||||
MXLogDebug(@"MXKSessionRecentsDataSource] Remove left room: %@", roomId);
|
||||
|
||||
[internalCellDataArray removeObject:roomData];
|
||||
|
||||
// Report change except if sync is in progress
|
||||
if (!roomDataSourceManager.isServerSyncInProgress)
|
||||
{
|
||||
[self sortCellDataAndNotifyChanges];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Order cells
|
||||
- (void)sortCellDataAndNotifyChanges
|
||||
{
|
||||
// Order them by origin_server_ts
|
||||
[internalCellDataArray sortUsingComparator:^NSComparisonResult(id<MXKRecentCellDataStoring> cellData1, id<MXKRecentCellDataStoring> cellData2)
|
||||
{
|
||||
return [cellData1.roomSummary.lastMessage compareOriginServerTs:cellData2.roomSummary.lastMessage];
|
||||
}];
|
||||
|
||||
// Snapshot the cell data array
|
||||
cellDataArray = [internalCellDataArray copy];
|
||||
|
||||
// Update search result if any
|
||||
if (searchPatternsList)
|
||||
{
|
||||
[self searchWithPatterns:searchPatternsList];
|
||||
}
|
||||
|
||||
// Update here data source state
|
||||
if (state != MXKDataSourceStateReady)
|
||||
{
|
||||
state = MXKDataSourceStateReady;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didStateChange:)])
|
||||
{
|
||||
[self.delegate dataSource:self didStateChange:state];
|
||||
}
|
||||
}
|
||||
|
||||
// And inform the delegate about the update
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
|
||||
// Find the cell data that stores information about the given room id
|
||||
- (id<MXKRecentCellDataStoring>)cellDataWithRoomId:(NSString*)roomId
|
||||
{
|
||||
id<MXKRecentCellDataStoring> theRoomData;
|
||||
|
||||
NSMutableArray *dataArray = internalCellDataArray;
|
||||
if (!roomDataSourceManager.isServerSyncInProgress)
|
||||
{
|
||||
dataArray = cellDataArray;
|
||||
}
|
||||
|
||||
for (id<MXKRecentCellDataStoring> roomData in dataArray)
|
||||
{
|
||||
if ([roomData.roomSummary.roomId isEqualToString:roomId])
|
||||
{
|
||||
theRoomData = roomData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return theRoomData;
|
||||
}
|
||||
|
||||
#pragma mark - KVO
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context
|
||||
{
|
||||
if (object == [MXKAppSettings standardAppSettings] && [keyPath isEqualToString:@"showAllRoomsInHomeSpace"])
|
||||
{
|
||||
if (self.currentSpace == nil)
|
||||
{
|
||||
[self loadData];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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>
|
||||
|
||||
#import "MXKRoomMemberCellDataStoring.h"
|
||||
|
||||
/**
|
||||
`MXKRoomMemberCellData` modelised the data for a `MXKRoomMemberTableViewCell` cell.
|
||||
*/
|
||||
@interface MXKRoomMemberCellData : MXKCellData <MXKRoomMemberCellDataStoring>
|
||||
|
||||
/**
|
||||
The matrix session
|
||||
*/
|
||||
@property (nonatomic, readonly) MXSession *mxSession;
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 "MXKRoomMemberCellData.h"
|
||||
|
||||
#import "MXKRoomMemberListDataSource.h"
|
||||
|
||||
@interface MXKRoomMemberCellData ()
|
||||
{
|
||||
MXKRoomMemberListDataSource *roomMemberListDataSource;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKRoomMemberCellData
|
||||
@synthesize roomMember;
|
||||
@synthesize memberDisplayName, powerLevel, isTyping;
|
||||
|
||||
- (instancetype)initWithRoomMember:(MXRoomMember*)member roomState:(MXRoomState*)roomState andRoomMemberListDataSource:(MXKRoomMemberListDataSource*)memberListDataSource
|
||||
{
|
||||
self = [self init];
|
||||
if (self)
|
||||
{
|
||||
roomMember = member;
|
||||
roomMemberListDataSource = memberListDataSource;
|
||||
|
||||
// Report member info from the current room state
|
||||
memberDisplayName = [roomState.members memberName:roomMember.userId];
|
||||
powerLevel = [roomState memberNormalizedPowerLevel:roomMember.userId];
|
||||
isTyping = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateWithRoomState:(MXRoomState*)roomState
|
||||
{
|
||||
memberDisplayName = [roomState.members memberName:roomMember.userId];
|
||||
powerLevel = [roomState memberNormalizedPowerLevel:roomMember.userId];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
roomMember = nil;
|
||||
roomMemberListDataSource = nil;
|
||||
}
|
||||
|
||||
- (MXSession*)mxSession
|
||||
{
|
||||
return roomMemberListDataSource.mxSession;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
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>
|
||||
|
||||
#import "MXKCellData.h"
|
||||
|
||||
@class MXKRoomMemberListDataSource;
|
||||
|
||||
/**
|
||||
`MXKRoomMemberCellDataStoring` defines a protocol a class must conform in order to store room member cell data
|
||||
managed by `MXKRoomMemberListDataSource`.
|
||||
*/
|
||||
@protocol MXKRoomMemberCellDataStoring <NSObject>
|
||||
|
||||
|
||||
#pragma mark - Data displayed by a room member cell
|
||||
|
||||
/**
|
||||
The member displayed by the cell.
|
||||
*/
|
||||
@property (nonatomic, readonly) MXRoomMember *roomMember;
|
||||
|
||||
/**
|
||||
The member display name
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *memberDisplayName;
|
||||
|
||||
/**
|
||||
The member power level
|
||||
*/
|
||||
@property (nonatomic, readonly) CGFloat powerLevel;
|
||||
|
||||
/**
|
||||
YES when member is typing in the room
|
||||
*/
|
||||
@property (nonatomic) BOOL isTyping;
|
||||
|
||||
#pragma mark - Public methods
|
||||
/**
|
||||
Create a new `MXKCellData` object for a new member cell.
|
||||
|
||||
@param memberListDataSource the `MXKRoomMemberListDataSource` object that will use this instance.
|
||||
@return the newly created instance.
|
||||
*/
|
||||
- (instancetype)initWithRoomMember:(MXRoomMember*)member roomState:(MXRoomState*)roomState andRoomMemberListDataSource:(MXKRoomMemberListDataSource*)memberListDataSource;
|
||||
|
||||
/**
|
||||
Update the member data with the provided roon state.
|
||||
*/
|
||||
- (void)updateWithRoomState:(MXRoomState*)roomState;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
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 <UIKit/UIKit.h>
|
||||
#import <MatrixSDK/MatrixSDK.h>
|
||||
|
||||
#import "MXKDataSource.h"
|
||||
#import "MXKRoomMemberCellData.h"
|
||||
|
||||
#import "MXKAppSettings.h"
|
||||
|
||||
/**
|
||||
Identifier to use for cells that display a room member.
|
||||
*/
|
||||
extern NSString *const kMXKRoomMemberCellIdentifier;
|
||||
|
||||
/**
|
||||
The data source for `MXKRoomMemberListViewController`.
|
||||
*/
|
||||
@interface MXKRoomMemberListDataSource : MXKDataSource <UITableViewDataSource> {
|
||||
|
||||
@protected
|
||||
|
||||
/**
|
||||
The data for the cells served by `MXKRoomMembersDataSource`.
|
||||
*/
|
||||
NSMutableArray *cellDataArray;
|
||||
|
||||
/**
|
||||
The filtered members: sub-list of `cellDataArray` defined by `searchWithPatterns:`.
|
||||
*/
|
||||
NSMutableArray *filteredCellDataArray;
|
||||
}
|
||||
|
||||
/**
|
||||
The id of the room managed by the data source.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *roomId;
|
||||
|
||||
/**
|
||||
The settings used to sort/display room members.
|
||||
|
||||
By default the shared application settings are considered.
|
||||
*/
|
||||
@property (nonatomic) MXKAppSettings *settings;
|
||||
|
||||
|
||||
#pragma mark - Life cycle
|
||||
|
||||
/**
|
||||
Initialise the data source to serve members corresponding to the passed room.
|
||||
|
||||
@param roomId the id of the room to get members from.
|
||||
@param mxSession the Matrix session to get data from.
|
||||
@return the newly created instance.
|
||||
*/
|
||||
- (instancetype)initWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
/**
|
||||
Filter the current members list according to the provided patterns.
|
||||
When patterns are not empty, the search result is stored in `filteredCellDataArray`,
|
||||
this array provides then data for the cells served by `MXKRoomMembersDataSource`.
|
||||
|
||||
@param patternsList the list of patterns (`NSString` instances) to match with. Set nil to cancel search.
|
||||
*/
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList;
|
||||
|
||||
/**
|
||||
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<MXKRoomMemberCellDataStoring>)cellDataAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
Get height of the celle at the given index.
|
||||
|
||||
@param index the index of the cell in the array
|
||||
@return the cell height
|
||||
*/
|
||||
- (CGFloat)cellHeightAtIndex:(NSInteger)index;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,464 @@
|
||||
/*
|
||||
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 "MXKRoomMemberListDataSource.h"
|
||||
|
||||
@import MatrixSDK.MXCallManager;
|
||||
|
||||
#import "MXKRoomMemberCellData.h"
|
||||
|
||||
|
||||
#pragma mark - Constant definitions
|
||||
NSString *const kMXKRoomMemberCellIdentifier = @"kMXKRoomMemberCellIdentifier";
|
||||
|
||||
|
||||
@interface MXKRoomMemberListDataSource ()
|
||||
{
|
||||
/**
|
||||
The room in which members are listed.
|
||||
*/
|
||||
MXRoom *mxRoom;
|
||||
|
||||
/**
|
||||
Cache for loaded room state.
|
||||
*/
|
||||
MXRoomState *mxRoomState;
|
||||
|
||||
/**
|
||||
The members events listener.
|
||||
*/
|
||||
id membersListener;
|
||||
|
||||
/**
|
||||
The typing notification listener in the room.
|
||||
*/
|
||||
id typingNotifListener;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKRoomMemberListDataSource
|
||||
|
||||
- (instancetype)initWithRoomId:(NSString*)roomId andMatrixSession:(MXSession*)mxSession
|
||||
{
|
||||
self = [super initWithMatrixSession:mxSession];
|
||||
if (self)
|
||||
{
|
||||
_roomId = roomId;
|
||||
|
||||
cellDataArray = [NSMutableArray array];
|
||||
filteredCellDataArray = nil;
|
||||
|
||||
// Consider the shared app settings by default
|
||||
_settings = [MXKAppSettings standardAppSettings];
|
||||
|
||||
// Set default data class
|
||||
[self registerCellDataClass:MXKRoomMemberCellData.class forCellIdentifier:kMXKRoomMemberCellIdentifier];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
cellDataArray = nil;
|
||||
filteredCellDataArray = nil;
|
||||
|
||||
if (membersListener)
|
||||
{
|
||||
[self.mxSession removeListener:membersListener];
|
||||
membersListener = nil;
|
||||
}
|
||||
|
||||
if (typingNotifListener)
|
||||
{
|
||||
MXWeakify(self);
|
||||
[mxRoom liveTimeline:^(MXEventTimeline *liveTimeline) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
[liveTimeline removeListener:self->typingNotifListener];
|
||||
self->typingNotifListener = nil;
|
||||
}];
|
||||
}
|
||||
|
||||
[super destroy];
|
||||
}
|
||||
|
||||
- (void)didMXSessionStateChange
|
||||
{
|
||||
if (MXSessionStateStoreDataReady <= self.mxSession.state)
|
||||
{
|
||||
// Check whether the room is not already set
|
||||
if (!mxRoom)
|
||||
{
|
||||
mxRoom = [self.mxSession roomWithRoomId:_roomId];
|
||||
if (mxRoom)
|
||||
{
|
||||
MXWeakify(self);
|
||||
[mxRoom state:^(MXRoomState *roomState) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
self->mxRoomState = roomState;
|
||||
|
||||
[self loadData];
|
||||
|
||||
// Register on typing notif
|
||||
[self listenTypingNotifications];
|
||||
|
||||
// Register on members events
|
||||
[self listenMembersEvents];
|
||||
|
||||
// Update here data source state
|
||||
self->state = MXKDataSourceStateReady;
|
||||
|
||||
// Notify delegate
|
||||
if (self.delegate)
|
||||
{
|
||||
if ([self.delegate respondsToSelector:@selector(dataSource:didStateChange:)])
|
||||
{
|
||||
[self.delegate dataSource:self didStateChange:self->state];
|
||||
}
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
MXLogDebug(@"[MXKRoomMemberDataSource] The user does not know the room %@", _roomId);
|
||||
|
||||
// Update here data source state
|
||||
state = MXKDataSourceStateFailed;
|
||||
|
||||
// Notify delegate
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dataSource:didStateChange:)])
|
||||
{
|
||||
[self.delegate dataSource:self didStateChange:state];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)searchWithPatterns:(NSArray*)patternsList
|
||||
{
|
||||
if (patternsList.count)
|
||||
{
|
||||
if (filteredCellDataArray)
|
||||
{
|
||||
[filteredCellDataArray removeAllObjects];
|
||||
}
|
||||
else
|
||||
{
|
||||
filteredCellDataArray = [NSMutableArray arrayWithCapacity:cellDataArray.count];
|
||||
}
|
||||
|
||||
for (id<MXKRoomMemberCellDataStoring> cellData in cellDataArray)
|
||||
{
|
||||
for (NSString* pattern in patternsList)
|
||||
{
|
||||
if ([[cellData memberDisplayName] rangeOfString:pattern options:NSCaseInsensitiveSearch].location != NSNotFound)
|
||||
{
|
||||
[filteredCellDataArray addObject:cellData];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
filteredCellDataArray = nil;
|
||||
}
|
||||
|
||||
if (self.delegate)
|
||||
{
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (id<MXKRoomMemberCellDataStoring>)cellDataAtIndex:(NSInteger)index
|
||||
{
|
||||
if (filteredCellDataArray)
|
||||
{
|
||||
return filteredCellDataArray[index];
|
||||
}
|
||||
return cellDataArray[index];
|
||||
}
|
||||
|
||||
- (CGFloat)cellHeightAtIndex:(NSInteger)index
|
||||
{
|
||||
if (self.delegate)
|
||||
{
|
||||
id<MXKRoomMemberCellDataStoring> cellData = [self cellDataAtIndex:index];
|
||||
|
||||
Class<MXKCellRendering> class = [self.delegate cellViewClassForCellData:cellData];
|
||||
return [class heightForCellData:cellData withMaximumWidth:0];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#pragma mark - Members processing
|
||||
|
||||
- (void)loadData
|
||||
{
|
||||
NSArray* membersList = [mxRoomState.members membersWithoutConferenceUser];
|
||||
|
||||
if (!_settings.showLeftMembersInRoomMemberList)
|
||||
{
|
||||
NSMutableArray* filteredMembers = [[NSMutableArray alloc] init];
|
||||
|
||||
for (MXRoomMember* member in membersList)
|
||||
{
|
||||
// Filter out left users
|
||||
if (member.membership != MXMembershipLeave)
|
||||
{
|
||||
[filteredMembers addObject:member];
|
||||
}
|
||||
}
|
||||
|
||||
membersList = filteredMembers;
|
||||
}
|
||||
|
||||
[cellDataArray removeAllObjects];
|
||||
|
||||
// Retrieve the MXKCellData class to manage the data
|
||||
Class class = [self cellDataClassForCellIdentifier:kMXKRoomMemberCellIdentifier];
|
||||
NSAssert([class conformsToProtocol:@protocol(MXKRoomMemberCellDataStoring)], @"MXKRoomMemberListDataSource only manages MXKCellData that conforms to MXKRoomMemberCellDataStoring protocol");
|
||||
|
||||
for (MXRoomMember *member in membersList)
|
||||
{
|
||||
|
||||
id<MXKRoomMemberCellDataStoring> cellData = [[class alloc] initWithRoomMember:member roomState:mxRoomState andRoomMemberListDataSource:self];
|
||||
if (cellData)
|
||||
{
|
||||
[cellDataArray addObject:cellData];
|
||||
}
|
||||
}
|
||||
|
||||
[self sortMembers];
|
||||
}
|
||||
|
||||
- (void)sortMembers
|
||||
{
|
||||
NSArray *sortedMembers = [cellDataArray sortedArrayUsingComparator:^NSComparisonResult(id<MXKRoomMemberCellDataStoring> member1, id<MXKRoomMemberCellDataStoring> member2)
|
||||
{
|
||||
|
||||
// Move banned and left members at the end of the list
|
||||
if (member1.roomMember.membership == MXMembershipLeave || member1.roomMember.membership == MXMembershipBan)
|
||||
{
|
||||
if (member2.roomMember.membership != MXMembershipLeave && member2.roomMember.membership != MXMembershipBan)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
}
|
||||
else if (member2.roomMember.membership == MXMembershipLeave || member2.roomMember.membership == MXMembershipBan)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
|
||||
// Move invited members just before left and banned members
|
||||
if (member1.roomMember.membership == MXMembershipInvite)
|
||||
{
|
||||
if (member2.roomMember.membership != MXMembershipInvite)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
}
|
||||
else if (member2.roomMember.membership == MXMembershipInvite)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
|
||||
if (self->_settings.sortRoomMembersUsingLastSeenTime)
|
||||
{
|
||||
// Get the users that correspond to these members
|
||||
MXUser *user1 = [self.mxSession userWithUserId:member1.roomMember.userId];
|
||||
MXUser *user2 = [self.mxSession userWithUserId:member2.roomMember.userId];
|
||||
|
||||
// Move users who are not online or unavailable at the end (before invited users)
|
||||
if ((user1.presence == MXPresenceOnline) || (user1.presence == MXPresenceUnavailable))
|
||||
{
|
||||
if ((user2.presence != MXPresenceOnline) && (user2.presence != MXPresenceUnavailable))
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
}
|
||||
else if ((user2.presence == MXPresenceOnline) || (user2.presence == MXPresenceUnavailable))
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Here both users are neither online nor unavailable (the lastActive ago is useless)
|
||||
// We will sort them according to their display, by keeping in front the offline users
|
||||
if (user1.presence == MXPresenceOffline)
|
||||
{
|
||||
if (user2.presence != MXPresenceOffline)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
}
|
||||
else if (user2.presence == MXPresenceOffline)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
return [[self->mxRoomState.members memberSortedName:member1.roomMember.userId] compare:[self->mxRoomState.members memberSortedName:member2.roomMember.userId] options:NSCaseInsensitiveSearch];
|
||||
}
|
||||
|
||||
// Consider user's lastActive ago value
|
||||
if (user1.lastActiveAgo < user2.lastActiveAgo)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
else if (user1.lastActiveAgo == user2.lastActiveAgo)
|
||||
{
|
||||
return [[self->mxRoomState.members memberSortedName:member1.roomMember.userId] compare:[self->mxRoomState.members memberSortedName:member2.roomMember.userId] options:NSCaseInsensitiveSearch];
|
||||
}
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move user without display name at the end (before invited users)
|
||||
if (member1.roomMember.displayname.length)
|
||||
{
|
||||
if (!member2.roomMember.displayname.length)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
}
|
||||
else if (member2.roomMember.displayname.length)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
|
||||
return [[self->mxRoomState.members memberSortedName:member1.roomMember.userId] compare:[self->mxRoomState.members memberSortedName:member2.roomMember.userId] options:NSCaseInsensitiveSearch];
|
||||
}
|
||||
}];
|
||||
|
||||
cellDataArray = [NSMutableArray arrayWithArray:sortedMembers];
|
||||
}
|
||||
|
||||
- (void)listenMembersEvents
|
||||
{
|
||||
// Remove the previous live listener
|
||||
if (membersListener)
|
||||
{
|
||||
[self.mxSession removeListener:membersListener];
|
||||
}
|
||||
|
||||
// Register a listener for events that concern room members
|
||||
NSArray *mxMembersEvents = @[
|
||||
kMXEventTypeStringRoomMember,
|
||||
kMXEventTypeStringRoomPowerLevels,
|
||||
kMXEventTypeStringPresence
|
||||
];
|
||||
membersListener = [self.mxSession listenToEventsOfTypes:mxMembersEvents onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject)
|
||||
{
|
||||
// consider only live event
|
||||
if (direction == MXTimelineDirectionForwards)
|
||||
{
|
||||
// Check the room Id (if any)
|
||||
if (event.roomId && [event.roomId isEqualToString:self->mxRoom.roomId] == NO)
|
||||
{
|
||||
// This event does not concern the current room members
|
||||
return;
|
||||
}
|
||||
|
||||
// refresh the whole members list. TODO GFO refresh only the updated members.
|
||||
[self loadData];
|
||||
|
||||
if (self.delegate)
|
||||
{
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)listenTypingNotifications
|
||||
{
|
||||
// Remove the previous live listener
|
||||
if (self->typingNotifListener)
|
||||
{
|
||||
[mxRoom removeListener:self->typingNotifListener];
|
||||
}
|
||||
|
||||
// Add typing notification listener
|
||||
self->typingNotifListener = [mxRoom listenToEventsOfTypes:@[kMXEventTypeStringTypingNotification] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) {
|
||||
// Handle only live events
|
||||
if (direction == MXTimelineDirectionForwards)
|
||||
{
|
||||
// Retrieve typing users list
|
||||
NSMutableArray *typingUsers = [NSMutableArray arrayWithArray:self->mxRoom.typingUsers];
|
||||
// Remove typing info for the current user
|
||||
NSUInteger index = [typingUsers indexOfObject:self.mxSession.myUser.userId];
|
||||
if (index != NSNotFound)
|
||||
{
|
||||
[typingUsers removeObjectAtIndex:index];
|
||||
}
|
||||
|
||||
for (id<MXKRoomMemberCellDataStoring> cellData in self->cellDataArray)
|
||||
{
|
||||
if ([typingUsers indexOfObject:cellData.roomMember.userId] == NSNotFound)
|
||||
{
|
||||
cellData.isTyping = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellData.isTyping = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.delegate)
|
||||
{
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
if (filteredCellDataArray)
|
||||
{
|
||||
return filteredCellDataArray.count;
|
||||
}
|
||||
return cellDataArray.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
id<MXKRoomMemberCellDataStoring> roomData = [self cellDataAtIndex:indexPath.row];
|
||||
|
||||
if (roomData && self.delegate)
|
||||
{
|
||||
NSString *identifier = [self.delegate cellReuseIdentifierForCellData:roomData];
|
||||
if (identifier)
|
||||
{
|
||||
UITableViewCell<MXKCellRendering> *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
|
||||
|
||||
// Make the bubble display the data
|
||||
[cell render:roomData];
|
||||
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a fake cell to prevent app from crashing.
|
||||
return [[UITableViewCell alloc] init];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
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 "MXKCellData.h"
|
||||
#import "MXKSearchCellDataStoring.h"
|
||||
|
||||
/**
|
||||
`MXKSearchCellData` modelised the data for a `MXKSearchCell` cell.
|
||||
*/
|
||||
@interface MXKSearchCellData : MXKCellData <MXKSearchCellDataStoring>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
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 "MXKSearchCellData.h"
|
||||
|
||||
#import "MXKSearchDataSource.h"
|
||||
|
||||
|
||||
@implementation MXKSearchCellData
|
||||
@synthesize roomId, senderDisplayName;
|
||||
@synthesize searchResult, title, message, date, shouldShowRoomDisplayName, roomDisplayName, attachment, isAttachmentWithThumbnail, attachmentIcon;
|
||||
|
||||
- (instancetype)initWithSearchResult:(MXSearchResult *)searchResult2 andSearchDataSource:(MXKSearchDataSource *)searchDataSource
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
searchResult = searchResult2;
|
||||
|
||||
if (searchDataSource.roomEventFilter.rooms.count == 1)
|
||||
{
|
||||
// We are displaying a search within a room
|
||||
// As title, display the user id
|
||||
title = searchResult.result.sender;
|
||||
|
||||
roomId = searchDataSource.roomEventFilter.rooms[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are displaying a search over all user's rooms
|
||||
// As title, display the room name of this search result
|
||||
MXRoom *room = [searchDataSource.mxSession roomWithRoomId:searchResult.result.roomId];
|
||||
if (room)
|
||||
{
|
||||
title = room.summary.displayname;
|
||||
}
|
||||
else
|
||||
{
|
||||
title = searchResult.result.roomId;
|
||||
}
|
||||
}
|
||||
|
||||
date = [searchDataSource.eventFormatter dateStringFromEvent:searchResult.result withTime:YES];
|
||||
|
||||
// Code from [MXEventFormatter stringFromEvent] for the particular case of a text message
|
||||
message = [searchResult.result.content[@"body"] isKindOfClass:[NSString class]] ? searchResult.result.content[@"body"] : nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (void)cellDataWithSearchResult:(MXSearchResult *)searchResult andSearchDataSource:(MXKSearchDataSource *)searchDataSource onComplete:(void (^)(id<MXKSearchCellDataStoring>))onComplete
|
||||
{
|
||||
onComplete([[self alloc] initWithSearchResult:searchResult andSearchDataSource:searchDataSource]);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
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>
|
||||
|
||||
#import "MXKAttachment.h"
|
||||
|
||||
@class MXKSearchDataSource;
|
||||
|
||||
/**
|
||||
`MXKSearchCellDataStoring` defines a protocol a class must conform in order to store
|
||||
a search result in a cell data managed by `MXKSearchDataSource`.
|
||||
*/
|
||||
@protocol MXKSearchCellDataStoring <NSObject>
|
||||
|
||||
/**
|
||||
The room id
|
||||
*/
|
||||
@property (nonatomic) NSString *roomId;
|
||||
|
||||
@property (nonatomic, readonly) NSString *title;
|
||||
@property (nonatomic, readonly) NSString *message;
|
||||
@property (nonatomic, readonly) NSString *date;
|
||||
|
||||
// Bulk result returned by MatrixSDK
|
||||
@property (nonatomic, readonly) MXSearchResult *searchResult;
|
||||
|
||||
/**
|
||||
Tell whether the room display name should be displayed in the cell. NO by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldShowRoomDisplayName;
|
||||
|
||||
/**
|
||||
The room display name.
|
||||
*/
|
||||
@property (nonatomic) NSString *roomDisplayName;
|
||||
|
||||
/**
|
||||
The sender display name.
|
||||
*/
|
||||
@property (nonatomic) NSString *senderDisplayName;
|
||||
|
||||
/**
|
||||
The bubble attachment (if any).
|
||||
*/
|
||||
@property (nonatomic) MXKAttachment *attachment;
|
||||
|
||||
/**
|
||||
YES when the bubble correspond to an attachment displayed with a thumbnail (see image, video).
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL isAttachmentWithThumbnail;
|
||||
|
||||
/**
|
||||
The default icon relative to the attachment (if any).
|
||||
*/
|
||||
@property (nonatomic, readonly) UIImage* attachmentIcon;
|
||||
|
||||
|
||||
#pragma mark - Public methods
|
||||
/**
|
||||
Create a new `MXKCellData` object for a new search result cell.
|
||||
|
||||
@param searchResult Bulk result returned by MatrixSDK.
|
||||
@param searchDataSource the `MXKSearchDataSource` object that will use this instance.
|
||||
@param onComplete a block providing the newly created instance.
|
||||
*/
|
||||
+ (void)cellDataWithSearchResult:(MXSearchResult*)searchResult andSearchDataSource:(MXKSearchDataSource*)searchDataSource onComplete:(void (^)(id<MXKSearchCellDataStoring> cellData))onComplete;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
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 "MXKDataSource.h"
|
||||
#import "MXKSearchCellDataStoring.h"
|
||||
|
||||
#import "MXKEventFormatter.h"
|
||||
|
||||
/**
|
||||
String identifying the object used to store and prepare the cell data of a result during a message search.
|
||||
*/
|
||||
extern NSString *const kMXKSearchCellDataIdentifier;
|
||||
|
||||
/**
|
||||
The data source for `MXKSearchViewController` in case of message search.
|
||||
|
||||
Use the `initWithMatrixSession:` constructor to search in all user's rooms.
|
||||
Use the `initWithRoomId:andMatrixSession: constructor to search in a specific room.
|
||||
*/
|
||||
@interface MXKSearchDataSource : MXKDataSource <UITableViewDataSource>
|
||||
{
|
||||
@protected
|
||||
/**
|
||||
List of results retrieved from the server.
|
||||
The` MXKSearchDataSource` class stores MXKSearchCellDataStoring objects in it.
|
||||
*/
|
||||
NSMutableArray<MXKCellData*> *cellDataArray;
|
||||
}
|
||||
|
||||
/**
|
||||
The current search.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSString *searchText;
|
||||
|
||||
/**
|
||||
The room events filter which is applied during the messages search.
|
||||
*/
|
||||
@property (nonatomic) MXRoomEventFilter *roomEventFilter;
|
||||
|
||||
/**
|
||||
Total number of results available on the server.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger serverCount;
|
||||
|
||||
/**
|
||||
The events to display texts formatter.
|
||||
`MXKCellData` instances can use it to format text.
|
||||
*/
|
||||
@property (nonatomic) MXKEventFormatter *eventFormatter;
|
||||
|
||||
/**
|
||||
Flag indicating if there are still results (in the past) to get with paginateBack.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL canPaginate;
|
||||
|
||||
/**
|
||||
Tell whether the room display name should be displayed in each result cell. NO by default.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldShowRoomDisplayName;
|
||||
|
||||
|
||||
/**
|
||||
Launch a message search homeserver side.
|
||||
|
||||
@discussion The result depends on the 'roomEventFilter' propertie.
|
||||
|
||||
@param textPattern the text to search in messages data.
|
||||
@param force tell whether the search must be launched even if the text pattern is unchanged.
|
||||
*/
|
||||
- (void)searchMessages:(NSString*)textPattern force:(BOOL)force;
|
||||
|
||||
/**
|
||||
Load more results from the past.
|
||||
*/
|
||||
- (void)paginateBack;
|
||||
|
||||
/**
|
||||
Get the data for the cell at the given index.
|
||||
|
||||
@param index the index of the cell in the array
|
||||
@return the cell data
|
||||
*/
|
||||
- (MXKCellData*)cellDataAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
Convert the results of a homeserver search requests into cells.
|
||||
|
||||
This methods is in charge of filling `cellDataArray`.
|
||||
|
||||
@param roomEventResults the homeserver response as provided by MatrixSDK.
|
||||
@param onComplete the block called once complete.
|
||||
*/
|
||||
- (void)convertHomeserverResultsIntoCells:(MXSearchRoomEventResults*)roomEventResults onComplete:(dispatch_block_t)onComplete;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
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 "MXKSearchDataSource.h"
|
||||
|
||||
#import "MXKSearchCellData.h"
|
||||
|
||||
#pragma mark - Constant definitions
|
||||
NSString *const kMXKSearchCellDataIdentifier = @"kMXKSearchCellDataIdentifier";
|
||||
|
||||
|
||||
@interface MXKSearchDataSource ()
|
||||
{
|
||||
/**
|
||||
The current search request.
|
||||
*/
|
||||
MXHTTPOperation *searchRequest;
|
||||
|
||||
/**
|
||||
Token that can be used to get the next batch of results in the group, if exists.
|
||||
*/
|
||||
NSString *nextBatch;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MXKSearchDataSource
|
||||
|
||||
- (instancetype)initWithMatrixSession:(MXSession *)mxSession
|
||||
{
|
||||
self = [super initWithMatrixSession:mxSession];
|
||||
if (self)
|
||||
{
|
||||
// Set default data and view classes
|
||||
// Cell data
|
||||
[self registerCellDataClass:MXKSearchCellData.class forCellIdentifier:kMXKSearchCellDataIdentifier];
|
||||
|
||||
// Set default MXEvent -> NSString formatter
|
||||
_eventFormatter = [[MXKEventFormatter alloc] initWithMatrixSession:mxSession];
|
||||
|
||||
_roomEventFilter = [[MXRoomEventFilter alloc] init];
|
||||
|
||||
cellDataArray = [NSMutableArray array];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)destroy
|
||||
{
|
||||
cellDataArray = nil;
|
||||
_eventFormatter = nil;
|
||||
|
||||
_roomEventFilter = nil;
|
||||
|
||||
[super destroy];
|
||||
}
|
||||
|
||||
- (void)searchMessages:(NSString*)textPattern force:(BOOL)force
|
||||
{
|
||||
if (force || ![_searchText isEqualToString:textPattern])
|
||||
{
|
||||
// Reset data before making the new search
|
||||
if (searchRequest)
|
||||
{
|
||||
[searchRequest cancel];
|
||||
searchRequest = nil;
|
||||
}
|
||||
|
||||
_searchText = textPattern;
|
||||
_serverCount = 0;
|
||||
_canPaginate = NO;
|
||||
nextBatch = nil;
|
||||
|
||||
self.state = MXKDataSourceStatePreparing;
|
||||
[cellDataArray removeAllObjects];
|
||||
|
||||
if (textPattern.length)
|
||||
{
|
||||
MXLogDebug(@"[MXKSearchDataSource] searchMessages: %@", textPattern);
|
||||
[self doSearch];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Refresh table display.
|
||||
self.state = MXKDataSourceStateReady;
|
||||
[self.delegate dataSource:self didCellChange:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)paginateBack
|
||||
{
|
||||
MXLogDebug(@"[MXKSearchDataSource] paginateBack");
|
||||
|
||||
self.state = MXKDataSourceStatePreparing;
|
||||
[self doSearch];
|
||||
}
|
||||
|
||||
- (MXKCellData*)cellDataAtIndex:(NSInteger)index
|
||||
{
|
||||
MXKCellData *cellData;
|
||||
if (index < cellDataArray.count)
|
||||
{
|
||||
cellData = cellDataArray[index];
|
||||
}
|
||||
|
||||
return cellData;
|
||||
}
|
||||
|
||||
- (void)convertHomeserverResultsIntoCells:(MXSearchRoomEventResults*)roomEventResults onComplete:(dispatch_block_t)onComplete
|
||||
{
|
||||
// Retrieve the MXKCellData class to manage the data
|
||||
// Note: MXKSearchDataSource only manages MXKCellData that conforms to MXKSearchCellDataStoring protocol
|
||||
// see `[registerCellDataClass:forCellIdentifier:]`
|
||||
Class class = [self cellDataClassForCellIdentifier:kMXKSearchCellDataIdentifier];
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
|
||||
for (MXSearchResult *result in roomEventResults.results)
|
||||
{
|
||||
dispatch_group_enter(group);
|
||||
[class cellDataWithSearchResult:result andSearchDataSource:self onComplete:^(__autoreleasing id<MXKSearchCellDataStoring> cellData) {
|
||||
dispatch_group_leave(group);
|
||||
|
||||
if (cellData)
|
||||
{
|
||||
((id<MXKSearchCellDataStoring>)cellData).shouldShowRoomDisplayName = self.shouldShowRoomDisplayName;
|
||||
|
||||
// Use profile information as data to display
|
||||
MXSearchUserProfile *userProfile = result.context.profileInfo[result.result.sender];
|
||||
cellData.senderDisplayName = userProfile.displayName;
|
||||
|
||||
[self->cellDataArray insertObject:cellData atIndex:0];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
||||
onComplete();
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Private methods
|
||||
|
||||
// Update the MXKDataSource and notify the delegate
|
||||
- (void)setState:(MXKDataSourceState)newState
|
||||
{
|
||||
state = newState;
|
||||
|
||||
if (self.delegate)
|
||||
{
|
||||
if ([self.delegate respondsToSelector:@selector(dataSource:didStateChange:)])
|
||||
{
|
||||
[self.delegate dataSource:self didStateChange:state];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)doSearch
|
||||
{
|
||||
// Handle one request at a time
|
||||
if (searchRequest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NSDate *startDate = [NSDate date];
|
||||
|
||||
MXWeakify(self);
|
||||
searchRequest = [self.mxSession.matrixRestClient searchMessagesWithText:_searchText roomEventFilter:_roomEventFilter beforeLimit:0 afterLimit:0 nextBatch:nextBatch success:^(MXSearchRoomEventResults *roomEventResults) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
MXLogDebug(@"[MXKSearchDataSource] searchMessages: %@ (%d). Done in %.3fms - Got %tu / %tu messages", self.searchText, self.roomEventFilter.containsURL, [[NSDate date] timeIntervalSinceDate:startDate] * 1000, roomEventResults.results.count, roomEventResults.count);
|
||||
|
||||
self->searchRequest = nil;
|
||||
self->_serverCount = roomEventResults.count;
|
||||
self->nextBatch = roomEventResults.nextBatch;
|
||||
self->_canPaginate = (nil != self->nextBatch);
|
||||
|
||||
// Process HS response to cells data
|
||||
MXWeakify(self);
|
||||
[self convertHomeserverResultsIntoCells:roomEventResults onComplete:^{
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
self.state = MXKDataSourceStateReady;
|
||||
|
||||
// Provide changes information to the delegate
|
||||
NSIndexSet *insertedIndexes;
|
||||
if (roomEventResults.results.count)
|
||||
{
|
||||
insertedIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, roomEventResults.results.count)];
|
||||
}
|
||||
|
||||
[self.delegate dataSource:self didCellChange:insertedIndexes];
|
||||
}];
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
|
||||
self->searchRequest = nil;
|
||||
self.state = MXKDataSourceStateFailed;
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Override MXKDataSource
|
||||
|
||||
- (void)registerCellDataClass:(Class)cellDataClass forCellIdentifier:(NSString *)identifier
|
||||
{
|
||||
if ([identifier isEqualToString:kMXKSearchCellDataIdentifier])
|
||||
{
|
||||
// Sanity check
|
||||
NSAssert([cellDataClass conformsToProtocol:@protocol(MXKSearchCellDataStoring)], @"MXKSearchDataSource only manages MXKCellData that conforms to MXKSearchCellDataStoring protocol");
|
||||
}
|
||||
|
||||
[super registerCellDataClass:cellDataClass forCellIdentifier:identifier];
|
||||
}
|
||||
|
||||
- (void)cancelAllRequests
|
||||
{
|
||||
if (searchRequest)
|
||||
{
|
||||
[searchRequest cancel];
|
||||
searchRequest = nil;
|
||||
}
|
||||
|
||||
[super cancelAllRequests];
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return cellDataArray.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
MXKCellData* cellData = [self cellDataAtIndex:indexPath.row];
|
||||
|
||||
NSString *cellIdentifier = [self.delegate cellReuseIdentifierForCellData:cellData];
|
||||
if (cellIdentifier)
|
||||
{
|
||||
UITableViewCell<MXKCellRendering> *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
|
||||
|
||||
// Make the bubble display the data
|
||||
[cell render:cellData];
|
||||
|
||||
// Disable any interactions defined in the cell
|
||||
// because we want [tableView didSelectRowAtIndexPath:] to be called
|
||||
cell.contentView.userInteractionEnabled = NO;
|
||||
|
||||
// Force background color change on selection
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleDefault;
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
// Return a fake cell to prevent app from crashing.
|
||||
return [[UITableViewCell alloc] init];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user