mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
4b16494e20
commit
7c0d5519bf
@ -21,6 +21,7 @@ public let savedMessagesIcon = generateTintedImage(image: UIImage(bundleImageNam
|
||||
public let repostStoryIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/RepostStoryIcon"), color: .white)
|
||||
private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed()
|
||||
private let repliesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/RepliesMessagesIcon"), color: .white)
|
||||
private let anonymousSavedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/AnonymousSenderIcon"), color: .white)
|
||||
|
||||
public func avatarPlaceholderFont(size: CGFloat) -> UIFont {
|
||||
return Font.with(size: size, design: .round, weight: .bold)
|
||||
@ -660,7 +661,7 @@ public final class AvatarNode: ASDisplayNode {
|
||||
icon = .repliesIcon
|
||||
case .anonymousSavedMessagesIcon:
|
||||
representation = nil
|
||||
icon = .repliesIcon
|
||||
icon = .anonymousSavedMessagesIcon
|
||||
case let .archivedChatsIcon(hiddenByDefault):
|
||||
representation = nil
|
||||
icon = .archivedChatsIcon(hiddenByDefault: hiddenByDefault)
|
||||
@ -897,8 +898,8 @@ public final class AvatarNode: ASDisplayNode {
|
||||
context.scaleBy(x: factor, y: -factor)
|
||||
context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
|
||||
|
||||
if let repliesIcon = repliesIcon {
|
||||
context.draw(repliesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - repliesIcon.size.width) / 2.0), y: floor((bounds.size.height - repliesIcon.size.height) / 2.0)), size: repliesIcon.size))
|
||||
if let anonymousSavedMessagesIcon = anonymousSavedMessagesIcon {
|
||||
context.draw(anonymousSavedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - anonymousSavedMessagesIcon.size.width) / 2.0), y: floor((bounds.size.height - anonymousSavedMessagesIcon.size.height) / 2.0)), size: anonymousSavedMessagesIcon.size))
|
||||
}
|
||||
} else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme, !parameters.hasImage {
|
||||
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||
|
@ -2205,7 +2205,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}, peerSelected: { [weak self] peer, chatPeer, threadId, _ in
|
||||
interaction.dismissInput()
|
||||
interaction.openPeer(peer, chatPeer, threadId, false)
|
||||
let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).startStandalone()
|
||||
switch location {
|
||||
case .chatList, .forum:
|
||||
let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).startStandalone()
|
||||
case .savedMessagesChats:
|
||||
break
|
||||
}
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
}, disabledPeerSelected: { _, _ in
|
||||
}, togglePeerSelected: { _, _ in
|
||||
@ -2670,7 +2675,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
let transition = chatListSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, filter: peersFilter, peerSelected: { peer, threadId in
|
||||
interaction.openPeer(peer, nil, threadId, true)
|
||||
if threadId == nil {
|
||||
let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).startStandalone()
|
||||
switch location {
|
||||
case .chatList, .forum:
|
||||
let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).startStandalone()
|
||||
case .savedMessagesChats:
|
||||
break
|
||||
}
|
||||
}
|
||||
self?.recentListNode.clearHighlightAnimated(true)
|
||||
}, disabledPeerSelected: { peer, threadId in
|
||||
|
@ -44,7 +44,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
||||
case iPadPro
|
||||
case iPadPro3rdGen
|
||||
case iPadMini6thGen
|
||||
case unknown(screenSize: CGSize, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?)
|
||||
case unknown(screenSize: CGSize, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?, screenCornerRadius: CGFloat)
|
||||
|
||||
public static let performance = Performance()
|
||||
|
||||
@ -111,14 +111,24 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
||||
return
|
||||
}
|
||||
}
|
||||
self = .unknown(screenSize: screenSize, statusBarHeight: statusBarHeight, onScreenNavigationHeight: onScreenNavigationHeight)
|
||||
|
||||
let screenCornerRadius: CGFloat
|
||||
if screenSize.width >= 1024.0 || screenSize.height >= 1024.0 {
|
||||
screenCornerRadius = 0.0
|
||||
} else if onScreenNavigationHeight != nil {
|
||||
screenCornerRadius = 39.0
|
||||
} else {
|
||||
screenCornerRadius = 0.0
|
||||
}
|
||||
|
||||
self = .unknown(screenSize: screenSize, statusBarHeight: statusBarHeight, onScreenNavigationHeight: onScreenNavigationHeight, screenCornerRadius: screenCornerRadius)
|
||||
}
|
||||
|
||||
public var type: DeviceType {
|
||||
switch self {
|
||||
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen:
|
||||
return .tablet
|
||||
case let .unknown(screenSize, _, _) where screenSize.width >= 744.0 && screenSize.height >= 1024.0:
|
||||
case let .unknown(screenSize, _, _, _) where screenSize.width >= 744.0 && screenSize.height >= 1024.0:
|
||||
return .tablet
|
||||
default:
|
||||
return .phone
|
||||
@ -175,7 +185,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
||||
return CGSize(width: 1024.0, height: 1366.0)
|
||||
case .iPadMini6thGen:
|
||||
return CGSize(width: 744.0, height: 1133.0)
|
||||
case let .unknown(screenSize, _, _):
|
||||
case let .unknown(screenSize, _, _, _):
|
||||
return screenSize
|
||||
}
|
||||
}
|
||||
@ -194,12 +204,8 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
||||
return 53.0 + UIScreenPixel
|
||||
case .iPhone14Pro, .iPhone14ProMax:
|
||||
return 55.0
|
||||
case let .unknown(_, _, onScreenNavigationHeight):
|
||||
if let _ = onScreenNavigationHeight {
|
||||
return 39.0
|
||||
} else {
|
||||
return 0.0
|
||||
}
|
||||
case let .unknown(_, _, _, screenCornerRadius):
|
||||
return screenCornerRadius
|
||||
default:
|
||||
return 0.0
|
||||
}
|
||||
@ -230,7 +236,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case let .unknown(_, _, onScreenNavigationHeight):
|
||||
case let .unknown(_, _, onScreenNavigationHeight, _):
|
||||
return onScreenNavigationHeight
|
||||
default:
|
||||
return nil
|
||||
@ -260,7 +266,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
||||
return 44.0
|
||||
case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
|
||||
return 24.0
|
||||
case let .unknown(_, statusBarHeight, _):
|
||||
case let .unknown(_, statusBarHeight, _, _):
|
||||
return statusBarHeight
|
||||
default:
|
||||
return 20.0
|
||||
|
@ -415,7 +415,7 @@ final class InstantPageSlideshowNode: ASDisplayNode, InstantPageNode {
|
||||
super.layout()
|
||||
|
||||
self.pagerNode.frame = self.bounds
|
||||
self.pagerNode.containerLayoutUpdated(ContainerViewLayout(size: self.bounds.size, metrics: LayoutMetrics(), deviceMetrics: .unknown(screenSize: CGSize(), statusBarHeight: 0.0, onScreenNavigationHeight: nil), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate)
|
||||
self.pagerNode.containerLayoutUpdated(ContainerViewLayout(size: self.bounds.size, metrics: LayoutMetrics(), deviceMetrics: .unknown(screenSize: CGSize(), statusBarHeight: 0.0, onScreenNavigationHeight: nil, screenCornerRadius: 0.0), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate)
|
||||
|
||||
self.pageControlNode.layer.transform = CATransform3DIdentity
|
||||
self.pageControlNode.frame = CGRect(origin: CGPoint(x: 0.0, y: self.bounds.size.height - 20.0), size: CGSize(width: self.bounds.size.width, height: 20.0))
|
||||
|
@ -148,7 +148,7 @@ public final class CallController: ViewController {
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
var useV2 = self.call.context.sharedContext.immediateExperimentalUISettings.callV2
|
||||
var useV2 = true
|
||||
if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_callui_v2"] {
|
||||
useV2 = false
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
private var didInitializeIsReady: Bool = false
|
||||
|
||||
private var callStartTimestamp: Double?
|
||||
private var smoothSignalQuality: Double?
|
||||
private var smoothSignalQualityTarget: Double?
|
||||
|
||||
private var callState: PresentationCallState?
|
||||
var isMuted: Bool = false
|
||||
@ -77,6 +79,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
private var panGestureState: PanGestureState?
|
||||
private var notifyDismissedInteractivelyOnPanGestureApply: Bool = false
|
||||
|
||||
private var signalQualityTimer: Foundation.Timer?
|
||||
|
||||
init(
|
||||
sharedContext: SharedAccountContext,
|
||||
account: Account,
|
||||
@ -190,6 +194,22 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
})
|
||||
|
||||
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
|
||||
|
||||
self.signalQualityTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let smoothSignalQuality = self.smoothSignalQuality, let smoothSignalQualityTarget = self.smoothSignalQualityTarget {
|
||||
let updatedSmoothSignalQuality = (smoothSignalQuality + smoothSignalQualityTarget) * 0.5
|
||||
if abs(updatedSmoothSignalQuality - smoothSignalQuality) > 0.001 {
|
||||
self.smoothSignalQuality = updatedSmoothSignalQuality
|
||||
|
||||
if let callState = self.callState {
|
||||
self.updateCallState(callState)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -197,6 +217,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
self.isMicrophoneMutedDisposable?.dispose()
|
||||
self.audioLevelDisposable?.dispose()
|
||||
self.audioOutputCheckTimer?.invalidate()
|
||||
self.signalQualityTimer?.invalidate()
|
||||
}
|
||||
|
||||
func updateAudioOutputs(availableOutputs: [AudioSessionOutput], currentOutput: AudioSessionOutput?) {
|
||||
@ -211,8 +232,24 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
mappedOutput = .internalSpeaker
|
||||
case .speaker:
|
||||
mappedOutput = .speaker
|
||||
case .headphones, .port:
|
||||
mappedOutput = .speaker
|
||||
case .headphones:
|
||||
mappedOutput = .headphones
|
||||
case let .port(port):
|
||||
switch port.type {
|
||||
case .wired:
|
||||
mappedOutput = .headphones
|
||||
default:
|
||||
let portName = port.name.lowercased()
|
||||
if portName.contains("airpods pro") {
|
||||
mappedOutput = .airpodsPro
|
||||
} else if portName.contains("airpods max") {
|
||||
mappedOutput = .airpodsMax
|
||||
} else if portName.contains("airpods") {
|
||||
mappedOutput = .airpods
|
||||
} else {
|
||||
mappedOutput = .bluetooth
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mappedOutput = .internalSpeaker
|
||||
@ -342,10 +379,16 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
case .connecting:
|
||||
mappedLifecycleState = .connecting
|
||||
case let .active(startTime, signalQuality, keyData):
|
||||
self.callStartTimestamp = startTime
|
||||
var signalQuality = signalQuality.flatMap(Int.init)
|
||||
self.smoothSignalQualityTarget = Double(signalQuality ?? 4)
|
||||
|
||||
var signalQuality = signalQuality
|
||||
signalQuality = 4
|
||||
if let smoothSignalQuality = self.smoothSignalQuality {
|
||||
signalQuality = Int(round(smoothSignalQuality))
|
||||
} else {
|
||||
signalQuality = 4
|
||||
}
|
||||
|
||||
self.callStartTimestamp = startTime
|
||||
|
||||
let _ = keyData
|
||||
mappedLifecycleState = .active(PrivateCallScreen.State.ActiveState(
|
||||
@ -354,6 +397,9 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
emojiKey: self.resolvedEmojiKey(data: keyData)
|
||||
))
|
||||
case let .reconnecting(startTime, _, keyData):
|
||||
self.smoothSignalQuality = nil
|
||||
self.smoothSignalQualityTarget = nil
|
||||
|
||||
if self.callStartTimestamp != nil {
|
||||
mappedLifecycleState = .active(PrivateCallScreen.State.ActiveState(
|
||||
startTime: startTime + kCFAbsoluteTimeIntervalSince1970,
|
||||
@ -517,51 +563,31 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
callScreenState.name = peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)
|
||||
callScreenState.shortName = peer.compactDisplayTitle
|
||||
|
||||
if self.currentPeer?.smallProfileImage != peer.smallProfileImage {
|
||||
if (self.currentPeer?.smallProfileImage != peer.smallProfileImage) || self.callScreenState?.avatarImage == nil {
|
||||
self.peerAvatarDisposable?.dispose()
|
||||
|
||||
if let smallProfileImage = peer.largeProfileImage, let peerReference = PeerReference(peer._asPeer()) {
|
||||
if let thumbnailImage = smallProfileImage.immediateThumbnailData.flatMap(decodeTinyThumbnail).flatMap(UIImage.init(data:)), let cgImage = thumbnailImage.cgImage {
|
||||
callScreenState.avatarImage = generateImage(CGSize(width: 128.0, height: 128.0), contextGenerator: { size, context in
|
||||
context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size))
|
||||
}, scale: 1.0).flatMap { image in
|
||||
return blurredImage(image, radius: 10.0)
|
||||
}
|
||||
}
|
||||
|
||||
let postbox = self.call.context.account.postbox
|
||||
self.peerAvatarDisposable = (Signal<UIImage?, NoError> { subscriber in
|
||||
let fetchDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: .other, userContentType: .avatar, reference: .avatar(peer: peerReference, resource: smallProfileImage.resource)).start()
|
||||
let dataDisposable = postbox.mediaBox.resourceData(smallProfileImage.resource).start(next: { data in
|
||||
if data.complete, let image = UIImage(contentsOfFile: data.path)?.precomposed() {
|
||||
subscriber.putNext(image)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
})
|
||||
|
||||
return ActionDisposable {
|
||||
fetchDisposable.dispose()
|
||||
dataDisposable.dispose()
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] image in
|
||||
let size = CGSize(width: 128.0, height: 128.0)
|
||||
if let representation = peer.largeProfileImage, let signal = peerAvatarImage(account: self.call.context.account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: representation, displayDimensions: size, synchronousLoad: self.callScreenState?.avatarImage == nil) {
|
||||
self.peerAvatarDisposable = (signal
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] imageVersions in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if var callScreenState = self.callScreenState {
|
||||
let image = imageVersions?.0
|
||||
if let image {
|
||||
callScreenState.avatarImage = image
|
||||
self.callScreenState = callScreenState
|
||||
self.update(transition: .immediate)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.peerAvatarDisposable?.dispose()
|
||||
self.peerAvatarDisposable = nil
|
||||
|
||||
callScreenState.avatarImage = generateImage(CGSize(width: 512, height: 512), scale: 1.0, rotatedContext: { size, context in
|
||||
let image = generateImage(size, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
drawPeerAvatarLetters(context: context, size: size, font: Font.semibold(20.0), letters: peer.displayLetters, peerId: peer.id, nameColor: peer.nameColor)
|
||||
})
|
||||
drawPeerAvatarLetters(context: context, size: size, font: avatarPlaceholderFont(size: 50.0), letters: peer.displayLetters, peerId: peer.id, nameColor: peer.nameColor)
|
||||
})!
|
||||
callScreenState.avatarImage = image
|
||||
self.callScreenState = callScreenState
|
||||
self.update(transition: .immediate)
|
||||
}
|
||||
}
|
||||
self.currentPeer = peer
|
||||
@ -893,6 +919,19 @@ private final class AdaptedCallVideoSource: VideoSource {
|
||||
let width = i420Buffer.width
|
||||
let height = i420Buffer.height
|
||||
|
||||
/*output = Output(
|
||||
resolution: CGSize(width: CGFloat(width), height: CGFloat(height)),
|
||||
textureLayout: .triPlanar(Output.TriPlanarTextureLayout(
|
||||
y: yTexture,
|
||||
uv: uvTexture
|
||||
)),
|
||||
dataBuffer: Output.NativeDataBuffer(pixelBuffer: nativeBuffer.pixelBuffer),
|
||||
rotationAngle: rotationAngle,
|
||||
followsDeviceOrientation: followsDeviceOrientation,
|
||||
mirrorDirection: mirrorDirection,
|
||||
sourceId: sourceId
|
||||
)*/
|
||||
|
||||
let _ = width
|
||||
let _ = height
|
||||
return
|
||||
|
@ -157,7 +157,7 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
public enum Content {
|
||||
public enum Content: Equatable {
|
||||
case call(SharedAccountContext, Account, PresentationCall)
|
||||
case groupCall(SharedAccountContext, Account, PresentationGroupCall)
|
||||
|
||||
@ -167,6 +167,23 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
return sharedContext
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: Content, rhs: Content) -> Bool {
|
||||
switch lhs {
|
||||
case let .call(sharedContext, account, call):
|
||||
if case let .call(rhsSharedContext, rhsAccount, rhsCall) = rhs, sharedContext === rhsSharedContext, account === rhsAccount, call === rhsCall {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .groupCall(sharedContext, account, groupCall):
|
||||
if case let .groupCall(rhsSharedContext, rhsAccount, rhsGroupCall) = rhs, sharedContext === rhsSharedContext, account === rhsAccount, groupCall === rhsGroupCall {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let backgroundNode: CallStatusBarBackgroundNode
|
||||
@ -236,10 +253,12 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
}
|
||||
|
||||
public func update(content: Content) {
|
||||
self.currentContent = content
|
||||
self.backgroundNode.animationsEnabled = content.sharedContext.energyUsageSettings.fullTranslucency
|
||||
if self.isCurrentlyInHierarchy {
|
||||
self.update()
|
||||
if self.currentContent != content {
|
||||
self.currentContent = content
|
||||
self.backgroundNode.animationsEnabled = content.sharedContext.energyUsageSettings.fullTranslucency
|
||||
if self.isCurrentlyInHierarchy {
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -419,6 +419,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/SavedMessages/SavedMessagesScreen",
|
||||
"//submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen",
|
||||
"//submodules/TelegramUI/Components/Settings/WallpaperGridScreen",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
"//build-system:ios_sim_arm64": [],
|
||||
|
@ -15,7 +15,7 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
||||
case end
|
||||
}
|
||||
|
||||
case speaker(isActive: Bool)
|
||||
case speaker(audioOutput: PrivateCallScreen.State.AudioOutput)
|
||||
case flipCamera
|
||||
case video(isActive: Bool)
|
||||
case microphone(isMuted: Bool)
|
||||
@ -215,10 +215,37 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
||||
let isActive: Bool
|
||||
var isDestructive: Bool = false
|
||||
switch button.content {
|
||||
case let .speaker(isActiveValue):
|
||||
title = "speaker"
|
||||
image = UIImage(bundleImageName: "Call/Speaker")
|
||||
isActive = isActiveValue
|
||||
case let .speaker(audioOutput):
|
||||
switch audioOutput {
|
||||
case .internalSpeaker, .speaker:
|
||||
title = "speaker"
|
||||
default:
|
||||
title = "audio"
|
||||
}
|
||||
|
||||
switch audioOutput {
|
||||
case .internalSpeaker:
|
||||
image = UIImage(bundleImageName: "Call/Speaker")
|
||||
isActive = false
|
||||
case .speaker:
|
||||
image = UIImage(bundleImageName: "Call/Speaker")
|
||||
isActive = true
|
||||
case .airpods:
|
||||
image = UIImage(bundleImageName: "Call/CallAirpodsButton")
|
||||
isActive = true
|
||||
case .airpodsPro:
|
||||
image = UIImage(bundleImageName: "Call/CallAirpodsProButton")
|
||||
isActive = true
|
||||
case .airpodsMax:
|
||||
image = UIImage(bundleImageName: "Call/CallAirpodsMaxButton")
|
||||
isActive = true
|
||||
case .headphones:
|
||||
image = UIImage(bundleImageName: "Call/CallHeadphonesButton")
|
||||
isActive = true
|
||||
case .bluetooth:
|
||||
image = UIImage(bundleImageName: "Call/CallBluetoothButton")
|
||||
isActive = true
|
||||
}
|
||||
case .flipCamera:
|
||||
title = "flip"
|
||||
image = UIImage(bundleImageName: "Call/Flip")
|
||||
|
@ -60,6 +60,11 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
public enum AudioOutput: Equatable {
|
||||
case internalSpeaker
|
||||
case speaker
|
||||
case headphones
|
||||
case airpods
|
||||
case airpodsPro
|
||||
case airpodsMax
|
||||
case bluetooth
|
||||
}
|
||||
|
||||
public var lifecycleState: LifecycleState
|
||||
@ -354,6 +359,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
if !self.isUpdating {
|
||||
let wereControlsHidden = self.areControlsHidden
|
||||
self.areControlsHidden = true
|
||||
self.displayEmojiTooltip = false
|
||||
self.update(transition: .immediate)
|
||||
|
||||
if !wereControlsHidden {
|
||||
@ -417,6 +423,24 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
if self.activeRemoteVideoSource != nil || self.activeLocalVideoSource != nil {
|
||||
self.areControlsHidden = !self.areControlsHidden
|
||||
update = true
|
||||
|
||||
if self.areControlsHidden {
|
||||
self.displayEmojiTooltip = false
|
||||
self.hideControlsTimer?.invalidate()
|
||||
self.hideControlsTimer = nil
|
||||
} else {
|
||||
self.hideControlsTimer?.invalidate()
|
||||
self.hideControlsTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if !self.areControlsHidden {
|
||||
self.areControlsHidden = true
|
||||
self.displayEmojiTooltip = false
|
||||
self.update(transition: .spring(duration: 0.4))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if update {
|
||||
@ -515,7 +539,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
if let previousParams = self.params, case .active = params.state.lifecycleState {
|
||||
switch previousParams.state.lifecycleState {
|
||||
case .requesting, .ringing, .connecting, .reconnecting:
|
||||
if self.hideEmojiTooltipTimer == nil {
|
||||
if self.hideEmojiTooltipTimer == nil && !self.areControlsHidden {
|
||||
self.displayEmojiTooltip = true
|
||||
|
||||
self.hideEmojiTooltipTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false, block: { [weak self] _ in
|
||||
@ -592,6 +616,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
}
|
||||
if !self.areControlsHidden {
|
||||
self.areControlsHidden = true
|
||||
self.displayEmojiTooltip = false
|
||||
self.update(transition: .spring(duration: 0.4))
|
||||
}
|
||||
})
|
||||
@ -704,7 +729,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
self.flipCameraAction?()
|
||||
}), at: 0)
|
||||
} else {
|
||||
buttons.insert(ButtonGroupView.Button(content: .speaker(isActive: params.state.audioOutput != .internalSpeaker), isEnabled: !isTerminated, action: { [weak self] in
|
||||
buttons.insert(ButtonGroupView.Button(content: .speaker(audioOutput: params.state.audioOutput), isEnabled: !isTerminated, action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -1157,9 +1182,12 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
transition.setPosition(layer: self.blobLayer, position: CGPoint(x: blobFrame.width * 0.5, y: blobFrame.height * 0.5))
|
||||
transition.setBounds(layer: self.blobLayer, bounds: CGRect(origin: CGPoint(), size: blobFrame.size))
|
||||
|
||||
let displayAudioLevelBlob: Bool
|
||||
let titleString: String
|
||||
switch params.state.lifecycleState {
|
||||
case let .terminated(terminatedState):
|
||||
displayAudioLevelBlob = false
|
||||
|
||||
self.titleView.contentMode = .center
|
||||
|
||||
switch terminatedState.reason {
|
||||
@ -1174,6 +1202,14 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
case .missed:
|
||||
titleString = "Call Missed"
|
||||
}
|
||||
default:
|
||||
displayAudioLevelBlob = !params.state.isRemoteAudioMuted
|
||||
|
||||
self.titleView.contentMode = .scaleToFill
|
||||
titleString = params.state.name
|
||||
}
|
||||
|
||||
if !displayAudioLevelBlob {
|
||||
genericAlphaTransition.setScale(layer: self.blobLayer, scale: 0.3)
|
||||
genericAlphaTransition.setAlpha(layer: self.blobLayer, alpha: 0.0)
|
||||
self.canAnimateAudioLevel = false
|
||||
@ -1181,11 +1217,12 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
self.currentAvatarAudioScale = 1.0
|
||||
transition.setScale(layer: self.avatarTransformLayer, scale: 1.0)
|
||||
transition.setScale(layer: self.blobTransformLayer, scale: 1.0)
|
||||
default:
|
||||
self.titleView.contentMode = .scaleToFill
|
||||
titleString = params.state.name
|
||||
} else {
|
||||
genericAlphaTransition.setAlpha(layer: self.blobLayer, alpha: (expandedEmojiKeyOverlapsAvatar && !havePrimaryVideo) ? 0.0 : 1.0)
|
||||
transition.setScale(layer: self.blobLayer, scale: expandedEmojiKeyOverlapsAvatar ? 0.001 : 1.0)
|
||||
if !havePrimaryVideo {
|
||||
self.canAnimateAudioLevel = true
|
||||
}
|
||||
}
|
||||
|
||||
let titleSize = self.titleView.update(
|
||||
|
@ -0,0 +1,38 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ChatMessageNotificationItem",
|
||||
module_name = "ChatMessageNotificationItem",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/TelegramUIPreferences",
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/LocalizedPeerData",
|
||||
"//submodules/StickerResources",
|
||||
"//submodules/PhotoResources",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/InvisibleInkDustNode",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
"//submodules/TelegramUI/Components/AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,233 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AvatarNode
|
||||
import AccountContext
|
||||
import LocalizedPeerData
|
||||
import StickerResources
|
||||
import PhotoResources
|
||||
import TelegramStringFormatting
|
||||
import TextFormat
|
||||
import InvisibleInkDustNode
|
||||
import TextNodeWithEntities
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
import BundleIconComponent
|
||||
import PlainButtonComponent
|
||||
|
||||
public final class ChatCallNotificationItem: NotificationItem {
|
||||
public let context: AccountContext
|
||||
public let strings: PresentationStrings
|
||||
public let nameDisplayOrder: PresentationPersonNameOrder
|
||||
public let peer: EnginePeer
|
||||
public let isVideo: Bool
|
||||
public let action: (Bool) -> Void
|
||||
|
||||
public var groupingKey: AnyHashable? {
|
||||
return nil
|
||||
}
|
||||
|
||||
public init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, peer: EnginePeer, isVideo: Bool, action: @escaping (Bool) -> Void) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
self.peer = peer
|
||||
self.isVideo = isVideo
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public func node(compact: Bool) -> NotificationItemNode {
|
||||
let node = ChatCallNotificationItemNode()
|
||||
node.setupItem(self, compact: compact)
|
||||
return node
|
||||
}
|
||||
|
||||
public func tapped(_ take: @escaping () -> (ASDisplayNode?, () -> Void)) {
|
||||
}
|
||||
|
||||
public func canBeExpanded() -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
public func expand(_ take: @escaping () -> (ASDisplayNode?, () -> Void)) {
|
||||
}
|
||||
}
|
||||
|
||||
private let compactAvatarFont = avatarPlaceholderFont(size: 20.0)
|
||||
private let avatarFont = avatarPlaceholderFont(size: 24.0)
|
||||
|
||||
final class ChatCallNotificationItemNode: NotificationItemNode {
|
||||
private var item: ChatCallNotificationItem?
|
||||
|
||||
private let avatarNode: AvatarNode
|
||||
private let title = ComponentView<Empty>()
|
||||
private let text = ComponentView<Empty>()
|
||||
private let answerButton = ComponentView<Empty>()
|
||||
private let declineButton = ComponentView<Empty>()
|
||||
|
||||
private var compact: Bool?
|
||||
private var validLayout: CGFloat?
|
||||
|
||||
override init() {
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
|
||||
super.init()
|
||||
|
||||
self.acceptsTouches = true
|
||||
|
||||
self.addSubnode(self.avatarNode)
|
||||
}
|
||||
|
||||
func setupItem(_ item: ChatCallNotificationItem, compact: Bool) {
|
||||
self.item = item
|
||||
|
||||
self.compact = compact
|
||||
if compact {
|
||||
self.avatarNode.font = compactAvatarFont
|
||||
}
|
||||
let presentationData = item.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.avatarNode.setPeer(context: item.context, theme: presentationData.theme, peer: item.peer, overrideImage: nil, emptyColor: presentationData.theme.list.mediaPlaceholderColor)
|
||||
|
||||
if let width = self.validLayout {
|
||||
let _ = self.updateLayout(width: width, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
self.validLayout = width
|
||||
|
||||
let panelHeight: CGFloat = 66.0
|
||||
|
||||
guard let item = self.item else {
|
||||
return panelHeight
|
||||
}
|
||||
|
||||
let presentationData = item.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let leftInset: CGFloat = 14.0
|
||||
let rightInset: CGFloat = 14.0
|
||||
let avatarSize: CGFloat = 38.0
|
||||
let avatarTextSpacing: CGFloat = 10.0
|
||||
let buttonSpacing: CGFloat = 14.0
|
||||
let titleTextSpacing: CGFloat = 0.0
|
||||
|
||||
let maxTextWidth: CGFloat = width - leftInset - avatarTextSpacing - rightInset - avatarSize * 2.0 - buttonSpacing - avatarTextSpacing
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: item.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.semibold(16.0), textColor: presentationData.theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: maxTextWidth, height: 100.0)
|
||||
)
|
||||
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: item.isVideo ? presentationData.strings.Notification_VideoCallIncoming : presentationData.strings.Notification_CallIncoming, font: Font.regular(13.0), textColor: presentationData.theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: maxTextWidth, height: 100.0)
|
||||
)
|
||||
|
||||
let titleTextHeight = titleSize.height + titleTextSpacing + textSize.height
|
||||
let titleTextY = floor((panelHeight - titleTextHeight) * 0.5)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset + avatarSize + avatarTextSpacing, y: titleTextY), size: titleSize)
|
||||
let textFrame = CGRect(origin: CGPoint(x: leftInset + avatarSize + avatarTextSpacing, y: titleTextY + titleSize.height + titleTextSpacing), size: textSize)
|
||||
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.view.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
}
|
||||
|
||||
if let textView = self.text.view {
|
||||
if textView.superview == nil {
|
||||
self.view.addSubview(textView)
|
||||
}
|
||||
textView.frame = textFrame
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: leftInset, y: (panelHeight - avatarSize) / 2.0), size: CGSize(width: avatarSize, height: avatarSize)))
|
||||
|
||||
let answerButtonSize = self.answerButton.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(ZStack([
|
||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(Circle(
|
||||
fillColor: UIColor(rgb: 0x34C759),
|
||||
size: CGSize(width: avatarSize, height: avatarSize)
|
||||
))),
|
||||
AnyComponentWithIdentity(id: 2, component: AnyComponent(BundleIconComponent(
|
||||
name: "Call/CallNotificationAnswerIcon",
|
||||
tintColor: .white
|
||||
)))
|
||||
])),
|
||||
effectAlignment: .center,
|
||||
minSize: CGSize(width: avatarSize, height: avatarSize),
|
||||
action: { [weak self] in
|
||||
guard let self, let item = self.item else {
|
||||
return
|
||||
}
|
||||
item.action(true)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: avatarSize, height: avatarSize)
|
||||
)
|
||||
let declineButtonSize = self.declineButton.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(ZStack([
|
||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(Circle(
|
||||
fillColor: UIColor(rgb: 0xFF3B30),
|
||||
size: CGSize(width: avatarSize, height: avatarSize)
|
||||
))),
|
||||
AnyComponentWithIdentity(id: 2, component: AnyComponent(BundleIconComponent(
|
||||
name: "Call/CallNotificationDeclineIcon",
|
||||
tintColor: .white
|
||||
)))
|
||||
])),
|
||||
effectAlignment: .center,
|
||||
minSize: CGSize(width: avatarSize, height: avatarSize),
|
||||
action: { [weak self] in
|
||||
guard let self, let item = self.item else {
|
||||
return
|
||||
}
|
||||
item.action(false)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: avatarSize, height: avatarSize)
|
||||
)
|
||||
|
||||
let declineButtonFrame = CGRect(origin: CGPoint(x: width - rightInset - avatarSize - buttonSpacing - declineButtonSize.width, y: floor((panelHeight - declineButtonSize.height) * 0.5)), size: declineButtonSize)
|
||||
if let declineButtonView = self.declineButton.view {
|
||||
if declineButtonView.superview == nil {
|
||||
self.view.addSubview(declineButtonView)
|
||||
}
|
||||
declineButtonView.frame = declineButtonFrame
|
||||
}
|
||||
|
||||
let answerButtonFrame = CGRect(origin: CGPoint(x: declineButtonFrame.maxX + buttonSpacing, y: floor((panelHeight - answerButtonSize.height) * 0.5)), size: answerButtonSize)
|
||||
if let answerButtonView = self.answerButton.view {
|
||||
if answerButtonView.superview == nil {
|
||||
self.view.addSubview(answerButtonView)
|
||||
}
|
||||
answerButtonView.frame = answerButtonFrame
|
||||
}
|
||||
|
||||
return panelHeight
|
||||
}
|
||||
}
|
@ -20,14 +20,14 @@ import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
|
||||
public final class ChatMessageNotificationItem: NotificationItem {
|
||||
let context: AccountContext
|
||||
let strings: PresentationStrings
|
||||
let dateTimeFormat: PresentationDateTimeFormat
|
||||
let nameDisplayOrder: PresentationPersonNameOrder
|
||||
let messages: [Message]
|
||||
let threadData: MessageHistoryThreadData?
|
||||
let tapAction: () -> Bool
|
||||
let expandAction: (@escaping () -> (ASDisplayNode?, () -> Void)) -> Void
|
||||
public let context: AccountContext
|
||||
public let strings: PresentationStrings
|
||||
public let dateTimeFormat: PresentationDateTimeFormat
|
||||
public let nameDisplayOrder: PresentationPersonNameOrder
|
||||
public let messages: [Message]
|
||||
public let threadData: MessageHistoryThreadData?
|
||||
public let tapAction: () -> Bool
|
||||
public let expandAction: (@escaping () -> (ASDisplayNode?, () -> Void)) -> Void
|
||||
|
||||
public var groupingKey: AnyHashable? {
|
||||
return messages.first?.id.peerId
|
||||
@ -380,7 +380,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
override public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
self.validLayout = width
|
||||
let compact = self.compact ?? false
|
||||
|
@ -13,7 +13,9 @@ public protocol NotificationItem {
|
||||
}
|
||||
|
||||
public class NotificationItemNode: ASDisplayNode {
|
||||
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
return 32.0
|
||||
}
|
||||
|
||||
public var acceptsTouches: Bool = false
|
||||
}
|
12
submodules/TelegramUI/Images.xcassets/Avatar/AnonymousSenderIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Avatar/AnonymousSenderIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "large_hidden 1.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
134
submodules/TelegramUI/Images.xcassets/Avatar/AnonymousSenderIcon.imageset/large_hidden 1.pdf
vendored
Normal file
134
submodules/TelegramUI/Images.xcassets/Avatar/AnonymousSenderIcon.imageset/large_hidden 1.pdf
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 2.886230 3.427368 cm
|
||||
1.000000 1.000000 1.000000 scn
|
||||
38.260708 21.876385 m
|
||||
41.966354 17.197329 l
|
||||
42.025131 17.123957 42.070347 17.040751 42.100487 16.951702 c
|
||||
42.245766 16.522448 42.015556 16.056696 41.586330 15.911341 c
|
||||
40.312458 15.480631 39.125717 14.924854 38.026104 14.244013 c
|
||||
36.921986 13.560385 35.998123 12.807886 35.254517 11.986513 c
|
||||
35.230656 11.961597 35.209145 11.934603 35.189388 11.906336 c
|
||||
34.929810 11.534874 35.020504 11.023315 35.391689 10.763336 c
|
||||
36.612873 9.912409 l
|
||||
36.764828 9.808632 36.876156 9.655817 36.929893 9.479834 c
|
||||
37.062233 9.046417 36.818161 8.587776 36.384750 8.455406 c
|
||||
33.044167 7.440701 29.867857 6.143715 26.855820 4.564449 c
|
||||
24.258381 3.202564 21.947493 1.751854 19.923153 0.212322 c
|
||||
19.704218 0.045612 19.416248 0.000019 19.156437 0.090702 c
|
||||
18.728577 0.240036 18.502787 0.707947 18.652121 1.135807 c
|
||||
18.758976 1.434193 l
|
||||
19.772457 4.209812 21.285860 6.707634 23.299189 8.927662 c
|
||||
25.982121 11.886038 29.317558 14.105398 33.305504 15.585741 c
|
||||
33.305504 20.218485 l
|
||||
33.304989 20.432394 33.468914 20.610800 33.682098 20.628386 c
|
||||
34.438572 20.690022 35.148914 20.831165 35.813126 21.051815 c
|
||||
36.474316 21.271461 37.109425 21.576414 37.718449 21.966673 c
|
||||
37.895584 22.080223 38.129875 22.041166 38.260708 21.876385 c
|
||||
h
|
||||
4.994442 22.027468 m
|
||||
8.012641 21.104988 l
|
||||
8.684547 18.079058 9.790257 15.434935 11.329765 13.172619 c
|
||||
12.755202 11.077932 14.742273 9.031746 17.290981 7.034061 c
|
||||
17.637016 6.764141 17.708672 6.269619 17.454308 5.911995 c
|
||||
16.618917 4.741905 l
|
||||
16.384727 4.413887 15.946983 4.304516 15.586034 4.483837 c
|
||||
5.774277 9.358383 l
|
||||
5.648376 9.420931 5.540658 9.514778 5.461451 9.630922 c
|
||||
5.206123 10.005320 5.302648 10.515812 5.677044 10.771139 c
|
||||
6.566517 11.377733 l
|
||||
6.661282 11.442360 6.741444 11.526138 6.801830 11.623659 c
|
||||
7.040406 12.008947 6.921472 12.514688 6.536184 12.753265 c
|
||||
0.388562 16.559954 l
|
||||
0.336297 16.592318 0.287836 16.630453 0.244088 16.673639 c
|
||||
-0.078415 16.992004 -0.081768 17.511530 0.236598 17.834032 c
|
||||
4.170660 21.819214 l
|
||||
4.385132 22.036472 4.702488 22.116701 4.994442 22.027468 c
|
||||
h
|
||||
30.742079 20.589767 m
|
||||
30.998411 20.158377 31.141327 19.678396 31.141327 19.172619 c
|
||||
31.141327 17.294851 29.171381 15.772619 26.741327 15.772619 c
|
||||
24.311274 15.772619 22.341328 17.294851 22.341328 19.172619 c
|
||||
22.341328 19.425110 22.376945 19.671173 22.444510 19.907970 c
|
||||
25.367607 19.966751 28.125259 20.198212 30.595587 20.567251 c
|
||||
30.742079 20.589767 l
|
||||
h
|
||||
11.389331 20.560408 m
|
||||
13.907309 20.186129 16.722143 19.955175 19.704977 19.904078 c
|
||||
19.726641 19.824015 l
|
||||
19.779579 19.613188 19.807308 19.395405 19.807308 19.172619 c
|
||||
19.807308 17.294851 17.837360 15.772619 15.407308 15.772619 c
|
||||
12.977255 15.772619 11.007308 17.294851 11.007308 19.172619 c
|
||||
11.007308 19.666950 11.143831 20.136642 11.389331 20.560408 c
|
||||
h
|
||||
26.181854 41.372620 m
|
||||
28.509888 41.372620 30.010063 37.754658 30.682379 30.518734 c
|
||||
35.052368 29.651772 37.913662 28.207664 37.913662 26.572620 c
|
||||
37.913662 23.921652 30.392046 21.772619 21.113663 21.772619 c
|
||||
11.835279 21.772619 4.313663 23.921652 4.313663 26.572620 c
|
||||
4.313663 28.208481 7.177811 29.653212 11.551497 30.519779 c
|
||||
12.364214 37.755043 13.874769 41.372620 16.083487 41.372620 c
|
||||
19.449152 41.372620 18.158268 37.207233 21.000959 37.207233 c
|
||||
23.843653 37.207233 22.634565 41.372620 26.181854 41.372620 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
3357
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 48.000000 48.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000003447 00000 n
|
||||
0000003470 00000 n
|
||||
0000003643 00000 n
|
||||
0000003717 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
3776
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Call/CallNotificationAnswerIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Call/CallNotificationAnswerIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "phone.fill.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.00575 11.8681C7.53795 14.4003 10.6134 16.3542 13.1281 16.3542C14.2584 16.3542 15.2485 15.96 16.0458 15.0838C16.5102 14.5668 16.7993 13.9622 16.7993 13.3664C16.7993 12.9283 16.6328 12.5078 16.221 12.2098L13.5311 10.2997C13.1193 10.0194 12.7776 9.87917 12.4622 9.87917C12.0679 9.87917 11.7086 10.107 11.3056 10.5013L10.6835 11.1146C10.5871 11.211 10.4644 11.2548 10.3505 11.2548C10.2191 11.2548 10.0877 11.2022 10.0001 11.1584C9.45681 10.8693 8.52805 10.0719 7.66062 9.21326C6.80195 8.3546 6.00461 7.42583 5.72423 6.88259C5.68042 6.78621 5.62785 6.66354 5.62785 6.53211C5.62785 6.41821 5.6629 6.3043 5.75928 6.20792L6.38137 5.5683C6.7669 5.16525 7.00347 4.81477 7.00347 4.41172C7.00347 4.09629 6.85452 3.75458 6.56538 3.34277L4.68156 0.687902C4.37489 0.267329 3.94556 0.0833282 3.47241 0.0833282C2.89412 0.0833282 2.29831 0.346186 1.78136 0.845617C0.931451 1.66048 0.554688 2.6681 0.554688 3.78086C0.554688 6.29554 2.47355 9.34469 5.00575 11.8681Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
12
submodules/TelegramUI/Images.xcassets/Call/CallNotificationDeclineIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Call/CallNotificationDeclineIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "phone.down.fill.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<svg width="22" height="9" viewBox="0 0 22 9" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.7451 0.305557C6.95123 0.305557 3.38512 1.10289 1.60645 2.88157C0.80035 3.68766 0.3973 4.66024 0.449872 5.83434C0.48492 6.54406 0.703968 7.17491 1.11578 7.58673C1.42245 7.90216 1.85178 8.07739 2.35121 7.99854L5.60189 7.44654C6.09256 7.36768 6.43427 7.21872 6.65332 6.99091C6.94247 6.71053 7.03009 6.28996 7.03009 5.73796V4.853C7.03009 4.71281 7.09142 4.60767 7.17904 4.52005C7.26666 4.41491 7.39809 4.3711 7.49447 4.34481C8.09028 4.20462 9.29943 4.07319 10.7451 4.07319C12.1909 4.07319 13.3913 4.17833 13.9958 4.35357C14.0834 4.37986 14.2061 4.43243 14.3025 4.52005C14.3813 4.60767 14.4339 4.70405 14.4427 4.84424L14.4514 5.73796C14.4602 6.28996 14.5478 6.71053 14.8282 6.99091C15.056 7.21872 15.3977 7.36768 15.8884 7.44654L19.0953 7.98978C19.6122 8.07739 20.0503 7.89339 20.392 7.56044C20.8039 7.15739 21.0317 6.53529 21.0492 5.82558C21.0755 4.64272 20.6199 3.67014 19.8313 2.88157C18.0526 1.10289 14.5391 0.305557 10.7451 0.305557Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
@ -896,7 +896,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
})
|
||||
|
||||
var setPresentationCall: ((PresentationCall?) -> Void)?
|
||||
let sharedContext = SharedAccountContextImpl(mainWindow: self.mainWindow, sharedContainerPath: legacyBasePath, basePath: rootPath, encryptionParameters: encryptionParameters, accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings, networkArguments: networkArguments, hasInAppPurchases: buildConfig.isAppStoreBuild && buildConfig.apiId == 1, rootPath: rootPath, legacyBasePath: legacyBasePath, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init), firebaseSecretStream: self.firebaseSecretStream.get(), setNotificationCall: { call in
|
||||
let sharedContext = SharedAccountContextImpl(mainWindow: self.mainWindow, sharedContainerPath: legacyBasePath, basePath: rootPath, encryptionParameters: encryptionParameters, accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings, networkArguments: networkArguments, hasInAppPurchases: buildConfig.isAppStoreBuild && buildConfig.apiId == 1, rootPath: rootPath, legacyBasePath: legacyBasePath, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init), firebaseSecretStream: self.firebaseSecretStream.get(), setNotificationCall: { call in
|
||||
setPresentationCall?(call)
|
||||
}, navigateToChat: { accountId, peerId, messageId in
|
||||
self.openChatWhenReady(accountId: accountId, peerId: peerId, threadId: nil, messageId: messageId, storyId: nil)
|
||||
@ -1202,6 +1202,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
return true
|
||||
})
|
||||
self.mainWindow.topLevelOverlayControllers = [context.sharedApplicationContext.overlayMediaController, context.notificationController]
|
||||
(context.context.sharedContext as? SharedAccountContextImpl)?.notificationController = context.notificationController
|
||||
var authorizeNotifications = true
|
||||
if #available(iOS 10.0, *) {
|
||||
authorizeNotifications = false
|
||||
|
@ -28,6 +28,7 @@ import TelegramCallsUI
|
||||
import AuthorizationUI
|
||||
import ChatListUI
|
||||
import StoryContainerScreen
|
||||
import ChatMessageNotificationItem
|
||||
|
||||
final class UnauthorizedApplicationContext {
|
||||
let sharedContext: SharedAccountContextImpl
|
||||
|
@ -27,6 +27,7 @@ public func makeTempContext(
|
||||
encryptionParameters: encryptionParameters,
|
||||
accountManager: accountManager,
|
||||
appLockContext: appLockContext,
|
||||
notificationController: nil,
|
||||
applicationBindings: applicationBindings,
|
||||
initialPresentationDataAndSettings: initialPresentationDataAndSettings,
|
||||
networkArguments: networkArguments,
|
||||
|
@ -20,6 +20,7 @@ import MediaEditorScreen
|
||||
import ChatControllerInteraction
|
||||
import SavedMessagesScreen
|
||||
import WallpaperGalleryScreen
|
||||
import ChatMessageNotificationItem
|
||||
|
||||
public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParams) {
|
||||
if case let .peer(peer) = params.chatLocation {
|
||||
|
@ -6,6 +6,7 @@ import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import ChatMessageNotificationItem
|
||||
|
||||
public final class NotificationContainerController: ViewController {
|
||||
private var controllerNode: NotificationContainerControllerNode {
|
||||
@ -97,6 +98,10 @@ public final class NotificationContainerController: ViewController {
|
||||
self.controllerNode.enqueue(item)
|
||||
}
|
||||
|
||||
public func setBlocking(_ item: NotificationItem?) {
|
||||
self.controllerNode.setBlocking(item)
|
||||
}
|
||||
|
||||
public func removeItems(_ f: (NotificationItem) -> Bool) {
|
||||
self.controllerNode.removeItems(f)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import ChatMessageNotificationItem
|
||||
|
||||
private final class NotificationContainerControllerNodeView: UITracingLayerView {
|
||||
var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
|
||||
@ -16,6 +17,7 @@ private final class NotificationContainerControllerNodeView: UITracingLayerView
|
||||
final class NotificationContainerControllerNode: ASDisplayNode {
|
||||
private var validLayout: ContainerViewLayout?
|
||||
private var topItemAndNode: (NotificationItem, NotificationItemContainerNode)?
|
||||
private var blockingItemAndNode: (NotificationItem, NotificationItemContainerNode)?
|
||||
|
||||
var displayingItemsUpdated: ((Bool) -> Void)?
|
||||
|
||||
@ -49,6 +51,9 @@ final class NotificationContainerControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let (_, blockingItemNode) = self.blockingItemAndNode {
|
||||
return blockingItemNode.hitTest(point, with: event)
|
||||
}
|
||||
if let (_, topItemNode) = self.topItemAndNode {
|
||||
return topItemNode.hitTest(point, with: event)
|
||||
}
|
||||
@ -77,6 +82,10 @@ final class NotificationContainerControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
func enqueue(_ item: NotificationItem) {
|
||||
if self.blockingItemAndNode != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if let (_, topItemNode) = self.topItemAndNode {
|
||||
topItemNode.animateOut(completion: { [weak topItemNode] in
|
||||
topItemNode?.removeFromSupernode()
|
||||
@ -89,9 +98,8 @@ final class NotificationContainerControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
let itemNode = item.node(compact: useCompactLayout)
|
||||
let containerNode = NotificationItemContainerNode(theme: self.presentationData.theme)
|
||||
let containerNode = NotificationItemContainerNode(theme: self.presentationData.theme, contentNode: itemNode)
|
||||
containerNode.item = item
|
||||
containerNode.contentNode = itemNode
|
||||
containerNode.dismissed = { [weak self] item in
|
||||
if let strongSelf = self {
|
||||
if let (topItem, topItemNode) = strongSelf.topItemAndNode, topItem.groupingKey != nil && topItem.groupingKey == item.groupingKey {
|
||||
@ -120,7 +128,12 @@ final class NotificationContainerControllerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
self.topItemAndNode = (item, containerNode)
|
||||
self.addSubnode(containerNode)
|
||||
|
||||
if let blockingItemAndNode = self.blockingItemAndNode {
|
||||
self.insertSubnode(containerNode, belowSubnode: blockingItemAndNode.1)
|
||||
} else {
|
||||
self.addSubnode(containerNode)
|
||||
}
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
containerNode.updateLayout(layout: validLayout, transition: .immediate)
|
||||
@ -133,6 +146,70 @@ final class NotificationContainerControllerNode: ASDisplayNode {
|
||||
self.resetTimeoutTimer()
|
||||
}
|
||||
|
||||
func setBlocking(_ item: NotificationItem?) {
|
||||
if let (_, blockingItemNode) = self.blockingItemAndNode {
|
||||
blockingItemNode.animateOut(completion: { [weak blockingItemNode] in
|
||||
blockingItemNode?.removeFromSupernode()
|
||||
})
|
||||
self.blockingItemAndNode = nil
|
||||
}
|
||||
|
||||
if let item = item {
|
||||
if let (_, topItemNode) = self.topItemAndNode {
|
||||
topItemNode.animateOut(completion: { [weak topItemNode] in
|
||||
topItemNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
self.topItemAndNode = nil
|
||||
|
||||
var useCompactLayout = false
|
||||
if let validLayout = self.validLayout {
|
||||
useCompactLayout = min(validLayout.size.width, validLayout.size.height) < 375.0
|
||||
}
|
||||
|
||||
let itemNode = item.node(compact: useCompactLayout)
|
||||
let containerNode = NotificationItemContainerNode(theme: self.presentationData.theme, contentNode: itemNode)
|
||||
containerNode.item = item
|
||||
containerNode.dismissed = { [weak self] item in
|
||||
if let strongSelf = self {
|
||||
if let (topItem, topItemNode) = strongSelf.topItemAndNode, topItem.groupingKey != nil && topItem.groupingKey == item.groupingKey {
|
||||
topItemNode.removeFromSupernode()
|
||||
strongSelf.topItemAndNode = nil
|
||||
|
||||
if let strongSelf = self, strongSelf.topItemAndNode == nil {
|
||||
strongSelf.displayingItemsUpdated?(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
containerNode.cancelTimeout = { [weak self] item in
|
||||
if let strongSelf = self {
|
||||
if let (topItem, _) = strongSelf.topItemAndNode, topItem.groupingKey != nil && topItem.groupingKey == item.groupingKey {
|
||||
strongSelf.timeoutTimer?.invalidate()
|
||||
strongSelf.timeoutTimer = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
containerNode.resumeTimeout = { [weak self] item in
|
||||
if let strongSelf = self {
|
||||
if let (topItem, _) = strongSelf.topItemAndNode, topItem.groupingKey != nil && topItem.groupingKey == item.groupingKey {
|
||||
strongSelf.resetTimeoutTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
self.blockingItemAndNode = (item, containerNode)
|
||||
self.addSubnode(containerNode)
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
containerNode.updateLayout(layout: validLayout, transition: .immediate)
|
||||
containerNode.frame = CGRect(origin: CGPoint(), size: validLayout.size)
|
||||
containerNode.animateIn()
|
||||
}
|
||||
|
||||
self.displayingItemsUpdated?(true)
|
||||
}
|
||||
}
|
||||
|
||||
func removeItems(_ f: (NotificationItem) -> Bool) {
|
||||
if let (topItem, topItemNode) = self.topItemAndNode {
|
||||
if f(topItem) {
|
||||
|
@ -140,7 +140,7 @@ public final class NotificationViewControllerImpl {
|
||||
return nil
|
||||
})
|
||||
|
||||
sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil)
|
||||
sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil)
|
||||
|
||||
presentationDataPromise.set(sharedAccountContext!.presentationData)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import ChatMessageNotificationItem
|
||||
|
||||
final class NotificationItemContainerNode: ASDisplayNode {
|
||||
private let backgroundNode: ASImageNode
|
||||
@ -45,7 +46,9 @@ final class NotificationItemContainerNode: ASDisplayNode {
|
||||
|
||||
var cancelledTimeout = false
|
||||
|
||||
init(theme: PresentationTheme) {
|
||||
init(theme: PresentationTheme, contentNode: NotificationItemNode?) {
|
||||
self.contentNode = contentNode
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
@ -54,16 +57,21 @@ final class NotificationItemContainerNode: ASDisplayNode {
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
if let contentNode {
|
||||
self.addSubnode(contentNode)
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
||||
panRecognizer.delaysTouchesBegan = false
|
||||
panRecognizer.cancelsTouchesInView = false
|
||||
self.view.addGestureRecognizer(panRecognizer)
|
||||
if let contentNode = self.contentNode, !contentNode.acceptsTouches {
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
||||
panRecognizer.delaysTouchesBegan = false
|
||||
panRecognizer.cancelsTouchesInView = false
|
||||
self.view.addGestureRecognizer(panRecognizer)
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
@ -113,6 +121,11 @@ final class NotificationItemContainerNode: ASDisplayNode {
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let contentNode = self.contentNode, contentNode.frame.contains(point) {
|
||||
if contentNode.acceptsTouches {
|
||||
if let result = contentNode.view.hitTest(self.view.convert(point, to: contentNode.view), with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return self.view
|
||||
}
|
||||
return nil
|
||||
|
@ -50,6 +50,7 @@ import ChatRecentActionsController
|
||||
import PeerInfoScreen
|
||||
import ChatQrCodeScreen
|
||||
import UndoUI
|
||||
import ChatMessageNotificationItem
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
@ -87,6 +88,15 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
public let basePath: String
|
||||
public let accountManager: AccountManager<TelegramAccountManagerTypes>
|
||||
public let appLockContext: AppLockContext
|
||||
public var notificationController: NotificationContainerController? {
|
||||
didSet {
|
||||
if self.notificationController !== oldValue {
|
||||
if let oldValue {
|
||||
oldValue.setBlocking(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let navigateToChatImpl: (AccountRecordId, PeerId, MessageId?) -> Void
|
||||
|
||||
@ -137,6 +147,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
public let hasOngoingCall = ValuePromise<Bool>(false)
|
||||
private let callState = Promise<PresentationCallState?>(nil)
|
||||
private var awaitingCallConnectionDisposable: Disposable?
|
||||
private var callPeerDisposable: Disposable?
|
||||
|
||||
private var groupCallController: VoiceChatController?
|
||||
public var currentGroupCallController: ViewController? {
|
||||
@ -216,7 +227,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
|
||||
private let energyUsageAutomaticDisposable = MetaDisposable()
|
||||
|
||||
init(mainWindow: Window1?, sharedContainerPath: String, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager<TelegramAccountManagerTypes>, appLockContext: AppLockContext, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, hasInAppPurchases: Bool, rootPath: String, legacyBasePath: String?, apsNotificationToken: Signal<Data?, NoError>, voipNotificationToken: Signal<Data?, NoError>, firebaseSecretStream: Signal<[String: String], NoError>, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }, appDelegate: AppDelegate?) {
|
||||
init(mainWindow: Window1?, sharedContainerPath: String, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager<TelegramAccountManagerTypes>, appLockContext: AppLockContext, notificationController: NotificationContainerController?, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, hasInAppPurchases: Bool, rootPath: String, legacyBasePath: String?, apsNotificationToken: Signal<Data?, NoError>, voipNotificationToken: Signal<Data?, NoError>, firebaseSecretStream: Signal<[String: String], NoError>, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }, appDelegate: AppDelegate?) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
precondition(!testHasInstance)
|
||||
@ -231,6 +242,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
self.navigateToChatImpl = navigateToChat
|
||||
self.displayUpgradeProgress = displayUpgradeProgress
|
||||
self.appLockContext = appLockContext
|
||||
self.notificationController = notificationController
|
||||
self.hasInAppPurchases = hasInAppPurchases
|
||||
|
||||
self.accountManager.mediaBox.fetchCachedResourceRepresentation = { (resource, representation) -> Signal<CachedMediaResourceRepresentationResult, NoError> in
|
||||
@ -767,18 +779,52 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
self.callController = nil
|
||||
self.hasOngoingCall.set(false)
|
||||
|
||||
self.notificationController?.setBlocking(nil)
|
||||
|
||||
self.callPeerDisposable?.dispose()
|
||||
self.callPeerDisposable = nil
|
||||
|
||||
if let call {
|
||||
self.callState.set(call.state
|
||||
|> map(Optional.init))
|
||||
self.hasOngoingCall.set(true)
|
||||
setNotificationCall(call)
|
||||
|
||||
if !call.isOutgoing && call.isIntegratedWithCallKit {
|
||||
if call.isOutgoing {
|
||||
self.presentControllerWithCurrentCall()
|
||||
} else {
|
||||
if !call.isIntegratedWithCallKit {
|
||||
self.callPeerDisposable?.dispose()
|
||||
self.callPeerDisposable = (call.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: call.peerId))
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self, weak call] peer in
|
||||
guard let self, let call, let peer else {
|
||||
return
|
||||
}
|
||||
if self.call !== call {
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = self.currentPresentationData.with { $0 }
|
||||
self.notificationController?.setBlocking(ChatCallNotificationItem(context: call.context, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, peer: peer, isVideo: call.isVideo, action: { [weak call] answerAction in
|
||||
guard let call else {
|
||||
return
|
||||
}
|
||||
if answerAction {
|
||||
call.answer()
|
||||
} else {
|
||||
call.rejectBusy()
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
self.awaitingCallConnectionDisposable = (call.state
|
||||
|> filter { state in
|
||||
switch state.state {
|
||||
case .ringing:
|
||||
return false
|
||||
case .terminating, .terminated:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
@ -788,10 +834,12 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.notificationController?.setBlocking(nil)
|
||||
self.presentControllerWithCurrentCall()
|
||||
|
||||
self.callPeerDisposable?.dispose()
|
||||
self.callPeerDisposable = nil
|
||||
})
|
||||
} else{
|
||||
self.presentControllerWithCurrentCall()
|
||||
}
|
||||
} else {
|
||||
self.callState.set(.single(nil))
|
||||
@ -861,6 +909,29 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
let callSignal: Signal<PresentationCall?, NoError> = .single(nil)
|
||||
|> then(
|
||||
callManager.currentCallSignal
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { call -> Signal<PresentationCall?, NoError> in
|
||||
guard let call else {
|
||||
return .single(nil)
|
||||
}
|
||||
return call.state
|
||||
|> map { [weak call] state -> PresentationCall? in
|
||||
guard let call else {
|
||||
return nil
|
||||
}
|
||||
switch state.state {
|
||||
case .ringing:
|
||||
return nil
|
||||
case .terminating, .terminated:
|
||||
return nil
|
||||
default:
|
||||
return call
|
||||
}
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
||||
return lhs === rhs
|
||||
})
|
||||
)
|
||||
let groupCallSignal: Signal<PresentationGroupCall?, NoError> = .single(nil)
|
||||
|> then(
|
||||
@ -1002,6 +1073,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
self.groupCallDisposable?.dispose()
|
||||
self.callStateDisposable?.dispose()
|
||||
self.awaitingCallConnectionDisposable?.dispose()
|
||||
self.callPeerDisposable?.dispose()
|
||||
}
|
||||
|
||||
private var didPerformAccountSettingsImport = false
|
||||
|
@ -25,6 +25,17 @@ final class ContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueueWebrtc
|
||||
func isCurrent() -> Bool {
|
||||
return self.queue.isCurrent()
|
||||
}
|
||||
|
||||
func scheduleBlock(_ f: @escaping () -> Void, after timeout: Double) -> GroupCallDisposable {
|
||||
let timer = SwiftSignalKit.Timer(timeout: timeout, repeat: false, completion: {
|
||||
f()
|
||||
}, queue: self.queue)
|
||||
timer.start()
|
||||
|
||||
return GroupCallDisposable(block: {
|
||||
timer.invalidate()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
enum BroadcastPartSubject {
|
||||
|
@ -213,7 +213,7 @@ public struct OngoingCallContextState: Equatable {
|
||||
public let remoteBatteryLevel: RemoteBatteryLevel
|
||||
}
|
||||
|
||||
private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc /*, OngoingCallThreadLocalContextQueueWebrtcCustom*/ {
|
||||
private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc {
|
||||
private let queue: Queue
|
||||
|
||||
init(queue: Queue) {
|
||||
@ -235,6 +235,17 @@ private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCal
|
||||
func isCurrent() -> Bool {
|
||||
return self.queue.isCurrent()
|
||||
}
|
||||
|
||||
func scheduleBlock(_ f: @escaping () -> Void, after timeout: Double) -> GroupCallDisposable {
|
||||
let timer = SwiftSignalKit.Timer(timeout: timeout, repeat: false, completion: {
|
||||
f()
|
||||
}, queue: self.queue)
|
||||
timer.start()
|
||||
|
||||
return GroupCallDisposable(block: {
|
||||
timer.invalidate()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func ongoingNetworkTypeForType(_ type: NetworkType) -> OngoingCallNetworkType {
|
||||
|
@ -98,11 +98,20 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
||||
OngoingCallDataSavingAlways
|
||||
};
|
||||
|
||||
@interface GroupCallDisposable : NSObject
|
||||
|
||||
- (instancetype _Nonnull)initWithBlock:(dispatch_block_t _Nonnull)block;
|
||||
- (void)dispose;
|
||||
|
||||
@end
|
||||
|
||||
@protocol OngoingCallThreadLocalContextQueueWebrtc <NSObject>
|
||||
|
||||
- (void)dispatch:(void (^ _Nonnull)())f;
|
||||
- (bool)isCurrent;
|
||||
|
||||
- (GroupCallDisposable * _Nonnull)scheduleBlock:(void (^ _Nonnull)())f after:(double)timeout;
|
||||
|
||||
@end
|
||||
|
||||
@interface VoipProxyServerWebrtc : NSObject
|
||||
@ -133,13 +142,6 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
||||
#endif
|
||||
@end
|
||||
|
||||
@interface GroupCallDisposable : NSObject
|
||||
|
||||
- (instancetype _Nonnull)initWithBlock:(dispatch_block_t _Nonnull)block;
|
||||
- (void)dispose;
|
||||
|
||||
@end
|
||||
|
||||
@protocol CallVideoFrameBuffer
|
||||
|
||||
@end
|
||||
|
@ -925,7 +925,11 @@ tgcalls::VideoCaptureInterfaceObject *GetVideoCaptureAssumingSameThread(tgcalls:
|
||||
std::unique_ptr<tgcalls::Instance> _tgVoip;
|
||||
bool _didStop;
|
||||
|
||||
OngoingCallStateWebrtc _pendingState;
|
||||
OngoingCallStateWebrtc _state;
|
||||
bool _didPushStateOnce;
|
||||
GroupCallDisposable *_pushStateDisposable;
|
||||
|
||||
OngoingCallVideoStateWebrtc _videoState;
|
||||
bool _connectedOnce;
|
||||
OngoingCallRemoteBatteryLevelWebrtc _remoteBatteryLevel;
|
||||
@ -1356,6 +1360,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
.directConnectionChannel = directConnectionChannel
|
||||
});
|
||||
_state = OngoingCallStateInitializing;
|
||||
_pendingState = OngoingCallStateInitializing;
|
||||
_signalBars = 4;
|
||||
}
|
||||
return self;
|
||||
@ -1374,6 +1379,8 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
_currentAudioDeviceModuleThread = nullptr;
|
||||
}
|
||||
|
||||
[_pushStateDisposable dispose];
|
||||
|
||||
if (_tgVoip != NULL) {
|
||||
[self stop:nil];
|
||||
}
|
||||
@ -1469,6 +1476,18 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pushPendingState {
|
||||
_didPushStateOnce = true;
|
||||
|
||||
if (_state != _pendingState) {
|
||||
_state = _pendingState;
|
||||
|
||||
if (_stateChanged) {
|
||||
_stateChanged(_state, _videoState, _remoteVideoState, _remoteAudioState, _remoteBatteryLevel, _remotePreferredAspectRatio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)controllerStateChanged:(tgcalls::State)state {
|
||||
OngoingCallStateWebrtc callState = OngoingCallStateInitializing;
|
||||
switch (state) {
|
||||
@ -1485,11 +1504,32 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_state != callState) {
|
||||
_state = callState;
|
||||
if (_pendingState != callState) {
|
||||
_pendingState = callState;
|
||||
|
||||
if (_stateChanged) {
|
||||
_stateChanged(_state, _videoState, _remoteVideoState, _remoteAudioState, _remoteBatteryLevel, _remotePreferredAspectRatio);
|
||||
[_pushStateDisposable dispose];
|
||||
_pushStateDisposable = nil;
|
||||
|
||||
bool maybeDelayPush = false;
|
||||
if (!_didPushStateOnce) {
|
||||
maybeDelayPush = false;
|
||||
} else if (callState == OngoingCallStateReconnecting) {
|
||||
maybeDelayPush = true;
|
||||
} else {
|
||||
maybeDelayPush = false;
|
||||
}
|
||||
|
||||
if (!maybeDelayPush) {
|
||||
[self pushPendingState];
|
||||
} else {
|
||||
__weak OngoingCallThreadLocalContextWebrtc *weakSelf = self;
|
||||
_pushStateDisposable = [_queue scheduleBlock:^{
|
||||
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
[strongSelf pushPendingState];
|
||||
} after:1.0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user