mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-29 07:42:40 +00:00
Merge pull request #1449 from vector-im/jitsi_widget
Integrating Jitsi into mobile apps
This commit is contained in:
commit
c21c40031a
45 changed files with 112200 additions and 78 deletions
|
@ -17,6 +17,12 @@
|
|||
32185B311F20FA2B00752141 /* LanguagePickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32185B301F20FA2B00752141 /* LanguagePickerViewController.m */; };
|
||||
322806A01F0F64C4008C53D7 /* RoomMembershipExpandedBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 3228069E1F0F64C4008C53D7 /* RoomMembershipExpandedBubbleCell.m */; };
|
||||
322806A11F0F64C4008C53D7 /* RoomMembershipExpandedBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3228069F1F0F64C4008C53D7 /* RoomMembershipExpandedBubbleCell.xib */; };
|
||||
3233F7311F31F4BF006ACA81 /* JitsiViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3233F72F1F31F4BF006ACA81 /* JitsiViewController.m */; };
|
||||
3233F7321F31F4BF006ACA81 /* JitsiViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3233F7301F31F4BF006ACA81 /* JitsiViewController.xib */; };
|
||||
3233F73C1F3306A7006ACA81 /* WidgetManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3233F73B1F3306A7006ACA81 /* WidgetManager.m */; };
|
||||
3233F73F1F331F05006ACA81 /* Widget.m in Sources */ = {isa = PBXBuildFile; fileRef = 3233F73E1F331F05006ACA81 /* Widget.m */; };
|
||||
3233F7461F3497E2006ACA81 /* JitsiMeet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3233F7441F3497DA006ACA81 /* JitsiMeet.framework */; };
|
||||
3233F7471F3497E2006ACA81 /* JitsiMeet.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3233F7441F3497DA006ACA81 /* JitsiMeet.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
32471CDC1F1373A100BDF50A /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 32471CDA1F1373A100BDF50A /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.m */; };
|
||||
32471CDD1F1373A100BDF50A /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32471CDB1F1373A100BDF50A /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.xib */; };
|
||||
32471CE11F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 32471CDF1F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.m */; };
|
||||
|
@ -500,6 +506,20 @@
|
|||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
3233F7481F3497E2006ACA81 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
3233F7471F3497E2006ACA81 /* JitsiMeet.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1129C74A281B080432B1A1A1 /* Pods-Riot.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Riot.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Riot/Pods-Riot.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
24B5103C1EFA7083004C6AD2 /* ReadReceiptsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReadReceiptsViewController.h; sourceTree = "<group>"; };
|
||||
|
@ -518,6 +538,14 @@
|
|||
3228069D1F0F64C4008C53D7 /* RoomMembershipExpandedBubbleCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RoomMembershipExpandedBubbleCell.h; sourceTree = "<group>"; };
|
||||
3228069E1F0F64C4008C53D7 /* RoomMembershipExpandedBubbleCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomMembershipExpandedBubbleCell.m; sourceTree = "<group>"; };
|
||||
3228069F1F0F64C4008C53D7 /* RoomMembershipExpandedBubbleCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RoomMembershipExpandedBubbleCell.xib; sourceTree = "<group>"; };
|
||||
3233F72E1F31F4BF006ACA81 /* JitsiViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JitsiViewController.h; sourceTree = "<group>"; };
|
||||
3233F72F1F31F4BF006ACA81 /* JitsiViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiViewController.m; sourceTree = "<group>"; };
|
||||
3233F7301F31F4BF006ACA81 /* JitsiViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JitsiViewController.xib; sourceTree = "<group>"; };
|
||||
3233F73A1F3306A6006ACA81 /* WidgetManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WidgetManager.h; sourceTree = "<group>"; };
|
||||
3233F73B1F3306A7006ACA81 /* WidgetManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WidgetManager.m; sourceTree = "<group>"; };
|
||||
3233F73D1F331F05006ACA81 /* Widget.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Widget.h; sourceTree = "<group>"; };
|
||||
3233F73E1F331F05006ACA81 /* Widget.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Widget.m; sourceTree = "<group>"; };
|
||||
3233F7441F3497DA006ACA81 /* JitsiMeet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = JitsiMeet.framework; sourceTree = "<group>"; };
|
||||
32471CD91F1373A100BDF50A /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RoomMembershipCollapsedWithPaginationTitleBubbleCell.h; sourceTree = "<group>"; };
|
||||
32471CDA1F1373A100BDF50A /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomMembershipCollapsedWithPaginationTitleBubbleCell.m; sourceTree = "<group>"; };
|
||||
32471CDB1F1373A100BDF50A /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RoomMembershipCollapsedWithPaginationTitleBubbleCell.xib; sourceTree = "<group>"; };
|
||||
|
@ -1131,6 +1159,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E2EAC1A4FBD6FE5228584591 /* libPods-Riot.a in Frameworks */,
|
||||
3233F7461F3497E2006ACA81 /* JitsiMeet.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1157,6 +1186,43 @@
|
|||
path = Directory;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3233F7291F31F3B4006ACA81 /* libs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3233F7431F3497DA006ACA81 /* jitsi-meet */,
|
||||
);
|
||||
path = libs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3233F72D1F31F47E006ACA81 /* Widgets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3233F72E1F31F4BF006ACA81 /* JitsiViewController.h */,
|
||||
3233F72F1F31F4BF006ACA81 /* JitsiViewController.m */,
|
||||
3233F7301F31F4BF006ACA81 /* JitsiViewController.xib */,
|
||||
);
|
||||
path = Widgets;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3233F7391F33065F006ACA81 /* Widgets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3233F73A1F3306A6006ACA81 /* WidgetManager.h */,
|
||||
3233F73B1F3306A7006ACA81 /* WidgetManager.m */,
|
||||
3233F73D1F331F05006ACA81 /* Widget.h */,
|
||||
3233F73E1F331F05006ACA81 /* Widget.m */,
|
||||
);
|
||||
path = Widgets;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3233F7431F3497DA006ACA81 /* jitsi-meet */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3233F7441F3497DA006ACA81 /* JitsiMeet.framework */,
|
||||
);
|
||||
path = "jitsi-meet";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
327382A71F276AD200356143 /* de.lproj */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1241,6 +1307,7 @@
|
|||
F083BC0F1E7009EC00A9B29C /* Utils */,
|
||||
F083BC191E7009EC00A9B29C /* ViewController */,
|
||||
F083BC571E7009EC00A9B29C /* Views */,
|
||||
3233F7291F31F3B4006ACA81 /* libs */,
|
||||
F083BB0C1E7009EC00A9B29C /* AppDelegate.h */,
|
||||
F083BB0D1E7009EC00A9B29C /* AppDelegate.m */,
|
||||
F083BBE21E7009EC00A9B29C /* Main.storyboard */,
|
||||
|
@ -1608,6 +1675,7 @@
|
|||
F083BC0F1E7009EC00A9B29C /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3233F7391F33065F006ACA81 /* Widgets */,
|
||||
F083BC101E7009EC00A9B29C /* AvatarGenerator.h */,
|
||||
F083BC111E7009EC00A9B29C /* AvatarGenerator.m */,
|
||||
F083BC121E7009EC00A9B29C /* EventFormatter.h */,
|
||||
|
@ -1623,6 +1691,7 @@
|
|||
F083BC191E7009EC00A9B29C /* ViewController */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3233F72D1F31F47E006ACA81 /* Widgets */,
|
||||
F0B4CBA81F41E71A008E99C5 /* RiotNavigationController.h */,
|
||||
F0B4CBA91F41E71A008E99C5 /* RiotNavigationController.m */,
|
||||
F0131DE31F2200D600CBF707 /* RiotSplitViewController.h */,
|
||||
|
@ -2105,6 +2174,7 @@
|
|||
F094A9A01B78D8F000B1FBBF /* Resources */,
|
||||
44C35695CFA4F9799C449367 /* [CP] Copy Pods Resources */,
|
||||
381DA4CC07D2104BFA23E45A /* [CP] Embed Pods Frameworks */,
|
||||
3233F7481F3497E2006ACA81 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
|
@ -2224,6 +2294,7 @@
|
|||
F083BD3D1E7009ED00A9B29C /* call_hangup_icon@2x.png in Resources */,
|
||||
F083BE551E7009ED00A9B29C /* RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.xib in Resources */,
|
||||
32471CDD1F1373A100BDF50A /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.xib in Resources */,
|
||||
3233F7321F31F4BF006ACA81 /* JitsiViewController.xib in Resources */,
|
||||
F083BD301E7009ED00A9B29C /* bubbles_bg_landscape.png in Resources */,
|
||||
F083BDA41E7009ED00A9B29C /* notifications.png in Resources */,
|
||||
F083BE0F1E7009ED00A9B29C /* ContactsTableViewController.xib in Resources */,
|
||||
|
@ -2631,6 +2702,7 @@
|
|||
F083BE661E7009ED00A9B29C /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.m in Sources */,
|
||||
F083BE141E7009ED00A9B29C /* HomeViewController.m in Sources */,
|
||||
F083BDFB1E7009ED00A9B29C /* RoomSearchDataSource.m in Sources */,
|
||||
3233F73C1F3306A7006ACA81 /* WidgetManager.m in Sources */,
|
||||
F083BE281E7009ED00A9B29C /* StartChatViewController.m in Sources */,
|
||||
F083BE841E7009ED00A9B29C /* RoomIdOrAliasTableViewCell.m in Sources */,
|
||||
F083BD1D1E7009ED00A9B29C /* RageShakeManager.m in Sources */,
|
||||
|
@ -2660,6 +2732,7 @@
|
|||
F083BE2F1E7009ED00A9B29C /* ContactTableViewCell.m in Sources */,
|
||||
F083BE901E7009ED00A9B29C /* RoomTitleView.m in Sources */,
|
||||
F083BE6E1E7009ED00A9B29C /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m in Sources */,
|
||||
3233F7311F31F4BF006ACA81 /* JitsiViewController.m in Sources */,
|
||||
F083BE2A1E7009ED00A9B29C /* UsersDevicesViewController.m in Sources */,
|
||||
F083BE2D1E7009ED00A9B29C /* ForgotPasswordInputsView.m in Sources */,
|
||||
F083BE7E1E7009ED00A9B29C /* InviteRecentTableViewCell.m in Sources */,
|
||||
|
@ -2683,6 +2756,7 @@
|
|||
F083BE401E7009ED00A9B29C /* RoomIncomingEncryptedTextMsgBubbleCell.m in Sources */,
|
||||
32AE61E91F0CE099007255F4 /* RoomMembershipWithPaginationTitleBubbleCell.m in Sources */,
|
||||
F083BDFD1E7009ED00A9B29C /* PublicRoomsDirectoryDataSource.m in Sources */,
|
||||
3233F73F1F331F05006ACA81 /* Widget.m in Sources */,
|
||||
F083BE191E7009ED00A9B29C /* RecentsViewController.m in Sources */,
|
||||
F0B4CBA51F418D0B008E99C5 /* WebViewViewController.m in Sources */,
|
||||
F083BE351E7009ED00A9B29C /* MediaAlbumTableCell.m in Sources */,
|
||||
|
@ -2964,6 +3038,11 @@
|
|||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
DEVELOPMENT_TEAM = 7J4U792NQT;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Riot/libs",
|
||||
"$(PROJECT_DIR)/Riot/libs/jitsi-meet",
|
||||
);
|
||||
INFOPLIST_FILE = Riot/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
|
@ -2981,6 +3060,11 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Riot/Riot.entitlements;
|
||||
DEVELOPMENT_TEAM = 7J4U792NQT;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Riot/libs",
|
||||
"$(PROJECT_DIR)/Riot/libs/jitsi-meet",
|
||||
);
|
||||
INFOPLIST_FILE = Riot/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#import "GAIDictionaryBuilder.h"
|
||||
|
||||
#import "MasterTabBarController.h"
|
||||
#import "JitsiViewController.h"
|
||||
|
||||
#import "RageShakeManager.h"
|
||||
|
||||
|
@ -40,7 +41,7 @@ extern NSString *const kAppDelegateDidTapStatusBarNotification;
|
|||
*/
|
||||
extern NSString *const kAppDelegateNetworkStatusDidChangeNotification;
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate, MXKCallViewControllerDelegate, UISplitViewControllerDelegate, UINavigationControllerDelegate>
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate, MXKCallViewControllerDelegate, UISplitViewControllerDelegate, UINavigationControllerDelegate, JitsiViewControllerDelegate>
|
||||
{
|
||||
BOOL isAPNSRegistered;
|
||||
|
||||
|
@ -133,6 +134,21 @@ extern NSString *const kAppDelegateNetworkStatusDidChangeNotification;
|
|||
*/
|
||||
- (BOOL)handleUniversalLinkFragment:(NSString*)fragment;
|
||||
|
||||
#pragma mark - Jitsi call
|
||||
|
||||
/**
|
||||
Open the Jitsi view controller from a widget.
|
||||
|
||||
@param jitsiWidget the jitsi widget.
|
||||
@param video to indicate voice or video call.
|
||||
*/
|
||||
- (void)displayJitsiViewControllerWithWidget:(Widget*)jitsiWidget andVideo:(BOOL)video;
|
||||
|
||||
/**
|
||||
The current Jitsi view controller being displayed.
|
||||
*/
|
||||
@property (nonatomic, readonly) JitsiViewController *jitsiViewController;
|
||||
|
||||
#pragma mark - Call status handling
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
#import "MatrixSDK/MatrixSDK.h"
|
||||
|
||||
#import "Tools.h"
|
||||
#import "MXRoom+Riot.h"
|
||||
#import "WidgetManager.h"
|
||||
|
||||
#import "AFNetworkReachabilityManager.h"
|
||||
|
||||
|
@ -1684,6 +1686,9 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
|
|||
|
||||
// Update home data sources
|
||||
[_masterTabBarController addMatrixSession:mxSession];
|
||||
|
||||
// Register the session to the widgets manager
|
||||
[[WidgetManager sharedManager] addMatrixSession:mxSession];
|
||||
|
||||
[mxSessionArray addObject:mxSession];
|
||||
|
||||
|
@ -1698,6 +1703,9 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
|
|||
|
||||
// Update home data sources
|
||||
[_masterTabBarController removeMatrixSession:mxSession];
|
||||
|
||||
// Update the widgets manager
|
||||
[[WidgetManager sharedManager] removeMatrixSession:mxSession];
|
||||
|
||||
// If any, disable the no VoIP support workaround
|
||||
[self disableNoVoIPOnMatrixSession:mxSession];
|
||||
|
@ -1792,7 +1800,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
|
|||
matrixCallObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXCallManagerNewCall object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
||||
|
||||
// Ignore the call if a call is already in progress
|
||||
if (!currentCallViewController)
|
||||
if (!currentCallViewController && !_jitsiViewController)
|
||||
{
|
||||
MXCall *mxCall = (MXCall*)notif.object;
|
||||
|
||||
|
@ -2489,6 +2497,89 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
|
|||
}
|
||||
}
|
||||
|
||||
#pragma mark - Jitsi call
|
||||
|
||||
- (void)displayJitsiViewControllerWithWidget:(Widget*)jitsiWidget andVideo:(BOOL)video
|
||||
{
|
||||
if (!_jitsiViewController && !currentCallViewController)
|
||||
{
|
||||
_jitsiViewController = [JitsiViewController jitsiViewController];
|
||||
|
||||
if ([_jitsiViewController openWidget:jitsiWidget withVideo:video])
|
||||
{
|
||||
_jitsiViewController.delegate = self;
|
||||
[self presentJitsiViewController:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
_jitsiViewController = nil;
|
||||
|
||||
NSError *error = [NSError errorWithDomain:@""
|
||||
code:0
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"call_jitsi_error", @"Vector", nil)
|
||||
}];
|
||||
[self showErrorAsAlert:error];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSError *error = [NSError errorWithDomain:@""
|
||||
code:0
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"call_already_displayed", @"Vector", nil)
|
||||
}];
|
||||
[self showErrorAsAlert:error];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)presentJitsiViewController:(void (^)())completion
|
||||
{
|
||||
[self removeCallStatusBar];
|
||||
|
||||
if (_jitsiViewController)
|
||||
{
|
||||
if (self.window.rootViewController.presentedViewController)
|
||||
{
|
||||
[self.window.rootViewController.presentedViewController presentViewController:_jitsiViewController animated:YES completion:completion];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self.window.rootViewController presentViewController:_jitsiViewController animated:YES completion:completion];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)jitsiViewController:(JitsiViewController *)jitsiViewController dismissViewJitsiController:(void (^)())completion
|
||||
{
|
||||
if (jitsiViewController == _jitsiViewController)
|
||||
{
|
||||
[_jitsiViewController dismissViewControllerAnimated:YES completion:completion];
|
||||
_jitsiViewController = nil;
|
||||
|
||||
[self removeCallStatusBar];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)jitsiViewController:(JitsiViewController *)jitsiViewController goBackToApp:(void (^)())completion
|
||||
{
|
||||
if (jitsiViewController == _jitsiViewController)
|
||||
{
|
||||
[_jitsiViewController dismissViewControllerAnimated:YES completion:^{
|
||||
|
||||
MXRoom *room = [_jitsiViewController.widget.mxSession roomWithRoomId:_jitsiViewController.widget.roomId];
|
||||
NSString *btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"active_call_details", @"Vector", nil), room.riotDisplayname];
|
||||
[self addCallStatusBar:btnTitle];
|
||||
|
||||
if (completion)
|
||||
{
|
||||
completion();
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Call status handling
|
||||
|
||||
- (void)addCallStatusBar:(NSString*)buttonTitle
|
||||
|
@ -2517,7 +2608,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
|
|||
}
|
||||
|
||||
[_callStatusBarButton setBackgroundColor:kRiotColorGreen];
|
||||
[_callStatusBarButton addTarget:self action:@selector(presentCallViewController) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_callStatusBarButton addTarget:self action:@selector(onCallStatusBarButtonPressed) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
// Place button into the new window
|
||||
[_callStatusBarButton setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
|
@ -2570,9 +2661,16 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN
|
|||
}
|
||||
}
|
||||
|
||||
- (void)presentCallViewController
|
||||
- (void)onCallStatusBarButtonPressed
|
||||
{
|
||||
[self presentCallViewController:nil];
|
||||
if (currentCallViewController)
|
||||
{
|
||||
[self presentCallViewController:nil];
|
||||
}
|
||||
else if (_jitsiViewController)
|
||||
{
|
||||
[self presentJitsiViewController:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)presentCallViewController:(void (^)())completion
|
||||
|
|
|
@ -231,6 +231,9 @@
|
|||
"room_unsent_messages_notification" = "Messages not sent. %@ or %@ now?";
|
||||
"room_unsent_messages_unknown_devices_notification" = "Message not sent due to unknown devices being present. %@ or %@ now?";
|
||||
"room_ongoing_conference_call" = "Ongoing conference call. Join as %@ or %@.";
|
||||
"room_ongoing_conference_call_with_close" = "Ongoing conference call. Join as %@ or %@. %@ it.";
|
||||
"room_ongoing_conference_call_close" = "Close";
|
||||
"room_conference_call_no_power" = "You need permission to manage conference call in this room";
|
||||
"room_prompt_resend" = "Resend all";
|
||||
"room_prompt_cancel" = "cancel all";
|
||||
"room_resend_unsent_messages" = "Resend unsent messages";
|
||||
|
@ -343,6 +346,7 @@
|
|||
|
||||
"settings_labs_e2e_encryption" = "End-to-End Encryption";
|
||||
"settings_labs_e2e_encryption_prompt_message" = "To finish setting up encryption you must log in again.";
|
||||
"settings_labs_create_conference_with_jitsi" = "Create conference calls with jitsi";
|
||||
|
||||
"settings_version" = "Version %@";
|
||||
"settings_olm_version" = "Olm Version %@";
|
||||
|
@ -468,6 +472,8 @@
|
|||
// Call
|
||||
"call_incoming_voice_prompt" = "Incoming voice call from %@";
|
||||
"call_incoming_video_prompt" = "Incoming video call from %@";
|
||||
"call_already_displayed" = "There is already a call in progress.";
|
||||
"call_jitsi_error" = "Failed to join the conference call.";
|
||||
|
||||
// No VoIP support
|
||||
"no_voip_title" = "Incoming call";
|
||||
|
@ -492,3 +498,7 @@
|
|||
"bug_report_progress_uploading" = "Uploading report";
|
||||
"bug_report_send" = "Send";
|
||||
|
||||
// Widget
|
||||
"widget_no_power_to_manage" = "You need permission to manage widgets in this room";
|
||||
"widget_creation_failure" = "Widget creation has failed";
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
#import <MatrixKit/MatrixKit.h>
|
||||
|
||||
#import "WidgetManager.h"
|
||||
|
||||
/**
|
||||
The data source for `RoomViewController` in Vector.
|
||||
*/
|
||||
|
@ -32,4 +34,11 @@
|
|||
*/
|
||||
@property(nonatomic) BOOL markTimelineInitialEvent;
|
||||
|
||||
/**
|
||||
Check if there is an active jitsi widget in the room and return it.
|
||||
|
||||
@return a widget representating the active jitsi conference in the room. Else, nil.
|
||||
*/
|
||||
- (Widget *)jitsiWidget;
|
||||
|
||||
@end
|
||||
|
|
|
@ -425,4 +425,14 @@
|
|||
_selectedEventId = selectedEventId;
|
||||
}
|
||||
|
||||
- (Widget *)jitsiWidget
|
||||
{
|
||||
Widget *jitsiWidget;
|
||||
|
||||
// Note: Manage only one jitsi widget at a time for the moment
|
||||
jitsiWidget = [[WidgetManager sharedManager] widgetsOfTypes:@[kWidgetTypeJitsi] inRoom:self.room].firstObject;
|
||||
|
||||
return jitsiWidget;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -59,6 +59,10 @@ extern UIStatusBarStyle kRiotDesignStatusBarStyle;
|
|||
extern UIBarStyle kRiotDesignSearchBarStyle;
|
||||
extern UIColor *kRiotDesignSearchBarTintColor;
|
||||
|
||||
// Flag to enable the display of jitsi conference widget
|
||||
// TODO: Disable it before release
|
||||
// TODO: Remove it once Riot web and android are ready
|
||||
#define USE_JITSI_WIDGET
|
||||
|
||||
/**
|
||||
`RiotDesignValues` class manages the Riot design parameters
|
||||
|
|
85
Riot/Utils/Widgets/Widget.h
Normal file
85
Riot/Utils/Widgets/Widget.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
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>
|
||||
|
||||
/**
|
||||
The `Widget` class represents scalar widget information.
|
||||
*/
|
||||
@interface Widget : NSObject
|
||||
|
||||
/**
|
||||
The widget id.
|
||||
*/
|
||||
@property (nonatomic, readonly, nonnull) NSString *widgetId;
|
||||
|
||||
/**
|
||||
The widget type.
|
||||
|
||||
Some types are defined in `WidgetManager.h`.
|
||||
Nil if the widget is no more active in the room.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSString *type;
|
||||
|
||||
/**
|
||||
The widget url.
|
||||
|
||||
Widgets are basically opened in a webview. `url` is the url the webview must use
|
||||
to render the widget.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSString *url;
|
||||
|
||||
/**
|
||||
The widget name.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSString *name;
|
||||
|
||||
/**
|
||||
The widget additional data.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSDictionary *data;
|
||||
|
||||
/**
|
||||
The widget event that is at the origin of the widget.
|
||||
*/
|
||||
@property (nonatomic, readonly, nonnull) MXEvent *widgetEvent;
|
||||
|
||||
/**
|
||||
The Matrix session where the widget is.
|
||||
*/
|
||||
@property (nonatomic, readonly, nonnull) MXSession *mxSession;
|
||||
|
||||
/**
|
||||
Indicate if the widget is still active.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL isActive;
|
||||
|
||||
/**
|
||||
The room id of the widget.
|
||||
*/
|
||||
@property (nonatomic, readonly, nonnull) NSString *roomId;
|
||||
|
||||
/**
|
||||
Create a Widget instance from a widget event.
|
||||
|
||||
@param widgetEvent the state event representing a widget.
|
||||
@return the newly created instance.
|
||||
*/
|
||||
- (instancetype _Nullable )initWithWidgetEvent:(MXEvent* _Nonnull)widgetEvent inMatrixSession:(MXSession* _Nonnull)mxSession;
|
||||
|
||||
@end
|
69
Riot/Utils/Widgets/Widget.m
Normal file
69
Riot/Utils/Widgets/Widget.m
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
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 "Widget.h"
|
||||
|
||||
#import "WidgetManager.h"
|
||||
|
||||
@implementation Widget
|
||||
|
||||
- (instancetype)initWithWidgetEvent:(MXEvent *)widgetEvent inMatrixSession:(MXSession*)mxSession
|
||||
{
|
||||
if (![widgetEvent.type isEqualToString:kWidgetEventTypeString])
|
||||
{
|
||||
// The Widget class works only with scalar, aka "im.vector.modular.widgets", widgets
|
||||
return nil;
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
_widgetId = widgetEvent.stateKey;
|
||||
_widgetEvent = widgetEvent;
|
||||
_mxSession = mxSession;
|
||||
|
||||
MXJSONModelSetString(_type, widgetEvent.content[@"type"]);
|
||||
MXJSONModelSetString(_url, widgetEvent.content[@"url"]);
|
||||
MXJSONModelSetString(_name, widgetEvent.content[@"name"]);
|
||||
MXJSONModelSetDictionary(_data, widgetEvent.content[@"data"]);
|
||||
|
||||
// Format the url string with user data
|
||||
_url = [_url stringByReplacingOccurrencesOfString:@"$matrix_user_id" withString:mxSession.myUser.userId];
|
||||
_url = [_url stringByReplacingOccurrencesOfString:@"$matrix_display_name"
|
||||
withString:mxSession.myUser.displayname ? mxSession.myUser.displayname : mxSession.myUser.userId];
|
||||
_url = [_url stringByReplacingOccurrencesOfString:@"$matrix_avatar_url"
|
||||
withString:mxSession.myUser.avatarUrl ? mxSession.myUser.avatarUrl : @""];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isActive
|
||||
{
|
||||
return (_type != nil && _url != nil);
|
||||
}
|
||||
|
||||
- (NSString *)roomId
|
||||
{
|
||||
return _widgetEvent.roomId;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<Widget: %p> id: %@ - type: %@ - name: %@ - url: %@", self, _widgetId, _type, _name, _url];
|
||||
}
|
||||
|
||||
@end
|
142
Riot/Utils/Widgets/WidgetManager.h
Normal file
142
Riot/Utils/Widgets/WidgetManager.h
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
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 "Widget.h"
|
||||
|
||||
/**
|
||||
The type of matrix event used for scalar widgets.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const kWidgetEventTypeString;
|
||||
|
||||
/**
|
||||
Known types widgets.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const kWidgetTypeJitsi;
|
||||
|
||||
/**
|
||||
Posted when a widget has been created, updated or disabled.
|
||||
The notification object is the `Widget` instance.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const kWidgetManagerDidUpdateWidgetNotification;
|
||||
|
||||
/**
|
||||
`WidgetManager` NSError domain and codes.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const WidgetManagerErrorDomain;
|
||||
|
||||
typedef enum : NSUInteger
|
||||
{
|
||||
WidgetManagerErrorCodeNotEnoughPower,
|
||||
WidgetManagerErrorCodeCreationFailed
|
||||
}
|
||||
WidgetManagerErrorCode;
|
||||
|
||||
|
||||
/**
|
||||
The `WidgetManager` helps to handle scalar widgets.
|
||||
*/
|
||||
@interface WidgetManager : NSObject
|
||||
|
||||
/**
|
||||
Returns the shared widget manager.
|
||||
|
||||
@return the shared widget manager.
|
||||
*/
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
/**
|
||||
List all active widgets in a room.
|
||||
|
||||
@param room the room to check.
|
||||
@return a list of widgets.
|
||||
*/
|
||||
- (NSArray<Widget*> *)widgetsInRoom:(MXRoom*)room;
|
||||
|
||||
/**
|
||||
List all active widgets of a given type in a room.
|
||||
|
||||
@param widgetType the types of widget to search.
|
||||
@param room the room to check.
|
||||
@return a list of widgets.
|
||||
*/
|
||||
- (NSArray<Widget*> *)widgetsOfTypes:(NSArray<NSString*>*)widgetTypes inRoom:(MXRoom*)room;
|
||||
|
||||
|
||||
/**
|
||||
Add a scalar widget to a room.
|
||||
|
||||
@param widgetId the id of the widget.
|
||||
@param widgetContent the widget content.
|
||||
@param room the room to create the widget to.
|
||||
|
||||
@param success A block object called when the operation succeeds. It provides the newly added widget.
|
||||
@param failure A block object called when the operation fails.
|
||||
|
||||
@return a MXHTTPOperation instance.
|
||||
*/
|
||||
- (MXHTTPOperation *)createWidget:(NSString*)widgetId
|
||||
withContent:(NSDictionary<NSString*, NSObject*>*)widgetContent
|
||||
inRoom:(MXRoom*)room
|
||||
success:(void (^)(Widget *widget))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Add a jitsi conference widget to a room.
|
||||
|
||||
@param room the room to create the widget to.
|
||||
@param video the conference type
|
||||
|
||||
@param success A block object called when the operation succeeds. It provides the newly added widget.
|
||||
@param failure A block object called when the operation fails.
|
||||
|
||||
@return a MXHTTPOperation instance.
|
||||
*/
|
||||
- (MXHTTPOperation *)createJitsiWidgetInRoom:(MXRoom*)room
|
||||
withVideo:(BOOL)video
|
||||
success:(void (^)(Widget *jitsiWidget))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
/**
|
||||
Close/Disable a widget in a room.
|
||||
|
||||
@param widgetId the id of the widget to close.
|
||||
@param room the room the widget is in.
|
||||
|
||||
@param success A block object called when the operation succeeds.
|
||||
@param failure A block object called when the operation fails.
|
||||
|
||||
@return a MXHTTPOperation instance.
|
||||
*/
|
||||
- (MXHTTPOperation *)closeWidget:(NSString*)widgetId inRoom:(MXRoom*)room
|
||||
success:(void (^)())success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
|
||||
|
||||
/**
|
||||
Add/remove matrix session.
|
||||
|
||||
Registering session allows to generate `kWidgetManagerDidUpdateWidgetNotification` notifications.
|
||||
|
||||
@param mxSession the session to add/remove.
|
||||
*/
|
||||
- (void)addMatrixSession:(MXSession*)mxSession;
|
||||
- (void)removeMatrixSession:(MXSession*)mxSession;
|
||||
|
||||
@end
|
329
Riot/Utils/Widgets/WidgetManager.m
Normal file
329
Riot/Utils/Widgets/WidgetManager.m
Normal file
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
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 "WidgetManager.h"
|
||||
|
||||
#pragma mark - Contants
|
||||
|
||||
NSString *const kWidgetEventTypeString = @"im.vector.modular.widgets";
|
||||
NSString *const kWidgetTypeJitsi = @"jitsi";
|
||||
|
||||
NSString *const kWidgetManagerDidUpdateWidgetNotification = @"kWidgetManagerDidUpdateWidgetNotification";
|
||||
|
||||
NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
|
||||
|
||||
@interface WidgetManager ()
|
||||
{
|
||||
// MXSession kind of hash -> Listener for matrix events for widgets.
|
||||
// There is one per matrix session
|
||||
NSMutableDictionary<NSString*, id> *widgetEventListener;
|
||||
|
||||
// Success blocks of widgets being created
|
||||
// MXSession kind of hash -> (Widget id -> `createWidget:` success block).
|
||||
NSMutableDictionary<NSString*,
|
||||
NSMutableDictionary<NSString*, void (^)(Widget *widget)>*> *successBlockForWidgetCreation;
|
||||
|
||||
// Failure blocks of widgets being created
|
||||
// MXSession kind of hash -> (Widget id -> `createWidget:` failure block).
|
||||
NSMutableDictionary<NSString*,
|
||||
NSMutableDictionary<NSString*, void (^)(NSError *error)>*> *failureBlockForWidgetCreation;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation WidgetManager
|
||||
|
||||
+ (instancetype)sharedManager
|
||||
{
|
||||
static WidgetManager *sharedManager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedManager = [[WidgetManager alloc] init];
|
||||
});
|
||||
|
||||
return sharedManager;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
widgetEventListener = [NSMutableDictionary dictionary];
|
||||
successBlockForWidgetCreation = [NSMutableDictionary dictionary];
|
||||
failureBlockForWidgetCreation = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray<Widget *> *)widgetsInRoom:(MXRoom *)room
|
||||
{
|
||||
return [self widgetsOfTypes:nil inRoom:room];
|
||||
}
|
||||
|
||||
- (NSArray<Widget *> *)widgetsOfTypes:(NSArray<NSString *> *)widgetTypes inRoom:(MXRoom *)room
|
||||
{
|
||||
// Widget id -> widget
|
||||
NSMutableDictionary <NSString*, Widget *> *widgets = [NSMutableDictionary dictionary];
|
||||
|
||||
// Get all im.vector.modular.widgets state events in the room
|
||||
NSMutableArray<MXEvent*> *widgetEvents = [NSMutableArray arrayWithArray:[room.state stateEventsWithType:kWidgetEventTypeString]];
|
||||
|
||||
// There can be several im.vector.modular.widgets state events for a same widget but
|
||||
// only the last one must be considered.
|
||||
|
||||
// Order widgetEvents with the last event first
|
||||
[widgetEvents sortUsingComparator:^NSComparisonResult(MXEvent *event1, MXEvent *event2) {
|
||||
|
||||
NSComparisonResult result = NSOrderedAscending;
|
||||
if (event2.originServerTs > event1.originServerTs)
|
||||
{
|
||||
result = NSOrderedDescending;
|
||||
}
|
||||
else if (event2.originServerTs == event1.originServerTs)
|
||||
{
|
||||
result = NSOrderedSame;
|
||||
}
|
||||
|
||||
return result;
|
||||
}];
|
||||
|
||||
// Create each widget from its lastest im.vector.modular.widgets state event
|
||||
for (MXEvent *widgetEvent in widgetEvents)
|
||||
{
|
||||
// Filter widget types if required
|
||||
if (widgetTypes)
|
||||
{
|
||||
NSString *widgetType;
|
||||
MXJSONModelSetString(widgetType, widgetEvent.content[@"type"]);
|
||||
|
||||
if (widgetType && NSNotFound == [widgetTypes indexOfObject:widgetType])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// widgetEvent.stateKey = widget id
|
||||
if (!widgets[widgetEvent.stateKey])
|
||||
{
|
||||
Widget *widget = [[Widget alloc] initWithWidgetEvent:widgetEvent inMatrixSession:room.mxSession];
|
||||
if (widget)
|
||||
{
|
||||
widgets[widget.widgetId] = widget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return active widgets only
|
||||
NSMutableArray<Widget *> *activeWidgets = [NSMutableArray array];
|
||||
for (Widget *widget in widgets.allValues)
|
||||
{
|
||||
if (widget.isActive)
|
||||
{
|
||||
[activeWidgets addObject:widget];
|
||||
}
|
||||
}
|
||||
|
||||
return activeWidgets;
|
||||
}
|
||||
|
||||
- (MXHTTPOperation *)createWidget:(NSString*)widgetId
|
||||
withContent:(NSDictionary<NSString*, NSObject*>*)widgetContent
|
||||
inRoom:(MXRoom*)room
|
||||
success:(void (^)(Widget *widget))success
|
||||
failure:(void (^)(NSError *error))failure
|
||||
{
|
||||
NSError *permissionError = [self checkWidgetPermissionInRoom:room];
|
||||
if (permissionError)
|
||||
{
|
||||
if (failure)
|
||||
{
|
||||
failure(permissionError);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Send a state event with the widget data
|
||||
// TODO: This API will be shortly replaced by a pure scalar API
|
||||
return [room sendStateEventOfType:kWidgetEventTypeString
|
||||
content:widgetContent
|
||||
stateKey:widgetId
|
||||
success:nil failure:failure];
|
||||
}
|
||||
|
||||
|
||||
- (MXHTTPOperation *)createJitsiWidgetInRoom:(MXRoom*)room
|
||||
withVideo:(BOOL)video
|
||||
success:(void (^)(Widget *jitsiWidget))success
|
||||
failure:(void (^)(NSError *error))failure
|
||||
{
|
||||
// Build data for a jitsi widget
|
||||
NSString *widgetId = [NSString stringWithFormat:@"%@_%@_%@", kWidgetTypeJitsi, room.mxSession.myUser.userId, @((uint64_t)([[NSDate date] timeIntervalSince1970] * 1000))];
|
||||
|
||||
// Create a random enough jitsi conference id
|
||||
// Note: the jitsi server automatically creates conference when the conference
|
||||
// id does not exist yet
|
||||
NSString *widgetSessionId = [[[[NSProcessInfo processInfo] globallyUniqueString] substringToIndex:7] lowercaseString];
|
||||
NSString *confId = [room.roomId substringWithRange:NSMakeRange(1, [room.roomId rangeOfString:@":"].location - 1)];
|
||||
confId = [confId stringByAppendingString:widgetSessionId];
|
||||
|
||||
// TODO: This url may come from scalar API
|
||||
// Note: this url can be used as is inside a web container (like iframe for Riot-web)
|
||||
// Riot-iOS does not directly use it but extracts params from it (see `[JitsiViewController openWidget:withVideo:]`)
|
||||
NSString *url = [NSString stringWithFormat:@"https://scalar-staging.riot.im/scalar/api/widgets/jitsi.html?confId=%@&isAudioConf=%@&displayName=$matrix_display_name&avatarUrl=$matrix_avatar_url&email=$matrix_user_id@", confId, video ? @"false" : @"true"];
|
||||
|
||||
NSString *hash = [NSString stringWithFormat:@"%p", room.mxSession];
|
||||
successBlockForWidgetCreation[hash][widgetId] = success;
|
||||
failureBlockForWidgetCreation[hash][widgetId] = failure;
|
||||
|
||||
return [self createWidget:widgetId
|
||||
withContent:@{
|
||||
@"url": url,
|
||||
@"type": kWidgetTypeJitsi,
|
||||
@"data": @{
|
||||
@"widgetSessionId": widgetSessionId
|
||||
}
|
||||
}
|
||||
inRoom:room
|
||||
success:success
|
||||
failure:failure];
|
||||
}
|
||||
|
||||
- (MXHTTPOperation *)closeWidget:(NSString *)widgetId inRoom:(MXRoom *)room success:(void (^)())success failure:(void (^)(NSError *))failure
|
||||
{
|
||||
NSError *permissionError = [self checkWidgetPermissionInRoom:room];
|
||||
if (permissionError)
|
||||
{
|
||||
if (failure)
|
||||
{
|
||||
failure(permissionError);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Send a state event with an empty content to disable the widget
|
||||
// TODO: This API will be shortly replaced by a pure scalar API
|
||||
return [room sendStateEventOfType:kWidgetEventTypeString
|
||||
content:@{}
|
||||
stateKey:widgetId
|
||||
success:^(NSString *eventId)
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
success();
|
||||
}
|
||||
} failure:failure];
|
||||
}
|
||||
|
||||
/**
|
||||
Check user's power for widgets management in a room.
|
||||
|
||||
@param room the room to check.
|
||||
@return an NSError if the user cannot act on widgets in this room. Else, nil.
|
||||
*/
|
||||
- (NSError *)checkWidgetPermissionInRoom:(MXRoom *)room
|
||||
{
|
||||
NSError *error;
|
||||
|
||||
// Check user's power in the room
|
||||
MXRoomPowerLevels *powerLevels = room.state.powerLevels;
|
||||
NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:room.mxSession.myUser.userId];
|
||||
|
||||
// The user must be able to send state events to manage widgets
|
||||
if (oneSelfPowerLevel < powerLevels.stateDefault)
|
||||
{
|
||||
error = [NSError errorWithDomain:WidgetManagerErrorDomain
|
||||
code:WidgetManagerErrorCodeNotEnoughPower
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"widget_no_power_to_manage", @"Vector", nil)
|
||||
}];
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
- (void)addMatrixSession:(MXSession *)mxSession
|
||||
{
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
|
||||
NSString *hash = [NSString stringWithFormat:@"%p", mxSession];
|
||||
|
||||
id listener = [mxSession listenToEventsOfTypes:@[kWidgetEventTypeString] onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) {
|
||||
|
||||
typeof(self) self = weakSelf;
|
||||
|
||||
if (self && direction == MXTimelineDirectionForwards)
|
||||
{
|
||||
// stateKey = widgetId
|
||||
NSString *widgetId = event.stateKey;
|
||||
|
||||
NSLog(@"[WidgetManager] New widget detected: %@ in %@", widgetId, event.roomId);
|
||||
|
||||
Widget *widget = [[Widget alloc] initWithWidgetEvent:event inMatrixSession:mxSession];
|
||||
if (widget)
|
||||
{
|
||||
// If it is a widget we have just created, indicate its creation is complete
|
||||
if (self->successBlockForWidgetCreation[hash][widgetId])
|
||||
{
|
||||
self->successBlockForWidgetCreation[hash][widgetId](widget);
|
||||
}
|
||||
|
||||
// Broadcast the generic notification
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:kWidgetManagerDidUpdateWidgetNotification object:widget];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"[WidgetManager] Cannot decode new widget - event: %@", event);
|
||||
|
||||
if (self->failureBlockForWidgetCreation[hash][widgetId])
|
||||
{
|
||||
// If it is a widget we have just created, indicate its creation has failed somehow
|
||||
NSError *error = [NSError errorWithDomain:WidgetManagerErrorDomain
|
||||
code:WidgetManagerErrorCodeCreationFailed
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"widget_creation_failure", @"Vector", nil)
|
||||
}];
|
||||
|
||||
self->failureBlockForWidgetCreation[hash][widgetId](error);
|
||||
}
|
||||
}
|
||||
|
||||
[self->successBlockForWidgetCreation[hash] removeObjectForKey:widgetId];
|
||||
[self->failureBlockForWidgetCreation[hash] removeObjectForKey:widgetId];
|
||||
}
|
||||
}];
|
||||
|
||||
widgetEventListener[hash] = listener;
|
||||
successBlockForWidgetCreation[hash] = [NSMutableDictionary dictionary];
|
||||
failureBlockForWidgetCreation[hash] = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
- (void)removeMatrixSession:(MXSession *)mxSession
|
||||
{
|
||||
// mxSession.myUser.userId and mxSession.matrixRestClient.credentials.userId may be nil here
|
||||
// So, use a kind of hash value instead
|
||||
NSString *hash = [NSString stringWithFormat:@"%p", mxSession];
|
||||
id listener = widgetEventListener[hash];
|
||||
|
||||
[mxSession removeListener:listener];
|
||||
|
||||
[widgetEventListener removeObjectForKey:hash];
|
||||
[successBlockForWidgetCreation removeObjectForKey:hash];
|
||||
[failureBlockForWidgetCreation removeObjectForKey:hash];
|
||||
}
|
||||
|
||||
@end
|
|
@ -49,6 +49,8 @@
|
|||
|
||||
#import "ReadReceiptsViewController.h"
|
||||
|
||||
#import "JitsiViewController.h"
|
||||
|
||||
#import "RoomEmptyBubbleCell.h"
|
||||
|
||||
#import "RoomIncomingTextMsgBubbleCell.h"
|
||||
|
@ -98,6 +100,7 @@
|
|||
|
||||
#import "AvatarGenerator.h"
|
||||
#import "Tools.h"
|
||||
#import "WidgetManager.h"
|
||||
|
||||
#import "GBDeviceInfo_iOS.h"
|
||||
|
||||
|
@ -164,6 +167,9 @@
|
|||
id kMXCallStateDidChangeObserver;
|
||||
id kMXCallManagerConferenceStartedObserver;
|
||||
id kMXCallManagerConferenceFinishedObserver;
|
||||
|
||||
// Observers to manage widgets
|
||||
id kMXKWidgetManagerDidUpdateWidgetObserver;
|
||||
|
||||
// Observer kMXRoomSummaryDidChangeNotification to keep updated the missed discussion count
|
||||
id mxRoomSummaryDidChangeObserver;
|
||||
|
@ -468,6 +474,7 @@
|
|||
|
||||
[self listenTypingNotifications];
|
||||
[self listenCallNotifications];
|
||||
[self listenWidgetNotifications];
|
||||
|
||||
if (self.showExpandedHeader)
|
||||
{
|
||||
|
@ -515,7 +522,8 @@
|
|||
}
|
||||
|
||||
[self removeCallNotificationsListeners];
|
||||
|
||||
[self removeWidgetNotificationsListeners];
|
||||
|
||||
// Re-enable the read marker display, and disable its update.
|
||||
self.roomDataSource.showReadMarker = YES;
|
||||
self.updateRoomReadMarker = NO;
|
||||
|
@ -1077,7 +1085,8 @@
|
|||
}
|
||||
|
||||
[self removeCallNotificationsListeners];
|
||||
|
||||
[self removeWidgetNotificationsListeners];
|
||||
|
||||
if (previewHeader || (self.expandedHeaderContainer.isHidden == NO))
|
||||
{
|
||||
// Here [destroy] is called before [viewWillDisappear:]
|
||||
|
@ -1222,9 +1231,11 @@
|
|||
userPictureView.clipsToBounds = YES;
|
||||
}
|
||||
|
||||
// Show the hangup button if there is an active call in the current room
|
||||
// Show the hangup button if there is an active call or an active jitsi
|
||||
// conference call in the current room
|
||||
MXCall *callInRoom = [self.roomDataSource.mxSession.callManager callInRoom:self.roomDataSource.roomId];
|
||||
if (callInRoom && callInRoom.state != MXCallStateEnded)
|
||||
if ((callInRoom && callInRoom.state != MXCallStateEnded)
|
||||
|| [[AppDelegate theDelegate].jitsiViewController.widget.roomId isEqualToString:self.roomDataSource.roomId])
|
||||
{
|
||||
roomInputToolbarView.activeCall = YES;
|
||||
}
|
||||
|
@ -2648,72 +2659,126 @@
|
|||
|
||||
- (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView placeCallWithVideo:(BOOL)video
|
||||
{
|
||||
// Conference call is not supported in encrypted rooms
|
||||
if (self.roomDataSource.room.state.isEncrypted && self.roomDataSource.room.state.joinedMembers.count > 2)
|
||||
{
|
||||
[currentAlert dismissViewControllerAnimated:NO completion:nil];
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"room_no_conference_call_in_encrypted_rooms"] message:nil preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
[currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action) {
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
self->currentAlert = nil;
|
||||
}
|
||||
|
||||
}]];
|
||||
|
||||
[currentAlert mxk_setAccessibilityIdentifier:@"RoomVCCallAlert"];
|
||||
[self presentViewController:currentAlert animated:YES completion:nil];
|
||||
}
|
||||
// In case of conference call, check that the user has enough power level
|
||||
else if (self.roomDataSource.room.state.joinedMembers.count > 2 &&
|
||||
![MXCallManager canPlaceConferenceCallInRoom:self.roomDataSource.room])
|
||||
{
|
||||
[currentAlert dismissViewControllerAnimated:NO completion:nil];
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"room_no_power_to_create_conference_call"] message:nil preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
[currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action) {
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
self->currentAlert = nil;
|
||||
}
|
||||
|
||||
}]];
|
||||
|
||||
[currentAlert mxk_setAccessibilityIdentifier:@"RoomVCCallAlert"];
|
||||
[self presentViewController:currentAlert animated:YES completion:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSString *appDisplayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
|
||||
|
||||
// Check app permissions before placing the call
|
||||
[MXKTools checkAccessForCall:video
|
||||
manualChangeMessageForAudio:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"microphone_access_not_granted_for_call"], appDisplayName]
|
||||
manualChangeMessageForVideo:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"camera_access_not_granted_for_call"], appDisplayName]
|
||||
showPopUpInViewController:self completionHandler:^(BOOL granted) {
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
|
||||
NSString *appDisplayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
|
||||
|
||||
// Check app permissions first
|
||||
[MXKTools checkAccessForCall:video
|
||||
manualChangeMessageForAudio:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"microphone_access_not_granted_for_call"], appDisplayName]
|
||||
manualChangeMessageForVideo:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"camera_access_not_granted_for_call"], appDisplayName]
|
||||
showPopUpInViewController:self completionHandler:^(BOOL granted) {
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
|
||||
if (granted)
|
||||
{
|
||||
[self.roomDataSource.room placeCallWithVideo:video success:nil failure:nil];
|
||||
[self roomInputToolbarView:toolbarView placeCallWithVideo2:video];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"RoomViewController: Warning: The application does not have the perssion to place the call");
|
||||
}
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView placeCallWithVideo2:(BOOL)video
|
||||
{
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
|
||||
#ifdef USE_JITSI_WIDGET
|
||||
// If there is already a jitsi widget, join it
|
||||
Widget *jitsiWidget = [customizedRoomDataSource jitsiWidget];
|
||||
if (jitsiWidget)
|
||||
{
|
||||
[[AppDelegate theDelegate] displayJitsiViewControllerWithWidget:jitsiWidget andVideo:video];
|
||||
}
|
||||
|
||||
// If enabled, create the conf using jitsi widget and open it directly
|
||||
else if ([[NSUserDefaults standardUserDefaults] boolForKey:@"createConferenceCallsWithJitsi"]
|
||||
&& self.roomDataSource.room.state.joinedMembers.count > 2)
|
||||
{
|
||||
[self startActivityIndicator];
|
||||
|
||||
[[WidgetManager sharedManager] createJitsiWidgetInRoom:self.roomDataSource.room
|
||||
withVideo:video
|
||||
success:^(Widget *jitsiWidget)
|
||||
{
|
||||
if (weakSelf)
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
[self stopActivityIndicator];
|
||||
|
||||
[[AppDelegate theDelegate] displayJitsiViewControllerWithWidget:jitsiWidget andVideo:video];
|
||||
}
|
||||
}
|
||||
failure:^(NSError *error)
|
||||
{
|
||||
if (weakSelf)
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
[self stopActivityIndicator];
|
||||
|
||||
[self showJitsiErrorAsAlert:error];
|
||||
}
|
||||
}];
|
||||
}
|
||||
else
|
||||
#endif
|
||||
|
||||
// Classic conference call is not supported in encrypted rooms
|
||||
if (self.roomDataSource.room.state.isEncrypted && self.roomDataSource.room.state.joinedMembers.count > 2)
|
||||
{
|
||||
[currentAlert dismissViewControllerAnimated:NO completion:nil];
|
||||
|
||||
currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"room_no_conference_call_in_encrypted_rooms"] message:nil preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
[currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action)
|
||||
{
|
||||
if (weakSelf)
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
self->currentAlert = nil;
|
||||
}
|
||||
|
||||
}]];
|
||||
|
||||
[currentAlert mxk_setAccessibilityIdentifier:@"RoomVCCallAlert"];
|
||||
[self presentViewController:currentAlert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
// In case of conference call, check that the user has enough power level
|
||||
else if (self.roomDataSource.room.state.joinedMembers.count > 2 &&
|
||||
![MXCallManager canPlaceConferenceCallInRoom:self.roomDataSource.room])
|
||||
{
|
||||
[currentAlert dismissViewControllerAnimated:NO completion:nil];
|
||||
|
||||
currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"room_no_power_to_create_conference_call"] message:nil preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
[currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * action)
|
||||
{
|
||||
if (weakSelf)
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
self->currentAlert = nil;
|
||||
}
|
||||
}]];
|
||||
|
||||
[currentAlert mxk_setAccessibilityIdentifier:@"RoomVCCallAlert"];
|
||||
[self presentViewController:currentAlert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
// Classic 1:1 or group call can be done
|
||||
else
|
||||
{
|
||||
[self.roomDataSource.room placeCallWithVideo:video success:nil failure:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2724,6 +2789,13 @@
|
|||
{
|
||||
[callInRoom hangup];
|
||||
}
|
||||
else if ([[AppDelegate theDelegate].jitsiViewController.widget.roomId isEqualToString:self.roomDataSource.roomId])
|
||||
{
|
||||
[[AppDelegate theDelegate].jitsiViewController hangup];
|
||||
}
|
||||
|
||||
[self refreshActivitiesViewDisplay];
|
||||
[self refreshRoomInputToolbar];
|
||||
}
|
||||
|
||||
- (void)roomInputToolbarView:(MXKRoomInputToolbarView*)toolbarView heightDidChanged:(CGFloat)height completion:(void (^)(BOOL finished))completion
|
||||
|
@ -3302,6 +3374,49 @@
|
|||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Widget notifications management
|
||||
|
||||
- (void)removeWidgetNotificationsListeners
|
||||
{
|
||||
if (kMXKWidgetManagerDidUpdateWidgetObserver)
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:kMXKWidgetManagerDidUpdateWidgetObserver];
|
||||
kMXKWidgetManagerDidUpdateWidgetObserver = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)listenWidgetNotifications
|
||||
{
|
||||
kMXKWidgetManagerDidUpdateWidgetObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kWidgetManagerDidUpdateWidgetNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
||||
|
||||
Widget *widget = notif.object;
|
||||
if (widget.mxSession == self.roomDataSource.mxSession
|
||||
&& [widget.roomId isEqualToString:customizedRoomDataSource.roomId])
|
||||
{
|
||||
// Jitsi conference widget existence is shown in the bottom bar
|
||||
// Update the bar
|
||||
[self refreshActivitiesViewDisplay];
|
||||
[self refreshRoomInputToolbar];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showJitsiErrorAsAlert:(NSError*)error
|
||||
{
|
||||
// Customise the error for permission issues
|
||||
if ([error.domain isEqualToString:WidgetManagerErrorDomain] && error.code == WidgetManagerErrorCodeNotEnoughPower)
|
||||
{
|
||||
error = [NSError errorWithDomain:error.domain
|
||||
code:error.code
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"room_conference_call_no_power", @"Vector", nil)
|
||||
}];
|
||||
}
|
||||
|
||||
// Alert user
|
||||
[[AppDelegate theDelegate] showErrorAsAlert:error];
|
||||
}
|
||||
|
||||
#pragma mark - Unreachable Network Handling
|
||||
|
||||
- (void)refreshActivitiesViewDisplay
|
||||
|
@ -3309,12 +3424,14 @@
|
|||
if ([self.activitiesView isKindOfClass:RoomActivitiesView.class])
|
||||
{
|
||||
RoomActivitiesView *roomActivitiesView = (RoomActivitiesView*)self.activitiesView;
|
||||
|
||||
|
||||
// Reset gesture recognizers
|
||||
while (roomActivitiesView.gestureRecognizers.count)
|
||||
{
|
||||
[roomActivitiesView removeGestureRecognizer:roomActivitiesView.gestureRecognizers[0]];
|
||||
}
|
||||
|
||||
Widget *jitsiWidget = [customizedRoomDataSource jitsiWidget];
|
||||
|
||||
if ([AppDelegate theDelegate].isOffline)
|
||||
{
|
||||
|
@ -3342,9 +3459,80 @@
|
|||
{
|
||||
[customizedRoomDataSource.room placeCallWithVideo:video success:nil failure:nil];
|
||||
}
|
||||
} onClosePressed:nil];
|
||||
}
|
||||
}
|
||||
#ifdef USE_JITSI_WIDGET
|
||||
else if (jitsiWidget)
|
||||
{
|
||||
// The room has an active jitsi widget
|
||||
// Show it in the banner if the user is not already in
|
||||
AppDelegate *appDelegate = [AppDelegate theDelegate];
|
||||
if ([appDelegate.jitsiViewController.widget.widgetId isEqualToString:jitsiWidget.widgetId])
|
||||
{
|
||||
if ([self checkUnsentMessages] == NO)
|
||||
{
|
||||
[self refreshTypingNotification];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[roomActivitiesView displayOngoingConferenceCall:^(BOOL video) {
|
||||
|
||||
NSLog(@"[RoomVC] onOngoingConferenceCallPressed (jitsi)");
|
||||
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
NSString *appDisplayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
|
||||
|
||||
// Check app permissions first
|
||||
[MXKTools checkAccessForCall:video
|
||||
manualChangeMessageForAudio:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"microphone_access_not_granted_for_call"], appDisplayName]
|
||||
manualChangeMessageForVideo:[NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"camera_access_not_granted_for_call"], appDisplayName]
|
||||
showPopUpInViewController:self completionHandler:^(BOOL granted) {
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
if (granted)
|
||||
{
|
||||
// Present the Jitsi view controller
|
||||
[appDelegate displayJitsiViewControllerWithWidget:jitsiWidget andVideo:video];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"[RoomVC] onOngoingConferenceCallPressed: Warning: The application does not have the perssion to join the call");
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
} onClosePressed:^{
|
||||
|
||||
[self startActivityIndicator];
|
||||
|
||||
// Close the widget
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
[[WidgetManager sharedManager] closeWidget:jitsiWidget.widgetId inRoom:self.roomDataSource.room success:^{
|
||||
|
||||
if (weakSelf)
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
[self stopActivityIndicator];
|
||||
|
||||
// The banner will automatically leave thanks to kWidgetManagerDidUpdateWidgetNotification
|
||||
}
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
if (weakSelf)
|
||||
{
|
||||
typeof(self) self = weakSelf;
|
||||
|
||||
[self showJitsiErrorAsAlert:error];
|
||||
[self stopActivityIndicator];
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if ([self checkUnsentMessages] == NO)
|
||||
{
|
||||
// Show "scroll to bottom" icon when the most recent message is not visible,
|
||||
|
|
|
@ -98,7 +98,10 @@ enum
|
|||
|
||||
enum
|
||||
{
|
||||
LABS_CRYPTO_INDEX = 0,
|
||||
#ifdef USE_JITSI_WIDGET
|
||||
LABS_MATRIX_APPS_INDEX = 0,
|
||||
#endif
|
||||
LABS_CRYPTO_INDEX,
|
||||
LABS_COUNT
|
||||
};
|
||||
|
||||
|
@ -1913,6 +1916,21 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
}
|
||||
else if (section == SETTINGS_SECTION_LABS_INDEX)
|
||||
{
|
||||
#ifdef USE_JITSI_WIDGET
|
||||
if (row == LABS_MATRIX_APPS_INDEX)
|
||||
{
|
||||
MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath];
|
||||
|
||||
labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_create_conference_with_jitsi", @"Vector", nil);
|
||||
labelAndSwitchCell.mxkSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:@"createConferenceCallsWithJitsi"];
|
||||
|
||||
[labelAndSwitchCell.mxkSwitch removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside];
|
||||
[labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleJitsiForConference:) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
cell = labelAndSwitchCell;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (row == LABS_CRYPTO_INDEX)
|
||||
{
|
||||
MXSession* session = [[AppDelegate theDelegate].mxSessions objectAtIndex:0];
|
||||
|
@ -2628,6 +2646,19 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)();
|
|||
}
|
||||
}
|
||||
|
||||
- (void)toggleJitsiForConference:(id)sender
|
||||
{
|
||||
if (sender && [sender isKindOfClass:UISwitch.class])
|
||||
{
|
||||
UISwitch *switchButton = (UISwitch*)sender;
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] setBool:switchButton.isOn forKey:@"createConferenceCallsWithJitsi"];
|
||||
[[NSUserDefaults standardUserDefaults] synchronize];
|
||||
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)toggleLabsEndToEndEncryption:(id)sender
|
||||
{
|
||||
if (sender && [sender isKindOfClass:UISwitch.class])
|
||||
|
|
104
Riot/ViewController/Widgets/JitsiViewController.h
Normal file
104
Riot/ViewController/Widgets/JitsiViewController.h
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
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 <MatrixKit/MatrixKit.h>
|
||||
|
||||
#import <JitsiMeet/JitsiMeet.h>
|
||||
|
||||
#import "WidgetManager.h"
|
||||
|
||||
@protocol JitsiViewControllerDelegate;
|
||||
|
||||
/**
|
||||
The `JitsiViewController` is a VC for specifically handling a jitsi widget using the
|
||||
jitsi-meet iOS SDK instead of displaying it in a webview like other scalar widgets.
|
||||
|
||||
https://github.com/jitsi/jitsi-meet/tree/master/ios
|
||||
*/
|
||||
@interface JitsiViewController : MXKViewController <JitsiMeetViewDelegate>
|
||||
|
||||
/**
|
||||
Returns the `UINib` object initialized for a `JitsiViewController`.
|
||||
|
||||
@return The initialized `UINib` object or `nil` if there were errors during initialization
|
||||
or the nib file could not be located.
|
||||
*/
|
||||
+ (UINib *)nib;
|
||||
|
||||
/**
|
||||
Creates and returns a new `JitsiViewController` object.
|
||||
|
||||
@discussion This is the designated initializer for programmatic instantiation.
|
||||
|
||||
@return An initialized `JitsiViewController` object if successful, `nil` otherwise.
|
||||
*/
|
||||
+ (instancetype)jitsiViewController;
|
||||
|
||||
/**
|
||||
Make jitsi-meet iOS SDK open the jitsi conference indicated by a jitsi widget.
|
||||
|
||||
@param widget the jitsi widget.
|
||||
@param video to indicate voice or video call.
|
||||
@return YES if the operation is successful.
|
||||
*/
|
||||
- (BOOL)openWidget:(Widget*)widget withVideo:(BOOL)video;
|
||||
|
||||
/**
|
||||
Hang up the jitsi conference call in progress.
|
||||
*/
|
||||
- (void)hangup;
|
||||
|
||||
/**
|
||||
The jitsi widget displayed by this `JitsiViewController`.
|
||||
*/
|
||||
@property (nonatomic, readonly) Widget *widget;
|
||||
|
||||
/**
|
||||
The delegate for the view controller.
|
||||
*/
|
||||
@property (nonatomic) id<JitsiViewControllerDelegate> delegate;
|
||||
|
||||
#pragma mark - Xib attributes
|
||||
|
||||
// The jitsi-meet SDK view
|
||||
@property (weak, nonatomic) IBOutlet JitsiMeetView *jitsiMeetView;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *backToAppButton;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
Delegate for `JitsiViewController` object
|
||||
*/
|
||||
@protocol JitsiViewControllerDelegate <NSObject>
|
||||
|
||||
/**
|
||||
Tells the delegate to dismiss the jitsi view controller.
|
||||
|
||||
@param jitsiViewController the jitsi view controller.
|
||||
@param completion the block to execute at the end of the operation.
|
||||
*/
|
||||
- (void)jitsiViewController:(JitsiViewController *)jitsiViewController dismissViewJitsiController:(void (^)())completion;
|
||||
|
||||
/**
|
||||
Tells the delegate to put the jitsi view controller in background.
|
||||
|
||||
@param jitsiViewController the jitsi view controller.
|
||||
@param completion the block to execute at the end of the operation.
|
||||
*/
|
||||
- (void)jitsiViewController:(JitsiViewController *)jitsiViewController goBackToApp:(void (^)())completion;
|
||||
|
||||
@end
|
147
Riot/ViewController/Widgets/JitsiViewController.m
Normal file
147
Riot/ViewController/Widgets/JitsiViewController.m
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
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 "JitsiViewController.h"
|
||||
|
||||
static const NSString *kJitsiServerUrl = @"https://jitsi.riot.im/";
|
||||
|
||||
@interface JitsiViewController ()
|
||||
{
|
||||
NSString *jitsiUrl;
|
||||
|
||||
BOOL video;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation JitsiViewController
|
||||
|
||||
#pragma mark - Class methods
|
||||
|
||||
+ (UINib *)nib
|
||||
{
|
||||
return [UINib nibWithNibName:NSStringFromClass(self.class)
|
||||
bundle:[NSBundle bundleForClass:self.class]];
|
||||
}
|
||||
|
||||
+ (instancetype)jitsiViewController
|
||||
{
|
||||
JitsiViewController *jitsiViewController = [[[self class] alloc] initWithNibName:NSStringFromClass(self.class)
|
||||
bundle:[NSBundle bundleForClass:self.class]];
|
||||
return jitsiViewController;
|
||||
}
|
||||
|
||||
- (BOOL)openWidget:(Widget*)widget withVideo:(BOOL)aVideo
|
||||
{
|
||||
video = aVideo;
|
||||
_widget = widget;
|
||||
|
||||
// Extract the jitsi conference id from the widget url
|
||||
NSString *confId;
|
||||
NSURL *url = [NSURL URLWithString:_widget.url];
|
||||
NSURLComponents *components = [[NSURLComponents new] initWithURL:url resolvingAgainstBaseURL:NO];
|
||||
NSArray *queryItems = [components queryItems];
|
||||
|
||||
for (NSURLQueryItem *item in queryItems)
|
||||
{
|
||||
if ([item.name isEqualToString:@"confId"])
|
||||
{
|
||||
confId = item.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// And build from it the url to use in jitsi-meet sdk
|
||||
if (confId)
|
||||
{
|
||||
jitsiUrl = [NSString stringWithFormat:@"%@%@", kJitsiServerUrl, confId];
|
||||
}
|
||||
|
||||
if (!jitsiUrl)
|
||||
{
|
||||
NSLog(@"[JitsiVC] Failed to load widget: %@", widget);
|
||||
}
|
||||
|
||||
return (jitsiUrl != nil);
|
||||
}
|
||||
|
||||
- (void)hangup
|
||||
{
|
||||
jitsiUrl = nil;
|
||||
|
||||
// It would have been nicer to ask JitsiMeetView but there is no api.
|
||||
// Dismissing the view controller and releasing it does the job for the moment
|
||||
if (_delegate)
|
||||
{
|
||||
[_delegate jitsiViewController:self dismissViewJitsiController:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
self.jitsiMeetView.delegate = self;
|
||||
|
||||
// Pass the URL to jitsi-meet sdk
|
||||
[self.jitsiMeetView loadURLObject: @{
|
||||
@"url": jitsiUrl,
|
||||
@"configOverwrite": @{
|
||||
@"startWithVideoMuted": @(!video)
|
||||
}
|
||||
}];
|
||||
|
||||
// TODO: Set up user info but it is not yet available in the jitsi-meet iOS SDK
|
||||
// See https://github.com/jitsi/jitsi-meet/issues/1880
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning
|
||||
{
|
||||
[super didReceiveMemoryWarning];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (IBAction)onBackToAppButtonPressed:(id)sender
|
||||
{
|
||||
if (_delegate)
|
||||
{
|
||||
[_delegate jitsiViewController:self goBackToApp:nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - JitsiMeetViewDelegate
|
||||
|
||||
- (void)conferenceFailed:(NSDictionary *)data
|
||||
{
|
||||
NSLog(@"[JitsiViewController] conferenceFailed - data: %@", data);
|
||||
}
|
||||
|
||||
- (void)conferenceLeft:(NSDictionary *)data
|
||||
{
|
||||
// The conference is over. Let the delegate close this view controller.
|
||||
if (_delegate)
|
||||
{
|
||||
[_delegate jitsiViewController:self dismissViewJitsiController:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do it ourself
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
65
Riot/ViewController/Widgets/JitsiViewController.xib
Normal file
65
Riot/ViewController/Widgets/JitsiViewController.xib
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="JitsiViewController">
|
||||
<connections>
|
||||
<outlet property="backToAppButton" destination="8tr-Cb-ue8" id="aUj-co-7JA"/>
|
||||
<outlet property="jitsiMeetView" destination="7hL-Cs-mak" id="7kR-Te-Klw"/>
|
||||
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7hL-Cs-mak" customClass="JitsiMeetView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8tr-Cb-ue8">
|
||||
<rect key="frame" x="10" y="5" width="44" height="44"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCBackToAppButton"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="3id-6Q-PUF"/>
|
||||
<constraint firstAttribute="width" constant="44" id="JGj-Jz-SbU"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="13"/>
|
||||
<color key="tintColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<inset key="titleEdgeInsets" minX="-69" minY="61" maxX="0.0" maxY="0.0"/>
|
||||
<state key="normal" image="back_icon.png">
|
||||
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<state key="highlighted">
|
||||
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="onBackToAppButtonPressed:" destination="-1" eventType="touchUpInside" id="2wo-nB-Rwd"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="7hL-Cs-mak" secondAttribute="trailing" id="8eH-2r-pjD"/>
|
||||
<constraint firstAttribute="bottom" secondItem="7hL-Cs-mak" secondAttribute="bottom" id="BAo-6X-ovC"/>
|
||||
<constraint firstItem="8tr-Cb-ue8" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" constant="5" id="FPS-wy-gK6"/>
|
||||
<constraint firstItem="8tr-Cb-ue8" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" constant="10" id="Xca-R4-1cu"/>
|
||||
<constraint firstItem="7hL-Cs-mak" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="s46-Fx-tT8"/>
|
||||
<constraint firstItem="7hL-Cs-mak" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="x3v-Xl-cKi"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="back_icon.png" width="12" height="23"/>
|
||||
</resources>
|
||||
</document>
|
|
@ -63,9 +63,10 @@
|
|||
|
||||
@param ongoingConferenceCallPressed the block called when the user clicks on the banner.
|
||||
video is YES if the user chose to join the conf in video mode.
|
||||
@param ongoingConferenceCallClosePressed the block called when the user clicks on the banner close button.
|
||||
nil means do not display a close button.
|
||||
*/
|
||||
- (void)displayOngoingConferenceCall:(void (^)(BOOL video))ongoingConferenceCallPressed;
|
||||
|
||||
- (void)displayOngoingConferenceCall:(void (^)(BOOL video))ongoingConferenceCallPressed onClosePressed:(void (^)(void))ongoingConferenceCallClosePressed;
|
||||
|
||||
/**
|
||||
Display a "scroll to bottom" icon.
|
||||
|
|
|
@ -211,17 +211,31 @@
|
|||
[self checkHeight:YES];
|
||||
}
|
||||
|
||||
- (void)displayOngoingConferenceCall:(void (^)(BOOL))onOngoingConferenceCallPressed
|
||||
- (void)displayOngoingConferenceCall:(void (^)(BOOL))onOngoingConferenceCallPressed onClosePressed:(void (^)(void))onOngoingConferenceCallClosePressed
|
||||
{
|
||||
[self reset];
|
||||
|
||||
objc_setAssociatedObject(self.messageTextView, "onOngoingConferenceCallPressed", [onOngoingConferenceCallPressed copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
|
||||
// Build the string to display in the banner
|
||||
NSString *onGoingConferenceCall =
|
||||
[NSString stringWithFormat:NSLocalizedStringFromTable(@"room_ongoing_conference_call", @"Vector", nil),
|
||||
NSLocalizedStringFromTable(@"voice", @"Vector", nil),
|
||||
NSLocalizedStringFromTable(@"video", @"Vector", nil)];
|
||||
NSString *onGoingConferenceCall;
|
||||
|
||||
if (!onOngoingConferenceCallClosePressed)
|
||||
{
|
||||
onGoingConferenceCall = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_ongoing_conference_call", @"Vector", nil),
|
||||
NSLocalizedStringFromTable(@"voice", @"Vector", nil),
|
||||
NSLocalizedStringFromTable(@"video", @"Vector", nil)];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Display the banner with a "Close it" string
|
||||
objc_setAssociatedObject(self.messageTextView, "onOngoingConferenceCallClosePressed", [onOngoingConferenceCallClosePressed copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
|
||||
onGoingConferenceCall = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_ongoing_conference_call_with_close", @"Vector", nil),
|
||||
NSLocalizedStringFromTable(@"voice", @"Vector", nil),
|
||||
NSLocalizedStringFromTable(@"video", @"Vector", nil),
|
||||
NSLocalizedStringFromTable(@"room_ongoing_conference_call_close", @"Vector", nil)];
|
||||
}
|
||||
|
||||
NSMutableAttributedString *onGoingConferenceCallAttibutedString = [[NSMutableAttributedString alloc] initWithString:onGoingConferenceCall];
|
||||
|
||||
|
@ -235,6 +249,14 @@
|
|||
[onGoingConferenceCallAttibutedString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:videoRange];
|
||||
[onGoingConferenceCallAttibutedString addAttribute:NSLinkAttributeName value:@"onOngoingConferenceCallWithVideoPressed" range:videoRange];
|
||||
|
||||
// Add a link on the "Close" string
|
||||
if (onOngoingConferenceCallClosePressed)
|
||||
{
|
||||
NSRange closeRange = [onGoingConferenceCall rangeOfString:NSLocalizedStringFromTable(@"room_ongoing_conference_call_close", @"Vector", nil)];
|
||||
[onGoingConferenceCallAttibutedString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:closeRange];
|
||||
[onGoingConferenceCallAttibutedString addAttribute:NSLinkAttributeName value:@"onOngoingConferenceCallClosePressed" range:closeRange];
|
||||
}
|
||||
|
||||
// Display the string in white on pink red
|
||||
NSRange wholeString = NSMakeRange(0, onGoingConferenceCallAttibutedString.length);
|
||||
[onGoingConferenceCallAttibutedString addAttribute:NSForegroundColorAttributeName value:kRiotPrimaryBgColor range:wholeString];
|
||||
|
@ -382,7 +404,16 @@
|
|||
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
else if ([[URL absoluteString] isEqualToString:@"onOngoingConferenceCallClosePressed"])
|
||||
{
|
||||
void (^onOngoingConferenceCallClosePressed)(BOOL) = objc_getAssociatedObject(self.messageTextView, "onOngoingConferenceCallClosePressed");
|
||||
if (onOngoingConferenceCallClosePressed)
|
||||
{
|
||||
onOngoingConferenceCallClosePressed(YES);
|
||||
}
|
||||
|
||||
return NO;
|
||||
} return YES;
|
||||
}
|
||||
|
||||
#pragma mark - UIGestureRecognizerDelegate
|
||||
|
|
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/Entypo.ttf
Normal file
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/Entypo.ttf
Normal file
Binary file not shown.
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/EvilIcons.ttf
Normal file
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/EvilIcons.ttf
Normal file
Binary file not shown.
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/FontAwesome.ttf
Normal file
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/FontAwesome.ttf
Normal file
Binary file not shown.
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/Foundation.ttf
Normal file
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/Foundation.ttf
Normal file
Binary file not shown.
18
Riot/libs/jitsi-meet/JitsiMeet.framework/Headers/JitsiMeet.h
Normal file
18
Riot/libs/jitsi-meet/JitsiMeet.framework/Headers/JitsiMeet.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty 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 <JitsiMeet/JitsiMeetView.h>
|
||||
#import <JitsiMeet/JitsiMeetViewDelegate.h>
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty 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/UIKit.h>
|
||||
|
||||
#import "JitsiMeetViewDelegate.h"
|
||||
|
||||
@interface JitsiMeetView : UIView
|
||||
|
||||
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
||||
|
||||
@property (nonatomic) BOOL welcomePageEnabled;
|
||||
|
||||
+ (BOOL)application:(UIApplication *_Nonnull)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary *_Nonnull)launchOptions;
|
||||
|
||||
+ (BOOL)application:(UIApplication * _Nonnull)application
|
||||
continueUserActivity:(NSUserActivity * _Nonnull)userActivity
|
||||
restorationHandler:(void (^ _Nullable)(NSArray * _Nullable))restorationHandler;
|
||||
|
||||
+ (BOOL)application:(UIApplication * _Nonnull)application
|
||||
openURL:(NSURL * _Nonnull)URL
|
||||
sourceApplication:(NSString * _Nullable)sourceApplication
|
||||
annotation:(id _Nullable)annotation;
|
||||
|
||||
- (void)loadURL:(NSURL * _Nullable)url;
|
||||
|
||||
- (void)loadURLObject:(NSDictionary * _Nullable)urlObject;
|
||||
|
||||
- (void)loadURLString:(NSString * _Nullable)urlString;
|
||||
|
||||
@end
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright @ 2017-present Atlassian Pty 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.
|
||||
*/
|
||||
|
||||
@protocol JitsiMeetViewDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Called when a joining a conference was unsuccessful or when there was an
|
||||
* error while in a conference.
|
||||
*
|
||||
* The {@code data} dictionary contains an "error" key describing the error and
|
||||
* a {@code url} key with the conference URL.
|
||||
*/
|
||||
- (void) conferenceFailed:(NSDictionary *)data;
|
||||
|
||||
/**
|
||||
* Called when a conference was joined.
|
||||
*
|
||||
* The {@code data} dictionary contains a {@code url} key with the conference
|
||||
* URL.
|
||||
*/
|
||||
- (void) conferenceJoined:(NSDictionary *)data;
|
||||
|
||||
/**
|
||||
* Called when a conference was left.
|
||||
*
|
||||
* The {@code data} dictionary contains a {@code url} key with the conference
|
||||
* URL.
|
||||
*/
|
||||
- (void) conferenceLeft:(NSDictionary *)data;
|
||||
|
||||
/**
|
||||
* Called before a conference is joined.
|
||||
*
|
||||
* The {@code data} dictionary contains a {@code url} key with the conference
|
||||
* URL.
|
||||
*/
|
||||
- (void) conferenceWillJoin:(NSDictionary *)data;
|
||||
|
||||
/**
|
||||
* Called before a conference is left.
|
||||
*
|
||||
* The {@code data} dictionary contains a {@code url} key with the conference
|
||||
* URL.
|
||||
*/
|
||||
- (void) conferenceWillLeave:(NSDictionary *)data;
|
||||
|
||||
@end
|
76
Riot/libs/jitsi-meet/JitsiMeet.framework/Info.plist
Normal file
76
Riot/libs/jitsi-meet/JitsiMeet.framework/Info.plist
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>16F73</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>JitsiMeet</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.jitsi.JitsiMeetSDK.ios</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>JitsiMeet</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>14E8301</string>
|
||||
<key>DTPlatformName</key>
|
||||
<string>iphoneos</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>10.3</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>14E8301</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>iphoneos10.3</string>
|
||||
<key>DTXcode</key>
|
||||
<string>0833</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>8E3004b</string>
|
||||
<key>JitsiMeetFonts</key>
|
||||
<array>
|
||||
<string>FontAwesome.ttf</string>
|
||||
<string>jitsi.ttf</string>
|
||||
</array>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>10.3</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>192.168.2.9.xip.io</key>
|
||||
<dict>
|
||||
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/Ionicons.ttf
Normal file
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/Ionicons.ttf
Normal file
Binary file not shown.
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/JitsiMeet
Executable file
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/JitsiMeet
Executable file
Binary file not shown.
Binary file not shown.
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/MaterialIcons.ttf
Normal file
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/MaterialIcons.ttf
Normal file
Binary file not shown.
|
@ -0,0 +1,6 @@
|
|||
framework module JitsiMeet {
|
||||
umbrella header "JitsiMeet.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/Octicons.ttf
Normal file
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/Octicons.ttf
Normal file
Binary file not shown.
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/SimpleLineIcons.ttf
Normal file
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/SimpleLineIcons.ttf
Normal file
Binary file not shown.
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/Zocial.ttf
Normal file
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/Zocial.ttf
Normal file
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 177 B |
Binary file not shown.
After Width: | Height: | Size: 273 B |
Binary file not shown.
After Width: | Height: | Size: 303 B |
Binary file not shown.
After Width: | Height: | Size: 439 B |
Binary file not shown.
After Width: | Height: | Size: 586 B |
1
Riot/libs/jitsi-meet/JitsiMeet.framework/ip.txt
Normal file
1
Riot/libs/jitsi-meet/JitsiMeet.framework/ip.txt
Normal file
|
@ -0,0 +1 @@
|
|||
192.168.2.9.xip.io
|
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/jitsi.ttf
Executable file
BIN
Riot/libs/jitsi-meet/JitsiMeet.framework/jitsi.ttf
Executable file
Binary file not shown.
110469
Riot/libs/jitsi-meet/JitsiMeet.framework/main.jsbundle
Normal file
110469
Riot/libs/jitsi-meet/JitsiMeet.framework/main.jsbundle
Normal file
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
®t»Ηη<>†ΩxV<78><56>ήGPƒmHΰ
|
10
Riot/libs/jitsi-meet/README.txt
Normal file
10
Riot/libs/jitsi-meet/README.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
jitsi-meet (https://github.com/jitsi/jitsi-meet) does not provide yet a pod for
|
||||
iOS SDK (https://github.com/jitsi/jitsi-meet/issues/1854). So, the framework is
|
||||
built as described below and it is temporarly added to the Xcode project.
|
||||
|
||||
How to build JitsiMeet.framework:
|
||||
- clone https://github.com/jitsi/jitsi-meet
|
||||
- build jitsi-meet following instruction at https://github.com/jitsi/jitsi-meet#building-the-sources
|
||||
- build it specifically for iOS using https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md#ios
|
||||
|
||||
Then, copy the generated JitsiMeet.framework here.
|
|
@ -1342,6 +1342,16 @@
|
|||
regulations.
|
||||
</pre>
|
||||
</li>
|
||||
<li>
|
||||
<b>jitsi-meet</b> (<a
|
||||
href="https://github.com/jitsi/jitsi-meet">https://github.com/jitsi/jitsi-meet</a>)
|
||||
<br/><br/>Jitsi Meet - Secure, Simple and Scalable Video Conferences.
|
||||
<br/><br/>Copyright @ 2017-present Atlassian Pty Ltd.
|
||||
<br/><br/>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:
|
||||
<br/><br/><a
|
||||
href="https://www.apache.org/licenses/LICENSE-2.0">https://www.apache.org/licenses/LICENSE-2.0</a>
|
||||
<br/><br/>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.
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in a new issue