mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Video call UI improvements
This commit is contained in:
parent
abb6e4a6bf
commit
2ead6ffb5d
@ -2293,7 +2293,9 @@ Unused sets are archived when you add more.";
|
||||
"Notification.CallIncoming" = "Incoming Call";
|
||||
"Notification.VideoCallIncoming" = "Incoming Video Call";
|
||||
"Notification.CallMissed" = "Missed Call";
|
||||
"Notification.VideoCallMissed" = "Missed Video Call";
|
||||
"Notification.CallCanceled" = "Cancelled Call";
|
||||
"Notification.VideoCallCanceled" = "Cancelled Video Call";
|
||||
"Notification.CallOutgoingShort" = "Outgoing";
|
||||
"Notification.CallIncomingShort" = "Incoming";
|
||||
"Notification.CallMissedShort" = "Missed";
|
||||
|
@ -201,9 +201,25 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
|
||||
if let discardReason = discardReason {
|
||||
switch discardReason {
|
||||
case .busy, .disconnect:
|
||||
messageText = strings.Notification_CallCanceled
|
||||
if isVideo {
|
||||
messageText = strings.Notification_VideoCallCanceled
|
||||
} else {
|
||||
messageText = strings.Notification_CallCanceled
|
||||
}
|
||||
case .missed:
|
||||
messageText = incoming ? strings.Notification_CallMissed : strings.Notification_CallCanceled
|
||||
if incoming {
|
||||
if isVideo {
|
||||
messageText = strings.Notification_VideoCallMissed
|
||||
} else {
|
||||
messageText = strings.Notification_CallMissed
|
||||
}
|
||||
} else {
|
||||
if isVideo {
|
||||
messageText = strings.Notification_VideoCallCanceled
|
||||
} else {
|
||||
messageText = strings.Notification_CallCanceled
|
||||
}
|
||||
}
|
||||
case .hangup:
|
||||
break
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NSString *randomCallsEmoji();
|
||||
NSString *stringForEmojiHashOfData(NSData *data, NSInteger count);
|
||||
|
||||
#endif /* CallsEmoji_h */
|
||||
|
@ -7,15 +7,23 @@ static int32_t positionExtractor(uint8_t *bytes, int32_t i, int32_t count) {
|
||||
return num % count;
|
||||
}
|
||||
|
||||
NSArray *emojisArray() {
|
||||
return @[ @"😉", @"😍", @"😛", @"😭", @"😱", @"😡", @"😎", @"😴", @"😵", @"😈", @"😬", @"😇", @"😏", @"👮", @"👷", @"💂", @"👶", @"👨", @"👩", @"👴", @"👵", @"😻", @"😽", @"🙀", @"👺", @"🙈", @"🙉", @"🙊", @"💀", @"👽", @"💩", @"🔥", @"💥", @"💤", @"👂", @"👀", @"👃", @"👅", @"👄", @"👍", @"👎", @"👌", @"👊", @"✌️", @"✋️", @"👐", @"👆", @"👇", @"👉", @"👈", @"🙏", @"👏", @"💪", @"🚶", @"🏃", @"💃", @"👫", @"👪", @"👬", @"👭", @"💅", @"🎩", @"👑", @"👒", @"👟", @"👞", @"👠", @"👕", @"👗", @"👖", @"👙", @"👜", @"👓", @"🎀", @"💄", @"💛", @"💙", @"💜", @"💚", @"💍", @"💎", @"🐶", @"🐺", @"🐱", @"🐭", @"🐹", @"🐰", @"🐸", @"🐯", @"🐨", @"🐻", @"🐷", @"🐮", @"🐗", @"🐴", @"🐑", @"🐘", @"🐼", @"🐧", @"🐥", @"🐔", @"🐍", @"🐢", @"🐛", @"🐝", @"🐜", @"🐞", @"🐌", @"🐙", @"🐚", @"🐟", @"🐬", @"🐋", @"🐐", @"🐊", @"🐫", @"🍀", @"🌹", @"🌻", @"🍁", @"🌾", @"🍄", @"🌵", @"🌴", @"🌳", @"🌞", @"🌚", @"🌙", @"🌎", @"🌋", @"⚡️", @"☔️", @"❄️", @"⛄️", @"🌀", @"🌈", @"🌊", @"🎓", @"🎆", @"🎃", @"👻", @"🎅", @"🎄", @"🎁", @"🎈", @"🔮", @"🎥", @"📷", @"💿", @"💻", @"☎️", @"📡", @"📺", @"📻", @"🔉", @"🔔", @"⏳", @"⏰", @"⌚️", @"🔒", @"🔑", @"🔎", @"💡", @"🔦", @"🔌", @"🔋", @"🚿", @"🚽", @"🔧", @"🔨", @"🚪", @"🚬", @"💣", @"🔫", @"🔪", @"💊", @"💉", @"💰", @"💵", @"💳", @"✉️", @"📫", @"📦", @"📅", @"📁", @"✂️", @"📌", @"📎", @"✒️", @"✏️", @"📐", @"📚", @"🔬", @"🔭", @"🎨", @"🎬", @"🎤", @"🎧", @"🎵", @"🎹", @"🎻", @"🎺", @"🎸", @"👾", @"🎮", @"🃏", @"🎲", @"🎯", @"🏈", @"🏀", @"⚽️", @"⚾️", @"🎾", @"🎱", @"🏉", @"🎳", @"🏁", @"🏇", @"🏆", @"🏊", @"🏄", @"☕️", @"🍼", @"🍺", @"🍷", @"🍴", @"🍕", @"🍔", @"🍟", @"🍗", @"🍱", @"🍚", @"🍜", @"🍡", @"🍳", @"🍞", @"🍩", @"🍦", @"🎂", @"🍰", @"🍪", @"🍫", @"🍭", @"🍯", @"🍎", @"🍏", @"🍊", @"🍋", @"🍒", @"🍇", @"🍉", @"🍓", @"🍑", @"🍌", @"🍐", @"🍍", @"🍆", @"🍅", @"🌽", @"🏡", @"🏥", @"🏦", @"⛪️", @"🏰", @"⛺️", @"🏭", @"🗻", @"🗽", @"🎠", @"🎡", @"⛲️", @"🎢", @"🚢", @"🚤", @"⚓️", @"🚀", @"✈️", @"🚁", @"🚂", @"🚋", @"🚎", @"🚌", @"🚙", @"🚗", @"🚕", @"🚛", @"🚨", @"🚔", @"🚒", @"🚑", @"🚲", @"🚠", @"🚜", @"🚦", @"⚠️", @"🚧", @"⛽️", @"🎰", @"🗿", @"🎪", @"🎭", @"🇯🇵", @"🇰🇷", @"🇩🇪", @"🇨🇳", @"🇺🇸", @"🇫🇷", @"🇪🇸", @"🇮🇹", @"🇷🇺", @"🇬🇧", @"1️⃣", @"2️⃣", @"3️⃣", @"4️⃣", @"5️⃣", @"6️⃣", @"7️⃣", @"8️⃣", @"9️⃣", @"0️⃣", @"🔟", @"❗️", @"❓", @"♥️", @"♦️", @"💯", @"🔗", @"🔱", @"🔴", @"🔵", @"🔶", @"🔷" ];
|
||||
}
|
||||
|
||||
NSString *randomCallsEmoji() {
|
||||
NSArray *emojis = emojisArray();
|
||||
return emojis[arc4random() % emojis.count];
|
||||
}
|
||||
|
||||
NSString *stringForEmojiHashOfData(NSData *data, NSInteger count) {
|
||||
if (data.length != 32)
|
||||
return @"";
|
||||
|
||||
NSArray *emojis = @[ @"😉", @"😍", @"😛", @"😭", @"😱", @"😡", @"😎", @"😴", @"😵", @"😈", @"😬", @"😇", @"😏", @"👮", @"👷", @"💂", @"👶", @"👨", @"👩", @"👴", @"👵", @"😻", @"😽", @"🙀", @"👺", @"🙈", @"🙉", @"🙊", @"💀", @"👽", @"💩", @"🔥", @"💥", @"💤", @"👂", @"👀", @"👃", @"👅", @"👄", @"👍", @"👎", @"👌", @"👊", @"✌️", @"✋️", @"👐", @"👆", @"👇", @"👉", @"👈", @"🙏", @"👏", @"💪", @"🚶", @"🏃", @"💃", @"👫", @"👪", @"👬", @"👭", @"💅", @"🎩", @"👑", @"👒", @"👟", @"👞", @"👠", @"👕", @"👗", @"👖", @"👙", @"👜", @"👓", @"🎀", @"💄", @"💛", @"💙", @"💜", @"💚", @"💍", @"💎", @"🐶", @"🐺", @"🐱", @"🐭", @"🐹", @"🐰", @"🐸", @"🐯", @"🐨", @"🐻", @"🐷", @"🐮", @"🐗", @"🐴", @"🐑", @"🐘", @"🐼", @"🐧", @"🐥", @"🐔", @"🐍", @"🐢", @"🐛", @"🐝", @"🐜", @"🐞", @"🐌", @"🐙", @"🐚", @"🐟", @"🐬", @"🐋", @"🐐", @"🐊", @"🐫", @"🍀", @"🌹", @"🌻", @"🍁", @"🌾", @"🍄", @"🌵", @"🌴", @"🌳", @"🌞", @"🌚", @"🌙", @"🌎", @"🌋", @"⚡️", @"☔️", @"❄️", @"⛄️", @"🌀", @"🌈", @"🌊", @"🎓", @"🎆", @"🎃", @"👻", @"🎅", @"🎄", @"🎁", @"🎈", @"🔮", @"🎥", @"📷", @"💿", @"💻", @"☎️", @"📡", @"📺", @"📻", @"🔉", @"🔔", @"⏳", @"⏰", @"⌚️", @"🔒", @"🔑", @"🔎", @"💡", @"🔦", @"🔌", @"🔋", @"🚿", @"🚽", @"🔧", @"🔨", @"🚪", @"🚬", @"💣", @"🔫", @"🔪", @"💊", @"💉", @"💰", @"💵", @"💳", @"✉️", @"📫", @"📦", @"📅", @"📁", @"✂️", @"📌", @"📎", @"✒️", @"✏️", @"📐", @"📚", @"🔬", @"🔭", @"🎨", @"🎬", @"🎤", @"🎧", @"🎵", @"🎹", @"🎻", @"🎺", @"🎸", @"👾", @"🎮", @"🃏", @"🎲", @"🎯", @"🏈", @"🏀", @"⚽️", @"⚾️", @"🎾", @"🎱", @"🏉", @"🎳", @"🏁", @"🏇", @"🏆", @"🏊", @"🏄", @"☕️", @"🍼", @"🍺", @"🍷", @"🍴", @"🍕", @"🍔", @"🍟", @"🍗", @"🍱", @"🍚", @"🍜", @"🍡", @"🍳", @"🍞", @"🍩", @"🍦", @"🎂", @"🍰", @"🍪", @"🍫", @"🍭", @"🍯", @"🍎", @"🍏", @"🍊", @"🍋", @"🍒", @"🍇", @"🍉", @"🍓", @"🍑", @"🍌", @"🍐", @"🍍", @"🍆", @"🍅", @"🌽", @"🏡", @"🏥", @"🏦", @"⛪️", @"🏰", @"⛺️", @"🏭", @"🗻", @"🗽", @"🎠", @"🎡", @"⛲️", @"🎢", @"🚢", @"🚤", @"⚓️", @"🚀", @"✈️", @"🚁", @"🚂", @"🚋", @"🚎", @"🚌", @"🚙", @"🚗", @"🚕", @"🚛", @"🚨", @"🚔", @"🚒", @"🚑", @"🚲", @"🚠", @"🚜", @"🚦", @"⚠️", @"🚧", @"⛽️", @"🎰", @"🗿", @"🎪", @"🎭", @"🇯🇵", @"🇰🇷", @"🇩🇪", @"🇨🇳", @"🇺🇸", @"🇫🇷", @"🇪🇸", @"🇮🇹", @"🇷🇺", @"🇬🇧", @"1️⃣", @"2️⃣", @"3️⃣", @"4️⃣", @"5️⃣", @"6️⃣", @"7️⃣", @"8️⃣", @"9️⃣", @"0️⃣", @"🔟", @"❗️", @"❓", @"♥️", @"♦️", @"💯", @"🔗", @"🔱", @"🔴", @"🔵", @"🔶", @"🔷" ];
|
||||
|
||||
uint8_t bytes[32];
|
||||
[data getBytes:bytes length:32];
|
||||
|
||||
NSArray *emojis = emojisArray();
|
||||
NSString *result = @"";
|
||||
for (int32_t i = 0; i < count; i++)
|
||||
{
|
||||
|
@ -119,6 +119,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
||||
self.overlayHighlightNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize))
|
||||
|
||||
if self.currentContent != content {
|
||||
let previousContent = self.currentContent
|
||||
self.currentContent = content
|
||||
|
||||
if content.hasProgress {
|
||||
@ -176,8 +177,8 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
||||
context.setBlendMode(.copy)
|
||||
}
|
||||
}
|
||||
let smallButtonSize: CGFloat = 60.0
|
||||
imageScale = self.largeButtonSize / smallButtonSize
|
||||
// let smallButtonSize: CGFloat = 60.0
|
||||
// imageScale = self.largeButtonSize / smallButtonSize
|
||||
case let .color(color):
|
||||
switch color {
|
||||
case .red:
|
||||
@ -233,7 +234,21 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
||||
self.contentBackgroundNode.image = contentBackgroundImage
|
||||
}
|
||||
|
||||
if transition.isAnimated, let contentImage = contentImage, let previousContent = self.contentNode.image {
|
||||
if transition.isAnimated, let previousContent = previousContent, previousContent.image == .accept && content.image == .end {
|
||||
let rotation = CGFloat.pi / 4.0 * 3.0
|
||||
|
||||
if let snapshotView = self.contentNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = self.contentNode.view.frame
|
||||
self.contentContainer.view.addSubview(snapshotView)
|
||||
|
||||
snapshotView.layer.animateRotation(from: 0.0, to: rotation, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
self.contentNode.image = contentImage
|
||||
self.contentNode.layer.animateRotation(from: -rotation, to: 0.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
} else if transition.isAnimated, let contentImage = contentImage, let previousContent = self.contentNode.image {
|
||||
self.contentNode.image = contentImage
|
||||
self.contentNode.layer.animate(from: previousContent.cgImage!, to: contentImage.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
|
||||
} else {
|
||||
|
@ -31,7 +31,8 @@ enum CallControllerButtonsMode: Equatable {
|
||||
private enum ButtonDescription: Equatable {
|
||||
enum Key: Hashable {
|
||||
case accept
|
||||
case end
|
||||
case acceptOrEnd
|
||||
case decline
|
||||
case enableCamera
|
||||
case switchCamera
|
||||
case soundOutput
|
||||
@ -60,9 +61,13 @@ private enum ButtonDescription: Equatable {
|
||||
var key: Key {
|
||||
switch self {
|
||||
case .accept:
|
||||
return .accept
|
||||
case .end:
|
||||
return .end
|
||||
return .acceptOrEnd
|
||||
case let .end(type):
|
||||
if type == .decline {
|
||||
return .decline
|
||||
} else {
|
||||
return .acceptOrEnd
|
||||
}
|
||||
case .enableCamera:
|
||||
return .enableCamera
|
||||
case .switchCamera:
|
||||
@ -85,9 +90,9 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
var isMuted = false
|
||||
var isCameraPaused = false
|
||||
|
||||
var accept: (() -> Void)?
|
||||
var acceptOrEnd: (() -> Void)?
|
||||
var decline: (() -> Void)?
|
||||
var mute: (() -> Void)?
|
||||
var end: (() -> Void)?
|
||||
var speaker: (() -> Void)?
|
||||
var toggleVideo: (() -> Void)?
|
||||
var rotateCamera: (() -> Void)?
|
||||
@ -230,14 +235,14 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
topButtons.append(.soundOutput(soundOutput))
|
||||
}
|
||||
|
||||
let topButtonsContentWidth = CGFloat(topButtons.count) * smallButtonSize
|
||||
let topButtonsContentWidth = CGFloat(topButtons.count) * largeButtonSize
|
||||
let topButtonsAvailableSpacingWidth = width - topButtonsContentWidth - minSmallButtonSideInset * 2.0
|
||||
let topButtonsSpacing = min(maxSmallButtonSpacing, topButtonsAvailableSpacingWidth / CGFloat(topButtons.count - 1))
|
||||
let topButtonsWidth = CGFloat(topButtons.count) * smallButtonSize + CGFloat(topButtons.count - 1) * topButtonsSpacing
|
||||
let topButtonsWidth = CGFloat(topButtons.count) * largeButtonSize + CGFloat(topButtons.count - 1) * topButtonsSpacing
|
||||
var topButtonsLeftOffset = floor((width - topButtonsWidth) / 2.0)
|
||||
for button in topButtons {
|
||||
buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: topButtonsLeftOffset, y: 0.0), size: CGSize(width: smallButtonSize, height: smallButtonSize))))
|
||||
topButtonsLeftOffset += smallButtonSize + topButtonsSpacing
|
||||
buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: topButtonsLeftOffset, y: 0.0), size: CGSize(width: largeButtonSize, height: largeButtonSize))))
|
||||
topButtonsLeftOffset += largeButtonSize + topButtonsSpacing
|
||||
}
|
||||
|
||||
if case .incomingRinging = mappedState {
|
||||
@ -253,11 +258,11 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
let bottomButtonsWidth = CGFloat(bottomButtons.count) * largeButtonSize + CGFloat(bottomButtons.count - 1) * bottomButtonsSpacing
|
||||
var bottomButtonsLeftOffset = floor((width - bottomButtonsWidth) / 2.0)
|
||||
for button in bottomButtons {
|
||||
buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: bottomButtonsLeftOffset, y: smallButtonSize + topBottomSpacing), size: CGSize(width: largeButtonSize, height: largeButtonSize))))
|
||||
buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: bottomButtonsLeftOffset, y: largeButtonSize + topBottomSpacing), size: CGSize(width: largeButtonSize, height: largeButtonSize))))
|
||||
bottomButtonsLeftOffset += largeButtonSize + bottomButtonsSpacing
|
||||
}
|
||||
|
||||
height = smallButtonSize + topBottomSpacing + largeButtonSize + max(bottomInset + 32.0, 46.0)
|
||||
height = largeButtonSize + topBottomSpacing + largeButtonSize + max(bottomInset + 32.0, 46.0)
|
||||
case .active:
|
||||
switch videoState {
|
||||
case .active, .incomingRequested, .outgoingRequested:
|
||||
@ -345,14 +350,14 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
topButtons.append(.mute(self.isMuted))
|
||||
topButtons.append(.soundOutput(soundOutput))
|
||||
|
||||
let topButtonsContentWidth = CGFloat(topButtons.count) * smallButtonSize
|
||||
let topButtonsContentWidth = CGFloat(topButtons.count) * largeButtonSize
|
||||
let topButtonsAvailableSpacingWidth = width - topButtonsContentWidth - minSmallButtonSideInset * 2.0
|
||||
let topButtonsSpacing = min(maxSmallButtonSpacing, topButtonsAvailableSpacingWidth / CGFloat(topButtons.count - 1))
|
||||
let topButtonsWidth = CGFloat(topButtons.count) * smallButtonSize + CGFloat(topButtons.count - 1) * topButtonsSpacing
|
||||
let topButtonsWidth = CGFloat(topButtons.count) * largeButtonSize + CGFloat(topButtons.count - 1) * topButtonsSpacing
|
||||
var topButtonsLeftOffset = floor((width - topButtonsWidth) / 2.0)
|
||||
for button in topButtons {
|
||||
buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: topButtonsLeftOffset, y: 0.0), size: CGSize(width: smallButtonSize, height: smallButtonSize))))
|
||||
topButtonsLeftOffset += smallButtonSize + topButtonsSpacing
|
||||
buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: topButtonsLeftOffset, y: 0.0), size: CGSize(width: largeButtonSize, height: largeButtonSize))))
|
||||
topButtonsLeftOffset += largeButtonSize + topButtonsSpacing
|
||||
}
|
||||
|
||||
bottomButtons.append(.end(.outgoing))
|
||||
@ -363,11 +368,11 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
let bottomButtonsWidth = CGFloat(bottomButtons.count) * largeButtonSize + CGFloat(bottomButtons.count - 1) * bottomButtonsSpacing
|
||||
var bottomButtonsLeftOffset = floor((width - bottomButtonsWidth) / 2.0)
|
||||
for button in bottomButtons {
|
||||
buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: bottomButtonsLeftOffset, y: smallButtonSize + topBottomSpacing), size: CGSize(width: largeButtonSize, height: largeButtonSize))))
|
||||
buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: bottomButtonsLeftOffset, y: largeButtonSize + topBottomSpacing), size: CGSize(width: largeButtonSize, height: largeButtonSize))))
|
||||
bottomButtonsLeftOffset += largeButtonSize + bottomButtonsSpacing
|
||||
}
|
||||
|
||||
height = smallButtonSize + topBottomSpacing + largeButtonSize + max(bottomInset + 32.0, 46.0)
|
||||
height = largeButtonSize + topBottomSpacing + largeButtonSize + max(bottomInset + 32.0, 46.0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -457,7 +462,7 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
buttonDelay = delayIncrement * 1.0
|
||||
case .switchCamera:
|
||||
buttonDelay = delayIncrement * 2.0
|
||||
case .end:
|
||||
case .acceptOrEnd:
|
||||
buttonDelay = delayIncrement * 3.0
|
||||
default:
|
||||
break
|
||||
@ -475,17 +480,11 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
if !validKeys.contains(key) {
|
||||
removedKeys.append(key)
|
||||
if animated {
|
||||
if case .accept = key {
|
||||
if let endButton = self.buttonNodes[.end] {
|
||||
transition.updateFrame(node: button, frame: endButton.frame)
|
||||
if let content = button.currentContent {
|
||||
button.update(size: endButton.frame.size, content: content, text: button.currentText, transition: transition)
|
||||
}
|
||||
transition.updateTransformScale(node: button, scale: 0.1)
|
||||
transition.updateAlpha(node: button, alpha: 0.0, completion: { [weak button] _ in
|
||||
button?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
if case .decline = key {
|
||||
transition.updateTransformScale(node: button, scale: 0.1)
|
||||
transition.updateAlpha(node: button, alpha: 0.0, completion: { [weak button] _ in
|
||||
button?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
transition.updateAlpha(node: button, alpha: 0.0, completion: { [weak button] _ in
|
||||
button?.removeFromSupernode()
|
||||
@ -508,9 +507,11 @@ final class CallControllerButtonsNode: ASDisplayNode {
|
||||
if button === listButton {
|
||||
switch key {
|
||||
case .accept:
|
||||
self.accept?()
|
||||
case .end:
|
||||
self.end?()
|
||||
self.acceptOrEnd?()
|
||||
case .acceptOrEnd:
|
||||
self.acceptOrEnd?()
|
||||
case .decline:
|
||||
self.decline?()
|
||||
case .enableCamera:
|
||||
self.toggleVideo?()
|
||||
case .switchCamera:
|
||||
|
127
submodules/TelegramCallsUI/Sources/CallControllerKeyButton.swift
Normal file
127
submodules/TelegramCallsUI/Sources/CallControllerKeyButton.swift
Normal file
@ -0,0 +1,127 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import CallsEmoji
|
||||
|
||||
private let labelFont = Font.regular(22.0)
|
||||
private let animationNodesCount = 3
|
||||
|
||||
private class EmojiSlotNode: ASDisplayNode {
|
||||
var emoji: String = "" {
|
||||
didSet {
|
||||
self.node.attributedText = NSAttributedString(string: emoji, font: labelFont, textColor: .black)
|
||||
let _ = self.node.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||
}
|
||||
}
|
||||
|
||||
private let maskNode: ASDisplayNode
|
||||
private let containerNode: ASDisplayNode
|
||||
private let node: ImmediateTextNode
|
||||
private let animationNodes: [ImmediateTextNode]
|
||||
|
||||
override init() {
|
||||
self.maskNode = ASDisplayNode()
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.node = ImmediateTextNode()
|
||||
self.animationNodes = (0 ..< animationNodesCount).map { _ in ImmediateTextNode() }
|
||||
|
||||
super.init()
|
||||
|
||||
let maskLayer = CAGradientLayer()
|
||||
maskLayer.colors = [UIColor.clear.cgColor, UIColor.white.cgColor, UIColor.white.cgColor, UIColor.clear.cgColor]
|
||||
maskLayer.locations = [0.0, 0.2, 0.8, 1.0]
|
||||
maskLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
|
||||
maskLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
|
||||
self.maskNode.layer.mask = maskLayer
|
||||
|
||||
self.addSubnode(self.maskNode)
|
||||
self.maskNode.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.node)
|
||||
self.animationNodes.forEach({ self.containerNode.addSubnode($0) })
|
||||
}
|
||||
|
||||
func animateIn(duration: Double) {
|
||||
for node in self.animationNodes {
|
||||
node.attributedText = NSAttributedString(string: randomCallsEmoji(), font: labelFont, textColor: .black)
|
||||
let _ = node.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||
}
|
||||
self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -self.containerNode.frame.height + self.bounds.height), to: CGPoint(), duration: duration, delay: 0.1, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
let maskInset: CGFloat = 4.0
|
||||
let maskFrame = self.bounds.insetBy(dx: 0.0, dy: -maskInset)
|
||||
self.maskNode.frame = maskFrame
|
||||
self.maskNode.layer.mask?.frame = CGRect(origin: CGPoint(), size: maskFrame.size)
|
||||
|
||||
let spacing: CGFloat = 2.0
|
||||
let containerSize = CGSize(width: self.bounds.width, height: self.bounds.height * CGFloat(animationNodesCount + 1) + spacing * CGFloat(animationNodesCount))
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: maskInset), size: containerSize)
|
||||
|
||||
self.node.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||
var offset: CGFloat = self.bounds.height + spacing
|
||||
for node in self.animationNodes {
|
||||
node.frame = CGRect(origin: CGPoint(x: 0.0, y: offset), size: self.bounds.size)
|
||||
offset += self.bounds.height + spacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class CallControllerKeyButton: HighlightableButtonNode {
|
||||
private let containerNode: ASDisplayNode
|
||||
private let nodes: [EmojiSlotNode]
|
||||
|
||||
var key: String = "" {
|
||||
didSet {
|
||||
var index = 0
|
||||
for emoji in self.key {
|
||||
guard index < 4 else {
|
||||
return
|
||||
}
|
||||
self.nodes[index].emoji = String(emoji)
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.nodes = (0 ..< 4).map { _ in EmojiSlotNode() }
|
||||
|
||||
super.init(pointerStyle: nil)
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
self.nodes.forEach({ self.containerNode.addSubnode($0) })
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.layoutIfNeeded()
|
||||
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
|
||||
var duration: Double = 0.75
|
||||
for node in self.nodes {
|
||||
node.animateIn(duration: duration)
|
||||
duration += 0.3
|
||||
}
|
||||
}
|
||||
|
||||
override func measure(_ constrainedSize: CGSize) -> CGSize {
|
||||
return CGSize(width: 114.0, height: 26.0)
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
self.containerNode.frame = self.bounds
|
||||
var index = 0
|
||||
let nodeSize = CGSize(width: 29.0, height: self.bounds.size.height)
|
||||
for node in self.nodes {
|
||||
node.frame = CGRect(origin: CGPoint(x: CGFloat(index) * nodeSize.width, y: 0.0), size: nodeSize)
|
||||
index += 1
|
||||
}
|
||||
self.nodes.forEach({ self.containerNode.addSubnode($0) })
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@ final class CallControllerKeyPreviewNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.keyTextNode.attributedText = NSAttributedString(string: keyText, attributes: [NSAttributedString.Key.font: Font.regular(58.0), NSAttributedString.Key.kern: 9.0 as NSNumber])
|
||||
self.keyTextNode.attributedText = NSAttributedString(string: keyText, attributes: [NSAttributedString.Key.font: Font.regular(58.0), NSAttributedString.Key.kern: 11.0 as NSNumber])
|
||||
|
||||
self.infoTextNode.attributedText = NSAttributedString(string: infoText, font: Font.regular(14.0), textColor: UIColor.white, paragraphAlignment: .center)
|
||||
|
||||
@ -53,7 +53,7 @@ final class CallControllerKeyPreviewNode: ASDisplayNode {
|
||||
let keyTextSize = self.keyTextNode.measure(CGSize(width: 300.0, height: 300.0))
|
||||
transition.updateFrame(node: self.keyTextNode, frame: CGRect(origin: CGPoint(x: floor((size.width - keyTextSize.width) / 2) + 6.0, y: floor((size.height - keyTextSize.height) / 2) - 50.0), size: keyTextSize))
|
||||
|
||||
let infoTextSize = self.infoTextNode.measure(CGSize(width: size.width - 20.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let infoTextSize = self.infoTextNode.measure(CGSize(width: size.width - 32.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
transition.updateFrame(node: self.infoTextNode, frame: CGRect(origin: CGPoint(x: floor((size.width - infoTextSize.width) / 2.0), y: floor((size.height - infoTextSize.height) / 2.0) + 30.0), size: infoTextSize))
|
||||
}
|
||||
|
||||
|
@ -285,7 +285,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
private var debugNode: CallDebugNode?
|
||||
|
||||
private var keyTextData: (Data, String)?
|
||||
private let keyButtonNode: HighlightableButtonNode
|
||||
private let keyButtonNode: CallControllerKeyButton
|
||||
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
private var disableActionsUntilTimestamp: Double = 0.0
|
||||
@ -366,7 +366,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
self.videoPausedNode.alpha = 0.0
|
||||
|
||||
self.buttonsNode = CallControllerButtonsNode(strings: self.presentationData.strings)
|
||||
self.keyButtonNode = HighlightableButtonNode()
|
||||
self.keyButtonNode = CallControllerKeyButton()
|
||||
|
||||
super.init()
|
||||
|
||||
@ -410,17 +410,18 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
self?.beginAudioOuputSelection?()
|
||||
}
|
||||
|
||||
self.buttonsNode.end = { [weak self] in
|
||||
self?.endCall?()
|
||||
}
|
||||
|
||||
self.buttonsNode.accept = { [weak self] in
|
||||
self.buttonsNode.acceptOrEnd = { [weak self] in
|
||||
guard let strongSelf = self, let callState = strongSelf.callState else {
|
||||
return
|
||||
}
|
||||
switch callState.state {
|
||||
case .active, .connecting, .reconnecting:
|
||||
strongSelf.call.acceptVideo()
|
||||
switch callState.videoState {
|
||||
case .incomingRequested:
|
||||
strongSelf.call.acceptVideo()
|
||||
default:
|
||||
self?.endCall?()
|
||||
}
|
||||
case .ringing:
|
||||
strongSelf.acceptCall?()
|
||||
default:
|
||||
@ -428,6 +429,10 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
}
|
||||
}
|
||||
|
||||
self.buttonsNode.decline = { [weak self] in
|
||||
self?.endCall?()
|
||||
}
|
||||
|
||||
self.buttonsNode.toggleVideo = { [weak self] in
|
||||
guard let strongSelf = self, let callState = strongSelf.callState else {
|
||||
return
|
||||
@ -745,12 +750,13 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
let text = stringForEmojiHashOfData(keyVisualHash, 4)!
|
||||
self.keyTextData = (keyVisualHash, text)
|
||||
|
||||
self.keyButtonNode.setAttributedTitle(NSAttributedString(string: text, attributes: [NSAttributedString.Key.font: Font.regular(22.0), NSAttributedString.Key.kern: 2.5 as NSNumber]), for: [])
|
||||
self.keyButtonNode.key = text
|
||||
|
||||
let keyTextSize = self.keyButtonNode.measure(CGSize(width: 200.0, height: 200.0))
|
||||
self.keyButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.keyButtonNode.frame = CGRect(origin: self.keyButtonNode.frame.origin, size: keyTextSize)
|
||||
|
||||
self.keyButtonNode.animateIn()
|
||||
|
||||
if let (layout, navigationBarHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
}
|
||||
@ -1520,6 +1526,9 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.debugNode != nil {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
if self.containerTransformationNode.frame.contains(point) {
|
||||
return self.containerTransformationNode.view.hitTest(self.view.convert(point, to: self.containerTransformationNode.view), with: event)
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ enum CallControllerStatusValue: Equatable {
|
||||
|
||||
final class CallControllerStatusNode: ASDisplayNode {
|
||||
private let titleNode: TextNode
|
||||
private let statusContainerNode: ASDisplayNode
|
||||
private let statusNode: TextNode
|
||||
private let statusMeasureNode: TextNode
|
||||
private let receptionNode: CallControllerReceptionNode
|
||||
@ -46,6 +47,21 @@ final class CallControllerStatusNode: ASDisplayNode {
|
||||
if self.status != oldValue {
|
||||
self.statusTimer?.invalidate()
|
||||
|
||||
if let snapshotView = self.statusContainerNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
snapshotView.frame = self.statusContainerNode.frame
|
||||
self.view.insertSubview(snapshotView, belowSubview: self.statusContainerNode.view)
|
||||
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
snapshotView.layer.animateScale(from: 1.0, to: 0.3, duration: 0.3, removeOnCompletion: false)
|
||||
snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: snapshotView.frame.height / 2.0), duration: 0.3, delay: 0.0, removeOnCompletion: false, additive: true)
|
||||
|
||||
self.statusContainerNode.layer.animateScale(from: 0.3, to: 1.0, duration: 0.3)
|
||||
self.statusContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.statusContainerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -snapshotView.frame.height / 2.0), to: CGPoint(), duration: 0.3, delay: 0.0, additive: true)
|
||||
}
|
||||
|
||||
if case .timer = self.status {
|
||||
self.statusTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
|
||||
if let strongSelf = self, let validLayoutWidth = strongSelf.validLayoutWidth {
|
||||
@ -90,6 +106,7 @@ final class CallControllerStatusNode: ASDisplayNode {
|
||||
|
||||
override init() {
|
||||
self.titleNode = TextNode()
|
||||
self.statusContainerNode = ASDisplayNode()
|
||||
self.statusNode = TextNode()
|
||||
self.statusNode.displaysAsynchronously = false
|
||||
self.statusMeasureNode = TextNode()
|
||||
@ -106,9 +123,10 @@ final class CallControllerStatusNode: ASDisplayNode {
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.statusNode)
|
||||
self.addSubnode(self.receptionNode)
|
||||
self.addSubnode(self.logoNode)
|
||||
self.addSubnode(self.statusContainerNode)
|
||||
self.statusContainerNode.addSubnode(self.statusNode)
|
||||
self.statusContainerNode.addSubnode(self.receptionNode)
|
||||
self.statusContainerNode.addSubnode(self.logoNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -168,12 +186,13 @@ final class CallControllerStatusNode: ASDisplayNode {
|
||||
let _ = statusMeasureApply()
|
||||
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: floor((constrainedWidth - titleLayout.size.width) / 2.0), y: 0.0), size: titleLayout.size)
|
||||
self.statusNode.frame = CGRect(origin: CGPoint(x: floor((constrainedWidth - statusMeasureLayout.size.width) / 2.0) + statusOffset, y: titleLayout.size.height + spacing), size: statusLayout.size)
|
||||
self.receptionNode.frame = CGRect(origin: CGPoint(x: self.statusNode.frame.minX - receptionNodeSize.width, y: titleLayout.size.height + spacing + 9.0), size: receptionNodeSize)
|
||||
self.statusContainerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: titleLayout.size.height + spacing), size: CGSize(width: constrainedWidth, height: statusLayout.size.height))
|
||||
self.statusNode.frame = CGRect(origin: CGPoint(x: floor((constrainedWidth - statusMeasureLayout.size.width) / 2.0) + statusOffset, y: 0.0), size: statusLayout.size)
|
||||
self.receptionNode.frame = CGRect(origin: CGPoint(x: self.statusNode.frame.minX - receptionNodeSize.width, y: 9.0), size: receptionNodeSize)
|
||||
self.logoNode.isHidden = !statusDisplayLogo
|
||||
if let image = self.logoNode.image, let firstLineRect = statusMeasureLayout.linesRects().first {
|
||||
let firstLineOffset = floor((statusMeasureLayout.size.width - firstLineRect.width) / 2.0)
|
||||
self.logoNode.frame = CGRect(origin: CGPoint(x: self.statusNode.frame.minX + firstLineOffset - image.size.width - 7.0, y: self.statusNode.frame.minY + 5.0), size: image.size)
|
||||
self.logoNode.frame = CGRect(origin: CGPoint(x: self.statusNode.frame.minX + firstLineOffset - image.size.width - 7.0, y: 5.0), size: image.size)
|
||||
}
|
||||
|
||||
return titleLayout.size.height + spacing + statusLayout.size.height
|
||||
|
@ -0,0 +1,37 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
|
||||
private let labelFont = Font.regular(17.0)
|
||||
|
||||
final class CallControllerToastNode: ASDisplayNode {
|
||||
struct Content: Equatable {
|
||||
enum Image {
|
||||
case cameraOff
|
||||
}
|
||||
|
||||
var image: Image
|
||||
var text: String
|
||||
|
||||
init(image: Image, text: String) {
|
||||
self.image = image
|
||||
self.text = text
|
||||
}
|
||||
}
|
||||
|
||||
let effectView: UIVisualEffectView
|
||||
|
||||
override init() {
|
||||
self.effectView = UIVisualEffectView()
|
||||
self.effectView.effect = UIBlurEffect(style: .light)
|
||||
self.effectView.layer.cornerRadius = 16.0
|
||||
self.effectView.clipsToBounds = true
|
||||
self.effectView.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.view.addSubview(self.effectView)
|
||||
}
|
||||
}
|
@ -357,7 +357,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
}, openSettings: {
|
||||
openSettings()
|
||||
}, { value in
|
||||
if isVideo {
|
||||
if isVideo && value {
|
||||
DeviceAccess.authorizeAccess(to: .camera, presentationData: presentationData, present: { c, a in
|
||||
present(c, a)
|
||||
}, openSettings: {
|
||||
@ -434,7 +434,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
}, openSettings: {
|
||||
openSettings()
|
||||
}, { value in
|
||||
if isVideo {
|
||||
if isVideo && value {
|
||||
DeviceAccess.authorizeAccess(to: .camera, presentationData: presentationData, present: { c, a in
|
||||
present(c, a)
|
||||
}, openSettings: {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_calls_video.pdf",
|
||||
"filename" : "ic_call_camera.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_calls_mute.pdf",
|
||||
"filename" : "ic_call_microphone.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_calls_speaker.pdf",
|
||||
"filename" : "ic_call_speaker.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_calls_cameraflip.pdf",
|
||||
"filename" : "ic_call_flip.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
Binary file not shown.
@ -85,10 +85,26 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
switch discardReason {
|
||||
case .busy, .disconnect:
|
||||
callSuccessful = false
|
||||
titleString = item.presentationData.strings.Notification_CallCanceled
|
||||
if isVideo {
|
||||
titleString = item.presentationData.strings.Notification_VideoCallCanceled
|
||||
} else {
|
||||
titleString = item.presentationData.strings.Notification_CallCanceled
|
||||
}
|
||||
case .missed:
|
||||
callSuccessful = false
|
||||
titleString = incoming ? item.presentationData.strings.Notification_CallMissed : item.presentationData.strings.Notification_CallCanceled
|
||||
if incoming {
|
||||
if isVideo {
|
||||
titleString = item.presentationData.strings.Notification_VideoCallMissed
|
||||
} else {
|
||||
titleString = item.presentationData.strings.Notification_CallMissed
|
||||
}
|
||||
} else {
|
||||
if isVideo {
|
||||
titleString = item.presentationData.strings.Notification_VideoCallCanceled
|
||||
} else {
|
||||
titleString = item.presentationData.strings.Notification_CallCanceled
|
||||
}
|
||||
}
|
||||
case .hangup:
|
||||
break
|
||||
}
|
||||
@ -99,7 +115,7 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
if titleString == nil {
|
||||
let baseString: String
|
||||
if message.flags.contains(.Incoming) {
|
||||
if incoming {
|
||||
if isVideo {
|
||||
baseString = item.presentationData.strings.Notification_VideoCallIncoming
|
||||
} else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user