Merge branch 'spaces_beta' into spaces_feature_unavailable
# Conflicts: # Riot/Modules/Application/LegacyAppDelegate.h
10
CHANGES.rst
|
@ -5,6 +5,15 @@ Changes to be released in next version
|
|||
*
|
||||
|
||||
🙌 Improvements
|
||||
* Jitsi: Use Jitsi server from homeserver's Well Known, if present, to create conferences (#3158).
|
||||
* RoomMemberDetailsVC: Enable / disable "Hide all messages from this user" from settings (#4281).
|
||||
* RoomVC: Show / Hide More and Report Content contextual menu from settings (#4285).
|
||||
* SettingsVC: Show / hide NSFW and decrypted content options from build settings (#4290).
|
||||
* RoomVC: Tweaked Scroll to Bottom FAB button (#4272).
|
||||
* DesignKit: Introduce a new framework to manage design components.
|
||||
* Add Jitsi widget remove banner for privileged users.
|
||||
* Update "Jump to unread" banner to a pill style button.
|
||||
* CallVC: Add transfer button.
|
||||
* Spaces: Hide spaces from room list and home but keep space invites (#4252).
|
||||
* Spaces: Show space invites and advertise that they are not available (#4277).
|
||||
* Advertise that spaces are not available when tapping on a space link or a space invite (#4279).
|
||||
|
@ -14,6 +23,7 @@ Changes to be released in next version
|
|||
* RiotSettings: Logging out resets RiotSettings (#4259).
|
||||
* RoomVC: Crash in `setScrollToBottomHidden` method (#4270).
|
||||
* Notifications: Make them work in debug mode (#4274).
|
||||
* VoIP: Fix call bar layout issue (#4300).
|
||||
|
||||
⚠️ API Changes
|
||||
*
|
||||
|
|
|
@ -180,7 +180,7 @@ final class BuildSettings: NSObject {
|
|||
"https://scalar-staging.riot.im/scalar/api",
|
||||
]
|
||||
// Jitsi server used outside integrations to create conference calls from the call button in the timeline
|
||||
static let jitsiServerUrl = NSURL(string: "https://jitsi.riot.im")
|
||||
static let jitsiServerUrl: URL = URL(string: "https://jitsi.riot.im")!
|
||||
|
||||
|
||||
// MARK: - Features
|
||||
|
@ -242,6 +242,8 @@ final class BuildSettings: NSObject {
|
|||
static let settingsScreenShowChangePassword:Bool = true
|
||||
static let settingsScreenShowInviteFriends:Bool = true
|
||||
static let settingsScreenShowEnableStunServerFallback: Bool = true
|
||||
static let settingsScreenShowNotificationDecodedContentOption: Bool = true
|
||||
static let settingsScreenShowNsfwRoomsOption: Bool = true
|
||||
static let settingsSecurityScreenShowSessions:Bool = true
|
||||
static let settingsSecurityScreenShowSetupBackup:Bool = true
|
||||
static let settingsSecurityScreenShowRestoreBackup:Bool = true
|
||||
|
@ -268,6 +270,12 @@ final class BuildSettings: NSObject {
|
|||
static let roomScreenAllowMediaLibraryAction: Bool = true
|
||||
static let roomScreenAllowStickerAction: Bool = true
|
||||
static let roomScreenAllowFilesAction: Bool = true
|
||||
|
||||
// MARK: - Room Contextual Menu
|
||||
|
||||
static let roomContextualMenuShowMoreOptionForMessages: Bool = true
|
||||
static let roomContextualMenuShowMoreOptionForStates: Bool = true
|
||||
static let roomContextualMenuShowReportContentOption: Bool = true
|
||||
|
||||
// MARK: - Room Info Screen
|
||||
|
||||
|
@ -284,6 +292,10 @@ final class BuildSettings: NSObject {
|
|||
static let roomSettingsScreenShowAdvancedSettings: Bool = true
|
||||
static let roomSettingsScreenAdvancedShowEncryptToVerifiedOption: Bool = true
|
||||
|
||||
// MARK: - Room Member Screen
|
||||
|
||||
static let roomMemberScreenShowIgnore: Bool = true
|
||||
|
||||
// MARK: - Message
|
||||
static let messageDetailsAllowShare: Bool = true
|
||||
static let messageDetailsAllowPermalink: Bool = true
|
||||
|
|
27
DesignKit/Common.xcconfig
Normal file
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// Copyright 2021 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.
|
||||
//
|
||||
|
||||
// Configuration settings file format documentation can be found at:
|
||||
// https://help.apple.com/xcode/#/dev745c5c974
|
||||
|
||||
#include "Config/AppIdentifiers.xcconfig"
|
||||
|
||||
PRODUCT_NAME = DesignKit
|
||||
PRODUCT_BUNDLE_IDENTIFIER = $(BASE_BUNDLE_IDENTIFIER).designkit
|
||||
|
||||
INFOPLIST_FILE = DesignKit/Info.plist
|
||||
|
||||
SKIP_INSTALL = YES
|
20
DesignKit/Debug.xcconfig
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// Copyright 2021 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.
|
||||
//
|
||||
|
||||
// Configuration settings file format documentation can be found at:
|
||||
// https://help.apple.com/xcode/#/dev745c5c974
|
||||
|
||||
#include "Common.xcconfig"
|
27
DesignKit/DesignKit.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for DesignKit.
|
||||
FOUNDATION_EXPORT double DesignKitVersionNumber;
|
||||
|
||||
//! Project version string for DesignKit.
|
||||
FOUNDATION_EXPORT const unsigned char DesignKitVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <DesignKit/PublicHeader.h>
|
||||
|
||||
|
22
DesignKit/Info.plist
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?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>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
</dict>
|
||||
</plist>
|
20
DesignKit/Release.xcconfig
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// Copyright 2021 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.
|
||||
//
|
||||
|
||||
// Configuration settings file format documentation can be found at:
|
||||
// https://help.apple.com/xcode/#/dev745c5c974
|
||||
|
||||
#include "Common.xcconfig"
|
64
DesignKit/Source/Colors.swift
Normal file
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// Colors at https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound?node-id=1255%3A1104
|
||||
@objc public protocol Colors {
|
||||
|
||||
/// - Focused/Active states
|
||||
/// - CTAs
|
||||
var accent: UIColor { get }
|
||||
|
||||
/// - Error messages
|
||||
/// - Content requiring user attention
|
||||
/// - Notification, alerts
|
||||
var alert: UIColor { get }
|
||||
|
||||
/// - Text
|
||||
/// - Icons
|
||||
var primaryContent: UIColor { get }
|
||||
|
||||
/// - Text
|
||||
/// - Icons
|
||||
var secondaryContent: UIColor { get }
|
||||
|
||||
/// - Text
|
||||
/// - Icons
|
||||
var tertiaryContent: UIColor { get }
|
||||
|
||||
/// - Text
|
||||
/// - Icons
|
||||
var quarterlyContent: UIColor { get }
|
||||
|
||||
/// Separating line
|
||||
var separator: UIColor { get }
|
||||
|
||||
// Cards, tiles
|
||||
var tile: UIColor { get }
|
||||
|
||||
/// Top navigation background on iOS
|
||||
var navigation: UIColor { get }
|
||||
|
||||
/// Background UI color
|
||||
var background: UIColor { get }
|
||||
|
||||
/// - Names in chat timeline
|
||||
/// - Avatars default states that include first name letter
|
||||
var namesAndAvatars: [UIColor] { get }
|
||||
|
||||
}
|
28
DesignKit/Source/ThemeV2.swift
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// Theme v2. May be named again as `Theme` when the migration completed.
|
||||
@objc public protocol ThemeV2 {
|
||||
|
||||
/// Colors object
|
||||
var colors: Colors { get }
|
||||
|
||||
/// may contain more design components in future, like icons, audio files etc.
|
||||
|
||||
}
|
56
DesignKit/Variants/Dark/DarkColors.swift
Normal file
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// Dark theme colors. Will be a struct when things are more Swifty.
|
||||
public class DarkColors: Colors {
|
||||
|
||||
public let accent: UIColor = UIColor(rgb: 0x0DBD8B)
|
||||
|
||||
public let alert: UIColor = UIColor(rgb: 0xFF4B55)
|
||||
|
||||
public let primaryContent: UIColor = UIColor(rgb: 0xFFFFFF)
|
||||
|
||||
public let secondaryContent: UIColor = UIColor(rgb: 0xA9B2BC)
|
||||
|
||||
public let tertiaryContent: UIColor = UIColor(rgb: 0x8E99A4)
|
||||
|
||||
public let quarterlyContent: UIColor = UIColor(rgb: 0x6F7882)
|
||||
|
||||
public let separator: UIColor = UIColor(rgb: 0x21262C)
|
||||
|
||||
public let tile: UIColor = UIColor(rgb: 0x394049)
|
||||
|
||||
public let navigation: UIColor = UIColor(rgb: 0x21262C)
|
||||
|
||||
public let background: UIColor = UIColor(rgb: 0x15191E)
|
||||
|
||||
public let namesAndAvatars: [UIColor] = [
|
||||
UIColor(rgb: 0x368BD6),
|
||||
UIColor(rgb: 0xAC3BA8),
|
||||
UIColor(rgb: 0x03B381),
|
||||
UIColor(rgb: 0xE64F7A),
|
||||
UIColor(rgb: 0xFF812D),
|
||||
UIColor(rgb: 0x2DC2C5),
|
||||
UIColor(rgb: 0x5C56F5),
|
||||
UIColor(rgb: 0x74D12C)
|
||||
]
|
||||
|
||||
public init() {}
|
||||
|
||||
}
|
56
DesignKit/Variants/Light/LightColors.swift
Normal file
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// Light theme colors. Will be a struct when things are more Swifty.
|
||||
public class LightColors: Colors {
|
||||
|
||||
public let accent: UIColor = UIColor(rgb: 0x0DBD8B)
|
||||
|
||||
public let alert: UIColor = UIColor(rgb: 0xFF4B55)
|
||||
|
||||
public let primaryContent: UIColor = UIColor(rgb: 0x17191C)
|
||||
|
||||
public let secondaryContent: UIColor = UIColor(rgb: 0x737D8C)
|
||||
|
||||
public let tertiaryContent: UIColor = UIColor(rgb: 0x8D97A5)
|
||||
|
||||
public let quarterlyContent: UIColor = UIColor(rgb: 0xC1C6CD)
|
||||
|
||||
public let separator: UIColor = UIColor(rgb: 0xE3E8F0)
|
||||
|
||||
public let tile: UIColor = UIColor(rgb: 0xF3F8FD)
|
||||
|
||||
public let navigation: UIColor = UIColor(rgb: 0xF4F6FA)
|
||||
|
||||
public let background: UIColor = UIColor(rgb: 0xFFFFFF)
|
||||
|
||||
public let namesAndAvatars: [UIColor] = [
|
||||
UIColor(rgb: 0x368BD6),
|
||||
UIColor(rgb: 0xAC3BA8),
|
||||
UIColor(rgb: 0x03B381),
|
||||
UIColor(rgb: 0xE64F7A),
|
||||
UIColor(rgb: 0xFF812D),
|
||||
UIColor(rgb: 0x2DC2C5),
|
||||
UIColor(rgb: 0x5C56F5),
|
||||
UIColor(rgb: 0x74D12C)
|
||||
]
|
||||
|
||||
public init() {}
|
||||
|
||||
}
|
33
DesignKit/target.yml
Normal file
|
@ -0,0 +1,33 @@
|
|||
name: DesignKit
|
||||
|
||||
schemes:
|
||||
DesignKit:
|
||||
analyze:
|
||||
config: Debug
|
||||
archive:
|
||||
config: Release
|
||||
build:
|
||||
targets:
|
||||
DesignKit:
|
||||
- running
|
||||
- profiling
|
||||
- analyzing
|
||||
- archiving
|
||||
profile:
|
||||
config: Release
|
||||
run:
|
||||
config: Debug
|
||||
disableMainThreadChecker: true
|
||||
|
||||
targets:
|
||||
DesignKit:
|
||||
type: framework
|
||||
platform: iOS
|
||||
|
||||
configFiles:
|
||||
Debug: Debug.xcconfig
|
||||
Release: Release.xcconfig
|
||||
|
||||
sources:
|
||||
- path: .
|
||||
- path: ../Riot/Categories/UIColor.swift
|
|
@ -68,7 +68,7 @@ If you want to modify MatrixKit and/or MatrixSDK locally and see the result in E
|
|||
But before you have to checkout [MatrixKit](https://github.com/matrix-org/matrix-ios-kit) repository in `../matrix-ios-kit` and [MatrixSDK](https://github.com/matrix-org/matrix-ios-sdk) in `../matrix-ios-sdk` locally relatively to your Element iOS project folder.
|
||||
Be sure to use compatible branches for Element iOS, MatrixKit and MatrixSDK. For example, if you want to modify Element iOS from develop branch, use MatrixKit and MatrixSDK develop branches and then make your modifications.
|
||||
|
||||
**Important**: By working with local pods (development pods) you will need to use legacy build system in Xcode, to have your local changes taken into account. To enable it go to Xcode menu and select `File > Workspace Settings… > Build System` and then choose `Legacy Build System`.
|
||||
**Important**: By working with [XcodeGen](https://github.com/yonaskolb/XcodeGen) you will need to use the _New Build System_ in Xcode, to have your some of the xcconfig variables taken into account. It should be enabled by default on the latest Xcode versions, but if you need to enable it go to Xcode menu and select `File > Workspace Settings… > Build System` and then choose `New Build System`.
|
||||
|
||||
### `$matrixKitVersion` Modification
|
||||
|
||||
|
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 15 KiB |
|
@ -19,5 +19,8 @@
|
|||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
|
|
23
Riot/Assets/Images.xcassets/Call/call_go_to_chat_icon.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "back.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "back@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "back@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Call/call_go_to_chat_icon.imageset/back.png
vendored
Normal file
After Width: | Height: | Size: 403 B |
BIN
Riot/Assets/Images.xcassets/Call/call_go_to_chat_icon.imageset/back@2x.png
vendored
Normal file
After Width: | Height: | Size: 794 B |
BIN
Riot/Assets/Images.xcassets/Call/call_go_to_chat_icon.imageset/back@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 13 KiB |
|
@ -19,5 +19,8 @@
|
|||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 13 KiB |
|
@ -19,8 +19,5 @@
|
|||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 370 B |
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 647 B |
Before Width: | Height: | Size: 888 B After Width: | Height: | Size: 686 B |
BIN
Riot/Assets/Images.xcassets/Room/Activities/new_close.imageset/Close.png
vendored
Normal file
After Width: | Height: | Size: 370 B |
BIN
Riot/Assets/Images.xcassets/Room/Activities/new_close.imageset/Close@2x.png
vendored
Normal file
After Width: | Height: | Size: 509 B |
BIN
Riot/Assets/Images.xcassets/Room/Activities/new_close.imageset/Close@3x.png
vendored
Normal file
After Width: | Height: | Size: 772 B |
26
Riot/Assets/Images.xcassets/Room/Activities/new_close.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Close.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Close@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Close@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
26
Riot/Assets/Images.xcassets/Room/Activities/room_scroll_up.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Up.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Up@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Up@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "original"
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Room/Activities/room_scroll_up.imageset/Up.png
vendored
Normal file
After Width: | Height: | Size: 609 B |
BIN
Riot/Assets/Images.xcassets/Room/Activities/room_scroll_up.imageset/Up@2x.png
vendored
Normal file
After Width: | Height: | Size: 1,015 B |
BIN
Riot/Assets/Images.xcassets/Room/Activities/room_scroll_up.imageset/Up@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 588 B After Width: | Height: | Size: 735 B |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 776 B After Width: | Height: | Size: 958 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2.3 KiB |
|
@ -20,8 +20,6 @@
|
|||
<false/>
|
||||
<key>syncLocalContacts</key>
|
||||
<false/>
|
||||
<key>createConferenceCallsWithJitsi</key>
|
||||
<true/>
|
||||
<key>enableRageShake</key>
|
||||
<true/>
|
||||
<key>maxAllowedMediaCacheSize</key>
|
||||
|
@ -34,5 +32,7 @@
|
|||
<integer>15020851</integer>
|
||||
<key>enableBotCreation</key>
|
||||
<false/>
|
||||
<key>enableRingingForGroupCalls</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "cancel.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "cancel@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "cancel@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,6 +123,12 @@
|
|||
/* Incoming named video conference invite from a specific person */
|
||||
"VIDEO_CONF_NAMED_FROM_USER" = "Video group call from %@: '%@'";
|
||||
|
||||
/* A user added a Jitsi call to a room */
|
||||
"GROUP_CALL_STARTED" = "Group call started";
|
||||
|
||||
/* Group call from user, CallKit caller name */
|
||||
"GROUP_CALL_FROM_USER" = "%@ (Group call)";
|
||||
|
||||
/** Key verification **/
|
||||
|
||||
"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ wants to verify";
|
||||
|
|
|
@ -65,12 +65,13 @@
|
|||
"less" = "Less";
|
||||
|
||||
// Call Bar
|
||||
"callbar_only_single_active" = "Active call (%@)";
|
||||
"callbar_only_single_active" = "Tap to return to the call (%@)";
|
||||
"callbar_active_and_single_paused" = "1 active call (%@) · 1 paused call";
|
||||
"callbar_active_and_multiple_paused" = "1 active call (%@) · %@ paused calls";
|
||||
"callbar_only_single_paused" = "Paused call";
|
||||
"callbar_only_multiple_paused" = "%@ paused calls";
|
||||
"callbar_return" = "Return";
|
||||
"callbar_only_single_active_group" = "Tap to Join the group call (%@)";
|
||||
|
||||
// Accessibility
|
||||
"accessibility_checkbox_label" = "checkbox";
|
||||
|
@ -312,7 +313,8 @@ Tap the + to start adding people.";
|
|||
"room_member_power_level_short_custom" = "Custom";
|
||||
|
||||
// Chat
|
||||
"room_jump_to_first_unread" = "Jump to first unread message";
|
||||
"room_slide_to_end_group_call" = "Slide to end the call for everyone";
|
||||
"room_jump_to_first_unread" = "Jump to unread";
|
||||
"room_accessiblity_scroll_to_bottom" = "Scroll to bottom";
|
||||
"room_new_message_notification" = "%d new message";
|
||||
"room_new_messages_notification" = "%d new messages";
|
||||
|
@ -395,6 +397,8 @@ Tap the + to start adding people.";
|
|||
"room_accessibility_hangup" = "Hang up";
|
||||
"room_place_voice_call" = "Voice call";
|
||||
"room_open_dialpad" = "Dial pad";
|
||||
"room_join_group_call" = "Join";
|
||||
"room_no_privileges_to_create_group_call" = "You need to be an admin or a moderator to start a call.";
|
||||
|
||||
"media_type_accessibility_image" = "Image";
|
||||
"media_type_accessibility_audio" = "Audio";
|
||||
|
@ -526,6 +530,7 @@ Tap the + to start adding people.";
|
|||
"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_labs_message_reaction" = "React to messages with emoji";
|
||||
"settings_labs_enable_ringing_for_group_calls" = "Ring for group calls";
|
||||
|
||||
"settings_version" = "Version %@";
|
||||
"settings_olm_version" = "Olm Version %@";
|
||||
|
@ -823,10 +828,22 @@ Tap the + to start adding people.";
|
|||
"event_formatter_message_edited_mention" = "(edited)";
|
||||
"event_formatter_call_voice" = "Voice call";
|
||||
"event_formatter_call_video" = "Video call";
|
||||
"event_formatter_call_has_ended" = "This call has ended";
|
||||
"event_formatter_call_you_currently_in" = "You're currently in this call";
|
||||
"event_formatter_call_connecting" = "Connecting…";
|
||||
"event_formatter_call_ringing" = "Ringing…";
|
||||
"event_formatter_call_has_ended" = "Ended %@";
|
||||
"event_formatter_call_you_currently_in" = "Active call";
|
||||
"event_formatter_call_you_declined" = "You declined this call";
|
||||
"event_formatter_call_you_missed" = "You missed this call";
|
||||
"event_formatter_call_connection_failed" = "Connection failed";
|
||||
"event_formatter_call_back" = "Call back";
|
||||
"event_formatter_call_decline" = "Decline";
|
||||
"event_formatter_call_answer" = "Answer";
|
||||
"event_formatter_call_retry" = "Retry";
|
||||
"event_formatter_call_end_call" = "End call";
|
||||
"event_formatter_group_call" = "Group call";
|
||||
"event_formatter_group_call_join" = "Join";
|
||||
"event_formatter_group_call_leave" = "Leave";
|
||||
"event_formatter_group_call_incoming" = "%@ in %@";
|
||||
|
||||
// Events formatter with you
|
||||
"event_formatter_widget_added_by_you" = "You added the widget: %@";
|
||||
|
|
|
@ -58,13 +58,6 @@ extern NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestAcceptPre
|
|||
*/
|
||||
extern NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed;
|
||||
|
||||
/**
|
||||
Action identifier used when the user pressed "Call back" button for a declined call.
|
||||
|
||||
The `userInfo` dictionary contains an `MXEvent` object under the `kMXKRoomBubbleCellEventKey` key, representing the invite event of the declined call.
|
||||
*/
|
||||
extern NSString *const kMXKRoomBubbleCellCallBackButtonPressed;
|
||||
|
||||
/**
|
||||
Define a `MXKRoomBubbleTableViewCell` category at Riot level to handle bubble customisation.
|
||||
*/
|
||||
|
|
|
@ -33,7 +33,6 @@ NSString *const kMXKRoomBubbleCellLongPressOnReactionView = @"kMXKRoomBubbleCell
|
|||
NSString *const kMXKRoomBubbleCellEventIdKey = @"kMXKRoomBubbleCellEventIdKey";
|
||||
NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestAcceptPressed = @"kMXKRoomBubbleCellKeyVerificationAcceptPressed";
|
||||
NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed = @"kMXKRoomBubbleCellKeyVerificationDeclinePressed";
|
||||
NSString *const kMXKRoomBubbleCellCallBackButtonPressed = @"kMXKRoomBubbleCellCallBackButtonPressed";
|
||||
|
||||
@implementation MXKRoomBubbleTableViewCell (Riot)
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
#import <MatrixSDK/MXSession.h>
|
||||
|
||||
@class HomeserverConfiguration;
|
||||
|
||||
@interface MXSession (Riot)
|
||||
|
||||
/**
|
||||
|
@ -26,15 +28,9 @@
|
|||
- (NSUInteger)vc_missedDiscussionsCount;
|
||||
|
||||
/**
|
||||
Check if E2E by default is welcomed on the user's HS.
|
||||
The default value is YES.
|
||||
|
||||
HS admins can disable it in /.well-known/matrix/client by returning:
|
||||
"im.vector.riot.e2ee": {
|
||||
"default": false
|
||||
}
|
||||
*/
|
||||
- (BOOL)vc_isE2EByDefaultEnabledByHSAdmin;
|
||||
Return the homeserver configuration based on HS Well-Known or BuildSettings properties according to existing values.
|
||||
*/
|
||||
- (HomeserverConfiguration*)vc_homeserverConfiguration;
|
||||
|
||||
/**
|
||||
Riot version of [MXSession canEnableE2EByDefaultInNewRoomWithUsers:]
|
||||
|
|
|
@ -49,25 +49,17 @@
|
|||
return missedDiscussionsCount;
|
||||
}
|
||||
|
||||
- (BOOL)vc_isE2EByDefaultEnabledByHSAdmin
|
||||
- (HomeserverConfiguration*)vc_homeserverConfiguration
|
||||
{
|
||||
BOOL isE2EByDefaultEnabledByHSAdmin = YES;
|
||||
|
||||
MXWellKnown *wellKnown = self.homeserverWellknown;
|
||||
|
||||
if (wellKnown.JSONDictionary[@"im.vector.riot.e2ee"][@"default"])
|
||||
{
|
||||
MXJSONModelSetBoolean(isE2EByDefaultEnabledByHSAdmin, wellKnown.JSONDictionary[@"im.vector.riot.e2ee"][@"default"]);
|
||||
}
|
||||
|
||||
return isE2EByDefaultEnabledByHSAdmin;
|
||||
HomeserverConfigurationBuilder *configurationBuilder = [HomeserverConfigurationBuilder new];
|
||||
return [configurationBuilder buildFrom:self.homeserverWellknown];
|
||||
}
|
||||
|
||||
- (MXHTTPOperation*)vc_canEnableE2EByDefaultInNewRoomWithUsers:(NSArray<NSString*>*)userIds
|
||||
success:(void (^)(BOOL canEnableE2E))success
|
||||
failure:(void (^)(NSError *error))failure;
|
||||
{
|
||||
if (self.vc_isE2EByDefaultEnabledByHSAdmin)
|
||||
if ([self vc_homeserverConfiguration].isE2EEByDefaultEnabled)
|
||||
{
|
||||
return [self canEnableE2EByDefaultInNewRoomWithUsers:userIds success:success failure:failure];
|
||||
}
|
||||
|
|
|
@ -64,4 +64,16 @@ extension UIView {
|
|||
self.accessibilityTraits.insert(.notEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func vc_addShadow(withColor color: UIColor, offset: CGSize, radius: CGFloat, opacity: CGFloat) {
|
||||
layer.shadowColor = color.cgColor
|
||||
layer.shadowOpacity = Float(opacity)
|
||||
layer.shadowRadius = radius
|
||||
layer.shadowOffset = offset
|
||||
}
|
||||
|
||||
@objc func vc_removeShadow() {
|
||||
layer.shadowColor = UIColor.clear.cgColor
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ internal enum Asset {
|
|||
internal static let callChatIcon = ImageAsset(name: "call_chat_icon")
|
||||
internal static let callDialpadBackspaceIcon = ImageAsset(name: "call_dialpad_backspace_icon")
|
||||
internal static let callDialpadCallIcon = ImageAsset(name: "call_dialpad_call_icon")
|
||||
internal static let callGoToChatIcon = ImageAsset(name: "call_go_to_chat_icon")
|
||||
internal static let callHangupLarge = ImageAsset(name: "call_hangup_large")
|
||||
internal static let callMoreIcon = ImageAsset(name: "call_more_icon")
|
||||
internal static let callPausedIcon = ImageAsset(name: "call_paused_icon")
|
||||
|
@ -104,7 +105,9 @@ internal enum Asset {
|
|||
internal static let actionSticker = ImageAsset(name: "action_sticker")
|
||||
internal static let error = ImageAsset(name: "error")
|
||||
internal static let errorMessageTick = ImageAsset(name: "error_message_tick")
|
||||
internal static let newClose = ImageAsset(name: "new_close")
|
||||
internal static let roomActivitiesRetry = ImageAsset(name: "room_activities_retry")
|
||||
internal static let roomScrollUp = ImageAsset(name: "room_scroll_up")
|
||||
internal static let scrolldown = ImageAsset(name: "scrolldown")
|
||||
internal static let scrolldownDark = ImageAsset(name: "scrolldown_dark")
|
||||
internal static let sendingMessageTick = ImageAsset(name: "sending_message_tick")
|
||||
|
|
|
@ -502,10 +502,14 @@ internal enum VectorL10n {
|
|||
internal static func callbarOnlyMultiplePaused(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Vector", "callbar_only_multiple_paused", p1)
|
||||
}
|
||||
/// Active call (%@)
|
||||
/// Tap to return to the call (%@)
|
||||
internal static func callbarOnlySingleActive(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Vector", "callbar_only_single_active", p1)
|
||||
}
|
||||
/// Tap to Join the group call (%@)
|
||||
internal static func callbarOnlySingleActiveGroup(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Vector", "callbar_only_single_active_group", p1)
|
||||
}
|
||||
/// Paused call
|
||||
internal static var callbarOnlySinglePaused: String {
|
||||
return VectorL10n.tr("Vector", "callbar_only_single_paused")
|
||||
|
@ -1238,13 +1242,41 @@ internal enum VectorL10n {
|
|||
internal static var errorUserAlreadyLoggedIn: String {
|
||||
return VectorL10n.tr("Vector", "error_user_already_logged_in")
|
||||
}
|
||||
/// Answer
|
||||
internal static var eventFormatterCallAnswer: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_answer")
|
||||
}
|
||||
/// Call back
|
||||
internal static var eventFormatterCallBack: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_back")
|
||||
}
|
||||
/// This call has ended
|
||||
internal static var eventFormatterCallHasEnded: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_has_ended")
|
||||
/// Connecting…
|
||||
internal static var eventFormatterCallConnecting: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_connecting")
|
||||
}
|
||||
/// Connection failed
|
||||
internal static var eventFormatterCallConnectionFailed: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_connection_failed")
|
||||
}
|
||||
/// Decline
|
||||
internal static var eventFormatterCallDecline: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_decline")
|
||||
}
|
||||
/// End call
|
||||
internal static var eventFormatterCallEndCall: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_end_call")
|
||||
}
|
||||
/// Ended %@
|
||||
internal static func eventFormatterCallHasEnded(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_has_ended", p1)
|
||||
}
|
||||
/// Retry
|
||||
internal static var eventFormatterCallRetry: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_retry")
|
||||
}
|
||||
/// Ringing…
|
||||
internal static var eventFormatterCallRinging: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_ringing")
|
||||
}
|
||||
/// Video call
|
||||
internal static var eventFormatterCallVideo: String {
|
||||
|
@ -1254,7 +1286,7 @@ internal enum VectorL10n {
|
|||
internal static var eventFormatterCallVoice: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_voice")
|
||||
}
|
||||
/// You're currently in this call
|
||||
/// Active call
|
||||
internal static var eventFormatterCallYouCurrentlyIn: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_you_currently_in")
|
||||
}
|
||||
|
@ -1262,6 +1294,26 @@ internal enum VectorL10n {
|
|||
internal static var eventFormatterCallYouDeclined: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_you_declined")
|
||||
}
|
||||
/// You missed this call
|
||||
internal static var eventFormatterCallYouMissed: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_call_you_missed")
|
||||
}
|
||||
/// Group call
|
||||
internal static var eventFormatterGroupCall: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_group_call")
|
||||
}
|
||||
/// %@ in %@
|
||||
internal static func eventFormatterGroupCallIncoming(_ p1: String, _ p2: String) -> String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_group_call_incoming", p1, p2)
|
||||
}
|
||||
/// Join
|
||||
internal static var eventFormatterGroupCallJoin: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_group_call_join")
|
||||
}
|
||||
/// Leave
|
||||
internal static var eventFormatterGroupCallLeave: String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_group_call_leave")
|
||||
}
|
||||
/// VoIP conference added by %@
|
||||
internal static func eventFormatterJitsiWidgetAdded(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Vector", "event_formatter_jitsi_widget_added", p1)
|
||||
|
@ -2874,7 +2926,11 @@ internal enum VectorL10n {
|
|||
internal static var roomIntroCellInformationRoomWithoutTopicSentence2Part2: String {
|
||||
return VectorL10n.tr("Vector", "room_intro_cell_information_room_without_topic_sentence2_part2")
|
||||
}
|
||||
/// Jump to first unread message
|
||||
/// Join
|
||||
internal static var roomJoinGroupCall: String {
|
||||
return VectorL10n.tr("Vector", "room_join_group_call")
|
||||
}
|
||||
/// Jump to unread
|
||||
internal static var roomJumpToFirstUnread: String {
|
||||
return VectorL10n.tr("Vector", "room_jump_to_first_unread")
|
||||
}
|
||||
|
@ -2950,6 +3006,10 @@ internal enum VectorL10n {
|
|||
internal static func roomNewMessagesNotification(_ p1: Int) -> String {
|
||||
return VectorL10n.tr("Vector", "room_new_messages_notification", p1)
|
||||
}
|
||||
/// You need to be an admin or a moderator to start a call.
|
||||
internal static var roomNoPrivilegesToCreateGroupCall: String {
|
||||
return VectorL10n.tr("Vector", "room_no_privileges_to_create_group_call")
|
||||
}
|
||||
/// Connectivity to the server has been lost.
|
||||
internal static var roomOfflineNotification: String {
|
||||
return VectorL10n.tr("Vector", "room_offline_notification")
|
||||
|
@ -3330,6 +3390,10 @@ internal enum VectorL10n {
|
|||
internal static var roomResourceUsageLimitReachedMessageContact3: String {
|
||||
return VectorL10n.tr("Vector", "room_resource_usage_limit_reached_message_contact_3")
|
||||
}
|
||||
/// Slide to end the call for everyone
|
||||
internal static var roomSlideToEndGroupCall: String {
|
||||
return VectorL10n.tr("Vector", "room_slide_to_end_group_call")
|
||||
}
|
||||
/// Invite members
|
||||
internal static var roomTitleInviteMembers: String {
|
||||
return VectorL10n.tr("Vector", "room_title_invite_members")
|
||||
|
@ -4230,6 +4294,10 @@ internal enum VectorL10n {
|
|||
internal static var settingsLabsE2eEncryptionPromptMessage: String {
|
||||
return VectorL10n.tr("Vector", "settings_labs_e2e_encryption_prompt_message")
|
||||
}
|
||||
/// Ring for group calls
|
||||
internal static var settingsLabsEnableRingingForGroupCalls: String {
|
||||
return VectorL10n.tr("Vector", "settings_labs_enable_ringing_for_group_calls")
|
||||
}
|
||||
/// React to messages with emoji
|
||||
internal static var settingsLabsMessageReaction: String {
|
||||
return VectorL10n.tr("Vector", "settings_labs_message_reaction")
|
||||
|
@ -4498,7 +4566,7 @@ internal enum VectorL10n {
|
|||
internal static var spaceBetaAnnounceBadge: String {
|
||||
return VectorL10n.tr("Vector", "space_beta_announce_badge")
|
||||
}
|
||||
/// Spaces are a new way to group rooms and people. They’re not on iOS yet, but you can use them now on Web and Desktop.
|
||||
/// Spaces are a new way to group rooms and people. They’re not on iOS yet, but you can use them now on Web and Desktop.
|
||||
internal static var spaceBetaAnnounceInformation: String {
|
||||
return VectorL10n.tr("Vector", "space_beta_announce_information")
|
||||
}
|
||||
|
|
|
@ -17,39 +17,67 @@
|
|||
import Foundation
|
||||
import MatrixKit
|
||||
|
||||
// swiftlint:disable file_length
|
||||
|
||||
#if canImport(JitsiMeetSDK)
|
||||
import JitsiMeetSDK
|
||||
import CallKit
|
||||
#endif
|
||||
|
||||
/// The number of milliseconds in one second.
|
||||
private let MSEC_PER_SEC: TimeInterval = 1000
|
||||
|
||||
@objcMembers
|
||||
/// Service to manage call screens and call bar UI management.
|
||||
class CallPresenter: NSObject {
|
||||
|
||||
private enum Constants {
|
||||
static let pipAnimationDuration: TimeInterval = 0.25
|
||||
static let groupCallInviteLifetime: TimeInterval = 30
|
||||
}
|
||||
|
||||
/// Utilized sessions
|
||||
private var sessions: [MXSession] = []
|
||||
/// Call view controllers map. Keys are callIds.
|
||||
private var callVCs: [String: CallViewController] = [:]
|
||||
/// Call background tasks map. Keys are callIds.
|
||||
private var callBackgroundTasks: [String: MXBackgroundTask] = [:]
|
||||
private weak var presentedCallVC: CallViewController? {
|
||||
/// Actively presented direct call view controller.
|
||||
private weak var presentedCallVC: UIViewController? {
|
||||
didSet {
|
||||
updateOnHoldCall()
|
||||
}
|
||||
}
|
||||
private weak var inBarCallVC: CallViewController?
|
||||
private weak var pipCallVC: CallViewController?
|
||||
private weak var inBarCallVC: UIViewController?
|
||||
private weak var pipCallVC: UIViewController?
|
||||
/// UI operation queue for various UI operations
|
||||
private var uiOperationQueue: OperationQueue = .main
|
||||
/// Flag to indicate whether the presenter is active.
|
||||
private var isStarted: Bool = false
|
||||
private var callTimer: Timer?
|
||||
#if canImport(JitsiMeetSDK)
|
||||
private var widgetEventsListener: Any?
|
||||
/// Jitsi calls map. Keys are CallKit call UUIDs, values are corresponding widgets.
|
||||
private var jitsiCalls: [UUID: Widget] = [:]
|
||||
/// The current Jitsi view controller being displayed or not.
|
||||
private(set) var jitsiVC: JitsiViewController? {
|
||||
didSet {
|
||||
updateOnHoldCall()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private var isCallKitEnabled: Bool {
|
||||
MXCallKitAdapter.callKitAvailable() && MXKAppSettings.standard()?.isCallKitEnabled == true
|
||||
}
|
||||
|
||||
private var activeCallVC: CallViewController? {
|
||||
private var activeCallVC: UIViewController? {
|
||||
return callVCs.values.filter { (callVC) -> Bool in
|
||||
guard let call = callVC.mxCall else {
|
||||
return false
|
||||
}
|
||||
return !call.isOnHold
|
||||
}.first
|
||||
}.first ?? jitsiVC
|
||||
}
|
||||
|
||||
private var onHoldCallVCs: [CallViewController] {
|
||||
|
@ -101,20 +129,201 @@ class CallPresenter: NSObject {
|
|||
}
|
||||
|
||||
/// Method to be called when the call status bar is tapped.
|
||||
/// - Returns: If the user interaction handled or not
|
||||
func callStatusBarButtonTapped() -> Bool {
|
||||
if let callVC = inBarCallVC ?? activeCallVC {
|
||||
func callStatusBarTapped() {
|
||||
if let callVC = (inBarCallVC ?? activeCallVC) as? CallViewController {
|
||||
dismissCallBar(for: callVC)
|
||||
presentCallVC(callVC)
|
||||
return true
|
||||
return
|
||||
}
|
||||
if let jitsiVC = jitsiVC {
|
||||
dismissCallBar(for: jitsiVC)
|
||||
presentCallVC(jitsiVC)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK - Group Calls
|
||||
|
||||
/// Open the Jitsi view controller from a widget.
|
||||
/// - Parameter widget: the jitsi widget
|
||||
func displayJitsiCall(withWidget widget: Widget) {
|
||||
#if canImport(JitsiMeetSDK)
|
||||
let createJitsiBlock = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.jitsiVC = JitsiViewController()
|
||||
self.jitsiVC?.openWidget(widget, withVideo: true, success: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
if let jitsiVC = self.jitsiVC {
|
||||
jitsiVC.delegate = self
|
||||
self.presentCallVC(jitsiVC)
|
||||
self.startJitsiCall(withWidget: widget)
|
||||
}
|
||||
}, failure: { [weak self] (error) in
|
||||
guard let self = self else { return }
|
||||
self.jitsiVC = nil
|
||||
AppDelegate.theDelegate().showAlert(withTitle: nil,
|
||||
message: VectorL10n.callJitsiError)
|
||||
})
|
||||
}
|
||||
|
||||
if let jitsiVC = jitsiVC {
|
||||
if jitsiVC.widget.widgetId == widget.widgetId {
|
||||
self.presentCallVC(jitsiVC)
|
||||
} else {
|
||||
// end previous Jitsi call first
|
||||
endActiveJitsiCall()
|
||||
createJitsiBlock()
|
||||
}
|
||||
} else {
|
||||
createJitsiBlock()
|
||||
}
|
||||
#else
|
||||
AppDelegate.theDelegate().showAlert(withTitle: nil,
|
||||
message: Bundle.mxk_localizedString(forKey: "not_supported_yet"))
|
||||
#endif
|
||||
}
|
||||
|
||||
private func startJitsiCall(withWidget widget: Widget) {
|
||||
if self.jitsiCalls.first(where: { $0.value.widgetId == widget.widgetId })?.key != nil {
|
||||
// this Jitsi call is already managed by this class, no need to report the call again
|
||||
return
|
||||
}
|
||||
|
||||
guard let roomId = widget.roomId else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let session = sessions.first else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let room = session.room(withRoomId: roomId) else {
|
||||
return
|
||||
}
|
||||
|
||||
let newUUID = UUID()
|
||||
let handle = CXHandle(type: .generic, value: roomId)
|
||||
let startCallAction = CXStartCallAction(call: newUUID, handle: handle)
|
||||
let transaction = CXTransaction(action: startCallAction)
|
||||
JMCallKitProxy.request(transaction) { (error) in
|
||||
if error == nil {
|
||||
JMCallKitProxy.reportCallUpdate(with: newUUID,
|
||||
handle: roomId,
|
||||
displayName: room.summary.displayname,
|
||||
hasVideo: true)
|
||||
JMCallKitProxy.reportOutgoingCall(with: newUUID, connectedAt: nil)
|
||||
|
||||
self.jitsiCalls[newUUID] = widget
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func endActiveJitsiCall() {
|
||||
guard let jitsiVC = jitsiVC else {
|
||||
// there is no active Jitsi call
|
||||
return
|
||||
}
|
||||
|
||||
if pipCallVC == jitsiVC {
|
||||
// this call currently in the PiP mode,
|
||||
// first present it by exiting PiP mode and then dismiss it
|
||||
exitPipCallVC(jitsiVC)
|
||||
}
|
||||
|
||||
dismissCallVC(jitsiVC)
|
||||
jitsiVC.hangup()
|
||||
|
||||
self.jitsiVC = nil
|
||||
|
||||
guard let widget = jitsiVC.widget else {
|
||||
return
|
||||
}
|
||||
guard let uuid = self.jitsiCalls.first(where: { $0.value.widgetId == widget.widgetId })?.key else {
|
||||
// this Jitsi call is not managed by this class
|
||||
return
|
||||
}
|
||||
|
||||
let endCallAction = CXEndCallAction(call: uuid)
|
||||
let transaction = CXTransaction(action: endCallAction)
|
||||
JMCallKitProxy.request(transaction) { (error) in
|
||||
if error == nil {
|
||||
self.jitsiCalls.removeValue(forKey: uuid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processWidgetEvent(_ event: MXEvent, inSession session: MXSession) {
|
||||
guard JMCallKitProxy.isProviderConfigured() else {
|
||||
// CallKit proxy is not configured, no benefit in parsing the event
|
||||
return
|
||||
}
|
||||
|
||||
guard let widget = Widget(widgetEvent: event, inMatrixSession: session) else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.jitsiCalls.first(where: { $0.value.widgetId == widget.widgetId })?.key != nil {
|
||||
// this Jitsi call is already managed by this class, no need to report the call again
|
||||
return
|
||||
}
|
||||
|
||||
if widget.isActive {
|
||||
guard widget.type == kWidgetTypeJitsiV1 || widget.type == kWidgetTypeJitsiV2 else {
|
||||
// not a Jitsi widget, ignore
|
||||
return
|
||||
}
|
||||
|
||||
if let jitsiVC = jitsiVC,
|
||||
jitsiVC.widget.widgetId == widget.widgetId {
|
||||
// this is already the Jitsi call we have atm
|
||||
return
|
||||
}
|
||||
|
||||
if TimeInterval(event.age)/MSEC_PER_SEC > Constants.groupCallInviteLifetime {
|
||||
// too late to process the event
|
||||
return
|
||||
}
|
||||
|
||||
// an active Jitsi widget
|
||||
let newUUID = UUID()
|
||||
|
||||
// assume this Jitsi call will survive
|
||||
self.jitsiCalls[newUUID] = widget
|
||||
|
||||
if event.sender == session.myUserId {
|
||||
// outgoing call
|
||||
JMCallKitProxy.reportOutgoingCall(with: newUUID, connectedAt: nil)
|
||||
} else {
|
||||
// incoming call
|
||||
guard RiotSettings.shared.enableRingingForGroupCalls else {
|
||||
// do not ring for Jitsi calls
|
||||
return
|
||||
}
|
||||
let user = session.user(withUserId: event.sender)
|
||||
let displayName = NSString.localizedUserNotificationString(forKey: "GROUP_CALL_FROM_USER",
|
||||
arguments: [user?.displayname as Any])
|
||||
JMCallKitProxy.reportNewIncomingCall(UUID: newUUID,
|
||||
handle: widget.roomId,
|
||||
displayName: displayName,
|
||||
hasVideo: true) { (error) in
|
||||
if error != nil {
|
||||
self.jitsiCalls.removeValue(forKey: newUUID)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
guard let uuid = self.jitsiCalls.first(where: { $0.value.widgetId == widget.widgetId })?.key else {
|
||||
// this Jitsi call is not managed by this class
|
||||
return
|
||||
}
|
||||
JMCallKitProxy.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
|
||||
self.jitsiCalls.removeValue(forKey: uuid)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func updateOnHoldCall() {
|
||||
guard let presentedCallVC = presentedCallVC else {
|
||||
guard let presentedCallVC = presentedCallVC as? CallViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -131,9 +340,6 @@ class CallPresenter: NSObject {
|
|||
}
|
||||
|
||||
private func shouldHandleCall(_ call: MXCall) -> Bool {
|
||||
if let delegate = delegate, !delegate.callPresenter(self, shouldHandleNewCall: call) {
|
||||
return false
|
||||
}
|
||||
return callVCs.count < maximumNumberOfConcurrentCalls
|
||||
}
|
||||
|
||||
|
@ -181,7 +387,19 @@ class CallPresenter: NSObject {
|
|||
}
|
||||
return
|
||||
}
|
||||
dismissCallVC(callVC, completion: completion)
|
||||
if callVC.isDisplayingAlert {
|
||||
completion()
|
||||
} else {
|
||||
dismissCallVC(callVC, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func logCallVC(_ callVC: UIViewController, log: String) {
|
||||
if let callVC = callVC as? CallViewController {
|
||||
NSLog("[CallPresenter] \(log): call: \(String(describing: callVC.mxCall?.callId))")
|
||||
} else if let callVC = callVC as? JitsiViewController {
|
||||
NSLog("[CallPresenter] \(log): call: \(callVC.widget.widgetId)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Timer
|
||||
|
@ -200,17 +418,18 @@ class CallPresenter: NSObject {
|
|||
}
|
||||
|
||||
@objc private func callTimerFired(_ timer: Timer) {
|
||||
guard let inBarCallVC = inBarCallVC else {
|
||||
return
|
||||
if let inBarCallVC = inBarCallVC as? CallViewController {
|
||||
guard let call = inBarCallVC.mxCall else {
|
||||
return
|
||||
}
|
||||
guard call.state != .ended else {
|
||||
return
|
||||
}
|
||||
|
||||
updateCallBar()
|
||||
} else if inBarCallVC as? JitsiViewController != nil {
|
||||
updateCallBar()
|
||||
}
|
||||
guard let call = inBarCallVC.mxCall else {
|
||||
return
|
||||
}
|
||||
guard call.state != .ended else {
|
||||
return
|
||||
}
|
||||
|
||||
presentCallBar(for: inBarCallVC, isUpdateOnly: true)
|
||||
}
|
||||
|
||||
// MARK: - Observers
|
||||
|
@ -232,8 +451,32 @@ class CallPresenter: NSObject {
|
|||
selector: #selector(callTileTapped(_:)),
|
||||
name: .RoomCallTileTapped,
|
||||
object: nil)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(groupCallTileTapped(_:)),
|
||||
name: .RoomGroupCallTileTapped,
|
||||
object: nil)
|
||||
|
||||
isStarted = true
|
||||
|
||||
#if canImport(JitsiMeetSDK)
|
||||
JMCallKitProxy.addListener(self)
|
||||
|
||||
guard let session = sessions.first else {
|
||||
return
|
||||
}
|
||||
|
||||
widgetEventsListener = session.listenToEvents([
|
||||
MXEventType(identifier: kWidgetMatrixEventTypeString),
|
||||
MXEventType(identifier: kWidgetModularEventTypeString)
|
||||
]) { (event, direction, _) in
|
||||
if direction == .backwards {
|
||||
// ignore backwards events
|
||||
return
|
||||
}
|
||||
|
||||
self.processWidgetEvent(event, inSession: session)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private func removeCallObservers() {
|
||||
|
@ -250,8 +493,24 @@ class CallPresenter: NSObject {
|
|||
NotificationCenter.default.removeObserver(self,
|
||||
name: .RoomCallTileTapped,
|
||||
object: nil)
|
||||
NotificationCenter.default.removeObserver(self,
|
||||
name: .RoomGroupCallTileTapped,
|
||||
object: nil)
|
||||
|
||||
isStarted = false
|
||||
|
||||
#if canImport(JitsiMeetSDK)
|
||||
JMCallKitProxy.removeListener(self)
|
||||
|
||||
guard let session = sessions.first else {
|
||||
return
|
||||
}
|
||||
|
||||
if let widgetEventsListener = widgetEventsListener {
|
||||
session.removeListener(widgetEventsListener)
|
||||
}
|
||||
widgetEventsListener = nil
|
||||
#endif
|
||||
}
|
||||
|
||||
@objc
|
||||
|
@ -269,6 +528,15 @@ class CallPresenter: NSObject {
|
|||
}
|
||||
newCallVC.playRingtone = !isCallKitEnabled
|
||||
newCallVC.delegate = self
|
||||
|
||||
if !call.isIncoming {
|
||||
// put other native calls on hold
|
||||
callVCs.values.forEach({ $0.mxCall.hold(true) })
|
||||
|
||||
// terminate Jitsi calls
|
||||
endActiveJitsiCall()
|
||||
}
|
||||
|
||||
callVCs[call.callId] = newCallVC
|
||||
|
||||
if UIApplication.shared.applicationState == .background && call.isIncoming {
|
||||
|
@ -276,7 +544,7 @@ class CallPresenter: NSObject {
|
|||
// Without CallKit this will allow us to play vibro until the call was ended
|
||||
// With CallKit we'll inform the system when the call is ended to let the system terminate our app to save resources
|
||||
let handler = MXSDKOptions.sharedInstance().backgroundModeHandler
|
||||
let callBackgroundTask = handler.startBackgroundTask(withName: "[CallService] addMatrixCallObserver", expirationHandler: nil)
|
||||
let callBackgroundTask = handler.startBackgroundTask(withName: "[CallPresenter] addMatrixCallObserver", expirationHandler: nil)
|
||||
|
||||
callBackgroundTasks[call.callId] = callBackgroundTask
|
||||
}
|
||||
|
@ -296,23 +564,23 @@ class CallPresenter: NSObject {
|
|||
|
||||
switch call.state {
|
||||
case .createAnswer:
|
||||
NSLog("[CallService] callStateChanged: call created answer: \(call.callId)")
|
||||
NSLog("[CallPresenter] callStateChanged: call created answer: \(call.callId)")
|
||||
if call.isIncoming, isCallKitEnabled, let callVC = callVCs[call.callId] {
|
||||
presentCallVC(callVC)
|
||||
}
|
||||
case .connected:
|
||||
NSLog("[CallService] callStateChanged: call connected: \(call.callId)")
|
||||
NSLog("[CallPresenter] callStateChanged: call connected: \(call.callId)")
|
||||
callTimer?.fire()
|
||||
case .onHold:
|
||||
NSLog("[CallService] callStateChanged: call holded: \(call.callId)")
|
||||
NSLog("[CallPresenter] callStateChanged: call holded: \(call.callId)")
|
||||
callTimer?.fire()
|
||||
callHolded(withCallId: call.callId)
|
||||
case .remotelyOnHold:
|
||||
NSLog("[CallService] callStateChanged: call remotely holded: \(call.callId)")
|
||||
NSLog("[CallPresenter] callStateChanged: call remotely holded: \(call.callId)")
|
||||
callTimer?.fire()
|
||||
callHolded(withCallId: call.callId)
|
||||
case .ended:
|
||||
NSLog("[CallService] callStateChanged: call ended: \(call.callId)")
|
||||
NSLog("[CallPresenter] callStateChanged: call ended: \(call.callId)")
|
||||
endCall(withCallId: call.callId)
|
||||
default:
|
||||
break
|
||||
|
@ -321,7 +589,8 @@ class CallPresenter: NSObject {
|
|||
|
||||
@objc
|
||||
private func callTileTapped(_ notification: Notification) {
|
||||
NSLog("[CallService] callTileTapped")
|
||||
NSLog("[CallPresenter] callTileTapped")
|
||||
|
||||
guard let bubbleData = notification.object as? RoomBubbleCellData else {
|
||||
return
|
||||
}
|
||||
|
@ -334,7 +603,7 @@ class CallPresenter: NSObject {
|
|||
return
|
||||
}
|
||||
|
||||
NSLog("[CallService] callTileTapped: for call: \(callEventContent.callId)")
|
||||
NSLog("[CallPresenter] callTileTapped: for call: \(callEventContent.callId)")
|
||||
|
||||
guard let session = sessions.first else { return }
|
||||
|
||||
|
@ -350,13 +619,55 @@ class CallPresenter: NSObject {
|
|||
return
|
||||
}
|
||||
|
||||
presentCallVC(callVC)
|
||||
if callVC == pipCallVC {
|
||||
exitPipCallVC(callVC)
|
||||
} else {
|
||||
presentCallVC(callVC)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
private func groupCallTileTapped(_ notification: Notification) {
|
||||
NSLog("[CallPresenter] groupCallTileTapped")
|
||||
|
||||
guard let bubbleData = notification.object as? RoomBubbleCellData else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let randomEvent = bubbleData.allLinkedEvents().randomElement() else {
|
||||
return
|
||||
}
|
||||
|
||||
guard randomEvent.eventType == .custom,
|
||||
(randomEvent.type == kWidgetMatrixEventTypeString ||
|
||||
randomEvent.type == kWidgetModularEventTypeString) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let session = sessions.first else { return }
|
||||
|
||||
guard let widget = Widget(widgetEvent: randomEvent, inMatrixSession: session) else {
|
||||
return
|
||||
}
|
||||
|
||||
NSLog("[CallPresenter] groupCallTileTapped: for call: \(widget.widgetId)")
|
||||
|
||||
guard let jitsiVC = jitsiVC,
|
||||
jitsiVC.widget.widgetId == widget.widgetId else {
|
||||
return
|
||||
}
|
||||
|
||||
if jitsiVC == pipCallVC {
|
||||
exitPipCallVC(jitsiVC)
|
||||
} else {
|
||||
presentCallVC(jitsiVC)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Call Screens
|
||||
|
||||
private func presentCallVC(_ callVC: CallViewController, completion: (() -> Void)? = nil) {
|
||||
NSLog("[CallService] presentCallVC: call: \(String(describing: callVC.mxCall?.callId))")
|
||||
private func presentCallVC(_ callVC: UIViewController, completion: (() -> Void)? = nil) {
|
||||
logCallVC(callVC, log: "presentCallVC")
|
||||
|
||||
// do not use PiP transitions here, as we really want to present the screen
|
||||
callVC.transitioningDelegate = nil
|
||||
|
@ -379,8 +690,8 @@ class CallPresenter: NSObject {
|
|||
uiOperationQueue.addOperation(operation)
|
||||
}
|
||||
|
||||
private func dismissCallVC(_ callVC: CallViewController, completion: (() -> Void)? = nil) {
|
||||
NSLog("[CallService] dismissCallVC: call: \(String(describing: callVC.mxCall?.callId))")
|
||||
private func dismissCallVC(_ callVC: UIViewController, completion: (() -> Void)? = nil) {
|
||||
logCallVC(callVC, log: "dismissCallVC")
|
||||
|
||||
// do not use PiP transitions here, as we really want to dismiss the screen
|
||||
callVC.transitioningDelegate = nil
|
||||
|
@ -394,8 +705,8 @@ class CallPresenter: NSObject {
|
|||
uiOperationQueue.addOperation(operation)
|
||||
}
|
||||
|
||||
private func enterPipCallVC(_ callVC: CallViewController, completion: (() -> Void)? = nil) {
|
||||
NSLog("[CallService] enterPipCallVC: call: \(String(describing: callVC.mxCall?.callId))")
|
||||
private func enterPipCallVC(_ callVC: UIViewController, completion: (() -> Void)? = nil) {
|
||||
logCallVC(callVC, log: "enterPipCallVC")
|
||||
|
||||
// assign self as transitioning delegate
|
||||
callVC.transitioningDelegate = self
|
||||
|
@ -410,8 +721,8 @@ class CallPresenter: NSObject {
|
|||
uiOperationQueue.addOperation(operation)
|
||||
}
|
||||
|
||||
private func exitPipCallVC(_ callVC: CallViewController, completion: (() -> Void)? = nil) {
|
||||
NSLog("[CallService] exitPipCallVC: call: \(String(describing: callVC.mxCall?.callId))")
|
||||
private func exitPipCallVC(_ callVC: UIViewController, completion: (() -> Void)? = nil) {
|
||||
logCallVC(callVC, log: "exitPipCallVC")
|
||||
|
||||
// assign self as transitioning delegate
|
||||
callVC.transitioningDelegate = self
|
||||
|
@ -428,24 +739,29 @@ class CallPresenter: NSObject {
|
|||
|
||||
// MARK: - Call Bar
|
||||
|
||||
private func presentCallBar(for callVC: CallViewController?, isUpdateOnly: Bool = false, completion: (() -> Void)? = nil) {
|
||||
NSLog("[CallService] presentCallBar: call: \(String(describing: callVC?.mxCall?.callId))")
|
||||
private func presentCallBar(for callVC: UIViewController, completion: (() -> Void)? = nil) {
|
||||
logCallVC(callVC, log: "presentCallBar")
|
||||
|
||||
let activeCallVC = self.activeCallVC
|
||||
|
||||
let operation = CallBarPresentOperation(presenter: self, activeCallVC: activeCallVC, numberOfPausedCalls: numberOfPausedCalls) { [weak self] in
|
||||
// active calls are more prior to paused ones.
|
||||
// So, if user taps the bar when we have one active and one paused calls, we navigate to the active one.
|
||||
if !isUpdateOnly {
|
||||
self?.inBarCallVC = activeCallVC ?? callVC
|
||||
}
|
||||
// So, if user taps the bar when we have one active and one paused call, we navigate to the active one.
|
||||
self?.inBarCallVC = activeCallVC ?? callVC
|
||||
completion?()
|
||||
}
|
||||
uiOperationQueue.addOperation(operation)
|
||||
}
|
||||
|
||||
private func dismissCallBar(for callVC: CallViewController, completion: (() -> Void)? = nil) {
|
||||
NSLog("[CallService] dismissCallBar: call: \(String(describing: callVC.mxCall?.callId))")
|
||||
private func updateCallBar() {
|
||||
let activeCallVC = self.activeCallVC
|
||||
|
||||
let operation = CallBarUpdateOperation(presenter: self, activeCallVC: activeCallVC, numberOfPausedCalls: numberOfPausedCalls)
|
||||
uiOperationQueue.addOperation(operation)
|
||||
}
|
||||
|
||||
private func dismissCallBar(for callVC: UIViewController, completion: (() -> Void)? = nil) {
|
||||
logCallVC(callVC, log: "dismissCallBar")
|
||||
|
||||
let operation = CallBarDismissOperation(presenter: self) { [weak self] in
|
||||
if callVC == self?.inBarCallVC {
|
||||
|
@ -474,8 +790,13 @@ extension CallPresenter: MXKCallViewControllerDelegate {
|
|||
// wait for the call state changes, will be handled there
|
||||
return
|
||||
} else {
|
||||
dismissCallVC(callVC)
|
||||
self.presentCallBar(for: callVC, completion: completion)
|
||||
if callVC.mxCall.isVideoCall {
|
||||
// go to pip mode here
|
||||
enterPipCallVC(callVC, completion: completion)
|
||||
} else {
|
||||
dismissCallVC(callVC)
|
||||
self.presentCallBar(for: callVC, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -497,20 +818,6 @@ extension CallPresenter: MXKCallViewControllerDelegate {
|
|||
presentCallVC(onHoldCallVC)
|
||||
}
|
||||
|
||||
func callViewControllerDidTapPiPButton(_ callViewController: MXKCallViewController!) {
|
||||
guard let callVC = callViewController as? CallViewController else {
|
||||
// this call screen is not handled by this service
|
||||
return
|
||||
}
|
||||
|
||||
// sanity check
|
||||
// do not enter PiP mode if not a video call
|
||||
guard callVC.mxCall.isVideoCall else { return }
|
||||
|
||||
// go to pip mode here
|
||||
enterPipCallVC(callVC)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UIViewControllerTransitioningDelegate
|
||||
|
@ -562,3 +869,84 @@ extension OperationQueue {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
#if canImport(JitsiMeetSDK)
|
||||
// MARK: - JMCallKitListener
|
||||
|
||||
extension CallPresenter: JMCallKitListener {
|
||||
|
||||
func providerDidReset() {
|
||||
|
||||
}
|
||||
|
||||
func performAnswerCall(UUID: UUID) {
|
||||
guard let widget = jitsiCalls[UUID] else {
|
||||
return
|
||||
}
|
||||
|
||||
displayJitsiCall(withWidget: widget)
|
||||
}
|
||||
|
||||
func performEndCall(UUID: UUID) {
|
||||
guard let widget = jitsiCalls[UUID] else {
|
||||
return
|
||||
}
|
||||
|
||||
if let jitsiVC = jitsiVC, jitsiVC.widget.widgetId == widget.widgetId {
|
||||
// hangup an active call
|
||||
dismissCallVC(jitsiVC)
|
||||
endActiveJitsiCall()
|
||||
} else {
|
||||
// decline incoming call
|
||||
JitsiService.shared.declineWidget(withId: widget.widgetId)
|
||||
}
|
||||
}
|
||||
|
||||
func performSetMutedCall(UUID: UUID, isMuted: Bool) {
|
||||
guard let widget = jitsiCalls[UUID] else {
|
||||
return
|
||||
}
|
||||
|
||||
if let jitsiVC = jitsiVC, jitsiVC.widget.widgetId == widget.widgetId {
|
||||
// mute the active Jitsi call
|
||||
jitsiVC.setAudioMuted(isMuted)
|
||||
}
|
||||
}
|
||||
|
||||
func performStartCall(UUID: UUID, isVideo: Bool) {
|
||||
|
||||
}
|
||||
|
||||
func providerDidActivateAudioSession(session: AVAudioSession) {
|
||||
|
||||
}
|
||||
|
||||
func providerDidDeactivateAudioSession(session: AVAudioSession) {
|
||||
|
||||
}
|
||||
|
||||
func providerTimedOutPerformingAction(action: CXAction) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - JitsiViewControllerDelegate
|
||||
|
||||
extension CallPresenter: JitsiViewControllerDelegate {
|
||||
|
||||
func jitsiViewController(_ jitsiViewController: JitsiViewController!, dismissViewJitsiController completion: (() -> Void)!) {
|
||||
if jitsiViewController == jitsiVC {
|
||||
endActiveJitsiCall()
|
||||
}
|
||||
}
|
||||
|
||||
func jitsiViewController(_ jitsiViewController: JitsiViewController!, goBackToApp completion: (() -> Void)!) {
|
||||
if jitsiViewController == jitsiVC {
|
||||
enterPipCallVC(jitsiViewController, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -18,32 +18,31 @@ import Foundation
|
|||
|
||||
@objc
|
||||
protocol CallPresenterDelegate: class {
|
||||
// New call
|
||||
func callPresenter(_ presenter: CallPresenter,
|
||||
shouldHandleNewCall call: MXCall) -> Bool
|
||||
|
||||
// Call screens
|
||||
func callPresenter(_ presenter: CallPresenter,
|
||||
presentCallViewController viewController: CallViewController,
|
||||
presentCallViewController viewController: UIViewController,
|
||||
completion:(() -> Void)?)
|
||||
func callPresenter(_ presenter: CallPresenter,
|
||||
dismissCallViewController viewController: CallViewController,
|
||||
dismissCallViewController viewController: UIViewController,
|
||||
completion:(() -> Void)?)
|
||||
|
||||
// Call Bar
|
||||
func callPresenter(_ presenter: CallPresenter,
|
||||
presentCallBarFor activeCallViewController: CallViewController?,
|
||||
presentCallBarFor activeCallViewController: UIViewController?,
|
||||
numberOfPausedCalls: UInt,
|
||||
completion:(() -> Void)?)
|
||||
func callPresenter(_ presenter: CallPresenter,
|
||||
updateCallBarFor activeCallViewController: UIViewController?,
|
||||
numberOfPausedCalls: UInt)
|
||||
func callPresenter(_ presenter: CallPresenter,
|
||||
dismissCallBar completion:(() -> Void)?)
|
||||
|
||||
// PiP
|
||||
func callPresenter(_ presenter: CallPresenter,
|
||||
enterPipForCallViewController viewController: CallViewController,
|
||||
enterPipForCallViewController viewController: UIViewController,
|
||||
completion:(() -> Void)?)
|
||||
|
||||
func callPresenter(_ presenter: CallPresenter,
|
||||
exitPipForCallViewController viewController: CallViewController,
|
||||
exitPipForCallViewController viewController: UIViewController,
|
||||
completion:(() -> Void)?)
|
||||
}
|
||||
|
|
|
@ -19,12 +19,12 @@ import Foundation
|
|||
class CallBarPresentOperation: AsyncOperation {
|
||||
|
||||
private var presenter: CallPresenter
|
||||
private var activeCallVC: CallViewController?
|
||||
private var activeCallVC: UIViewController?
|
||||
private var numberOfPausedCalls: UInt
|
||||
private var completion: (() -> Void)?
|
||||
|
||||
init(presenter: CallPresenter,
|
||||
activeCallVC: CallViewController?,
|
||||
activeCallVC: UIViewController?,
|
||||
numberOfPausedCalls: UInt,
|
||||
completion: (() -> Void)? = nil) {
|
||||
self.presenter = presenter
|
||||
|
@ -36,7 +36,10 @@ class CallBarPresentOperation: AsyncOperation {
|
|||
override func main() {
|
||||
presenter.delegate?.callPresenter(presenter, presentCallBarFor: activeCallVC, numberOfPausedCalls: numberOfPausedCalls, completion: {
|
||||
self.finish()
|
||||
self.completion?()
|
||||
// wait for the next life cycle to detect status bar layout updates
|
||||
DispatchQueue.main.async {
|
||||
self.completion?()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
38
Riot/Managers/Call/Operations/CallBarUpdateOperation.swift
Normal file
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// Copyright 2020 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class CallBarUpdateOperation: AsyncOperation {
|
||||
|
||||
private var presenter: CallPresenter
|
||||
private var activeCallVC: UIViewController?
|
||||
private var numberOfPausedCalls: UInt
|
||||
|
||||
init(presenter: CallPresenter,
|
||||
activeCallVC: UIViewController?,
|
||||
numberOfPausedCalls: UInt) {
|
||||
self.presenter = presenter
|
||||
self.activeCallVC = activeCallVC
|
||||
self.numberOfPausedCalls = numberOfPausedCalls
|
||||
}
|
||||
|
||||
override func main() {
|
||||
presenter.delegate?.callPresenter(presenter, updateCallBarFor: activeCallVC, numberOfPausedCalls: numberOfPausedCalls)
|
||||
self.finish()
|
||||
}
|
||||
|
||||
}
|
|
@ -19,11 +19,11 @@ import Foundation
|
|||
class CallVCDismissOperation: AsyncOperation {
|
||||
|
||||
private var presenter: CallPresenter
|
||||
private var callVC: CallViewController
|
||||
private var callVC: UIViewController
|
||||
private var completion: (() -> Void)?
|
||||
|
||||
init(presenter: CallPresenter,
|
||||
callVC: CallViewController,
|
||||
callVC: UIViewController,
|
||||
completion: (() -> Void)? = nil) {
|
||||
self.presenter = presenter
|
||||
self.callVC = callVC
|
||||
|
|
|
@ -19,11 +19,11 @@ import Foundation
|
|||
class CallVCEnterPipOperation: AsyncOperation {
|
||||
|
||||
private var presenter: CallPresenter
|
||||
private var callVC: CallViewController
|
||||
private var callVC: UIViewController
|
||||
private var completion: (() -> Void)?
|
||||
|
||||
init(presenter: CallPresenter,
|
||||
callVC: CallViewController,
|
||||
callVC: UIViewController,
|
||||
completion: (() -> Void)? = nil) {
|
||||
self.presenter = presenter
|
||||
self.callVC = callVC
|
||||
|
|
|
@ -19,11 +19,11 @@ import Foundation
|
|||
class CallVCExitPipOperation: AsyncOperation {
|
||||
|
||||
private var presenter: CallPresenter
|
||||
private var callVC: CallViewController
|
||||
private var callVC: UIViewController
|
||||
private var completion: (() -> Void)?
|
||||
|
||||
init(presenter: CallPresenter,
|
||||
callVC: CallViewController,
|
||||
callVC: UIViewController,
|
||||
completion: (() -> Void)? = nil) {
|
||||
self.presenter = presenter
|
||||
self.callVC = callVC
|
||||
|
|
|
@ -19,11 +19,11 @@ import Foundation
|
|||
class CallVCPresentOperation: AsyncOperation {
|
||||
|
||||
private var presenter: CallPresenter
|
||||
private var callVC: CallViewController
|
||||
private var callVC: UIViewController
|
||||
private var completion: (() -> Void)?
|
||||
|
||||
init(presenter: CallPresenter,
|
||||
callVC: CallViewController,
|
||||
callVC: UIViewController,
|
||||
completion: (() -> Void)? = nil) {
|
||||
self.presenter = presenter
|
||||
self.callVC = callVC
|
||||
|
|
|
@ -25,7 +25,7 @@ import Foundation
|
|||
class PiPAnimator: NSObject {
|
||||
|
||||
private enum Constants {
|
||||
static let pipViewScale: CGFloat = 0.3
|
||||
static let pipViewSize: CGSize = CGSize(width: 90, height: 130)
|
||||
}
|
||||
|
||||
let animationDuration: TimeInterval
|
||||
|
@ -62,15 +62,15 @@ class PiPAnimator: NSObject {
|
|||
pipView.delegate = pipViewDelegate
|
||||
keyWindow.addSubview(pipView)
|
||||
|
||||
let transform = CGAffineTransform(scaleX: Constants.pipViewScale, y: Constants.pipViewScale)
|
||||
let targetRect = fromVC.view.bounds.applying(transform)
|
||||
let scale = Constants.pipViewSize.width/pipView.frame.width
|
||||
let transform = CGAffineTransform(scaleX: scale, y: scale)
|
||||
let targetSize = Constants.pipViewSize
|
||||
|
||||
let animator = UIViewPropertyAnimator(duration: animationDuration, dampingRatio: 1) {
|
||||
pipView.transform = transform
|
||||
|
||||
pipView.move(in: keyWindow,
|
||||
to: .bottomLeft,
|
||||
targetSize: targetRect.size)
|
||||
targetSize: targetSize)
|
||||
}
|
||||
|
||||
animator.addCompletion { (position) in
|
||||
|
@ -117,6 +117,7 @@ class PiPAnimator: NSObject {
|
|||
animator.addCompletion { (position) in
|
||||
|
||||
toVC.additionalSafeAreaInsets = .zero
|
||||
toVC.view.frame = context.finalFrame(for: toVC)
|
||||
toVC.view.isHidden = false
|
||||
|
||||
snapshot.removeFromSuperview()
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
import UIKit
|
||||
|
||||
@objc enum PiPViewPosition: Int {
|
||||
case bottomLeft // default value
|
||||
case bottomRight
|
||||
case bottomLeft
|
||||
case bottomRight // default value
|
||||
case topRight
|
||||
case topLeft
|
||||
}
|
||||
|
@ -32,12 +32,12 @@ import UIKit
|
|||
class PiPView: UIView {
|
||||
|
||||
private enum Defaults {
|
||||
static let margins: UIOffset = UIOffset(horizontal: 20, vertical: 20)
|
||||
static let margins: UIEdgeInsets = UIEdgeInsets(top: 64, left: 20, bottom: 64, right: 20)
|
||||
static let cornerRadius: CGFloat = 8
|
||||
static let animationDuration: TimeInterval = 0.25
|
||||
}
|
||||
|
||||
var margins: UIOffset = Defaults.margins {
|
||||
var margins: UIEdgeInsets = Defaults.margins {
|
||||
didSet {
|
||||
guard self.superview != nil else { return }
|
||||
self.move(to: self.position, animated: true)
|
||||
|
@ -48,7 +48,7 @@ class PiPView: UIView {
|
|||
layer.cornerRadius = cornerRadius
|
||||
}
|
||||
}
|
||||
var position: PiPViewPosition = .bottomLeft
|
||||
var position: PiPViewPosition = .bottomRight
|
||||
weak var delegate: PiPViewDelegate?
|
||||
|
||||
private var originalCenter: CGPoint = .zero
|
||||
|
@ -97,7 +97,7 @@ NSLayoutConstraint.activate([
|
|||
}
|
||||
|
||||
func move(in view: UIView? = nil,
|
||||
to position: PiPViewPosition = .bottomLeft,
|
||||
to position: PiPViewPosition = .bottomRight,
|
||||
targetSize: CGSize? = nil,
|
||||
animated: Bool = false,
|
||||
completion: ((Bool) -> Void)? = nil) {
|
||||
|
@ -148,25 +148,36 @@ NSLayoutConstraint.activate([
|
|||
}
|
||||
let targetSize = targetSize ?? frame.size
|
||||
|
||||
var superviewWidth: CGFloat = 0
|
||||
var superviewHeight: CGFloat = 0
|
||||
|
||||
if UIDevice.current.orientation.isPortrait {
|
||||
superviewWidth = min(view.bounds.width, view.bounds.height)
|
||||
superviewHeight = max(view.bounds.width, view.bounds.height)
|
||||
} else {
|
||||
superviewWidth = max(view.bounds.width, view.bounds.height)
|
||||
superviewHeight = min(view.bounds.width, view.bounds.height)
|
||||
}
|
||||
|
||||
switch position {
|
||||
case .bottomLeft:
|
||||
let origin = CGPoint(x: margins.horizontal + view.safeAreaInsets.left,
|
||||
y: view.bounds.height - view.safeAreaInsets.bottom - targetSize.height - margins.vertical)
|
||||
let origin = CGPoint(x: margins.left + view.safeAreaInsets.left,
|
||||
y: superviewHeight - view.safeAreaInsets.bottom - targetSize.height - margins.bottom)
|
||||
return CGRect(origin: origin,
|
||||
size: targetSize)
|
||||
case .bottomRight:
|
||||
let origin = CGPoint(x: view.bounds.width - view.safeAreaInsets.right - margins.horizontal - targetSize.width,
|
||||
y: view.bounds.height - view.safeAreaInsets.bottom - targetSize.height - margins.vertical)
|
||||
let origin = CGPoint(x: superviewWidth - view.safeAreaInsets.right - margins.right - targetSize.width,
|
||||
y: superviewHeight - view.safeAreaInsets.bottom - targetSize.height - margins.bottom)
|
||||
return CGRect(origin: origin,
|
||||
size: targetSize)
|
||||
case .topRight:
|
||||
let origin = CGPoint(x: view.bounds.width - view.safeAreaInsets.right - margins.horizontal - targetSize.width,
|
||||
y: margins.vertical + view.safeAreaInsets.top)
|
||||
let origin = CGPoint(x: superviewWidth - view.safeAreaInsets.right - margins.right - targetSize.width,
|
||||
y: margins.top + view.safeAreaInsets.top)
|
||||
return CGRect(origin: origin,
|
||||
size: targetSize)
|
||||
case .topLeft:
|
||||
let origin = CGPoint(x: margins.horizontal + view.safeAreaInsets.left,
|
||||
y: margins.vertical + view.safeAreaInsets.top)
|
||||
let origin = CGPoint(x: margins.left + view.safeAreaInsets.left,
|
||||
y: margins.top + view.safeAreaInsets.top)
|
||||
return CGRect(origin: origin,
|
||||
size: targetSize)
|
||||
}
|
||||
|
|
|
@ -323,6 +323,30 @@ Matrix session observer used to detect new opened sessions.
|
|||
|
||||
#pragma mark - UNUserNotificationCenterDelegate
|
||||
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
|
||||
{
|
||||
NSDictionary *userInfo = notification.request.content.userInfo;
|
||||
if (userInfo[Constants.userInfoKeyPresentNotificationOnForeground])
|
||||
{
|
||||
if (!userInfo[Constants.userInfoKeyPresentNotificationInRoom]
|
||||
&& [[AppDelegate theDelegate].visibleRoomId isEqualToString:userInfo[@"room_id"]])
|
||||
{
|
||||
// do not show the notification when we're in the notified room
|
||||
completionHandler(UNNotificationPresentationOptionNone);
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(UNNotificationPresentationOptionBadge
|
||||
| UNNotificationPresentationOptionSound
|
||||
| UNNotificationPresentationOptionAlert);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(UNNotificationPresentationOptionNone);
|
||||
}
|
||||
}
|
||||
|
||||
// iOS 10+, see application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
|
||||
{
|
||||
|
@ -568,23 +592,37 @@ Matrix session observer used to detect new opened sessions.
|
|||
return;
|
||||
}
|
||||
|
||||
// process the call invite synchronously
|
||||
[session.callManager handleCallEvent:lastCallInvite];
|
||||
MXCall *call = [session.callManager callWithCallId:lastCallInvite.content[@"call_id"]];
|
||||
if (call)
|
||||
if (lastCallInvite.eventType == MXEventTypeCallInvite)
|
||||
{
|
||||
[session.callManager.callKitAdapter reportIncomingCall:call];
|
||||
NSLog(@"[PushNotificationService] didReceiveIncomingPushWithPayload: Reporting new call in room %@ for the event: %@", roomId, eventId);
|
||||
|
||||
// Wait for the sync response in cache to be processed for data integrity.
|
||||
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
|
||||
// After reporting the call, we can continue async. Launch a background sync to handle call answers/declines on other devices of the user.
|
||||
[self launchBackgroundSync];
|
||||
});
|
||||
// process the call invite synchronously
|
||||
[session.callManager handleCallEvent:lastCallInvite];
|
||||
MXCall *call = [session.callManager callWithCallId:lastCallInvite.content[@"call_id"]];
|
||||
if (call)
|
||||
{
|
||||
[session.callManager.callKitAdapter reportIncomingCall:call];
|
||||
NSLog(@"[PushNotificationService] didReceiveIncomingPushWithPayload: Reporting new call in room %@ for the event: %@", roomId, eventId);
|
||||
|
||||
// Wait for the sync response in cache to be processed for data integrity.
|
||||
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
|
||||
// After reporting the call, we can continue async. Launch a background sync to handle call answers/declines on other devices of the user.
|
||||
[self launchBackgroundSync];
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"[PushNotificationService] didReceiveIncomingPushWithPayload: Error on call object on room %@ for the event: %@", roomId, eventId);
|
||||
}
|
||||
}
|
||||
else if ([lastCallInvite.type isEqualToString:kWidgetMatrixEventTypeString] ||
|
||||
[lastCallInvite.type isEqualToString:kWidgetModularEventTypeString])
|
||||
{
|
||||
[[AppDelegate theDelegate].callPresenter processWidgetEvent:lastCallInvite
|
||||
inSession:session];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"[PushNotificationService] didReceiveIncomingPushWithPayload: Error on call object on room %@ for the event: %@", roomId, eventId);
|
||||
// It's a serious error. There is nothing to avoid iOS to kill us here.
|
||||
NSLog(@"[PushNotificationService] didReceiveIncomingPushWithPayload: We have an unknown type of event for %@. There is something wrong.", eventId);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -27,7 +27,6 @@ final class RiotSettings: NSObject {
|
|||
static let identityServerUrlString = "identityserverurl"
|
||||
static let enableCrashReport = "enableCrashReport"
|
||||
static let enableRageShake = "enableRageShake"
|
||||
static let createConferenceCallsWithJitsi = "createConferenceCallsWithJitsi"
|
||||
static let userInterfaceTheme = "userInterfaceTheme"
|
||||
static let notificationsShowDecryptedContent = "showDecryptedContent"
|
||||
static let pinRoomsWithMissedNotifications = "pinRoomsWithMissedNotif"
|
||||
|
@ -52,6 +51,7 @@ final class RiotSettings: NSObject {
|
|||
static let roomCreationScreenAllowRoomTypeConfiguration = "roomCreationScreenAllowRoomTypeConfiguration"
|
||||
static let roomCreationScreenRoomIsPublic = "roomCreationScreenRoomIsPublic"
|
||||
static let allowInviteExernalUsers = "allowInviteExernalUsers"
|
||||
static let enableRingingForGroupCalls = "enableRingingForGroupCalls"
|
||||
static let roomSettingsScreenShowLowPriorityOption = "roomSettingsScreenShowLowPriorityOption"
|
||||
static let roomSettingsScreenShowDirectChatOption = "roomSettingsScreenShowDirectChatOption"
|
||||
static let roomSettingsScreenAllowChangingAccessSettings = "roomSettingsScreenAllowChangingAccessSettings"
|
||||
|
@ -60,6 +60,8 @@ final class RiotSettings: NSObject {
|
|||
static let roomSettingsScreenShowFlairSettings = "roomSettingsScreenShowFlairSettings"
|
||||
static let roomSettingsScreenShowAdvancedSettings = "roomSettingsScreenShowAdvancedSettings"
|
||||
static let roomSettingsScreenAdvancedShowEncryptToVerifiedOption = "roomSettingsScreenAdvancedShowEncryptToVerifiedOption"
|
||||
static let settingsScreenShowNotificationDecodedContentOption = "settingsScreenShowNotificationDecodedContentOption"
|
||||
static let settingsScreenShowNsfwRoomsOption = "settingsScreenShowNsfwRoomsOption"
|
||||
static let roomsAllowToJoinPublicRooms = "roomsAllowToJoinPublicRooms"
|
||||
static let homeScreenShowFavouritesTab = "homeScreenShowFavouritesTab"
|
||||
static let homeScreenShowPeopleTab = "homeScreenShowPeopleTab"
|
||||
|
@ -71,7 +73,11 @@ final class RiotSettings: NSObject {
|
|||
static let roomScreenAllowMediaLibraryAction = "roomScreenAllowMediaLibraryAction"
|
||||
static let roomScreenAllowStickerAction = "roomScreenAllowStickerAction"
|
||||
static let roomScreenAllowFilesAction = "roomScreenAllowFilesAction"
|
||||
static let roomContextualMenuShowMoreOptionForMessages = "roomContextualMenuShowMoreOptionForMessages"
|
||||
static let roomContextualMenuShowMoreOptionForStates = "roomContextualMenuShowMoreOptionForStates"
|
||||
static let roomContextualMenuShowReportContentOption = "roomContextualMenuShowReportContentOption"
|
||||
static let roomInfoScreenShowIntegrations = "roomInfoScreenShowIntegrations"
|
||||
static let roomMemberScreenShowIgnore = "roomMemberScreenShowIgnore"
|
||||
static let unifiedSearchScreenShowPublicDirectory = "unifiedSearchScreenShowPublicDirectory"
|
||||
static let hideSpaceBetaAnnounce = "hideSpaceBetaAnnounce"
|
||||
}
|
||||
|
@ -199,14 +205,15 @@ final class RiotSettings: NSObject {
|
|||
|
||||
// MARK: Labs
|
||||
|
||||
var createConferenceCallsWithJitsi: Bool {
|
||||
/// Indicates if CallKit ringing is enabled for group calls. This setting does not disable the CallKit integration for group calls, only relates to ringing.
|
||||
var enableRingingForGroupCalls: Bool {
|
||||
get {
|
||||
return defaults.bool(forKey: UserDefaultsKeys.createConferenceCallsWithJitsi)
|
||||
return defaults.bool(forKey: UserDefaultsKeys.enableRingingForGroupCalls)
|
||||
} set {
|
||||
defaults.set(newValue, forKey: UserDefaultsKeys.createConferenceCallsWithJitsi)
|
||||
defaults.set(newValue, forKey: UserDefaultsKeys.enableRingingForGroupCalls)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Calls
|
||||
|
||||
/// Indicate if `allowStunServerFallback` settings has been set once.
|
||||
|
@ -323,6 +330,39 @@ final class RiotSettings: NSObject {
|
|||
defaults.set(newValue, forKey: UserDefaultsKeys.roomScreenAllowFilesAction)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Room Contextual Menu
|
||||
|
||||
var roomContextualMenuShowMoreOptionForMessages: Bool {
|
||||
get {
|
||||
guard defaults.object(forKey: UserDefaultsKeys.roomContextualMenuShowMoreOptionForMessages) != nil else {
|
||||
return BuildSettings.roomContextualMenuShowMoreOptionForMessages
|
||||
}
|
||||
return defaults.bool(forKey: UserDefaultsKeys.roomContextualMenuShowMoreOptionForMessages)
|
||||
} set {
|
||||
defaults.set(newValue, forKey: UserDefaultsKeys.roomContextualMenuShowMoreOptionForMessages)
|
||||
}
|
||||
}
|
||||
var roomContextualMenuShowMoreOptionForStates: Bool {
|
||||
get {
|
||||
guard defaults.object(forKey: UserDefaultsKeys.roomContextualMenuShowMoreOptionForStates) != nil else {
|
||||
return BuildSettings.roomContextualMenuShowMoreOptionForStates
|
||||
}
|
||||
return defaults.bool(forKey: UserDefaultsKeys.roomContextualMenuShowMoreOptionForStates)
|
||||
} set {
|
||||
defaults.set(newValue, forKey: UserDefaultsKeys.roomContextualMenuShowMoreOptionForStates)
|
||||
}
|
||||
}
|
||||
var roomContextualMenuShowReportContentOption: Bool {
|
||||
get {
|
||||
guard defaults.object(forKey: UserDefaultsKeys.roomContextualMenuShowReportContentOption) != nil else {
|
||||
return BuildSettings.roomContextualMenuShowReportContentOption
|
||||
}
|
||||
return defaults.bool(forKey: UserDefaultsKeys.roomContextualMenuShowReportContentOption)
|
||||
} set {
|
||||
defaults.set(newValue, forKey: UserDefaultsKeys.roomContextualMenuShowReportContentOption)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Room Info Screen
|
||||
|
||||
|
@ -337,6 +377,19 @@ final class RiotSettings: NSObject {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Room Member Screen
|
||||
|
||||
var roomMemberScreenShowIgnore: Bool {
|
||||
get {
|
||||
guard defaults.object(forKey: UserDefaultsKeys.roomMemberScreenShowIgnore) != nil else {
|
||||
return BuildSettings.roomMemberScreenShowIgnore
|
||||
}
|
||||
return defaults.bool(forKey: UserDefaultsKeys.roomMemberScreenShowIgnore)
|
||||
} set {
|
||||
defaults.set(newValue, forKey: UserDefaultsKeys.roomMemberScreenShowIgnore)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Room Creation Screen
|
||||
|
||||
var roomCreationScreenAllowEncryptionConfiguration: Bool {
|
||||
|
@ -468,6 +521,26 @@ final class RiotSettings: NSObject {
|
|||
defaults.set(newValue, forKey: UserDefaultsKeys.settingsScreenShowEnableStunServerFallback)
|
||||
}
|
||||
}
|
||||
var settingsScreenShowNotificationDecodedContentOption: Bool {
|
||||
get {
|
||||
guard defaults.object(forKey: UserDefaultsKeys.settingsScreenShowNotificationDecodedContentOption) != nil else {
|
||||
return BuildSettings.settingsScreenShowNotificationDecodedContentOption
|
||||
}
|
||||
return defaults.bool(forKey: UserDefaultsKeys.settingsScreenShowNotificationDecodedContentOption)
|
||||
} set {
|
||||
defaults.set(newValue, forKey: UserDefaultsKeys.settingsScreenShowNotificationDecodedContentOption)
|
||||
}
|
||||
}
|
||||
var settingsScreenShowNsfwRoomsOption: Bool {
|
||||
get {
|
||||
guard defaults.object(forKey: UserDefaultsKeys.settingsScreenShowNsfwRoomsOption) != nil else {
|
||||
return BuildSettings.settingsScreenShowNsfwRoomsOption
|
||||
}
|
||||
return defaults.bool(forKey: UserDefaultsKeys.settingsScreenShowNsfwRoomsOption)
|
||||
} set {
|
||||
defaults.set(newValue, forKey: UserDefaultsKeys.settingsScreenShowNsfwRoomsOption)
|
||||
}
|
||||
}
|
||||
var settingsSecurityScreenShowSessions: Bool {
|
||||
get {
|
||||
guard defaults.object(forKey: UserDefaultsKeys.settingsSecurityScreenShowSessions) != nil else {
|
||||
|
|
|
@ -15,10 +15,11 @@
|
|||
*/
|
||||
|
||||
import UIKit
|
||||
import DesignKit
|
||||
|
||||
/// Provide color constant values defined by the designer
|
||||
/// https://app.zeplin.io/project/5c122fa790c5b4241ffa6be7/screen/5c619592daff2f1241d82e75
|
||||
@objc protocol Theme {
|
||||
@objc protocol Theme: ThemeV2 {
|
||||
|
||||
var identifier: String { get }
|
||||
|
||||
|
@ -93,6 +94,9 @@ import UIKit
|
|||
/// Color to tint the search background image
|
||||
var matrixSearchBackgroundImageTintColor: UIColor { get }
|
||||
|
||||
/// Color to use in shadows. Should be contrast to `backgroundColor`.
|
||||
var shadowColor: UIColor { get }
|
||||
|
||||
// MARK: - Customisation methods
|
||||
|
||||
|
||||
|
|
|
@ -155,6 +155,9 @@ NSString *const kThemeServiceDidChangeThemeNotification = @"kThemeServiceDidChan
|
|||
|
||||
// Define the UISearchBar cancel button color
|
||||
[[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UISearchBar class]]] setTitleTextAttributes:@{ NSForegroundColorAttributeName : self.theme.tintColor } forState: UIControlStateNormal];
|
||||
|
||||
[[UIStackView appearanceWhenContainedInInstancesOfClasses:@[[UINavigationBar class]]] setSpacing:-7];
|
||||
[[UIStackView appearanceWhenContainedInInstancesOfClasses:@[[UINavigationBar class]]] setDistribution:UIStackViewDistributionEqualCentering];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import DesignKit
|
||||
|
||||
/// Color constants for the dark theme
|
||||
@objcMembers
|
||||
|
@ -87,6 +88,8 @@ class DarkTheme: NSObject, Theme {
|
|||
var matrixSearchBackgroundImageTintColor: UIColor = UIColor(rgb: 0x7E7E7E)
|
||||
var secondaryCircleButtonBackgroundColor: UIColor = UIColor(rgb: 0xE3E8F0)
|
||||
|
||||
var shadowColor: UIColor = UIColor(rgb: 0xFFFFFF)
|
||||
|
||||
var messageTickColor: UIColor = .white
|
||||
|
||||
func applyStyle(onTabBar tabBar: UITabBar) {
|
||||
|
@ -137,4 +140,10 @@ class DarkTheme: NSObject, Theme {
|
|||
button.tintColor = self.tintColor
|
||||
button.setTitleColor(self.tintColor, for: .normal)
|
||||
}
|
||||
|
||||
/// MARK: - Theme v2
|
||||
|
||||
lazy var colors: Colors = {
|
||||
return DarkColors()
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import DesignKit
|
||||
|
||||
/// Color constants for the default theme
|
||||
@objcMembers
|
||||
|
@ -97,6 +98,8 @@ class DefaultTheme: NSObject, Theme {
|
|||
|
||||
var secondaryCircleButtonBackgroundColor: UIColor = UIColor(rgb: 0xE3E8F0)
|
||||
|
||||
var shadowColor: UIColor = UIColor(rgb: 0x000000)
|
||||
|
||||
func applyStyle(onTabBar tabBar: UITabBar) {
|
||||
tabBar.unselectedItemTintColor = self.tabBarUnselectedItemTintColor
|
||||
tabBar.tintColor = self.tintColor
|
||||
|
@ -144,4 +147,10 @@ class DefaultTheme: NSObject, Theme {
|
|||
button.tintColor = self.tintColor
|
||||
button.setTitleColor(self.tintColor, for: .normal)
|
||||
}
|
||||
|
||||
/// MARK: - Theme v2
|
||||
|
||||
lazy var colors: Colors = {
|
||||
return LightColors()
|
||||
}()
|
||||
}
|
||||
|
|
42
Riot/Managers/Widgets/WidgetConstants.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
The type of matrix event used for matrix widgets.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const kWidgetMatrixEventTypeString;
|
||||
|
||||
/**
|
||||
The type of matrix event used for modular widgets.
|
||||
TODO: It should be replaced by kWidgetMatrixEventTypeString.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const kWidgetModularEventTypeString;
|
||||
|
||||
/**
|
||||
Known types widgets.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const kWidgetTypeJitsiV1;
|
||||
FOUNDATION_EXPORT NSString *const kWidgetTypeJitsiV2;
|
||||
FOUNDATION_EXPORT NSString *const kWidgetTypeStickerPicker;
|
||||
|
||||
@interface WidgetConstants : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
|
||||
@end
|
27
Riot/Managers/Widgets/WidgetConstants.m
Normal file
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#import "WidgetConstants.h"
|
||||
|
||||
NSString *const kWidgetMatrixEventTypeString = @"m.widget";
|
||||
NSString *const kWidgetModularEventTypeString = @"im.vector.modular.widgets";
|
||||
NSString *const kWidgetTypeJitsiV1 = @"jitsi";
|
||||
NSString *const kWidgetTypeJitsiV2 = @"m.jitsi";
|
||||
NSString *const kWidgetTypeStickerPicker = @"m.stickerpicker";
|
||||
|
||||
@implementation WidgetConstants
|
||||
|
||||
@end
|
|
@ -20,27 +20,10 @@
|
|||
#import <MatrixSDK/MatrixSDK.h>
|
||||
|
||||
#import "Widget.h"
|
||||
#import "WidgetConstants.h"
|
||||
|
||||
@class WidgetManagerConfig;
|
||||
|
||||
/**
|
||||
The type of matrix event used for matrix widgets.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const kWidgetMatrixEventTypeString;
|
||||
|
||||
/**
|
||||
The type of matrix event used for modular widgets.
|
||||
TODO: It should be replaced by kWidgetMatrixEventTypeString.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const kWidgetModularEventTypeString;
|
||||
|
||||
/**
|
||||
Known types widgets.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const kWidgetTypeJitsiV1;
|
||||
FOUNDATION_EXPORT NSString *const kWidgetTypeJitsiV2;
|
||||
FOUNDATION_EXPORT NSString *const kWidgetTypeStickerPicker;
|
||||
|
||||
/**
|
||||
Posted when a widget has been created, updated or disabled.
|
||||
The notification object is the `Widget` instance.
|
||||
|
|
|
@ -19,17 +19,12 @@
|
|||
|
||||
#import "Riot-Swift.h"
|
||||
#import "JitsiWidgetData.h"
|
||||
#import "MXSession+Riot.h"
|
||||
|
||||
#import <MatrixKit/MatrixKit.h>
|
||||
|
||||
#pragma mark - Contants
|
||||
|
||||
NSString *const kWidgetMatrixEventTypeString = @"m.widget";
|
||||
NSString *const kWidgetModularEventTypeString = @"im.vector.modular.widgets";
|
||||
NSString *const kWidgetTypeJitsiV1 = @"jitsi";
|
||||
NSString *const kWidgetTypeJitsiV2 = @"m.jitsi";
|
||||
NSString *const kWidgetTypeStickerPicker = @"m.stickerpicker";
|
||||
|
||||
NSString *const kWidgetManagerDidUpdateWidgetNotification = @"kWidgetManagerDidUpdateWidgetNotification";
|
||||
|
||||
NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
|
||||
|
@ -284,7 +279,7 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain";
|
|||
// Riot-Web still uses V1 type
|
||||
NSString *widgetId = [NSString stringWithFormat:@"%@_%@_%@", kWidgetTypeJitsiV1, room.mxSession.myUser.userId, @((uint64_t)([[NSDate date] timeIntervalSince1970] * 1000))];
|
||||
|
||||
NSURL *preferredJitsiServerUrl = BuildSettings.jitsiServerUrl;
|
||||
NSURL *preferredJitsiServerUrl = [room.mxSession vc_homeserverConfiguration].jitsi.serverURL;
|
||||
|
||||
JitsiService *jitsiService = JitsiService.shared;
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// Copyright 2020 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Represents the homeserver configuration (usually based on HS Well-Known or hardoced values in the project)
|
||||
@objcMembers
|
||||
final class HomeserverConfiguration: NSObject {
|
||||
|
||||
// Note: Use an object per configuration subject when there is multiple properties related
|
||||
let jitsi: HomeserverJitsiConfiguration
|
||||
let isE2EEByDefaultEnabled: Bool
|
||||
|
||||
init(jitsi: HomeserverJitsiConfiguration,
|
||||
isE2EEByDefaultEnabled: Bool) {
|
||||
self.jitsi = jitsi
|
||||
self.isE2EEByDefaultEnabled = isE2EEByDefaultEnabled
|
||||
|
||||
super.init()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
//
|
||||
// Copyright 2020 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// `HomeserverConfigurationBuilder` build `HomeserverConfiguration` objects according to injected inputs
|
||||
@objcMembers
|
||||
final class HomeserverConfigurationBuilder: NSObject {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private let vectorWellKnownParser = VectorWellKnownParser()
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
/// Create an `HomeserverConfiguration` from an HS Well-Known when possible otherwise it takes hardcoded values from BuildSettings by default.
|
||||
func build(from wellKnown: MXWellKnown?) -> HomeserverConfiguration {
|
||||
|
||||
let isE2EEByDefaultEnabled: Bool
|
||||
let jitsiPreferredDomain: String
|
||||
|
||||
var vectorWellKnownEncryptionConfiguration: VectorWellKnownEncryptionConfiguration?
|
||||
var vectorWellKnownJitsiConfiguration: VectorWellKnownJitsiConfiguration?
|
||||
|
||||
if let wellKnown = wellKnown, let vectorWellKnown = self.vectorWellKnownParser.parse(jsonDictionary: wellKnown.jsonDictionary()) {
|
||||
vectorWellKnownEncryptionConfiguration = self.getEncryptionConfiguration(from: vectorWellKnown)
|
||||
vectorWellKnownJitsiConfiguration = self.getJitsiConfiguration(from: vectorWellKnown)
|
||||
}
|
||||
|
||||
// Encryption configuration
|
||||
if let vectorWellKnownEncryptionConfig = vectorWellKnownEncryptionConfiguration {
|
||||
isE2EEByDefaultEnabled = vectorWellKnownEncryptionConfig.isE2EEByDefaultEnabled
|
||||
} else {
|
||||
// Enable E2EE by default when there is no value
|
||||
isE2EEByDefaultEnabled = true
|
||||
}
|
||||
|
||||
// Jitsi configuration
|
||||
let jitsiServerURL: URL
|
||||
let hardcodedJitsiServerURL: URL = BuildSettings.jitsiServerUrl
|
||||
|
||||
if let vectorWellKnownJitsiConfig = vectorWellKnownJitsiConfiguration {
|
||||
jitsiPreferredDomain = vectorWellKnownJitsiConfig.preferredDomain
|
||||
jitsiServerURL = self.jitsiServerURL(from: jitsiPreferredDomain) ?? hardcodedJitsiServerURL
|
||||
} else {
|
||||
guard let hardcodedJitsiDomain = hardcodedJitsiServerURL.host else {
|
||||
fatalError("[HomeserverConfigurationBuilder] Fail to get Jitsi domain from hardcoded Jitsi URL")
|
||||
}
|
||||
jitsiPreferredDomain = hardcodedJitsiDomain
|
||||
jitsiServerURL = hardcodedJitsiServerURL
|
||||
}
|
||||
|
||||
// Create HomeserverConfiguration
|
||||
|
||||
let jitsiConfiguration = HomeserverJitsiConfiguration(serverDomain: jitsiPreferredDomain,
|
||||
serverURL: jitsiServerURL)
|
||||
|
||||
return HomeserverConfiguration(jitsi: jitsiConfiguration, isE2EEByDefaultEnabled: isE2EEByDefaultEnabled)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func getJitsiConfiguration(from vectorWellKnown: VectorWellKnown) -> VectorWellKnownJitsiConfiguration? {
|
||||
|
||||
let jitsiConfiguration: VectorWellKnownJitsiConfiguration?
|
||||
|
||||
if let lastJitsiConfiguration = vectorWellKnown.jitsi {
|
||||
jitsiConfiguration = lastJitsiConfiguration
|
||||
} else if let deprecatedJitsiConfiguration = vectorWellKnown.deprecatedJitsi {
|
||||
NSLog("[HomeserverConfigurationBuilder] getJitsiConfiguration - Use deprecated configuration")
|
||||
jitsiConfiguration = deprecatedJitsiConfiguration
|
||||
} else {
|
||||
NSLog("[HomeserverConfigurationBuilder] getJitsiConfiguration - No configuration found")
|
||||
jitsiConfiguration = nil
|
||||
}
|
||||
|
||||
return jitsiConfiguration
|
||||
}
|
||||
|
||||
private func getEncryptionConfiguration(from vectorWellKnown: VectorWellKnown) -> VectorWellKnownEncryptionConfiguration? {
|
||||
|
||||
let encryptionConfiguration: VectorWellKnownEncryptionConfiguration?
|
||||
|
||||
if let lastEncryptionConfiguration = vectorWellKnown.encryption {
|
||||
encryptionConfiguration = lastEncryptionConfiguration
|
||||
} else if let deprecatedEncryptionConfiguration = vectorWellKnown.deprecatedEncryption {
|
||||
NSLog("[HomeserverConfigurationBuilder] getEncryptionConfiguration - Use deprecated configuration")
|
||||
encryptionConfiguration = deprecatedEncryptionConfiguration
|
||||
} else {
|
||||
NSLog("[HomeserverConfigurationBuilder] getEncryptionConfiguration - No configuration found")
|
||||
encryptionConfiguration = nil
|
||||
}
|
||||
|
||||
return encryptionConfiguration
|
||||
}
|
||||
|
||||
private func jitsiServerURL(from jitsiServerDomain: String) -> URL? {
|
||||
let jitsiStringURL: String
|
||||
if jitsiServerDomain.starts(with: "http") {
|
||||
jitsiStringURL = jitsiServerDomain
|
||||
} else {
|
||||
jitsiStringURL = "https://\(jitsiServerDomain)"
|
||||
}
|
||||
|
||||
guard let jitsiServerURL = URL(string: jitsiStringURL) else {
|
||||
NSLog("[HomeserverConfigurationBuilder] Jitsi server URL is not valid")
|
||||
return nil
|
||||
}
|
||||
|
||||
return jitsiServerURL
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// Copyright 2020 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// `HomeserverJitsiConfiguration` gives Jitsi widget configuration used by homeserver
|
||||
@objcMembers
|
||||
final class HomeserverJitsiConfiguration: NSObject {
|
||||
let serverDomain: String
|
||||
let serverURL: URL
|
||||
|
||||
init(serverDomain: String, serverURL: URL) {
|
||||
self.serverDomain = serverDomain
|
||||
self.serverURL = serverURL
|
||||
|
||||
super.init()
|
||||
}
|
||||
}
|
60
Riot/Model/WellKnown/VectorWellKnown.swift
Normal file
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// Copyright 2020 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Well Known
|
||||
|
||||
/// `VectorWellKnown` represents additional Well Known configuration specific to Element client
|
||||
struct VectorWellKnown {
|
||||
let encryption: VectorWellKnownEncryptionConfiguration?
|
||||
let jitsi: VectorWellKnownJitsiConfiguration?
|
||||
|
||||
// Deprecated properties
|
||||
let deprecatedEncryption: VectorWellKnownEncryptionConfiguration?
|
||||
let deprecatedJitsi: VectorWellKnownJitsiConfiguration?
|
||||
}
|
||||
|
||||
// MARK: Decodable
|
||||
extension VectorWellKnown: Decodable {
|
||||
/// JSON keys associated to VectorWellKnown properties
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case encryption = "io.element.e2ee"
|
||||
case jitsi = "io.element.jitsi"
|
||||
// Deprecated keys
|
||||
case deprecatedEncryption = "im.vector.riot.e2ee"
|
||||
case deprecatedJitsi = "im.vector.riot.jitsi"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Encryption
|
||||
|
||||
struct VectorWellKnownEncryptionConfiguration: Decodable {
|
||||
|
||||
/// Indicate if E2EE is enabled by default
|
||||
let isE2EEByDefaultEnabled: Bool
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case isE2EEByDefaultEnabled = "default"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Jitsi
|
||||
struct VectorWellKnownJitsiConfiguration: Decodable {
|
||||
|
||||
/// Default Jitsi server
|
||||
let preferredDomain: String
|
||||
}
|
34
Riot/Model/WellKnown/VectorWellKnownParser.swift
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// Copyright 2020 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class VectorWellKnownParser {
|
||||
|
||||
func parse(jsonDictionary: [AnyHashable: Any]) -> VectorWellKnown? {
|
||||
let serializationService = SerializationService()
|
||||
let vectorWellKnown: VectorWellKnown?
|
||||
|
||||
do {
|
||||
vectorWellKnown = try serializationService.deserialize(jsonDictionary)
|
||||
} catch {
|
||||
vectorWellKnown = nil
|
||||
NSLog("[VectorWellKnownParser] Fail to parse application Well Known keys with error: \(error)")
|
||||
}
|
||||
|
||||
return vectorWellKnown
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@
|
|||
@protocol Configurable;
|
||||
@protocol LegacyAppDelegateDelegate;
|
||||
@class CallBar;
|
||||
@class CallPresenter;
|
||||
|
||||
#pragma mark - Notifications
|
||||
/**
|
||||
|
@ -54,8 +55,8 @@ extern NSString *const AppDelegateUniversalLinkDidChangeNotification;
|
|||
@interface LegacyAppDelegate : UIResponder <
|
||||
UIApplicationDelegate,
|
||||
UISplitViewControllerDelegate,
|
||||
UINavigationControllerDelegate,
|
||||
JitsiViewControllerDelegate>
|
||||
UINavigationControllerDelegate
|
||||
>
|
||||
{
|
||||
// background sync management
|
||||
void (^_completionHandler)(UIBackgroundFetchResult);
|
||||
|
@ -117,6 +118,11 @@ JitsiViewControllerDelegate>
|
|||
// Build Settings
|
||||
@property (nonatomic, readonly) id<Configurable> configuration;
|
||||
|
||||
/**
|
||||
Call presenter instance. May be nil unless at least one session initialized.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) CallPresenter *callPresenter;
|
||||
|
||||
+ (instancetype)theDelegate;
|
||||
|
||||
#pragma mark - Push Notifications
|
||||
|
|
|
@ -224,7 +224,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
@property (nonatomic, strong) PushNotificationService *pushNotificationService;
|
||||
@property (nonatomic, strong) PushNotificationStore *pushNotificationStore;
|
||||
@property (nonatomic, strong) LocalAuthenticationService *localAuthenticationService;
|
||||
@property (nonatomic, strong) CallPresenter *callPresenter;
|
||||
@property (nonatomic, strong, readwrite) CallPresenter *callPresenter;
|
||||
|
||||
@property (nonatomic, strong) MajorUpdateManager *majorUpdateManager;
|
||||
|
||||
|
@ -3061,90 +3061,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
}
|
||||
}
|
||||
|
||||
#pragma mark - Jitsi call
|
||||
|
||||
- (void)displayJitsiViewControllerWithWidget:(Widget*)jitsiWidget andVideo:(BOOL)video
|
||||
{
|
||||
#ifdef CALL_STACK_JINGLE
|
||||
if (!_jitsiViewController)
|
||||
{
|
||||
MXWeakify(self);
|
||||
[self checkPermissionForNativeWidget:jitsiWidget fromUrl:JitsiService.shared.serverURL completion:^(BOOL granted) {
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
if (!granted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self->_jitsiViewController = [JitsiViewController jitsiViewController];
|
||||
|
||||
[self->_jitsiViewController openWidget:jitsiWidget withVideo:video success:^{
|
||||
|
||||
self->_jitsiViewController.delegate = self;
|
||||
[self presentJitsiViewController:nil];
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
||||
self->_jitsiViewController = nil;
|
||||
|
||||
[self showAlertWithTitle:nil message:NSLocalizedStringFromTable(@"call_jitsi_error", @"Vector", nil)];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self showAlertWithTitle:nil message:NSLocalizedStringFromTable(@"call_already_displayed", @"Vector", nil)];
|
||||
}
|
||||
#else
|
||||
[self showAlertWithTitle:nil message:[NSBundle mxk_localizedStringForKey:@"not_supported_yet"]];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)presentJitsiViewController:(void (^)(void))completion
|
||||
{
|
||||
[self removeCallStatusBar];
|
||||
|
||||
if (_jitsiViewController)
|
||||
{
|
||||
if (@available(iOS 13.0, *))
|
||||
{
|
||||
_jitsiViewController.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
}
|
||||
|
||||
[self presentViewController:_jitsiViewController animated:YES completion:completion];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)jitsiViewController:(JitsiViewController *)jitsiViewController dismissViewJitsiController:(void (^)(void))completion
|
||||
{
|
||||
if (jitsiViewController == _jitsiViewController)
|
||||
{
|
||||
[_jitsiViewController dismissViewControllerAnimated:YES completion:completion];
|
||||
_jitsiViewController = nil;
|
||||
|
||||
[self removeCallStatusBar];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)jitsiViewController:(JitsiViewController *)jitsiViewController goBackToApp:(void (^)(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.summary.displayname];
|
||||
[self updateCallStatusBar:btnTitle];
|
||||
|
||||
if (completion)
|
||||
{
|
||||
completion();
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Native Widget Permission
|
||||
|
||||
- (void)checkPermissionForNativeWidget:(Widget*)widget fromUrl:(NSURL*)url completion:(void (^)(BOOL granted))completion
|
||||
|
@ -3277,13 +3193,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
return result;
|
||||
}
|
||||
|
||||
- (void)updateCallStatusBar:(NSString*)title
|
||||
- (void)displayCallStatusBarWithTitle:(NSString*)title
|
||||
{
|
||||
if (_callBar)
|
||||
{
|
||||
_callBar.title = title;
|
||||
return;
|
||||
}
|
||||
// Add a call status bar
|
||||
CGSize topBarSize = CGSizeMake([[UIScreen mainScreen] bounds].size.width, [self calculateCallStatusBarHeight]);
|
||||
|
||||
|
@ -3306,22 +3217,27 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
[_callBar.heightAnchor constraintEqualToAnchor:_callStatusBarWindow.heightAnchor].active = YES;
|
||||
|
||||
_callStatusBarWindow.hidden = NO;
|
||||
[self statusBarDidChangeFrame];
|
||||
[self deviceOrientationDidChange];
|
||||
|
||||
// We need to listen to the system status bar size change events to refresh the root controller frame.
|
||||
// We need to listen to the device orientation change events to refresh the root controller frame.
|
||||
// Else the navigation bar position will be wrong.
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(statusBarDidChangeFrame)
|
||||
name:UIApplicationDidChangeStatusBarFrameNotification
|
||||
selector:@selector(deviceOrientationDidChange)
|
||||
name:UIDeviceOrientationDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)updateCallStatusBarWithTitle:(NSString*)title
|
||||
{
|
||||
_callBar.title = title;
|
||||
}
|
||||
|
||||
- (void)removeCallStatusBar
|
||||
{
|
||||
if (_callStatusBarWindow)
|
||||
{
|
||||
// No more need to listen to system status bar changes
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
|
||||
// No more need to listen to device orientation changes
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
|
||||
|
||||
// Hide & destroy it
|
||||
_callStatusBarWindow.hidden = YES;
|
||||
|
@ -3329,11 +3245,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
_callBar = nil;
|
||||
_callStatusBarWindow = nil;
|
||||
|
||||
[self statusBarDidChangeFrame];
|
||||
[self deviceOrientationDidChange];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)statusBarDidChangeFrame
|
||||
- (void)deviceOrientationDidChange
|
||||
{
|
||||
UIApplication *app = [UIApplication sharedApplication];
|
||||
UIViewController *rootController = app.keyWindow.rootViewController;
|
||||
|
@ -3345,30 +3261,20 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
{
|
||||
CGFloat callStatusBarHeight = [self calculateCallStatusBarHeight];
|
||||
|
||||
UIInterfaceOrientation statusBarOrientation = [UIApplication sharedApplication].statusBarOrientation;
|
||||
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
|
||||
CGFloat width;
|
||||
|
||||
switch (statusBarOrientation)
|
||||
if (UIDeviceOrientationIsPortrait(deviceOrientation))
|
||||
{
|
||||
case UIInterfaceOrientationLandscapeLeft:
|
||||
{
|
||||
_callStatusBarWindow.frame = CGRectMake(-rootControllerFrame.size.width / 2, -callStatusBarHeight / 2, rootControllerFrame.size.width, callStatusBarHeight);
|
||||
_callStatusBarWindow.transform = CGAffineTransformMake(0, -1, 1, 0, callStatusBarHeight / 2, rootControllerFrame.size.width / 2);
|
||||
break;
|
||||
}
|
||||
case UIInterfaceOrientationLandscapeRight:
|
||||
{
|
||||
_callStatusBarWindow.frame = CGRectMake(-rootControllerFrame.size.width / 2, -callStatusBarHeight / 2, rootControllerFrame.size.width, callStatusBarHeight);
|
||||
_callStatusBarWindow.transform = CGAffineTransformMake(0, 1, -1, 0, rootControllerFrame.size.height - callStatusBarHeight / 2, rootControllerFrame.size.width / 2);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
_callStatusBarWindow.transform = CGAffineTransformIdentity;
|
||||
_callStatusBarWindow.frame = CGRectMake(0, 0, rootControllerFrame.size.width, callStatusBarHeight);
|
||||
break;
|
||||
}
|
||||
width = MIN(rootControllerFrame.size.width, rootControllerFrame.size.height);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
width = MAX(rootControllerFrame.size.width, rootControllerFrame.size.height);
|
||||
}
|
||||
|
||||
_callStatusBarWindow.frame = CGRectMake(0, 0, width, callStatusBarHeight);
|
||||
|
||||
// Apply the vertical offset due to call status bar
|
||||
rootControllerFrame.origin.y = callStatusBarHeight;
|
||||
rootControllerFrame.size.height -= callStatusBarHeight;
|
||||
|
@ -4530,12 +4436,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
|
||||
#pragma mark - CallPresenterDelegate
|
||||
|
||||
- (BOOL)callPresenter:(CallPresenter *)presenter shouldHandleNewCall:(MXCall *)call
|
||||
{
|
||||
// Ignore the call if a call is already in progress
|
||||
return _jitsiViewController == nil;
|
||||
}
|
||||
|
||||
- (void)callPresenter:(CallPresenter *)presenter presentCallViewController:(CallViewController *)viewController completion:(void (^)(void))completion
|
||||
{
|
||||
if (@available(iOS 13.0, *))
|
||||
|
@ -4543,19 +4443,23 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
viewController.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
}
|
||||
|
||||
[self presentViewController:viewController animated:YES completion:completion];
|
||||
[self presentViewController:viewController animated:NO completion:completion];
|
||||
}
|
||||
|
||||
- (void)callPresenter:(CallPresenter *)presenter dismissCallViewController:(CallViewController *)viewController completion:(void (^)(void))completion
|
||||
- (void)callPresenter:(CallPresenter *)presenter dismissCallViewController:(UIViewController *)viewController completion:(void (^)(void))completion
|
||||
{
|
||||
// Check whether the call view controller is actually presented
|
||||
if (viewController.presentingViewController)
|
||||
{
|
||||
[viewController dismissViewControllerAnimated:YES completion:^{
|
||||
[viewController.presentingViewController dismissViewControllerAnimated:NO completion:^{
|
||||
|
||||
if (viewController.shouldPromptForStunServerFallback)
|
||||
if ([viewController isKindOfClass:CallViewController.class])
|
||||
{
|
||||
[self promptForStunServerFallback];
|
||||
CallViewController *callVC = (CallViewController *)viewController;
|
||||
if (callVC.shouldPromptForStunServerFallback)
|
||||
{
|
||||
[self promptForStunServerFallback];
|
||||
}
|
||||
}
|
||||
|
||||
if (completion)
|
||||
|
@ -4574,26 +4478,69 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
}
|
||||
}
|
||||
|
||||
- (void)callPresenter:(CallPresenter *)presenter presentCallBarFor:(CallViewController *)activeCallViewController numberOfPausedCalls:(NSUInteger)numberOfPausedCalls completion:(void (^)(void))completion
|
||||
- (void)callPresenter:(CallPresenter *)presenter presentCallBarFor:(UIViewController *)activeCallViewController numberOfPausedCalls:(NSUInteger)numberOfPausedCalls completion:(void (^)(void))completion
|
||||
{
|
||||
[self displayCallStatusBarWithTitle:nil];
|
||||
[self callPresenter:presenter updateCallBarFor:activeCallViewController numberOfPausedCalls:numberOfPausedCalls];
|
||||
|
||||
if (completion)
|
||||
{
|
||||
completion();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)callPresenter:(CallPresenter *)presenter updateCallBarFor:(UIViewController *)activeCallViewController numberOfPausedCalls:(NSUInteger)numberOfPausedCalls
|
||||
{
|
||||
NSString *btnTitle;
|
||||
|
||||
if (activeCallViewController)
|
||||
{
|
||||
NSString *callStatus = @"";
|
||||
BOOL isGroupCall = NO;
|
||||
if ([activeCallViewController isKindOfClass:[CallViewController class]])
|
||||
{
|
||||
CallViewController *activeCallVC = (CallViewController *)activeCallViewController;
|
||||
callStatus = activeCallVC.callStatusLabel.text;
|
||||
}
|
||||
else if ([activeCallViewController isKindOfClass:[JitsiViewController class]])
|
||||
{
|
||||
JitsiViewController *jitsiVC = (JitsiViewController *)activeCallViewController;
|
||||
NSUInteger duration = jitsiVC.callDuration / 1000;
|
||||
NSUInteger secs = duration % 60;
|
||||
NSUInteger mins = (duration / 60) % 60;
|
||||
NSUInteger hours = duration / 3600;
|
||||
if (hours > 0)
|
||||
{
|
||||
callStatus = [NSString stringWithFormat:@"%02tu:%02tu:%02tu", hours, mins, secs];
|
||||
}
|
||||
else
|
||||
{
|
||||
callStatus = [NSString stringWithFormat:@"%02tu:%02tu", mins, secs];
|
||||
}
|
||||
isGroupCall = YES;
|
||||
}
|
||||
|
||||
if (numberOfPausedCalls == 0)
|
||||
{
|
||||
// only one active
|
||||
btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"callbar_only_single_active", @"Vector", nil), activeCallViewController.callStatusLabel.text];
|
||||
if (isGroupCall)
|
||||
{
|
||||
btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"callbar_only_single_active_group", @"Vector", nil), callStatus];
|
||||
}
|
||||
else
|
||||
{
|
||||
btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"callbar_only_single_active", @"Vector", nil), callStatus];
|
||||
}
|
||||
}
|
||||
else if (numberOfPausedCalls == 1)
|
||||
{
|
||||
// one active and one paused
|
||||
btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"callbar_active_and_single_paused", @"Vector", nil), activeCallViewController.callStatusLabel.text];
|
||||
btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"callbar_active_and_single_paused", @"Vector", nil), callStatus];
|
||||
}
|
||||
else
|
||||
{
|
||||
// one active and multiple paused
|
||||
btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"callbar_active_and_multiple_paused", @"Vector", nil), activeCallViewController.callStatusLabel.text, @(numberOfPausedCalls)];
|
||||
btnTitle = [NSString stringWithFormat:NSLocalizedStringFromTable(@"callbar_active_and_multiple_paused", @"Vector", nil), callStatus, @(numberOfPausedCalls)];
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -4609,12 +4556,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
}
|
||||
}
|
||||
|
||||
[self updateCallStatusBar:btnTitle];
|
||||
|
||||
if (completion)
|
||||
{
|
||||
completion();
|
||||
}
|
||||
[self updateCallStatusBarWithTitle:btnTitle];
|
||||
}
|
||||
|
||||
- (void)callPresenter:(CallPresenter *)presenter dismissCallBar:(void (^)(void))completion
|
||||
|
@ -4627,7 +4569,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
}
|
||||
}
|
||||
|
||||
- (void)callPresenter:(CallPresenter *)presenter enterPipForCallViewController:(CallViewController *)viewController completion:(void (^)(void))completion
|
||||
- (void)callPresenter:(CallPresenter *)presenter enterPipForCallViewController:(UIViewController *)viewController completion:(void (^)(void))completion
|
||||
{
|
||||
// Check whether the call view controller is actually presented
|
||||
if (viewController.presentingViewController)
|
||||
|
@ -4643,7 +4585,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
}
|
||||
}
|
||||
|
||||
- (void)callPresenter:(CallPresenter *)presenter exitPipForCallViewController:(CallViewController *)viewController completion:(void (^)(void))completion
|
||||
- (void)callPresenter:(CallPresenter *)presenter exitPipForCallViewController:(UIViewController *)viewController completion:(void (^)(void))completion
|
||||
{
|
||||
if (@available(iOS 13.0, *))
|
||||
{
|
||||
|
@ -4655,18 +4597,11 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
|
|||
|
||||
#pragma mark - CallBarDelegate
|
||||
|
||||
- (void)callBarDidTapReturnButton:(CallBar *)callBar
|
||||
- (void)callBarDidTap:(CallBar *)callBar
|
||||
{
|
||||
if ([_callPresenter callStatusBarButtonTapped])
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (_jitsiViewController)
|
||||
{
|
||||
[self presentJitsiViewController:nil];
|
||||
}
|
||||
[_callPresenter callStatusBarTapped];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Authentication
|
||||
|
||||
- (BOOL)continueSSOLoginWithToken:(NSString*)loginToken txnId:(NSString*)txnId
|
||||
|
|
|
@ -1430,7 +1430,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
|||
// TODO: This is still not sure we want to disable the automatic cross-signing bootstrap
|
||||
// if the admin disabled e2e by default.
|
||||
// Do like riot-web for the moment
|
||||
if (session.vc_isE2EByDefaultEnabledByHSAdmin)
|
||||
if ([session vc_homeserverConfiguration].isE2EEByDefaultEnabled)
|
||||
{
|
||||
// Bootstrap cross-signing on user's account
|
||||
// We do it for both registration and new login as long as cross-signing does not exist yet
|
||||
|
|
|
@ -21,13 +21,11 @@
|
|||
*/
|
||||
@interface CallViewController : MXKCallViewController
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIView *gradientMaskContainerView;
|
||||
@property (weak, nonatomic) IBOutlet UIButton *chatButton;
|
||||
|
||||
@property (weak, nonatomic) IBOutlet UIView *callControlsBackgroundView;
|
||||
|
||||
@property (unsafe_unretained, nonatomic) IBOutlet NSLayoutConstraint *callerImageViewWidthConstraint;
|
||||
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *moreButtonLeadingConstraint;
|
||||
|
||||
// Effect views
|
||||
@property (weak, nonatomic) IBOutlet MXKImageView *blurredCallerImageView;
|
||||
|
|
|
@ -107,6 +107,12 @@
|
|||
[self.endCallButton setImage:hangUpButtonImage forState:UIControlStateNormal];
|
||||
[self.endCallButton setImage:hangUpButtonImage forState:UIControlStateHighlighted];
|
||||
|
||||
// force orientation to portrait if phone
|
||||
if ([UIDevice currentDevice].isPhone)
|
||||
{
|
||||
[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger: UIInterfaceOrientationPortrait] forKey:@"orientation"];
|
||||
}
|
||||
|
||||
[self updateLocalPreviewLayout];
|
||||
|
||||
[self configureUserInterface];
|
||||
|
@ -134,6 +140,8 @@
|
|||
self.callStatusLabel.textColor = self.overriddenTheme.baseTextPrimaryColor;
|
||||
[self.resumeButton setTitleColor:self.overriddenTheme.tintColor
|
||||
forState:UIControlStateNormal];
|
||||
[self.transferButton setTitleColor:self.overriddenTheme.tintColor
|
||||
forState:UIControlStateNormal];
|
||||
|
||||
self.localPreviewContainerView.layer.borderColor = self.overriddenTheme.tintColor.CGColor;
|
||||
self.localPreviewContainerView.layer.borderWidth = 2;
|
||||
|
@ -152,6 +160,30 @@
|
|||
[super viewWillDisappear:animated];
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
{
|
||||
// limit orientation to portrait only for phone
|
||||
if ([UIDevice currentDevice].isPhone)
|
||||
{
|
||||
return UIInterfaceOrientationMaskPortrait;
|
||||
}
|
||||
return [super supportedInterfaceOrientations];
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
|
||||
{
|
||||
if ([UIDevice currentDevice].isPhone)
|
||||
{
|
||||
return UIInterfaceOrientationPortrait;
|
||||
}
|
||||
return [super preferredInterfaceOrientationForPresentation];
|
||||
}
|
||||
|
||||
- (BOOL)shouldAutorotate
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - override MXKViewController
|
||||
|
||||
- (UIView *)createIncomingCallView
|
||||
|
@ -347,18 +379,6 @@
|
|||
return _overriddenTheme;
|
||||
}
|
||||
|
||||
- (void)setMxCall:(MXCall *)mxCall
|
||||
{
|
||||
[super setMxCall:mxCall];
|
||||
|
||||
if (self.videoMuteButton.isHidden)
|
||||
{
|
||||
// shift more button to left
|
||||
self.moreButtonLeadingConstraint.constant = 8.0;
|
||||
[self.view layoutIfNeeded];
|
||||
}
|
||||
}
|
||||
|
||||
- (UIImage*)picturePlaceholder
|
||||
{
|
||||
CGFloat fontSize = floor(self.callerImageViewWidthConstraint.constant * 0.7);
|
||||
|
@ -385,26 +405,19 @@
|
|||
|
||||
- (void)updatePeerInfoDisplay
|
||||
{
|
||||
NSString *peerDisplayName;
|
||||
NSString *peerAvatarURL;
|
||||
[super updatePeerInfoDisplay];
|
||||
|
||||
NSString *peerAvatarURL;
|
||||
|
||||
if (self.peer)
|
||||
{
|
||||
peerDisplayName = [self.peer displayname];
|
||||
if (!peerDisplayName.length)
|
||||
{
|
||||
peerDisplayName = self.peer.userId;
|
||||
}
|
||||
peerAvatarURL = self.peer.avatarUrl;
|
||||
}
|
||||
else if (self.mxCall.isConferenceCall)
|
||||
{
|
||||
peerDisplayName = self.mxCall.room.summary.displayname;
|
||||
peerAvatarURL = self.mxCall.room.summary.avatar;
|
||||
}
|
||||
|
||||
self.callerNameLabel.text = peerDisplayName;
|
||||
|
||||
|
||||
self.blurredCallerImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
self.callerImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
if (peerAvatarURL)
|
||||
|
@ -415,7 +428,7 @@
|
|||
andImageOrientation:UIImageOrientationUp
|
||||
previewImage:self.picturePlaceholder
|
||||
mediaManager:self.mainSession.mediaManager];
|
||||
|
||||
|
||||
// Retrieve the avatar in full resolution
|
||||
[self.callerImageView setImageURI:peerAvatarURL
|
||||
withType:nil
|
||||
|
@ -521,7 +534,8 @@
|
|||
showsBackspaceButton:NO
|
||||
showsCallButton:NO
|
||||
formattingEnabled:NO
|
||||
editingEnabled:NO];
|
||||
editingEnabled:NO
|
||||
playTones:YES];
|
||||
DialpadViewController *controller = [DialpadViewController instantiateWithConfiguration:config];
|
||||
controller.delegate = self;
|
||||
self.customSizedPresentationController = [[CustomSizedPresentationController alloc] initWithPresentedViewController:controller presentingViewController:self];
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_5" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
|
@ -20,24 +20,25 @@
|
|||
<outlet property="callerImageView" destination="v1I-LH-wvv" id="xjv-GV-1ST"/>
|
||||
<outlet property="callerImageViewWidthConstraint" destination="wqp-O7-7Yc" id="Yx8-2L-lzA"/>
|
||||
<outlet property="callerNameLabel" destination="IW8-8P-mS3" id="eaa-oo-l01"/>
|
||||
<outlet property="cameraSwitchButton" destination="Iiz-W1-oNW" id="bsM-mH-ti8"/>
|
||||
<outlet property="cameraSwitchButton" destination="QPM-9v-fDU" id="CbP-Nw-sX7"/>
|
||||
<outlet property="chatButton" destination="Iiz-W1-oNW" id="ion-1n-TOr"/>
|
||||
<outlet property="endCallButton" destination="lVK-d8-Dqf" id="zgE-Go-bo6"/>
|
||||
<outlet property="localPreviewActivityView" destination="Bhz-hS-5B6" id="2yA-xn-ITf"/>
|
||||
<outlet property="localPreviewContainerView" destination="6gQ-zo-2Zw" id="6Ba-YZ-OmX"/>
|
||||
<outlet property="localPreviewContainerView" destination="6gQ-zo-2Zw" id="2ch-8A-wzL"/>
|
||||
<outlet property="localPreviewContainerViewHeightConstraint" destination="dZW-ZL-5rV" id="apO-M6-y8C"/>
|
||||
<outlet property="localPreviewContainerViewLeadingConstraint" destination="Qvg-FG-sBr" id="gtd-fs-rQh"/>
|
||||
<outlet property="localPreviewContainerViewTopConstraint" destination="6gi-ec-ZnO" id="a0K-Ix-oIo"/>
|
||||
<outlet property="localPreviewContainerViewWidthConstraint" destination="gyu-kv-SLy" id="urn-uo-hnt"/>
|
||||
<outlet property="localPreviewVideoView" destination="5XG-md-r93" id="Zdf-Mj-rWp"/>
|
||||
<outlet property="moreButton" destination="xCi-hD-FBs" id="2Hn-T9-jZY"/>
|
||||
<outlet property="moreButtonLeadingConstraint" destination="Erd-Js-vLN" id="fpN-9m-rOc"/>
|
||||
<outlet property="onHoldCallContainerView" destination="4TX-46-pAi" id="SsP-eX-aHP"/>
|
||||
<outlet property="onHoldCallerImageView" destination="uFS-C2-TxV" id="SfP-iP-jgd"/>
|
||||
<outlet property="overlayContainerView" destination="JAR-tn-sGN" id="09u-3G-1UA"/>
|
||||
<outlet property="pausedIcon" destination="rjn-DE-i10" id="CHR-mM-Dae"/>
|
||||
<outlet property="pipButton" destination="Yq6-d2-Ea2" id="scW-S6-fpr"/>
|
||||
<outlet property="rejectCallButton" destination="H4g-11-Y8d" id="8aM-2q-Lv7"/>
|
||||
<outlet property="remotePreviewContainerView" destination="Tjb-57-yB1" id="MaR-IC-ZKw"/>
|
||||
<outlet property="resumeButton" destination="OaQ-Ki-dbe" id="23i-sp-y3f"/>
|
||||
<outlet property="transferButton" destination="wqv-Uf-iNe" id="jua-io-h9q"/>
|
||||
<outlet property="videoMuteButton" destination="UHM-u9-ODN" id="bfQ-lc-WMB"/>
|
||||
<outlet property="view" destination="iN0-l3-epB" id="JDF-cz-roW"/>
|
||||
</connections>
|
||||
|
@ -77,21 +78,54 @@
|
|||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCRemotePreviewContainerView"/>
|
||||
</view>
|
||||
<view hidden="YES" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleAspectFill" translatesAutoresizingMaskIntoConstraints="NO" id="6gQ-zo-2Zw" userLabel="Local Preview Container">
|
||||
<rect key="frame" x="20" y="464" width="79" height="106"/>
|
||||
<view opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleAspectFill" translatesAutoresizingMaskIntoConstraints="NO" id="6gQ-zo-2Zw" userLabel="Local Preview Container">
|
||||
<rect key="frame" x="20" y="464" width="90" height="130"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5XG-md-r93" userLabel="Local Preview Video View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="90" height="130"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="DsK-QY-Lz0">
|
||||
<rect key="frame" x="0.0" y="106" width="90" height="24"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QPM-9v-fDU">
|
||||
<rect key="frame" x="60" y="-6" width="30" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="A1c-u0-AXO"/>
|
||||
<constraint firstAttribute="width" constant="30" id="pzQ-Xn-q0G"/>
|
||||
</constraints>
|
||||
<state key="normal" image="camera_switch"/>
|
||||
<connections>
|
||||
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="GQO-Fz-RBU"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="24" id="LuF-W3-nmg"/>
|
||||
<constraint firstAttribute="bottom" secondItem="QPM-9v-fDU" secondAttribute="bottom" id="Pvs-4X-IjN"/>
|
||||
<constraint firstAttribute="trailing" secondItem="QPM-9v-fDU" secondAttribute="trailing" id="lFs-G2-cuc"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="Bhz-hS-5B6">
|
||||
<rect key="frame" x="21" y="34.5" width="37" height="37"/>
|
||||
<rect key="frame" x="26.666666666666664" y="46.666666666666686" width="37" height="37"/>
|
||||
</activityIndicatorView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCLocalPreviewContainerView">
|
||||
<accessibilityTraits key="traits" notEnabled="YES"/>
|
||||
</accessibility>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="106" id="dZW-ZL-5rV"/>
|
||||
<constraint firstAttribute="width" constant="79" id="gyu-kv-SLy"/>
|
||||
<constraint firstItem="DsK-QY-Lz0" firstAttribute="leading" secondItem="6gQ-zo-2Zw" secondAttribute="leading" id="7Hz-rj-fhw"/>
|
||||
<constraint firstAttribute="bottom" secondItem="DsK-QY-Lz0" secondAttribute="bottom" id="C9P-5h-UAT"/>
|
||||
<constraint firstAttribute="trailing" secondItem="DsK-QY-Lz0" secondAttribute="trailing" id="ZbL-0k-X3Z"/>
|
||||
<constraint firstItem="5XG-md-r93" firstAttribute="leading" secondItem="6gQ-zo-2Zw" secondAttribute="leading" id="aQX-E8-wvE"/>
|
||||
<constraint firstItem="5XG-md-r93" firstAttribute="top" secondItem="6gQ-zo-2Zw" secondAttribute="top" id="aWP-oA-jDd"/>
|
||||
<constraint firstAttribute="height" constant="130" id="dZW-ZL-5rV"/>
|
||||
<constraint firstAttribute="width" constant="90" id="gyu-kv-SLy"/>
|
||||
<constraint firstItem="Bhz-hS-5B6" firstAttribute="centerY" secondItem="6gQ-zo-2Zw" secondAttribute="centerY" id="jTh-Rj-Eew"/>
|
||||
<constraint firstAttribute="bottom" secondItem="5XG-md-r93" secondAttribute="bottom" id="phV-bf-EXl"/>
|
||||
<constraint firstAttribute="trailing" secondItem="5XG-md-r93" secondAttribute="trailing" id="t7q-e1-042"/>
|
||||
<constraint firstItem="Bhz-hS-5B6" firstAttribute="centerX" secondItem="6gQ-zo-2Zw" secondAttribute="centerX" id="w3I-wQ-iiT"/>
|
||||
</constraints>
|
||||
</view>
|
||||
|
@ -110,7 +144,6 @@
|
|||
<constraint firstAttribute="width" constant="44" id="yAh-Gb-GtB"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="13"/>
|
||||
<inset key="titleEdgeInsets" minX="-69" minY="61" maxX="0.0" maxY="0.0"/>
|
||||
<state key="normal" image="back_icon">
|
||||
<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"/>
|
||||
|
@ -122,17 +155,43 @@
|
|||
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="yKw-Aj-XkE"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Iiz-W1-oNW" userLabel="Switch Camera Btn">
|
||||
<rect key="frame" x="360" y="5" width="44" height="44"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="scaleToFill" text="Name" textAlignment="center" lineBreakMode="middleTruncation" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="IW8-8P-mS3">
|
||||
<rect key="frame" x="54" y="4" width="306" height="20.333333333333332"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCCallerNameLabel"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="scaleToFill" text="Duration" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="12" translatesAutoresizingMaskIntoConstraints="NO" id="29y-MK-OWH">
|
||||
<rect key="frame" x="182" y="32.333333333333329" width="50" height="14.333333333333336"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCCallStatusLabel"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="12"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="IW8-8P-mS3" secondAttribute="trailing" constant="54" id="KKK-Wz-1w3"/>
|
||||
<constraint firstItem="IW8-8P-mS3" firstAttribute="top" secondItem="sOu-ER-kOe" secondAttribute="top" constant="4" id="Wwm-db-dYV"/>
|
||||
<constraint firstItem="nff-fB-sTq" firstAttribute="leading" secondItem="sOu-ER-kOe" secondAttribute="leading" constant="10" id="guD-oo-Tl5"/>
|
||||
<constraint firstItem="29y-MK-OWH" firstAttribute="top" secondItem="IW8-8P-mS3" secondAttribute="bottom" constant="8" id="iXL-M7-wEB"/>
|
||||
<constraint firstItem="nff-fB-sTq" firstAttribute="centerY" secondItem="sOu-ER-kOe" secondAttribute="centerY" id="vP7-sN-t5g"/>
|
||||
<constraint firstItem="29y-MK-OWH" firstAttribute="centerX" secondItem="sOu-ER-kOe" secondAttribute="centerX" id="yrO-M6-IFX"/>
|
||||
<constraint firstItem="IW8-8P-mS3" firstAttribute="leading" secondItem="nff-fB-sTq" secondAttribute="trailing" id="zfx-8Q-WF1"/>
|
||||
<constraint firstAttribute="height" constant="54" id="zsh-GQ-fv2"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="nk9-Un-LVP" userLabel="Call Control Container View">
|
||||
<rect key="frame" x="0.0" y="774" width="414" height="68"/>
|
||||
<subviews>
|
||||
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Iiz-W1-oNW" userLabel="Chat Button">
|
||||
<rect key="frame" x="12" y="7" width="48" height="48"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCCameraSwitchButton"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="2ci-nt-cgP"/>
|
||||
<constraint firstAttribute="width" constant="44" id="owR-ds-wTp"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="13"/>
|
||||
<inset key="titleEdgeInsets" minX="-69" minY="61" maxX="0.0" maxY="0.0"/>
|
||||
<state key="normal" image="camera_switch">
|
||||
<inset key="contentEdgeInsets" minX="12" minY="12" maxX="12" maxY="12"/>
|
||||
<state key="normal" image="call_go_to_chat_icon">
|
||||
<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>
|
||||
|
@ -143,84 +202,70 @@
|
|||
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="UaC-FO-rmW"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="Iiz-W1-oNW" secondAttribute="trailing" constant="10" id="YYf-d4-U5q"/>
|
||||
<constraint firstItem="nff-fB-sTq" firstAttribute="leading" secondItem="sOu-ER-kOe" secondAttribute="leading" constant="10" id="guD-oo-Tl5"/>
|
||||
<constraint firstItem="Iiz-W1-oNW" firstAttribute="centerY" secondItem="sOu-ER-kOe" secondAttribute="centerY" id="ns9-vB-ywU"/>
|
||||
<constraint firstItem="nff-fB-sTq" firstAttribute="centerY" secondItem="sOu-ER-kOe" secondAttribute="centerY" id="vP7-sN-t5g"/>
|
||||
<constraint firstAttribute="height" constant="54" id="zsh-GQ-fv2"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="nk9-Un-LVP" userLabel="Call Control Container View">
|
||||
<rect key="frame" x="0.0" y="798" width="414" height="64"/>
|
||||
<subviews>
|
||||
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Yq6-d2-Ea2">
|
||||
<rect key="frame" x="71" y="8" width="48" height="48"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCSpeakerButton"/>
|
||||
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="13"/>
|
||||
<inset key="contentEdgeInsets" minX="12" minY="12" maxX="12" maxY="12"/>
|
||||
<state key="normal" image="call_pip_icon">
|
||||
<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="onButtonPressed:" destination="-1" eventType="touchUpInside" id="kfq-w6-EXx"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fxP-zM-kfT">
|
||||
<rect key="frame" x="123" y="12" width="48" height="48"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCAudioMuteButton"/>
|
||||
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="13"/>
|
||||
<state key="normal" image="call_audio_mute_off_icon">
|
||||
<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="onButtonPressed:" destination="-1" eventType="touchUpInside" id="CgE-f8-nPS"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="lVK-d8-Dqf" userLabel="End Call Button">
|
||||
<rect key="frame" x="183" y="8" width="48" height="48"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCEndCallButton"/>
|
||||
<inset key="contentEdgeInsets" minX="4" minY="4" maxX="4" maxY="4"/>
|
||||
<state key="normal" image="call_hangup_large">
|
||||
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<state key="selected" image="call_hangup_icon"/>
|
||||
<state key="highlighted" image="call_hangup_icon"/>
|
||||
<connections>
|
||||
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="ZTw-gz-mwM"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="UHM-u9-ODN">
|
||||
<rect key="frame" x="243" y="12" width="48" height="48"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCVideoMuteButton"/>
|
||||
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="13"/>
|
||||
<state key="normal" image="call_video_mute_off_icon">
|
||||
<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="onButtonPressed:" destination="-1" eventType="touchUpInside" id="9Jd-Wv-foD"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xCi-hD-FBs">
|
||||
<rect key="frame" x="295" y="8" width="48" height="48"/>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="18" translatesAutoresizingMaskIntoConstraints="NO" id="K0z-Tt-rHv">
|
||||
<rect key="frame" x="105" y="6" width="204" height="56"/>
|
||||
<subviews>
|
||||
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fxP-zM-kfT" userLabel="Audio Mute Button">
|
||||
<rect key="frame" x="0.0" y="0.0" width="56" height="56"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCAudioMuteButton"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="56" id="2za-pq-LeK"/>
|
||||
<constraint firstAttribute="height" constant="56" id="WzU-Gl-l7E"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="13"/>
|
||||
<state key="normal" image="call_audio_mute_off_icon">
|
||||
<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="onButtonPressed:" destination="-1" eventType="touchUpInside" id="CgE-f8-nPS"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="lVK-d8-Dqf" userLabel="End Call Button">
|
||||
<rect key="frame" x="74" y="0.0" width="56" height="56"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCEndCallButton"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="56" id="dOZ-Rv-ioc"/>
|
||||
<constraint firstAttribute="height" constant="56" id="ry3-Rb-qxA"/>
|
||||
</constraints>
|
||||
<state key="normal" image="call_hangup_large">
|
||||
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</state>
|
||||
<state key="selected" image="call_hangup_icon"/>
|
||||
<state key="highlighted" image="call_hangup_icon"/>
|
||||
<connections>
|
||||
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="ZTw-gz-mwM"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="UHM-u9-ODN" userLabel="Video Mute Button">
|
||||
<rect key="frame" x="148" y="0.0" width="56" height="56"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCVideoMuteButton"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="56" id="jA5-Bg-GZa"/>
|
||||
<constraint firstAttribute="height" constant="56" id="tJ7-KN-uzF"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="13"/>
|
||||
<state key="normal" image="call_video_mute_off_icon">
|
||||
<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="onButtonPressed:" destination="-1" eventType="touchUpInside" id="9Jd-Wv-foD"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<button opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xCi-hD-FBs" userLabel="More Button">
|
||||
<rect key="frame" x="354" y="6" width="48" height="48"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCChatButton"/>
|
||||
<fontDescription key="fontDescription" name="Helvetica-Bold" family="Helvetica" pointSize="13"/>
|
||||
<inset key="contentEdgeInsets" minX="12" minY="12" maxX="12" maxY="12"/>
|
||||
|
@ -239,21 +284,17 @@
|
|||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCCallControlContainerView"/>
|
||||
<constraints>
|
||||
<constraint firstItem="UHM-u9-ODN" firstAttribute="leading" secondItem="lVK-d8-Dqf" secondAttribute="trailing" constant="12" id="4l8-GS-LMC"/>
|
||||
<constraint firstItem="lVK-d8-Dqf" firstAttribute="centerX" secondItem="nk9-Un-LVP" secondAttribute="centerX" id="C77-dh-aGp"/>
|
||||
<constraint firstItem="Yq6-d2-Ea2" firstAttribute="centerY" secondItem="nk9-Un-LVP" secondAttribute="centerY" id="CgC-oj-Epe"/>
|
||||
<constraint firstItem="xCi-hD-FBs" firstAttribute="leading" secondItem="lVK-d8-Dqf" secondAttribute="trailing" constant="64" id="Erd-Js-vLN"/>
|
||||
<constraint firstItem="fxP-zM-kfT" firstAttribute="centerY" secondItem="nk9-Un-LVP" secondAttribute="centerY" constant="4" id="FXE-lL-Q91"/>
|
||||
<constraint firstItem="UHM-u9-ODN" firstAttribute="centerY" secondItem="nk9-Un-LVP" secondAttribute="centerY" constant="4" id="aOF-7c-8gC"/>
|
||||
<constraint firstItem="fxP-zM-kfT" firstAttribute="leading" secondItem="Yq6-d2-Ea2" secondAttribute="trailing" constant="4" id="aR8-up-zPg"/>
|
||||
<constraint firstAttribute="height" constant="64" id="eS5-qj-HTc"/>
|
||||
<constraint firstItem="lVK-d8-Dqf" firstAttribute="centerY" secondItem="nk9-Un-LVP" secondAttribute="centerY" id="hY9-ut-lPv"/>
|
||||
<constraint firstItem="lVK-d8-Dqf" firstAttribute="leading" secondItem="fxP-zM-kfT" secondAttribute="trailing" constant="12" id="jZa-SH-3LK"/>
|
||||
<constraint firstItem="xCi-hD-FBs" firstAttribute="centerY" secondItem="nk9-Un-LVP" secondAttribute="centerY" id="orn-eU-ogl"/>
|
||||
<constraint firstItem="K0z-Tt-rHv" firstAttribute="centerY" secondItem="nk9-Un-LVP" secondAttribute="centerY" id="42s-hp-QOL"/>
|
||||
<constraint firstItem="Iiz-W1-oNW" firstAttribute="leading" secondItem="nk9-Un-LVP" secondAttribute="leading" constant="12" id="6Ac-bF-DKJ"/>
|
||||
<constraint firstAttribute="trailing" secondItem="xCi-hD-FBs" secondAttribute="trailing" constant="12" id="PIj-uS-7Wf"/>
|
||||
<constraint firstItem="Iiz-W1-oNW" firstAttribute="centerY" secondItem="nk9-Un-LVP" secondAttribute="centerY" constant="-3" id="WGS-jR-0O2"/>
|
||||
<constraint firstAttribute="height" constant="68" id="eS5-qj-HTc"/>
|
||||
<constraint firstItem="xCi-hD-FBs" firstAttribute="centerY" secondItem="nk9-Un-LVP" secondAttribute="centerY" constant="-4" id="orn-eU-ogl"/>
|
||||
<constraint firstItem="K0z-Tt-rHv" firstAttribute="centerX" secondItem="nk9-Un-LVP" secondAttribute="centerX" id="qPd-Nd-Opc"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="Zc0-eq-2kY">
|
||||
<rect key="frame" x="167" y="364" width="80" height="168"/>
|
||||
<rect key="frame" x="167" y="311.66666666666669" width="80" height="117"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="FNb-tG-f7m">
|
||||
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
|
||||
|
@ -288,22 +329,8 @@
|
|||
<constraint firstAttribute="width" constant="80" id="wqp-O7-7Yc"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="scaleToFill" text="Name" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="IW8-8P-mS3">
|
||||
<rect key="frame" x="17" y="88" width="46.5" height="20.5"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCCallerNameLabel"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="scaleToFill" text="Duration" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="12" translatesAutoresizingMaskIntoConstraints="NO" id="29y-MK-OWH">
|
||||
<rect key="frame" x="15" y="116.5" width="50" height="14.5"/>
|
||||
<accessibility key="accessibilityConfiguration" identifier="CallVCCallStatusLabel"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="12"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="OaQ-Ki-dbe">
|
||||
<rect key="frame" x="13" y="139" width="54" height="29"/>
|
||||
<rect key="frame" x="13" y="88" width="54" height="29"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
|
||||
<color key="tintColor" systemColor="systemGreenColor"/>
|
||||
<state key="normal" title="Resume"/>
|
||||
|
@ -311,6 +338,15 @@
|
|||
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="pJG-7Q-UTa"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wqv-Uf-iNe" userLabel="Transfer">
|
||||
<rect key="frame" x="11.5" y="176" width="57" height="29"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
|
||||
<color key="tintColor" systemColor="systemGreenColor"/>
|
||||
<state key="normal" title="Transfer"/>
|
||||
<connections>
|
||||
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="KUu-CC-dnC"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
|
@ -321,7 +357,7 @@
|
|||
<constraint firstItem="nk9-Un-LVP" firstAttribute="leading" secondItem="JAR-tn-sGN" secondAttribute="leading" id="9wq-Y2-kZa"/>
|
||||
<constraint firstItem="sOu-ER-kOe" firstAttribute="leading" secondItem="JAR-tn-sGN" secondAttribute="leading" id="INI-a4-QCn"/>
|
||||
<constraint firstAttribute="trailing" secondItem="sOu-ER-kOe" secondAttribute="trailing" id="LEA-qC-Gko"/>
|
||||
<constraint firstItem="Zc0-eq-2kY" firstAttribute="centerY" secondItem="JAR-tn-sGN" secondAttribute="centerY" id="ju3-Ix-AGc"/>
|
||||
<constraint firstItem="Zc0-eq-2kY" firstAttribute="centerY" secondItem="JAR-tn-sGN" secondAttribute="centerY" constant="-78" id="ju3-Ix-AGc"/>
|
||||
<constraint firstAttribute="trailing" secondItem="nk9-Un-LVP" secondAttribute="trailing" id="yES-yI-i3m"/>
|
||||
</constraints>
|
||||
</view>
|
||||
|
@ -425,7 +461,7 @@
|
|||
<constraint firstItem="6gQ-zo-2Zw" firstAttribute="leading" secondItem="r1a-fi-tZ0" secondAttribute="leading" priority="750" constant="20" id="Qvg-FG-sBr"/>
|
||||
<constraint firstItem="VWv-s0-46r" firstAttribute="leading" secondItem="r1a-fi-tZ0" secondAttribute="leading" id="SNc-WF-jXg"/>
|
||||
<constraint firstItem="4TX-46-pAi" firstAttribute="top" secondItem="r1a-fi-tZ0" secondAttribute="top" constant="56" id="TQG-q5-eS7"/>
|
||||
<constraint firstItem="r1a-fi-tZ0" firstAttribute="bottom" secondItem="nk9-Un-LVP" secondAttribute="bottom" id="VpW-QU-xiw"/>
|
||||
<constraint firstItem="r1a-fi-tZ0" firstAttribute="bottom" secondItem="nk9-Un-LVP" secondAttribute="bottom" constant="20" id="VpW-QU-xiw"/>
|
||||
<constraint firstItem="r1a-fi-tZ0" firstAttribute="trailing" secondItem="JAR-tn-sGN" secondAttribute="trailing" id="Xcc-jT-zbd"/>
|
||||
<constraint firstItem="UGa-hI-iqx" firstAttribute="top" secondItem="r1a-fi-tZ0" secondAttribute="top" constant="120" id="YlJ-XZ-rCM"/>
|
||||
<constraint firstItem="Tjb-57-yB1" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="aKy-Ol-bhc"/>
|
||||
|
@ -433,7 +469,7 @@
|
|||
<constraint firstItem="JAR-tn-sGN" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="ck8-BX-Uyq"/>
|
||||
<constraint firstItem="UGa-hI-iqx" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="dzn-mw-OrJ"/>
|
||||
<constraint firstItem="sOu-ER-kOe" firstAttribute="top" secondItem="r1a-fi-tZ0" secondAttribute="top" id="iV6-LV-kcn"/>
|
||||
<constraint firstItem="r1a-fi-tZ0" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="6gQ-zo-2Zw" secondAttribute="bottom" constant="74" id="jCm-3K-6ah"/>
|
||||
<constraint firstItem="r1a-fi-tZ0" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="6gQ-zo-2Zw" secondAttribute="bottom" constant="90" id="jCm-3K-6ah"/>
|
||||
<constraint firstItem="Tjb-57-yB1" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="mW3-RA-hx2"/>
|
||||
<constraint firstItem="VWv-s0-46r" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="pzW-oL-TS7"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Tjb-57-yB1" secondAttribute="trailing" id="rXn-dm-69F"/>
|
||||
|
@ -445,14 +481,14 @@
|
|||
</objects>
|
||||
<resources>
|
||||
<image name="back_icon" width="14" height="23"/>
|
||||
<image name="call_audio_mute_off_icon" width="48" height="48"/>
|
||||
<image name="call_audio_mute_off_icon" width="68" height="68"/>
|
||||
<image name="call_go_to_chat_icon" width="24" height="24"/>
|
||||
<image name="call_hangup_icon" width="24.5" height="26"/>
|
||||
<image name="call_hangup_large" width="40" height="40"/>
|
||||
<image name="call_hangup_large" width="68" height="68"/>
|
||||
<image name="call_more_icon" width="24" height="24"/>
|
||||
<image name="call_paused_white_icon" width="20" height="20"/>
|
||||
<image name="call_pip_icon" width="24" height="24"/>
|
||||
<image name="call_video_mute_off_icon" width="48" height="48"/>
|
||||
<image name="camera_switch" width="24" height="24"/>
|
||||
<image name="call_video_mute_off_icon" width="68" height="68"/>
|
||||
<image name="camera_switch" width="13.333333015441895" height="13.333333015441895"/>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
|
|
|
@ -38,6 +38,9 @@ class DialpadConfiguration: NSObject {
|
|||
/// Option for a dial pad to enable editing on typed text or not.
|
||||
var editingEnabled: Bool
|
||||
|
||||
/// Option for a dial pad to play tones when digits tapped or not.
|
||||
var playTones: Bool
|
||||
|
||||
/// Default configuration object. All options are enabled by default.
|
||||
static let `default`: DialpadConfiguration = DialpadConfiguration()
|
||||
|
||||
|
@ -46,13 +49,15 @@ class DialpadConfiguration: NSObject {
|
|||
showsBackspaceButton: Bool = true,
|
||||
showsCallButton: Bool = true,
|
||||
formattingEnabled: Bool = true,
|
||||
editingEnabled: Bool = true) {
|
||||
editingEnabled: Bool = true,
|
||||
playTones: Bool = true) {
|
||||
self.showsTitle = showsTitle
|
||||
self.showsCloseButton = showsCloseButton
|
||||
self.showsBackspaceButton = showsBackspaceButton
|
||||
self.showsCallButton = showsCallButton
|
||||
self.formattingEnabled = formattingEnabled
|
||||
self.editingEnabled = editingEnabled
|
||||
self.playTones = playTones
|
||||
super.init()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="dBQ-CG-VDL">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="dBQ-CG-VDL">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="Stack View standard spacing" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
|
@ -171,7 +171,7 @@
|
|||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" spacing="32" translatesAutoresizingMaskIntoConstraints="NO" id="dvZ-XT-zLP">
|
||||
<rect key="frame" x="53.5" y="299" width="268" height="93.5"/>
|
||||
<subviews>
|
||||
<button opaque="NO" tag="-99" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hh8-U6-dJT" customClass="DialpadButton" customModule="Riot" customModuleProvider="target">
|
||||
<button opaque="NO" tag="-1" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hh8-U6-dJT" customClass="DialpadButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="12.5" width="68" height="68"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="68" id="4mU-dy-vEa"/>
|
||||
|
@ -196,7 +196,7 @@
|
|||
<action selector="digitButtonAction:" destination="dBQ-CG-VDL" eventType="touchUpInside" id="Irt-qu-feP"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" tag="-1" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Izh-vM-Fao" customClass="DialpadButton" customModule="Riot" customModuleProvider="target">
|
||||
<button opaque="NO" tag="-2" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Izh-vM-Fao" customClass="DialpadButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="200" y="12.5" width="68" height="68"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="68" id="4J4-8u-YKL"/>
|
||||
|
@ -213,18 +213,13 @@
|
|||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" spacing="32" translatesAutoresizingMaskIntoConstraints="NO" id="xzj-PF-Bxu">
|
||||
<rect key="frame" x="53.5" y="398.5" width="268" height="93.5"/>
|
||||
<subviews>
|
||||
<button opaque="NO" tag="-99" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SVW-fH-Ey6" customClass="DialpadActionButton" customModule="Riot" customModuleProvider="target">
|
||||
<button opaque="NO" userInteractionEnabled="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SVW-fH-Ey6">
|
||||
<rect key="frame" x="0.0" y="12.5" width="68" height="68"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="68" id="L2l-Qz-R8A"/>
|
||||
<constraint firstAttribute="width" constant="68" id="nEw-2X-e3a"/>
|
||||
</constraints>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="contentEdgeInsets" minX="0.0" minY="0.0" maxX="4" maxY="0.0"/>
|
||||
<state key="normal" image="call_dialpad_backspace_icon"/>
|
||||
<connections>
|
||||
<action selector="backspaceButtonAction:" destination="dBQ-CG-VDL" eventType="touchUpInside" id="3MX-35-8Jg"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="B17-BJ-SiW" customClass="DialpadActionButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="100" y="12.5" width="68" height="68"/>
|
||||
|
@ -238,13 +233,19 @@
|
|||
<action selector="callButtonAction:" destination="dBQ-CG-VDL" eventType="touchUpInside" id="TOV-1D-XQj"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" userInteractionEnabled="NO" tag="-1" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Xig-ln-gBC">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Xig-ln-gBC" customClass="DialpadActionButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="200" y="12.5" width="68" height="68"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="68" id="WLJ-xL-89j"/>
|
||||
<constraint firstAttribute="width" constant="68" id="afw-C4-e8i"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="28"/>
|
||||
<color key="tintColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="contentEdgeInsets" minX="0.0" minY="0.0" maxX="4" maxY="0.0"/>
|
||||
<state key="normal" image="call_dialpad_backspace_icon"/>
|
||||
<connections>
|
||||
<action selector="backspaceButtonAction:" destination="dBQ-CG-VDL" eventType="touchUpInside" id="ONz-mt-4D3"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
|
@ -285,15 +286,27 @@
|
|||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="backspaceButton" destination="SVW-fH-Ey6" id="s2W-oS-a90"/>
|
||||
<outlet property="backspaceButton" destination="Xig-ln-gBC" id="kKQ-7z-fVM"/>
|
||||
<outlet property="callButton" destination="B17-BJ-SiW" id="rmo-gK-pkv"/>
|
||||
<outlet property="closeButton" destination="6Yp-ue-lX3" id="xc1-ue-iUo"/>
|
||||
<outlet property="digitsStackView" destination="fnQ-jm-HT2" id="ltK-Bg-hwY"/>
|
||||
<outlet property="lineView" destination="iak-30-PMd" id="T6L-kl-Dsy"/>
|
||||
<outlet property="phoneNumberTextField" destination="iWR-Bv-qzs" id="ezn-FP-ihl"/>
|
||||
<outlet property="phoneNumberTextFieldTopConstraint" destination="nkS-49-CiR" id="Anh-q0-26b"/>
|
||||
<outlet property="spaceButton" destination="Xig-ln-gBC" id="tJi-AD-WOu"/>
|
||||
<outlet property="spaceButton" destination="SVW-fH-Ey6" id="WJd-w7-YxB"/>
|
||||
<outlet property="titleLabel" destination="ObS-Bw-Z12" id="0ZH-xI-zhw"/>
|
||||
<outletCollection property="digitButtons" destination="Dum-9Z-dKh" collectionClass="NSMutableArray" id="mnM-oA-G7N"/>
|
||||
<outletCollection property="digitButtons" destination="TIL-aL-5ij" collectionClass="NSMutableArray" id="4Ge-8g-ooV"/>
|
||||
<outletCollection property="digitButtons" destination="Yrk-zC-XL5" collectionClass="NSMutableArray" id="123-Qp-rzG"/>
|
||||
<outletCollection property="digitButtons" destination="JfX-9z-x4y" collectionClass="NSMutableArray" id="cPo-i1-bbI"/>
|
||||
<outletCollection property="digitButtons" destination="hI1-sM-NT5" collectionClass="NSMutableArray" id="b8d-5A-i6e"/>
|
||||
<outletCollection property="digitButtons" destination="cMg-az-Zgx" collectionClass="NSMutableArray" id="J0I-Qb-Zj6"/>
|
||||
<outletCollection property="digitButtons" destination="EF6-7Y-DhC" collectionClass="NSMutableArray" id="r8I-vD-jSM"/>
|
||||
<outletCollection property="digitButtons" destination="4Od-gu-C4x" collectionClass="NSMutableArray" id="XFt-db-l5O"/>
|
||||
<outletCollection property="digitButtons" destination="Rgd-Tt-xlI" collectionClass="NSMutableArray" id="aGQ-Ef-eBC"/>
|
||||
<outletCollection property="digitButtons" destination="hh8-U6-dJT" collectionClass="NSMutableArray" id="EaB-Ug-qsx"/>
|
||||
<outletCollection property="digitButtons" destination="4Rc-ls-1wr" collectionClass="NSMutableArray" id="cUp-yg-U48"/>
|
||||
<outletCollection property="digitButtons" destination="Izh-vM-Fao" collectionClass="NSMutableArray" id="Gfp-Fl-LMb"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="bLY-II-iJ3" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
|
|
|
@ -60,6 +60,7 @@ class DialpadViewController: UIViewController {
|
|||
}
|
||||
@IBOutlet private weak var lineView: UIView!
|
||||
@IBOutlet private weak var digitsStackView: UIStackView!
|
||||
@IBOutlet private var digitButtons: [DialpadButton]!
|
||||
@IBOutlet private weak var backspaceButton: DialpadActionButton! {
|
||||
didSet {
|
||||
backspaceButton.type = .backspace
|
||||
|
@ -83,6 +84,20 @@ class DialpadViewController: UIViewController {
|
|||
private enum Constants {
|
||||
static let sizeOniPad: CGSize = CGSize(width: 375, height: 667)
|
||||
static let additionalTopInset: CGFloat = 20
|
||||
static let digitButtonViewDatas: [Int: DialpadButton.ViewData] = [
|
||||
-2: .init(title: "#", tone: 1211),
|
||||
-1: .init(title: "*", tone: 1210),
|
||||
0: .init(title: "0", tone: 1200, subtitle: "+"),
|
||||
1: .init(title: "1", tone: 1201, showsSubtitleSpace: true),
|
||||
2: .init(title: "2", tone: 1202, subtitle: "ABC"),
|
||||
3: .init(title: "3", tone: 1203, subtitle: "DEF"),
|
||||
4: .init(title: "4", tone: 1204, subtitle: "GHI"),
|
||||
5: .init(title: "5", tone: 1205, subtitle: "JKL"),
|
||||
6: .init(title: "6", tone: 1206, subtitle: "MNO"),
|
||||
7: .init(title: "7", tone: 1207, subtitle: "PQRS"),
|
||||
8: .init(title: "8", tone: 1208, subtitle: "TUV"),
|
||||
9: .init(title: "9", tone: 1209, subtitle: "WXYZ")
|
||||
]
|
||||
}
|
||||
|
||||
private var wasCursorAtTheEnd: Bool = true
|
||||
|
@ -135,6 +150,12 @@ class DialpadViewController: UIViewController {
|
|||
if UIDevice.current.isPhone {
|
||||
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
|
||||
}
|
||||
|
||||
for button in digitButtons {
|
||||
if let viewData = Constants.digitButtonViewDatas[button.tag] {
|
||||
button.render(withViewData: viewData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
|
@ -212,10 +233,15 @@ class DialpadViewController: UIViewController {
|
|||
theme.applyStyle(onNavigationBar: navigationBar)
|
||||
}
|
||||
|
||||
titleLabel.textColor = theme.noticeSecondaryColor
|
||||
if theme.identifier == ThemeIdentifier.light.rawValue {
|
||||
titleLabel.textColor = theme.noticeSecondaryColor
|
||||
closeButton.setBackgroundImage(Asset.Images.closeButton.image.vc_tintedImage(usingColor: theme.tabBarUnselectedItemTintColor), for: .normal)
|
||||
} else {
|
||||
titleLabel.textColor = theme.baseTextSecondaryColor
|
||||
closeButton.setBackgroundImage(Asset.Images.closeButton.image.vc_tintedImage(usingColor: theme.baseTextSecondaryColor), for: .normal)
|
||||
}
|
||||
phoneNumberTextField.textColor = theme.textPrimaryColor
|
||||
lineView.backgroundColor = theme.lineBreakColor
|
||||
closeButton.setBackgroundImage(Asset.Images.closeButton.image.vc_tintedImage(usingColor: theme.tabBarUnselectedItemTintColor), for: .normal)
|
||||
|
||||
updateThemesOfAllButtons(in: digitsStackView, with: theme)
|
||||
}
|
||||
|
@ -253,12 +279,19 @@ class DialpadViewController: UIViewController {
|
|||
}
|
||||
|
||||
@IBAction private func digitButtonAction(_ sender: DialpadButton) {
|
||||
let digit = sender.title(for: .normal) ?? ""
|
||||
guard let digitViewData = Constants.digitButtonViewDatas[sender.tag] else {
|
||||
return
|
||||
}
|
||||
let digit = digitViewData.title
|
||||
|
||||
defer {
|
||||
delegate?.dialpadViewControllerDidTapDigit?(self, digit: digit)
|
||||
}
|
||||
|
||||
if configuration.playTones {
|
||||
AudioServicesPlaySystemSound(digitViewData.tone)
|
||||
}
|
||||
|
||||
if !configuration.editingEnabled {
|
||||
phoneNumber += digit
|
||||
return
|
||||
|
|
|
@ -30,9 +30,11 @@ class DialpadActionButton: DialpadButton {
|
|||
override func update(theme: Theme) {
|
||||
switch type {
|
||||
case .backspace:
|
||||
backgroundColor = theme.warningColor
|
||||
backgroundColor = .clear
|
||||
tintColor = theme.colors.tertiaryContent
|
||||
case .call:
|
||||
backgroundColor = theme.tintColor
|
||||
backgroundColor = theme.colors.accent
|
||||
tintColor = .white
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,27 @@ import UIKit
|
|||
/// Digit button class for Dialpad screen
|
||||
class DialpadButton: UIButton {
|
||||
|
||||
struct ViewData {
|
||||
let title: String
|
||||
let tone: SystemSoundID
|
||||
let subtitle: String?
|
||||
let showsSubtitleSpace: Bool
|
||||
|
||||
init(title: String, tone: SystemSoundID, subtitle: String? = nil, showsSubtitleSpace: Bool = false) {
|
||||
self.title = title
|
||||
self.tone = tone
|
||||
self.subtitle = subtitle
|
||||
self.showsSubtitleSpace = showsSubtitleSpace
|
||||
}
|
||||
}
|
||||
|
||||
private var viewData: ViewData?
|
||||
private var theme: Theme = ThemeService.shared().theme
|
||||
|
||||
private enum Constants {
|
||||
static let size: CGSize = CGSize(width: 68, height: 68)
|
||||
static let titleFont: UIFont = .boldSystemFont(ofSize: 32)
|
||||
static let subtitleFont: UIFont = .boldSystemFont(ofSize: 12)
|
||||
}
|
||||
|
||||
init() {
|
||||
|
@ -40,6 +59,31 @@ class DialpadButton: UIButton {
|
|||
private func setup() {
|
||||
clipsToBounds = true
|
||||
layer.cornerRadius = Constants.size.width/2
|
||||
vc_enableMultiLinesTitle()
|
||||
}
|
||||
|
||||
func render(withViewData viewData: ViewData) {
|
||||
self.viewData = viewData
|
||||
|
||||
let totalAttributedString = NSMutableAttributedString(string: viewData.title,
|
||||
attributes: [
|
||||
.font: Constants.titleFont,
|
||||
.foregroundColor: theme.textPrimaryColor
|
||||
])
|
||||
|
||||
if let subtitle = viewData.subtitle {
|
||||
totalAttributedString.append(NSAttributedString(string: "\n" + subtitle, attributes: [
|
||||
.font: Constants.subtitleFont,
|
||||
.foregroundColor: theme.textPrimaryColor
|
||||
]))
|
||||
} else if viewData.showsSubtitleSpace {
|
||||
totalAttributedString.append(NSAttributedString(string: "\n ", attributes: [
|
||||
.font: Constants.subtitleFont,
|
||||
.foregroundColor: theme.textPrimaryColor
|
||||
]))
|
||||
}
|
||||
|
||||
setAttributedTitle(totalAttributedString, for: .normal)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -49,8 +93,14 @@ class DialpadButton: UIButton {
|
|||
extension DialpadButton: Themable {
|
||||
|
||||
func update(theme: Theme) {
|
||||
setTitleColor(theme.textPrimaryColor, for: .normal)
|
||||
self.theme = theme
|
||||
|
||||
backgroundColor = theme.headerBackgroundColor
|
||||
|
||||
// re-render view data if set
|
||||
if let viewData = self.viewData {
|
||||
render(withViewData: viewData)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,24 +18,13 @@ import Foundation
|
|||
import Reusable
|
||||
|
||||
@objc protocol CallBarDelegate: class {
|
||||
func callBarDidTapReturnButton(_ callBar: CallBar)
|
||||
func callBarDidTap(_ callBar: CallBar)
|
||||
}
|
||||
|
||||
@objcMembers
|
||||
class CallBar: UIView, NibLoadable {
|
||||
|
||||
@IBOutlet private weak var callIcon: UIImageView! {
|
||||
didSet {
|
||||
callIcon.image = Asset.Images.voiceCallHangonIcon.image.vc_tintedImage(usingColor: .white)
|
||||
}
|
||||
}
|
||||
|
||||
@IBOutlet private weak var titleLabel: UILabel!
|
||||
@IBOutlet private weak var returnLabel: UILabel! {
|
||||
didSet {
|
||||
returnLabel.text = VectorL10n.callbarReturn
|
||||
}
|
||||
}
|
||||
@IBOutlet private weak var returnButton: UIButton!
|
||||
|
||||
weak var delegate: CallBarDelegate?
|
||||
|
||||
|
@ -43,6 +32,13 @@ class CallBar: UIView, NibLoadable {
|
|||
return CallBar.loadFromNib()
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:)))
|
||||
addGestureRecognizer(tapRecognizer)
|
||||
}
|
||||
|
||||
var title: String? {
|
||||
get {
|
||||
return titleLabel.text
|
||||
|
@ -51,7 +47,8 @@ class CallBar: UIView, NibLoadable {
|
|||
}
|
||||
}
|
||||
|
||||
@IBAction private func returnButtonTapped(_ sender: UIButton) {
|
||||
delegate?.callBarDidTapReturnButton(self)
|
||||
@objc
|
||||
private func tapGestureRecognized(_ sender: UITapGestureRecognizer) {
|
||||
delegate?.callBarDidTap(self)
|
||||
}
|
||||
}
|
||||
|
|